diff --git a/.gitignore b/.gitignore index 6627127..342d3ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .eval-workspaces/ +deploy-vertex-e2e.sh +setup-vertex-adc.sh \ No newline at end of file diff --git a/evals/skills/intelliaide/system_prompt.md b/evals/skills/intelliaide/system_prompt.md new file mode 100644 index 0000000..42ff4ee --- /dev/null +++ b/evals/skills/intelliaide/system_prompt.md @@ -0,0 +1,11 @@ +You are an assistant that answers questions about the IntelliAide root-cause analysis pipeline for OpenShift clusters. + +The IntelliAide skill lives at /app/skills/intelliaide/. When answering questions: +1. Read /app/skills/intelliaide/SKILL.md first — it contains the full pipeline specification and all protocol rules +2. For implementation constants (thresholds, path literals), read the relevant Python scripts: + - /app/skills/intelliaide/extract_cluster.py + - /app/skills/intelliaide/perform_rca.py + - /app/skills/intelliaide/analyze_data.py +3. For configuration defaults, read /app/skills/intelliaide/Config/config.json +4. Base every answer strictly on what is in those files — do not rely on prior knowledge +5. If the answer is not in the files, say so clearly diff --git a/evals/skills/intelliaide/test_cases.yaml b/evals/skills/intelliaide/test_cases.yaml new file mode 100644 index 0000000..02add7a --- /dev/null +++ b/evals/skills/intelliaide/test_cases.yaml @@ -0,0 +1,115 @@ +# ── IntelliAide Agent-Level Eval Cases ──────────────────────────────────────── +# +# These test cases verify that an LLM agent can correctly discover and apply the +# IntelliAide SKILL.md specification. They focus on *protocol knowledge* that +# requires reading comprehension — things that unit tests cannot cover because +# they depend on agent reasoning, not script mechanics. +# +# Implementation constants (min_data_files, max_unwrap_depth, max_chunk_tokens, +# data_input_path, etc.) are now validated by standalone functional tests in +# test_functional.py, which exercise the actual scripts deterministically. +# +# ── POSITIVE CASES ──────────────────────────────────────────────────────────── + +- name: priority_tier_count + query: "How many priority tiers does the IntelliAide RCA pipeline run analysis passes for?" + schema: + type: object + properties: + tier_count: + type: integer + enum: [1, 2, 3, 4, 5] + description: "Number of priority tiers (high/medium/low) in the pipeline. Use the 'intelliaide' skill to find this." + required: [tier_count] + expected: + tier_count: 3 + +# Source: SKILL.md line 144 → high_priority_file_selection_max: 20 +- name: high_priority_file_selection_max + query: "According to IntelliAide's file-selection rules in Step 2b, what is the maximum number of high-priority files the orchestrating agent should select?" + schema: + type: object + properties: + max_files: + type: integer + enum: [10, 15, 20, 25, 30] + description: "Upper bound on high-priority file count in Step 2b selection. Use the 'intelliaide' skill to find this." + required: [max_files] + expected: + max_files: 20 + +# Source: SKILL.md line 89 → "SDK's 1 MB message buffer and crashes the session" +- name: sdk_buffer_size_mb + query: >- + IntelliAide's buffer safety rule exists because must-gather bundles are too + large to read inline. According to that rule, what is the size in megabytes + of the SDK message buffer that a direct file read would overflow? + schema: + type: object + properties: + buffer_size_mb: + type: integer + enum: [1, 5, 10, 50, 100] + description: "SDK message buffer size in MB stated in IntelliAide's buffer safety rule. Use the 'intelliaide' skill to find this." + required: [buffer_size_mb] + expected: + buffer_size_mb: 1 + +# ── NEGATIVE CASES ──────────────────────────────────────────────────────────── +# Each describes a concrete action a naive agent (one without IntelliAide) would +# naturally take, then asks what IntelliAide's protocol says about that action. +# These guard against regressions if someone modifies the SKILL.md rules. + +# Naive agent action: run 'cat /data/input/.../current.log' to inspect pod logs. +# Source: SKILL.md lines 88–98 → buffer safety rule, forbidden-command list +- name: naive_cat_bundle_file_crashes_session + query: >- + A naive agent without IntelliAide tries to diagnose an etcd issue by running + 'cat /data/input/namespaces/openshift-etcd/pods/etcd-0/etcd/etcd/logs/current.log' + to inspect the pod logs directly. According to IntelliAide's buffer safety rule, + what does this action do to the orchestrating agent's session? + schema: + type: object + properties: + session_outcome: + type: string + enum: ["crashes_the_session", "returns_empty_string", "truncates_output", "raises_python_exception", "restarts_the_container"] + description: "What happens to the session when a must-gather file is read inline. Use the 'intelliaide' skill to find this." + required: [session_outcome] + expected: + session_outcome: "crashes_the_session" + +# Naive agent action: run 'grep -r error /data/input' across all logs. +# Source: SKILL.md lines 88–98 → explicit forbidden-command list +- name: naive_grep_r_on_bundle_forbidden + query: >- + A naive agent without IntelliAide decides to search for errors across the + must-gather bundle by running 'grep -r error /data/input'. Does IntelliAide's + buffer safety rule permit this command after extract_cluster.py has succeeded? + schema: + type: object + properties: + is_permitted: + type: boolean + description: "Whether grep -r across must-gather bundle files is allowed by IntelliAide's buffer safety rule. Use the 'intelliaide' skill to find this." + required: [is_permitted] + expected: + is_permitted: false + +# Naive agent action: fall back to 'kubectl get pods' / 'oc describe' when a +# pipeline step fails. +# Source: SKILL.md line 325 → "Never fall back to kubectl/oc commands" +- name: kubectl_fallback_forbidden_on_pipeline_failure + query: >- + A step in the IntelliAide pipeline returns an error. A naive agent's instinct + is to fall back to 'kubectl get pods' or 'oc describe' to gather data another + way. Does IntelliAide's error-handling protocol permit this fallback? + schema: + type: object + properties: + fallback_permitted: + type: boolean + description: "Whether kubectl/oc fallback is allowed when an IntelliAide pipeline step fails. Use the 'intelliaide' skill to find this." + required: [fallback_permitted] + expected: + fallback_permitted: false diff --git a/evals/skills/intelliaide/test_functional.py b/evals/skills/intelliaide/test_functional.py new file mode 100644 index 0000000..4217889 --- /dev/null +++ b/evals/skills/intelliaide/test_functional.py @@ -0,0 +1,606 @@ +"""Standalone functional tests for IntelliAide pipeline scripts. + +These tests exercise IntelliAide's Python scripts directly — without an LLM, +without a sandbox container, and without API keys. They follow the Lightspeed +operator team's unit-testing pattern: mock inputs, call the code, verify outputs. + +Run independently (no container / no API keys required): + python3 -m pytest evals/skills/intelliaide/test_functional.py -v + +Or filter by marker alongside the full eval suite: + python3 -m pytest evals/ -v -m unit +""" + +from __future__ import annotations + +import importlib.util +import json +import os +import subprocess +import sys +from pathlib import Path +from typing import Any + +import pytest + +# ── Path constants ─────────────────────────────────────────────────────────── + +_THIS_DIR = Path(__file__).resolve().parent +REPO_ROOT = _THIS_DIR.parents[2] # .../agentic-skills/ +SKILL_DIR = REPO_ROOT / "intelliaide" + + +# ── Import extract_cluster.py directly (zero vendor deps) ─────────────────── + +def _import_skill_script(name: str): + """Import a top-level IntelliAide script by filename (no package needed).""" + path = SKILL_DIR / f"{name}.py" + spec = importlib.util.spec_from_file_location(name, str(path)) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_ec = _import_skill_script("extract_cluster") + + +# ── Vendor-dep availability check (for subprocess-based tests) ─────────────── + +def _vendor_imports_available() -> bool: + """Return True if the vendored DataAnalyzer import chain works.""" + env = {**os.environ, "SKILL_DIR": str(SKILL_DIR), "ORCHESTRATOR_QUIET": "1"} + code = ( + "import os, sys; " + "sd = os.environ['SKILL_DIR']; " + "sys.path.insert(0, os.path.join(sd, 'vendor')); " + "sys.path.insert(0, os.path.join(sd, 'Main-program')); " + "sys.path.insert(0, sd); " + "from data_analyzer import DataAnalyzer; print('ok')" + ) + try: + r = subprocess.run( + [sys.executable, "-c", code], + capture_output=True, text=True, timeout=30, env=env, + ) + return r.returncode == 0 and "ok" in r.stdout + except Exception: + return False + + +_HAS_VENDOR = _vendor_imports_available() +_SKIP_VENDOR = pytest.mark.skipif( + not _HAS_VENDOR, + reason="Vendored deps (drain3 etc.) not importable with this Python", +) + + +# ── Helpers ────────────────────────────────────────────────────────────────── + +def _run_script(script_name: str, args: list[str], timeout: int = 120): + """Run an IntelliAide script via subprocess and return CompletedProcess.""" + cmd = [sys.executable, str(SKILL_DIR / script_name)] + args + return subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=timeout, + env={**os.environ, "ORCHESTRATOR_QUIET": "1"}, + ) + + +def _parse_stdout_json(stdout: str) -> dict[str, Any]: + """Extract the last JSON object from stdout (scripts emit progress on stderr).""" + for line in reversed(stdout.strip().splitlines()): + line = line.strip() + if line.startswith("{"): + return json.loads(line) + raise ValueError(f"No JSON found in stdout:\n{stdout[:500]}") + + +# ── Mock must-gather data generators ──────────────────────────────────────── + +_MOCK_ETCD_LOG = "\n".join( + # 20 repeated high-frequency errors (Drain3 should cluster these) + [ + f"2026-06-01T10:00:{i:02d}.000000Z E | rafthttp: failed to reach " + f"the peer https://etcd-1.openshift-etcd.svc:2380 (connection refused)" + for i in range(20) + ] + # 2 rare errors (unique — Drain3 should flag these) + + [ + "2026-06-01T10:00:25.000000Z E | etcdserver: leader changed from member-a to member-b", + "2026-06-01T10:00:26.000000Z E | etcdserver: request timed out waiting for node response", + ] + # 3 warnings + + [ + "2026-06-01T10:00:27.000000Z W | etcdserver: read-only range request took too long (2.503s)", + "2026-06-01T10:00:28.000000Z W | etcdserver: read-only range request took too long (3.124s)", + "2026-06-01T10:00:29.000000Z W | etcdserver: slow fdatasync took 1.012s", + ] + # 3 info lines + + [ + "2026-06-01T10:00:30.000000Z I | etcdserver: compacted raft log at index 234567", + "2026-06-01T10:00:31.000000Z I | etcdserver: compacted raft log at index 234568", + "2026-06-01T10:00:32.000000Z I | mvcc: store.index: compact 123456", + ] +) + "\n" + + +_MOCK_CLUSTER_OPERATOR_YAML = """\ +apiVersion: config.openshift.io/v1 +kind: ClusterOperator +metadata: + name: etcd +status: + conditions: + - type: Degraded + status: "True" + reason: EtcdMembersDegraded + message: "1 of 3 members are available, etcd-1 is not started" + lastTransitionTime: "2026-06-01T10:00:00Z" + - type: Available + status: "False" + reason: EtcdMembersNotAvailable + message: "Not enough etcd members are available" + lastTransitionTime: "2026-06-01T10:00:00Z" + - type: Progressing + status: "True" + reason: EtcdWaitingForMembers + message: "Waiting for etcd-1 to join" + lastTransitionTime: "2026-06-01T09:55:00Z" +""" + +_MOCK_NODE_YAML = """\ +apiVersion: v1 +kind: Node +metadata: + name: worker-0.example.com +status: + conditions: + - type: Ready + status: "True" + reason: KubeletReady + message: "kubelet is posting ready status" + - type: MemoryPressure + status: "False" + - type: DiskPressure + status: "False" +""" + + +# ── Fixtures ───────────────────────────────────────────────────────────────── + +@pytest.fixture +def mock_must_gather(tmp_path: Path) -> Path: + """Build a minimal mock must-gather bundle (>=3 files) under tmp_path.""" + mg = tmp_path / "data-input" + + log_dir = mg / "namespaces" / "openshift-etcd" / "pods" / "etcd-0" / "etcd" / "logs" + log_dir.mkdir(parents=True) + (log_dir / "current.log").write_text(_MOCK_ETCD_LOG) + + yaml_dir = mg / "cluster-scoped-resources" + yaml_dir.mkdir(parents=True) + (yaml_dir / "clusteroperators.yaml").write_text(_MOCK_CLUSTER_OPERATOR_YAML) + (yaml_dir / "nodes.yaml").write_text(_MOCK_NODE_YAML) + + return mg + + +@pytest.fixture +def job_dir(tmp_path: Path) -> Path: + """Return an empty temporary job directory.""" + d = tmp_path / "job" + d.mkdir() + return d + + +# ═════════════════════════════════════════════════════════════════════════════ +# extract_cluster.py — validation & unwrap logic (no vendor deps) +# ═════════════════════════════════════════════════════════════════════════════ + +@pytest.mark.unit +class TestExtractClusterValidation: + """Unit tests for extract_cluster.py data-source validation.""" + + def test_happy_path_succeeds(self, mock_must_gather, monkeypatch): + monkeypatch.setattr(_ec, "_DATA_INPUT_DIR", mock_must_gather) + cluster_dir, success, error = _ec._check_data_source() + assert success, f"Expected success, got: {error}" + assert Path(cluster_dir).exists() + + def test_nonexistent_mount_fails(self, tmp_path, monkeypatch): + monkeypatch.setattr(_ec, "_DATA_INPUT_DIR", tmp_path / "no-such-dir") + _, success, error = _ec._check_data_source() + assert not success + assert "No data source" in error + + def test_empty_dir_fails(self, tmp_path, monkeypatch): + empty = tmp_path / "data-input" + empty.mkdir() + monkeypatch.setattr(_ec, "_DATA_INPUT_DIR", empty) + _, success, error = _ec._check_data_source() + assert not success + assert "empty" in error.lower() + + def test_too_few_files_fails(self, tmp_path, monkeypatch): + sparse = tmp_path / "data-input" + sparse.mkdir() + (sparse / "only_one.yaml").write_text("key: value") + monkeypatch.setattr(_ec, "_DATA_INPUT_DIR", sparse) + _, success, error = _ec._check_data_source() + assert not success + assert "too few" in error.lower() + + def test_exactly_three_files_passes(self, tmp_path, monkeypatch): + di = tmp_path / "data-input" + di.mkdir() + for i in range(3): + (di / f"file{i}.yaml").write_text(f"k: {i}") + monkeypatch.setattr(_ec, "_DATA_INPUT_DIR", di) + _, success, _ = _ec._check_data_source() + assert success + + def test_lost_and_found_ignored(self, tmp_path, monkeypatch): + di = tmp_path / "data-input" + di.mkdir() + (di / "lost+found").mkdir() + monkeypatch.setattr(_ec, "_DATA_INPUT_DIR", di) + entries = _ec._real_entries(di) + assert all(e.name != "lost+found" for e in entries) + + def test_hidden_files_ignored(self, tmp_path, monkeypatch): + di = tmp_path / "data-input" + di.mkdir() + (di / ".hidden").write_text("secret") + (di / "visible.yaml").write_text("k: v") + monkeypatch.setattr(_ec, "_DATA_INPUT_DIR", di) + entries = _ec._real_entries(di) + assert all(not e.name.startswith(".") for e in entries) + + +@pytest.mark.unit +class TestExtractClusterUnwrap: + """Unit tests for single-child wrapper directory unwrapping.""" + + def test_unwraps_through_single_child(self, tmp_path): + deep = tmp_path / "w1" / "w2" / "data" + deep.mkdir(parents=True) + (deep / "f1.yaml").write_text("a: 1") + (deep / "f2.yaml").write_text("b: 2") + result = _ec._unwrap_single_child_dirs(tmp_path) + assert result == deep + + def test_stops_at_max_depth(self, tmp_path): + current = tmp_path + for i in range(6): + current = current / f"level{i}" + current.mkdir(parents=True) + (current / "data.yaml").write_text("x: 1") + result = _ec._unwrap_single_child_dirs(tmp_path) + assert result.name == f"level{_ec._MAX_UNWRAP_DEPTH - 1}" + + def test_stops_when_fanout(self, tmp_path): + (tmp_path / "wrapper" / "dir_a").mkdir(parents=True) + (tmp_path / "wrapper" / "dir_b").mkdir(parents=True) + (tmp_path / "wrapper" / "dir_a" / "f.yaml").write_text("") + (tmp_path / "wrapper" / "dir_b" / "f.yaml").write_text("") + result = _ec._unwrap_single_child_dirs(tmp_path) + assert result.name == "wrapper" + + def test_flat_dir_returns_self(self, tmp_path): + (tmp_path / "a.yaml").write_text("") + (tmp_path / "b.yaml").write_text("") + result = _ec._unwrap_single_child_dirs(tmp_path) + assert result == tmp_path + + +@pytest.mark.unit +class TestExtractClusterMain: + """Integration tests for extract_cluster.py main() flow.""" + + def test_state_json_created_on_success( + self, mock_must_gather, tmp_path, monkeypatch, + ): + monkeypatch.setattr(_ec, "_DATA_INPUT_DIR", mock_must_gather) + monkeypatch.setattr(_ec, "_JOB_BASE", str(tmp_path / "jobs")) + monkeypatch.setattr(sys, "argv", [ + "extract_cluster.py", "--query", "etcd pods not ready", + ]) + + _ec.main() # exits 0 on success + + job_dirs = list((tmp_path / "jobs").iterdir()) + assert len(job_dirs) == 1 + state = json.loads((job_dirs[0] / "state.json").read_text()) + assert state["query"] == "etcd pods not ready" + assert state["mode"] == "must-gather" + assert "cluster_dir" in state + + def test_state_json_created_on_failure(self, tmp_path, monkeypatch): + monkeypatch.setattr(_ec, "_DATA_INPUT_DIR", tmp_path / "nope") + monkeypatch.setattr(_ec, "_JOB_BASE", str(tmp_path / "jobs")) + monkeypatch.setattr(sys, "argv", [ + "extract_cluster.py", "--query", "test", + ]) + + with pytest.raises(SystemExit) as exc: + _ec.main() + assert exc.value.code == 1 + + job_dirs = list((tmp_path / "jobs").iterdir()) + assert len(job_dirs) == 1 + state = json.loads((job_dirs[0] / "state.json").read_text()) + assert state["query"] == "test" + + +# ═════════════════════════════════════════════════════════════════════════════ +# analyze_data.py — ML pipeline / Drain3 log deduplication (needs vendor) +# ═════════════════════════════════════════════════════════════════════════════ + +@pytest.mark.unit +@_SKIP_VENDOR +class TestAnalyzeData: + """Functional tests for analyze_data.py — Drain3 log deduplication & ML.""" + + def test_log_deduplication(self, mock_must_gather, job_dir): + """Drain3 should compress 28 log lines into categorized error templates. + + The mentor's requirement: 'to test the Drain3 algorithm/ML we can give + input as a few long lines which some redundant lines and make sure it is + able to return the relevant logs.' + """ + log_path = str( + mock_must_gather / "namespaces" / "openshift-etcd" + / "pods" / "etcd-0" / "etcd" / "logs" / "current.log" + ) + + self._write_job_state(job_dir, mock_must_gather) + self._write_file_selection( + job_dir, mock_must_gather, + high=[{"original": log_path, "resolved": log_path, + "found": True, "reason": "etcd pod log"}], + ) + + result = _run_script("analyze_data.py", [ + "--job-dir", str(job_dir), "--priority", "high", + ]) + assert result.returncode == 0, self._fmt_err(result) + + output = _parse_stdout_json(result.stdout) + assert output["log_files"] >= 1, "Expected at least one log file processed" + + analysis = json.loads(Path(output["analysis_path"]).read_text()) + assert analysis["priority"] == "high" + assert isinstance(analysis["log_entries"], list) + + if analysis["log_entries"]: + total_output_lines = sum( + entry["content"].count("\n") for entry in analysis["log_entries"] + ) + input_line_count = _MOCK_ETCD_LOG.strip().count("\n") + 1 + assert total_output_lines < input_line_count, ( + f"Drain3 should deduplicate: {total_output_lines} output lines " + f">= {input_line_count} input lines" + ) + + def test_empty_priority_produces_empty_analysis(self, job_dir, tmp_path): + """An empty file-selection tier should produce a valid empty analysis.""" + mg = tmp_path / "empty-mg" + mg.mkdir() + self._write_job_state(job_dir, mg) + self._write_file_selection(job_dir, mg, high=[]) + + result = _run_script("analyze_data.py", [ + "--job-dir", str(job_dir), "--priority", "high", + ]) + assert result.returncode == 0, self._fmt_err(result) + + output = _parse_stdout_json(result.stdout) + assert output["yaml_files"] == 0 + assert output["log_files"] == 0 + assert output["log_entries"] == 0 + + analysis = json.loads(Path(output["analysis_path"]).read_text()) + assert analysis["log_entries"] == [] + assert analysis["yaml_errors"] == {} + + # ── Helpers ────────────────────────────────────────────────────────────── + + @staticmethod + def _write_job_state(job_dir: Path, cluster_dir: Path): + (job_dir / "state.json").write_text(json.dumps({ + "job_id": "func0001", + "job_dir": str(job_dir), + "cluster_dir": str(cluster_dir), + "query": "etcd pods not ready", + "mode": "must-gather", + })) + + @staticmethod + def _write_file_selection( + job_dir: Path, cluster_dir: Path, + high: list | None = None, + medium: list | None = None, + low: list | None = None, + ): + (job_dir / "file_selection.json").write_text(json.dumps({ + "query": "etcd pods not ready", + "cluster_dir": str(cluster_dir), + "problem_category": "etcd", + "high": high or [], + "medium": medium or [], + "low": low or [], + })) + + @staticmethod + def _fmt_err(result) -> str: + return ( + f"analyze_data.py exited {result.returncode}\n" + f"--- stdout ---\n{result.stdout[:2000]}\n" + f"--- stderr ---\n{result.stderr[:2000]}" + ) + + +# ═════════════════════════════════════════════════════════════════════════════ +# perform_rca.py — chunking & reduce phases (needs vendor) +# ═════════════════════════════════════════════════════════════════════════════ + +@pytest.mark.unit +@_SKIP_VENDOR +class TestPerformRcaChunks: + """Tests for perform_rca.py --mode chunks (prompt chunking).""" + + def test_creates_manifest_and_chunk_files(self, job_dir): + """Given log entries, chunks mode should produce chunk prompts + manifest.""" + self._setup_job(job_dir, log_entries=[{ + "file": "current_RareError.txt", + "content": ( + "E | etcdserver: leader changed from member-a to member-b\n" + "E | etcdserver: request timed out waiting for node response\n" + ), + "original_size": 5000, + }]) + + result = _run_script("perform_rca.py", [ + "--job-dir", str(job_dir), "--priority", "high", + ]) + assert result.returncode == 0, self._fmt_err(result) + + output = _parse_stdout_json(result.stdout) + assert output["mode"] == "chunks" + assert output["chunk_count"] >= 1 + assert output["has_medium"] is True + assert output["has_low"] is False + + manifest = json.loads(Path(output["manifest_path"]).read_text()) + assert manifest["chunk_count"] == output["chunk_count"] + for chunk_info in manifest["chunk_files"]: + chunk_path = Path(chunk_info["path"]) + assert chunk_path.exists(), f"Missing chunk file: {chunk_path}" + assert chunk_info["estimated_tokens"] > 0 + + def test_empty_analysis_gives_zero_chunks(self, job_dir): + """No data at all should produce chunk_count=0.""" + self._setup_job(job_dir, log_entries=[], yaml_errors={}) + + result = _run_script("perform_rca.py", [ + "--job-dir", str(job_dir), "--priority", "high", + ]) + assert result.returncode == 0 + assert _parse_stdout_json(result.stdout)["chunk_count"] == 0 + + # ── Helpers ────────────────────────────────────────────────────────────── + + @staticmethod + def _setup_job( + job_dir: Path, + log_entries: list | None = None, + yaml_errors: dict | None = None, + priority: str = "high", + ): + (job_dir / "state.json").write_text(json.dumps({ + "job_id": "func0002", + "job_dir": str(job_dir), + "cluster_dir": "/tmp/fake-cluster", + "query": "etcd pods are not ready after node reboot", + "mode": "must-gather", + })) + (job_dir / f"analysis_{priority}.json").write_text(json.dumps({ + "priority": priority, + "yaml_errors": yaml_errors if yaml_errors is not None else {}, + "log_entries": log_entries if log_entries is not None else [], + "yaml_files": 0, + "log_files": 1 if log_entries else 0, + "failed_files": [], + })) + (job_dir / "file_selection.json").write_text(json.dumps({ + "query": "etcd pods are not ready", + "cluster_dir": "/tmp/fake-cluster", + "high": [{"original": "etcd.log", "resolved": "etcd.log", + "found": True, "reason": "test"}], + "medium": [{"original": "api.log", "resolved": "api.log", + "found": True, "reason": "test"}], + "low": [], + })) + + @staticmethod + def _fmt_err(result) -> str: + return ( + f"perform_rca.py exited {result.returncode}\n" + f"--- stdout ---\n{result.stdout[:2000]}\n" + f"--- stderr ---\n{result.stderr[:2000]}" + ) + + +@pytest.mark.unit +@_SKIP_VENDOR +class TestPerformRcaReduce: + """Tests for perform_rca.py --mode reduce (hierarchical summary batching).""" + + def test_single_summary_is_final(self, job_dir): + """One summary file → single batch with is_final=true.""" + self._write_state(job_dir) + summary = job_dir / "chunk_summary_high_1.md" + summary.write_text("## RCA Summary\nThe etcd leader election timed out.\n") + + result = _run_script("perform_rca.py", [ + "--job-dir", str(job_dir), "--priority", "high", + "--mode", "reduce", "--level", "1", + "--summary-files", str(summary), + ]) + assert result.returncode == 0 + + output = _parse_stdout_json(result.stdout) + assert output["mode"] == "reduce" + assert output["is_final"] is True + assert output["batch_count"] == 1 + + manifest = json.loads(Path(output["manifest_path"]).read_text()) + assert len(manifest["batches"]) == 1 + + def test_large_summaries_split_into_batches(self, job_dir): + """Summaries exceeding the token budget should be split.""" + self._write_state(job_dir) + + # ~85k tokens each (80k budget → can't fit two in one batch) + large_text = "etcd leader election timed out " * 30000 + summaries = [] + for i in range(3): + p = job_dir / f"chunk_summary_high_{i + 1}.md" + p.write_text(large_text) + summaries.append(str(p)) + + result = _run_script("perform_rca.py", [ + "--job-dir", str(job_dir), "--priority", "high", + "--mode", "reduce", "--level", "1", + "--summary-files", *summaries, + ]) + assert result.returncode == 0 + + output = _parse_stdout_json(result.stdout) + assert output["batch_count"] >= 2, "Expected batching due to token budget" + assert output["is_final"] is False + + def test_missing_summary_files_exits_with_error(self, job_dir): + """Reduce mode without --summary-files should exit 1 with error JSON.""" + self._write_state(job_dir) + + result = _run_script("perform_rca.py", [ + "--job-dir", str(job_dir), "--priority", "high", + "--mode", "reduce", + ]) + assert result.returncode == 1 + + output = _parse_stdout_json(result.stdout) + assert "error" in output + + @staticmethod + def _write_state(job_dir: Path): + (job_dir / "state.json").write_text(json.dumps({ + "job_id": "func0003", + "job_dir": str(job_dir), + "cluster_dir": "/tmp/fake", + "query": "etcd pods not ready", + "mode": "must-gather", + })) diff --git a/evals/workspace/skills/intelliaide b/evals/workspace/skills/intelliaide new file mode 120000 index 0000000..dbd8bf7 --- /dev/null +++ b/evals/workspace/skills/intelliaide @@ -0,0 +1 @@ +../../../intelliaide \ No newline at end of file diff --git a/intelliaide/Config/config.json b/intelliaide/Config/config.json new file mode 100644 index 0000000..d90addd --- /dev/null +++ b/intelliaide/Config/config.json @@ -0,0 +1,16 @@ +{ + "must_gather_docs_dir": "DataSource", + "claude": { + "max_chunk_tokens": 80000 + }, + "agent": { + "max_iterations": 25, + "max_deepening_rounds": 3 + }, + "notes": { + "must_gather_docs_dir": "Path to must-gather documentation directory (MUST_GATHER_*.md files). Relative paths are resolved from the project root. Default: DataSource", + "max_chunk_tokens": "RCA chunking budget for large must-gathers (default 80000). Increase if the orchestrating LLM supports a larger context window.", + "max_iterations": "Maximum tool-call iterations for the ReAct agent loop (default: 25)", + "max_deepening_rounds": "Maximum deepening rounds HIGH -> MEDIUM -> LOW (default: 3)" + } +} diff --git a/intelliaide/Config/json_config.json b/intelliaide/Config/json_config.json new file mode 100644 index 0000000..a568389 --- /dev/null +++ b/intelliaide/Config/json_config.json @@ -0,0 +1,39 @@ +{ + "error_detection": { + "error_array_indicators": { + "dropped": "failed_connection", + "failed": "failure", + "error": "error", + "unhealthy": "health_issue", + "down": "unavailable", + "unreachable": "connectivity", + "degraded": "degradation", + "invalid": "validation", + "timeout": "timeout", + "rejected": "rejection", + "stale": "outdated" + }, + "error_state_values": [ + "firing", + "down", + "failed", + "error", + "unhealthy", + "degraded", + "unavailable", + "unreachable", + "critical" + ], + "state_field_names": [ + "state", + "status", + "health", + "condition" + ], + "exclude_patterns": [ + "watchdog", + "test", + "canary" + ] + } + } \ No newline at end of file diff --git a/intelliaide/Config/log_config.json b/intelliaide/Config/log_config.json new file mode 100644 index 0000000..fbed090 --- /dev/null +++ b/intelliaide/Config/log_config.json @@ -0,0 +1,50 @@ +{ + "pipeline": { + "use_chunk_pipeline": null, + "file_size_threshold_mb": 50.0 + }, + "chunk_settings": { + "chunk_size": 10000, + "parallel_chunks": true, + "chunk_workers": null, + "min_chunks_for_parallel": 3 + }, + "drain3_settings": { + "batch_size": 1000, + "similarity_threshold": 0.3, + "depth": 4, + "max_children": 100 + }, + "output": { + "output_dir": null, + "levels_to_process": ["E", "W", "I", "UNKNOWN"] + }, + "output_flags": { + "include_rare_errors": true, + "include_highfreq_errors": true, + "include_warnings": true, + "include_information": true, + "include_unknown": true, + "include_config_changes": true + }, + "frequency_settings": { + "high_freq_cumulative_threshold": 0.80, + "enable_tiered_error_output": true, + "rare_absolute_max_size": null, + "rare_max_lines_per_template": 15 + }, + "preprocessing": { + "remove_timestamps": true, + "timestamp_patterns": [ + "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z?\\s*", + "^\\d{4}-\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?\\s*", + "^([IWE])\\d{4}\\s+\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?\\s*", + "^(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s+\\d{1,2}\\s+\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?\\s+\\S+\\s+", + "\\b\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?\\b" + ] + }, + "logging": { + "verbose": true, + "print_progress": true + } +} diff --git a/intelliaide/Config/yaml_processing.yaml b/intelliaide/Config/yaml_processing.yaml new file mode 100644 index 0000000..f633316 --- /dev/null +++ b/intelliaide/Config/yaml_processing.yaml @@ -0,0 +1,176 @@ +# config/yaml_processing.yaml + + +# CHUNKING CONFIGURATION + +chunking: + # Indentation settings + max_items_section_indent: 2 # Max indent to recognize 'items:' as list container + list_item_indent_offset: 2 # Expected indent after 'items:' + indent_tolerance: 2 # Tolerance when matching list item indents + top_level_indent: 0 # Indent that marks top-level keys + + # What triggers a new chunk + chunk_separators: + - "---" # YAML document separator + + # Keys that indicate list container (case-insensitive) + list_container_keys: + - "items" + - "objects" # Some CRDs use 'objects' + + +# CRITICAL FIELDS CONFIGURATION + +critical_fields: + # Default fields to extract when no specific config exists + default_paths: + - "metadata.name" + - "metadata.namespace" + - "status.conditions" + - "status.phase" + + # File-specific configurations + file_specific: + # Cluster operators + clusteroperators.yaml: + paths: + - "metadata.name" + - "status.conditions" + - "status.versions" + + # Cluster versions + clusterversions.yaml: + paths: + - "metadata.name" + - "status.history" + - "status.conditions" + - "status.desired.version" + + # Networking files + networks.yaml: + paths: + - "metadata.name" + - "spec.clusterNetwork" + - "spec.serviceNetwork" + - "spec.networkType" + - "status.conditions" + - "status.clusterNetworkMTU" + + ingresses.yaml: + paths: + - "metadata.name" + - "spec.domain" + - "spec.loadBalancer" + - "status.componentRoutes[].conditions" + - "status.defaultPlacement" + + events.yaml: + paths: + - "metadata.namespace" + - "type" + - "reason" + - "message" + - "count" + - "involvedObject.kind" + - "involvedObject.name" + + +# ERROR PATTERN DETECTION CONFIGURATION + +error_patterns: + # Verbose error patterns for rare anomaly detection + verbose_patterns: + - "not progressing" + - "not available" + - "not ready" + - "not healthy" + - "halted" + - "stopped" + - "paused" + - "suspended" + - "blocked" + - "broken" + - "degraded" + - "unhealthy" + - "unavailable" + - "down" + - "timeout" + - "timed out" + - "expired" + - "unable to reach" + - "cannot connect" + - "connection refused" + - "connection failed" + - "out of memory" + - "out of disk" + - "resource exhausted" + - "quota exceeded" + - "missing" + - "not found" + - "not exist" + - "does not exist" + - "denied" + - "forbidden" + - "unauthorized" + - "access denied" + - "permission denied" + - "rejected" + - "not allowed" + - "stuck" + - "hung" + - "deadlock" + - "waiting" + - "pending too long" + - "retry failed" + - "retries exhausted" + - "max retries" + + # Explicit error keywords (high priority) + explicit_keywords: + - "error" + - "failed" + - "failure" + - "exception" + - "critical" + - "fatal" + + # Field names to exclude from error detection (metadata/config, not errors) + excluded_key_patterns: + - "failure-domain" # Topology labels (AWS region/zone) + - "topology.kubernetes.io" # Kubernetes topology labels + - "node.kubernetes.io" # Node labels/annotations + - "failurepolicy" # Webhook failure policy config + +# CLASSIFICATION THRESHOLDS + +classification: + # Percentage of objects needed to be considered "common" (Majority Error) + # Objects with error patterns below this are "Error" (rare anomaly) + common_pattern_threshold: 0.30 # 30% of objects + + # Percentage needed to be considered "Majority" (normal/healthy) + majority_threshold: 0.10 # 10% frequency + + # Low frequency threshold for "Error" classification + error_threshold: 0.05 # 5% frequency + +# OUTPUT CONTROL FLAGS + +output_flags: + # Which classification categories to include in aggregated output JSON + include_errors: true # Rare pattern errors + include_majority_errors: true # High frequency errors + include_config_changes: true # Configuration changes + include_normal_reports: false # Normal/healthy objects + + # Optional: Limit instances per pattern (0 = unlimited, return all) + max_instances_per_pattern: 0 # For future use with large clusters + + # Summary information + include_summary: true # Add summary counts per file + +# Base directory configuration +base_directory: + # Path to must-gather root (leave empty to use current directory) + must_gather_path: "" # Empty = use current working directory diff --git a/intelliaide/DataSource/MUST_GATHER_DOCUMENTATION_README.md b/intelliaide/DataSource/MUST_GATHER_DOCUMENTATION_README.md new file mode 100644 index 0000000..5eca6a7 --- /dev/null +++ b/intelliaide/DataSource/MUST_GATHER_DOCUMENTATION_README.md @@ -0,0 +1,113 @@ +--- +# Must-Gather Documentation Suite + +This documentation suite provides comprehensive information about the OpenShift must-gather tool's output structure and how to use it for problem diagnosis. This guide ensures consistent LLM routing and reduces variance in file path recommendations across different models. + +## Documentation Files + +### 1. **MUST_GATHER_STRUCTURE.md** +**Purpose**: Detailed description of the directory structure and file purposes + +**Contents**: +- Complete directory tree with descriptions +- File formats and contents +- Collection process overview +- Resource types collected + +**Use When**: You need to understand what data is collected and where it's stored + +--- + +### 2. **MUST_GATHER_ROUTING_GUIDE.md** +**Purpose**: Problem-based routing guide for LLM-assisted diagnosis + +**Contents**: +- 15 problem categories with detailed mappings +- Keywords for each problem type +- Primary directories and files for each category +- Related resources cross-references +- Routing decision tree +- Quick reference table + +**Use When**: You have a problem statement and need to identify relevant files + +**Problem Categories Covered**: +1. API Server & Authentication Issues +2. Cluster Operator & Control Plane Issues +3. Networking & Connectivity Issues +4. Storage & Volume Issues +5. Node & Machine Configuration Issues +6. Pod & Container Issues +7. Performance & Resource Issues +8. Security & Audit Issues +9. Service Mesh & Istio Issues +10. Monitoring & Metrics Issues +11. Windows Node Issues +12. Platform-Specific Issues (vSphere, ARO) +13. etcd Issues +14. Storage Version Migration Issues +15. IPsec & Network Security Issues + +--- + +### 3. **MUST_GATHER_INDEX.md** +**Purpose**: Quick reference index for fast keyword-based lookups + +**Contents**: +- Directory structure quick index +- Keyword to directory mapping +- File type patterns +- Common problem patterns +- Search strategy for LLMs + +**Use When**: You need quick lookups or keyword matching + +--- + +## Standardization Rules for LLM Outputs + +### Path Format Standards + +1. **Use actual on-disk file extensions**: `.log`, `.yaml`, `.json` for uncompressed files; `.log.gz`, `.gz`, or `.tar.gz` for compressed files +2. **Wildcard substitution rules**: + - `` for actual namespace names + - `` for actual pod names + - `` for actual node names + - `*` only when multiple files of same type exist +3. **Consistent path separators**: Always use forward slashes `/` +4. **Base path convention**: All paths relative to `//` + +### Mandatory File Inclusion Rules + +1. **For pod issues**: MUST include both `current.log` AND `previous.log` from container logs +2. **For operator issues**: MUST include both pod logs AND events from the operator namespace +3. **For etcd issues**: MUST include ALL files from `etcd_info/` directory +4. **For networking issues**: MUST include both pod logs AND network connectivity checks +5. **For API server issues**: MUST include both audit logs AND pod logs AND priority/fairness data + +### Cross-Component Dependencies + +When analyzing these components, ALWAYS include related components: + +- **etcd issues** → Include API server logs and cluster operator status +- **Networking issues** → Include container runtime logs and node service logs +- **Storage issues** → Include both CSI driver logs and pod mount logs +- **Performance issues** → Include both metrics data and node-level diagnostics +- **Operator issues** → Include both operator namespace and cluster operator status + +--- + +## Key Concepts + +### Directory Organization +- **Base Path**: All data under `//` +- **Namespace-based**: Most resources organized by namespace +- **Cluster-scoped**: Cluster-level resources in `cluster-scoped-resources/` +- **Feature-specific**: Some directories only exist if features enabled + +### File Types +- **Logs**: `.log` for most logs; some compressed as `.log.gz` or `.gz` +- **Status**: `.json` - Health, status, configuration state +- **Config**: `.yaml`, `.config` - Resource definitions +- **Metrics**: `.openmetrics` - Performance and resource metrics +- **Archives**: `.tar.gz` - Compressed archives diff --git a/intelliaide/DataSource/MUST_GATHER_INDEX.md b/intelliaide/DataSource/MUST_GATHER_INDEX.md new file mode 100644 index 0000000..7d200d3 --- /dev/null +++ b/intelliaide/DataSource/MUST_GATHER_INDEX.md @@ -0,0 +1,110 @@ +--- +# Must-Gather Directory and File Index + +Quick reference index for LLM routing. Use this for fast lookups of directories and files by problem keywords. + +## Directory Structure Quick Index + +User supplies **``** (e.g. `must-gather-Prashanth-Testcase-failure`). Under it there is **one folder ``** (any name, e.g. `quay-content`). Under that folder: + +``` +/ +└── / # one child, any name (e.g. quay-content) + ├── version # Must-gather version info + ├── event-filter.html # ★ All events in searchable web UI + │ + ├── ── CLUSTER-WIDE RESOURCES ── + ├── cluster-scoped-resources/ # All cluster-level objects + │ ├── config.openshift.io/ # ★ PRIMARY: Cluster configuration + │ │ ├── clusteroperators.yaml # ★ START HERE: Operator health + │ │ ├── clusterversions.yaml # Version, upgrade status, history + │ │ └── ... + │ └── core/ # Kubernetes core resources + │ └── nodes/ # ★ Per-node YAML (status, taints, labels) + │ + ├── ── NAMESPACE RESOURCES ── + ├── namespaces// # Per-namespace data + │ ├── core/events.yaml # ★ CRITICAL: All events + │ ├── pods///logs/ + │ │ ├── current.log # ★ Active container logs + │ │ └── previous.log # ★ Crashed container logs + │ └── ... + │ + ├── host_service_logs/ # ★ Systemd service logs + │ ├── masters/kubelet_service.log + │ ├── masters/crio_service.log + │ └── workers/... + │ + ├── etcd_info/ # Etcd cluster health + │ ├── endpoint_health.json + │ ├── member_list.json + │ ├── alarm_list.json + │ └── object_count.json + │ + ├── network_logs/ # Network-specific data + │ ├── leader_ovnnb_status + │ ├── leader_ovnsb_status + │ └── ipsec/ (conditional) + │ + └── pod_network_connectivity_check/ + └── podnetworkconnectivitychecks.yaml +``` + +--- + +## Decision Matrix: Problem Type → Primary Files + +### 1. Cluster Health / Operator Issues +1. `cluster-scoped-resources/config.openshift.io/clusteroperators.yaml` +2. `cluster-scoped-resources/config.openshift.io/clusterversions.yaml` +3. `namespaces/openshift-/core/events.yaml` +4. `namespaces/openshift-/pods/*///logs/current.log` + +### 2. Pod / Container Issues +1. `namespaces//core/events.yaml` +2. `namespaces//core/pods.yaml` +3. `namespaces//pods////logs/current.log` +4. `namespaces//pods////logs/previous.log` +5. `host_service_logs/masters|workers/crio_service.log` + +### 3. Node Issues +1. `cluster-scoped-resources/core/nodes/.yaml` +2. `nodes//dmesg` +3. `host_service_logs/masters|workers/kubelet_service.log` + +### 4. Networking Issues +1. `cluster-scoped-resources/config.openshift.io/networks.yaml` +2. `namespaces/openshift-ovn-kubernetes/pods/*/logs/current.log` +3. `pod_network_connectivity_check/podnetworkconnectivitychecks.yaml` +4. `network_logs/leader_ovnnb_status`, `leader_ovnsb_status` + +### 5. Storage Issues +1. `cluster-scoped-resources/storage.k8s.io/storageclasses.yaml` +2. `namespaces//core/persistentvolumeclaims.yaml` +3. `cluster-scoped-resources/storage.k8s.io/volumeattachments/` + +### 6. API Server Issues +1. `namespaces/openshift-kube-apiserver/pods/*/logs/current.log` +2. `etcd_info/endpoint_health.json` +3. `etcd_info/alarm_list.json` + +### 7. Etcd Issues +1. `etcd_info/endpoint_health.json` +2. `etcd_info/member_list.json` +3. `etcd_info/alarm_list.json` +4. `namespaces/openshift-etcd/pods/*/logs/current.log` + +### 8. Storage Version Migration +1. `namespaces/openshift-kube-storage-version-migrator/core/events.yaml` +2. `namespaces/openshift-kube-storage-version-migrator/pods/*/logs/current.log` +3. `cluster-scoped-resources/migration.k8s.io/storageversionmigrations.yaml` + +--- + +## Mandatory Inclusion Rules + +- **Always include both current.log AND previous.log** for pod logs +- **Always include both pod logs AND events** for namespace issues +- **etcd issues**: Always include API server logs too +- **API Server issues**: Always include etcd health +- **Network issues**: Always include connectivity checks diff --git a/intelliaide/DataSource/MUST_GATHER_ROUTING_GUIDE.md b/intelliaide/DataSource/MUST_GATHER_ROUTING_GUIDE.md new file mode 100644 index 0000000..9bf81c3 --- /dev/null +++ b/intelliaide/DataSource/MUST_GATHER_ROUTING_GUIDE.md @@ -0,0 +1,209 @@ +--- +# Must-Gather Routing Guide for LLM Problem Diagnosis + +This document provides a structured mapping of problem types to relevant directories and files in the must-gather output. + +**Path convention**: All paths are under `//` + +**Path substitution rules**: +- `` = actual namespace name (e.g. `openshift-etcd`) +- `` = actual pod name +- `` = actual node name +- `*` = wildcard for any matching file/directory name + +--- + +## 1. API Server & Authentication Issues + +**Keywords**: `api server`, `apiserver`, `authentication`, `authorization`, `oauth`, `kube-apiserver`, `401`, `403`, `throttling` + +### Primary Files (MANDATORY): +- `audit_logs/kube-apiserver/-audit.log` (Conditional) +- `namespaces/openshift-kube-apiserver/pods////logs/current.log` +- `namespaces/openshift-kube-apiserver/pods////logs/previous.log` +- `namespaces/openshift-kube-apiserver/core/events.yaml` +- `namespaces/openshift-kube-apiserver/pods//kube-apiserver/kube-apiserver/api_priority_and_fairness/queues` +- `static-pods/kube-apiserver/-startup.log.gz` (compressed) +- `static-pods/kube-apiserver/-termination.log.gz` (compressed) + +### Cross-Component Dependencies: +- `etcd_info/endpoint_health.json` +- `cluster-scoped-resources/config.openshift.io/apiservers.yaml` + +--- + +## 2. Cluster Operator & Control Plane Issues + +**Keywords**: `cluster operator`, `operator`, `degraded`, `progressing`, `cluster version`, `upgrade` + +### Primary Files (MANDATORY): +- `cluster-scoped-resources/config.openshift.io/clusteroperators.yaml` +- `cluster-scoped-resources/config.openshift.io/clusterversions.yaml` +- `namespaces/openshift-cluster-version/core/events.yaml` +- `namespaces/openshift-cluster-version/pods//logs/current.log` +- `namespaces/openshift-cluster-version/pods//logs/previous.log` +- `namespaces//pods//logs/current.log` +- `namespaces//pods//logs/previous.log` +- `namespaces//core/events.yaml` + +--- + +## 3. Networking & Connectivity Issues + +**Keywords**: `network`, `connectivity`, `dns`, `ovn`, `sdn`, `OVNKubernetesResourceRetryFailure`, `bz-networking` + +### Primary Files (MANDATORY): +- `pod_network_connectivity_check/podnetworkconnectivitychecks.yaml` +- `network_logs/cluster_scale` +- `cluster-scoped-resources/config.openshift.io/networks.yaml` +- `monitoring/alertmanager/` (for OVN alerts) +- `monitoring/prometheus/` (for OVN alerts) +- `network_logs/leader_ovnnb_status` +- `network_logs/leader_ovnsb_status` +- `network_logs/ovn_kubernetes_top_pods` +- `namespaces/openshift-ovn-kubernetes/pods//logs/current.log` +- `namespaces/openshift-ovn-kubernetes/pods//logs/previous.log` +- `namespaces/openshift-ovn-kubernetes/core/events.yaml` +- `host_service_logs/masters/NetworkManager_service.log` +- `host_service_logs/workers/NetworkManager_service.log` + +--- + +## 4. Storage & Volume Issues + +**Keywords**: `storage`, `volume`, `pvc`, `pv`, `csi`, `mount`, `attach` + +### Primary Files (MANDATORY): +- `cluster-scoped-resources/storage.k8s.io/storageclasses.yaml` +- `cluster-scoped-resources/storage.k8s.io/volumeattachments/.yaml` +- `namespaces//core/persistentvolumeclaims.yaml` +- `namespaces/openshift-cluster-csi-drivers/pods//logs/current.log` +- `namespaces/openshift-cluster-csi-drivers/pods//logs/previous.log` +- `cluster-scoped-resources/storage.k8s.io/csidrivers.yaml` +- `cluster-scoped-resources/storage.k8s.io/csinodes.yaml` + +--- + +## 5. Node & Machine Configuration Issues + +**Keywords**: `node`, `machine config`, `mco`, `not ready`, `kubelet` + +### Primary Files (MANDATORY): +- `cluster-scoped-resources/core/nodes/.yaml` +- `nodes//_logs_kubelet.gz` (compressed) +- `host_service_logs/masters/kubelet_service.log` +- `host_service_logs/workers/kubelet_service.log` +- `namespaces/openshift-machine-config-operator/pods//logs/current.log` +- `cluster-scoped-resources/machineconfiguration.openshift.io/machineconfigs.yaml` +- `cluster-scoped-resources/machineconfiguration.openshift.io/machineconfigpools.yaml` + +--- + +## 6. Pod & Container Issues + +**Keywords**: `pod`, `container`, `crash`, `restart`, `pending`, `image pull`, `OOMKilled` + +### Primary Files (MANDATORY): +- `namespaces//pods////logs/current.log` +- `namespaces//pods////logs/previous.log` +- `namespaces//core/events.yaml` +- `namespaces//core/pods.yaml` +- `host_service_logs/masters/crio_service.log` +- `host_service_logs/workers/crio_service.log` + +--- + +## 7. Performance & Resource Issues + +**Keywords**: `performance`, `cpu`, `memory`, `slow`, `latency`, `high load` + +### Primary Files (MANDATORY): +- `monitoring/metrics/metrics.openmetrics` (Conditional) +- `monitoring/prometheus/status/config.json` +- `monitoring/alertmanager/status.json` +- `nodes//` (all node performance data) +- `namespaces/openshift-kube-apiserver/pods//kube-apiserver/kube-apiserver/api_priority_and_fairness/queues` +- `etcd_info/object_count.json` + +--- + +## 13. etcd Issues + +**Keywords**: `etcd`, `quorum`, `alarm`, `etcdGRPCRequestsSlow`, `NOSPACE`, `CORRUPT` + +### Primary Files (MANDATORY): +- `etcd_info/endpoint_health.json` +- `etcd_info/endpoint_status.json` +- `etcd_info/member_list.json` +- `etcd_info/alarm_list.json` +- `etcd_info/object_count.json` +- `namespaces/openshift-etcd/pods////logs/current.log` +- `namespaces/openshift-etcd/pods////logs/previous.log` +- `namespaces/openshift-etcd/core/events.yaml` +- `namespaces/openshift-etcd-operator/pods////logs/current.log` +- `namespaces/openshift-etcd-operator/pods////logs/previous.log` +- `audit_logs/etcd/-audit.log` (Conditional) + +### Cross-Component Dependencies (MANDATORY): +- All API server files from section 1 +- `monitoring/metrics/metrics.openmetrics` (for performance spikes) +- `host_service_logs/masters/kubelet_service.log` +- `cluster-scoped-resources/config.openshift.io/clusteroperators.yaml` + +--- + +## 14. Storage Version Migration Issues + +**Keywords**: `storage version migration`, `kube-storage-version-migrator`, `Available=False`, `KubeStorageVersionMigrator_Deploying` + +### Primary Files (MANDATORY): +- `cluster-scoped-resources/migration.k8s.io/storageversionmigrations.yaml` +- `namespaces/openshift-kube-storage-version-migrator/pods//logs/current.log` +- `namespaces/openshift-kube-storage-version-migrator/pods//logs/previous.log` +- `namespaces/openshift-kube-storage-version-migrator/core/events.yaml` +- `namespaces/openshift-kube-storage-version-migrator/apps/deployments.yaml` +- `namespaces/openshift-kube-storage-version-migrator/batch/jobs.yaml` +- `cluster-scoped-resources/apiregistration.k8s.io/apiservices.yaml` +- `cluster-scoped-resources/apiextensions.k8s.io/customresourcedefinitions.yaml` + +### Cross-Component Dependencies (MANDATORY): +- `cluster-scoped-resources/config.openshift.io/clusteroperators.yaml` +- `namespaces/openshift-cluster-version/core/events.yaml` +- `namespaces/openshift-kube-apiserver/pods//logs/current.log` +- `namespaces/openshift-kube-apiserver/pods//logs/previous.log` +- All `etcd_info/*.json` + +--- + +## 15. IPsec & Network Security Issues + +**Keywords**: `ipsec`, `encryption`, `libreswan`, `xfrm`, `Feature:IPsec` + +### Primary Files (MANDATORY): +- `network_logs/ipsec/status/` +- `network_logs/ipsec/trafficstatus/` +- `network_logs/ipsec/xfrm/` +- `network_logs/ipsec/_ipsec.conf` +- `network_logs/ipsec/_libreswan.log` +- `pod_network_connectivity_check/podnetworkconnectivitychecks.yaml` +- `namespaces/openshift-ovn-kubernetes/pods//logs/current.log` +- `namespaces/openshift-ovn-kubernetes/pods//logs/previous.log` +- `namespaces/openshift-network-operator/pods//logs/current.log` +- `host_service_logs/masters/NetworkManager_service.log` +- `host_service_logs/workers/NetworkManager_service.log` + +--- + +## Mandatory File Combinations + +### For any pod issue: +- **BOTH** `logs/current.log` AND `logs/previous.log` +- **BOTH** pod YAML AND namespace events + +### For any etcd issue: +- **ALL** etcd health files (endpoint_health.json, member_list.json, alarm_list.json) +- **BOTH** etcd logs AND API server logs + +### For any networking issue: +- **BOTH** network operator logs AND OVN database status +- **BOTH** pod connectivity checks AND network events diff --git a/intelliaide/DataSource/MUST_GATHER_STRUCTURE.md b/intelliaide/DataSource/MUST_GATHER_STRUCTURE.md new file mode 100644 index 0000000..f9f7ef3 --- /dev/null +++ b/intelliaide/DataSource/MUST_GATHER_STRUCTURE.md @@ -0,0 +1,110 @@ +--- +# OpenShift Must-Gather Log Structure Summary + +## Overview +The OpenShift must-gather tool collects comprehensive diagnostic information from an OpenShift cluster. The **user supplies the root folder** (e.g. `must-gather-Prashanth-Testcase-failure`). Under that root there is **one folder with any name** (e.g. `quay-content`—the name is not fixed). Under that folder are `host_service_logs/`, `namespaces/`, `cluster-scoped-resources/`, etc. + +## Base Directory Structure + +- **`/`** — The path the **user supplies** (root folder). +- **`//`** — One child folder under the root; **name can be any string**. This folder contains `version`, `audit_logs/`, `host_service_logs/`, `namespaces/`, etc. + +--- + +## Directory Structure and Purpose + +All paths below are under **`//`**. + +### 1. **`audit_logs/`** (Conditional) +- `kube-apiserver/-audit.log` — K8s API server audit logs +- `openshift-apiserver/-audit.log` — OpenShift API server audit logs +- `oauth-apiserver/-audit.log` — OAuth API server audit logs +- `etcd/-audit.log` — etcd audit logs + +### 2. **`host_service_logs/`** (Always Present) +- `masters/kubelet_service.log` — Kubelet service logs +- `masters/crio_service.log` — CRI-O container runtime logs +- `masters/machine-config-daemon-host_service.log` — MCO host service logs +- `masters/NetworkManager_service.log` — NetworkManager logs +- `workers/` — Same structure as masters/ + +### 3. **`etcd_info/`** (Always Present) +- `member_list.json` — etcd cluster member list and status +- `endpoint_status.json` — Detailed status of all etcd endpoints +- `endpoint_health.json` — Health status of all etcd endpoints +- `alarm_list.json` — List of active etcd alarms (NOSPACE, CORRUPT) +- `object_count.json` — Count of Kubernetes objects by type + +### 4. **`network_logs/`** (Always Present) +- `leader_ovnnb_status` — OVN Northbound database cluster status +- `leader_ovnsb_status` — OVN Southbound database cluster status +- `ovn_kubernetes_top_pods` — OVN-Kubernetes pod resource usage +- `cluster_scale` — Network resource counts +- `ipsec/` (Conditional) — IPsec status, traffic, xfrm data + +### 5. **`pod_network_connectivity_check/`** (Always Present) +- `podnetworkconnectivitychecks.yaml` — Network connectivity test results + +### 6. **`nodes/`** (Always Present) +- `/_logs_kubelet.gz` — Kubelet journal logs (compressed) +- `/dmesg` — Kernel messages +- `/sysinfo.log` — System info (df, ps, uptime) + +### 7. **`namespaces/`** (Always Present) +Per-namespace structure: +``` +namespaces// +├── .yaml +├── core/ +│ ├── events.yaml # ★ All events (errors, warnings) +│ ├── pods.yaml # All pod definitions & status +│ └── ... +├── apps/ +│ ├── deployments.yaml +│ └── ... +└── pods// + └── //logs/ + ├── current.log # ★ Active container logs + └── previous.log # ★ Crashed container logs +``` + +Key namespaces always collected: +- `openshift-cluster-version/` — CVO (upgrades) +- `openshift-etcd/` — etcd pods +- `openshift-kube-apiserver/` — K8s API server +- `openshift-kube-apiserver/pods/*/kube-apiserver/kube-apiserver/api_priority_and_fairness/` — API throttling +- `openshift-monitoring/` — Prometheus stack +- `openshift-ovn-kubernetes/` — OVN networking +- `openshift-machine-config-operator/` — Node config + +### 8. **`cluster-scoped-resources/`** (Always Present) +- `config.openshift.io/clusteroperators.yaml` — ★ All ClusterOperator status +- `config.openshift.io/clusterversions.yaml` — Cluster version, upgrade status +- `config.openshift.io/networks.yaml` — Network configuration +- `core/nodes/.yaml` — Individual node status +- `storage.k8s.io/storageclasses.yaml` — Storage classes +- `storage.k8s.io/volumeattachments/` — Volume attachment status +- `migration.k8s.io/storageversionmigrations.yaml` — Storage version migrations + +### 9. **`static-pods/`** (Always Present) +- `kube-apiserver/-startup.log.gz` — API server startup logs +- `kube-apiserver/-termination.log.gz` — API server termination logs + +--- + +## File Size Awareness + +| File / directory | Typical size | Notes | +|------------------|-------------|-------| +| `audit_logs/*-audit.log` | 100 MB – 1 GB+ | Parse only for auth/authz investigation | +| `monitoring/metrics/metrics.openmetrics` | 50 MB – 500 MB+ | Parse only for performance analysis | +| `nodes//_logs_kubelet.gz` | 10 MB – 100 MB+ | Decompress only for kubelet issues | + +--- + +## Notes + +- All paths are relative to `//` +- Some directories only exist if features are enabled (conditional) +- Compressed files use `.gz` extension — reference them with that extension +- Always check `events.yaml` first — human-readable errors with timestamps diff --git a/intelliaide/Machine-learning/ML_LOG_CLASSIFICATION.py b/intelliaide/Machine-learning/ML_LOG_CLASSIFICATION.py new file mode 100644 index 0000000..09c5dff --- /dev/null +++ b/intelliaide/Machine-learning/ML_LOG_CLASSIFICATION.py @@ -0,0 +1,2491 @@ +""" +Log Analysis Pipeline with Drain3 Template Mining + +This module provides production-grade log analysis using Drain3 for template extraction. + +MAIN ENTRY POINT: + analyze_logs(file_path, output_dir, config_path) -> Dict + + Returns a JSON-serializable response with: + - Generated template files (rare_errors, highfreq_errors, warnings, etc.) + - Template and line counts + - Pareto-based error frequency analysis + +FEATURES: + - Log level classification (Error, Warning, Info, Unknown) + - Configuration change detection + - Error template frequency analysis (Pareto/80-20 rule) + - Template shifting (errors found in W/I/U → Error category) + - Configurable output via log_config.json + +PROCESSING MODES: + - ENTIRE FILE: For smaller files (< threshold) + - CHUNK PIPELINE: For larger files, with parallel/sequential options + +CONFIGURATION: + All settings controlled via log_config.json: + - output_flags: Control which files to generate + - frequency_settings: Pareto threshold, sample counts + - drain3_settings: Template mining parameters + - preprocessing: Timestamp removal patterns + +Usage: + from testing_log import analyze_logs + result = analyze_logs("app.log") + print(result['files']) # Generated output files +""" + +import os +import re +import json +import time +import multiprocessing +from pathlib import Path +from typing import List, Dict, Tuple, Optional, Generator +from collections import defaultdict +from datetime import datetime +from dataclasses import dataclass, field +from concurrent.futures import ProcessPoolExecutor + +try: + from drain3 import TemplateMiner + from drain3.template_miner_config import TemplateMinerConfig + DRAIN3_AVAILABLE = True +except ImportError: + DRAIN3_AVAILABLE = False + + +# ============================================================================= +# CONFIGURATION +# ============================================================================= +CONFIG_FILE_NAME = "log_config.json" + + +@dataclass +class Drain3Config: + """Drain3 algorithm settings.""" + batch_size: int = 1000 + similarity_threshold: float = 0.3 + depth: int = 4 + max_children: int = 100 + + +@dataclass +class FrequencyConfig: + """Configuration for template frequency-based segregation (Pareto/80-20 rule). + + High-frequency templates: Templates that cumulatively cover X% of total error lines. + These are typically repetitive/known issues - show only 1 sample per template. + + Rare/Priority templates: Templates that fall outside the high-frequency threshold. + These are priority for investigation - show full detail with all lines. + + Note: All output files now include ALL log lines for each template (no sampling). + """ + # Pareto threshold: templates covering this % of lines are "high frequency" + # Used as FALLBACK when rare_max_lines_per_template is None. + high_freq_cumulative_threshold: float = 0.80 # 80% = Pareto rule + + # Enable tiered output for errors (creates separate files for high-freq vs rare) + enable_tiered_error_output: bool = True + + # Optional: absolute maximum size for a template to be considered "rare" + # If set, templates with size <= this value are always considered rare + # regardless of cumulative threshold. None = disabled. + rare_absolute_max_size: Optional[int] = None + + # Simple count-based bifurcation: templates with <= this many lines are "rare errors", + # templates with > this many lines are "high-frequency errors". + # When set (not None), this OVERRIDES the Pareto cumulative threshold. + # Default: 15. Set to None to fall back to Pareto threshold. + rare_max_lines_per_template: Optional[int] = 15 + + +@dataclass +class OutputFlagsConfig: + """Configuration for controlling which outputs to generate. + + These flags control the output files generated by analyze_logs(). + All flags default to True (generate all outputs). + """ + include_rare_errors: bool = True # Rare/priority error templates (detailed) + include_highfreq_errors: bool = True # High-frequency error templates (summary) + include_warnings: bool = True # Warning templates + include_information: bool = True # Information templates + include_unknown: bool = True # Unknown level templates + include_config_changes: bool = True # Configuration change templates + + +@dataclass +class PreprocessingConfig: + """Preprocessing settings.""" + remove_timestamps: bool = True + timestamp_patterns: List[str] = field(default_factory=lambda: [ + r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z?\s*', + r'^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}(\.\d+)?\s*', + r'^([IWE])\d{4}\s+\d{2}:\d{2}:\d{2}(\.\d+)?\s*', + r'^(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}(\.\d+)?\s+\S+\s+', + r'\b\d{2}:\d{2}:\d{2}(\.\d+)?\b', + ]) + + +@dataclass +class PipelineConfig: + """Configuration for the log processing pipeline.""" + # Branch selection + use_chunk_pipeline: Optional[bool] = None # None=auto, True=chunk, False=entire + file_size_threshold_mb: float = 50.0 + + # Chunk settings + chunk_size: int = 10_000 + parallel_chunks: bool = True + chunk_workers: Optional[int] = None + min_chunks_for_parallel: int = 3 + + # Output settings + output_dir: Optional[str] = None + levels_to_process: List[str] = field(default_factory=lambda: ['E', 'W', 'I', 'UNKNOWN']) + + # Sub-configs + drain3: Drain3Config = field(default_factory=Drain3Config) + preprocessing: PreprocessingConfig = field(default_factory=PreprocessingConfig) + frequency: FrequencyConfig = field(default_factory=FrequencyConfig) + output_flags: OutputFlagsConfig = field(default_factory=OutputFlagsConfig) + + # Logging + verbose: bool = True + print_progress: bool = True + + +def load_config(config_path: Optional[str] = None) -> PipelineConfig: + """ + Load configuration from JSON file. + + Search order: + 1. Explicit config_path if provided + 2. log_config.json in current working directory + 3. log_config.json in script directory + 4. Default configuration + """ + search_paths = [] + + if config_path: + search_paths.append(Path(config_path)) + + # Current working directory + search_paths.append(Path.cwd() / CONFIG_FILE_NAME) + + # Script directory + search_paths.append(Path(__file__).parent / CONFIG_FILE_NAME) + + for path in search_paths: + if path.exists(): + try: + with open(path, 'r', encoding='utf-8') as f: + config_dict = json.load(f) + print(f"[Config] Loaded configuration from: {path}") + + # Build config from dict + config = PipelineConfig() + + # Pipeline settings + pipeline = config_dict.get('pipeline', {}) + if 'use_chunk_pipeline' in pipeline: + config.use_chunk_pipeline = pipeline['use_chunk_pipeline'] + if 'file_size_threshold_mb' in pipeline: + config.file_size_threshold_mb = pipeline['file_size_threshold_mb'] + + # Chunk settings + chunk = config_dict.get('chunk_settings', {}) + if 'chunk_size' in chunk: + config.chunk_size = chunk['chunk_size'] + if 'parallel_chunks' in chunk: + config.parallel_chunks = chunk['parallel_chunks'] + if 'chunk_workers' in chunk: + config.chunk_workers = chunk['chunk_workers'] + if 'min_chunks_for_parallel' in chunk: + config.min_chunks_for_parallel = chunk['min_chunks_for_parallel'] + + # Drain3 settings + drain3 = config_dict.get('drain3_settings', {}) + if 'batch_size' in drain3: + config.drain3.batch_size = drain3['batch_size'] + if 'similarity_threshold' in drain3: + config.drain3.similarity_threshold = drain3['similarity_threshold'] + if 'depth' in drain3: + config.drain3.depth = drain3['depth'] + if 'max_children' in drain3: + config.drain3.max_children = drain3['max_children'] + + # Output settings + output = config_dict.get('output', {}) + if 'output_dir' in output: + config.output_dir = output['output_dir'] + if 'levels_to_process' in output: + config.levels_to_process = output['levels_to_process'] + + # Preprocessing settings + preproc = config_dict.get('preprocessing', {}) + if 'remove_timestamps' in preproc: + config.preprocessing.remove_timestamps = preproc['remove_timestamps'] + if 'timestamp_patterns' in preproc: + config.preprocessing.timestamp_patterns = preproc['timestamp_patterns'] + + # Logging settings + logging_cfg = config_dict.get('logging', {}) + if 'verbose' in logging_cfg: + config.verbose = logging_cfg['verbose'] + if 'print_progress' in logging_cfg: + config.print_progress = logging_cfg['print_progress'] + + # Frequency settings (Pareto/80-20 rule for error template classification) + freq = config_dict.get('frequency_settings', {}) + if 'high_freq_cumulative_threshold' in freq: + config.frequency.high_freq_cumulative_threshold = freq['high_freq_cumulative_threshold'] + if 'enable_tiered_error_output' in freq: + config.frequency.enable_tiered_error_output = freq['enable_tiered_error_output'] + if 'rare_absolute_max_size' in freq: + config.frequency.rare_absolute_max_size = freq['rare_absolute_max_size'] + if 'rare_max_lines_per_template' in freq: + config.frequency.rare_max_lines_per_template = freq['rare_max_lines_per_template'] + + # Output flags (control which outputs to generate) + output_flags = config_dict.get('output_flags', {}) + if 'include_rare_errors' in output_flags: + config.output_flags.include_rare_errors = output_flags['include_rare_errors'] + if 'include_highfreq_errors' in output_flags: + config.output_flags.include_highfreq_errors = output_flags['include_highfreq_errors'] + if 'include_warnings' in output_flags: + config.output_flags.include_warnings = output_flags['include_warnings'] + if 'include_information' in output_flags: + config.output_flags.include_information = output_flags['include_information'] + if 'include_unknown' in output_flags: + config.output_flags.include_unknown = output_flags['include_unknown'] + if 'include_config_changes' in output_flags: + config.output_flags.include_config_changes = output_flags['include_config_changes'] + + return config + + except (json.JSONDecodeError, Exception) as e: + print(f"[Config] Warning: Error loading {path}: {e}") + continue + + print("[Config] Using default configuration") + return PipelineConfig() + + +# Global config instance (loaded on first use) +_GLOBAL_CONFIG: Optional[PipelineConfig] = None + + +def get_config(config_path: Optional[str] = None, reload: bool = False) -> PipelineConfig: + """Get the global configuration instance.""" + global _GLOBAL_CONFIG + if _GLOBAL_CONFIG is None or reload: + _GLOBAL_CONFIG = load_config(config_path) + return _GLOBAL_CONFIG + + +# Default configuration (for backward compatibility) +DEFAULT_CONFIG = PipelineConfig() + + +# ============================================================================= +# TIMESTAMP PARSER +# ============================================================================= +def remove_timestamps(log_line: str, config: Optional[PipelineConfig] = None) -> str: + """ + Remove timestamp entries from a single log line. + + Uses patterns from config.preprocessing.timestamp_patterns if provided, + otherwise uses default hardcoded patterns. + """ + if not log_line.strip(): + return log_line + + config = config or get_config() + + # Check if timestamp removal is enabled + if not config.preprocessing.remove_timestamps: + return log_line + + line = log_line + + # Use configurable patterns + patterns = config.preprocessing.timestamp_patterns + + # Apply each pattern + for i, pattern in enumerate(patterns): + # Pattern at index 2 is the I/W/E + MMDD pattern, needs special replacement + if i == 2 and pattern.startswith('^([IWE])'): + line = re.sub(pattern, r'\1 ', line) + # Pattern at index 3 is systemd/journal, needs case-insensitive + elif i == 3 and 'Jan|Feb|Mar' in pattern: + line = re.sub(pattern, '', line, flags=re.IGNORECASE) + else: + line = re.sub(pattern, '', line) + + # Clean up whitespace + line = re.sub(r'\s+', ' ', line).strip() + return line + + +# ============================================================================= +# TIMESTAMP EXTRACTION FROM LOG LINES (for sampling by time) +# ============================================================================= + +# Month abbreviation -> number mapping for systemd/journal timestamps +_MONTH_MAP = { + 'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6, + 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12, +} + +# Pattern: Jan 12 14:22:56 (systemd/journal logs) +_TS_SYSTEMD = re.compile( + r'^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+(\d{1,2})\s+(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?' +) + +# Pattern: I0112 14:22:56 or E0112 14:22:56 (Kubernetes logs) +# Format is: — e.g. I0112 means Info, Jan 12 +_TS_KUBE = re.compile( + r'^[IWEU](\d{4})\s+(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?' +) + +# Pattern: 2024-01-12T14:22:56 (ISO timestamps) +_TS_ISO = re.compile( + r'(\d{4})-(\d{2})-(\d{2})[T\s](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?' +) + + +def extract_timestamp_from_log(log_line: str) -> Optional[str]: + """ + Extract a sortable timestamp string from a log line. + + Supported formats (checked in order): + 1. ``Jan 12 14:22:56`` – systemd / journal logs + 2. ``I0112 14:22:56`` – Kubernetes I/W/E/U prefixed logs + 3. ``2024-01-12T14:22:56`` – ISO 8601 timestamps + + Returns: + A sortable string in the form ``MMDD-HH:MM:SS.ffffff`` (for formats 1 & 2) + or ``YYYY-MM-DDTHH:MM:SS.ffffff`` (for ISO), or ``None`` if no timestamp + could be parsed. The returned string is designed for lexicographic + ordering so that ``sorted()`` produces chronological order. + """ + if not log_line: + return None + + # 1. Systemd / journal: Jan 12 14:22:56.123456 + m = _TS_SYSTEMD.match(log_line) + if m: + month = _MONTH_MAP.get(m.group(1), 0) + day = int(m.group(2)) + frac = (m.group(6) or '0').ljust(6, '0')[:6] + return f"{month:02d}{day:02d}-{m.group(3)}:{m.group(4)}:{m.group(5)}.{frac}" + + # 2. Kubernetes: E0112 14:22:56.789 + m = _TS_KUBE.match(log_line) + if m: + mmdd = m.group(1) # "MMDD" — 4 digits captured after the level char (e.g. "0112" = Jan 12) + frac = (m.group(5) or '0').ljust(6, '0')[:6] + return f"{mmdd}-{m.group(2)}:{m.group(3)}:{m.group(4)}.{frac}" + + # 3. ISO 8601: 2024-01-12T14:22:56.123Z + m = _TS_ISO.search(log_line) + if m: + frac = (m.group(7) or '0').ljust(6, '0')[:6] + return (f"{m.group(1)}-{m.group(2)}-{m.group(3)}T" + f"{m.group(4)}:{m.group(5)}:{m.group(6)}.{frac}") + + return None + + +def sample_lines_by_timestamp( + lines: List[str], + first_n: int = 3, + middle_n: int = 1, + last_n: int = 3, +) -> List[str]: + """ + Sample representative lines from a list using timestamp-based ordering. + + For a template cluster with many lines this returns a small, representative + subset that shows *when the error started* (first N), a *mid-point* + occurrence (middle N), and the *most recent* occurrences (last N). + + Algorithm: + 1. Try to parse a timestamp from each line via + :func:`extract_timestamp_from_log`. + 2. If **any** timestamps were found, sort lines by timestamp. + Otherwise, keep original order (log files are usually + chronologically ordered). + 3. Sample ``first_n + middle_n + last_n`` lines (default 7). + 4. If the cluster has fewer lines than the sample size, return all + lines (no sampling needed). + + Args: + lines: All original log lines belonging to one template cluster. + first_n: Number of earliest lines to include (default 3). + middle_n: Number of middle lines to include (default 1). + last_n: Number of latest lines to include (default 3). + + Returns: + A list of sampled original log lines (order preserved from the + sorted/original sequence). + """ + total_sample = first_n + middle_n + last_n # default 7 + + if len(lines) <= total_sample: + return list(lines) # nothing to sample — return all + + # Attempt timestamp extraction + ts_line_pairs = [] + has_timestamps = False + for line in lines: + ts = extract_timestamp_from_log(line) + ts_line_pairs.append((ts, line)) + if ts is not None: + has_timestamps = True + + # Sort by timestamp if available; otherwise keep original order + if has_timestamps: + # Lines without a timestamp get a sentinel that sorts first + ts_line_pairs.sort(key=lambda pair: pair[0] if pair[0] is not None else '') + + sorted_lines = [line for _, line in ts_line_pairs] + + # Sample indices + n = len(sorted_lines) + first_indices = list(range(0, min(first_n, n))) + last_indices = list(range(max(n - last_n, 0), n)) + mid_start = (n // 2) - (middle_n // 2) + middle_indices = list(range(mid_start, min(mid_start + middle_n, n))) + + # Merge indices, deduplicate, preserve order + seen = set() + sampled = [] + for idx in first_indices + middle_indices + last_indices: + if idx not in seen: + seen.add(idx) + sampled.append(sorted_lines[idx]) + + return sampled + + +# ============================================================================= +# LOG LEVEL PARSER +# ============================================================================= +def parse_log_level(log_line: str) -> Optional[str]: + """ + Parse log level from a log line. + + Prioritizes content-based detection (error/warning phrases) + over structural prefixes (I/W/E) because Info-level logs often contain errors. + + Returns: + 'E' for Error, 'W' for Warning, 'I' for Info, None for Unknown + """ + if not log_line.strip(): + return None + s = log_line.strip() + upper = s.upper() + + # 1) Content-based detection: Error phrases (highest priority) + error_phrases = [ + r'\bFAILURE\b', r'\bFAILED\b', r'\bstatus=1/', r'\bstatus=2/', r'\bstatus=3/', + r"exit-code", r"exit code", r"exited with", r"exited, code=", + r"failed to start", r"could not start", r"failed with result", + r'\bERROR\b', r'\bFATAL\b', r'\bCRASH', r'\bOOMKILLED\b', + r'\bTIMEOUT\b', r'\bUNREACHABLE\b', r'\bREFUSED\b', + r'connection refused', r'no such file', r'permission denied', + ] + for pat in error_phrases: + if re.search(pat, upper): + return 'E' + + # 2) Content-based detection: Warning phrases + warn_phrases = [r'\bWARN(?:ING)?\b', r'\bDEPRECATED\b', r'\bRETRY\b', r'\bBACKOFF\b'] + for pat in warn_phrases: + if re.search(pat, upper): + return 'W' + + # 3) Structural prefix: I/W/E at start of line (Kubernetes-style logs) + # Handles both original (E0112, W0112, I0112) and preprocessed (E , W , I ) formats + # After timestamp removal, "W0112 17:02:33..." becomes "W ..." + match = re.match(r'([IWEiwe])(?:\d{4}\b|\s)', s) + if match: + return match.group(1).upper() + + # 4) Bracketed level [INFO], [WARN], [ERROR] + match = re.search(r'\[(INFO|WARN|WARNING|ERROR|ERR)\]', upper) + if match: + level_str = match.group(1) + if level_str in ['INFO', 'INFORMATION']: + return 'I' + elif level_str in ['WARN', 'WARNING']: + return 'W' + elif level_str in ['ERROR', 'ERR']: + return 'E' + + # 5) Info keywords + if re.search(r'\bINFO\b', upper) or re.search(r'\bStarted\b', s): + return 'I' + + return None + + +# ============================================================================= +# DRAIN3 UTILITIES +# ============================================================================= +def create_drain3_miner(config: Optional[PipelineConfig] = None) -> 'TemplateMiner': + """Create and configure a new Drain3 TemplateMiner instance.""" + config = config or get_config() + drain_config = TemplateMinerConfig() + drain_config.sim_th = config.drain3.similarity_threshold + drain_config.depth = config.drain3.depth + drain_config.max_children = config.drain3.max_children + drain_config.profiling_enabled = False + return TemplateMiner(config=drain_config) + + +def extract_drain3_results(miner: 'TemplateMiner') -> Tuple[Dict, List[Dict]]: + """Extract cluster sizes and template list from a Drain3 miner.""" + cluster_sizes = {} + template_list = [] + + clusters = miner.drain.clusters if hasattr(miner, 'drain') else [] + + # Handle both list and dict formats + if isinstance(clusters, dict): + cluster_list = list(clusters.values()) + elif hasattr(clusters, '__iter__'): + cluster_list = list(clusters) + else: + cluster_list = [] + + for cluster in cluster_list: + try: + # Use the ACTUAL cluster_id from the LogCluster object + # This ensures consistency with miner.match() results + cid = getattr(cluster, 'cluster_id', None) + if cid is None: + continue + size = getattr(cluster, 'size', 1) + template = cluster.get_template() if hasattr(cluster, 'get_template') else str(cluster) + cluster_sizes[cid] = size + template_list.append({'id': cid, 'template': template, 'size': size}) + except Exception: + pass + + return cluster_sizes, template_list + + +def process_lines_with_drain3( + lines: List[str], + config: Optional[PipelineConfig] = None +) -> Tuple[Dict, List[Dict], List]: + """Process lines through Drain3 in batches. + + Returns: + Tuple of (cluster_sizes, template_list, event_ids) + - event_ids: List of FINAL cluster_ids for each input line + """ + if not DRAIN3_AVAILABLE or not lines: + return {}, [], [None] * len(lines) + + config = config or get_config() + batch_size = config.drain3.batch_size + + miner = create_drain3_miner(config) + + # STEP 1: Process all lines to build the clusters + for i in range(0, len(lines), batch_size): + batch = lines[i:i + batch_size] + for line in batch: + if line.strip(): + miner.add_log_message(line) + + # STEP 2: Extract final cluster results + cluster_sizes, template_list = extract_drain3_results(miner) + + # STEP 3: Re-match each line to its FINAL cluster ID + # This ensures event_ids reflect the final cluster assignments + event_ids = [] + for line in lines: + if line.strip(): + # match() returns the cluster without modifying the miner + result = miner.match(line) + if result: + event_ids.append(result.cluster_id) + else: + event_ids.append(None) + else: + event_ids.append(None) + + return cluster_sizes, template_list, event_ids + + + + +# ============================================================================= +# BRANCH 1: ENTIRE FILE PIPELINE +# ============================================================================= +def entire_file_pipeline(file_path: str, config: Optional[PipelineConfig] = None) -> Dict[str, Dict[str, List[str]]]: + """ + BRANCH 1: Process entire file at once. + + Pipeline: + 1. Read entire file into memory + 2. Process all lines through timestamp parser + 3. Categorize all lines by log level + + Best for: Smaller files (< file_size_threshold_mb) + + Args: + file_path: Path to the log file + config: Pipeline configuration (uses global config if None) + + Returns: + Dict mapping level ('E', 'W', 'I', 'UNKNOWN') to + {"preprocessed": [lines], "original": [lines]} + """ + config = config or get_config() + + try: + with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: + log_content = f.read() + except Exception as e: + if config.verbose: + print(f"[EntireFile] Error reading file {file_path}: {e}") + return {} + + if not log_content.strip(): + return {} + + result = defaultdict(lambda: {"preprocessed": [], "original": []}) + for line in log_content.split('\n'): + if not line.strip(): + continue + # Step 1: Remove timestamps (using config) + preprocessed_line = remove_timestamps(line, config) + # Step 2: Categorize by log level (using config) + level = parse_log_level(preprocessed_line) + if level == 'E': + result['E']["preprocessed"].append(preprocessed_line) + result['E']["original"].append(line) + elif level == 'W': + result['W']["preprocessed"].append(preprocessed_line) + result['W']["original"].append(line) + elif level == 'I': + result['I']["preprocessed"].append(preprocessed_line) + result['I']["original"].append(line) + else: + result['UNKNOWN']["preprocessed"].append(preprocessed_line) + result['UNKNOWN']["original"].append(line) + + return dict(result) + + +# ============================================================================= +# BRANCH 2: CHUNK PIPELINE - Core Functions +# ============================================================================= +def chunk_file_reader(file_path: str, chunk_size: int) -> Generator[List[str], None, None]: + """ + Generator that reads a file in chunks. + + Yields: + List[str]: A chunk of lines (up to chunk_size lines per chunk) + """ + try: + with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: + chunk = [] + for line in f: + stripped = line.rstrip('\n\r') + if stripped: + chunk.append(stripped) + if len(chunk) >= chunk_size: + yield chunk + chunk = [] + if chunk: + yield chunk + except Exception as e: + print(f"[ChunkReader] Error reading file {file_path}: {e}") + + +def process_single_chunk(chunk: List[str], config: Optional[PipelineConfig] = None) -> Dict[str, Dict[str, List[str]]]: + """ + Process a single chunk: timestamp removal + categorization. + + This function is the worker for both sequential and parallel processing. + + Args: + chunk: List of raw log lines + config: Pipeline configuration (uses global config if None) + + Returns: + Dict mapping level ('E', 'W', 'I', 'UNKNOWN') to + {"preprocessed": [lines], "original": [lines]} + """ + config = config or get_config() + categorized = defaultdict(lambda: {"preprocessed": [], "original": []}) + + for line in chunk: + # Step 1: Remove timestamps (using config) + preprocessed = remove_timestamps(line, config) + # Step 2: Categorize by log level (using config) + level = parse_log_level(preprocessed) + if level == 'E': + categorized['E']["preprocessed"].append(preprocessed) + categorized['E']["original"].append(line) + elif level == 'W': + categorized['W']["preprocessed"].append(preprocessed) + categorized['W']["original"].append(line) + elif level == 'I': + categorized['I']["preprocessed"].append(preprocessed) + categorized['I']["original"].append(line) + else: + categorized['UNKNOWN']["preprocessed"].append(preprocessed) + categorized['UNKNOWN']["original"].append(line) + + return dict(categorized) + + +def merge_chunk_results(results: List[Dict[str, Dict[str, List[str]]]]) -> Dict[str, Dict[str, List[str]]]: + """Merge categorized results from multiple chunks.""" + merged = defaultdict(lambda: {"preprocessed": [], "original": []}) + for result in results: + for level, data in result.items(): + if isinstance(data, dict) and "preprocessed" in data: + merged[level]["preprocessed"].extend(data["preprocessed"]) + merged[level]["original"].extend(data["original"]) + elif isinstance(data, list): + # Backward compat: old format without originals + merged[level]["preprocessed"].extend(data) + merged[level]["original"].extend(data) + return dict(merged) + + +# ============================================================================= +# BRANCH 2A: CHUNK PIPELINE - SEQUENTIAL +# ============================================================================= +def chunk_pipeline_sequential(file_path: str, config: Optional[PipelineConfig] = None) -> Dict[str, List[str]]: + """ + BRANCH 2A: Process file in chunks SEQUENTIALLY. + + Pipeline: + 1. Read file chunk by chunk + 2. For each chunk: + - Remove timestamps + - Categorize by log level + 3. Accumulate results + + Args: + file_path: Path to the log file + config: Pipeline configuration (uses global config if None) + + Returns: + Dict mapping level ('E', 'W', 'I', 'UNKNOWN') to list of preprocessed lines + """ + config = config or get_config() + by_level = defaultdict(list) + chunk_count = 0 + + for chunk in chunk_file_reader(file_path, config.chunk_size): + chunk_count += 1 + # Process this chunk (passing config) + chunk_result = process_single_chunk(chunk, config) + # Accumulate results + for level, lines in chunk_result.items(): + by_level[level].extend(lines) + + return dict(by_level) + + +# ============================================================================= +# BRANCH 2B: CHUNK PIPELINE - PARALLEL +# ============================================================================= +def chunk_pipeline_parallel(file_path: str, config: Optional[PipelineConfig] = None) -> Dict[str, List[str]]: + """ + BRANCH 2B: Process file chunks IN PARALLEL using multiprocessing. + + Pipeline: + 1. Read all chunks from file (sequential I/O) + 2. Process chunks in parallel (timestamp + categorization) + 3. Merge results from all chunks + + Note: For parallel processing, config is loaded fresh in each worker process. + + Args: + file_path: Path to the log file + config: Pipeline configuration (uses global config if None) + + Returns: + Dict mapping level ('E', 'W', 'I', 'UNKNOWN') to list of preprocessed lines + """ + config = config or get_config() + + # Step 1: Read all chunks into memory (I/O is sequential) + chunks = list(chunk_file_reader(file_path, config.chunk_size)) + + if not chunks: + return {} + + num_chunks = len(chunks) + + # Check if parallel is worth it + if num_chunks < config.min_chunks_for_parallel: + # Fall back to sequential for small number of chunks + return chunk_pipeline_sequential(file_path, config) + + # Determine worker count + max_workers = config.chunk_workers + if max_workers is None: + max_workers = min(multiprocessing.cpu_count(), num_chunks, 8) + + # Step 2: Process chunks in parallel + chunk_results = [] + + with ProcessPoolExecutor(max_workers=max_workers) as executor: + futures = [executor.submit(process_single_chunk, chunk) for chunk in chunks] + + # Collect results (preserves order) + for future in futures: + try: + result = future.result() + chunk_results.append(result) + except Exception as e: + print(f"[ChunkParallel] Error processing chunk: {e}") + + # Step 3: Merge all chunk results + return merge_chunk_results(chunk_results) + + +# ============================================================================= +# UNIFIED CHUNK PIPELINE (auto-selects sequential or parallel) +# ============================================================================= +def chunk_pipeline(file_path: str, config: Optional[PipelineConfig] = None) -> Dict[str, List[str]]: + """ + BRANCH 2: Process file in chunks. + + Automatically selects SEQUENTIAL or PARALLEL mode based on config.parallel_chunks. + + Args: + file_path: Path to the log file + config: Pipeline configuration (uses global config if None) + + Returns: + Dict mapping level ('E', 'W', 'I', 'UNKNOWN') to list of preprocessed lines + """ + config = config or get_config() + if config.parallel_chunks: + return chunk_pipeline_parallel(file_path, config) + else: + return chunk_pipeline_sequential(file_path, config) + + +def build_cluster_to_lines( + lines: List[str], + event_ids: List, +) -> Dict[int, List[str]]: + """Map cluster_id -> list of original log lines that belong to that cluster.""" + cluster_to_lines: Dict[int, List[str]] = defaultdict(list) + for i, line in enumerate(lines): + if i >= len(event_ids): + break + cid = event_ids[i] + if cid is not None: + cluster_to_lines[cid].append(line) + return dict(cluster_to_lines) + + +# ============================================================================= +# SAVE LOG LINES +# ============================================================================= +def save_templates_to_file( + template_list: List[Dict], + cluster_sizes: Dict, + logs: List[Tuple[str, str]], + event_ids: List, + output_dir: str, + level_suffix: str, + filename_prefix: Optional[str] = None, + cluster_to_lines: Optional[Dict[int, List[str]]] = None, + max_original_lines_per_cluster: int = 5000, +) -> str: + """ + Save the log lines for one level to a file (no templates/headers). + Also saves a companion template file listing each Drain3 template + with its cluster size, named ``__templates.txt``. + + Args: + template_list: List of template dicts + cluster_sizes: Dict of cluster_id -> size + logs: List of (file_path, line) tuples + event_ids: Event IDs from Drain3 + output_dir: Output directory + level_suffix: Level name (e.g., 'Warning', 'Information') + filename_prefix: Optional filename prefix + cluster_to_lines: Mapping of cluster_id -> original lines + max_original_lines_per_cluster: Ignored + + Returns: + Path to saved log-lines file + """ + output_path = Path(output_dir) + output_path.mkdir(parents=True, exist_ok=True) + if filename_prefix: + filename = output_path / f"{filename_prefix}_{level_suffix}.txt" + template_filename = output_path / f"{filename_prefix}_{level_suffix}_templates.txt" + else: + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = output_path / f"log_classification_{level_suffix}_{timestamp}.txt" + template_filename = output_path / f"log_classification_{level_suffix}_templates_{timestamp}.txt" + + cluster_to_lines = cluster_to_lines or {} + + # Collect all log lines from all clusters + all_lines = [] + for tmpl in template_list: + cid = tmpl['id'] + lines = cluster_to_lines.get(cid, []) + all_lines.extend(lines) + + # Write only the log lines + with open(filename, 'w', encoding='utf-8') as f: + for line in all_lines: + f.write(line + "\n") + + # ── Save companion template file ── + if template_list: + with open(template_filename, 'w', encoding='utf-8') as f: + f.write(f"# {level_suffix} Templates ({len(template_list)} templates)\n") + f.write(f"# Each template shown with cluster size (number of matching log lines)\n") + f.write("=" * 80 + "\n\n") + for i, tmpl in enumerate(template_list, 1): + cid = tmpl['id'] + size = cluster_sizes.get(cid, tmpl.get('size', len(cluster_to_lines.get(cid, [])))) + template_text = tmpl.get('template', '(unknown)') + f.write(f"Template {i} | cluster_id={cid} | lines={size}\n") + f.write(f" {template_text}\n\n") + + return str(filename) + + +# ============================================================================= +# CONFIGURATION CHANGE AND ERROR DETECTION +# ============================================================================= + +# Patterns that indicate configuration changes in OpenShift/Kubernetes logs +CONFIG_CHANGE_PATTERNS = [ + r'ObservedConfigChanged', + r'OperatorStatusChanged', + r'OperatorVersionChanged', + r'FeatureGates\s*(updated|initialized)', + r'Config\s*Drift\s*Monitor', + r'currentConfig', + r'desiredConfig', + r'ConfigMapCreated', + r'ConfigMapUpdated', + r'DeploymentUpdated', + r'DaemonSetUpdated', + r'ValidatingWebhookConfigurationUpdated', + r'MutatingWebhookConfigurationUpdated', + r'TLSConfigChanged', + r'Update\s+completed\s+for\s+config', + r'rendered-master-', + r'rendered-worker-', + r'Setting\s+node.*currentConfig', + r'MachineConfiguration\s+status', + r'Status\s+for\s+clusteroperator', + r'version.*changed', + r'ObserveTLS', + r'ObserveAPI', +] + +# Compiled regex for configuration change detection (case-insensitive) +CONFIG_CHANGE_REGEX = re.compile( + '|'.join(CONFIG_CHANGE_PATTERNS), + re.IGNORECASE +) + +# Error patterns for detecting errors in non-Error categories +ERROR_DETECTION_PATTERNS = [ + r'\bERROR\b', + r'\bFAILED\b', + r'\bFAILURE\b', + r'\bFATAL\b', + r'\bCRASH', + r'\bPANIC\b', + r'\bOOMKILLED\b', + r'\bTIMEOUT\b', + r'Unhandled\s*Error', + r'reconciliation\s*failed', + r'connection\s*refused', + r'no\s*such\s*file', + r'permission\s*denied', + r'exit[- ]code[=:\s]+[1-9]', + r'status[=:\s]+[1-9]', + r'exited\s+with\s+[1-9]', +] + +# Compiled regex for error detection (case-insensitive) +ERROR_DETECTION_REGEX = re.compile( + '|'.join(ERROR_DETECTION_PATTERNS), + re.IGNORECASE +) + + +def is_error_template(template: str) -> bool: + """Check if a template contains error indicators.""" + return bool(ERROR_DETECTION_REGEX.search(template)) + + +def is_config_change_template(template: str) -> bool: + """Check if a template represents a configuration change.""" + return bool(CONFIG_CHANGE_REGEX.search(template)) + + +def classify_templates_for_level( + level: str, + template_list: List[Dict], + cluster_to_lines: Dict[int, List[str]], + lines: List[str], + event_ids: List, +) -> Dict[str, Dict]: + """ + Classify templates from a non-Error level into: + 1. Error templates (contain error indicators) -> to be moved to Error + 2. Config change templates -> to be moved to ConfigChanges + 3. Regular templates -> stay in original level + + Works at TEMPLATE level - entire clusters (template + all lines) move together. + + Args: + level: Current level ('W', 'I', or 'UNKNOWN') + template_list: List of Drain3 templates + cluster_to_lines: Mapping of cluster_id -> original lines + lines: All lines for this level + event_ids: Event IDs from Drain3 (cluster assignments) + + Returns: + Dict with keys 'regular', 'error', 'config' each containing: + - templates: List of template dicts + - cluster_to_lines: Dict of cluster_id -> lines + - cluster_ids: Set of cluster IDs + """ + # Skip classification for Error level + if level == 'E': + return { + 'regular': { + 'templates': template_list, + 'cluster_to_lines': cluster_to_lines, + 'cluster_ids': {t['id'] for t in template_list}, + }, + 'error': {'templates': [], 'cluster_to_lines': {}, 'cluster_ids': set()}, + 'config': {'templates': [], 'cluster_to_lines': {}, 'cluster_ids': set()}, + } + + # Classify each template + regular_templates = [] + error_templates = [] + config_templates = [] + + regular_cluster_ids = set() + error_cluster_ids = set() + config_cluster_ids = set() + + for tmpl in template_list: + template_text = tmpl['template'] + cluster_id = tmpl['id'] + + # Priority: Error > Config > Regular + if is_error_template(template_text): + error_templates.append(tmpl) + error_cluster_ids.add(cluster_id) + elif is_config_change_template(template_text): + config_templates.append(tmpl) + config_cluster_ids.add(cluster_id) + else: + regular_templates.append(tmpl) + regular_cluster_ids.add(cluster_id) + + # Separate cluster_to_lines based on template classification + regular_cluster_to_lines = {} + error_cluster_to_lines = {} + config_cluster_to_lines = {} + + for cid, clines in cluster_to_lines.items(): + if cid in error_cluster_ids: + error_cluster_to_lines[cid] = clines + elif cid in config_cluster_ids: + config_cluster_to_lines[cid] = clines + elif cid in regular_cluster_ids: + regular_cluster_to_lines[cid] = clines + + return { + 'regular': { + 'templates': regular_templates, + 'cluster_to_lines': regular_cluster_to_lines, + 'cluster_ids': regular_cluster_ids, + }, + 'error': { + 'templates': error_templates, + 'cluster_to_lines': error_cluster_to_lines, + 'cluster_ids': error_cluster_ids, + }, + 'config': { + 'templates': config_templates, + 'cluster_to_lines': config_cluster_to_lines, + 'cluster_ids': config_cluster_ids, + }, + } + + +def get_lines_for_clusters( + lines: List[str], + event_ids: List, + cluster_ids: set, +) -> Tuple[List[str], List]: + """Get lines and event_ids that belong to specific clusters.""" + filtered_lines = [] + filtered_event_ids = [] + + for line, eid in zip(lines, event_ids): + if eid in cluster_ids: + filtered_lines.append(line) + filtered_event_ids.append(eid) + + return filtered_lines, filtered_event_ids + + +def merge_template_results( + results_list: List[Dict], +) -> Dict: + """ + Merge multiple template results into one. + Each result has: templates, cluster_to_lines, cluster_ids + """ + merged_templates = [] + merged_cluster_to_lines = {} + merged_cluster_ids = set() + + for result in results_list: + merged_templates.extend(result.get('templates', [])) + merged_cluster_to_lines.update(result.get('cluster_to_lines', {})) + merged_cluster_ids.update(result.get('cluster_ids', set())) + + return { + 'templates': merged_templates, + 'cluster_to_lines': merged_cluster_to_lines, + 'cluster_ids': merged_cluster_ids, + } + + +# ============================================================================= +# FREQUENCY-BASED TEMPLATE CLASSIFICATION (PARETO/80-20 RULE) +# ============================================================================= + +def classify_templates_by_frequency( + template_list: List[Dict], + cluster_to_lines: Dict[int, List[str]], + freq_config: FrequencyConfig, +) -> Dict[str, Dict]: + """ + Classify templates into high-frequency vs rare using Pareto/80-20 rule. + + High-frequency: Templates that cumulatively account for X% of total error lines. + These are repetitive/known issues - only 1 sample needed. + + Rare/Priority: Templates outside the high-frequency threshold. + These need full investigation - show all lines. + + Algorithm: + 1. Sort templates by size (descending) + 2. Cumulative sum until threshold (e.g., 80%) is reached + 3. Templates in cumulative sum = high frequency + 4. Remaining templates = rare/priority + + Args: + template_list: List of template dicts with 'id', 'size', 'template' keys + cluster_to_lines: Mapping of cluster_id -> original log lines + freq_config: Frequency configuration settings + + Returns: + Dict with keys 'high_freq' and 'rare', each containing: + - templates: List of template dicts (sorted by size desc) + - cluster_to_lines: Dict of cluster_id -> lines + - cluster_ids: Set of cluster IDs + - total_lines: Total line count for this category + - stats: Additional statistics (percentage coverage, etc.) + """ + if not template_list: + empty_result = { + 'templates': [], + 'cluster_to_lines': {}, + 'cluster_ids': set(), + 'total_lines': 0, + 'stats': {'template_count': 0, 'line_coverage_pct': 0.0}, + } + return {'high_freq': empty_result, 'rare': empty_result} + + # Sort templates by size (descending) for Pareto analysis + sorted_templates = sorted(template_list, key=lambda x: x['size'], reverse=True) + + # Calculate total lines across all templates + total_lines = sum(t['size'] for t in sorted_templates) + + if total_lines == 0: + empty_result = { + 'templates': [], + 'cluster_to_lines': {}, + 'cluster_ids': set(), + 'total_lines': 0, + 'stats': {'template_count': 0, 'line_coverage_pct': 0.0}, + } + return {'high_freq': empty_result, 'rare': empty_result} + + # Determine bifurcation strategy + use_count_based = (freq_config.rare_max_lines_per_template is not None) + count_threshold = freq_config.rare_max_lines_per_template or 15 + + high_freq_templates = [] + high_freq_cluster_ids = set() + rare_templates = [] + rare_cluster_ids = set() + + if use_count_based: + # ── COUNT-BASED: templates with > N lines = high-freq, <= N = rare ── + for tmpl in sorted_templates: + cluster_id = tmpl['id'] + template_size = tmpl['size'] + + if template_size > count_threshold: + high_freq_templates.append(tmpl) + high_freq_cluster_ids.add(cluster_id) + else: + rare_templates.append(tmpl) + rare_cluster_ids.add(cluster_id) + else: + # ── PARETO-BASED (fallback): cumulative coverage threshold ── + threshold_lines = total_lines * freq_config.high_freq_cumulative_threshold + cumulative_lines = 0 + + for tmpl in sorted_templates: + cluster_id = tmpl['id'] + template_size = tmpl['size'] + + # Check if this template should be forced into "rare" category + # based on absolute size threshold (if configured) + if (freq_config.rare_absolute_max_size is not None + and template_size <= freq_config.rare_absolute_max_size): + rare_templates.append(tmpl) + rare_cluster_ids.add(cluster_id) + continue + + # Pareto classification + if cumulative_lines < threshold_lines: + high_freq_templates.append(tmpl) + high_freq_cluster_ids.add(cluster_id) + cumulative_lines += template_size + else: + rare_templates.append(tmpl) + rare_cluster_ids.add(cluster_id) + + # Split cluster_to_lines based on classification + high_freq_cluster_to_lines = { + cid: lines for cid, lines in cluster_to_lines.items() + if cid in high_freq_cluster_ids + } + rare_cluster_to_lines = { + cid: lines for cid, lines in cluster_to_lines.items() + if cid in rare_cluster_ids + } + + # Calculate stats + high_freq_lines = sum(t['size'] for t in high_freq_templates) + rare_lines = sum(t['size'] for t in rare_templates) + + return { + 'high_freq': { + 'templates': high_freq_templates, + 'cluster_to_lines': high_freq_cluster_to_lines, + 'cluster_ids': high_freq_cluster_ids, + 'total_lines': high_freq_lines, + 'stats': { + 'template_count': len(high_freq_templates), + 'line_coverage_pct': (high_freq_lines / total_lines * 100) if total_lines > 0 else 0, + }, + }, + 'rare': { + 'templates': rare_templates, + 'cluster_to_lines': rare_cluster_to_lines, + 'cluster_ids': rare_cluster_ids, + 'total_lines': rare_lines, + 'stats': { + 'template_count': len(rare_templates), + 'line_coverage_pct': (rare_lines / total_lines * 100) if total_lines > 0 else 0, + }, + }, + } + + +def save_high_freq_summary( + template_list: List[Dict], + cluster_to_lines: Dict[int, List[str]], + output_dir: str, + level_suffix: str, + filename_prefix: Optional[str] = None, + sample_count: int = 1, +) -> str: + """ + Save sampled log lines for high-frequency error templates. + + Instead of dumping every line (which can be 10k+), this applies + **per-template sampling**: for each template cluster, pick 7 + representative lines (first 3, middle 1, last 3) ordered by + timestamp. If a template has fewer than 7 lines, all lines are + kept. + + Additionally, a companion **template file** is saved alongside the + log-lines file. It lists each template string with its cluster + size, making it easy to see which patterns dominate. + + Args: + template_list: List of high-frequency templates (sorted by size desc) + cluster_to_lines: Mapping of cluster_id -> original lines + output_dir: Output directory + level_suffix: Level name (e.g., 'Error_HighFreq') + filename_prefix: Optional filename prefix + sample_count: Ignored (kept for backward compatibility) + + Returns: + Path to the saved sampled-log-lines file + """ + output_path = Path(output_dir) + output_path.mkdir(parents=True, exist_ok=True) + + if filename_prefix: + filename = output_path / f"{filename_prefix}_highfreq_error.txt" + template_filename = output_path / f"{filename_prefix}_highfreq_error_templates.txt" + else: + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = output_path / f"log_classification_highfreq_error_{timestamp}.txt" + template_filename = output_path / f"log_classification_highfreq_error_templates_{timestamp}.txt" + + # ── Per-template sampling: first 3, middle 1, last 3 ── + all_sampled_lines = [] + for tmpl in template_list: + cid = tmpl['id'] + lines = cluster_to_lines.get(cid, []) + if not lines: + continue + sampled = sample_lines_by_timestamp(lines, first_n=3, middle_n=1, last_n=3) + all_sampled_lines.extend(sampled) + + # Write sampled log lines + with open(filename, 'w', encoding='utf-8') as f: + for line in all_sampled_lines: + f.write(line + "\n") + + # ── Save companion template file ── + with open(template_filename, 'w', encoding='utf-8') as f: + f.write(f"# High-Frequency Error Templates ({len(template_list)} templates)\n") + f.write(f"# Each template shown with cluster size (number of matching log lines)\n") + f.write("=" * 80 + "\n\n") + for i, tmpl in enumerate(template_list, 1): + cid = tmpl['id'] + size = tmpl.get('size', len(cluster_to_lines.get(cid, []))) + template_text = tmpl.get('template', '(unknown)') + f.write(f"Template {i} | cluster_id={cid} | lines={size}\n") + f.write(f" {template_text}\n\n") + + return str(filename) + + +def save_rare_priority_templates( + template_list: List[Dict], + cluster_to_lines: Dict[int, List[str]], + logs: List[Tuple[str, str]], + event_ids: List, + output_dir: str, + level_suffix: str, + filename_prefix: Optional[str] = None, + max_original_lines_per_cluster: int = 5000, +) -> str: + """ + Save only the log lines for rare/priority error templates (no templates/headers). + These are the log lines from templates outside the Pareto threshold - priority for debugging. + Also saves a companion template file listing each template with its cluster size. + + Args: + template_list: List of rare templates (sorted by size desc) + cluster_to_lines: Mapping of cluster_id -> original lines + logs: List of (file_path, line) tuples (unused) + event_ids: Event IDs from Drain3 (unused) + output_dir: Output directory + level_suffix: Level name (e.g., 'Error_Rare') + filename_prefix: Optional filename prefix + max_original_lines_per_cluster: Ignored + + Returns: + Path to saved file + """ + output_path = Path(output_dir) + output_path.mkdir(parents=True, exist_ok=True) + + if filename_prefix: + filename = output_path / f"{filename_prefix}_rare_error.txt" + template_filename = output_path / f"{filename_prefix}_rare_error_templates.txt" + else: + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = output_path / f"log_classification_rare_error_{timestamp}.txt" + template_filename = output_path / f"log_classification_rare_error_templates_{timestamp}.txt" + + # Collect all log lines from all rare clusters + all_lines = [] + for tmpl in template_list: + cid = tmpl['id'] + lines = cluster_to_lines.get(cid, []) + all_lines.extend(lines) + + # Write only the log lines + with open(filename, 'w', encoding='utf-8') as f: + for line in all_lines: + f.write(line + "\n") + + # ── Save companion template file ── + with open(template_filename, 'w', encoding='utf-8') as f: + f.write(f"# Rare/Priority Error Templates ({len(template_list)} templates)\n") + f.write(f"# Each template shown with cluster size (number of matching log lines)\n") + f.write("=" * 80 + "\n\n") + for i, tmpl in enumerate(template_list, 1): + cid = tmpl['id'] + size = tmpl.get('size', len(cluster_to_lines.get(cid, []))) + template_text = tmpl.get('template', '(unknown)') + f.write(f"Template {i} | cluster_id={cid} | lines={size}\n") + f.write(f" {template_text}\n\n") + + return str(filename) + + +# ============================================================================= +# MAIN PROCESSING FUNCTION +# ============================================================================= +def process_log_file( + file_path: str, + config: Optional[PipelineConfig] = None, + config_path: Optional[str] = None, + **overrides +) -> Dict: + """ + Main function to process a single log file. + + Configuration is loaded in the following priority: + 1. Explicit config object passed as parameter + 2. Config loaded from config_path JSON file + 3. Config loaded from log_config.json (auto-detected) + 4. Default configuration + + Override parameters (passed as **kwargs) can override config values: + - output_dir: Output directory for template files + - use_chunk_pipeline: Force branch selection (None=auto, True=chunk, False=entire) + - parallel_chunks: For CHUNK pipeline: True = parallel, False = sequential + - chunk_size: Lines per chunk + - chunk_workers: Workers for parallel chunk processing + - file_size_threshold_mb: Threshold for auto branch selection + + TWO BRANCHES: + 1. ENTIRE FILE: Loads entire file into memory, processes at once + 2. CHUNK: Reads file in chunks, processes each chunk + - SEQUENTIAL: Chunks processed one at a time + - PARALLEL: Chunks processed in parallel + + Returns: + Dict with processing results: + - file_path: Input file path + - branch_used: 'ENTIRE_FILE' or 'CHUNK_SEQUENTIAL' or 'CHUNK_PARALLEL' + - file_size_mb: File size in MB + - elapsed_ms: Processing time + - saved_files: Dict of level -> output file path + - summary: Dict of level -> {count, templates_count, path} + - config_used: Configuration file path or 'default' + - error: Error message if failed + """ + if not DRAIN3_AVAILABLE: + return { + "file_path": file_path, + "error": "drain3 not installed. pip install drain3", + } + + # Validate file exists + if not Path(file_path).exists(): + return { + "file_path": file_path, + "error": f"File not found: {file_path}", + } + + # Load configuration + if config is not None: + pass # Use provided config + elif config_path: + config = load_config(config_path) + else: + config = get_config() + + # Apply overrides from kwargs + if 'output_dir' in overrides and overrides['output_dir'] is not None: + config.output_dir = overrides['output_dir'] + if 'use_chunk_pipeline' in overrides: + config.use_chunk_pipeline = overrides['use_chunk_pipeline'] + if 'parallel_chunks' in overrides: + config.parallel_chunks = overrides['parallel_chunks'] + if 'chunk_size' in overrides: + config.chunk_size = overrides['chunk_size'] + if 'chunk_workers' in overrides: + config.chunk_workers = overrides['chunk_workers'] + if 'file_size_threshold_mb' in overrides: + config.file_size_threshold_mb = overrides['file_size_threshold_mb'] + + # Set default output directory if not specified + if config.output_dir is None: + config.output_dir = str(Path(__file__).parent / "output") + + # Get file info + try: + file_size_mb = os.path.getsize(file_path) / (1024 * 1024) + except: + file_size_mb = 0 + + # Determine which branch to use + if config.use_chunk_pipeline is None: + # Auto-select based on file size + use_chunk = file_size_mb > config.file_size_threshold_mb + else: + use_chunk = config.use_chunk_pipeline + + # Determine branch name for reporting + if use_chunk: + branch_used = "CHUNK_PARALLEL" if config.parallel_chunks else "CHUNK_SEQUENTIAL" + else: + branch_used = "ENTIRE_FILE" + + level_names = {'E': 'Error', 'W': 'Warning', 'I': 'Information', 'UNKNOWN': 'Unknown'} + stem = Path(file_path).stem or Path(file_path).name + + if config.print_progress: + print(f"\n{'='*60}") + print(f"Processing: {Path(file_path).name}") + print(f"File size: {file_size_mb:.2f} MB") + print(f"Branch: {branch_used}") + if use_chunk: + print(f"Chunk size: {config.chunk_size:,} lines") + if config.parallel_chunks: + workers = config.chunk_workers or f"auto (up to {min(multiprocessing.cpu_count(), 8)})" + print(f"Chunk workers: {workers}") + print(f"{'='*60}") + + # ========================================================================= + # STEP 1: Read and Categorize (based on branch) + # ========================================================================= + start_time = time.perf_counter() + + try: + if use_chunk: + # BRANCH 2: CHUNK PIPELINE + by_level = chunk_pipeline(file_path, config) + else: + # BRANCH 1: ENTIRE FILE PIPELINE + by_level = entire_file_pipeline(file_path, config) + except Exception as e: + return { + 'file_path': file_path, + 'error': str(e), + 'branch_used': branch_used, + 'file_size_mb': file_size_mb, + } + + categorize_time = time.perf_counter() - start_time + + if not by_level: + return { + 'file_path': file_path, + 'error': None, + 'branch_used': branch_used, + 'file_size_mb': file_size_mb, + 'saved_files': {}, + 'summary': {}, + 'elapsed_ms': categorize_time * 1000, + } + + # ========================================================================= + # STEP 2: Process with Drain3 (initial pass for all levels) + # ========================================================================= + saved_files = {} + summary = {} + level_results = {} + + # First pass: Process all levels with Drain3 + for level in config.levels_to_process: + level_data = by_level.get(level, {}) + # Support both new format {"preprocessed": [...], "original": [...]} + # and old format (plain list) + if isinstance(level_data, dict) and "preprocessed" in level_data: + preprocessed_lines = level_data["preprocessed"] + original_lines = level_data["original"] + elif isinstance(level_data, list): + preprocessed_lines = level_data + original_lines = level_data # fallback: no originals available + else: + continue + if not preprocessed_lines: + continue + + # Process with Drain3 using PREPROCESSED lines (timestamp-free, better clustering) + cluster_sizes, template_list, event_ids = process_lines_with_drain3( + preprocessed_lines, config + ) + + # Build cluster-to-lines mapping using ORIGINAL lines (with timestamps) + # so output files contain the full original log lines + cluster_to_lines = build_cluster_to_lines(original_lines, event_ids) + + level_results[level] = { + 'lines': original_lines, # original lines for output + 'preprocessed_lines': preprocessed_lines, # kept for internal use + 'template_list': template_list, + 'cluster_sizes': cluster_sizes, + 'event_ids': event_ids, + 'cluster_to_lines': cluster_to_lines, + } + + # ========================================================================= + # STEP 3: Classify templates in W/I/U -> Error templates, Config templates, Regular + # (Working at TEMPLATE level - entire clusters move together) + # ========================================================================= + classified_results = {} + error_templates_to_merge = [] # Error templates found in W/I/U + config_templates_to_merge = [] # Config templates found in W/I/U + + for level in ['W', 'I', 'UNKNOWN']: + if level not in level_results: + continue + + result = level_results[level] + template_list = result['template_list'] + cluster_to_lines = result['cluster_to_lines'] + lines = result['lines'] + event_ids = result['event_ids'] + + # Classify templates into error, config, regular + classification = classify_templates_for_level( + level, template_list, cluster_to_lines, lines, event_ids + ) + + classified_results[level] = classification + + # Collect error templates to merge into Error level + if classification['error']['templates']: + error_templates_to_merge.append({ + 'source_level': level, + **classification['error'] + }) + if config.print_progress: + error_count = sum(len(v) for v in classification['error']['cluster_to_lines'].values()) + print(f" [Shift->Error] {len(classification['error']['templates'])} templates ({error_count} lines) from {level_names.get(level, level)}") + + # Collect config templates + if classification['config']['templates']: + config_templates_to_merge.append({ + 'source_level': level, + **classification['config'] + }) + if config.print_progress: + config_count = sum(len(v) for v in classification['config']['cluster_to_lines'].values()) + print(f" [Shift->Config] {len(classification['config']['templates'])} templates ({config_count} lines) from {level_names.get(level, level)}") + + # Update level results with only regular templates + regular = classification['regular'] + regular_lines, regular_event_ids = get_lines_for_clusters( + lines, event_ids, regular['cluster_ids'] + ) + + level_results[level] = { + 'lines': regular_lines, + 'template_list': regular['templates'], + 'cluster_sizes': {t['id']: t['size'] for t in regular['templates']}, + 'event_ids': regular_event_ids, + 'cluster_to_lines': regular['cluster_to_lines'], + } + + # ========================================================================= + # STEP 4: Merge error templates from W/I/U into Error level + # (Keep Drain3 templates - no re-processing needed) + # ========================================================================= + if error_templates_to_merge: + # Get existing Error level data + existing_error = level_results.get('E', { + 'lines': [], 'template_list': [], 'cluster_sizes': {}, + 'event_ids': [], 'cluster_to_lines': {} + }) + + # Merge error templates from W/I/U + merged_error_templates = list(existing_error.get('template_list', [])) + merged_error_cluster_to_lines = dict(existing_error.get('cluster_to_lines', {})) + merged_error_lines = list(existing_error.get('lines', [])) + merged_error_event_ids = list(existing_error.get('event_ids', [])) + + for error_data in error_templates_to_merge: + merged_error_templates.extend(error_data['templates']) + merged_error_cluster_to_lines.update(error_data['cluster_to_lines']) + # Get lines for these clusters from original data + source_level = error_data['source_level'] + if source_level in classified_results: + orig_lines = by_level.get(source_level, []) + orig_result = level_results.get(source_level, {}) + # Add lines belonging to error clusters + for cid, clines in error_data['cluster_to_lines'].items(): + merged_error_lines.extend(clines) + merged_error_event_ids.extend([cid] * len(clines)) + + level_results['E'] = { + 'lines': merged_error_lines, + 'template_list': merged_error_templates, + 'cluster_sizes': {t['id']: t['size'] for t in merged_error_templates}, + 'event_ids': merged_error_event_ids, + 'cluster_to_lines': merged_error_cluster_to_lines, + } + + # ========================================================================= + # STEP 5: Save templates for each level (filtered - without error/config templates) + # For Error level: apply Pareto/80-20 frequency classification + # ========================================================================= + frequency_stats = {} # Store frequency classification stats for return + + for level in config.levels_to_process: + if level not in level_results: + continue + + result = level_results[level] + lines = result.get('lines', []) + template_list = result.get('template_list', []) + cluster_sizes = result.get('cluster_sizes', {}) + event_ids = result.get('event_ids', []) + cluster_to_lines = result.get('cluster_to_lines', {}) + + if not template_list: + continue + + level_name = level_names.get(level, level) + + # Prepare logs tuple format for save_templates_to_file + level_logs = [(file_path, line) for line in lines] + + # ===================================================================== + # SPECIAL HANDLING FOR ERROR LEVEL: Tiered Output (Pareto/80-20) + # ===================================================================== + if level == 'E' and config.frequency.enable_tiered_error_output: + # Classify error templates by frequency + freq_classification = classify_templates_by_frequency( + template_list, cluster_to_lines, config.frequency + ) + + high_freq = freq_classification['high_freq'] + rare = freq_classification['rare'] + + # Store stats for return value + frequency_stats['Error'] = { + 'high_freq': high_freq['stats'], + 'rare': rare['stats'], + 'threshold_pct': config.frequency.high_freq_cumulative_threshold * 100, + } + + saved_error_paths = {} + + # Save HIGH-FREQUENCY templates (all lines included) + if high_freq['templates']: + high_freq_path = save_high_freq_summary( + high_freq['templates'], + high_freq['cluster_to_lines'], + config.output_dir, + level_name, + filename_prefix=stem, + ) + saved_error_paths['high_freq'] = high_freq_path + + if config.print_progress: + pct = high_freq['stats']['line_coverage_pct'] + print(f" [highfreq_error] {high_freq['total_lines']:,} lines -> " + f"{len(high_freq['templates'])} templates ({pct:.1f}% coverage)") + + # Save RARE/PRIORITY templates (full detail) + if rare['templates']: + rare_logs = [(file_path, line) for cid in rare['cluster_ids'] + for line in rare['cluster_to_lines'].get(cid, [])] + rare_event_ids = [cid for cid in rare['cluster_ids'] + for _ in rare['cluster_to_lines'].get(cid, [])] + + rare_path = save_rare_priority_templates( + rare['templates'], + rare['cluster_to_lines'], + rare_logs, + rare_event_ids, + config.output_dir, + level_name, + filename_prefix=stem, + ) + saved_error_paths['rare'] = rare_path + + if config.print_progress: + pct = rare['stats']['line_coverage_pct'] + print(f" [rare_error] {rare['total_lines']:,} lines -> " + f"{len(rare['templates'])} templates ({pct:.1f}% coverage)") + + # Also save combined Error file for backward compatibility + saved_path = save_templates_to_file( + template_list, cluster_sizes, level_logs, event_ids, + config.output_dir, level_name, filename_prefix=stem, + cluster_to_lines=cluster_to_lines, + ) + saved_error_paths['combined'] = saved_path + + saved_files[level_name] = saved_error_paths + summary[level_name] = { + 'count': len(lines), + 'templates_count': len(template_list), + 'paths': saved_error_paths, + 'frequency_classification': { + 'high_freq_templates': len(high_freq['templates']), + 'high_freq_lines': high_freq['total_lines'], + 'rare_templates': len(rare['templates']), + 'rare_lines': rare['total_lines'], + 'threshold_pct': config.frequency.high_freq_cumulative_threshold * 100, + }, + } + + if config.print_progress: + print(f" [Error] {len(lines):,} logs -> {len(template_list)} templates (combined)") + + else: + # ===================================================================== + # STANDARD HANDLING FOR OTHER LEVELS (W, I, UNKNOWN) + # ===================================================================== + # Save templates with all original lines + saved_path = save_templates_to_file( + template_list, cluster_sizes, level_logs, event_ids, + config.output_dir, level_name, filename_prefix=stem, + cluster_to_lines=cluster_to_lines, + ) + + saved_files[level_name] = saved_path + summary[level_name] = { + 'count': len(lines), + 'templates_count': len(template_list), + 'path': saved_path, + } + + if config.print_progress: + print(f" [{level_name}] {len(lines):,} logs -> {len(template_list)} templates") + + # ========================================================================= + # STEP 6: Save ConfigChanges templates (from W/I/U - keep Drain3 templates) + # ========================================================================= + if config_templates_to_merge: + # Merge all config templates (keeping Drain3 structure) + merged_config = merge_template_results([ + {'templates': d['templates'], 'cluster_to_lines': d['cluster_to_lines'], 'cluster_ids': d['cluster_ids']} + for d in config_templates_to_merge + ]) + + config_template_list = merged_config['templates'] + config_cluster_to_lines = merged_config['cluster_to_lines'] + + # Get all config lines + all_config_lines = [] + config_event_ids = [] + for cid, clines in config_cluster_to_lines.items(): + all_config_lines.extend(clines) + config_event_ids.extend([cid] * len(clines)) + + config_cluster_sizes = {t['id']: t['size'] for t in config_template_list} + + # Prepare logs tuple format + config_logs = [(file_path, line) for line in all_config_lines] + + # Save config changes templates (all lines included) + config_saved_path = save_templates_to_file( + config_template_list, config_cluster_sizes, config_logs, config_event_ids, + config.output_dir, "ConfigChanges", filename_prefix=stem, + cluster_to_lines=config_cluster_to_lines, + ) + + saved_files['ConfigChanges'] = config_saved_path + source_levels_list = [d['source_level'] for d in config_templates_to_merge] + summary['ConfigChanges'] = { + 'count': len(all_config_lines), + 'templates_count': len(config_template_list), + 'path': config_saved_path, + 'source_levels': source_levels_list, + } + + if config.print_progress: + source_levels_str = ', '.join(level_names.get(l, l) for l in source_levels_list) + print(f" [ConfigChanges] {len(all_config_lines):,} logs -> {len(config_template_list)} templates (from {source_levels_str})") + + elapsed_ms = (time.perf_counter() - start_time) * 1000 + + # Calculate stats for return value + shifted_error_count = sum( + sum(len(v) for v in d['cluster_to_lines'].values()) + for d in error_templates_to_merge + ) if error_templates_to_merge else 0 + + config_by_level = { + level_names.get(d['source_level'], d['source_level']): sum(len(v) for v in d['cluster_to_lines'].values()) + for d in config_templates_to_merge + } if config_templates_to_merge else {} + + if config.print_progress: + print(f"\nCompleted in {elapsed_ms:.2f} ms") + print(f"Output: {config.output_dir}") + if shifted_error_count > 0: + print(f"Note: {shifted_error_count} lines shifted from W/I/Unknown to Error") + if frequency_stats: + err_stats = frequency_stats.get('Error', {}) + if err_stats: + hf = err_stats.get('high_freq', {}) + rare = err_stats.get('rare', {}) + print(f"Error frequency split: {hf.get('template_count', 0)} high-freq templates " + f"({hf.get('line_coverage_pct', 0):.1f}%), " + f"{rare.get('template_count', 0)} rare/priority templates " + f"({rare.get('line_coverage_pct', 0):.1f}%)") + + return { + 'file_path': file_path, + 'error': None, + 'branch_used': branch_used, + 'file_size_mb': file_size_mb, + 'elapsed_ms': elapsed_ms, + 'saved_files': saved_files, + 'summary': summary, + 'shifted_errors_count': shifted_error_count, + 'config_changes_by_level': config_by_level, + 'frequency_stats': frequency_stats, + } + + +# ============================================================================= +# AGENT INTERFACE - SINGLE POINT OF CONTACT +# ============================================================================= + +def analyze_logs( + file_path: str, + output_dir: Optional[str] = None, + config_path: Optional[str] = None, +) -> Dict: + """ + AGENT INTERFACE: Single point of contact for log analysis. + + All settings are controlled via log_config.json for consistent configuration. + + Args: + file_path: Path to the log file to analyze + output_dir: Output directory for generated files (None = use config) + config_path: Path to config JSON file (None = auto-detect) + + Returns: + Production-grade JSON response: + { + "status": "success" | "error", + "error": null | "error message", + "input": { + "file": "/path/to/input.log", + "size_mb": 12.5 + }, + "output_dir": "/path/to/output", + "elapsed_ms": 1234.56, + "files": { + "rare_errors": {"path": "...", "templates": 45, "lines": 1500}, + "highfreq_errors": {"path": "...", "templates": 5, "lines": 8500}, + "warnings": {"path": "...", "templates": 12, "lines": 340}, + "information": {"path": "...", "templates": 8, "lines": 200}, + "unknown": {"path": "...", "templates": 3, "lines": 50}, + "config_changes": {"path": "...", "templates": 7, "lines": 120} + }, + "totals": { + "templates": 80, + "lines": 10710 + }, + "error_analysis": { + "pareto_threshold": 0.80, + "highfreq": {"templates": 5, "lines": 8500, "coverage_pct": 85.0}, + "rare": {"templates": 45, "lines": 1500, "coverage_pct": 15.0} + } + } + """ + # Input validation + if not file_path: + return { + 'status': 'error', + 'error': 'file_path is required', + 'input': {'file': None, 'size_mb': 0}, + 'output_dir': None, + 'elapsed_ms': 0, + 'files': {}, + 'totals': {'templates': 0, 'lines': 0}, + 'error_analysis': None + } + + file_path = str(Path(file_path).resolve()) + + if not Path(file_path).exists(): + return { + 'status': 'error', + 'error': f'File not found: {file_path}', + 'input': {'file': file_path, 'size_mb': 0}, + 'output_dir': None, + 'elapsed_ms': 0, + 'files': {}, + 'totals': {'templates': 0, 'lines': 0}, + 'error_analysis': None + } + + if not DRAIN3_AVAILABLE: + return { + 'status': 'error', + 'error': 'drain3 library not installed. Install with: pip install drain3', + 'input': {'file': file_path, 'size_mb': 0}, + 'output_dir': None, + 'elapsed_ms': 0, + 'files': {}, + 'totals': {'templates': 0, 'lines': 0}, + 'error_analysis': None + } + + # Load configuration (all flags come from config file) + try: + config = load_config(config_path) + except Exception as e: + return { + 'status': 'error', + 'error': f'Failed to load configuration: {str(e)}', + 'input': {'file': file_path, 'size_mb': 0}, + 'output_dir': None, + 'elapsed_ms': 0, + 'files': {}, + 'totals': {'templates': 0, 'lines': 0}, + 'error_analysis': None + } + + # Apply output directory override + if output_dir: + config.output_dir = output_dir + + # Get flags from config + flags = config.output_flags + include_rare_errors = flags.include_rare_errors + include_highfreq_errors = flags.include_highfreq_errors + include_warnings = flags.include_warnings + include_information = flags.include_information + include_unknown = flags.include_unknown + include_config_changes = flags.include_config_changes + + # Determine which levels to process based on config flags + levels_needed = set() + if include_rare_errors or include_highfreq_errors: + levels_needed.add('E') + if include_warnings: + levels_needed.add('W') + if include_information: + levels_needed.add('I') + if include_unknown: + levels_needed.add('UNKNOWN') + if include_config_changes: + levels_needed.update(['W', 'I', 'UNKNOWN']) + + config.levels_to_process = list(levels_needed) if levels_needed else ['E', 'W', 'I', 'UNKNOWN'] + config.frequency.enable_tiered_error_output = include_rare_errors or include_highfreq_errors + + # Run processing with error handling + try: + result = process_log_file(file_path, config=config) + except Exception as e: + return { + 'status': 'error', + 'error': f'Processing failed: {str(e)}', + 'input': {'file': file_path, 'size_mb': 0}, + 'output_dir': config.output_dir, + 'elapsed_ms': 0, + 'files': {}, + 'totals': {'templates': 0, 'lines': 0}, + 'error_analysis': None + } + + # Error response from process_log_file + if result.get('error'): + return { + 'status': 'error', + 'error': result['error'], + 'input': {'file': file_path, 'size_mb': result.get('file_size_mb', 0)}, + 'output_dir': config.output_dir, + 'elapsed_ms': result.get('elapsed_ms', 0), + 'files': {}, + 'totals': {'templates': 0, 'lines': 0}, + 'error_analysis': None + } + + # Build file outputs + files = {} + total_templates = 0 + total_lines = 0 + + saved_files = result.get('saved_files', {}) + summary = result.get('summary', {}) + + # Error outputs (tiered) + error_data = saved_files.get('Error', {}) + error_summary = summary.get('Error', {}) + freq_class = error_summary.get('frequency_classification', {}) if isinstance(error_summary, dict) else {} + + if isinstance(error_data, dict): + if include_rare_errors and 'rare' in error_data: + t_count = freq_class.get('rare_templates', 0) + l_count = freq_class.get('rare_lines', 0) + files['rare_errors'] = {'path': error_data['rare'], 'templates': t_count, 'lines': l_count} + total_templates += t_count + total_lines += l_count + + if include_highfreq_errors and 'high_freq' in error_data: + t_count = freq_class.get('high_freq_templates', 0) + l_count = freq_class.get('high_freq_lines', 0) + files['highfreq_errors'] = {'path': error_data['high_freq'], 'templates': t_count, 'lines': l_count} + total_templates += t_count + total_lines += l_count + elif isinstance(error_data, str) and (include_rare_errors or include_highfreq_errors): + t_count = error_summary.get('templates_count', 0) + l_count = error_summary.get('count', 0) + files['errors'] = {'path': error_data, 'templates': t_count, 'lines': l_count} + total_templates += t_count + total_lines += l_count + + # Warning output + if include_warnings and 'Warning' in saved_files: + s = summary.get('Warning', {}) + t_count, l_count = s.get('templates_count', 0), s.get('count', 0) + files['warnings'] = {'path': saved_files['Warning'], 'templates': t_count, 'lines': l_count} + total_templates += t_count + total_lines += l_count + + # Information output + if include_information and 'Information' in saved_files: + s = summary.get('Information', {}) + t_count, l_count = s.get('templates_count', 0), s.get('count', 0) + files['information'] = {'path': saved_files['Information'], 'templates': t_count, 'lines': l_count} + total_templates += t_count + total_lines += l_count + + # Unknown output + if include_unknown and 'Unknown' in saved_files: + s = summary.get('Unknown', {}) + t_count, l_count = s.get('templates_count', 0), s.get('count', 0) + files['unknown'] = {'path': saved_files['Unknown'], 'templates': t_count, 'lines': l_count} + total_templates += t_count + total_lines += l_count + + # ConfigChanges output + if include_config_changes and 'ConfigChanges' in saved_files: + s = summary.get('ConfigChanges', {}) + t_count, l_count = s.get('templates_count', 0), s.get('count', 0) + files['config_changes'] = {'path': saved_files['ConfigChanges'], 'templates': t_count, 'lines': l_count} + total_templates += t_count + total_lines += l_count + + # Error analysis (Pareto stats) + error_analysis = None + freq_stats = result.get('frequency_stats', {}).get('Error', {}) + if freq_stats: + hf = freq_stats.get('high_freq', {}) + rare = freq_stats.get('rare', {}) + error_analysis = { + 'pareto_threshold': config.frequency.high_freq_cumulative_threshold, + 'highfreq': { + 'templates': hf.get('template_count', 0), + 'lines': freq_class.get('high_freq_lines', 0), + 'coverage_pct': round(hf.get('line_coverage_pct', 0), 1) + }, + 'rare': { + 'templates': rare.get('template_count', 0), + 'lines': freq_class.get('rare_lines', 0), + 'coverage_pct': round(rare.get('line_coverage_pct', 0), 1) + } + } + + return { + 'status': 'success', + 'error': None, + 'input': { + 'file': file_path, + 'size_mb': round(result.get('file_size_mb', 0), 2) + }, + 'output_dir': config.output_dir, + 'elapsed_ms': round(result.get('elapsed_ms', 0), 2), + 'files': files, + 'totals': { + 'templates': total_templates, + 'lines': total_lines + }, + 'error_analysis': error_analysis + } + + +# ============================================================================= +# UTILITY FUNCTIONS +# ============================================================================= +def get_file_info(file_path: str) -> Dict: + """Get file size and line count info.""" + try: + size_bytes = os.path.getsize(file_path) + size_mb = size_bytes / (1024 * 1024) + + with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: + line_count = sum(1 for _ in f) + + return { + 'path': file_path, + 'name': Path(file_path).name, + 'size_mb': size_mb, + 'line_count': line_count, + } + except Exception as e: + return { + 'path': file_path, + 'name': Path(file_path).name, + 'size_mb': 0, + 'line_count': 0, + 'error': str(e), + } + + +def compare_branches(file_path: str, output_base: str = None) -> Dict: + """ + Compare all three processing modes on the same file. + + Runs: + 1. ENTIRE_FILE branch + 2. CHUNK_SEQUENTIAL branch + 3. CHUNK_PARALLEL branch + + Returns comparison results with timing for each. + """ + if output_base is None: + output_base = str(Path(__file__).parent / "comparison_output") + + file_info = get_file_info(file_path) + print(f"\n{'='*80}") + print(f"BRANCH COMPARISON: {file_info['name']}") + print(f"Size: {file_info['size_mb']:.2f} MB, Lines: {file_info.get('line_count', 'N/A'):,}") + print(f"{'='*80}") + + results = {} + + # 1. ENTIRE FILE branch + print("\n[1/3] Running ENTIRE_FILE branch...") + results['entire_file'] = process_log_file( + file_path, + output_dir=f"{output_base}/entire_file", + use_chunk_pipeline=False, + ) + + # 2. CHUNK SEQUENTIAL branch + print("\n[2/3] Running CHUNK_SEQUENTIAL branch...") + results['chunk_sequential'] = process_log_file( + file_path, + output_dir=f"{output_base}/chunk_sequential", + use_chunk_pipeline=True, + parallel_chunks=False, + ) + + # 3. CHUNK PARALLEL branch + print("\n[3/3] Running CHUNK_PARALLEL branch...") + results['chunk_parallel'] = process_log_file( + file_path, + output_dir=f"{output_base}/chunk_parallel", + use_chunk_pipeline=True, + parallel_chunks=True, + ) + + # Print comparison + print(f"\n{'='*80}") + print("TIMING COMPARISON") + print(f"{'='*80}") + + for name, result in results.items(): + elapsed = result.get('elapsed_ms', 0) + branch = result.get('branch_used', 'N/A') + print(f" {name:20s}: {elapsed:>10.2f} ms ({branch})") + + # Find fastest + times = {k: v.get('elapsed_ms', float('inf')) for k, v in results.items()} + fastest = min(times, key=times.get) + print(f"\n FASTEST: {fastest} ({times[fastest]:.2f} ms)") + + return { + 'file_info': file_info, + 'results': results, + 'fastest': fastest, + } + + +# ============================================================================= +# MAIN +# ============================================================================= +def main(): + """ + Main function demonstrating the pipeline. + + Usage: + python testing-log.py # Use default test file with analyze_logs + python testing-log.py file.log # Process single file with analyze_logs + python testing-log.py file.log --analyze # Use analyze_logs (default, explicit) + python testing-log.py file.log --legacy # Use legacy process_log_file + python testing-log.py file.log --compare # Compare all branches + python testing-log.py file.log --config path # Use specific config file + python testing-log.py file.log --output dir # Specify output directory + python testing-log.py file.log --chunk # Force chunk pipeline (legacy mode) + python testing-log.py file.log --entire # Force entire file pipeline (legacy mode) + python testing-log.py file.log --sequential # Force sequential chunk processing (legacy mode) + """ + import sys + import json as json_module + + script_dir = Path(__file__).parent + + # Parse arguments + args = sys.argv[1:] + + # Parse flags + analyze_mode = '--analyze' in args or '--legacy' not in args # Default to analyze mode + legacy_mode = '--legacy' in args + compare_mode = '--compare' in args + force_chunk = '--chunk' in args + force_entire = '--entire' in args + force_sequential = '--sequential' in args + + # Parse --config and --output options + config_path = None + output_dir = None + + filtered_args = [] + i = 0 + while i < len(args): + if args[i] == '--config' and i + 1 < len(args): + config_path = args[i + 1] + i += 2 + elif args[i] == '--output' and i + 1 < len(args): + output_dir = args[i + 1] + i += 2 + elif args[i] not in ['--compare', '--chunk', '--entire', '--sequential', '--analyze', '--legacy']: + filtered_args.append(args[i]) + i += 1 + else: + i += 1 + + args = filtered_args + + # Get file path + if args: + file_path = str(Path(args[0]).resolve()) + else: + # Default test files + default_files = [ + script_dir / "current.log", + script_dir / 'crio_service.log', + script_dir / "kubelet_service.log", + ] + for f in default_files: + if f.exists(): + file_path = str(f) + break + else: + print("Error: No log file found.") + print("\nUsage: python testing-log.py [OPTIONS]") + print("\nOptions:") + print(" --analyze Use analyze_logs function (default)") + print(" --legacy Use legacy process_log_file function") + print(" --config Use specific config file") + print(" --output Specify output directory") + print(" --compare Compare all branches (legacy mode)") + print(" --chunk Force chunk pipeline (legacy mode)") + print(" --entire Force entire file pipeline (legacy mode)") + print(" --sequential Force sequential chunk processing (legacy mode)") + sys.exit(1) + + # Validate file exists + if not Path(file_path).exists(): + print(f"Error: File not found: {file_path}") + sys.exit(1) + + # Check drain3 + if not DRAIN3_AVAILABLE: + print("Error: drain3 is not installed. Install with: pip install drain3") + sys.exit(1) + + # ========================================================================= + # MODE: analyze_logs (NEW - default) + # ========================================================================= + if analyze_mode and not legacy_mode and not compare_mode: + print("\n" + "=" * 60) + print("ANALYZE LOGS MODE (Agent Interface)") + print("=" * 60) + print(f"File: {file_path}") + print(f"Config: {config_path or 'auto-detect'}") + print(f"Output: {output_dir or 'auto'}") + print("=" * 60 + "\n") + + # Call analyze_logs + result = analyze_logs( + file_path=file_path, + output_dir=output_dir, + config_path=config_path, + ) + + # Print JSON result + print("\n" + "=" * 60) + print("JSON RESPONSE") + print("=" * 60) + print(json_module.dumps(result, indent=2)) + print("=" * 60) + + if result['status'] == 'error': + sys.exit(1) + + # ========================================================================= + # MODE: Legacy (process_log_file) + # ========================================================================= + else: + # Load config (will auto-detect log_config.json if config_path not specified) + config = load_config(config_path) + + # Apply command-line overrides + overrides = {} + if output_dir: + overrides['output_dir'] = output_dir + if force_chunk: + overrides['use_chunk_pipeline'] = True + if force_entire: + overrides['use_chunk_pipeline'] = False + if force_sequential: + overrides['parallel_chunks'] = False + + if compare_mode: + # Compare all branches + compare_branches(file_path) + else: + # Process with config + result = process_log_file(file_path, config=config, **overrides) + if result.get('error'): + print(f"Error: {result['error']}") + sys.exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/intelliaide/Machine-learning/ML_YAML_CLASSIFICATION.py b/intelliaide/Machine-learning/ML_YAML_CLASSIFICATION.py new file mode 100644 index 0000000..0551d1a --- /dev/null +++ b/intelliaide/Machine-learning/ML_YAML_CLASSIFICATION.py @@ -0,0 +1,1210 @@ +""" +ML YAML Classification Module + +This module receives critical fields extracted from YAML objects (as JSON) +and classifies them using Drain3-based ML algorithm. +Only critical field values are processed - no line numbers are used. +""" + +import json +import re +from typing import List, Dict, Tuple, Optional, Any +from collections import Counter, defaultdict +from datetime import datetime +from pathlib import Path +import yaml +import os + +# QUIET MODE: Check if orchestrator has enabled quiet mode +def _is_quiet(): + """Check if orchestrator has enabled quiet mode via environment variable""" + return os.environ.get('ORCHESTRATOR_QUIET', '0') == '1' + +# LOAD ERROR PATTERN CONFIGURATION FROM CONFIG FILE + +def _load_error_pattern_config() -> Dict[str, Any]: + """Load error pattern configuration from config/yaml_processing.yaml""" + config_path = Path(__file__).parent / "config" / "yaml_processing.yaml" + + # Default fallback configuration + default_config = { + "error_patterns": { + "verbose_patterns": [ + "not progressing", "not available", "not ready", "not healthy", + "halted", "stopped", "degraded", "unavailable", "down", + "timeout", "timed out", "missing", "not found", "denied", + "forbidden", "stuck", "waiting" + ], + "explicit_keywords": ["error", "failed", "failure", "exception", "critical", "fatal"], + "excluded_key_patterns": ["failure-domain", "topology.kubernetes.io", + "node.kubernetes.io", "failurepolicy"] + }, + "classification": { + "common_pattern_threshold": 0.30, + "majority_threshold": 0.10, + "error_threshold": 0.01 + } + } + + # Try to load from config file + if config_path.exists(): + try: + with open(config_path, 'r') as f: + loaded_config = yaml.safe_load(f) + if loaded_config and "error_patterns" in loaded_config: + # Merge with defaults + default_config.update(loaded_config) + except Exception as e: + if not _is_quiet(): + print(f"[Warning] Could not load error patterns from config: {e}") + print("[Warning] Using default error patterns") + + return default_config + + +# Load configuration once at module import time +_ERROR_CONFIG = _load_error_pattern_config() + +# Create convenient accessors for use throughout the module +VERBOSE_ERROR_PATTERNS = _ERROR_CONFIG.get("error_patterns", {}).get("verbose_patterns", []) +EXPLICIT_ERROR_KEYWORDS = _ERROR_CONFIG.get("error_patterns", {}).get("explicit_keywords", []) +EXCLUDED_KEY_PATTERNS = _ERROR_CONFIG.get("error_patterns", {}).get("excluded_key_patterns", []) + +def flatten_critical_fields_to_string(critical_fields_data: Dict[str, Any]) -> str: + """ + Flatten critical fields JSON structure into a string for Drain3 processing. + Recursively extracts all key-value pairs from nested JSON structure. + Line numbers are excluded - only field paths and values are used. + + Args: + critical_fields_data: Dictionary containing critical fields (nested structure) + Example: {"metadata": {"name": "authentication"}, + "status": {"conditions": [...]}} + + Returns: + Flattened string representation: "metadata.name: authentication, status.conditions.type: Degraded, ..." + """ + kv_pairs = [] + + def extract_kv_pairs(obj: Any, prefix: str = ""): + """Recursively extract key-value pairs from nested structure.""" + if isinstance(obj, dict): + # Check if this dict represents a semantic unit (type+status pair) + if _is_semantic_unit(obj): + # Combine related fields into one string + combined = _combine_semantic_fields(obj) + if combined: + kv_pairs.append(f"{prefix}: {combined}") + return # Don't recurse further + + # Normal dict processing + for key, value in obj.items(): + if key == "_line_numbers" or key.endswith("_line_numbers"): + continue + + current_path = f"{prefix}.{key}" if prefix else key + + if isinstance(value, dict): + extract_kv_pairs(value, current_path) + elif isinstance(value, list): + for idx, item in enumerate(value): + if isinstance(item, dict): + extract_kv_pairs(item, f"{current_path}[{idx}]") + else: + kv_pairs.append(f"{current_path}[{idx}]: {item}") + else: + kv_pairs.append(f"{current_path}: {value}") + + extract_kv_pairs(critical_fields_data) + return ", ".join(kv_pairs) + + +def _is_semantic_unit(obj: dict) -> bool: + """ + Check if a dict represents a semantic unit that should be combined. + Generic heuristic: If dict has 'type' + 'status' keys, it's likely a condition. + Or 'key' + 'value', 'name' + 'value', etc. + """ + keys = set(obj.keys()) - {'_line_numbers'} # Ignore metadata + + # Common patterns for semantic units: + # 1. type + status (Kubernetes conditions) + if 'type' in keys and 'status' in keys: + return True + + # 2. key + value (ConfigMaps, env vars) + if 'key' in keys and 'value' in keys: + return True + + # 3. name + value (labels, annotations) + if 'name' in keys and 'value' in keys: + return True + + return False + +def _combine_semantic_fields(obj: dict) -> str: + """ + Combine related fields into a single meaningful string. + Examples: + {"type": "Degraded", "status": "False"} → "Degraded=False" + {"key": "LOG_LEVEL", "value": "debug"} → "LOG_LEVEL=debug" + """ + # Remove metadata keys + clean_obj = {k: v for k, v in obj.items() if not k.endswith('_line_numbers')} + + # Pattern 1: type + status (most common) + if 'type' in clean_obj and 'status' in clean_obj: + type_val = clean_obj['type'] + status_val = clean_obj['status'] + # Include message/reason if present (but keep them short) + extras = [] + if 'reason' in clean_obj: + extras.append(f"reason={clean_obj['reason']}") + if 'message' in clean_obj: + # Truncate long messages + msg = str(clean_obj['message'])[:50] + extras.append(f"message={msg}") + + extra_str = f" ({', '.join(extras)})" if extras else "" + return f"{type_val}={status_val}{extra_str}" + + # Pattern 2: key + value + if 'key' in clean_obj and 'value' in clean_obj: + return f"{clean_obj['key']}={clean_obj['value']}" + + # Pattern 3: name + value + if 'name' in clean_obj and 'value' in clean_obj: + return f"{clean_obj['name']}={clean_obj['value']}" + + # Fallback: just combine all key=value pairs + pairs = [f"{k}={v}" for k, v in clean_obj.items()] + return ", ".join(pairs) + +def process_objects_with_drain3(critical_fields_list: List[Dict[str, Any]]) -> Tuple[Dict, List[Dict], Dict]: + """ + Process critical fields from multiple objects using Drain3 to extract templates. + + Args: + critical_fields_list: List of critical fields dictionaries (one per object) + Each dict contains the extracted critical fields structure + + Returns: + Tuple of (cluster_info_dict, template_list, object_to_cluster) + """ + try: + from drain3 import TemplateMiner + from drain3.template_miner_config import TemplateMinerConfig + except ImportError: + print("Error: drain3 library not installed. Please install it using: pip install drain3") + raise + + # Configure Drain3 - create config programmatically + config = TemplateMinerConfig() + config.sim_th = 0.3 # Lower threshold for key-value pairs + config.depth = 4 + config.max_children = 100 + config.profiling_enabled = False + + # Set masking patterns + config.masking = [ + {"regex_pattern": r"(?<=:\s).+", "mask_with": "VALUE"}, # Mask all values after ": " + {"regex_pattern": r"\d+", "mask_with": "NUM"}, + {"regex_pattern": r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}", "mask_with": "UUID"}, + {"regex_pattern": r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}", "mask_with": "TIMESTAMP"}, + ] + + # Create miner with config + miner = TemplateMiner(config=config) + + # Flatten each object's critical fields to string + object_strings = [] + for critical_fields_data in critical_fields_list: + flattened = flatten_critical_fields_to_string(critical_fields_data) + object_strings.append(flattened) + + # Process each object string + cluster_sizes = {} + cluster_templates = {} + object_to_cluster = {} # Map object index to cluster_id + + for idx, obj_string in enumerate(object_strings): + if not obj_string.strip(): + continue + + # Add to Drain3 + result = miner.add_log_message(obj_string) + cluster_id = result.get("cluster_id") + + if cluster_id: + object_to_cluster[idx] = cluster_id + + # Get all clusters with their templates + clusters = miner.drain.clusters + if not isinstance(clusters, dict): + clusters = {cid: cluster for cid, cluster in enumerate(clusters)} if hasattr(clusters, '__iter__') else {} + + # Build cluster information + template_list = [] + for cluster_id, cluster in clusters.items(): + if hasattr(cluster, 'get_template') and hasattr(cluster, 'size'): + cluster_sizes[cluster_id] = cluster.size + template = cluster.get_template() + cluster_templates[cluster_id] = template + template_list.append({ + 'id': cluster_id, + 'template': template, + 'size': cluster.size + }) + else: + # Alternative access method + try: + size = cluster.size if hasattr(cluster, 'size') else 1 + template = cluster.get_template() if hasattr(cluster, 'get_template') else str(cluster) + cluster_sizes[cluster_id] = size + cluster_templates[cluster_id] = template + template_list.append({ + 'id': cluster_id, + 'template': template, + 'size': size + }) + except (AttributeError, TypeError) as e: + if not _is_quiet(): + print(f"[ML YAML] Fallback for cluster {cluster_id}: {e}") + cluster_sizes[cluster_id] = 1 + template = f"cluster_{cluster_id}" + cluster_templates[cluster_id] = template + template_list.append({ + 'id': cluster_id, + 'template': template, + 'size': 1 + }) + + return cluster_sizes, template_list, object_to_cluster + + +def extract_key_value_pairs_from_critical_fields(critical_fields_data: Dict[str, Any]) -> List[Tuple[str, str]]: + """ + Extract flat list of (key_path, value) pairs from critical fields structure. + No line numbers - only paths and values. + + Args: + critical_fields_data: Dictionary containing critical fields + + Returns: + List of (key_path, value) tuples + """ + kv_pairs = [] + + def extract_pairs(obj: Any, prefix: str = ""): + if isinstance(obj, dict): + # Check if semantic unit + if _is_semantic_unit(obj): + combined = _combine_semantic_fields(obj) + if combined: + kv_pairs.append((prefix, combined)) + return # Don't recurse + + # Normal processing + for key, value in obj.items(): + if key == "_line_numbers" or key.endswith("_line_numbers"): + continue + + current_path = f"{prefix}.{key}" if prefix else key + + if isinstance(value, dict): + extract_pairs(value, current_path) + elif isinstance(value, list): + for idx, item in enumerate(value): + if isinstance(item, dict): + extract_pairs(item, f"{current_path}[{idx}]") + else: + kv_pairs.append((f"{current_path}[{idx}]", str(item))) + else: + kv_pairs.append((current_path, str(value))) + + extract_pairs(critical_fields_data) + return kv_pairs + + +def detect_verbose_error(key: str, value: str) -> bool: + """ + Detect if a key-value pair indicates an error condition. + Uses generic linguistic patterns to detect positive vs negative contexts. + + Args: + key: The key path (e.g., "status.conditions.type") + value: The value + + Returns: + True if appears to be an error condition + """ + key_lower = key.lower() + value_lower = str(value).lower() + combined = f"{key_lower} {value_lower}" + + # Exclude known label/annotation patterns that contain error-like words but aren't errors + # Use patterns from config + excluded_key_patterns = EXCLUDED_KEY_PATTERNS + + for excluded_pattern in excluded_key_patterns: + if excluded_pattern in key_lower: + return False # Not an error, just a label/annotation name + + # Handle combined semantic values (e.g., "Degraded=False") + if '=' in value_lower: + # This is a combined field like "Degraded=False" or "Available=True" + # Split by the FIRST '=' only (in case message contains '=') + parts = value_lower.split('=', 1) + if len(parts) == 2: + field_name, field_value = parts[0].strip(), parts[1].strip() + + # Extract just the boolean part if there's extra context + # e.g., "false (reason=xyz)" → "false" + if '(' in field_value: + field_value = field_value.split('(')[0].strip() + + # Now check for error conditions based on field_name + field_value + + # 1. Degraded=True → ERROR + if field_name == 'degraded': + if field_value == 'true': + return True # Error condition + elif field_value == 'false': + return False # Healthy condition + + # 2. Available=False → ERROR + if field_name == 'available': + if field_value == 'false': + return True # Error condition + elif field_value == 'true': + return False # Healthy condition + + # 3. Ready=False → ERROR + if field_name == 'ready': + if field_value == 'false': + return True # Error condition + elif field_value == 'true': + return False # Healthy condition + + # 4. Healthy=False → ERROR + if field_name == 'healthy': + if field_value == 'false': + return True # Error condition + elif field_value == 'true': + return False # Healthy condition + + # 5. Progressing=True → MAYBE error (depends on context) + if field_name == 'progressing': + if field_value == 'true': + # Check if there's error context in the message/reason + if 'error' in value_lower or 'failed' in value_lower or 'unable' in value_lower: + return True # Error condition + # Otherwise, progressing is normal + return False + elif field_value == 'false': + return False # Not progressing is usually normal + + # 6. Failing=True → ERROR + if field_name == 'failing': + if field_value == 'true': + return True # Error condition + elif field_value == 'false': + return False # Healthy condition + + # 7. Upgradeable=False → MAYBE error (but often normal) + if field_name == 'upgradeable': + # Upgradeable=False is often normal during updates + # Only flag as error if there's explicit error context + if field_value == 'false' and ('error' in value_lower or 'failed' in value_lower): + return True + return False # Usually not an error + + # If we couldn't determine from the combined value, fall through to existing checks + + # Generic positive status words + positive_status_words = ['deployed', 'available', 'ready', 'healthy', 'running', 'active', + 'operational', 'functioning', 'working', 'completed', 'finished', + 'successful', 'successfully', 'done', 'replicas', 'replica'] + + # Generic negative status words + negative_status_words = ['unstarted', 'pending', 'waiting', 'stuck', 'hung', 'broken', + 'degraded', 'unhealthy', 'unavailable', 'down', 'halted', + 'stopped', 'paused', 'suspended', 'blocked'] + + # Negation words + negation_words = ['no', 'not', 'none', 'without', 'lack'] + + # Positive action verbs + positive_verbs = ['is', 'are', 'has', 'have', 'was', 'were'] + + # Check for explicit errors first + explicit_error_keywords = ['error', 'failed', 'failure', 'exception', 'critical', 'fatal'] + for keyword in explicit_error_keywords: + if keyword in combined: + return True + + # Check for positive status indicators with positive verbs + words = value_lower.split() + for i in range(len(words) - 1): + if words[i] in positive_verbs and any(pos_word in words[i+1] for pos_word in positive_status_words): + if i > 0 and words[i-1] in ['not', 'no']: + continue # "is not deployed" = error + return False # "is deployed" = positive, not an error + + # Check for negation + negative status word pattern + for i in range(len(words) - 1): + if words[i] in negation_words and any(neg_word in words[i+1] for neg_word in negative_status_words): + return False + + # Check for "all" + positive status + for i in range(len(words) - 1): + if words[i] == 'all' and any(pos_word in words[i+1] for pos_word in positive_status_words): + return False + + # Generic error patterns + error_patterns = [ + 'not progressing', 'not available', 'not ready', 'not healthy', + 'unable to reach', 'cannot connect', 'connection refused', 'connection failed', + 'out of memory', 'out of disk', 'resource exhausted', 'quota exceeded', + 'timeout', 'timed out', 'expired', + 'missing', 'not found', 'not exist', 'does not exist', + 'denied', 'forbidden', 'unauthorized', 'access denied', 'permission denied', + 'rejected', 'not allowed', + 'retry failed', 'retries exhausted', 'max retries', + ] + + # Check for error patterns + for pattern in error_patterns: + if pattern in value_lower: + return True + + # Check for negative status words + # BUT: Skip if value is in "key=value" format (already handled above) + if '=' not in value_lower: # Only check if NOT combined format + for neg_word in negative_status_words: + if neg_word in value_lower: + words_list = value_lower.split() + neg_index = -1 + for idx, word in enumerate(words_list): + if neg_word in word: + neg_index = idx + break + if neg_index > 0 and words_list[neg_index - 1] in negation_words: + continue # Negated, so it's positive + return True # Not negated, so it's an error + + # Check for status keys with negative values + # NOTE: "Status: False" and "Status: True" are common patterns and should NOT be treated as errors + # They are normal state indicators, not error conditions + status_keys = ['status', 'progressing', 'available', 'ready', 'healthy', 'condition'] + if any(status_key in key_lower for status_key in status_keys): + # "Status: True" is a positive indicator - never an error + if 'true' in value_lower or '1' in value_lower: + return False # "Status: True" is always positive, not an error + + # "Status: False" or "status: false" is a common pattern (e.g., "Progressing: False" means not progressing, which is normal) + # Only treat as error if combined with explicit error indicators + negative_values = ['false', '0', 'none', 'null'] + if any(neg_val in value_lower for neg_val in negative_values): + # Check if this is just a status indicator without error context + # "Status: False" alone is not an error - it's a normal state + # Only treat as error if there are additional error indicators + if 'degraded' in value_lower or 'error' in value_lower or 'failed' in value_lower: + return True + # "Status: False" without error context is NOT an error - return False + return False + + # "degraded" status is still an error indicator + if 'degraded' in value_lower: + if 'no' in value_lower: + words_list = value_lower.split() + for i in range(len(words_list) - 1): + if words_list[i] == 'no' and any(neg_word in words_list[i+1] for neg_word in negative_status_words): + return False # "no degraded" = positive + return True + + # Check for message/reason fields with error indicators + if 'message' in key_lower or 'reason' in key_lower: + error_indicators = ['unable', 'cannot', 'failed', 'error', 'issue', 'problem', + 'not working', 'not functioning', 'not responding'] + if any(indicator in value_lower for indicator in error_indicators): + return True + + return False + + +def detect_config_change(key: str, value: str) -> bool: + """ + Detect if a key-value pair indicates a configuration change. + + PRINCIPLE: Only flag changes to DESIRED STATE (spec, config, data) + NOT changes to OBSERVED STATE (status, metadata) + + Args: + key: The key path (e.g., "spec.image", "metadata.resourceVersion") + value: The value + + Returns: + True if appears to indicate a configuration change + """ + key_lower = key.lower() + value_lower = str(value).lower() + + # UNIVERSAL RULE: Config changes MUST be in user-controlled sections + # - spec.* = Desired state (Deployments, StatefulSets, etc.) + # - config.* = Configuration objects + # - data.* = ConfigMap/Secret data + # + # NOT in: + # - metadata.* = Kubernetes housekeeping (resourceVersion, uid, etc.) + # - status.* = Observed state (not desired state) + + # Extract the first section of the key path + path_parts = key_lower.split('.') + if len(path_parts) > 0: + first_section = path_parts[0] + + # EXCLUDE: metadata and status sections entirely + if first_section in ['metadata', 'status']: + return False # Housekeeping/observed state, not config + + # ONLY check for config changes in spec/config/data sections + if first_section not in ['spec', 'config', 'data']: + return False # Not a recognized config section + + # Now check for config change indicators within spec/config/data sections + + # Version/image changes (most common config changes) + if 'version' in key_lower or 'image' in key_lower: + return True + + # Resource scaling and limits + if any(indicator in key_lower for indicator in ['replicas', 'limits', 'requests', 'resources']): + return True + + # Environment variables, volumes, and mounts + if any(indicator in key_lower for indicator in ['env', 'volume', 'mount', 'secret', 'configmap']): + return True + + # Check if value contains change action verbs (e.g., "updated to version X") + change_verbs = ['update', 'change', 'modify', 'set', 'configure', 'adjust', + 'alter', 'edit', 'replace', 'switch', 'toggle', 'enable', 'disable'] + + if '=' not in value_lower: # Skip semantic values like "Available=True" + words = value_lower.split() + + for i in range(len(words)): + word = words[i] + + # Check if word contains a change verb + matched_verb = None + for verb in change_verbs: + if verb in word: + matched_verb = verb + break + + if matched_verb: + # Check for action context patterns + if i > 0 and words[i-1] in ['was', 'were', 'has', 'have', 'is', 'are', 'being', 'been']: + return True # e.g., "was updated", "has been changed" + + if i > 0 and words[i-1] == 'to': + return True # e.g., "updated to version 4.11" + + if i < len(words) - 1 and words[i+1] in ['to', 'from', 'with', 'version', 'image']: + return True # e.g., "changed to", "updated version" + + return False + +def analyze_verbose_error_patterns(critical_fields_list: List[Dict[str, Any]]) -> Dict[str, int]: + """ + Analyze all objects to find verbose error patterns and their frequencies. + + Args: + critical_fields_list: List of critical fields dictionaries (one per object) + + Returns: + Dictionary mapping verbose error patterns to their occurrence count + """ + pattern_counts = {} + + for critical_fields_data in critical_fields_list: + kv_pairs = extract_key_value_pairs_from_critical_fields(critical_fields_data) + object_patterns = set() + + for key_path, value in kv_pairs: + if detect_verbose_error(key_path, value): + key_lower = key_path.lower() + value_lower = str(value).lower() + combined = f"{key_lower} {value_lower}" + + # Use patterns from config instead of hardcoded list + verbose_error_patterns = VERBOSE_ERROR_PATTERNS + + # Find matching pattern + matched_pattern = None + + # If value is in "key=value" format, extract the actual error part + actual_value_for_matching = value_lower + if '=' in value_lower: + parts = value_lower.split('=', 1) + if len(parts) == 2: + value_part = parts[1].strip() + if '(' in value_part: + actual_value_for_matching = value_part.split('(', 1)[1].rstrip(')') + else: + actual_value_for_matching = "" + + for pattern in verbose_error_patterns: + search_string = f"{key_lower} {actual_value_for_matching}" + if pattern in search_string: + matched_pattern = pattern + break + + # Check for explicit error keywords if no pattern matched yet + if not matched_pattern: + explicit_errors = EXPLICIT_ERROR_KEYWORDS + for err in explicit_errors: + # Check in combined string (includes both key and value) + if err in combined: + matched_pattern = err + break + + # Also check for status keys with negative values + if not matched_pattern: + status_keys = ['status', 'progressing', 'available', 'ready', 'healthy', 'condition'] + negative_values = ['no', '0', 'none', 'null', 'unknown'] + + # "Status: False" is a common pattern, not an error - exclude it + if any(status_key in key_lower for status_key in status_keys): + if any(neg_val in value_lower for neg_val in negative_values): + matched_pattern = f"{key_lower}:{value_lower}" + # Only treat "false" as error if combined with other error indicators + elif 'false' in value_lower: + if 'error' in combined or 'failed' in combined: + matched_pattern = f"{key_lower}:{value_lower}_error" + # Otherwise, "Status: False" is normal - don't create error pattern + + # Also check for explicit error keywords + if not matched_pattern: + explicit_errors = ['error', 'failed', 'failure', 'exception', 'critical', 'fatal'] + for err in explicit_errors: + if err in combined: + matched_pattern = err + break + + # Use a simplified pattern if no specific match + if not matched_pattern: + # "Status: False" is a common pattern, not an error - don't mark as verbose error + # Only mark as error if combined with other error indicators + if 'false' in value_lower and any(sk in key_lower for sk in ['status', 'progressing', 'available']): + # Check if there are additional error indicators + if 'error' in combined or 'failed' in combined: + matched_pattern = f"status_false_error" + else: + # "Status: False" alone is not an error pattern - skip it + continue # Don't add this as a verbose error pattern + elif 'unknown' in value_lower: + matched_pattern = 'unknown' + else: + matched_pattern = 'other_verbose' + + object_patterns.add(matched_pattern) + + # Count each pattern once per object + for pattern in object_patterns: + pattern_counts[pattern] = pattern_counts.get(pattern, 0) + 1 + + return pattern_counts + + +def classify_objects( + critical_fields_list: List[Dict[str, Any]], + cluster_sizes: Dict, + template_list: List[Dict], + object_to_cluster: Dict[int, int], + frequency_threshold: float = 0.05 +) -> Tuple[List[Dict], Dict[str, int], Dict[str, int]]: + """ + Classify each object as 'Majority Error', 'Majority', 'CONFIG', or 'Error' based on frequency and patterns. + + Args: + critical_fields_list: List of critical fields dictionaries (one per object) + cluster_sizes: Dictionary of cluster_id -> size + template_list: List of template dictionaries + object_to_cluster: Map of object index to cluster_id + frequency_threshold: Threshold for majority (default 5% of total) + + Returns: + Tuple of (classifications, common_patterns, rare_patterns) + """ + if not cluster_sizes or not critical_fields_list: + return [], {}, {} + + total_objects = len(critical_fields_list) + total_clustered = sum(cluster_sizes.values()) + + # Step 1: Analyze verbose error patterns + pattern_counts = analyze_verbose_error_patterns(critical_fields_list) + + # Determine threshold for common patterns (from config) + common_threshold_pct = _ERROR_CONFIG.get("classification", {}).get("common_pattern_threshold", 0.30) + common_pattern_threshold = max(3, int(total_objects * common_threshold_pct)) + common_patterns = {pattern: count for pattern, count in pattern_counts.items() + if count >= common_pattern_threshold} + rare_patterns = {pattern: count for pattern, count in pattern_counts.items() + if count < common_pattern_threshold} + + if not _is_quiet(): + print(f"\nVerbose Error Pattern Analysis:") + print(f" Total objects analyzed: {total_objects}") + print(f" Common pattern threshold: {common_pattern_threshold} objects") + print(f" Common negative patterns (-> Majority Error): {len(common_patterns)}") + if common_patterns: + print(f" Examples: {', '.join(list(common_patterns.keys())[:5])}") + print(f" Rare patterns (-> Error): {len(rare_patterns)}") + if rare_patterns: + print(f" Examples: {', '.join(list(rare_patterns.keys())[:5])}") + + # Calculate frequency for each cluster + cluster_frequencies = {} + for cluster_id, size in cluster_sizes.items(): + frequency = size / total_clustered if total_clustered > 0 else 0 + cluster_frequencies[cluster_id] = frequency + + # Determine majority threshold + if cluster_frequencies: + frequencies = list(cluster_frequencies.values()) + median_freq = sorted(frequencies)[len(frequencies) // 2] if frequencies else 0 + majority_threshold = max(frequency_threshold, median_freq * 2) + else: + majority_threshold = frequency_threshold + + # Get thresholds from config (with fallbacks) + classification_config = _ERROR_CONFIG.get("classification", {}) + error_threshold = classification_config.get("error_threshold", 0.01) + # majority_threshold can be overridden if needed + config_majority_threshold = classification_config.get("majority_threshold", 0.10) + # Use the dynamic majority_threshold calculated above, but can be adjusted by config + + # Helper function to get verbose patterns in an object + def get_verbose_patterns_in_object(critical_fields_data): + """Get verbose error patterns found in an object.""" + patterns = set() + kv_pairs = extract_key_value_pairs_from_critical_fields(critical_fields_data) + + for key_path, value in kv_pairs: + if detect_verbose_error(key_path, value): + key_lower = key_path.lower() + value_lower = str(value).lower() + combined = f"{key_lower} {value_lower}" + + # Use the SAME pattern matching logic as analyze_verbose_error_patterns + verbose_error_patterns = VERBOSE_ERROR_PATTERNS + + matched_pattern = None + + # If value is in "key=value" format, extract the actual error part + actual_value_for_matching = value_lower + if '=' in value_lower: + # For "Degraded=False (message=...)", we want to check the message part, not "degraded" + # Split by '=' and only check after the first boolean value + parts = value_lower.split('=', 1) + if len(parts) == 2: + # Extract just the value part: "false (message=xyz)" + value_part = parts[1].strip() + # If there's a parenthesis (extra info), check that + if '(' in value_part: + actual_value_for_matching = value_part.split('(', 1)[1].rstrip(')') + else: + # Just the boolean value - skip pattern matching + # (already handled by detect_verbose_error) + actual_value_for_matching = "" + + # Check verbose_error_patterns FIRST (same order as analyze_verbose_error_patterns) + for pattern in verbose_error_patterns: + search_string = f"{key_lower} {actual_value_for_matching}" + if pattern in search_string: + matched_pattern = pattern + break + + # Check explicit errors SECOND if no pattern matched yet + if not matched_pattern: + explicit_errors = EXPLICIT_ERROR_KEYWORDS + for err in explicit_errors: + # Check in combined string (includes both key and value) + if err in combined: + matched_pattern = err + break + + # If still no match, check for other patterns (continue with existing code) + if not matched_pattern: + # "Status: False" is a common pattern, not an error - don't mark as verbose error + # Only mark as error if combined with other error indicators + if 'false' in value_lower and any(sk in key_lower for sk in ['status', 'progressing', 'available']): + # Check if there are additional error indicators + if 'error' in combined or 'failed' in combined: + matched_pattern = f"status_false_error" + else: + # "Status: False" alone is not an error pattern - skip it + continue # Don't add this as a verbose error pattern + elif 'unknown' in value_lower: + matched_pattern = 'unknown' + else: + matched_pattern = 'other_verbose' + + patterns.add(matched_pattern) + + return patterns + + # Helper function to check for positive indicators + def has_positive_indicators(critical_fields_data): + """Check if object has positive/healthy status indicators.""" + kv_pairs = extract_key_value_pairs_from_critical_fields(critical_fields_data) + + # Check for "FieldName=True" format first + # This handles combined semantic values like "Available=True", "Ready=True" + for key, value in kv_pairs: + value_lower = str(value).lower() + + # Check if value is in "fieldname=status" format + if '=' in value_lower: + parts = value_lower.split('=', 1) + if len(parts) == 2: + field_name = parts[0].strip() + field_value = parts[1].split()[0] if ' ' in parts[1] else parts[1] # Take first word before any extra text + + # Positive field names with True status + positive_field_names = ['available', 'ready', 'healthy', 'running', 'active', + 'deployed', 'operational', 'functioning', 'working', + 'successful', 'completed', 'succeeded', 'accepted', 'established'] + + # Negative field names with False status + negative_field_names = ['degraded', 'failed', 'error', 'failing', 'unhealthy', + 'unavailable', 'notready', 'broken', 'stuck', 'blocked', + 'progressing'] + + # Check patterns: + # "Available=True" or "Ready=True" → Positive + if any(pos in field_name for pos in positive_field_names) and field_value == 'true': + return True + + # "Degraded=False" or "Failed=False" → Positive (absence of problem) + if any(neg in field_name for neg in negative_field_names) and field_value == 'false': + return True + # Check for verb+adjective patterns (keep this as fallback) + value_lower_combined = " ".join([str(value).lower() for _, value in kv_pairs]) + + positive_status_words = ['deployed', 'available', 'ready', 'healthy', 'running', 'active', + 'operational', 'functioning', 'working', 'completed', 'finished', + 'successful', 'successfully', 'done'] + positive_verbs = ['is', 'are', 'has', 'have', 'was', 'were'] + + words = value_lower_combined.split() + for i in range(len(words) - 1): + if words[i] in positive_verbs and any(pos_word in words[i+1] for pos_word in positive_status_words): + if i > 0 and words[i-1] in ['not', 'no']: + continue + return True + + for i in range(len(words) - 1): + if words[i] == 'all' and any(pos_word in words[i+1] for pos_word in positive_status_words): + return True + + return False + + # Classify each object + classifications = [] + for idx, critical_fields_data in enumerate(critical_fields_list): + cluster_id = object_to_cluster.get(idx) + + # Extract key-value pairs for this object + kv_pairs = extract_key_value_pairs_from_critical_fields(critical_fields_data) + + # Check if object contains configuration changes + has_config_change = any(detect_config_change(key, value) for key, value in kv_pairs) + + # Check verbose error patterns + object_verbose_patterns = get_verbose_patterns_in_object(critical_fields_data) + has_common_negative = any(pattern in common_patterns for pattern in object_verbose_patterns) + has_rare_negative = any(pattern in rare_patterns for pattern in object_verbose_patterns) + + # Check for positive indicators + has_positive = has_positive_indicators(critical_fields_data) + + # Check if object has meaningful status conditions + # If no conditions exist and no error patterns, treat as config/metadata object (Majority) + has_status_conditions = any('condition' in key.lower() for key, _ in kv_pairs) + has_any_errors = bool(object_verbose_patterns) # Has any error patterns detected + + if cluster_id is None: + # Not clustered + if has_rare_negative: + classification = "Error" + elif has_common_negative: + classification = "Majority Error" + elif has_config_change: + classification = "CONFIG" + elif has_positive: + classification = "Majority" + else: + if has_any_errors: + classification = "Error" + else: + classification = "Majority" # Unique but healthy + frequency = 0.0 + template = "UNCLUSTERED" + cluster_size = 0 + else: + frequency = cluster_frequencies.get(cluster_id, 0.0) + cluster_size = cluster_sizes.get(cluster_id, 0) + + # Find template + template = None + for tmpl in template_list: + if tmpl['id'] == cluster_id: + template = tmpl['template'] + break + + # Classify based on frequency, config detection, and verbose error detection + # Priority: Rare Negative (Error) > Common Negative (Majority Error) > CONFIG > Majority (frequency/positive) > Error > Other + if has_rare_negative: + # Rare negative pattern = Error (highest priority) + classification = "Error" + elif has_common_negative: + # Common negative patterns (high frequency) = Majority Error + classification = "Majority Error" + elif has_config_change: + # Configuration change detected = CONFIG + classification = "CONFIG" + elif has_positive or (frequency >= majority_threshold or cluster_size >= (total_objects * majority_threshold)): + # Normal/healthy logs or high frequency = Majority + classification = "Majority" + elif not has_status_conditions and not has_any_errors: + # Object has no status conditions and no errors = config/metadata object + classification = "Majority" + elif frequency <= error_threshold: + if has_any_errors: + classification = "Error" + else: + classification = "Majority" + else: + classification = "Majority" # Default to Majority for medium frequency + + # Generate reason for classification (pass kv_pairs for detailed reasons) + reason = get_classification_reason( + classification, + object_verbose_patterns, + common_patterns, + rare_patterns, + frequency, + cluster_size, + has_config_change, + has_positive, + kv_pairs + ) + + classifications.append({ + 'object_index': idx, + 'critical_fields': critical_fields_data, + 'classification': classification, + 'frequency': frequency, + 'cluster_id': cluster_id, + 'cluster_size': cluster_size, + 'template': template or "UNCLUSTERED", + 'num_key_value_pairs': len(kv_pairs), + 'verbose_patterns': list(object_verbose_patterns), + 'has_config_change': has_config_change, + 'reason': reason + }) + + return classifications, common_patterns, rare_patterns + + +def get_classification_reason( + classification: str, + verbose_patterns: List[str], + common_patterns: Dict[str, int] = None, + rare_patterns: Dict[str, int] = None, + frequency: float = 0.0, + cluster_size: int = 0, + has_config_change: bool = False, + has_positive: bool = False, + kv_pairs: List[Tuple[str, str]] = None +) -> str: + """ + Get the reason for a classification. Matches the logic from cluster_yaml_with_drain3.py. + + Args: + classification: The classification result + verbose_patterns: List of verbose error patterns found + common_patterns: Dictionary of common patterns + rare_patterns: Dictionary of rare patterns + frequency: Cluster frequency + cluster_size: Cluster size + has_config_change: Whether config changes were detected + has_positive: Whether positive indicators were found + kv_pairs: List of (key, value) pairs for detailed reason generation + + Returns: + String describing the reason for classification + """ + if classification == "Error": + # Check if it's an error due to rare verbose patterns + if verbose_patterns: + rare_found = [p for p in verbose_patterns if rare_patterns and p in rare_patterns] + if rare_found: + # Show all rare patterns found, not just first 3 + patterns_str = ', '.join(rare_found) + return f"Rare patterns (anomaly): {patterns_str}" + + # Other error reasons + if cluster_size == 0: + return "Not clustered / unique pattern" + elif frequency <= 0.01: + return f"Very low frequency ({frequency:.2%})" + else: + return "Rare/unique pattern" + + elif classification == "Majority Error": + reasons = [] + if verbose_patterns: + common_found = [p for p in verbose_patterns if common_patterns and p in common_patterns] + if common_found: + # Format: "Common verbose patterns: pattern1, pattern2, pattern3" + patterns_str = ', '.join(common_found[:3]) + reasons.append(f"Common verbose patterns: {patterns_str}") + + if frequency > 0: + reasons.append(f"High frequency ({frequency:.2%})") + elif cluster_size > 1: + reasons.append(f"Cluster size: {cluster_size}") + + if reasons: + return "; ".join(reasons) + return "Common negative pattern (high frequency)" + + elif classification == "Majority": + reasons = [] + if frequency > 0: + reasons.append(f"High frequency ({frequency:.2%})") + elif cluster_size > 1: + reasons.append(f"Cluster size: {cluster_size}") + + # Check if it has positive indicators (using kv_pairs if available) + if has_positive: + reasons.append("Normal/healthy status indicators") + elif kv_pairs: + # Re-check positive indicators from kv_pairs + value_lower_combined = " ".join([str(value).lower() for _, value in kv_pairs]) + words = value_lower_combined.split() + positive_verbs = ['is', 'are', 'has', 'have'] + positive_words = ['deployed', 'available', 'ready', 'healthy', 'running', 'active'] + for i in range(len(words) - 1): + if words[i] in positive_verbs and any(pw in words[i+1] for pw in positive_words): + if i == 0 or words[i-1] not in ['not', 'no']: + reasons.append("Normal/healthy status indicators") + break + + if reasons: + return "; ".join(reasons) + return "High frequency pattern (normal/healthy)" + + elif classification == "CONFIG": + # Find which keys indicate config changes + config_change_keys = [] + if kv_pairs: + for key, value in kv_pairs: + if detect_config_change(key, value): + config_change_keys.append(key) + if config_change_keys: + return f"Configuration change detected in: {', '.join(config_change_keys[:3])}" + return "Configuration change detected (updated/modified/set)" + + else: # Other + return f"Medium frequency ({frequency:.2%})" + + +def classify_critical_fields(critical_fields_list: List[Dict[str, Any]]) -> Dict[str, Any]: + """ + Main function to classify objects based on their critical fields. + + Args: + critical_fields_list: List of critical fields dictionaries (one per object) + Each dict contains extracted critical fields (no line numbers) + + Returns: + Dict with keys: + - results: List of classification dicts, one per object/chunk. + Each result contains: object_index, classification, frequency, + cluster_id, cluster_size, template, verbose_patterns, + has_config_change, reason. + - error: None on success, error message string on failure. + """ + # Input validation + if not isinstance(critical_fields_list, list): + return {"results": [], "error": f"Input must be a list, got {type(critical_fields_list).__name__}"} + + if not critical_fields_list: + return {"results": [], "error": None} + + valid_items = [] + for i, item in enumerate(critical_fields_list): + if isinstance(item, dict): + valid_items.append(item) + else: + if not _is_quiet(): + print(f"[ML YAML] Skipping non-dict item at index {i}: {type(item).__name__}") + if not valid_items: + return {"results": [], "error": "No valid dict items in input list"} + + if not _is_quiet(): + print("Starting ML classification of critical fields...") + print(f"Processing {len(valid_items)} objects\n") + + # Step 1: Drain3 Clustering + if not _is_quiet(): + print("Processing with Drain3...") + try: + cluster_sizes, template_list, object_to_cluster = process_objects_with_drain3(valid_items) + if not _is_quiet(): + print(f"Found {len(template_list)} unique clusters") + except ImportError as e: + msg = f"drain3 not installed: {e}" + if not _is_quiet(): + print(f"[ML YAML] {msg}") + return {"results": [], "error": msg} + except Exception as e: + msg = f"Drain3 processing failed: {e}" + if not _is_quiet(): + print(f"[ML YAML] {msg}") + import traceback + traceback.print_exc() + return {"results": [], "error": msg} + + # Step 2: Classify objects + if not _is_quiet(): + print("\nClassifying objects...") + classifications, common_patterns, rare_patterns = classify_objects( + valid_items, + cluster_sizes, + template_list, + object_to_cluster + ) + + # Build clean result list + results = [] + for classification in classifications: + results.append({ + 'object_index': classification['object_index'], + 'classification': classification['classification'], + 'frequency': classification['frequency'], + 'cluster_id': classification['cluster_id'], + 'cluster_size': classification['cluster_size'], + 'template': classification['template'], + 'verbose_patterns': classification['verbose_patterns'], + 'has_config_change': classification['has_config_change'], + 'reason': classification['reason'] + }) + + return {"results": results, "error": None} diff --git a/intelliaide/Main-program/agent_prompts.py b/intelliaide/Main-program/agent_prompts.py new file mode 100644 index 0000000..c9445d2 --- /dev/null +++ b/intelliaide/Main-program/agent_prompts.py @@ -0,0 +1,432 @@ +""" +Agent Prompts for the Agentic Orchestrator + +Contains the system prompt sent to Claude at the start of every orchestrator +agent run. This is the text that tells Claude "you are an OpenShift debugging +agent, here are your tools, here is how to use them, and here is your strategy." + +Kept in its own file so English-prose tuning and Python-code changes happen in +separate files. Functionally, these could be string constants at the top of +orchestrator_agent.py and everything would work identically. +""" + +from typing import Optional + + +# --------------------------------------------------------------------------- +# 1. SYSTEM PROMPT — core identity, strategy, and constraints +# --------------------------------------------------------------------------- + +SYSTEM_PROMPT = """\ +You are an expert OpenShift / Kubernetes must-gather debuggability agent. + +Mission: given a user's problem statement and a must-gather bundle on disk, +identify the root cause by analyzing relevant files and producing an +evidence-based Root Cause Analysis (RCA) report. + +You have nine tools (no direct file-system access). + +═══════════════════════════════════════════════════════════════════════════════ +TOOLS +═══════════════════════════════════════════════════════════════════════════════ + +1. check_llm_availability + Verify LLM API is reachable; returns model_id, context_window, + max_output_tokens, rate-limit headers. + Call FIRST. If success=false → STOP (see STEP 0). + +2. select_files + Analyze problem statement against must-gather docs; returns PRIORITIZED + file list (high/medium/low), problem category, keywords, AND file + availability (found/not found) in one call. Internal LLM call. + No need to call check_file_availability after this. + +3. check_file_availability + Re-check specific file paths on disk. Only needed when re-checking + after expanding tiers — select_files already covers the initial check. + +4. analyze_yaml + ML-classify YAML files → Error, CONFIG, Majority Error, Majority objects. + Cumulative across calls. No LLM call. + Diagnostic value order: Error > CONFIG > Majority Error > Majority. + +5. analyze_logs + Drain3 template mining on log/txt files → six severity levels: + Rare Pattern Errors > Config Changes > High-Freq Errors > Warnings > + Information > Unknown. Cumulative. No LLM call. + +6. analyze_json + Dual-path JSON analysis: keyword-match for obvious errors, else + ML-classify like analyze_yaml. Merges into same error pool. + Cumulative. No LLM call. + +7. validate_token_budget + Builds the exact perform_rca prompt, estimates tokens, returns per-file + breakdown + fits:true/false. If over budget → auto-trims largest files. + Pass only problem_statement (+ previous_rca_summary for continuation). + Accumulated data is read automatically — do NOT pass it manually. + MANDATORY before every perform_rca. + +8. perform_rca + Send accumulated data + problem statement to LLM for RCA. Returns + summary, token usage, cost. Pass only problem_statement. Accumulated + data read automatically. Supports continuation via previous_rca_summary + + priority stage fields. + +═══════════════════════════════════════════════════════════════════════════════ +STEP 0 — LLM AVAILABILITY GATE +═══════════════════════════════════════════════════════════════════════════════ + +Call check_llm_availability first. + +• success=true → note model_id / context_window / max_output_tokens, proceed. +• success=false → STOP. Return a FINAL ANSWER (not a tool call): + + ## LLM Unavailable — Workflow Cannot Proceed + **Error**:
+ **What this means**: LLM API is required for file selection and RCA. + **Suggested actions**: + - Check API key in config.json + - Verify API URL is reachable + - Check verify_ssl for custom gateways + - Check API quota / rate limit + +═══════════════════════════════════════════════════════════════════════════════ +STRATEGY — INCREMENTAL DEEPENING +═══════════════════════════════════════════════════════════════════════════════ + +DATA PRIORITY ORDER (both YAML and LOG analysis): + 1. Rare Pattern Errors + Error YAML objects (highest value, smallest) + 2. Config Changes / CONFIG YAML objects (often root cause, small) + 3. High-Freq Errors / Majority Error YAML (patterns, larger) + 4. Warnings (context, medium volume) + 5. Information (background, large) + 6. Unknown (lowest value, large) + +First RCA: send only categories 1-2. Add 3 if budget allows. +Categories 4-6 are only included if budget allows. + +ROUND 1 — HIGH priority files (this is ALL you do in the orchestrator loop) + 1. check_llm_availability (STOP if fails). + 2. select_files → separate AVAILABLE files by priority (HIGH/MEDIUM/LOW) + and type (YAML/JSON/LOG). + CRITICAL: NEVER pass NOT FOUND files to any tool in later iterations. + 3. analyze_yaml / analyze_json / analyze_logs on AVAILABLE HIGH files. + 4. validate_token_budget → perform_rca. + 5. Proceed to FINAL REPORT. + +USER-DRIVEN DEEPENING (Rounds 2 & 3): + The user controls whether to add more data via the "Not Satisfactory" button. + You do NOT autonomously decide to add more rounds. After Round 1, always + proceed to FINAL REPORT. The backend handles Rounds 2 (medium) and 3 (low) + via continue_rca_with_feedback when the user requests it. + + Do NOT attempt to analyze medium or low priority files within this loop. + Do NOT attempt to self-evaluate RCA quality or loop based on confidence. + +═══════════════════════════════════════════════════════════════════════════════ +ERROR HANDLING — STRICT NO-RETRY POLICY +═══════════════════════════════════════════════════════════════════════════════ + +CRITICAL RULE: NEVER call a tool that has already returned success=false +or thrown an exception. The orchestrator blocks re-execution of failed +tools. If you attempt to call a failed tool again, you will receive an +immediate BLOCKED error and waste an iteration. + +When ANY tool returns success=false: + +• API_UNAVAILABLE / AUTH_ERROR → STOP immediately, return FINAL ANSWER + with error details and config.json troubleshooting suggestions. +• RATE_LIMITED / OVERLOADED / TIMEOUT → do NOT retry. STOP and return + partial results with whatever data you have so far. +• CONTEXT_OVERFLOW → do NOT retry. STOP and return partial RCA. +• UNKNOWN_ERROR / any other failure → do NOT retry, skip the step, + proceed with remaining workflow or return FINAL ANSWER. + +When ANY tool throws an exception (as opposed to returning success=false): + → The orchestrator will immediately terminate the agent loop. + → You will NOT get another iteration. + +Never silently swallow errors — report all failures in Analysis Coverage. + +═══════════════════════════════════════════════════════════════════════════════ +TOKEN BUDGET +═══════════════════════════════════════════════════════════════════════════════ + +If validate_token_budget reports AUTO-TRIM: + 1. Note removed vs kept files. + 2. Proceed to perform_rca (trimmed data used automatically). + +If even a single file exceeds budget after trimming → proceed to FINAL +REPORT and note the limitation. + +═══════════════════════════════════════════════════════════════════════════════ +REASONING BETWEEN TOOL CALLS +═══════════════════════════════════════════════════════════════════════════════ + +After EVERY tool result, reason about what you learned before deciding the +next action. Max 1-2 related tool calls per turn, then explicit reasoning. + +═══════════════════════════════════════════════════════════════════════════════ +PRIORITY REDISTRIBUTION +═══════════════════════════════════════════════════════════════════════════════ + +If select_files returns all files at the same priority, redistribute: + Top 2/3 → HIGH, next 1/6 → MEDIUM, remaining 1/6 → LOW. + +═══════════════════════════════════════════════════════════════════════════════ +STATE ACCUMULATION +═══════════════════════════════════════════════════════════════════════════════ + +Results accumulate across rounds (yaml_errors dict, log_error_entries list, +files_analyzed list). validate_token_budget and perform_rca read accumulated +data automatically — never pass it manually. + +═══════════════════════════════════════════════════════════════════════════════ +USER FEEDBACK +═══════════════════════════════════════════════════════════════════════════════ + +After FINAL REPORT delivery: + +1. "RCA Satisfactory" → session complete. + +2. "RCA Not Satisfactory" (no new info) → expand to next tier if available. + All tiers exhausted → exit cycle, request new observations from user. + +3. New observation from user — determine: + (a) ADDS TO / NARROWS the problem → continue current cycle with hint, + add specific files, re-run RCA. Do NOT restart from scratch. + (b) REPLACES / CONTRADICTS the problem → start new cycle from scratch. + If ambiguous, ask the user which option. + +═══════════════════════════════════════════════════════════════════════════════ +OUTPUT FORMAT — FINAL RCA REPORT +═══════════════════════════════════════════════════════════════════════════════ + +When you are ready to return the final report, produce your answer (not a tool +call) with the following structure. Use markdown formatting. + +CRITICAL: You MUST use the EXACT section headings shown below (## level) and +the EXACT sub-section headings (### level) where specified. The frontend +parses these headings programmatically — any deviation will break rendering. + +--- + +## User Reported Issue + + +## Executive Summary + + +## Chronology of Events +**: +- ****: +> + +## Primary Root Cause(s) + + - + - +2. + - +> + +## Secondary Causes / Contributing Factors + + +## Aggregated Error Patterns + | | | | + +Include ALL relevant error patterns. Do NOT use plain text or bullet lists +for this section — it MUST be a pipe-delimited table.> + +## Analysis Coverage + + +### Round 1 — HIGH Priority Files + | YAML/LOG/JSON | | + +Add a note after the table if applicable (e.g., confidence level).> + +### Round 2 — MEDIUM Priority Files + + +### Round 3 — LOW Priority Files + + +### Files Not Found in This Must-Gather + + +### Priority Tiers Analyzed + files analyzed (confidence: ) +- MEDIUM — files analyzed (confidence: ) +- LOW — not analyzed () +> + +### Key Limitation + + +## Recommendations + + +--- + +═══════════════════════════════════════════════════════════════════════════════ +CONSTRAINTS +═══════════════════════════════════════════════════════════════════════════════ + +• Max {max_iterations} tool-call iterations. Near the limit → skip to + perform_rca with current data → FINAL REPORT. +• NEVER retry a failed tool. If a tool returned success=false, do NOT + call it again. Move on to the next step or return FINAL REPORT. +• Never fabricate evidence or cite unanalyzed files. +• Non-LLM tool failure → note it, skip, continue (no retry). +• Always prioritize root causes by relevance to the USER REPORTED ISSUE. +• Always state the key cause explicitly in the Executive Summary. +""" + + +# --------------------------------------------------------------------------- +# 2. USER MESSAGE TEMPLATE — injected as the first user message +# --------------------------------------------------------------------------- + +USER_MESSAGE_TEMPLATE = """\ +I need you to analyze a must-gather bundle to diagnose the following issue. + +PROBLEM STATEMENT: +{problem_statement} + +MUST-GATHER LOCATION: + must_gather_docs_dir : {must_gather_docs_dir} + must_gather_base_dir : {must_gather_base_dir} + +Please begin by calling check_llm_availability to verify the LLM API is +reachable. If it succeeds, proceed with select_files. If it fails, stop +and report the error. +""" + + +# --------------------------------------------------------------------------- +# 3. USER OBSERVATION MESSAGE TEMPLATE — for when user provides new input +# after the initial RCA cycle +# --------------------------------------------------------------------------- + +USER_OBSERVATION_TEMPLATE = """\ +The user has provided additional input after reviewing the RCA: + +USER'S NEW INPUT: +{user_observation} + +PREVIOUS PROBLEM STATEMENT: +{original_problem_statement} + +CURRENT RCA PRIORITY STAGE: {current_priority_stage} +ROUNDS COMPLETED: {rounds_completed} of 3 + +Determine whether this input is: +(A) A continuation hint (adds to / narrows the existing investigation), or +(B) A new problem statement (replaces / contradicts the original problem). + +If (A): Continue the current cycle. Identify specific files to add based on +the user's hint, call analyze_yaml / analyze_logs on those files, merge with +existing results, and re-run perform_rca. + +If (B): Indicate that a NEW CYCLE should be started. Return a message +confirming the new problem statement and instruct the orchestrator to restart. + +If ambiguous: CONTINUE the current cycle. +""" + + +# --------------------------------------------------------------------------- +# 4. BUILDER FUNCTIONS — called by orchestrator_agent.py +# --------------------------------------------------------------------------- + +def build_system_prompt( + max_iterations: int = 25, +) -> str: + """ + Return the fully-interpolated system prompt. + + Parameters + ---------- + max_iterations : int + Hard cap on total tool-call iterations. Injected into the prompt so + Claude knows when to stop. + """ + return SYSTEM_PROMPT.format(max_iterations=max_iterations) + + +def build_user_message( + problem_statement: str, + must_gather_docs_dir: str, + must_gather_base_dir: str = "", +) -> str: + """ + Return the first user message that kicks off the agent loop. + + Parameters + ---------- + problem_statement : str + The user's problem description. + must_gather_docs_dir : str + Directory containing MUST_GATHER_*.md structure documentation files. + must_gather_base_dir : str + Root directory of the actual must-gather bundle on disk. + """ + return USER_MESSAGE_TEMPLATE.format( + problem_statement=problem_statement.strip(), + must_gather_docs_dir=must_gather_docs_dir, + must_gather_base_dir=must_gather_base_dir or "(not specified)", + ) + + +def build_user_observation_message( + user_observation: str, + original_problem_statement: str, + current_priority_stage: str = "unknown", + rounds_completed: int = 0, +) -> str: + """ + Return a user message for when the user provides feedback / new observations + after the initial RCA. + + Parameters + ---------- + user_observation : str + The user's new input / observation / comment. + original_problem_statement : str + The original problem statement from the start of the cycle. + current_priority_stage : str + Which priority tiers have been analyzed so far. + rounds_completed : int + Number of deepening rounds completed (0-3). + """ + return USER_OBSERVATION_TEMPLATE.format( + user_observation=user_observation.strip(), + original_problem_statement=original_problem_statement.strip(), + current_priority_stage=current_priority_stage, + rounds_completed=rounds_completed, + ) diff --git a/intelliaide/Main-program/data_analyzer.py b/intelliaide/Main-program/data_analyzer.py new file mode 100644 index 0000000..3f334c4 --- /dev/null +++ b/intelliaide/Main-program/data_analyzer.py @@ -0,0 +1,2184 @@ +""" +Data Analyzer Sub-Agent + +This module is called by orchestrator_agent.py to analyze must-gather files. +It extracts critical fields from YAML files and sends them to ML classifier. +""" + +import os +import sys +import json +import yaml +import re +import glob +import importlib.util +from typing import Dict, List, Any, Optional +from pathlib import Path + +# QUIET MODE: Check if orchestrator has enabled quiet mode +def _is_quiet(): + """Check if orchestrator has enabled quiet mode via environment variable""" + return os.environ.get('ORCHESTRATOR_QUIET', '0') == '1' + + +def _import_ml_module(module_name: str, symbol: str): + """ + Import a symbol from a Machine-learning module, handling both .py and .PY + extensions (Linux is case-sensitive so .PY is invisible to normal import). + """ + # 1. Try normal import first (works if .py exists on sys.path) + try: + mod = __import__(module_name, fromlist=[symbol]) + return getattr(mod, symbol) + except (ImportError, AttributeError): + pass + + # 2. Locate the .PY or .py file manually in Machine-learning/ + ml_dir = Path(__file__).resolve().parent.parent / "Machine-learning" + for ext in (".py", ".PY"): + candidate = ml_dir / f"{module_name}{ext}" + if candidate.is_file(): + spec = importlib.util.spec_from_file_location(module_name, str(candidate)) + if spec and spec.loader: + mod = importlib.util.module_from_spec(spec) + sys.modules[module_name] = mod + spec.loader.exec_module(mod) + return getattr(mod, symbol) + + raise ImportError(f"Cannot find {module_name}.py or {module_name}.PY in {ml_dir}") + + +try: + classify_critical_fields = _import_ml_module("ML_YAML_CLASSIFICATION", "classify_critical_fields") + ML_CLASSIFIER_AVAILABLE = True +except (ImportError, Exception) as e: + ML_CLASSIFIER_AVAILABLE = False + if not _is_quiet(): + print(f"[Data Analyzer] ML_YAML_CLASSIFICATION not available: {e}") + +try: + # analyze_logs is the public entry point (validates inputs, checks drain3, loads config) + analyze_logs_ml = _import_ml_module("ML_LOG_CLASSIFICATION", "analyze_logs") + ML_LOG_CLASSIFICATION_AVAILABLE = True +except (ImportError, Exception) as e: + ML_LOG_CLASSIFICATION_AVAILABLE = False + if not _is_quiet(): + print(f"[Data Analyzer] ML_LOG_CLASSIFICATION not available: {e}") + +try: + from odf.opendocument import load as load_odt + from odf.table import Table, TableRow, TableCell + from odf.text import P + ODT_AVAILABLE = True +except ImportError: + ODT_AVAILABLE = False + +# Load from config, fallback to current directory +def _get_default_base_folder(): + config_path = Path(__file__).parent.parent / "Config" / "yaml_processing.yaml" + if config_path.exists(): + try: + with open(config_path, 'r') as f: + config = yaml.safe_load(f) + if isinstance(config, dict): + configured_path = config.get('base_directory', {}).get('must_gather_path', '') + if configured_path: + return configured_path + except Exception: + pass + return os.getcwd() + +DEFAULT_BASE_FOLDER = _get_default_base_folder() + +# Path to critical fields document (uses app_paths for frozen exe) +from app_paths import get_keyfields_path, get_application_dir +KEYFIELDS_ODT_PATH = get_keyfields_path() + +# Load YAML processing configuration +def _load_yaml_processing_config(): + """Load YAML processing configuration from config/yaml_processing.yaml""" + import yaml as yaml_loader + config_path = Path(__file__).parent.parent / "Config" / "yaml_processing.yaml" + default_config = { + "chunking": { + "max_items_section_indent": 2, + "list_item_indent_offset": 2, + "indent_tolerance": 2, + "top_level_indent": 0, + "list_container_keys": ["items"] + }, + "critical_fields": { + "default_paths": ["metadata.name", "status.conditions"], + "file_specific": {}, + "pattern_matching": {} + } + } + try: + with open(config_path, 'r', encoding='utf-8') as f: + loaded = yaml_loader.safe_load(f) + if loaded: + # Merge loaded config with defaults + for key in loaded: + if key in default_config and isinstance(default_config[key], dict): + default_config[key].update(loaded[key]) + else: + default_config[key] = loaded[key] + except FileNotFoundError: + if not _is_quiet(): + print(f"[DataAnalyzer] Config not found at {config_path}, using defaults") + except Exception as e: + if not _is_quiet(): + print(f"[DataAnalyzer] Error loading config: {e}, using defaults") + return default_config + +YAML_PROCESSING_CONFIG = _load_yaml_processing_config() + +# Path-based fields for clusteroperators.yaml: use "metadata.name" to get the operator name +# (line 86, 247, etc.) without matching other "name" keys (ownerReferences.name, etc.). +# Include specific status fields - only extract what's explicitly mentioned. +# RECOMMENDED_PATHS_FOR_CLUSTEROPERATORS = ["metadata.name", "status.conditions"] + +# # Path-based fields for clusterversions.yaml: version info and status +# RECOMMENDED_PATHS_FOR_CLUSTERVERSIONS = ["metadata.name", "status.history", "status.conditions"] + + +class DataAnalyzer: + """ + Analyzes must-gather files (YAML, log, JSON). Called by the orchestrator. + """ + + def __init__(self, must_gather_base_dir: str = None): + """ + Initialize Data Analyzer. + + Parameters: + ----------- + must_gather_base_dir : str, optional + Base directory of the must-gather (or quay) collection + """ + self.must_gather_base_dir = must_gather_base_dir or "" + # Memory for storing extracted metadata + self.extracted_metadata = {} + # Critical fields are loaded per-file from docx table (not at init) + self.config = YAML_PROCESSING_CONFIG + # Transient error from last chunking operation (set by _extract_yaml_object_chunks) + self._last_chunk_error = None + + @staticmethod + def _strip_must_gather_prefix(path: str) -> str: + """Strip the leading wildcard / must-gather / quay prefix from a path. + + These prefixes represent the variable subfolder name in must-gather + collections (quay-content, must-gather.local.xxx, random hash, etc.). + """ + prefixes_to_strip = [ + "/quay*/", + "/quay*", + "/must-gather*/", + "/must-gather*", + "/must-gather/", + "/must-gather", + "/" + ] + for prefix in prefixes_to_strip: + if path.lower().startswith(prefix.lower()): + path = path[len(prefix):].lstrip("/\\") + break + return path + + @staticmethod + def _is_path_under_base(resolved: str, base_folder: str) -> bool: + """Verify that *resolved* is under *base_folder* (prevent path traversal).""" + try: + resolved_real = os.path.realpath(resolved) + base_real = os.path.realpath(base_folder) + return resolved_real.startswith(base_real + os.sep) or resolved_real == base_real + except (ValueError, OSError): + return False + + def _resolve_path(self, relative_path: str, base_folder: str) -> str: + """ + Resolve orchestrator path (e.g. /quay*/namespaces/...) to full path under base_folder. + + The wildcard prefix (/quay*/, /must-gather/, etc.) represents the variable subfolder + name in must-gather collections. This can be any name (quay-content, must-gather.local.xxx, + random hash, etc.) - NOT hardcoded to any specific value. + + User should point base_folder to either: + 1. The actual subfolder containing cluster-scoped-resources/, namespaces/, etc. + 2. The parent folder - we'll search for */path pattern using glob + + NOTE: For paths containing wildcards (e.g. pods/*/logs/current.log) + use :meth:`_resolve_path_glob` instead — it returns ALL matches. + """ + + import glob + + path = relative_path.strip() + base_folder = base_folder.rstrip("/\\") + + # Fast path: already an absolute path under base — use directly, no stripping. + if os.path.isabs(path) and self._is_path_under_base(path, base_folder): + return os.path.normpath(path) + + path = self._strip_must_gather_prefix(path) + + if not path: + return base_folder + + # Convert path separators for current OS + path_normalized = path.replace("/", os.sep) + + # Try 1: Direct join with base_folder + direct_path = os.path.normpath(os.path.join(base_folder, path_normalized)) + if os.path.exists(direct_path) and self._is_path_under_base(direct_path, base_folder): + return direct_path + + # Try 2: Search with wildcard for first subfolder + wildcard_pattern = os.path.join(base_folder, "*", path_normalized) + for m in glob.glob(wildcard_pattern): + if self._is_path_under_base(m, base_folder): + return m + + # Try 3: Search with two levels of wildcards + wildcard_pattern_2 = os.path.join(base_folder, "*", "*", path_normalized) + for m in glob.glob(wildcard_pattern_2): + if self._is_path_under_base(m, base_folder): + return m + + # Return direct path as fallback (will fail — file treated as not found) + return direct_path + + + def _resolve_path_glob(self, relative_path: str, base_folder: str) -> List[str]: + """ + Resolves paths containing wildcards by searching through nested folders. + + Handles must-gather's deep pod structure where the LLM suggests + 'pods/*/logs/current.log' but the real layout is + 'pods////logs/current.log' (any depth). + + Strategy — try four patterns in order: + 1. Direct: base/path/*/rest (single-level * as written) + 2. Deep prefix: base/**/path/*/rest (allow prefix dirs, single-level *) + 3. Deep star: base/path/**/rest (replace every * with ** for any depth) + 4. Deep both: base/**/path/**/rest (combine prefix + deep star) + """ + import re as _re + + path = relative_path.strip() + base_folder = os.path.abspath(base_folder.rstrip("/\\")) + + # Fast path: already an absolute path under base — skip stripping. + if not os.path.isabs(path) or not self._is_path_under_base(path, base_folder): + path = self._strip_must_gather_prefix(path) + if not path: + return [] + + path_normalized = path.replace("/", os.sep) + + # Build a "deep" version: replace every standalone * (not already **) with ** + # so pods/*/logs/current.log → pods/**/logs/current.log + # This matches pods//logs/current.log + deep_path = _re.sub(r'(? str: + """Replace LLM template placeholders with appropriate glob patterns. + + ```` (and variations like ````, ````) + are replaced with ``**`` so glob can match zero-or-more directory + levels — this handles layouts where the node-name directory does not + exist (e.g. ``host_service_logs/masters/kubelet_service.log`` has no + per-node subdirectory). + + All other ```` tokens are replaced with ``*`` (single + directory level). + """ + import re + path = re.sub(r'', '**', path, flags=re.IGNORECASE) + return re.sub(r'<[^>]+>', '*', path) + + def _expand_node_name_placeholder(self, path: str, base_folder: str) -> list: + """Expand ```` by scanning actual directories and matching + against :pyattr:`MUST_GATHER_NODE_NAME_RE`. + + Returns a list of concrete paths (one per matching node directory). + Falls back to glob ``**`` expansion if directory scanning yields nothing. + """ + import re as _re + placeholder_re = _re.compile(r'', _re.IGNORECASE) + m = placeholder_re.search(path) + if not m: + return [] + + prefix_raw = path[:m.start()].rstrip("/\\") + suffix_raw = path[m.end():].lstrip("/\\") + prefix_stripped = self._strip_must_gather_prefix(prefix_raw) + + # Resolve the prefix directory on disk + candidate_dirs = [] + if prefix_stripped: + direct = os.path.normpath(os.path.join(base_folder, prefix_stripped.replace("/", os.sep))) + if os.path.isdir(direct): + candidate_dirs.append(direct) + # Also try one level deeper (must-gather subfolder) + for d in glob.glob(os.path.join(base_folder, "*", prefix_stripped.replace("/", os.sep))): + if os.path.isdir(d): + candidate_dirs.append(d) + + expanded: list = [] + for parent_dir in candidate_dirs: + try: + entries = os.listdir(parent_dir) + except OSError: + continue + for entry in entries: + full_entry = os.path.join(parent_dir, entry) + if not os.path.isdir(full_entry): + continue + if self.MUST_GATHER_NODE_NAME_RE.match(entry): + if suffix_raw: + target = os.path.normpath(os.path.join(full_entry, suffix_raw.replace("/", os.sep))) + if os.path.exists(target): + expanded.append(target) + else: + expanded.append(full_entry) + + # If regex-based scan found nothing, try without the node-name segment + # (the directory level may simply not exist in this must-gather layout) + if not expanded and suffix_raw: + for parent_dir in candidate_dirs: + target = os.path.normpath(os.path.join(parent_dir, suffix_raw.replace("/", os.sep))) + if os.path.exists(target): + expanded.append(target) + + return expanded + + @staticmethod + def _path_has_wildcard(path: str) -> bool: + """Return True if the path contains glob wildcard characters or LLM placeholders.""" + return '*' in path or '?' in path or '<' in path + + def _check_file_access(self, file_paths: List[str], base_folder: str) -> Dict: + """ + Check if each file sent by the orchestrator is accessible under base_folder. + + Paths that contain wildcard characters (``*``, ``?``) are expanded via + :func:`glob.glob` so that ``namespaces/openshift-etcd/pods/*/logs/current.log`` + resolves to *all* matching pod log files. Each match is added as a + separate accessible entry (with the original wildcard path preserved so + the caller can trace where it came from). + + Returns dict with accessible, not_found, and errors lists. + """ + base_folder = base_folder or DEFAULT_BASE_FOLDER + accessible = [] + not_found = [] + errors = [] + + for raw_path in file_paths: + # Fast path: if this is already a fully-resolved absolute path that + # exists on disk (e.g. returned by a prior report_files_availability + # call and stored in agent_state), use it directly — no re-resolution. + if os.path.isabs(raw_path) and os.path.exists(raw_path) and self._is_path_under_base(raw_path, base_folder): + if os.path.isfile(raw_path): + accessible.append({"original": raw_path, "resolved": raw_path}) + else: + accessible.append({"original": raw_path, "resolved": raw_path, "note": "path is a directory"}) + continue + + # Try regex-based node-name expansion first (before generic placeholder normalization) + node_expanded = self._expand_node_name_placeholder(raw_path, base_folder) if '<' in raw_path else [] + if node_expanded: + for resolved in node_expanded: + accessible.append({"original": raw_path, "resolved": resolved, "note": "node-name expansion"}) + if not _is_quiet(): + print(f" [Node] {raw_path} -> {len(node_expanded)} file(s) found via node-name regex") + continue + + path = self._normalize_placeholders(raw_path) + try: + if self._path_has_wildcard(self._strip_must_gather_prefix(path)): + # ── Wildcard path: expand to all matching files ── + matches = self._resolve_path_glob(path, base_folder) + if matches: + for resolved in matches: + accessible.append({ + "original": path, + "resolved": resolved, + "note": "glob expansion", + }) + if not _is_quiet(): + print(f" [Glob] {path} -> {len(matches)} file(s) found") + else: + not_found.append({"original": path, "resolved": path}) + else: + # ── Concrete path (no wildcards) ── + full_path = self._resolve_path(path, base_folder) + if os.path.exists(full_path): + if os.path.isfile(full_path): + with open(full_path, "rb") as f: + f.read(1) + accessible.append({"original": path, "resolved": full_path}) + else: + accessible.append({"original": path, "resolved": full_path, "note": "path is a directory"}) + else: + not_found.append({"original": path, "resolved": full_path}) + except Exception as e: + errors.append({"original": path, "resolved": path, "error": str(e)}) + + return {"accessible": accessible, "not_found": not_found, "errors": errors} + + def _load_critical_fields(self, file_path: str) -> List[str]: + """ + Load critical field paths from config, with fallback to docx table. + + Priority: + 1. ODT/docx table (primary source of truth) + 2. File-specific config (fallback) + 3. Default paths from config + """ + file_name = os.path.basename(file_path).lower() + cf_config = self.config.get("critical_fields", {}) + + # 1. Try ODT/docx table FIRST (primary source) + docx_fields = self._load_critical_fields_from_docx(file_path) + if docx_fields: + if not _is_quiet(): + print(f"[DataAnalyzer] Using document critical fields for {file_name}: {docx_fields}") + return docx_fields + + # 2. Fallback to file-specific config + file_specific = cf_config.get("file_specific", {}) + if file_name in file_specific: + paths = file_specific[file_name].get("paths", []) + if paths: + if not _is_quiet(): + print(f"[DataAnalyzer] Using config critical fields for {file_name}: {paths}") + return paths.copy() + + # 3. Fallback to default paths + default_paths = cf_config.get("default_paths", []) + if default_paths: + if not _is_quiet(): + print(f"[DataAnalyzer] Using default critical fields for {file_name}: {default_paths}") + return default_paths.copy() + + return [] + + def _load_critical_fields_from_docx(self, file_path: str) -> List[str]: + """ + Read critical field names from keyfields_yaml_ml_input.odt table. + (Original _load_critical_fields logic moved here) + + Returns: + List of field path strings, or [] if not available / no match. + """ + if not ODT_AVAILABLE: + if not _is_quiet(): + print("[DataAnalyzer] ODT library not installed — keyfields document not applied") + return [] + + if not KEYFIELDS_ODT_PATH.exists(): + if not _is_quiet(): + print(f"[DataAnalyzer] Keyfields document not found at {KEYFIELDS_ODT_PATH}") + return [] + + def get_cell_text(cell) -> str: + """Extract text content from an ODT table cell.""" + text_parts = [] + for p in cell.getElementsByType(P): + for node in p.childNodes: + if hasattr(node, 'data'): + text_parts.append(node.data) + return ''.join(text_parts).strip() + + try: + doc = load_odt(str(KEYFIELDS_ODT_PATH)) + + file_path_normalized = file_path.strip().lower() + if file_path_normalized.startswith("/quay*/"): + file_path_normalized = file_path_normalized[7:] + elif file_path_normalized.startswith("/quay*"): + file_path_normalized = file_path_normalized[6:] + file_path_normalized = file_path_normalized.lstrip("/\\") + + file_name = os.path.basename(file_path_normalized).lower() + + fields = [] + matched_cells = None + + for table in doc.getElementsByType(Table): + for row in table.getElementsByType(TableRow): + cells = row.getElementsByType(TableCell) + if len(cells) < 2: + continue + + first_cell = get_cell_text(cells[0]) + first_cell_normalized = first_cell.lower().replace('\\','/') + + if not first_cell_normalized: + continue + + if first_cell_normalized.startswith("/quay*/"): + first_cell_normalized = first_cell_normalized[7:] + elif first_cell_normalized.startswith("/quay*"): + first_cell_normalized = first_cell_normalized[6:] + first_cell_normalized = first_cell_normalized.lstrip("/\\") + + if (file_path_normalized == first_cell_normalized or + file_path_normalized in first_cell_normalized or + first_cell_normalized in file_path_normalized or + file_name == os.path.basename(first_cell_normalized).lower() or + file_name in first_cell_normalized): + matched_cells = cells + break + + if matched_cells: + break + + if not matched_cells: + return [] # No match in docx, let caller use defaults + + for cell in matched_cells[1:]: + cell_text = get_cell_text(cell) + if not cell_text: + continue + + lines = cell_text.replace(',', '\n').replace('•', '\n').split('\n') + for line in lines: + line = line.strip() + if not line: + continue + + line = line.lstrip('•-* \t') + + if ('.' in line or '[' in line or + line.startswith('metadata.') or + line.startswith('status.') or + line.startswith('spec.') or + line.startswith('data.') or + any(keyword in line.lower() for keyword in ['name', 'status', 'type', 'message', 'reason', 'condition'])): + if line not in fields: + fields.append(line) + + fields = [f.strip() for f in fields if f and f.strip()] + return fields + + except Exception as e: + if not _is_quiet(): + print(f"[DataAnalyzer] Failed to read keyfields document: {e}") + return [] + + def _find_key_in_yaml_lines(self, lines: List[str], key_name: str) -> List[int]: + """ + Find all line numbers where a key appears in YAML lines. + Returns list of line numbers where the key is found. + """ + line_numbers = [] + for line_num, line in enumerate(lines, 1): + stripped = line.strip() + if not stripped or stripped.startswith('#'): + continue + if ':' in stripped: + key = stripped.split(':')[0].strip() + # Check if key matches (case-insensitive, or as part of path) + if key.lower() == key_name.lower() or key_name.lower() in key.lower(): + line_numbers.append(line_num) + return line_numbers + + def _extract_yaml_object_chunks(self, yaml_file_path: str) -> List[tuple]: + """ + Step 2: Extract YAML objects as chunks using the proven extraction logic. + Each list item (starting with '-') at the items level is treated as a single chunk. + + Returns: + -------- + List of (chunk_string, start_line, end_line, key_value_pairs) tuples + where key_value_pairs is List[Tuple[str, str, int]] = (path, value, line_number) + and path is the YAML path (e.g. "metadata.name") so "metadata.name" can be matched + without matching other "name" keys (e.g. metadata.ownerReferences.name). + """ + import re + + try: + with open(yaml_file_path, 'r', encoding='utf-8') as f: + yaml_content = f.read() + + lines = yaml_content.split('\n') + chunks = [] + current_chunk = [] + current_chunk_start = None + current_chunk_lines = [] + list_item_indent = None # Track the indent level of list items (e.g., under 'items:') + indent_stack = [] # [(indent, key), ...] for building YAML path (e.g. metadata.name) + + for line_num, line in enumerate(lines, start=1): + stripped = line.strip() + + # Skip empty lines + if not stripped: + if current_chunk_start is not None: + current_chunk_lines.append(line) + continue + + if stripped.startswith('#'): + if current_chunk_start is not None: + current_chunk_lines.append(line) + continue + + if stripped.startswith('---'): + # Document separator - end current chunk if any + if current_chunk_start is not None: + chunks.append(( + '\n'.join(current_chunk_lines), + current_chunk_start, + line_num - 1, + current_chunk + )) + current_chunk = [] + current_chunk_lines = [] + current_chunk_start = None + list_item_indent = None + continue + + # Calculate indentation + indent = len(line) - len(stripped) + + # Start a chunk if we haven't started one yet (for files without --- at the start) + if current_chunk_start is None: + current_chunk_start = line_num + current_chunk_lines = [] # Don't add line here, it will be added below + current_chunk = [] + indent_stack = [] + + # Check if this is a list item under 'items:' (typically starts with '- ' at indent 0 or 2) + is_list_item = stripped.startswith('-') + + # Detect if we're in the 'items:' section (only at low indent - Kubernetes list items) + # High-indent 'items:' (like in CRD schemas) should be ignored + # if indent <= 2 and ('items:' in stripped.lower() or (stripped.endswith(':') and 'items' in stripped.lower())): + chunking_cfg = self.config.get("chunking", {}) + max_indent = chunking_cfg.get("max_items_section_indent", 2) + container_keys = chunking_cfg.get("list_container_keys", ["items"]) + + if indent <= max_indent and any(k + ':' in stripped.lower() or (stripped.endswith(':') and k in stripped.lower()) for k in container_keys): + # This is the items: line, next list items will be chunks + # list_item_indent = indent + 2 # List items are typically indented 2 spaces after 'items:' + indent_offset = chunking_cfg.get("list_item_indent_offset", 2) + list_item_indent = indent + indent_offset + continue + + # Check if this is a new list item (new chunk) + if is_list_item: + # Check if this is at the list item indent level (or close to it) + # if list_item_indent is not None and abs(indent - list_item_indent) <= 2: + tolerance = chunking_cfg.get("indent_tolerance", 2) + if list_item_indent is not None and abs(indent - list_item_indent) <= tolerance: + # This is a new list item - start a new chunk + if current_chunk_start is not None: + # Save previous chunk + chunks.append(( + '\n'.join(current_chunk_lines), + current_chunk_start, + line_num - 1, + current_chunk + )) + indent_stack = [] + + # Start new chunk + current_chunk = [] + current_chunk_lines = [line] + current_chunk_start = line_num + list_item_indent = indent # Update to current list item indent + indent_stack = [] # Reset path stack for new object + else: + # This is a nested list item - continue current chunk + if current_chunk_start is not None: + current_chunk_lines.append(line) + else: + # Regular line - add to current chunk if we have one + if current_chunk_start is not None: + # Check if we've gone back to a higher level (end of current object) + if list_item_indent is not None and indent <= list_item_indent and not is_list_item: + # Check if this is a top-level key that signals end of items section + # Keys like 'kind:', 'metadata:', 'apiVersion:' at indent 0 after items + # indicate the list wrapper metadata, not part of the item + # is_top_level_key = indent == 0 and ':' in stripped and not stripped.startswith('-') + top_level_indent = chunking_cfg.get("top_level_indent", 0) + is_top_level_key = indent == top_level_indent and ':' in stripped and not stripped.startswith('-') + if is_top_level_key: + # End of chunk - this is list wrapper metadata + chunks.append(( + '\n'.join(current_chunk_lines), + current_chunk_start, + line_num - 1, + current_chunk + )) + current_chunk = [] + current_chunk_lines = [] + current_chunk_start = None + list_item_indent = None + indent_stack = [] + else: + # Continue adding to chunk + current_chunk_lines.append(line) + else: + current_chunk_lines.append(line) + + # Extract key-value pair if present (for current chunk); track YAML path + if current_chunk_start is not None: + match = re.match(r'^(\s*)([^:]+?):\s*(.*)$', line) + if match: + key = match.group(2).strip() + value = match.group(3).strip() + if not key: + continue + # Pop stack until we're at a parent of this indent + while indent_stack and indent_stack[-1][0] >= indent: + indent_stack.pop() + if value: + # Leaf key: value on same line — build path and store (path, value, line_num) + path = ".".join(k for _, k in indent_stack) + "." + key if indent_stack else key + value = value.strip('"\'') + current_chunk.append((path, value, line_num)) + else: + # Parent key (e.g. "metadata:" or "annotations:") — push for path context + indent_stack.append((indent, key)) + + # Add final chunk if exists + if current_chunk_start is not None: + chunks.append(( + '\n'.join(current_chunk_lines), + current_chunk_start, + len(lines), + current_chunk + )) + + return chunks + except Exception as e: + if not _is_quiet(): + print(f"[DataAnalyzer] YAML parse error in {yaml_file_path}: {e}") + self._last_chunk_error = str(e) + return [] + + def _get_line_numbers_for_key(self, lines: List[str], key: str, value: Any = None) -> List[int]: + """ + Find line numbers where a key appears in YAML lines. + """ + line_numbers = [] + for line_num, line in enumerate(lines, 1): + stripped = line.strip() + if not stripped or stripped.startswith('#'): + continue + if ':' in stripped: + line_key = stripped.split(':')[0].strip() + # Remove quotes if present + line_key = line_key.strip('"\'') + if line_key == key: + line_numbers.append(line_num) + return line_numbers + + def _get_nested_value(self, data: Any, path: str) -> Any: + """ + Navigate through nested dict/list structure using a path like "metadata.name" or "status.conditions[].type". + Returns the value at that path, or None if not found. + """ + if not path or not data: + return None + + parts = path.split('.') + current = data + + for part in parts: + if '[]' in part: + # Handle array notation like "conditions[]" - means all items in array + key = part.replace('[]', '') + if isinstance(current, dict) and key in current: + current = current[key] + if isinstance(current, list): + # Return the list itself for array paths + return current + else: + return None + else: + return None + elif '[' in part and ']' in part: + # Handle indexed access like "conditions[0]" + key, index_str = part.split('[', 1) + index_str = index_str.rstrip(']') + if isinstance(current, dict) and key in current: + current = current[key] + if isinstance(current, list): + try: + index = int(index_str) + if 0 <= index < len(current): + current = current[index] + else: + return None + except ValueError: + return None + else: + return None + else: + return None + else: + # Regular key access + if isinstance(current, dict) and part in current: + current = current[part] + else: + return None + + return current + + def _extract_nested_structure(self, data: Any, path: str, path_parts: List[str] = None) -> Dict[str, Any]: + """ + Extract a nested structure from data following the path, preserving hierarchy. + For paths like "status.conditions[].type", extracts the full "status.conditions" array. + Returns a nested dict structure that contains the value at the path. + """ + if path_parts is None: + path_parts = path.split('.') if path else [] + + if not path_parts: + return data if isinstance(data, dict) else {} + + part = path_parts[0] + remaining = path_parts[1:] + + if '[]' in part: + # Array notation - extract the full array structure + key = part.replace('[]', '') + if isinstance(data, dict) and key in data: + value = data[key] + if isinstance(value, list): + # If there are remaining parts (e.g., "conditions[].type"), + # we still extract the full array but could filter later + # For now, extract the full array to preserve structure + return {key: value} + else: + return {key: value} + else: + return None + else: + # Regular key + if isinstance(data, dict) and part in data: + value = data[part] + if remaining: + # Continue deeper + extracted = self._extract_nested_structure(value, None, remaining) + if extracted is not None: + return {part: extracted} + else: + # If deeper extraction failed, return what we have + return {part: value} + else: + # This is the target - return the structure containing this key + return {part: value} + else: + return None + + def _merge_dicts(self, dict1: Dict, dict2: Dict) -> Dict: + """ + Deep merge two dictionaries, combining nested structures. + """ + result = dict1.copy() + for key, value in dict2.items(): + if key in result: + if isinstance(result[key], dict) and isinstance(value, dict): + result[key] = self._merge_dicts(result[key], value) + elif isinstance(result[key], list) and isinstance(value, list): + # For lists, we might need to merge or append - for now, prefer dict2 + result[key] = value + else: + # Prefer dict2 value + result[key] = value + else: + result[key] = value + return result + + def _group_line_numbers_into_ranges(self, line_numbers: List[int]) -> List[List[int]]: + """ + Group consecutive line numbers into ranges. + Returns a list of [start, end] pairs for consecutive ranges. + + Example: + [4, 5, 8, 9, 10, 11, 12, 14, 15] -> [[4, 5], [8, 12], [14, 15]] + """ + if not line_numbers: + return [] + + sorted_lines = sorted(set(line_numbers)) + ranges = [] + start = sorted_lines[0] + end = sorted_lines[0] + + for i in range(1, len(sorted_lines)): + if sorted_lines[i] == end + 1: + # Consecutive - extend current range + end = sorted_lines[i] + else: + # Gap found - save current range and start new one + ranges.append([start, end]) + start = sorted_lines[i] + end = sorted_lines[i] + + # Add the last range + ranges.append([start, end]) + + return ranges + + def _add_line_numbers_to_structure(self, structure: Dict, key_value_pairs: List[tuple], path_prefix: str = "") -> Dict: + """ + Add line number metadata to the structure by matching paths from key_value_pairs. + Returns structure with line number ranges added at appropriate levels. + Line numbers are grouped into ranges for compact representation. + """ + # Create a mapping of paths to line numbers + path_to_lines = {} + for path, value, line_num in key_value_pairs: + if path not in path_to_lines: + path_to_lines[path] = [] + path_to_lines[path].append(line_num) + + # Find the overall line range for the structure + all_lines = [] + for path, value, line_num in key_value_pairs: + all_lines.append(line_num) + + if all_lines: + sorted_all_lines = sorted(set(all_lines)) + ranges = self._group_line_numbers_into_ranges(sorted_all_lines) + structure["_line_numbers"] = { + "min": min(sorted_all_lines), + "max": max(sorted_all_lines), + "ranges": ranges + } + + # Recursively add line numbers for nested keys + def add_lines(obj, current_path=""): + if isinstance(obj, dict): + result = {} + for key, value in obj.items(): + if key == "_line_numbers": + result[key] = value + continue + + full_path = f"{current_path}.{key}" if current_path else key + if isinstance(value, (dict, list)): + nested = add_lines(value, full_path) + result[key] = nested + else: + result[key] = value + + # Add line number if we have it for this specific path + if full_path in path_to_lines: + lines = sorted(set(path_to_lines[full_path])) + ranges = self._group_line_numbers_into_ranges(lines) + result[f"{key}_line_numbers"] = { + "min": min(lines), + "max": max(lines), + "ranges": ranges + } + return result + elif isinstance(obj, list): + return [add_lines(item, current_path) for item in obj] + else: + return obj + + return add_lines(structure) + + def _extract_critical_fields_from_chunk(self, chunk_string: str, key_value_pairs: List[tuple], critical_fields: List[str]) -> Dict[str, Any]: + """ + Extract critical fields from a YAML chunk, preserving the original nested structure. + + Parameters: + ----------- + chunk_string : str + The YAML chunk as a string + key_value_pairs : List[Tuple[str, str, int]] + List of (path, value, line_number) for line number tracking + critical_fields : List[str] + List of critical field paths to extract (e.g., ["metadata.name", "status.conditions[].type"]) + For paths like "status.conditions[].type", extracts the full "status.conditions" array. + + Returns: + -------- + Dict: Nested JSON structure preserving YAML hierarchy, with line number metadata + """ + try: + # Parse the chunk as YAML to get the full structure + # The chunk might start with '-' (list item) or be a direct dict + yaml_data = yaml.safe_load(chunk_string) + if yaml_data is None: + return {} + + # Handle case where chunk is a list item (starts with -) + # If it's a list, take the first item (the actual object) + if isinstance(yaml_data, list): + if len(yaml_data) > 0: + yaml_data = yaml_data[0] + else: + return {} + + if not isinstance(yaml_data, dict): + return {} + + # Extract structures for each critical field and merge them + merged_structure = {} + + for critical_field in critical_fields: + field = critical_field.strip() + if not field: + continue + + # Strip items[]. prefix - chunks are already individual items + if field.startswith('items[].'): + field = field[8:] # Remove 'items[].' + + # Extract ONLY the specific path mentioned in critical fields + # For "status.conditions[].type", extract only "status.conditions" (not entire "status") + # For "status", extract only "status" + if '[]' in field: + # Extract up to and including the array (e.g., "status.conditions") + parts = field.split('.') + array_part_idx = None + for i, part in enumerate(parts): + if '[]' in part: + array_part_idx = i + break + + if array_part_idx is not None: + # Extract only up to the array level, not the parent + path_up_to_array = '.'.join(parts[:array_part_idx + 1]) + extracted = self._extract_nested_structure(yaml_data, path_up_to_array) + else: + extracted = self._extract_nested_structure(yaml_data, field) + else: + # Regular path - extract only this specific path + extracted = self._extract_nested_structure(yaml_data, field) + + if extracted is not None: + merged_structure = self._merge_dicts(merged_structure, extracted) + + # If no structure was extracted, return empty + if not merged_structure: + return {} + + # Add line number metadata + structure_with_lines = self._add_line_numbers_to_structure(merged_structure, key_value_pairs) + + return structure_with_lines + except Exception as e: + return {} + + def _remove_line_numbers_from_structure(self, structure: Dict[str, Any]) -> Dict[str, Any]: + """ + Remove all line number metadata from the structure, keeping only the actual field values. + + Args: + structure: Dictionary containing critical fields with line number metadata + + Returns: + Dictionary with line numbers removed + """ + if not isinstance(structure, dict): + return structure + + result = {} + for key, value in structure.items(): + # Skip line number fields + if key == "_line_numbers" or key.endswith("_line_numbers"): + continue + + if isinstance(value, dict): + result[key] = self._remove_line_numbers_from_structure(value) + elif isinstance(value, list): + result[key] = [self._remove_line_numbers_from_structure(item) if isinstance(item, dict) else item for item in value] + else: + result[key] = value + + return result + + def _remove_timestamps_from_structure(self, structure: Dict[str, Any]) -> Dict[str, Any]: + """ + Remove all timestamp fields from the structure before passing to ML classifier. + Only verbose error patterns are needed, not timestamps. + + Args: + structure: Dictionary containing critical fields + + Returns: + Dictionary with timestamp fields removed + """ + if not isinstance(structure, dict): + return structure + + # Exact timestamp field names (case-insensitive exact match, not substring) + timestamp_keys_exact = { + 'lasttransitiontime', 'lasttransition', 'transitiontime', + 'timestamp', 'time', 'created', 'updated', 'creationtimestamp', + 'starttime', 'endtime', 'firsttimestamp', 'lasttimestamp', + 'eventtime', 'observedat', 'observedtime', + } + + # Pattern to match ISO timestamp strings (e.g., "2026-01-12T14:08:28Z") + iso_timestamp_pattern = re.compile(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}') + + result = {} + for key, value in structure.items(): + # Skip timestamp fields by exact key name match (not substring) + if key.lower() in timestamp_keys_exact: + continue + + if isinstance(value, dict): + result[key] = self._remove_timestamps_from_structure(value) + elif isinstance(value, list): + filtered_list = [] + for item in value: + if isinstance(item, dict): + filtered_item = self._remove_timestamps_from_structure(item) + # Only add if the item has non-timestamp content + if filtered_item: + filtered_list.append(filtered_item) + else: + # Check if the value itself is a timestamp string + if isinstance(item, str) and iso_timestamp_pattern.match(item): + continue # Skip timestamp values + filtered_list.append(item) + if filtered_list: # Only add list if it has content after filtering + result[key] = filtered_list + else: + # Check if the value itself is a timestamp string + if isinstance(value, str) and iso_timestamp_pattern.match(value): + continue # Skip timestamp values + result[key] = value + + return result + + def _remove_alphanumeric_patterns_from_messages(self, structure: Dict[str, Any]) -> Dict[str, Any]: + """ + Remove alphanumeric patterns (version strings, commit hashes, etc.) from message fields + before sending to ML classifier. Only verbose error patterns are needed. + + Args: + structure: Dictionary containing critical fields + + Returns: + Dictionary with alphanumeric patterns removed from message fields + """ + if not isinstance(structure, dict): + return structure + + # Patterns to match and remove: + # - Version strings: "0.0.0-7d2dd62c2a73b66aa99e82c90f64ab53dd523ddf" + # - Commit hashes: long hex strings (40+ chars) + # - Version numbers: "v1.2.3", "1.2.3-abc123" + # - UUIDs: already handled, but keep pattern for completeness + + # Pattern for version strings with commit hashes: "0.0.0-" + version_commit_pattern = re.compile(r'\d+\.\d+\.\d+[-\w]*[a-f0-9]{20,}', re.IGNORECASE) + # Pattern for long hex strings (commit hashes, etc.) — 20+ hex chars + long_hex_pattern = re.compile(r'\b[a-f0-9]{20,}\b', re.IGNORECASE) + # Pattern for version numbers: "v1.2.3" or "1.2.3-abc" + version_pattern = re.compile(r'v?\d+\.\d+\.\d+[-\w]*', re.IGNORECASE) + # Pattern for build IDs: require at least one segment that is mostly + # digits/hex (e.g., "abc123-def456-789"), not plain hyphenated words + # like "kube-api-server" which are meaningful identifiers + build_id_pattern = re.compile( + r'\b[a-z0-9]{8,}[-_][a-z0-9]{4,}[-_][a-z0-9]{4,}\b', re.IGNORECASE + ) + + def clean_message_value(value: str) -> str: + """Remove alphanumeric patterns from a message string.""" + if not isinstance(value, str): + return value + + # Remove version strings with commit hashes + value = version_commit_pattern.sub('', value) + # Remove long hex strings + value = long_hex_pattern.sub('', value) + # Remove version numbers + value = version_pattern.sub('', value) + # Remove build IDs + value = build_id_pattern.sub('', value) + + # Clean up extra spaces + value = re.sub(r'\s+', ' ', value).strip() + + return value + + result = {} + for key, value in structure.items(): + key_lower = key.lower() + + # Process message fields + if 'message' in key_lower: + if isinstance(value, str): + cleaned = clean_message_value(value) + if cleaned: # Only add if there's content left after cleaning + result[key] = cleaned + # If message becomes empty after cleaning, skip it + elif isinstance(value, list): + cleaned_list = [] + for item in value: + if isinstance(item, str): + cleaned = clean_message_value(item) + if cleaned: + cleaned_list.append(cleaned) + else: + cleaned_list.append(item) + if cleaned_list: + result[key] = cleaned_list + else: + result[key] = value + elif isinstance(value, dict): + result[key] = self._remove_alphanumeric_patterns_from_messages(value) + elif isinstance(value, list): + processed_list = [] + for item in value: + if isinstance(item, dict): + processed_item = self._remove_alphanumeric_patterns_from_messages(item) + if processed_item: + processed_list.append(processed_item) + else: + processed_list.append(item) + if processed_list: + result[key] = processed_list + else: + result[key] = value + + return result + + def _extract_critical_fields_from_yaml(self, yaml_file_path: str, original_path: str = None) -> Dict[str, Any]: + """ + Extract critical fields from YAML file - process ALL objects. + Returns JSON with filename, extracted critical fields, ML classifications, and classification table. + + Parameters: + ----------- + yaml_file_path : str + Resolved full path to the YAML file + original_path : str, optional + Original path from orchestrator (relative from quay*) for table lookup + """ + file_name = os.path.basename(yaml_file_path) + result = { + 'file_name': file_name, + 'file_path': yaml_file_path, + 'objects': [], + 'classification_table': [] + } + + try: + # Step 1: Load critical fields for this specific file from docx table + file_path_for_lookup = original_path or yaml_file_path + critical_fields = self._load_critical_fields(file_path_for_lookup) + + # Step 2: Extract YAML object chunks (using proven extraction logic) + self._last_chunk_error = None + chunks = self._extract_yaml_object_chunks(yaml_file_path) + + if not chunks: + if self._last_chunk_error: + result['error'] = f"YAML parse error in {file_name}: {self._last_chunk_error}" + else: + result['error'] = f"No YAML objects found in {file_name}" + return result + + # Step 3: Process ALL chunks (all objects) + critical_fields_list = [] # For ML classifier (without line numbers) + objects_with_lines = [] # For result (with line numbers for reference) + + for idx, (chunk_string, start_line, end_line, key_value_pairs) in enumerate(chunks): + # Extract critical fields preserving YAML structure + critical_fields_data = self._extract_critical_fields_from_chunk( + chunk_string, + key_value_pairs, + critical_fields + ) + + # Remove line numbers, timestamps, and alphanumeric patterns from messages for ML classifier + # Only verbose error patterns are needed + critical_fields_clean = self._remove_line_numbers_from_structure(critical_fields_data) + critical_fields_clean = self._remove_timestamps_from_structure(critical_fields_clean) + critical_fields_clean = self._remove_alphanumeric_patterns_from_messages(critical_fields_clean) + critical_fields_list.append(critical_fields_clean) + + # Store object with line numbers for reference + object_result = { + 'object_index': idx, + 'start_line': start_line, + 'end_line': end_line, + 'key_value_pairs_count': len(key_value_pairs), + 'critical_fields': critical_fields_data + } + objects_with_lines.append(object_result) + + #result['objects'] = objects_with_lines + + filtered_critical_fields = [] + filtered_objects = [] + + for cf, obj in zip(critical_fields_list, objects_with_lines): + # Skip empty chunks (wrapper metadata) + if not cf or cf == {}: + if not _is_quiet(): + print(f" [Filter] Skipping Chunk {obj['object_index']+1} (lines {obj['start_line']}-{obj['end_line']}): Empty/wrapper metadata") + continue + + # Skip chunks with only wrapper fields (continue, resourceVersion) + keys = set(cf.keys()) if isinstance(cf, dict) else set() + if keys == {'metadata'}: + nested_keys = set(cf.get('metadata', {}).keys()) + if nested_keys <= {'continue', 'resourceVersion'}: + if not _is_quiet(): + print(f" [Filter] Skipping Chunk {obj['object_index']+1} (lines {obj['start_line']}-{obj['end_line']}): List wrapper metadata only") + continue + + filtered_critical_fields.append(cf) + filtered_objects.append(obj) + + # Replace lists with filtered versions + critical_fields_list = filtered_critical_fields + objects_with_lines = filtered_objects + + # Update result with filtered objects + result['objects'] = objects_with_lines + + # Step 4: Send to ML Classifier + if ML_CLASSIFIER_AVAILABLE and critical_fields_list: + ml_response = classify_critical_fields(critical_fields_list) + + # Handle both structured (dict) and legacy (list) returns + if isinstance(ml_response, dict): + ml_results = ml_response.get('results', []) + ml_error = ml_response.get('error') + else: + ml_results = ml_response if isinstance(ml_response, list) else [] + ml_error = None + + if ml_error: + result.setdefault('warnings', []).append(f"ML classification: {ml_error}") + if not _is_quiet(): + print(f" [ML Warning] {ml_error}") + + # Validate result count matches input + if ml_results and len(ml_results) != len(objects_with_lines): + if not _is_quiet(): + print(f" [ML Warning] Result count mismatch: expected {len(objects_with_lines)}, got {len(ml_results)}") + result.setdefault('warnings', []).append( + f"ML result count mismatch: expected {len(objects_with_lines)}, got {len(ml_results)}" + ) + + # Warn when ML returns empty for a file that had objects + if not ml_results and objects_with_lines: + warning_msg = f"ML classification returned no results for {file_name} ({len(objects_with_lines)} objects submitted)" + result.setdefault('warnings', []).append(warning_msg) + if not _is_quiet(): + print(f" [ML Warning] {warning_msg}") + + # Step 5: Create classification table + classification_table = [] + for obj_result, ml_result in zip(objects_with_lines, ml_results): + chunk_num = obj_result['object_index'] + 1 + lines = f"{obj_result['start_line']}-{obj_result['end_line']}" + classification = ml_result.get('classification', 'Unknown') + reason = ml_result.get('reason', 'No reason provided') + + classification_table.append({ + 'Chunk': chunk_num, + 'Lines': lines, + 'Classification': classification, + 'Reason': reason + }) + + result['classification_table'] = classification_table + result['ml_results'] = ml_results + + # Step 6: Collect raw critical fields for objects classified as + # Error only (not Majority Error — Majority Error represents + # common negative patterns that are widespread and less useful + # for root-cause analysis; only rare/anomalous "Error" objects + # are sent to the LLM RCA agent). + error_objects_raw = [] + for obj_result, ml_result in zip(objects_with_lines, ml_results): + if ml_result.get('classification') == 'Error': + critical_fields_no_lines = self._remove_line_numbers_from_structure(obj_result['critical_fields']) + error_objects_raw.append({ + 'object_index': obj_result['object_index'], + 'start_line': obj_result['start_line'], + 'end_line': obj_result['end_line'], + 'critical_fields': critical_fields_no_lines, + }) + result['error_objects_raw'] = error_objects_raw + else: + result['error_objects_raw'] = [] + if not ML_CLASSIFIER_AVAILABLE: + result['error'] = "ML Classifier not available" + else: + result['error'] = "No critical fields extracted" + + except Exception as e: + result['error'] = str(e) + result['error_objects_raw'] = [] + + return result + + def print_classification_table(self, classification_table: List[Dict[str, Any]], file_name: str = None): + """ + Print the ML classification table in the requested format. + + Args: + classification_table: List of dicts with keys: Chunk, Lines, Classification, Reason + file_name: Optional file name to print as header (e.g., clusterversions.yaml) + """ + if not classification_table: + return + + # Print file name header when processing multiple files + if not _is_quiet(): + if file_name: + print(f"\n--- {file_name} ---") + + # Print header + print(f"\n{'Chunk':<8} {'Lines':<15} {'Classification':<20} {'Reason':<60}") + print("-" * 100) + + # Print each row + for row in classification_table: + chunk = str(row.get('Chunk', '')) + lines = str(row.get('Lines', '')) + classification = str(row.get('Classification', '')) + reason = str(row.get('Reason', '')) + + # Handle long reasons - wrap to next line if needed + if len(reason) <= 60: + print(f"{chunk:<8} {lines:<15} {classification:<20} {reason:<60}") + else: + # First line - show first 60 chars + print(f"{chunk:<8} {lines:<15} {classification:<20} {reason[:60]}") + # Continuation lines - indent to align with Reason column (8+15+20+1 = 44 spaces before Reason) + remaining = reason[60:] + while remaining: + # Align continuation with Reason column start + print(f"{'':<44}{remaining[:60]}") + remaining = remaining[60:] + + print() + + def _is_yaml_path(self, path: str) -> bool: + """True if path looks like a YAML file.""" + p = (path or "").lower() + return p.endswith(".yaml") or p.endswith(".yml") + + def _is_log_path(self, path: str) -> bool: + """True if path looks like a log/txt file (extension or path contains logs).""" + p = (path or "").lower() + return p.endswith(".log") or p.endswith(".txt") or "/logs/" in p or "\\logs\\" in p + + def report_files_availability(self, file_paths: List[str]) -> Dict: + """ + Search which shortlisted files are available in the supplied must-gather directory + or elsewhere under root. Prints a report to terminal and returns data for UI. + Saves a full resolved_paths.json to Results/ for debuggability. + + The JSON has exactly one entry per input path so the total always adds up. + Wildcard paths show all matched physical files. Deduplicated paths are flagged + instead of silently dropped. + """ + import json as _json + base_folder = self.must_gather_base_dir or DEFAULT_BASE_FOLDER + + # ── Track seen resolved paths for deduplication across all input paths ── + seen_resolved: set = set() + + # ── Per-path records (one entry per raw input path) ── + # Each record: raw_path, normalized_path, status, resolved_paths[], note, searched_path + # status: "found" | "not_found" | "error" + # Duplicates are reported as "not_found" with a "duplicate" note. + per_path_records: List[Dict] = [] + + # Also build the legacy lists needed by the return dict and callers + found_in_supplied_dir: List[Dict] = [] + not_found_list: List[str] = [] + + for raw_path in file_paths: + # Try regex-based node-name expansion before generic placeholder normalization + if '<' in raw_path: + node_expanded = self._expand_node_name_placeholder(raw_path, base_folder) + if node_expanded: + new_matches = [] + for ne in node_expanded: + key = os.path.normpath(os.path.normcase(ne)) + if key not in seen_resolved: + seen_resolved.add(key) + new_matches.append(ne) + found_in_supplied_dir.append({"original": raw_path, "resolved": ne}) + if new_matches: + per_path_records.append({ + "raw_path": raw_path, + "status": "found", + "resolved_paths": new_matches, + "note": f"node-name regex expansion: {len(node_expanded)} file(s) matched", + }) + else: + not_found_list.append(raw_path) + per_path_records.append({ + "raw_path": raw_path, + "status": "not_found", + "resolved_paths": [], + "note": "duplicate — node-name expanded files already resolved by earlier entries", + }) + continue + + normalized = self._normalize_placeholders(raw_path) + is_wildcard = self._path_has_wildcard(self._strip_must_gather_prefix(normalized)) + + try: + if is_wildcard: + matches = self._resolve_path_glob(normalized, base_folder) + if matches: + new_matches = [] + dup_matches = [] + for m in matches: + key = os.path.normpath(os.path.normcase(m)) + if key not in seen_resolved: + seen_resolved.add(key) + new_matches.append(m) + found_in_supplied_dir.append({"original": raw_path, "resolved": m}) + else: + dup_matches.append(m) + if new_matches: + per_path_records.append({ + "raw_path": raw_path, + "normalized_path": normalized if normalized != raw_path else None, + "status": "found", + "resolved_paths": new_matches, + "duplicate_paths": dup_matches if dup_matches else None, + "note": f"glob expansion: {len(matches)} physical file(s) matched", + }) + else: + not_found_list.append(raw_path) + per_path_records.append({ + "raw_path": raw_path, + "normalized_path": normalized if normalized != raw_path else None, + "status": "not_found", + "resolved_paths": [], + "note": "duplicate — all matched files already resolved by earlier entries", + }) + else: + # Wildcard found no matches at all + not_found_list.append(raw_path) + per_path_records.append({ + "raw_path": raw_path, + "normalized_path": normalized if normalized != raw_path else None, + "status": "not_found", + "resolved_paths": [], + "note": "wildcard/placeholder pattern — 0 files matched", + "searched_pattern": normalized, + }) + else: + # Concrete (non-wildcard) path + # Fast path: already resolved absolute path under base — skip re-resolution. + if (os.path.isabs(normalized) + and self._is_path_under_base(normalized, base_folder)): + full_path = os.path.normpath(normalized) + else: + full_path = self._resolve_path(normalized, base_folder) + if os.path.exists(full_path) and os.path.isfile(full_path): + key = os.path.normpath(os.path.normcase(full_path)) + if key not in seen_resolved: + seen_resolved.add(key) + found_in_supplied_dir.append({"original": raw_path, "resolved": full_path}) + per_path_records.append({ + "raw_path": raw_path, + "status": "found", + "resolved_paths": [full_path], + "note": "direct", + }) + else: + not_found_list.append(raw_path) + per_path_records.append({ + "raw_path": raw_path, + "status": "not_found", + "resolved_paths": [], + "note": "duplicate — same physical file already resolved by an earlier entry", + }) + else: + not_found_list.append(raw_path) + per_path_records.append({ + "raw_path": raw_path, + "status": "not_found", + "resolved_paths": [], + "note": "not found", + "searched_path": full_path, + }) + except Exception as exc: + not_found_list.append(raw_path) + per_path_records.append({ + "raw_path": raw_path, + "normalized_path": normalized if normalized != raw_path else None, + "status": "error", + "resolved_paths": [], + "note": f"exception during resolution: {exc}", + }) + + # ── Summary counts (all based on per_path_records → always add up to total) ── + total_raw = len(file_paths) + total_found = sum(1 for r in per_path_records if r["status"] == "found") + total_not_found = sum(1 for r in per_path_records if r["status"] == "not_found") + total_error = sum(1 for r in per_path_records if r["status"] == "error") + total_physical = len(seen_resolved) + total_duplicate = sum(1 for r in per_path_records if "duplicate" in (r.get("note") or "").lower()) + + if not _is_quiet(): + print("\n" + "=" * 100) + print("Shortlisted files: availability in must-gather directory") + print("=" * 100) + print(f"Supplied must-gather directory: {base_folder}") + print(f"Total raw paths shortlisted : {total_raw}") + print(f" Found (unique physical) : {total_found} raw paths → {total_physical} physical file(s)") + print(f" In supplied directory : {len(found_in_supplied_dir)}") + print(f" NOT found : {total_not_found}" + + (f" (of which {total_duplicate} are duplicates of already-resolved files)" if total_duplicate else "")) + if total_error: + print(f" Errors during resolution : {total_error}") + print(f" CHECK: {total_found} + {total_not_found}" + + (f" + {total_error} errors" if total_error else "") + + f" = {total_found + total_not_found + total_error}" + + (" ✓" if total_found + total_not_found + total_error == total_raw else " ✗ MISMATCH")) + print() + for rec in per_path_records: + status = rec["status"] + raw = rec["raw_path"] + note = rec.get("note", "") + if status == "found": + paths = rec.get("resolved_paths", []) + print(f" [FOUND] {raw}") + for p in paths: + print(f" -> {p} [{note}]") + elif status == "not_found": + is_dup = "duplicate" in note.lower() + tag = "NOT FOUND (duplicate)" if is_dup else "NOT FOUND" + print(f" [{tag}] {raw}") + if is_dup: + print(f" reason: {note}") + else: + searched = rec.get("searched_path") or rec.get("searched_pattern", "") + if searched: + print(f" searched: {searched}") + elif status == "error": + print(f" [ERROR] {raw} — {note}") + print("=" * 100 + "\n") + + # ── Save resolved_paths.json (one entry per input path — always N entries for N paths) ── + try: + from app_paths import get_results_dir + out_path = get_results_dir() / "resolved_paths.json" + with open(out_path, "w", encoding="utf-8") as _f: + _json.dump({ + "base_folder": base_folder, + "total_raw_paths": total_raw, + "summary": { + "found": total_found, + "not_found": total_not_found, + "not_found_duplicates": total_duplicate, + "error": total_error, + "physical_files": total_physical, + "check": f"{total_found}+{total_not_found}" + + (f"+{total_error}(err)" if total_error else "") + + f"={total_found+total_not_found+total_error}" + + (" OK" if total_found+total_not_found+total_error == total_raw else " MISMATCH"), + }, + "entries": per_path_records, + }, _f, indent=2, ensure_ascii=False) + if not _is_quiet(): + print(f"[Availability] Full path resolution saved to: {out_path}") + except Exception as _e: + if not _is_quiet(): + print(f"[Availability] Warning: could not save resolved_paths.json: {_e}") + + # Build summary text for UI + lines = [ + f"Supplied directory: {base_folder}", + f"Total raw paths shortlisted: {total_raw}", + f" Found: {total_found} paths → {total_physical} physical files", + f" Duplicate (same file, not re-sent): {total_duplicate}", + f" Not found: {total_not_found}", + ] + return { + "base_folder": base_folder, + "total_shortlisted": total_raw, + "total_physical_files": total_physical, + "found_in_supplied_dir": found_in_supplied_dir, + "found_elsewhere": [], + "not_found": not_found_list, + "summary_text": "\n".join(lines), + } + + def analyze_files( + self, + file_paths: List[str], + file_types: List[str] = None, + progress_callback=None, + suggested_files_with_priority: Optional[List[Dict]] = None, + ) -> Dict: + """ + Analyze a list of files (YAML and log). Called by orchestrator_agent.py. + Resolves paths under base folder; files not found are skipped. + Prints all YAML and log files to analyze, grouped by priority (high/medium/low). + + Parameters: + ----------- + file_paths : List[str] + List of file paths (suggested by LLM or default) + file_types : List[str], optional + List of file types to include ('yaml', 'log', 'json'). If None, ['yaml', 'log', 'json']. + suggested_files_with_priority : List[Dict], optional + LLM suggested list with 'path' and 'priority' (high/medium/low) for printing by priority. + + Returns: + -------- + dict : Analysis results (analyzed_files, failed_files, data, summary, log_files_for_analysis, etc.) + """ + if file_types is None: + file_types = ["yaml", "log", "json"] + + # Check if files are accessible under the configured base folder + base_folder = self.must_gather_base_dir or DEFAULT_BASE_FOLDER + access_result = self._check_file_access(file_paths, base_folder) + + # Files not found at their resolved path are marked as not found. + # No parent-directory fallback search is performed. + + # Build priority map from LLM suggested list (original path -> priority) + path_to_priority = {} + if suggested_files_with_priority: + for s in suggested_files_with_priority: + p = (s.get("path") or "").strip() + if p: + path_to_priority[p] = (s.get("priority") or "medium").lower() + + # Deduplicate by resolved path so same file (e.g. kubelet from LLM + ALWAYS_INCLUDE) is processed once + def _dedupe_accessible_by_resolved(items: list) -> list: + seen = set() + out = [] + for item in items: + res = item.get("resolved", "") + key = os.path.normpath(os.path.normcase(res)) if res else res + if key and key not in seen: + seen.add(key) + out.append(item) + return out + + accessible_deduped = _dedupe_accessible_by_resolved(access_result["accessible"]) + # Create mapping: resolved_path -> original_path for table lookup + path_mapping = {item['resolved']: item['original'] for item in accessible_deduped} + + # Classify accessible items as YAML or log (only actual files, not directories) + all_resolved = [] + for item in accessible_deduped: + res = item.get("resolved", "") + orig = item.get("original", "") + if not res or item.get("note") == "path is a directory": + continue + if not os.path.isfile(res): + continue + all_resolved.append((res, orig)) + + yaml_files = [res for res, _ in all_resolved if self._is_yaml_path(res)] + # Deduplicate log files by normalized path so same file (e.g. kubelet_service from two names) is processed once + _log_seen = set() + log_files = [] + for res, _ in all_resolved: + if not self._is_log_path(res): + continue + key = os.path.normpath(os.path.normcase(os.path.abspath(res))) + if key not in _log_seen: + _log_seen.add(key) + log_files.append(res) + + # Print resolved file paths + if not _is_quiet(): + yaml_here = [(r, o) for r, o in all_resolved if self._is_yaml_path(r)] + log_here = [(r, o) for r, o in all_resolved if self._is_log_path(r)] + other_here = [(r, o) for r, o in all_resolved if not self._is_yaml_path(r) and not self._is_log_path(r)] + print("\n" + "=" * 100) + print(f"Resolved files to analyze ({len(all_resolved)} total)") + print("=" * 100) + for res, orig in yaml_here: + print(f" [YAML] {res}") + for res, orig in log_here: + print(f" [LOG] {res}") + for res, orig in other_here: + print(f" [FILE] {res}") + print("=" * 100 + "\n") + + total_yaml_bytes = 0 + yaml_file_sizes = {} + if progress_callback and (yaml_files or log_files): + try: + progress_callback("file_analysis_intent", "Intent: Load critical fields from docx, extract YAML objects, and classify each with ML (Error, Majority Error, Majority, CONFIG). Log files are listed for analysis.", None) + all_names = [os.path.basename(r) for r in yaml_files] + [os.path.basename(r) for r in log_files] + progress_callback("yaml_files_list", "Shortlisted files for analysis (YAML + log, resolved under base dir):", {"files": all_names}) + except Exception: + pass + + if yaml_files: + # Process all YAML files (clusteroperators.yaml, clusterversions.yaml, etc.) + for yaml_file in yaml_files: + file_key = os.path.basename(yaml_file) + original_path = path_mapping.get(yaml_file, yaml_file) + if progress_callback: + try: + progress_callback("yaml_processing", f"Sending to ML: {file_key}", {"file": file_key}) + except Exception: + pass + try: + sz = os.path.getsize(yaml_file) + total_yaml_bytes += sz + yaml_file_sizes[file_key] = sz + except Exception: + yaml_file_sizes[file_key] = 0 + + if not _is_quiet(): + print(f"\n[Data Analyzer] Processing: {file_key}") + metadata = self._extract_critical_fields_from_yaml(yaml_file, original_path) + + # Store in memory + self.extracted_metadata[file_key] = metadata + + # Progress: classification table for this file (with brief explanation of labels) + if progress_callback and metadata.get('classification_table'): + try: + progress_callback("classification", f"ML results for {file_key}", { + "file": file_key, + "table": metadata['classification_table'], + "explanation": "Error = rare/anomaly; Majority Error = common negative pattern; Majority = common healthy; CONFIG = config change.", + }) + except Exception: + pass + elif progress_callback and file_key: + # No table: report why + reason = "no objects found" if not metadata.get('objects') else "no critical fields extracted or no ML table" + try: + progress_callback("classification_no_output", f"No ML output for {file_key}: {reason}.", {"file": file_key, "reason": reason}) + except Exception: + pass + + # Check for errors or issues + if not _is_quiet(): + if 'error' in metadata: + print(f"\n[Warning] Error processing {file_key}: {metadata['error']}") + elif not metadata.get('objects'): + print(f"\n[Info] {file_key}: No objects found in file") + elif not metadata.get('classification_table'): + print(f"\n[Info] {file_key}: No classification table generated (may have no critical fields extracted)") + + # Print classification table for this file if available + if 'classification_table' in metadata and metadata['classification_table']: + # Print file name so user knows which file the table is for + self.print_classification_table(metadata['classification_table'], file_key) + + # Process log/txt files with ML_LOG_CLASSIFICATION (Error, Information, Warning) + # analyze_logs_ml is the public entry point: takes a single file_path, returns + # {"status", "files": {"rare_errors": {path, templates, lines}, ...}, "totals", ...} + log_processing_result = {} + if log_files and ML_LOG_CLASSIFICATION_AVAILABLE: + output_dir = str(get_application_dir() / "Results" / "log_classifications") + _level_map = { + "rare_errors": "RareError", + "highfreq_errors": "HighFreqError", + "warnings": "Warning", + "information": "Information", + "unknown": "Unknown", + "config_changes": "CONFIG", + } + all_per_file = [] + try: + for log_file in log_files: + try: + result = analyze_logs_ml(log_file, output_dir=output_dir) + if result.get("error"): + if not _is_quiet(): + print(f"[Data Analyzer] Log classification error for {os.path.basename(log_file)}: {result['error']}") + continue + saved = {} + for out_key, file_info in result.get("files", {}).items(): + level_name = _level_map.get(out_key, out_key) + if isinstance(file_info, dict) and file_info.get("path"): + saved[level_name] = file_info["path"] + all_per_file.append({"file": log_file, "saved": saved}) + if not _is_quiet(): + print(f"[Data Analyzer] Log processed: {os.path.basename(log_file)} " + f"({result.get('totals', {}).get('lines', 0)} lines, " + f"{result.get('totals', {}).get('templates', 0)} templates)") + except Exception as e: + if not _is_quiet(): + print(f"[Data Analyzer] Log classification error for {os.path.basename(log_file)}: {e}") + + log_processing_result = { + "per_file": all_per_file, + "logs_count": len(all_per_file), + "summary": {}, + } + + if not _is_quiet() and all_per_file: + print("\n" + "=" * 80) + print("LOG PROCESSING (Error, Information, Warning) — one set per log file (each file once)") + print("=" * 80) + print(f" Log files processed: {len(all_per_file)}") + for pf in all_per_file: + print(f" {pf.get('file', '')}:") + for level_name, path in (pf.get("saved") or {}).items(): + print(f" {level_name} -> {path}") + print("=" * 80 + "\n") + + if progress_callback: + try: + progress_callback("log_processing_result", "Log processing complete (Error, Information, Warning)", log_processing_result) + except Exception: + pass + except Exception as e: + if not _is_quiet(): + print(f"[Data Analyzer] Log classification error: {e}") + log_processing_result = {"error": str(e), "summary": {}, "saved_files": {}} + if progress_callback: + try: + progress_callback("log_processing_result", "Log processing failed", log_processing_result) + except Exception: + pass + elif log_files and not ML_LOG_CLASSIFICATION_AVAILABLE: + if not _is_quiet(): + print("[Data Analyzer] Log files present but ML_LOG_CLASSIFICATION not available (install drain3).") + if progress_callback: + try: + progress_callback("log_processing_result", "Log processing skipped (drain3 not installed)", {"error": "drain3 not installed", "summary": {}, "saved_files": {}}) + except Exception: + pass + + # Load output flags from config (with defaults) + DEFAULT_OUTPUT_FLAGS = { + 'include_errors': True, + 'include_majority_errors': True, + 'include_config_changes': True, + 'include_normal_reports': False, + 'include_summary': True + } + output_flags = {**DEFAULT_OUTPUT_FLAGS, **self.config.get('output_flags', {})} + + # Aggregate classified objects based on flags across all files + aggregated_results = {} + files_with_no_output = [] + all_warnings = [] + + for file_key, file_data in self.extracted_metadata.items(): + ml_results = file_data.get('ml_results', []) + objects_with_lines = file_data.get('objects', []) + + # Collect per-file warnings (ML failures, parse errors, etc.) + for w in file_data.get('warnings', []): + all_warnings.append(f"{file_key}: {w}") + + if not ml_results: + if not file_data.get('classification_table'): + file_error = file_data.get('error', '') + if 'parse error' in (file_error or '').lower(): + reason = f"YAML parse error: {file_error}" + elif not objects_with_lines: + reason = "no objects in file" + else: + reason = "no critical fields matched or ML produced no table" + files_with_no_output.append({"file": file_key, "reason": reason}) + continue + + # Filter objects based on output flags + file_classified_objects = [] + + for obj_result, ml_result in zip(objects_with_lines, ml_results): + classification = ml_result.get('classification') + + # Apply flags to determine if this object should be included + should_include = False + + if classification == 'Error' and output_flags.get('include_errors', True): + should_include = True + elif classification == 'Majority Error' and output_flags.get('include_majority_errors', True): + should_include = True + elif classification == 'CONFIG' and output_flags.get('include_config_changes', True): + should_include = True + elif classification == 'Majority' and output_flags.get('include_normal_reports', False): + should_include = True + + if should_include: + # Remove line numbers from critical fields for clean output + critical_fields_clean = self._remove_line_numbers_from_structure(obj_result['critical_fields']) + + file_classified_objects.append({ + 'object_index': obj_result['object_index'], + 'start_line': obj_result['start_line'], + 'end_line': obj_result['end_line'], + 'classification': classification, + 'reason': ml_result.get('reason', ''), + 'patterns': ml_result.get('verbose_patterns', []), + 'has_config_change': ml_result.get('has_config_change', False), + 'critical_fields': critical_fields_clean + }) + + # Add file results if any objects were included + if file_classified_objects: + file_result = { + 'objects': file_classified_objects + } + + # Add summary if enabled + if output_flags.get('include_summary', True): + summary = { + 'total_objects': len(ml_results), + 'error_count': sum(1 for ml in ml_results if ml.get('classification') == 'Error'), + 'majority_error_count': sum(1 for ml in ml_results if ml.get('classification') == 'Majority Error'), + 'config_change_count': sum(1 for ml in ml_results if ml.get('classification') == 'CONFIG'), + 'normal_count': sum(1 for ml in ml_results if ml.get('classification') == 'Majority'), + 'included_in_output': len(file_classified_objects) + } + file_result['summary'] = summary + + aggregated_results[file_key] = file_result + + if progress_callback and files_with_no_output: + try: + progress_callback("files_no_output", "Why no ML output for some files:", {"files": files_with_no_output}) + except Exception: + pass + + # Store aggregated results JSON to file + results_output_path = get_application_dir() / "errors_aggregate.json" + + # Add metadata to output + output_data = { + 'output_flags': output_flags, + 'files': aggregated_results + } + + try: + with open(results_output_path, 'w', encoding='utf-8') as f: + json.dump(output_data, f, indent=2, default=str) + except (IOError, OSError) as e: + if not _is_quiet(): + print(f"[DataAnalyzer] Failed to write {results_output_path}: {e}") + results_output_path = None + + # Build included_categories and results_text unconditionally + # (used by progress_callback and return value even in quiet mode) + included_categories = [] + if output_flags.get('include_errors', True): + included_categories.append('Error') + if output_flags.get('include_majority_errors', True): + included_categories.append('Majority Error') + if output_flags.get('include_config_changes', True): + included_categories.append('CONFIG') + if output_flags.get('include_normal_reports', False): + included_categories.append('Majority') + + results_text = json.dumps(output_data, indent=2, default=str) + + if not _is_quiet(): + print("\n" + "=" * 80) + print("YAML Classifications (filtered by output flags)") + print("=" * 80) + print(f"Included categories: {', '.join(included_categories)}") + print("=" * 80) + print(results_text) + print("=" * 80) + if results_output_path: + print(f"Written to: {results_output_path}") + else: + print("Warning: errors_aggregate.json could not be written") + + if progress_callback: + try: + progress_callback("yaml_classifications", "YAML Classifications (filtered by output flags)", { + "text": results_text, + "path": str(results_output_path) if results_output_path else None, + "included_categories": included_categories + }) + except Exception: + pass + + # Return structure expected by orchestrator + # Aggregate classification tables from ALL processed files (not just the first one) + classification_table = [] + for file_data in self.extracted_metadata.values(): + if 'classification_table' in file_data: + classification_table.extend(file_data['classification_table']) + if not classification_table: + classification_table = None + + # Log files are listed for analysis (no ML classification; available for future processing) + log_files_original = [path_mapping.get(r, r) for r in log_files] + + return { + "analyzed_files": [item['original'] for item in accessible_deduped], + "failed_files": [item['original'] for item in access_result['not_found'] + access_result['errors']], + "data": self.extracted_metadata, + "classification_table": classification_table, + "ml_classification_result": output_data, + "yaml_classifications_path": str(results_output_path) if results_output_path else None, + "output_flags": output_flags, + "included_categories": included_categories, + "total_yaml_bytes": total_yaml_bytes, + "files_with_no_output": files_with_no_output, + "warnings": all_warnings, + "log_files_for_analysis": log_files_original, + "yaml_files_processed": yaml_files, + "log_files_resolved": log_files, + "log_processing_result": log_processing_result, + "yaml_file_sizes": yaml_file_sizes, + "summary": { + "files_processed": len(yaml_files), + "log_files_for_analysis": len(log_files), + "critical_fields_extracted": sum(len(m.get('objects', [])) for m in self.extracted_metadata.values()), + }, + "errors": [item.get('error', '') for item in access_result['errors']], + "file_count": len(file_paths), + "file_types_processed": file_types, + "file_access_check": access_result, + } diff --git a/intelliaide/Main-program/llm_rca_agent.py b/intelliaide/Main-program/llm_rca_agent.py new file mode 100644 index 0000000..d939dba --- /dev/null +++ b/intelliaide/Main-program/llm_rca_agent.py @@ -0,0 +1,601 @@ +""" +RCA Agent — pure-Python data preparation helpers. + +Provides chunking, prompt building, and manifest generation for the +IntelliAide RCA pipeline. All LLM reasoning is performed by the +orchestrating agent (Lightspeed's Claude) that reads the files written +here. No HTTP calls or external credentials are needed. +""" + +import os +import re +import json +from typing import Dict, List, Any, Optional, Tuple +from pathlib import Path + + +def _remove_line_numbers_from_structure(structure: Any) -> Any: + """Remove all line number metadata from a structure (dict/list).""" + if isinstance(structure, dict): + result = {} + for key, value in structure.items(): + if key == "_line_numbers" or (isinstance(key, str) and key.endswith("_line_numbers")): + continue + result[key] = _remove_line_numbers_from_structure(value) + return result + if isinstance(structure, list): + return [_remove_line_numbers_from_structure(item) for item in structure] + return structure + + +try: + import tiktoken + TIKTOKEN_AVAILABLE = True +except ImportError: + TIKTOKEN_AVAILABLE = False + + +CHARS_PER_TOKEN: Dict[str, float] = { + "claude": 3.5, + "llama": 3.8, + "gpt-4": 3.7, + "gpt-3": 4.0, + "mistral": 3.9, +} + + +def estimate_tokens(text: str, model_id: str = "") -> int: + """Estimate token count using model-aware chars-per-token ratios.""" + if not text: + return 0 + if TIKTOKEN_AVAILABLE: + try: + encoding = tiktoken.get_encoding("cl100k_base") + return len(encoding.encode(text)) + except Exception: + pass + model_lower = (model_id or "").lower() + cpt = next((v for k, v in CHARS_PER_TOKEN.items() if k in model_lower), 3.7) + return max(1, int(len(text) / cpt)) + + +SUMMARY_MAX_CHARS = 80000 + + +def _get_folder_path(file_path: str) -> str: + """Extracts the folder path from a file string to group logical components.""" + if not file_path: + return "unknown" + p = Path(file_path) + return str(p.parent) if len(p.parts) > 1 else "root" + + +def _split_large_log_entry_tokens(entry: Dict[str, Any], max_tokens: int) -> List[Dict[str, Any]]: + """Split an oversized log entry into sub-entries at line boundaries based on tokens.""" + content = entry.get("content", "") + if estimate_tokens(content) <= max_tokens: + return [entry] + + lines = content.split("\n") + parts: List[str] = [] + current_lines: List[str] = [] + current_tokens = 0 + + for line in lines: + line_toks = estimate_tokens(line + "\n") + if current_tokens + line_toks > max_tokens and current_lines: + parts.append("\n".join(current_lines)) + current_lines = [line] + current_tokens = line_toks + else: + current_lines.append(line) + current_tokens += line_toks + + if current_lines: + parts.append("\n".join(current_lines)) + + file_name = entry.get("file", "unknown") + total = len(parts) + return [ + {**entry, "file": f"{file_name} (part {i+1}/{total})", "content": part} + for i, part in enumerate(parts) + ] + + +def load_config(config_path: str = "config.json") -> Dict: + """Load configuration from JSON file. + + Only the ``max_chunk_tokens`` and ``model_id`` fields under the ``claude`` + key are used by the pure-Python helpers in this module (for token budget + estimation). No credentials or endpoint URLs are read here. + """ + try: + from app_paths import get_config_path + path = get_config_path() if config_path == "config.json" else Path(config_path) + except ImportError: + path = Path(__file__).parent / config_path + default: Dict = {"claude": {"max_chunk_tokens": 60000, "model_id": ""}} + if not path.exists(): + return default + try: + with open(path, "r", encoding="utf-8") as f: + cfg = json.load(f) + if "claude" in cfg: + default["claude"].update(cfg["claude"]) + except Exception: + pass + return default + + +# ISO timestamp patterns +_ISO_TS = re.compile(r"(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?)") +_LOG_TIME = re.compile(r'time="(\d{4}-\d{2}-\d{2}(?:T|\s)\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?)"', re.IGNORECASE) +_LOG_TIME_CRIO = re.compile(r'time="(\d{4}-\d{2}-\d{2})\s+\.(\d+)Z"', re.IGNORECASE) +_LOG_TIME_DATE = re.compile(r'time="(\d{4}-\d{2}-\d{2})[\s.]', re.IGNORECASE) + + +def _parse_iso_like(ts_str: str) -> Optional[str]: + """Normalize to sortable ISO-like string. Returns None if unparseable.""" + if not ts_str or len(ts_str) < 19: + return None + normalized = ts_str.replace(" ", "T", 1).strip() + if "T" in normalized: + date_part, rest = normalized.split("T", 1) + rest = re.sub(r"[^\d:]", "", rest[:8]) + if len(rest) >= 6: + return f"{date_part}T{rest[:2]}:{rest[2:4]}:{rest[4:6]}" + return normalized[:19] if len(normalized) >= 19 else None + + +def _extract_timestamps_from_value(value: Any, key_path: str, out: List[Tuple[str, str, str]]) -> None: + """Recursively extract timestamp keys and ISO values from YAML-like structure.""" + if isinstance(value, dict): + for k, v in value.items(): + key_lower = k.lower() if isinstance(k, str) else "" + path = f"{key_path}.{k}" if key_path else k + if any(t in key_lower for t in ("timestamp", "time", "created", "updated", "transition", "eventtime", "observed")): + if isinstance(v, str) and _ISO_TS.search(v): + sortable = _parse_iso_like(_ISO_TS.search(v).group(1)) + if sortable: + out.append((sortable, path, v[:120])) + else: + _extract_timestamps_from_value(v, path, out) + else: + _extract_timestamps_from_value(v, path, out) + elif isinstance(value, list): + for i, item in enumerate(value): + _extract_timestamps_from_value(item, f"{key_path}[{i}]", out) + elif isinstance(value, str) and key_path and _ISO_TS.search(value): + sortable = _parse_iso_like(_ISO_TS.search(value).group(1)) + if sortable: + out.append((sortable, key_path, value[:120])) + + +def build_chronology_from_payload( + payload_yaml: Dict[str, List[Dict[str, Any]]], + log_error_entries: Optional[List[Dict[str, Any]]], +) -> List[Dict[str, str]]: + """Build a time-ordered chronology from YAML timestamps and log line timestamps.""" + events: List[Tuple[str, str, str]] = [] + + for file_name, objects in (payload_yaml or {}).items(): + for obj in objects: + cf = obj if isinstance(obj, dict) else (obj.get("critical_fields") or obj) + _extract_timestamps_from_value(cf, f"YAML:{file_name}", events) + + for entry in (log_error_entries or []): + content = entry.get("content") or "" + file_name = entry.get("file") or "log" + for line in content.split("\n"): + line = line.strip() + if not line: + continue + m = _LOG_TIME.search(line) + if m: + raw = m.group(1) + sortable = _parse_iso_like(raw.replace(" ", "T")) + if sortable: + events.append((sortable, file_name, line[:150].replace("\n", " "))) + continue + m_crio = _LOG_TIME_CRIO.search(line) + if m_crio: + date_part = m_crio.group(1) + frac = m_crio.group(2)[:9].ljust(9, "0") + sortable = f"{date_part}T00:00:00.{frac}" + events.append((sortable, file_name, line[:150].replace("\n", " "))) + continue + m2 = _LOG_TIME_DATE.search(line) + if m2: + sortable = _parse_iso_like(m2.group(1) + "T00:00:00") + if sortable: + events.append((sortable, file_name, line[:150].replace("\n", " "))) + continue + iso = _ISO_TS.match(line) + if iso: + sortable = _parse_iso_like(iso.group(1)) + if sortable: + events.append((sortable, file_name, line[:150].replace("\n", " "))) + + seen = set() + unique = [] + for sortable_ts, source, snippet in events: + key = (sortable_ts, snippet[:80]) + if key in seen: + continue + seen.add(key) + unique.append((sortable_ts, source, snippet)) + unique.sort(key=lambda x: x[0]) + + return [{"timestamp": ts, "source": src, "snippet": snip} for ts, src, snip in unique] + + +def format_chronology_block(chronology: List[Dict[str, str]], max_events: int = 150) -> str: + """Format chronology list as a clear text block for the prompt.""" + if not chronology: + return "" + if len(chronology) <= max_events: + sampled = chronology + else: + half = max_events // 2 + sampled = chronology[:half] + chronology[-half:] + lines = [] + for e in sampled: + ts = e.get("timestamp", "") + src = e.get("source", "") + snip = (e.get("snippet") or "").replace("\n", " ").strip()[:120] + lines.append(f" {ts} | {src} | {snip}") + return "\n".join(lines) + + +def prepare_payload_for_llm(ml_classification_result: Dict[str, Any]) -> Dict[str, List[Dict[str, Any]]]: + """Prepare data for LLM: normalize input format, remove line numbers, keep only YAML objects.""" + payload = {} + for file_name, file_data in (ml_classification_result or {}).items(): + if isinstance(file_data, dict) and "objects" in file_data: + objects = file_data["objects"] + elif isinstance(file_data, list): + objects = file_data + else: + continue + + yaml_objects = [] + for obj in objects: + if not isinstance(obj, dict): + continue + raw_critical_fields = obj.get("critical_fields", {}) + cleaned = _remove_line_numbers_from_structure(raw_critical_fields) + if cleaned: + yaml_objects.append(cleaned) + if yaml_objects: + payload[file_name] = yaml_objects + return payload + + +def build_rca_prompt(payload_for_llm: Dict[str, Any], problem_statement: Optional[str] = None) -> str: + """Build the full RCA user-content prompt string from the payload.""" + chronology = payload_for_llm.get("chronology") or [] + chronology_block = format_chronology_block(chronology) + payload_for_data = {k: v for k, v in payload_for_llm.items() if k != "chronology"} + data_str = json.dumps(payload_for_data, indent=2, default=str) + user_problem = (problem_statement or "").strip() or "Not specified" + + if chronology_block: + chronology_section = f""" +2. CHRONOLOGY OF EVENTS (include in your ## Chronology of Events section): +{chronology_block} + +3. AGGREGATED DATA (YAML/LOG/JSON error objects with file metadata): +""" + else: + chronology_section = """ +2. AGGREGATED DATA (YAML/LOG/JSON error objects with file metadata): +""" + + return f"""You are an expert OpenShift/Kubernetes system analyst performing root cause analysis. + +TASK: Identify the root cause(s) and state clearly: "The key cause for the user's problem is: [...]" + +SOURCES: + +1. USER REPORTED ISSUE (primary focus): +{user_problem} +{chronology_section} +{data_str} + +INSTRUCTIONS: +- Correlate the user's issue with YAML error patterns and/or log error content. +- Identify PRIMARY root cause(s) with evidence, SECONDARY contributing factors. +- Prioritize root causes by relevance to the USER REPORTED ISSUE. +- Base analysis on actual evidence only — do not fabricate. + +OUTPUT FORMAT — use these EXACT ## headings (frontend parses them programmatically): + +## User Reported Issue +## Executive Summary +## Chronology of Events +## Primary Root Cause(s) +## Secondary Causes / Contributing Factors +## Aggregated Error Patterns +(MUST be a pipe-delimited markdown table: +| Pattern | Source | Classification | Significance | +|---------|--------|----------------|--------------| +| ... | ... | ... | ... |) +## Recommendations +(MANDATORY — always include. Numbered, actionable remediation steps. + This section MUST appear last in the report.) + +Do NOT rename headings. Do NOT use plain text for Aggregated Error Patterns.""" + + + + +def chunk_payload( + yaml_errors: Dict[str, Any], + log_entries: List[Dict[str, Any]], + max_chunk_tokens: int = 80000 +) -> List[Dict[str, Any]]: + """Groups files logically by folder path, then builds chunks up to max_chunk_tokens.""" + all_items = [] + + for fkey, objs in yaml_errors.items(): + all_items.append({"folder": _get_folder_path(fkey), "type": "yaml", "file": fkey, "payload": objs}) + + for entry in log_entries: + fkey = entry.get("file", "") + all_items.append({"folder": _get_folder_path(fkey), "type": "log", "file": fkey, "payload": entry}) + + grouped_items = {} + for item in all_items: + grouped_items.setdefault(item["folder"], []).append(item) + + chunks = [] + current_chunk = {"yaml_errors": {}, "log_entries": [], "_tokens": 0} + + def close_chunk(): + nonlocal current_chunk + if current_chunk["yaml_errors"] or current_chunk["log_entries"]: + clean_chunk = {"yaml_errors": current_chunk["yaml_errors"], "log_entries": current_chunk["log_entries"]} + chunks.append(clean_chunk) + current_chunk = {"yaml_errors": {}, "log_entries": [], "_tokens": 0} + + for folder, items in grouped_items.items(): + for item in items: + item_str = json.dumps(item["payload"], indent=2, default=str) + item_tokens = estimate_tokens(item_str) + + if item_tokens > max_chunk_tokens: + if item["type"] == "log": + split_logs = _split_large_log_entry_tokens(item["payload"], max_chunk_tokens) + for sub_log in split_logs: + sub_toks = estimate_tokens(json.dumps(sub_log, indent=2, default=str)) + if current_chunk["_tokens"] + sub_toks > max_chunk_tokens: + close_chunk() + current_chunk["log_entries"].append(sub_log) + current_chunk["_tokens"] += sub_toks + elif item["type"] == "yaml": + if current_chunk["_tokens"] > 0: + close_chunk() + current_chunk["yaml_errors"][item["file"]] = item["payload"] + close_chunk() + continue + + if current_chunk["_tokens"] + item_tokens > max_chunk_tokens: + close_chunk() + + if item["type"] == "yaml": + current_chunk["yaml_errors"][item["file"]] = item["payload"] + else: + current_chunk["log_entries"].append(item["payload"]) + current_chunk["_tokens"] += item_tokens + + close_chunk() + print(f"[RCA Chunked] Produced {len(chunks)} contextual chunk(s) (Limit: {max_chunk_tokens:,} tokens)") + return chunks + + + + +def prepare_rca_chunks( + ml_classification_result: Dict[str, Any], + job_dir: str, + priority: str, + log_error_entries: Optional[List[Dict[str, Any]]] = None, + config_path: str = "config.json", +) -> Dict[str, Any]: + """Pure-Python Map phase: chunk cluster data and write formatted prompt files to disk. + + Replicates all of run_rca_chunked()'s pure-Python work — token estimation, + chunking, chronology extraction, prompt formatting — WITHOUT making any LLM + call. The resulting chunk files are ready for the orchestrating agent (Claude) + to read and reason over inline. + + Args: + ml_classification_result: Output of analyze_data.py (yaml_errors dict). + job_dir: Path to the shared IntelliAide job directory. + priority: "high" | "medium" | "low" — used in output file names. + log_error_entries: List of log error dicts from analyze_data.py (may be None). + config_path: Path to config.json (used to read max_chunk_tokens / model_id). + + Writes: + /chunk__.md — formatted RCA prompt per chunk + /rca_chunks__manifest.json + + Returns manifest dict: + { + "priority": "high", + "chunk_count": 3, + "chunk_files": [ + {"path": "/chunk_high_1.md", "estimated_tokens": 58200}, + ... + ], + "manifest_path": "/rca_chunks_high_manifest.json" + } + """ + config = load_config(config_path) + claude_config = config.get("claude", {}) + max_chunk_tokens = claude_config.get("max_chunk_tokens", 60000) + + payload_yaml = prepare_payload_for_llm(ml_classification_result) + log_entries = log_error_entries or [] + + chunks = chunk_payload( + yaml_errors=payload_yaml, + log_entries=log_entries, + max_chunk_tokens=max_chunk_tokens, + ) + + job_path = Path(job_dir) + chunk_files: List[Dict[str, Any]] = [] + + for i, chunk_data in enumerate(chunks, start=1): + chunk_yaml = chunk_data["yaml_errors"] + chunk_logs = chunk_data["log_entries"] + + chunk_llm_payload: Dict[str, Any] = {"yaml_errors": chunk_yaml} + if chunk_logs: + chunk_llm_payload["log_errors"] = chunk_logs + + chronology = build_chronology_from_payload( + chunk_yaml if isinstance(chunk_yaml, dict) else {}, chunk_logs, + ) + if chronology: + chunk_llm_payload["chronology"] = chronology + + prompt_str = build_rca_prompt(chunk_llm_payload, problem_statement=None) + estimated_toks = estimate_tokens(prompt_str) + + chunk_path = job_path / f"chunk_{priority}_{i}.md" + chunk_path.write_text(prompt_str, encoding="utf-8") + + chunk_files.append({ + "path": str(chunk_path), + "estimated_tokens": estimated_toks, + }) + + manifest = { + "priority": priority, + "chunk_count": len(chunk_files), + "chunk_files": chunk_files, + } + manifest_path = job_path / f"rca_chunks_{priority}_manifest.json" + manifest_path.write_text(json.dumps(manifest, indent=2, ensure_ascii=False)) + manifest["manifest_path"] = str(manifest_path) + + print( + f"[prepare_rca_chunks] priority={priority} chunks={len(chunk_files)} " + f"manifest={manifest_path}", + file=__import__("sys").stderr, + ) + return manifest + + +def prepare_reduce_batches( + summary_files: List[str], + job_dir: str, + level: int, + problem_statement: Optional[str] = None, + config_path: str = "config.json", +) -> Dict[str, Any]: + """Pure-Python Reduce phase: batch Claude-generated summary files by token budget. + + Replicates _hierarchical_reduce()'s token estimation and batching decisions + WITHOUT making any LLM call. The returned manifest tells the orchestrating + agent (Claude) exactly which summary files belong in each batch and where to + write its synthesis output, so Claude can execute just the reasoning step. + + Args: + summary_files: List of paths to Claude-written chunk summary files. + job_dir: Path to the shared IntelliAide job directory. + level: Reduce level (1 for first reduce, 2+ for recursive). + problem_statement: Original problem text (used for overhead estimation). + config_path: Path to config.json (max_chunk_tokens, model_id). + + Writes: + /reduce_level_manifest.json + + Returns manifest dict: + { + "level": 1, + "batch_count": 2, + "is_final": false, + "batches": [ + { + "batch_index": 0, + "summary_files": ["/chunk_summary_high_1.md", ...], + "output_file": "/reduce_level1_batch1.md" + }, + ... + ], + "manifest_path": "/reduce_level1_manifest.json" + } + + If is_final=True there is exactly one batch. The orchestrating agent should + write its synthesis to batches[0]["output_file"] and treat that as the final + RCA text. If is_final=False, the agent processes each batch, then calls + prepare_reduce_batches() again at level+1 with the output_file paths. + """ + import sys as _sys + + config = load_config(config_path) + claude_config = config.get("claude", {}) + max_chunk_tokens = claude_config.get("max_chunk_tokens", 60000) + model_id = claude_config.get("model_id", "") + + overhead = estimate_tokens(problem_statement or "", model_id) + 2000 + available_tokens = max_chunk_tokens - overhead + + summaries: List[Dict[str, Any]] = [] + for fp in summary_files: + try: + text = Path(fp).read_text(encoding="utf-8") + except Exception as e: + print(f"[prepare_reduce_batches] Warning: could not read {fp}: {e}", file=_sys.stderr) + text = "" + summaries.append({ + "file": fp, + "tokens": estimate_tokens(text, model_id), + }) + + batches: List[List[Dict[str, Any]]] = [] + current_batch: List[Dict[str, Any]] = [] + current_tokens = 0 + + for s in summaries: + if current_tokens + s["tokens"] > available_tokens and current_batch: + batches.append(current_batch) + current_batch = [s] + current_tokens = s["tokens"] + else: + current_batch.append(s) + current_tokens += s["tokens"] + + if current_batch: + batches.append(current_batch) + + job_path = Path(job_dir) + manifest_batches = [] + for i, batch in enumerate(batches): + manifest_batches.append({ + "batch_index": i, + "summary_files": [s["file"] for s in batch], + "output_file": str(job_path / f"reduce_level{level}_batch{i + 1}.md"), + }) + + manifest = { + "level": level, + "batch_count": len(batches), + "is_final": len(batches) == 1, + "batches": manifest_batches, + } + manifest_path = job_path / f"reduce_level{level}_manifest.json" + manifest_path.write_text(json.dumps(manifest, indent=2, ensure_ascii=False)) + manifest["manifest_path"] = str(manifest_path) + + print( + f"[prepare_reduce_batches] level={level} batches={len(batches)} " + f"is_final={manifest['is_final']} manifest={manifest_path}", + file=_sys.stderr, + ) + return manifest + + diff --git a/intelliaide/Main-program/must_gather_file_selector.py b/intelliaide/Main-program/must_gather_file_selector.py new file mode 100644 index 0000000..b333427 --- /dev/null +++ b/intelliaide/Main-program/must_gather_file_selector.py @@ -0,0 +1,198 @@ +""" +Must-Gather File Selector Module + +Builds file-selection prompts from must-gather structure documentation. +All LLM reasoning is performed by the orchestrating agent (Lightspeed's Claude) +that reads the prompt file written here. No HTTP calls or credentials needed. +""" + +import os +import json +from typing import Dict, List, Optional +from pathlib import Path + + +def load_config_by_name(config_file_name: Optional[str] = None) -> Dict: + """Resolve config file by name, load it, and return the full config dict.""" + if config_file_name is None: + config_file_name = "config.json" + script_dir = os.path.dirname(os.path.abspath(__file__)) + candidates = [] + if os.path.isabs(config_file_name) and os.path.isfile(config_file_name): + candidates.append(config_file_name) + else: + try: + from app_paths import get_config_dir + candidates.append(str(get_config_dir() / config_file_name)) + except ImportError: + pass + candidates.append(os.path.join(script_dir, config_file_name)) + for path in candidates: + if not path or not os.path.exists(path): + continue + try: + with open(path, "rb") as f: + raw = f.read() + text = raw.decode("utf-8-sig").strip() + if not text: + continue + return json.loads(text) + except (json.JSONDecodeError, Exception): + continue + print(f"Warning: Could not load config from {config_file_name}.") + return {} + + +def load_config(config_path: Optional[str] = None) -> Dict: + """Load configuration. Delegates to load_config_by_name.""" + return load_config_by_name(config_path) + + +def _resolve_must_gather_docs_dir() -> str: + """Resolve the must-gather docs directory from config.json, app_paths, or fallback.""" + _script_dir = os.path.dirname(os.path.abspath(__file__)) + _project_root = os.path.dirname(_script_dir) + + try: + from app_paths import get_config_path + cfg_path = str(get_config_path()) + except ImportError: + cfg_path = os.path.join(_project_root, "Config", "config.json") + if os.path.isfile(cfg_path): + try: + with open(cfg_path, "r", encoding="utf-8") as f: + cfg = json.load(f) + configured = cfg.get("must_gather_docs_dir", "") + if configured: + if not os.path.isabs(configured): + configured = os.path.join(_project_root, configured) + if os.path.isdir(configured): + return configured + except Exception: + pass + + try: + from app_paths import get_must_gather_docs_dir as _get_docs_dir + docs_dir = str(_get_docs_dir()) + if os.path.isdir(docs_dir): + return docs_dir + except ImportError: + pass + + return os.path.join(_project_root, "DataSource") + + +MUST_GATHER_DOCS_DIR_DEFAULT = _resolve_must_gather_docs_dir() + + +def load_must_gather_documentation(must_gather_docs_dir: str) -> Dict[str, str]: + """Load all must-gather structure documentation files.""" + docs = {} + doc_files = [ + 'MUST_GATHER_STRUCTURE.md', + 'MUST_GATHER_INDEX.md', + 'MUST_GATHER_ROUTING_GUIDE.md', + 'MUST_GATHER_DOCUMENTATION_README.md', + ] + docs_path = Path(must_gather_docs_dir) + if not docs_path.exists(): + raise FileNotFoundError(f"Must-gather documentation directory not found: {must_gather_docs_dir}") + for doc_file in doc_files: + doc_path = docs_path / doc_file + if doc_path.exists(): + try: + with open(doc_path, 'r', encoding='utf-8') as f: + docs[doc_file] = f.read() + except Exception as e: + print(f"Warning: Could not read {doc_file}: {e}") + else: + print(f"Warning: Documentation file not found: {doc_file}") + return docs + + +class MustGatherFileSelector: + """Builds must-gather file-selection prompts (no LLM calls).""" + + def _create_file_selection_prompt(self, problem_statement: str, docs: Dict[str, str]) -> str: + docs_context = "" + for doc_name, doc_content in docs.items(): + docs_context += f"\n\n=== {doc_name} ===\n{doc_content}\n" + return f"""You are an expert OpenShift/Kubernetes system analyst. Analyze the user's problem statement and suggest which files from a must-gather collection need to be analyzed. + +MUST-GATHER DOCUMENTATION: +{docs_context} + +USER PROBLEM STATEMENT: +{problem_statement} + +TASK: Suggest specific file paths from the must-gather collection. + +OUTPUT FORMAT — CRITICAL: Problem category line, then numbered list with [high]/[medium]/[low] priority tags. + +Problem category: +1. [high] path/to/file — reason +2. [medium] path/to/file — reason +3. [low] path/to/file — reason + +Rules: +- First line must be exactly "Problem category: " +- Each list line: number, period, space, [high] or [medium] or [low], space, path, then " — " and a short reason +- Always include both current.log and previous.log for pod logs +- Base suggestions on the must-gather structure documentation above +- Output NOTHING else. No JSON. No markdown.""" + + + +def prepare_file_selection_prompt(problem_statement: str, docs_dir: str, job_dir: str) -> Dict: + """Pure-Python alternative to MustGatherFileSelector.suggest_files(). + + Loads must-gather documentation, builds the file-selection prompt string that + the LLM would normally receive, and writes it to /file_selection_prompt.md. + Returns a manifest dict with the prompt path so the orchestrating agent (Claude) + can read the file and perform file-selection reasoning inline — no HTTP/LLM call made here. + + Args: + problem_statement: The user's problem description. + docs_dir: Path to the must-gather documentation directory. + job_dir: Path to the shared IntelliAide job directory. + + Returns: + {"prompt_path": "/file_selection_prompt.md", + "docs_dir": docs_dir, + "has_docs": True/False} + """ + try: + docs = load_must_gather_documentation(docs_dir) + except FileNotFoundError: + docs = {} + + has_docs = bool(docs) + + if has_docs: + selector = MustGatherFileSelector.__new__(MustGatherFileSelector) + prompt = selector._create_file_selection_prompt(problem_statement.strip(), docs) + else: + # No docs available — fall back to a minimal prompt that asks Claude to + # suggest standard must-gather paths for the problem. + prompt = ( + "You are an expert OpenShift/Kubernetes system analyst.\n" + "No must-gather structure documentation is available.\n\n" + f"USER PROBLEM STATEMENT:\n{problem_statement.strip()}\n\n" + "Suggest the most likely must-gather file paths for this problem using your knowledge " + "of standard OpenShift must-gather layout.\n\n" + "OUTPUT FORMAT:\nProblem category: \n" + "1. [high] path/to/file — reason\n" + "2. [medium] path/to/file — reason\n" + "3. [low] path/to/file — reason" + ) + + prompt_path = Path(job_dir) / "file_selection_prompt.md" + prompt_path.write_text(prompt, encoding="utf-8") + + return { + "prompt_path": str(prompt_path), + "docs_dir": docs_dir, + "has_docs": has_docs, + } + + diff --git a/intelliaide/Main-program/root_cause_analyzer_yaml.py b/intelliaide/Main-program/root_cause_analyzer_yaml.py new file mode 100644 index 0000000..d856a82 --- /dev/null +++ b/intelliaide/Main-program/root_cause_analyzer_yaml.py @@ -0,0 +1,377 @@ +""" +Root Cause Analyzer (YAML only) Sub-Agent + +This module performs root cause analysis on YAML data extracted by the Data Analyzer. +""" + +import json +from typing import Dict, List, Optional, Any + + +class RootCauseAnalyzerYAML: + """ + Analyzes YAML data to identify root causes of issues. + """ + + def __init__(self): + """Initialize Root Cause Analyzer""" + pass + + def analyze(self, data_analyzer_results: Dict) -> Dict: + """ + Perform root cause analysis on YAML data. + + Parameters: + ----------- + data_analyzer_results : dict + Results from Data Analyzer containing analyzed YAML files + + Returns: + -------- + dict : Root cause analysis results containing: + - root_causes: List of identified root causes + - contributing_factors: List of contributing factors + - evidence: Evidence supporting the analysis + - confidence: Confidence level of the analysis + - recommendations: Recommended actions + """ + print("\n[Root Cause Analyzer] Starting YAML root cause analysis...") + + analysis = { + 'root_causes': [], + 'contributing_factors': [], + 'evidence': [], + 'confidence': 'medium', + 'recommendations': [], + 'analysis_summary': '' + } + + # Extract YAML data + yaml_files_data = {} + for file_path, file_data in data_analyzer_results.get('data', {}).items(): + if file_data.get('file_type') == 'yaml': + yaml_files_data[file_path] = file_data + + if not yaml_files_data: + analysis['root_causes'].append({ + 'type': 'insufficient_data', + 'description': 'No YAML files were successfully analyzed', + 'severity': 'high' + }) + analysis['confidence'] = 'low' + return analysis + + print(f"[Root Cause Analyzer] Analyzing {len(yaml_files_data)} YAML files...") + + # Analyze each YAML file + for file_path, file_data in yaml_files_data.items(): + file_analysis = self._analyze_yaml_file(file_path, file_data) + + # Merge findings + if file_analysis.get('root_causes'): + analysis['root_causes'].extend(file_analysis['root_causes']) + if file_analysis.get('contributing_factors'): + analysis['contributing_factors'].extend(file_analysis['contributing_factors']) + if file_analysis.get('evidence'): + analysis['evidence'].extend(file_analysis['evidence']) + if file_analysis.get('recommendations'): + analysis['recommendations'].extend(file_analysis['recommendations']) + + # Deduplicate and prioritize + analysis = self._prioritize_findings(analysis) + + # Generate summary + analysis['analysis_summary'] = self._generate_summary(analysis) + + print(f"[Root Cause Analyzer] Analysis complete:") + print(f" - Root causes identified: {len(analysis['root_causes'])}") + print(f" - Contributing factors: {len(analysis['contributing_factors'])}") + print(f" - Evidence items: {len(analysis['evidence'])}") + + return analysis + + def _analyze_yaml_file(self, file_path: str, file_data: Dict) -> Dict: + """Analyze a single YAML file for root causes""" + findings = { + 'root_causes': [], + 'contributing_factors': [], + 'evidence': [], + 'recommendations': [] + } + + raw_data = file_data.get('raw_data', {}) + metadata = file_data.get('metadata', {}) + kind = metadata.get('kind', '') + + # Analyze based on Kubernetes resource type + if kind == 'Pod': + pod_analysis = self._analyze_pod(raw_data, file_path) + findings.update(pod_analysis) + elif kind == 'Event': + event_analysis = self._analyze_event(raw_data, file_path) + findings.update(event_analysis) + elif kind in ['Service', 'ConfigMap', 'Secret']: + config_analysis = self._analyze_config_resource(raw_data, file_path, kind) + findings.update(config_analysis) + else: + # Generic analysis + generic_analysis = self._analyze_generic(raw_data, file_path) + findings.update(generic_analysis) + + return findings + + def _analyze_pod(self, pod_data: Dict, file_path: str) -> Dict: + """Analyze Pod YAML for issues""" + findings = { + 'root_causes': [], + 'contributing_factors': [], + 'evidence': [], + 'recommendations': [] + } + + # Check pod status + status = pod_data.get('status', {}) + phase = status.get('phase', '') + + if phase not in ['Running', 'Succeeded']: + findings['root_causes'].append({ + 'type': 'pod_state', + 'description': f'Pod is in {phase} state', + 'severity': 'high', + 'file': file_path + }) + findings['evidence'].append({ + 'type': 'pod_phase', + 'value': phase, + 'file': file_path + }) + + # Check container statuses + container_statuses = status.get('containerStatuses', []) + for container_status in container_statuses: + state = container_status.get('state', {}) + + # Check for waiting state + if 'waiting' in state: + reason = state['waiting'].get('reason', '') + message = state['waiting'].get('message', '') + findings['root_causes'].append({ + 'type': 'container_waiting', + 'description': f'Container {container_status.get("name")} is waiting: {reason}', + 'details': message, + 'severity': 'high', + 'file': file_path + }) + + # Check for terminated state + if 'terminated' in state: + reason = state['terminated'].get('reason', '') + exit_code = state['terminated'].get('exitCode', 0) + if exit_code != 0: + findings['root_causes'].append({ + 'type': 'container_terminated', + 'description': f'Container {container_status.get("name")} terminated with exit code {exit_code}', + 'reason': reason, + 'severity': 'high', + 'file': file_path + }) + + # Check conditions + conditions = status.get('conditions', []) + for condition in conditions: + if condition.get('type') == 'Ready' and condition.get('status') != 'True': + findings['root_causes'].append({ + 'type': 'pod_not_ready', + 'description': 'Pod is not in Ready state', + 'reason': condition.get('reason', ''), + 'message': condition.get('message', ''), + 'severity': 'high', + 'file': file_path + }) + + # Check resource requests/limits + spec = pod_data.get('spec', {}) + containers = spec.get('containers', []) + for container in containers: + resources = container.get('resources', {}) + if not resources.get('requests') and not resources.get('limits'): + findings['contributing_factors'].append({ + 'type': 'missing_resource_limits', + 'description': f'Container {container.get("name")} has no resource requests/limits', + 'severity': 'medium', + 'file': file_path + }) + + return findings + + def _analyze_event(self, event_data: Dict, file_path: str) -> Dict: + """Analyze Event YAML for issues""" + findings = { + 'root_causes': [], + 'contributing_factors': [], + 'evidence': [], + 'recommendations': [] + } + + # Events are typically lists + if isinstance(event_data, list): + events = event_data + elif isinstance(event_data, dict) and 'items' in event_data: + events = event_data['items'] + else: + events = [event_data] + + # Analyze events + error_events = [] + warning_events = [] + + for event in events: + if isinstance(event, dict): + event_type = event.get('type', '') + reason = event.get('reason', '') + message = event.get('message', '') + + if event_type == 'Warning': + warning_events.append({ + 'reason': reason, + 'message': message, + 'count': event.get('count', 1) + }) + elif 'Error' in reason or 'Failed' in reason: + error_events.append({ + 'reason': reason, + 'message': message, + 'count': event.get('count', 1) + }) + + # Add findings + if error_events: + for error in error_events[:5]: # Top 5 errors + findings['root_causes'].append({ + 'type': 'event_error', + 'description': f'Error event: {error["reason"]}', + 'message': error['message'], + 'count': error['count'], + 'severity': 'high', + 'file': file_path + }) + + if warning_events: + for warning in warning_events[:5]: # Top 5 warnings + findings['contributing_factors'].append({ + 'type': 'event_warning', + 'description': f'Warning event: {warning["reason"]}', + 'message': warning['message'], + 'count': warning['count'], + 'severity': 'medium', + 'file': file_path + }) + + return findings + + def _analyze_config_resource(self, resource_data: Dict, file_path: str, kind: str) -> Dict: + """Analyze ConfigMap, Secret, or Service YAML""" + findings = { + 'root_causes': [], + 'contributing_factors': [], + 'evidence': [], + 'recommendations': [] + } + + # Check if resource exists + if not resource_data: + findings['contributing_factors'].append({ + 'type': 'missing_config', + 'description': f'{kind} resource is missing or empty', + 'severity': 'medium', + 'file': file_path + }) + + return findings + + def _analyze_generic(self, data: Dict, file_path: str) -> Dict: + """Generic analysis for unknown YAML types""" + findings = { + 'root_causes': [], + 'contributing_factors': [], + 'evidence': [], + 'recommendations': [] + } + + # Look for common error indicators + if isinstance(data, dict): + # Check for status fields + if 'status' in data: + status = data['status'] + if isinstance(status, dict): + # Look for error conditions + for key, value in status.items(): + if 'error' in key.lower() or 'fail' in key.lower(): + findings['evidence'].append({ + 'type': 'status_indicator', + 'key': key, + 'value': str(value), + 'file': file_path + }) + + return findings + + def _prioritize_findings(self, analysis: Dict) -> Dict: + """Deduplicate and prioritize findings""" + # Deduplicate root causes + seen_rc = {} + unique_rc = [] + for rc in analysis['root_causes']: + key = f"{rc.get('type')}_{rc.get('description', '')}" + if key not in seen_rc: + seen_rc[key] = True + unique_rc.append(rc) + + analysis['root_causes'] = unique_rc + + # Sort by severity + severity_order = {'high': 0, 'medium': 1, 'low': 2} + analysis['root_causes'].sort(key=lambda x: severity_order.get(x.get('severity', 'low'), 2)) + analysis['contributing_factors'].sort(key=lambda x: severity_order.get(x.get('severity', 'low'), 2)) + + return analysis + + def _generate_summary(self, analysis: Dict) -> str: + """Generate human-readable summary""" + summary_parts = [] + + if analysis['root_causes']: + summary_parts.append(f"Identified {len(analysis['root_causes'])} root cause(s):") + for i, rc in enumerate(analysis['root_causes'][:3], 1): # Top 3 + summary_parts.append(f" {i}. {rc.get('description', 'Unknown')}") + + if analysis['contributing_factors']: + summary_parts.append(f"\nFound {len(analysis['contributing_factors'])} contributing factor(s)") + + if analysis['recommendations']: + summary_parts.append(f"\nGenerated {len(analysis['recommendations'])} recommendation(s)") + + return '\n'.join(summary_parts) if summary_parts else "No significant findings identified." + + +def main(): + """Example usage""" + analyzer = RootCauseAnalyzerYAML() + + # Example data analyzer results + example_data = { + 'data': { + '/path/to/pod.yaml': { + 'file_type': 'yaml', + 'raw_data': {'kind': 'Pod', 'status': {'phase': 'Failed'}}, + 'metadata': {'kind': 'Pod'} + } + } + } + + result = analyzer.analyze(example_data) + print(json.dumps(result, indent=2, default=str)) + + +if __name__ == "__main__": + main() diff --git a/intelliaide/OWNERS b/intelliaide/OWNERS new file mode 100644 index 0000000..9398dc9 --- /dev/null +++ b/intelliaide/OWNERS @@ -0,0 +1,11 @@ +# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md + +approvers: + - Prashanth684 + - dattaramba +reviewers: + - Prashanth684 + - dattaramba + + +component: "IntelliAide RCA Lightspeed Skills" diff --git a/intelliaide/README.md b/intelliaide/README.md new file mode 100644 index 0000000..48a849f --- /dev/null +++ b/intelliaide/README.md @@ -0,0 +1,44 @@ +# IntelliAide RCA Skills + +IntelliAide is a deep troubleshooting and root-cause analysis pipeline for +OpenShift clusters. It performs a 3-pass analysis (High → Medium → Low +priority) over a must-gather archive and produces a structured diagnosis with +prioritised findings and remediation recommendations. + +## How it works + +1. **extract_cluster** — validates and unpacks the must-gather archive + mounted into the sandbox pod from a PVC. +2. **select_files** — uses ML classification and LLM-guided file selection to + identify the most relevant logs, events, and resource manifests. +3. **analyze_data** — chunks the selected files within the LLM context budget + and prepares structured analysis prompts. +4. **perform_rca** — the orchestrating Claude session synthesises findings + across all priority passes and maps results into the standard + `diagnosis`/`proposal` fields for console rendering. + +## Dependencies + +Python dependencies are pre-installed in `vendor/` (Python 3.12, x86-64) so +that no `pip install` is required at image build time or at runtime inside the +restricted sandbox environment. + +To regenerate `vendor/` for a new Python version, run: + +```bash +mkdir -p intelliaide/vendor/ +podman run --rm --user root \ + -v $(pwd)/intelliaide:/intelliaide:Z \ + registry.redhat.io/rhel9/python-312:latest \ + pip3.12 install --no-cache-dir --target /intelliaide/vendor/ \ + -r /intelliaide/requirements.txt +``` + +## Triggering + +This skill is invoked automatically when the Proposal request contains +keywords such as `root cause analysis`, `RCA`, `deep analysis`, `deep +troubleshooting`, `must-gather`, or `IntelliAide`. + +See [`../examples/setup/09-intelliaide-proposals.yaml`][proposals] for ready-to-use +Proposal templates. diff --git a/intelliaide/SKILL.md b/intelliaide/SKILL.md new file mode 100644 index 0000000..3d25dea --- /dev/null +++ b/intelliaide/SKILL.md @@ -0,0 +1,407 @@ +--- +name: intelliaide +description: Deep troubleshooting and root cause analysis for OpenShift clusters using must-gather archives. Use when the user wants a thorough investigation, deep analysis, or RCA of any cluster issue and has a must-gather available. +--- + +# IntelliAide RCA Skills + +You are the orchestrator of the IntelliAide root-cause analysis pipeline for OpenShift clusters. +You call each IntelliAide component as a separate skill and decide what to run next based on +what each step reports. + +**Important:** All LLM reasoning in this pipeline is performed by YOU (the orchestrating agent). +The Python skill scripts handle only pure computation: token estimation, chunking, prompt +formatting, and file I/O. No script makes an independent LLM call. + +--- + +## When to use IntelliAide + +Use IntelliAide whenever the user wants any of the following: + +- Deep troubleshooting of a cluster issue using a must-gather archive +- Root cause analysis (RCA) or structured diagnosis of any cluster problem +- Thorough investigation where a quick inspection is not sufficient +- A detailed remediation report with prioritised findings and recommendations + +--- + +## Progress Reporting to the Console + +At the **start of the pipeline** (before Step 1), derive the Proposal name and namespace from +the sandbox pod hostname, then annotate the Proposal so progress is visible in the console and +in `oc describe proposal`: + +```bash +_PROPOSAL=$(hostname | sed 's/^ls-[^-]*-analysis-//;s/^ls-analysis-//') +_NS="openshift-lightspeed" +``` + +After **each step** completes, update the progress annotation: + +```bash +oc annotate proposal "$_PROPOSAL" -n "$_NS" \ + intelliaide.io/progress="" --overwrite 2>/dev/null || true +``` + +Use these messages at each step: + +- Before Step 1: `"IntelliAide: validating must-gather data at /data/input (PVC)"` +- After Step 1: `"IntelliAide: must-gather ready — preparing file selection"` +- After Step 2a: `"IntelliAide: file selection prompt ready — selecting files"` +- After Step 2b: `"IntelliAide: file selection complete (high=N, medium=N, low=N) — running ML analysis"` +- After Step 3: `"IntelliAide: ML analysis complete — preparing RCA chunks (high priority)"` +- After Step 4a: `"IntelliAide: RCA chunks ready (N chunks) — analyzing"` +- After Step 4b: `"IntelliAide: chunk analysis complete — batching for synthesis"` +- After Step 5: `"IntelliAide: high-priority RCA complete"` +- After medium/low passes (if run): `"IntelliAide: medium-priority RCA complete"` / `"IntelliAide: low-priority RCA complete"` +- At the very end: `"IntelliAide: pipeline complete — mapping to output schema"` + +If `oc annotate` fails, ignore it and continue — it is non-critical. + +--- + +## Step-by-Step Orchestration + +Work through the steps below **in order**. After each command, parse the JSON line printed to +stdout and decide whether to continue. + +### Step 1 — Validate the pre-populated must-gather PVC + +``` +python /app/skills/intelliaide/extract_cluster.py --query "" +``` + +The script immediately validates the must-gather data mounted from the PVC at `/data/input`. +The PVC must be pre-populated with diagnostic data before the Proposal is created — the operator +mounts it read-only into the sandbox pod automatically. No manual `oc cp` is required. + +Capture `job_dir` and `cluster_dir` from the output JSON. If `success=false`, **stop +immediately** and return the following error JSON — do not proceed to Step 2: + +```json +{"success": false, "options": [], "error": ""} +``` + +> **CRITICAL — buffer safety rule (applies to ALL steps):** +> Never `cat`, `ls -R`, `find`, `grep -r`, or otherwise read must-gather bundle files +> directly after this step. Must-gather bundles are gigabytes of raw data. Reading any +> part of the bundle inline overflows the SDK's 1 MB message buffer and crashes the session. +> All file access is performed exclusively through the Python skill scripts in Steps 2–5, +> which chunk the data safely. The only `cat` calls permitted are: +> +> - `cat ` in Step 2b (the generated file-selection prompt — ≤ 512 KB) +> - `cat ` in Step 4b (individual chunk files — bounded by perform_rca.py) +> - `cat ` in Step 5b (bounded summaries) +> - `cat /rca_.json` in Step 6 (final report only) +> +> Do **not** deviate from this list. + +--- + +### Step 2 — Select relevant files + +#### Step 2a — Prepare the file-selection prompt (Python, no LLM) + +``` +python /app/skills/intelliaide/select_files.py --job-dir +``` + +Output JSON: + +```json +{"prompt_path": "/file_selection_prompt.md", "cluster_dir": "...", "docs_dir": "...", "has_docs": true} +``` + +#### Step 2b — Perform file selection (YOU reason inline) + +``` +cat +``` + +Read this **generated prompt file** (it contains a file-tree index, not raw must-gather data — +it is safe to cat). Then select the most relevant must-gather files for the problem, grouped +by priority (high / medium / low). Do **not** cat any of the listed must-gather files themselves. + +Write `/file_selection.json` with the following schema: + +```json +{ + "query": "", + "cluster_dir": "", + "problem_category": "", + "high": [{"original": "path/to/file", "resolved": "path/to/file", "found": true, "reason": "why high priority"}], + "medium": [...], + "low": [...] +} +``` + +Rules: + +- Include both `current.log` and `previous.log` for relevant pod log directories. +- Set `"resolved"` equal to `"original"` (path verification happens downstream). +- Set `"found": true` for all entries (downstream analyze_data.py checks actual existence). +- Aim for 5–20 high-priority files, fewer medium/low. + +--- + +### Step 3 — Analyze data with ML (Python, no LLM) + +Run for each priority tier that has files (high first, then medium/low only if needed): + +``` +python /app/skills/intelliaide/analyze_data.py --job-dir --priority high +``` + +Capture `priority`, `yaml_files`, `log_files` from output. + +--- + +### Step 4 — Map phase: prepare and analyze RCA chunks + +#### Step 4a — Prepare chunk prompt files (Python, no LLM) + +``` +python /app/skills/intelliaide/perform_rca.py --job-dir --priority high +``` + +Output JSON: + +```json +{"mode": "chunks", "priority": "high", "chunk_count": N, "manifest_path": "...", "has_medium": true, "has_low": false} +``` + +Capture `chunk_count`, `manifest_path`, `has_medium`, `has_low`. + +If `chunk_count == 0`: stop and return an error JSON — IntelliAide found no data to analyze +and cannot proceed without must-gather content: + +```json +{"success": false, "options": [], "error": "No data chunks found for priority — must-gather content may be empty or unreadable"} +``` + +#### Step 4b — Analyze each chunk (YOU reason inline) + +For each entry in `manifest["chunk_files"]`: + +``` +cat +``` + +After reading the chunk, write a concise root-cause summary (≤600 words) for this chunk to: + +``` +/chunk_summary_high_.md +``` + +where `` is the 1-based index of the chunk. Focus on: + +- Specific error patterns and their likely cause +- Key files or components implicated +- Any timestamps or chronology relevant to the problem + +Keep summaries concise — they are fed into the reduce phase. + +--- + +### Step 5 — Reduce phase: batch and synthesize (iterative until is_final) + +#### Step 5a — Prepare reduce batches (Python, no LLM) + +After ALL chunk summaries have been written, call: + +``` +python /app/skills/intelliaide/perform_rca.py \ + --job-dir \ + --priority high \ + --mode reduce \ + --level 1 \ + --summary-files /chunk_summary_high_1.md \ + /chunk_summary_high_2.md \ + ... (all chunk summary files) +``` + +Output JSON: + +```json +{"mode": "reduce", "priority": "high", "level": 1, "batch_count": N, "is_final": true/false, "manifest_path": "..."} +``` + +Step 5b — Synthesize each batch (YOU reason inline) + +For each batch in `manifest["batches"]`: + +``` +cat +``` + +Read all summaries in the batch, then write a synthesized analysis to `batch["output_file"]`. + +If `is_final=true` (only one batch): this synthesized output is the **final RCA text**. + → Skip to Step 6. + +If `is_final=false` (multiple batches): after writing all batch outputs, call Step 5a again at +the next level: + +``` +python /app/skills/intelliaide/perform_rca.py \ + --job-dir \ + --priority high \ + --mode reduce \ + --level \ + --summary-files +``` + +Repeat until `is_final=true`. + +--- + +### Step 6 — Write final RCA output + +When Step 5 is complete with `is_final=true`, the last `batch["output_file"]` contains the +final RCA text. + +``` +cat +``` + +Write `/rca_high.json`: + +```json +{ + "priority": "high", + "rca_text": "", + "cost_usd": 0, + "input_tokens": 0, + "output_tokens": 0, + "has_medium": true, + "has_low": false, + "error": null +} +``` + +Set `cost_usd`, `input_tokens`, `output_tokens` to 0 — Claude's usage is tracked at the +operator session level, not inside IntelliAide. + +--- + +### Steps 7–8 (optional) — Medium and Low priority passes + +If `has_medium=true` after the high-priority pass, repeat Steps 3–6 with `--priority medium`. +When calling analyze_data.py for medium, prefix your chunk summaries with the high-priority +RCA text as context so the analysis builds on prior findings. + +If `has_low=true` after the medium pass, repeat with `--priority low`. + +--- + +### Step 9 — Map final RCA to output schema + +Read the final `rca_.json` file: + +``` +cat /rca_.json +``` + +The `rca_text` field contains the full markdown RCA report with headings: +`## Root Cause`, `## Key Findings`, `## Recommendations`, `## Chronology`. + +Parse those sections and map them to the output schema as described in the +**Output Schema Mapping** section below. + +--- + +## Error Handling + +| Situation | What to do | +| ---------------------------------------------------------- | --------------------------------------------------------------------------- | +| `extract_cluster.py` exits non-zero | **Stop — return error JSON with the error message. Do not proceed.** | +| `select_files.py` returns no `prompt_path` | **Stop — return error JSON: file selection prompt is missing** | +| `file_selection.json` has all-zero counts | **Stop — return error JSON: no files were selected for analysis** | +| `analyze_data.py` exits non-zero | **Stop — return error JSON for this priority tier** | +| `perform_rca.py --mode chunks` returns `chunk_count=0` | **Stop — return error JSON: no data chunks to analyze** | +| Any step times out | **Stop — return error JSON noting which step timed out** | + +Never fall back to `kubectl`/`oc` commands — the IntelliAide pipeline requires must-gather data. +If any step fails, return a structured error JSON immediately: + +```json +{"success": false, "options": [], "error": ""} +``` + +--- + +## Output Schema Mapping + +After all steps complete, produce one option per major root cause identified in the final +`rca_text`. Map the RCA sections to the **standard** output fields as follows: + +| RCA section heading (in `rca_text`) | Standard output field | +| --------------------------------------------------------- | ------------------------------- | +| `## Root Cause` summary paragraph + `## Key Findings` | `diagnosis.summary` | +| `## Root Cause` / specific underlying cause | `diagnosis.rootCause` | +| Evidence quality (High/Medium/Low) | `diagnosis.confidence` | +| `## Recommendations` section (full markdown) | `proposal.description` | +| Individual remediation steps | `proposal.actions[]` | +| Risk level of remediation | `proposal.risk` | +| Whether the fix is reversible | `proposal.reversible` | +| `## Chronology` section (if present) | append to `diagnosis.summary` | + +For each option use this structure: + +```json +{ + "title": "", + "diagnosis": { + "summary": "", + "rootCause": "", + "confidence": "High | Medium | Low" + }, + "proposal": { + "description": "", + "actions": [ + {"type": "command | config | patch", "description": ""} + ], + "risk": "Low | Medium | High | Critical", + "reversible": "Reversible | Irreversible | Partial" + } +} +``` + +**Do not** produce an `rcaSummary` top-level key. All RCA content goes into +`diagnosis` and `proposal` inside each option. These fields are rendered +with markdown formatting in the console UI. + +--- + +## Final Response Format + +After completing Step 9, your **entire response** MUST be raw JSON in exactly +this shape — the operator's HTTP client requires it: + +```json +{ + "success": true, + "options": [ + { + "title": "...", + "diagnosis": { "summary": "...", "rootCause": "...", "confidence": "High | Medium | Low" }, + "proposal": { + "description": "...", + "actions": [{"type": "...", "description": "..."}], + "risk": "Low | Medium | High | Critical", + "reversible": "Reversible | Irreversible | Partial" + } + } + ] +} +``` + +Rules: + +- `success` **MUST** be `true`. +- `options` **MUST** contain at least one entry. If IntelliAide found no + anomalies, produce one option titled `"No Issues Found"` with a diagnosis + summarising what was collected and confirmed healthy, and a proposal with + `risk: "Low"` and `reversible: "Reversible"`. +- Output **raw JSON only** — no markdown fences, no prose before or after. diff --git a/intelliaide/analyze_data.py b/intelliaide/analyze_data.py new file mode 100644 index 0000000..f3902ef --- /dev/null +++ b/intelliaide/analyze_data.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +""" +analyze_data.py — Step 3 (per-priority pass) of IntelliAide pure-skills pipeline. + +Runs ML-based YAML classification and log template mining on the +extracted cluster files for one priority tier, then serializes both the ML +classification result and the log error entries to a JSON file so that +perform_rca.py can read them without accessing the Results/ directory. + +Usage: + python /app/skills/intelliaide/analyze_data.py --job-dir /tmp/intelliaide/ --priority high + +Reads: + /state.json (cluster_dir) + /file_selection.json (list of files for this priority) + +Writes: + /analysis_.json + +Output (stdout JSON): + { + "priority": "high", + "yaml_files": N, + "log_files": N, + "log_entries": N, + "analysis_path": "/analysis_high.json" + } +""" + +import argparse +import json +import os +import sys +from pathlib import Path + +# All IntelliAide engine code lives alongside this script in the same folder. +# At runtime (in the sandbox container) this resolves to /app/skills/intelliaide/ +_SKILL_DIR = Path(__file__).resolve().parent +for _p in ( + str(_SKILL_DIR / "vendor"), # vendored packages (drain3, scikit-learn, etc.) + str(_SKILL_DIR / "Main-program"), + str(_SKILL_DIR / "python-client"), + str(_SKILL_DIR), +): + if _p not in sys.path: + sys.path.insert(0, _p) + +from data_analyzer import DataAnalyzer # noqa: E402 +import data_analyzer as _da # noqa: E402 +import app_paths as _ap # noqa: E402 + +# The skills image volume is mounted read-only by Kubernetes. +# Redirect all IntelliAide write paths to /tmp which is always writable. +_WRITABLE_APP = Path("/tmp/intelliaide-app") +_WRITABLE_APP.mkdir(parents=True, exist_ok=True) +(Path("/tmp/intelliaide-app/Results/log_classifications")).mkdir(parents=True, exist_ok=True) + +# data_analyzer.py binds get_application_dir at import time (from app_paths import ...). +# Patch its local reference so output goes to /tmp instead of the read-only image mount. +_da.get_application_dir = lambda: _WRITABLE_APP + +# llm_rca_agent.py and data_analyzer.py use lazy `from app_paths import get_results_dir`. +# Patch the app_paths module so those lazy imports resolve to a writable path. +_ap.get_results_dir = lambda: _WRITABLE_APP / "Results" + + +def _log_pod(msg: str) -> None: + """Write a progress line directly to the container log stream (PID 1 stdout).""" + line = f"[intelliaide] {msg}\n" + try: + with open("/proc/1/fd/1", "a") as fh: + fh.write(line) + except Exception: + sys.stderr.write(line) + + +_ERROR_LEVELS = {"RareError", "HighFreqError", "Error"} + + +def _build_log_entries(log_processing_result: dict) -> list: + """Read error-level log content from per-file saved paths. + + Mirrors the logic in tools.py:execute_analyze_logs so that the resulting + list can be passed directly to run_rca_chunked / run_rca_and_summary_continued. + """ + entries = [] + for pf in log_processing_result.get("per_file", []): + source_path = pf.get("file", "") + try: + original_size = os.path.getsize(source_path) if source_path and os.path.exists(source_path) else 0 + except Exception: + original_size = 0 + + for level_name, path in (pf.get("saved") or {}).items(): + if level_name not in _ERROR_LEVELS or not path: + continue + try: + with open(path, "r", encoding="utf-8", errors="ignore") as fh: + content = fh.read() + entries.append({ + "file": os.path.basename(path), + "content": content, + "original_size": original_size, + }) + except Exception: + pass + return entries + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--job-dir", required=True) + parser.add_argument("--priority", required=True, choices=["high", "medium", "low"]) + args = parser.parse_args() + + job_dir = Path(args.job_dir) + state = json.loads((job_dir / "state.json").read_text()) + cluster_dir = state["cluster_dir"] + + file_selection = json.loads((job_dir / "file_selection.json").read_text()) + entries = file_selection.get(args.priority, []) + + # Use resolved paths where the extractor actually found the file + file_paths = [ + e["resolved"] if e.get("found") else e["original"] + for e in entries + ] + + analysis_path = str(job_dir / f"analysis_{args.priority}.json") + + # --- No-op for empty priority tier --- + if not file_paths: + print(f"[analyze_data] No {args.priority}-priority files — skipping analysis.", file=sys.stderr) + empty = { + "priority": args.priority, + "yaml_errors": {}, + "log_entries": [], + "yaml_files": 0, + "log_files": 0, + "failed_files": [], + } + Path(analysis_path).write_text(json.dumps(empty, indent=2)) + print(json.dumps({ + "priority": args.priority, + "yaml_files": 0, + "log_files": 0, + "log_entries": 0, + "analysis_path": analysis_path, + })) + return + + _log_pod(f"Step 3/4 — analyze_data priority={args.priority} files={len(file_paths)}") + print( + f"[analyze_data] Analyzing {len(file_paths)} {args.priority}-priority files " + f"under {cluster_dir}", + file=sys.stderr, + ) + + analyzer = DataAnalyzer(must_gather_base_dir=cluster_dir) + result = analyzer.analyze_files( + file_paths, + file_types=["yaml", "log", "json"], + suggested_files_with_priority=entries, + ) + + yaml_errors = result.get("ml_classification_result", {}) + log_processing_result = result.get("log_processing_result", {}) + log_entries = _build_log_entries(log_processing_result) + + yaml_count = len(result.get("yaml_files_processed", [])) + log_count = len(result.get("log_files_for_analysis", [])) + + print( + f"[analyze_data] YAML={yaml_count} log={log_count} " + f"log_entries={len(log_entries)} failed={len(result.get('failed_files', []))}", + file=sys.stderr, + ) + + analysis = { + "priority": args.priority, + "yaml_errors": yaml_errors, + "log_entries": log_entries, + "yaml_files": yaml_count, + "log_files": log_count, + "failed_files": result.get("failed_files", []), + } + Path(analysis_path).write_text(json.dumps(analysis, indent=2, default=str)) + + _log_pod( + f"Step 3/4 — analyze_data done priority={args.priority} " + f"yaml={yaml_count} logs={log_count} log_entries={len(log_entries)}" + ) + print(json.dumps({ + "priority": args.priority, + "yaml_files": yaml_count, + "log_files": log_count, + "log_entries": len(log_entries), + "analysis_path": analysis_path, + })) + + +if __name__ == "__main__": + main() diff --git a/intelliaide/app_paths.py b/intelliaide/app_paths.py new file mode 100644 index 0000000..e4e0539 --- /dev/null +++ b/intelliaide/app_paths.py @@ -0,0 +1,102 @@ +""" +Application paths for normal run vs PyInstaller frozen exe. +Layout: DataSource/, Config/, Machine-learning/, Main-program/, Chatbot/, Results/ +- When frozen: APPLICATION_DIR = folder containing the .exe. +- Config: config.json, json_config.json, agent_memory.json +- DataSource: keyfields_yaml_ml_input.docx, keyfields_json_ml_input.odt, MUST_GATHER_*.md +- Results: errors_aggregate.json, rca_summary.txt, ML output files + +When running inside the agentic-operator skills image, this file lives at: + /intelliaide/app_paths.py → container path /app/skills/intelliaide/app_paths.py + +So Path(__file__).resolve().parent == /app/skills/intelliaide/ +And Config/ == /app/skills/intelliaide/Config/ (read-only image mount) + DataSource/ == /app/skills/intelliaide/DataSource/ (read-only image mount) + Results/ is redirected to /tmp by the skill scripts at startup. +""" + +import sys +from pathlib import Path + +def _is_frozen() -> bool: + return getattr(sys, "frozen", False) + +def get_application_dir() -> Path: + """Project root (or exe dir when frozen).""" + if _is_frozen(): + return Path(sys.executable).resolve().parent + return Path(__file__).resolve().parent + +def get_resource_dir() -> Path: + """Read-only bundled resources. When frozen = sys._MEIPASS.""" + if _is_frozen(): + return Path(sys._MEIPASS) + return Path(__file__).resolve().parent + +def get_config_dir() -> Path: + """Config directory (config.json, agent_memory.json).""" + return get_application_dir() / "Config" + +def get_config_path() -> Path: + """Config file: Config/config.json (or bundled).""" + app = get_application_dir() + cfg_dir = get_config_dir() + res = get_resource_dir() + for d in (cfg_dir, app, res, res / "Config"): + p = d / "config.json" + if p.exists(): + return p + return cfg_dir / "config.json" + +def get_data_source_dir() -> Path: + """DataSource directory (keyfields, MUST_GATHER docs).""" + return get_application_dir() / "DataSource" + +def get_keyfields_path() -> Path: + """Path to keyfields_yaml_ml_input.docx (DataSource or bundled).""" + app_ds = get_data_source_dir() / "keyfields_yaml_ml_input.docx" + if app_ds.exists(): + return app_ds + res_ds = get_resource_dir() / "DataSource" / "keyfields_yaml_ml_input.docx" + if res_ds.exists(): + return res_ds + return get_resource_dir() / "keyfields_yaml_ml_input.docx" + +def get_json_keyfields_path() -> Path: + """Path to keyfields_json_ml_input.odt (DataSource or bundled).""" + app_ds = get_data_source_dir() / "keyfields_json_ml_input.odt" + if app_ds.exists(): + return app_ds + res_ds = get_resource_dir() / "DataSource" / "keyfields_json_ml_input.odt" + if res_ds.exists(): + return res_ds + return get_resource_dir() / "keyfields_json_ml_input.odt" + +def get_json_config_path() -> Path: + """JSON processing config: Config/json_config.json (or bundled).""" + cfg_dir = get_config_dir() + res = get_resource_dir() + for d in (cfg_dir, get_application_dir(), res, res / "Config"): + p = d / "json_config.json" + if p.exists(): + return p + return cfg_dir / "json_config.json" + +def get_memory_file_path() -> Path: + """Agent memory file: Config/agent_memory.json.""" + return get_config_dir() / "agent_memory.json" + +def get_must_gather_docs_dir() -> Path: + """MUST_GATHER topology docs: DataSource (MUST_GATHER_*.md).""" + return get_data_source_dir() + +def get_results_dir() -> Path: + """Results directory (errors_aggregate.json, rca_summary.txt, ML outputs). + NOTE: skill scripts redirect this to /tmp at startup since the image is read-only.""" + d = get_application_dir() / "Results" + d.mkdir(parents=True, exist_ok=True) + return d + +# Convenience +APPLICATION_DIR = get_application_dir() +RESOURCE_DIR = get_resource_dir() diff --git a/intelliaide/extract_cluster.py b/intelliaide/extract_cluster.py new file mode 100644 index 0000000..5e7d186 --- /dev/null +++ b/intelliaide/extract_cluster.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 +""" +extract_cluster.py — Step 1 of IntelliAide pure-skills pipeline. + +Reads diagnostic data from /data/input (mounted from a PVC specified in the +Proposal's spec.dataSource.claimName) and validates it. + +The PVC mount point is /data/input; cluster_dir in state.json is the deepest +directory that serves as the logical root for downstream file resolution. +Single-child wrapper directories are unwrapped so cluster_dir lands as close +to the real data as possible. No assumptions are made about the internal +layout — downstream DataAnalyzer._resolve_path handles path expansion with +glob wildcards for any directory structure. + +The PVC must be pre-populated with diagnostic data before the Proposal is +created. The operator mounts it read-only at /data/input. + +Usage: + python /app/skills/intelliaide/extract_cluster.py --query "etcd pods not ready" + + # Reuse an existing job dir (skips validation) + python /app/skills/intelliaide/extract_cluster.py --query "..." --job-dir /tmp/intelliaide/abc123 + +Output (stdout JSON): + { + "job_id": "<8-char id>", + "job_dir": "/tmp/intelliaide/", + "cluster_dir": "/data/input/...", + "mode": "must-gather", + "success": true, + "return_code": 0 + } + +On failure the script prints JSON with success=false and exits with code 1 so +the orchestrating agent stops immediately rather than proceeding with no data. +""" + +import argparse +import json +import sys +import uuid +from pathlib import Path + +_SKILL_DIR = Path(__file__).resolve().parent +_JOB_BASE = "/tmp/intelliaide" + +_DATA_INPUT_DIR = Path("/data/input") + + +def _log_pod(msg: str) -> None: + """Write a progress line directly to the container log stream (PID 1 stdout). + + Skill scripts run as subprocesses of the Claude Code CLI whose stdout is + piped internally — it never reaches the pod log stream. Opening PID 1's + stdout directly is the only way to surface progress in `oc logs`. + Falls back silently if the file is not accessible. + """ + line = f"[intelliaide] {msg}\n" + try: + with open("/proc/1/fd/1", "a") as fh: + fh.write(line) + except Exception: + sys.stderr.write(line) + + +_SKIP_DIRS = frozenset({"lost+found"}) + +_MIN_DATA_FILES = 3 + +_MAX_UNWRAP_DEPTH = 4 + + +def _real_entries(parent: Path) -> "list[Path]": + """List children of *parent*, skipping lost+found and hidden entries.""" + try: + return sorted( + p for p in parent.iterdir() + if p.name not in _SKIP_DIRS and not p.name.startswith(".") + ) + except OSError: + return [] + + +def _unwrap_single_child_dirs(raw_dir: Path) -> Path: + """Walk single-child wrapper directories to reach the actual data root. + + Many diagnostic bundles wrap content in one or more levels of a single + subdirectory (e.g. /data/input/bundle-abc123/cluster-dump/...). This + traverses down as long as a directory has exactly one real child that is + itself a directory, stopping when the directory fans out or max depth is + reached. + """ + current = raw_dir + for _ in range(_MAX_UNWRAP_DEPTH): + children = _real_entries(current) + dirs = [c for c in children if c.is_dir()] + if len(dirs) == 1 and len(children) == 1: + current = dirs[0] + else: + break + return current + + +def _total_files(entries: "list[Path]") -> int: + """Count files across all top-level entries (dirs and regular files).""" + total = 0 + for entry in entries: + if entry.is_file(): + total += 1 + elif entry.is_dir(): + total += sum(1 for _ in entry.rglob("*") if _.is_file()) + return total + + +def _check_data_source() -> "tuple[Path, bool, str]": + """Validate /data/input and return the data root for downstream resolution. + + Checks: mount exists, is readable, is non-empty, has enough files. + Then unwraps single-child wrapper directories so cluster_dir is as close + to the real data as possible. + + Returns (cluster_dir, success, error_message). + """ + if not _DATA_INPUT_DIR.exists(): + return _DATA_INPUT_DIR, False, ( + "No data source found at /data/input. " + "Ensure spec.dataSource.claimName is set on the Proposal " + "and the PVC contains diagnostic data." + ) + + try: + list(_DATA_INPUT_DIR.iterdir()) + except OSError as exc: + return _DATA_INPUT_DIR, False, ( + f"Cannot read /data/input: {exc}. " + "Check PVC permissions and content." + ) + + real = _real_entries(_DATA_INPUT_DIR) + if not real: + return _DATA_INPUT_DIR, False, ( + "Data source at /data/input is empty (only lost+found). " + "Ensure the PVC contains diagnostic data before creating the Proposal." + ) + + total_files = _total_files(real) + if total_files < _MIN_DATA_FILES: + return _DATA_INPUT_DIR, False, ( + f"Data source at /data/input has too few files ({total_files}). " + "Ensure the PVC contains a complete diagnostic bundle." + ) + + data_root = _unwrap_single_child_dirs(_DATA_INPUT_DIR) + return data_root, True, "" + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--query", required=True, + help="Problem statement for RCA (passed through to state.json)") + parser.add_argument("--job-dir", default=None, + help="Reuse an existing job dir (skips validation, updates state.json)") + args = parser.parse_args() + + if args.job_dir: + job_dir = Path(args.job_dir) + job_id = job_dir.name + else: + job_id = str(uuid.uuid4())[:8] + job_dir = Path(_JOB_BASE) / job_id + + job_dir.mkdir(parents=True, exist_ok=True) + + mode = "must-gather" + + if args.job_dir: + cluster_dir, success, error_msg = _check_data_source() + else: + _log_pod(f"Step 1/4 — extract_cluster job_id={job_id} mode=must-gather") + print(f"[extract_cluster] job_id={job_id} mode=must-gather", file=sys.stderr) + + _log_pod("Step 1/4 — checking data source at /data/input (PVC mount)") + cluster_dir, success, error_msg = _check_data_source() + + state = { + "job_id": job_id, + "job_dir": str(job_dir), + "cluster_dir": str(cluster_dir), + "query": args.query, + "mode": mode, + } + (job_dir / "state.json").write_text(json.dumps(state, indent=2)) + + if not success: + _log_pod(f"Step 1/4 — extract_cluster FAILED: {error_msg}") + print(json.dumps({ + "job_id": job_id, + "job_dir": str(job_dir), + "cluster_dir": str(cluster_dir), + "mode": mode, + "success": False, + "return_code": 1, + "error": error_msg, + })) + sys.exit(1) + + _log_pod(f"Step 1/4 — extract_cluster done mode={mode} cluster_dir={cluster_dir}") + print(f"[extract_cluster] Data source validated: {cluster_dir}", file=sys.stderr) + print(json.dumps({ + "job_id": job_id, + "job_dir": str(job_dir), + "cluster_dir": str(cluster_dir), + "mode": mode, + "success": True, + "return_code": 0, + })) + + +if __name__ == "__main__": + main() diff --git a/intelliaide/perform_rca.py b/intelliaide/perform_rca.py new file mode 100644 index 0000000..41c41be --- /dev/null +++ b/intelliaide/perform_rca.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 +""" +perform_rca.py — Step 4 of IntelliAide pure-skills pipeline (LLM-free). + +This script has two modes, selected with --mode: + + chunks (default) + ---------------- + Reads analyze_data.py output, runs token-aware chunking and prompt formatting + (all pure Python), writes chunk prompt files to disk, and outputs a manifest. + NO LLM call is made. After this script exits, the orchestrating agent (Claude) + must read each chunk file, reason about root cause, and write one summary file + per chunk: /chunk_summary__.md + + reduce + ------ + Reads the Claude-written summary files, estimates their token sizes, and groups + them into batches that fit within the configured token budget — exactly as + _hierarchical_reduce() did, but without calling an LLM. Outputs a reduce + manifest. After this exits, the orchestrating agent synthesises each batch and + writes . If is_final=false, the agent calls this script + again at level+1 with the batch output files as --summary-files. + +Usage: + # Chunk phase (first call per priority): + python perform_rca.py --job-dir --priority high + + # Reduce phase (after Claude writes chunk summaries): + python perform_rca.py --job-dir --priority high --mode reduce \\ + --level 1 \\ + --summary-files /chunk_summary_high_1.md \\ + /chunk_summary_high_2.md + + # Continuation pass — medium priority, building on high-priority results: + python perform_rca.py --job-dir --priority medium + python perform_rca.py --job-dir --priority medium --mode reduce \\ + --level 1 --summary-files /chunk_summary_medium_1.md ... + +Reads (chunks mode): + /state.json + /analysis_.json + /file_selection.json (to determine has_medium / has_low) + +Writes (chunks mode): + /chunk__.md + /rca_chunks__manifest.json + +Writes (reduce mode): + /reduce_level_manifest.json + +Output (chunks mode, stdout JSON): + { + "mode": "chunks", + "priority": "high", + "chunk_count": 3, + "manifest_path": "/rca_chunks_high_manifest.json", + "has_medium": true, + "has_low": false + } + +Output (reduce mode, stdout JSON): + { + "mode": "reduce", + "priority": "high", + "level": 1, + "batch_count": 2, + "is_final": false, + "manifest_path": "/reduce_level1_manifest.json" + } +""" + +import argparse +import json +import sys +from pathlib import Path + +_SKILL_DIR = Path(__file__).resolve().parent +for _p in ( + str(_SKILL_DIR / "vendor"), + str(_SKILL_DIR / "Main-program"), + str(_SKILL_DIR / "python-client"), + str(_SKILL_DIR), +): + if _p not in sys.path: + sys.path.insert(0, _p) + +from llm_rca_agent import prepare_rca_chunks, prepare_reduce_batches # noqa: E402 +from app_paths import get_config_path # noqa: E402 + + +def _log_pod(msg: str) -> None: + line = f"[intelliaide] {msg}\n" + try: + with open("/proc/1/fd/1", "a") as fh: + fh.write(line) + except Exception: + sys.stderr.write(line) + + +def _mode_chunks(args: argparse.Namespace) -> None: + job_dir = Path(args.job_dir) + state = json.loads((job_dir / "state.json").read_text()) + query = state["query"] + + analysis_file = job_dir / f"analysis_{args.priority}.json" + analysis = json.loads(analysis_file.read_text()) + yaml_errors = analysis.get("yaml_errors", {}) + log_entries = analysis.get("log_entries", []) or None + + _log_pod( + f"Step 4 — perform_rca (chunks) priority={args.priority} " + f"yaml_keys={len(yaml_errors)} " + f"log_entries={len(log_entries) if log_entries else 0}" + ) + print( + f"[perform_rca] chunks priority={args.priority} " + f"yaml_keys={len(yaml_errors)} " + f"log_entries={len(log_entries) if log_entries else 0}", + file=sys.stderr, + ) + + config_path = str(get_config_path()) + manifest = prepare_rca_chunks( + ml_classification_result=yaml_errors, + job_dir=str(job_dir), + priority=args.priority, + log_error_entries=log_entries, + config_path=config_path, + ) + + file_selection = json.loads((job_dir / "file_selection.json").read_text()) + has_medium = len(file_selection.get("medium", [])) > 0 + has_low = len(file_selection.get("low", [])) > 0 + + _log_pod( + f"Step 4 — perform_rca chunks done priority={args.priority} " + f"chunks={manifest['chunk_count']}" + ) + print(json.dumps({ + "mode": "chunks", + "priority": args.priority, + "chunk_count": manifest["chunk_count"], + "manifest_path": manifest["manifest_path"], + "has_medium": has_medium, + "has_low": has_low, + })) + + +def _mode_reduce(args: argparse.Namespace) -> None: + job_dir = Path(args.job_dir) + state = json.loads((job_dir / "state.json").read_text()) + query = state["query"] + + if not args.summary_files: + print( + json.dumps({"error": "--summary-files is required in reduce mode"}), + ) + sys.exit(1) + + _log_pod( + f"Step 5 — perform_rca (reduce) priority={args.priority} " + f"level={args.level} files={len(args.summary_files)}" + ) + print( + f"[perform_rca] reduce priority={args.priority} " + f"level={args.level} files={len(args.summary_files)}", + file=sys.stderr, + ) + + config_path = str(get_config_path()) + manifest = prepare_reduce_batches( + summary_files=args.summary_files, + job_dir=str(job_dir), + level=args.level, + problem_statement=query, + config_path=config_path, + ) + + _log_pod( + f"Step 5 — perform_rca reduce done priority={args.priority} " + f"level={args.level} batches={manifest['batch_count']} " + f"is_final={manifest['is_final']}" + ) + print(json.dumps({ + "mode": "reduce", + "priority": args.priority, + "level": args.level, + "batch_count": manifest["batch_count"], + "is_final": manifest["is_final"], + "manifest_path": manifest["manifest_path"], + })) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--job-dir", required=True) + parser.add_argument("--priority", required=True, choices=["high", "medium", "low"]) + parser.add_argument("--mode", default="chunks", choices=["chunks", "reduce"], + help="'chunks' (default): prepare chunk prompts; " + "'reduce': batch Claude-written summaries") + parser.add_argument("--level", type=int, default=1, + help="Reduce level (1 = first reduce pass). Only used with --mode reduce.") + parser.add_argument("--summary-files", nargs="+", default=[], + help="Paths to Claude-written summary files. Only used with --mode reduce.") + args = parser.parse_args() + + if args.mode == "chunks": + _mode_chunks(args) + else: + _mode_reduce(args) + + +if __name__ == "__main__": + main() diff --git a/intelliaide/requirements.txt b/intelliaide/requirements.txt new file mode 100644 index 0000000..ae200a2 --- /dev/null +++ b/intelliaide/requirements.txt @@ -0,0 +1,32 @@ +# ============================================================================= +# IntelliAide Skills — Python Dependencies +# ============================================================================= +# Vendored into /intelliaide/vendor/ at image build time via: +# pip install --no-cache-dir --target /intelliaide/vendor -r requirements.txt +# +# Vendoring is required because the sandbox pod runs scripts with its own +# Python interpreter (from the lightspeed-agentic base image), not the UBI9 +# Python embedded in this skills image. The vendor/ directory is added to +# PYTHONPATH / sys.path by each skill script at startup so packages are found. +# +# Excludes API server / MCP server deps (fastapi, uvicorn, mcp) — those are +# only needed for the standalone GitLab deployment, not for skills execution. +# ============================================================================= + +# --- HTTP client (LLM API calls) --- +requests>=2.31.0 + +# --- YAML parsing --- +PyYAML>=6.0.0 + +# --- GCP Vertex AI authentication (gcloud token exchange) --- +google-auth>=2.20.0 + +# --- Log template mining (Drain3 ML algorithm) --- +drain3>=0.9.11 + +# --- ODT keyfields document parsing --- +odfpy>=1.4.1 + +# --- DOCX keyfields document parsing --- +python-docx>=1.0 diff --git a/intelliaide/select_files.py b/intelliaide/select_files.py new file mode 100644 index 0000000..653d9f8 --- /dev/null +++ b/intelliaide/select_files.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +""" +select_files.py — Step 2 of IntelliAide pure-skills pipeline. + +Prepares a file-selection prompt and writes it to disk so the orchestrating +agent (Claude) can perform the file-selection reasoning inline, with no hidden +LLM call in this script. + +Usage: + python /app/skills/intelliaide/select_files.py --job-dir /tmp/intelliaide/ + +Reads: + /state.json (query + cluster_dir from extract_cluster.py) + +Writes: + /file_selection_prompt.md (formatted prompt for the orchestrator) + +Output (stdout JSON): + { + "prompt_path": "/file_selection_prompt.md", + "cluster_dir": "", + "docs_dir": "", + "has_docs": true/false + } + +After this script exits, the orchestrating agent MUST: + 1. cat + 2. Reason about which files are most relevant. + 3. Write /file_selection.json with the following schema: + { + "query": "", + "cluster_dir": "", + "problem_category": "", + "high": [{"original": "path", "resolved": "path", "found": true, "reason": "..."}], + "medium": [...], + "low": [...] + } + The "resolved" field should equal "original" when the agent cannot verify + existence on disk; the downstream analyze_data.py step handles missing files. +""" + +import argparse +import json +import sys +from pathlib import Path + +# All IntelliAide engine code lives alongside this script in the same folder. +# At runtime (in the sandbox container) this resolves to /app/skills/intelliaide/ +_SKILL_DIR = Path(__file__).resolve().parent +for _p in ( + str(_SKILL_DIR / "vendor"), # vendored packages + str(_SKILL_DIR / "Main-program"), + str(_SKILL_DIR / "python-client"), + str(_SKILL_DIR), +): + if _p not in sys.path: + sys.path.insert(0, _p) + +from must_gather_file_selector import prepare_file_selection_prompt # noqa: E402 +from app_paths import get_must_gather_docs_dir # noqa: E402 + + +def _log_pod(msg: str) -> None: + """Write a progress line directly to the container log stream (PID 1 stdout).""" + line = f"[intelliaide] {msg}\n" + try: + with open("/proc/1/fd/1", "a") as fh: + fh.write(line) + except Exception: + sys.stderr.write(line) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--job-dir", required=True, help="Path to the shared job directory") + args = parser.parse_args() + + job_dir = Path(args.job_dir) + state = json.loads((job_dir / "state.json").read_text()) + query = state["query"] + cluster_dir = state["cluster_dir"] + docs_dir = str(get_must_gather_docs_dir()) + + _log_pod(f"Step 2/4 — select_files (prepare prompt) query={query[:60]!r}") + print(f"[select_files] query={query[:80]!r}", file=sys.stderr) + print(f"[select_files] docs_dir={docs_dir}", file=sys.stderr) + print(f"[select_files] cluster_dir={cluster_dir}", file=sys.stderr) + + result = prepare_file_selection_prompt( + problem_statement=query, + docs_dir=docs_dir, + job_dir=str(job_dir), + ) + + _log_pod( + f"Step 2/4 — select_files prompt ready " + f"has_docs={result['has_docs']} prompt={result['prompt_path']}" + ) + print(f"[select_files] prompt written to {result['prompt_path']}", file=sys.stderr) + + print(json.dumps({ + "prompt_path": result["prompt_path"], + "cluster_dir": cluster_dir, + "docs_dir": result["docs_dir"], + "has_docs": result["has_docs"], + })) + + +if __name__ == "__main__": + main() diff --git a/intelliaide/vendor/81d243bd2c585b0f4821__mypyc.cpython-312-x86_64-linux-gnu.so b/intelliaide/vendor/81d243bd2c585b0f4821__mypyc.cpython-312-x86_64-linux-gnu.so new file mode 100755 index 0000000..83590cf Binary files /dev/null and b/intelliaide/vendor/81d243bd2c585b0f4821__mypyc.cpython-312-x86_64-linux-gnu.so differ diff --git a/intelliaide/vendor/_cffi_backend.cpython-312-x86_64-linux-gnu.so b/intelliaide/vendor/_cffi_backend.cpython-312-x86_64-linux-gnu.so new file mode 100755 index 0000000..156ee43 Binary files /dev/null and b/intelliaide/vendor/_cffi_backend.cpython-312-x86_64-linux-gnu.so differ diff --git a/intelliaide/vendor/_yaml/__init__.py b/intelliaide/vendor/_yaml/__init__.py new file mode 100644 index 0000000..7baa8c4 --- /dev/null +++ b/intelliaide/vendor/_yaml/__init__.py @@ -0,0 +1,33 @@ +# This is a stub package designed to roughly emulate the _yaml +# extension module, which previously existed as a standalone module +# and has been moved into the `yaml` package namespace. +# It does not perfectly mimic its old counterpart, but should get +# close enough for anyone who's relying on it even when they shouldn't. +import yaml + +# in some circumstances, the yaml module we imoprted may be from a different version, so we need +# to tread carefully when poking at it here (it may not have the attributes we expect) +if not getattr(yaml, '__with_libyaml__', False): + from sys import version_info + + exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError + raise exc("No module named '_yaml'") +else: + from yaml._yaml import * + import warnings + warnings.warn( + 'The _yaml extension module is now located at yaml._yaml' + ' and its location is subject to change. To use the' + ' LibYAML-based parser and emitter, import from `yaml`:' + ' `from yaml import CLoader as Loader, CDumper as Dumper`.', + DeprecationWarning + ) + del warnings + # Don't `del yaml` here because yaml is actually an existing + # namespace member of _yaml. + +__name__ = '_yaml' +# If the module is top-level (i.e. not a part of any specific package) +# then the attribute should be set to ''. +# https://docs.python.org/3.8/library/types.html +__package__ = '' diff --git a/intelliaide/vendor/bin/csv2ods b/intelliaide/vendor/bin/csv2ods new file mode 100755 index 0000000..b05f085 --- /dev/null +++ b/intelliaide/vendor/bin/csv2ods @@ -0,0 +1,229 @@ +#!/opt/app-root/bin/python3.12 +# -*- coding: utf-8 -*- +# Copyright (C) 2008 Agustin Henze -> agustinhenze at gmail.com +# +# This is free software. You may redistribute it under the terms +# of the Apache license and the GNU General Public License Version +# 2 or at your option any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Contributor(s): +# +# Søren Roug +# +# Oct 2014: Georges Khaznadar +# - ported to Python3 +# - imlemented the missing switch -c / --encoding, with an extra +# feature for POSIX platforms which can guess encoding. + +from odf.opendocument import OpenDocumentSpreadsheet +from odf.style import Style, TextProperties, ParagraphProperties, TableColumnProperties +from odf.text import P +from odf.table import Table, TableColumn, TableRow, TableCell +from optparse import OptionParser +import sys,csv,re, os, codecs + +if sys.version_info[0]==3: unicode=str + +if sys.version_info[0]==2: + class UTF8Recoder: + """ + Iterator that reads an encoded stream and reencodes the input to UTF-8 + """ + def __init__(self, f, encoding): + self.reader = codecs.getreader(encoding)(f) + + def __iter__(self): + return self + + def next(self): + return self.reader.next().encode("utf-8") + + class UnicodeReader: + """ + A CSV reader which will iterate over lines in the CSV file "f", + which is encoded in the given encoding. + """ + + def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds): + f = UTF8Recoder(f, encoding) + self.reader = csv.reader(f, dialect=dialect, **kwds) + + def next(self): + row = self.reader.next() + return [unicode(s, "utf-8") for s in row] + + def __iter__(self): + return self + + +def csvToOds( pathFileCSV, pathFileODS, tableName='table', + delimiter=',', quoting=csv.QUOTE_MINIMAL, + quotechar = '"', escapechar = None, + skipinitialspace = False, lineterminator = '\r\n', + encoding="utf-8"): + textdoc = OpenDocumentSpreadsheet() + # Create a style for the table content. One we can modify + # later in the word processor. + tablecontents = Style(name="Table Contents", family="paragraph") + tablecontents.addElement(ParagraphProperties(numberlines="false", linenumber="0")) + tablecontents.addElement(TextProperties(fontweight="bold")) + textdoc.styles.addElement(tablecontents) + + # Start the table + table = Table( name=tableName ) + + if sys.version_info[0]==3: + reader = csv.reader(open(pathFileCSV, encoding=encoding), + delimiter=delimiter, + quoting=quoting, + quotechar=quotechar, + escapechar=escapechar, + skipinitialspace=skipinitialspace, + lineterminator=lineterminator) + else: + reader = UnicodeReader(open(pathFileCSV), + encoding=encoding, + delimiter=delimiter, + quoting=quoting, + quotechar=quotechar, + escapechar=escapechar, + skipinitialspace=skipinitialspace, + lineterminator=lineterminator) + fltExp = re.compile('^\s*[-+]?\d+(\.\d+)?\s*$') + + for row in reader: + tr = TableRow() + table.addElement(tr) + for val in row: + if fltExp.match(val): + tc = TableCell(valuetype="float", value=val.strip()) + else: + tc = TableCell(valuetype="string") + tr.addElement(tc) + p = P(stylename=tablecontents,text=val) + tc.addElement(p) + + textdoc.spreadsheet.addElement(table) + textdoc.save( pathFileODS ) + +if __name__ == "__main__": + usage = "%prog -i file.csv -o file.ods -d" + parser = OptionParser(usage=usage, version="%prog 0.1") + parser.add_option('-i','--input', action='store', + dest='input', help='File input in csv') + parser.add_option('-o','--output', action='store', + dest='output', help='File output in ods') + parser.add_option('-d','--delimiter', action='store', + dest='delimiter', help='specifies a one-character string to use as the field separator. It defaults to ",".') + + parser.add_option('-c','--encoding', action='store', + dest='encoding', help='specifies the encoding the file csv. It defaults to utf-8') + + parser.add_option('-t','--table', action='store', + dest='tableName', help='The table name in the output file') + + parser.add_option('-s','--skipinitialspace', + dest='skipinitialspace', help='''specifies how to interpret whitespace which + immediately follows a delimiter. It defaults to False, which + means that whitespace immediately following a delimiter is part + of the following field.''') + + parser.add_option('-l','--lineterminator', action='store', + dest='lineterminator', help='''specifies the character sequence which should + terminate rows.''') + + parser.add_option('-q','--quoting', action='store', + dest='quoting', help='''It can take on any of the following module constants: + 0 = QUOTE_MINIMAL means only when required, for example, when a field contains either the quotechar or the delimiter + 1 = QUOTE_ALL means that quotes are always placed around fields. + 2 = QUOTE_NONNUMERIC means that quotes are always placed around fields which do not parse as integers or floating point numbers. + 3 = QUOTE_NONE means that quotes are never placed around fields. + It defaults is QUOTE_MINIMAL''') + + parser.add_option('-e','--escapechar', action='store', + dest='escapechar', help='''specifies a one-character string used to escape the delimiter when quoting is set to QUOTE_NONE.''') + + parser.add_option('-r','--quotechar', action='store', + dest='quotechar', help='''specifies a one-character string to use as the quoting character. It defaults to ".''') + + (options, args) = parser.parse_args() + + if options.input: + pathFileCSV = options.input + else: + parser.print_help() + exit( 0 ) + + if options.output: + pathFileODS = options.output + else: + parser.print_help() + exit( 0 ) + + if options.delimiter: + delimiter = options.delimiter + else: + delimiter = "," + + if options.skipinitialspace: + skipinitialspace = True + else: + skipinitialspace=False + + if options.lineterminator: + lineterminator = options.lineterminator + else: + lineterminator ="\r\n" + + if options.escapechar: + escapechar = options.escapechar + else: + escapechar=None + + if options.tableName: + tableName = options.tableName + else: + tableName = "table" + + if options.quotechar: + quotechar = options.quotechar + else: + quotechar = "\"" + + encoding = "utf-8" # default setting + ########################################################### + ## try to guess the encoding; this is implemented only with + ## POSIX platforms. Can it be improved? + output = os.popen('/usr/bin/file ' + pathFileCSV).read() + m=re.match(r'^.*: ([-a-zA-Z0-9]+) text$', output) + if m: + encoding=m.group(1) + if 'ISO-8859' in encoding: + encoding="latin-1" + else: + encoding="utf-8" + ############################################################ + # when the -c or --coding switch is used, it takes precedence + if options.encoding: + encoding = options.encoding + + csvToOds( pathFileCSV=unicode(pathFileCSV), + pathFileODS=unicode(pathFileODS), + delimiter=delimiter, skipinitialspace=skipinitialspace, + escapechar=escapechar, + lineterminator=unicode(lineterminator), + tableName=tableName, quotechar=quotechar, + encoding=encoding) + +# Local Variables: *** +# mode: python *** +# End: *** diff --git a/intelliaide/vendor/bin/idna b/intelliaide/vendor/bin/idna new file mode 100755 index 0000000..24caf23 --- /dev/null +++ b/intelliaide/vendor/bin/idna @@ -0,0 +1,8 @@ +#!/opt/app-root/bin/python3.12 +# -*- coding: utf-8 -*- +import re +import sys +from idna.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/intelliaide/vendor/bin/mailodf b/intelliaide/vendor/bin/mailodf new file mode 100755 index 0000000..94801c7 --- /dev/null +++ b/intelliaide/vendor/bin/mailodf @@ -0,0 +1,95 @@ +#!/opt/app-root/bin/python3.12 +# -*- coding: utf-8 -*- +# Copyright (C) 2006 Søren Roug, European Environment Agency +# +# This is free software. You may redistribute it under the terms +# of the Apache license and the GNU General Public License Version +# 2 or at your option any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Contributor(s): +# +from odf.odf2xhtml import ODF2XHTML +import zipfile +import sys, os, smtplib, getopt + +from email.mime.multipart import MIMEMultipart +from email.mime.nonmultipart import MIMENonMultipart +from email.mime.text import MIMEText +from email.encoders import encode_base64 + +if sys.version_info[0]==3: unicode=str + +def usage(): + sys.stderr.write("Usage: %s [-f from] [-s subject] inputfile recipients...\n" % sys.argv[0]) + +try: + opts, args = getopt.getopt(sys.argv[1:], "f:s:", ["from=", "subject="]) +except getopt.GetoptError: + usage() + sys.exit(2) + +fromaddr = os.getlogin() + "@" + os.getenv('HOSTNAME','localhost') +subject = None +for o, a in opts: + if o in ("-f", "--from"): + fromaddr = a + if o in ("-s", "--subject"): + subject = a + +if len(args) < 2: + usage() + sys.exit(2) + +suffices = { + 'wmf':('image','x-wmf'), + 'png':('image','png'), + 'gif':('image','gif'), + 'jpg':('image','jpeg'), + 'jpeg':('image','jpeg') + } + +msg = MIMEMultipart('related',type="text/html") +msg['From'] = fromaddr +# msg['Date'] = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()) +msg['To'] = ','.join(args[1:]) +msg.preamble = 'This is a multi-part message in MIME format.' +msg.epilogue = '' +odhandler = ODF2XHTML() +result = odhandler.odf2xhtml(unicode(args[0])) +if subject: + msg['Subject'] = subject +else: + msg['Subject'] = odhandler.title +htmlpart = MIMEText(result,'html','us-ascii') +htmlpart['Content-Location'] = 'index.html' +msg.attach(htmlpart) +z = zipfile.ZipFile(unicode(args[0])) +for file in z.namelist(): + if file[0:9] == 'Pictures/': + suffix = file[file.rfind(".")+1:] + main,sub = suffices.get(suffix,('application','octet-stream')) + img = MIMENonMultipart(main,sub) + img.set_payload(z.read(file)) + img['Content-Location'] = "" + file + encode_base64(img) + msg.attach(img) +z.close() + +server = smtplib.SMTP('localhost') +#server.set_debuglevel(1) +server.sendmail(fromaddr, args[1:], msg.as_string()) +server.quit() + + +# Local Variables: *** +# mode: python *** +# End: *** diff --git a/intelliaide/vendor/bin/normalizer b/intelliaide/vendor/bin/normalizer new file mode 100755 index 0000000..0fe3ac7 --- /dev/null +++ b/intelliaide/vendor/bin/normalizer @@ -0,0 +1,8 @@ +#!/opt/app-root/bin/python3.12 +# -*- coding: utf-8 -*- +import re +import sys +from charset_normalizer.cli import cli_detect +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(cli_detect()) diff --git a/intelliaide/vendor/bin/odf2mht b/intelliaide/vendor/bin/odf2mht new file mode 100755 index 0000000..c09473e --- /dev/null +++ b/intelliaide/vendor/bin/odf2mht @@ -0,0 +1,72 @@ +#!/opt/app-root/bin/python3.12 +# -*- coding: utf-8 -*- +# Copyright (C) 2006 Søren Roug, European Environment Agency +# +# This is free software. You may redistribute it under the terms +# of the Apache license and the GNU General Public License Version +# 2 or at your option any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Contributor(s): +# +from __future__ import print_function +from odf.odf2xhtml import ODF2XHTML +import zipfile +import sys +#from time import gmtime, strftime + +from email.mime.multipart import MIMEMultipart +from email.mime.nonmultipart import MIMENonMultipart +from email.mime.text import MIMEText +from email import encoders + +if sys.version_info[0]==3: unicode=str + +if len(sys.argv) != 2: + sys.stderr.write("Usage: %s inputfile\n" % sys.argv[0]) + sys.exit(1) + +suffices = { + 'wmf':('image','x-wmf'), + 'png':('image','png'), + 'gif':('image','gif'), + 'jpg':('image','jpeg'), + 'jpeg':('image','jpeg') + } + +msg = MIMEMultipart('related',type="text/html") +# msg['Subject'] = 'Subject here' +# msg['From'] = '' +# msg['Date'] = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()) +msg.preamble = 'This is a multi-part message in MIME format.' +msg.epilogue = '' +odhandler = ODF2XHTML() +result = odhandler.odf2xhtml(unicode(sys.argv[1])) +htmlpart = MIMEText(result,'html','us-ascii') +htmlpart['Content-Location'] = 'index.html' +msg.attach(htmlpart) +z = zipfile.ZipFile(sys.argv[1]) +for file in z.namelist(): + if file[0:9] == 'Pictures/': + suffix = file[file.rfind(".")+1:] + main,sub = suffices.get(suffix,('application','octet-stream')) + img = MIMENonMultipart(main,sub) + img.set_payload(z.read(file)) + img['Content-Location'] = "" + file + encoders.encode_base64(img) + msg.attach(img) +z.close() +print (msg.as_string()) + + +# Local Variables: *** +# mode: python *** +# End: *** diff --git a/intelliaide/vendor/bin/odf2xhtml b/intelliaide/vendor/bin/odf2xhtml new file mode 100755 index 0000000..74a2aef --- /dev/null +++ b/intelliaide/vendor/bin/odf2xhtml @@ -0,0 +1,59 @@ +#!/opt/app-root/bin/python3.12 +# -*- coding: utf-8 -*- +# Copyright (C) 2007 Søren Roug, European Environment Agency +# +# This is free software. You may redistribute it under the terms +# of the Apache license and the GNU General Public License Version +# 2 or at your option any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Contributor(s): +# +from odf.odf2xhtml import ODF2XHTML +import sys, getopt + +if sys.version_info[0]==3: unicode=str + +from io import StringIO + +def usage(): + sys.stderr.write("Usage: %s [-p] inputfile\n" % sys.argv[0]) + +try: + opts, args = getopt.getopt(sys.argv[1:], "ep", ["plain","embedable"]) +except getopt.GetoptError: + usage() + sys.exit(2) + +generatecss = True +embedable = False +for o, a in opts: + if o in ("-p", "--plain"): + generatecss = False + if o in ("-e", "--embedable"): + embedable = True + +if len(args) != 1: + usage() + sys.exit(2) + +odhandler = ODF2XHTML(generatecss, embedable) +try: + result = odhandler.odf2xhtml(unicode(args[0])) +except: + sys.stderr.write("Unable to open file %s or file is not OpenDocument\n" % args[0]) + sys.exit(1) +sys.stdout.write(result) + + +# Local Variables: *** +# mode: python *** +# End: *** diff --git a/intelliaide/vendor/bin/odf2xml b/intelliaide/vendor/bin/odf2xml new file mode 100755 index 0000000..6cb576b --- /dev/null +++ b/intelliaide/vendor/bin/odf2xml @@ -0,0 +1,81 @@ +#!/opt/app-root/bin/python3.12 +# -*- coding: utf-8 -*- +# Copyright (C) 2008 Søren Roug, European Environment Agency +# +# This is free software. You may redistribute it under the terms +# of the Apache license and the GNU General Public License Version +# 2 or at your option any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Contributor(s): +# +# + +# OpenDocument can be a complete office document in a single +# XML document. This script will create such a document. +import sys, getopt, base64 +from odf.opendocument import load +from odf.draw import Image, ObjectOle +from odf.style import BackgroundImage +from odf.text import ListLevelStyleImage +from odf.office import BinaryData + +if sys.version_info[0]==3: unicode=str + +def usage(): + sys.stderr.write("Usage: %s [-e] [-o outputfile] [inputfile]\n" % sys.argv[0]) + + +if __name__ == "__main__": + embedimage = False + try: + opts, args = getopt.getopt(sys.argv[1:], "o:e", ["output="]) + except getopt.GetoptError: + usage() + sys.exit(2) + + outputfile = '-' + + for o, a in opts: + if o in ("-o", "--output"): + outputfile = a + if o == '-e': + embedimage = True + + if len(args) > 1: + usage() + sys.exit(2) + if len(args) == 0: + d = load(sys.stdin) + else: + d = load(unicode(args[0])) + if embedimage: + images = d.getElementsByType(Image) + \ + d.getElementsByType(BackgroundImage) + \ + d.getElementsByType(ObjectOle) + \ + d.getElementsByType(ListLevelStyleImage) + for image in images: + href = image.getAttribute('href') + if href and href[:9] == "Pictures/": + p = d.Pictures[href] + bp = base64.encodestring(p[1]) + image.addElement(BinaryData(text=bp)) + image.removeAttribute('href') + xml = d.xml() + if outputfile == '-': + print (xml) + else: + open(outputfile,"wb").write(xml) + + +# Local Variables: *** +# mode: python *** +# End: *** diff --git a/intelliaide/vendor/bin/odfimgimport b/intelliaide/vendor/bin/odfimgimport new file mode 100755 index 0000000..ae76b6a --- /dev/null +++ b/intelliaide/vendor/bin/odfimgimport @@ -0,0 +1,190 @@ +#!/opt/app-root/bin/python3.12 +# -*- coding: utf-8 -*- +# Copyright (C) 2007-2009 Søren Roug, European Environment Agency +# +# This is free software. You may redistribute it under the terms +# of the Apache license and the GNU General Public License Version +# 2 or at your option any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Contributor(s): +# +from __future__ import print_function + +import zipfile, sys, getopt, mimetypes +try: + from urllib2 import urlopen, quote, unquote +except ImportError: + from urllib.request import urlopen, quote, unquote +try: + from urlparse import urlunsplit, urlsplit +except ImportError: + from urllib.parse import urlunsplit, urlsplit +from odf.opendocument import load +from odf.draw import Image + +if sys.version_info[0]==3: unicode=str + +#sys.tracebacklimit = 0 + +# Variable to count the number of retrieval failures +failures = 0 + +# Set to one if quiet behaviour is wanted +quiet = 0 + +# If set will write every url to import +verbose = 0 + +# Dictionary with new pictures. Key is original file path +# Item is newfilename +newpictures = {} +doc = None + +def importpicture(href): + """ Add the picture to the ZIP file + Returns the new path name to the file in the zip archive + If it is unable to import, then it returns the original href + Sideeffect: add line to manifest + """ + global doc, newpictures, failures, verbose + + # Check that it is not already in the manifest + if href in doc.Pictures: return href + + image = None + if verbose: print ("Importing", href, file=sys.stderr) + if href[:7] == "http://" or href[:8] == "https://" or href[:6] == "ftp://": + # There is a bug in urlopen: It can't open urls with non-ascii unicode + # characters. Convert to UTF-8 and then use percent encoding + try: + goodhref = href.encode('ascii') + except: + o = list(urlsplit(href)) + o[2] = quote(o[2].encode('utf-8')) + goodhref = urlunsplit(o) + if goodhref in newpictures: + if verbose: print ("already imported", file=sys.stderr) + return newpictures[goodhref] # Already imported + try: + f = urlopen(goodhref.decode("utf-8")) + image = f.read() + headers = f.info() + f.close() + # Get the mimetype from the headerlines + c_t = headers['Content-Type'].split(';')[0].strip() + if c_t: mediatype = c_t.split(';')[0].strip() + if verbose: print ("OK", file=sys.stderr) + except: + failures += 1 + if verbose: print ("failed", file=sys.stderr) + return href + # Remove query string + try: href= href[:href.rindex('?')] + except: pass + try: + lastslash = href[href.rindex('/'):] + ext = lastslash[lastslash.rindex('.'):] + except: ext = mimetypes.guess_extension(mediatype) + # Everything is a simple path. + else: + goodhref = href + if href[:3] == '../': + if directory is None: + goodhref = unquote(href[3:]) + else: + goodhref = unquote(directory + href[2:]) + if goodhref in newpictures: + if verbose: print ("already imported", file=sys.stderr) + return newpictures[goodhref] # Already imported + mediatype, encoding = mimetypes.guess_type(goodhref) + if mediatype is None: + mediatype = '' + try: ext = goodhref[goodhref.rindex('.'):] + except: ext='' + else: + ext = mimetypes.guess_extension(mediatype) + try: + image = file(goodhref).read() + if verbose: print ("OK", file=sys.stderr) + except: + failures += 1 + if verbose: print ("failed", file=sys.stderr) + return href + # If we have a picture to import, the image variable contains it + # and manifestfn, ext and mediatype has a value + if image: + manifestfn = doc.addPictureFromString(image, unicode(mediatype)) + newpictures[goodhref] = manifestfn + return manifestfn + + if verbose: print ("not imported", file=sys.stderr) + return href + +def exitwithusage(exitcode=2): + """ Print out usage information and exit """ + print ("Usage: %s [-q] [-v] [-o output] [inputfile]" % sys.argv[0], file=sys.stderr) + print ("\tInputfile must be OpenDocument format", file=sys.stderr) + sys.exit(exitcode) + +outputfile = None +writefile = True + +try: + opts, args = getopt.getopt(sys.argv[1:], "qvo:") +except getopt.GetoptError: + exitwithusage() + +for o, a in opts: + if o == "-o": + outputfile = a + writefile = True + if o == "-q": + quiet = 1 + if o == "-v": + verbose = 1 + +if len(args) == 0: + try: + doc = load(sys.stdin) + directory = None + except: + print ("Couldn't open OpenDocument file", file=sys.stderr) + exitwithusage() +else: + fn = unicode(args[0]) + if not zipfile.is_zipfile(fn): + exitwithusage() + dirinx = max(fn.rfind('\\'), fn.rfind('/')) + if dirinx >= 0: directory = fn[:dirinx] + else: directory = "." + doc = load(fn) + +for image in doc.getElementsByType(Image): + href = image.getAttribute('href') + newhref = importpicture(href) + image.setAttribute('href',newhref) + +if writefile: + if outputfile is None: + doc.save(fn) + else: + doc.save(unicode(outputfile)) + + +if quiet == 0 and failures > 0: + print ("Couldn't import %d image(s)" % failures, file=sys.stderr) +sys.exit( int(failures > 0) ) + + +# Local Variables: *** +# mode: python *** +# End: *** diff --git a/intelliaide/vendor/bin/odflint b/intelliaide/vendor/bin/odflint new file mode 100755 index 0000000..aa0396b --- /dev/null +++ b/intelliaide/vendor/bin/odflint @@ -0,0 +1,216 @@ +#!/opt/app-root/bin/python3.12 +# -*- coding: utf-8 -*- +# Copyright (C) 2009 Søren Roug, European Environment Agency +# +# This is free software. You may redistribute it under the terms +# of the Apache license and the GNU General Public License Version +# 2 or at your option any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Contributor(s): +# +import zipfile +from xml.sax import make_parser,handler +from xml.sax.xmlreader import InputSource +import xml.sax.saxutils +import sys +from odf.opendocument import OpenDocument +from odf import element, grammar +from odf.namespaces import * +from odf.attrconverters import attrconverters, cnv_string + +from io import BytesIO + +if sys.version_info[0]==3: unicode=str + +extension_attributes = { + "OpenOffice.org" : { + (METANS,u'template'): ( + (XLINKNS,u'role'), + ), + (STYLENS,u'graphic-properties'): ( + (STYLENS,u'background-transparency'), + ), + (STYLENS,u'paragraph-properties'): ( + (TEXTNS,u'enable-numbering'), + (STYLENS,u'join-border'), + ), + (STYLENS,u'table-cell-properties'): ( + (STYLENS,u'writing-mode'), + ), + (STYLENS,u'table-row-properties'): ( + (STYLENS,u'keep-together'), + ), + }, + "KOffice" : { + (STYLENS,u'graphic-properties'): ( + (KOFFICENS,u'frame-behavior-on-new-page'), + ), + (DRAWNS,u'page'): ( + (KOFFICENS,u'name'), + ), + (PRESENTATIONNS,u'show-shape'): ( + (KOFFICENS,u'order-id'), + ), + (PRESENTATIONNS,u'hide-shape'): ( + (KOFFICENS,u'order-id'), + ), + (CHARTNS,u'legend'): ( + (KOFFICENS,u'title'), + ), + } +} + +printed_errors = [] + +def print_error(str): + if str not in printed_errors: + printed_errors.append(str) + print (str) + +def chop_arg(arg): + if len(arg) > 20: + return "%s..." % arg[0:20] + return arg + +def make_qname(tag): + return "%s:%s" % (nsdict.get(tag[0],tag[0]), tag[1]) + +def allowed_attributes(tag): + return grammar.allowed_attributes.get(tag) + + +class ODFElementHandler(handler.ContentHandler): + """ Extract headings from content.xml of an ODT file """ + def __init__(self, document): + self.doc = document + self.tagstack = [] + self.data = [] + self.currtag = None + + def characters(self, data): + self.data.append(data) + + def startElementNS(self, tag, qname, attrs): + """ Pseudo-create an element + """ + allowed_attrs = grammar.allowed_attributes.get(tag) + attrdict = {} + for (att,value) in attrs.items(): + prefix = nsdict.get(att[0],att[0]) + # Check if it is a known extension + notan_extension = True + for product, ext_attrs in extension_attributes.items(): + allowed_ext_attrs = ext_attrs.get(tag) + if allowed_ext_attrs and att in allowed_ext_attrs: + print_error("Warning: Attribute %s in element <%s> is illegal - %s extension" % ( make_qname(att), make_qname(tag), product)) + notan_extension = False + # Check if it is an allowed attribute + if notan_extension and allowed_attrs and att not in allowed_attrs: + print_error("Error: Attribute %s:%s is not allowed in element <%s>" % ( prefix, att[1], make_qname(tag))) + # Check the value + try: + convert = attrconverters.get(att, cnv_string) + convert(att, value, tag) + except ValueError as res: + print_error("Error: Bad value '%s' for attribute %s:%s in tag: <%s> - %s" % + (chop_arg(value), prefix, att[1], make_qname(tag), res)) + + self.tagstack.append(tag) + self.data = [] + # Check that the parent allows this child element + if tag not in ( (OFFICENS, 'document'), (OFFICENS, 'document-content'), (OFFICENS, 'document-styles'), + (OFFICENS, 'document-meta'), (OFFICENS, 'document-settings'), + (MANIFESTNS,'manifest')): + try: + parent = self.tagstack[-2] + allowed_children = grammar.allowed_children.get(parent) + except: + print_error("Error: This document starts with the wrong tag: <%s>" % make_qname(tag)) + allowed_children = None + if allowed_children and tag not in allowed_children: + print_error("Error: Element %s is not allowed in element %s" % ( make_qname(tag), make_qname(parent))) + # Test that all mandatory attributes have been added. + required = grammar.required_attributes.get(tag) + if required: + for r in required: + if attrs.get(r) is None: + print_error("Error: Required attribute missing: %s in <%s>" % (make_qname(r), make_qname(tag))) + + + def endElementNS(self, tag, qname): + self.currtag = self.tagstack.pop() + str = ''.join(self.data).strip() + # Check that only elements that can take text have text + # But only elements we know exist in grammar + if tag in grammar.allowed_children: + if str != '' and tag not in grammar.allows_text: + print_error("Error: %s does not allow text data" % make_qname(tag)) + self.data = [] + +class ODFDTDHandler(handler.DTDHandler): + def notationDecl(self, name, public_id, system_id): + """ Ignore DTDs """ + print_error("Warning: ODF doesn't use DOCTYPEs") + +def exitwithusage(exitcode=2): + """ print out usage information """ + sys.stderr.write("Usage: %s inputfile\n" % sys.argv[0]) + sys.stderr.write("\tInputfile must be OpenDocument format\n") + sys.exit(exitcode) + +def lint(odffile): + if not zipfile.is_zipfile(odffile): + print_error("Error: This is not a zipped file") + return + zfd = zipfile.ZipFile(odffile) + try: + mimetype = zfd.read('mimetype') + except: + mimetype='' + d = OpenDocument(unicode(mimetype)) + first = True + for zi in zfd.infolist(): + if first: + if zi.filename == 'mimetype': + if zi.compress_type != zipfile.ZIP_STORED: + print_error("Error: The 'mimetype' member must be stored - not deflated") + if zi.comment != "": + print_error("Error: The 'mimetype' member must not have extra header info") + else: + print_error("Warning: The first member in the archive should be the mimetype") + first = False + if zi.filename in ('META-INF/manifest.xml', 'content.xml', 'meta.xml', 'styles.xml', 'settings.xml'): + content = zfd.read(zi.filename) + parser = make_parser() + parser.setFeature(handler.feature_namespaces, True) + parser.setFeature(handler.feature_external_ges, False) + parser.setContentHandler(ODFElementHandler(d)) + dtdh = ODFDTDHandler() + parser.setDTDHandler(dtdh) + parser.setErrorHandler(handler.ErrorHandler()) + + inpsrc = InputSource() + if not isinstance(content, str): + content=content + inpsrc.setByteStream(BytesIO(content)) + parser.parse(inpsrc) + + +if len(sys.argv) != 2: + exitwithusage() +lint(unicode(sys.argv[1])) + + + +# Local Variables: *** +# mode: python *** +# End: *** diff --git a/intelliaide/vendor/bin/odfmeta b/intelliaide/vendor/bin/odfmeta new file mode 100755 index 0000000..b23a6d8 --- /dev/null +++ b/intelliaide/vendor/bin/odfmeta @@ -0,0 +1,266 @@ +#!/opt/app-root/bin/python3.12 +# -*- coding: utf-8 -*- +# Copyright (C) 2006-2009 Søren Roug, European Environment Agency +# +# This is free software. You may redistribute it under the terms +# of the Apache license and the GNU General Public License Version +# 2 or at your option any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Contributor(s): +# +import zipfile, time, sys, getopt, re +import xml.sax, xml.sax.saxutils +from odf.namespaces import TOOLSVERSION, OFFICENS, XLINKNS, DCNS, METANS +from io import BytesIO + +OUTENCODING="utf-8" + +whitespace = re.compile(r'\s+') + +fields = { +'title': (DCNS,u'title'), +'description': (DCNS,u'description'), +'subject': (DCNS,u'subject'), +'creator': (DCNS,u'creator'), +'date': (DCNS,u'date'), +'language': (DCNS,u'language'), +'generator': (METANS,u'generator'), +'initial-creator': (METANS,u'initial-creator'), +'keyword': (METANS,u'keyword'), +'editing-duration': (METANS,u'editing-duration'), +'editing-cycles': (METANS,u'editing-cycles'), +'printed-by': (METANS,u'printed-by'), +'print-date': (METANS,u'print-date'), +'creation-date': (METANS,u'creation-date'), +'user-defined': (METANS,u'user-defined'), +#'template': (METANS,u'template'), +} + +xfields = [] +Xfields = [] +addfields = {} +deletefields = {} +yieldfields = {} +showversion = None + +def exitwithusage(exitcode=2): + """ print out usage information """ + sys.stderr.write("Usage: %s [-cdlvV] [-xXaAI metafield]... [-o output] [inputfile]\n" % sys.argv[0]) + sys.stderr.write("\tInputfile must be OpenDocument format\n") + sys.exit(exitcode) + +def normalize(str): + """ + The normalize-space function returns the argument string with whitespace + normalized by stripping leading and trailing whitespace and replacing + sequences of whitespace characters by a single space. + """ + return whitespace.sub(' ', str).strip() + +class MetaCollector: + """ + The MetaCollector is a pseudo file object, that can temporarily ignore write-calls + It could probably be replaced with a StringIO object. + """ + def __init__(self): + self._content = [] + self.dowrite = True + + def write(self, str): + if self.dowrite: + self._content.append(str) + + def content(self): + return ''.join(self._content) + + +base = xml.sax.saxutils.XMLGenerator + +class odfmetaparser(base): + """ Parse a meta.xml file with an event-driven parser and replace elements. + It would probably be a cleaner approach to use a DOM based parser and + then manipulate in memory. + Small issue: Reorders elements + """ + version = 'Unknown' + + def __init__(self): + self._mimetype = '' + self.output = MetaCollector() + self._data = [] + self.seenfields = {} + base.__init__(self, self.output, OUTENCODING) + + def startElementNS(self, name, qname, attrs): + self._data = [] + field = name +# I can't modify the template until the tool replaces elements at the same +# location and not at the end +# if name == (METANS,u'template'): +# self._data = [attrs.get((XLINKNS,u'title'),'')] + if showversion and name == (OFFICENS,u'document-meta'): + if showversion == '-V': + print ("version:%s" % attrs.get((OFFICENS,u'version'),'Unknown').decode('utf-8')) + else: + print ("%s" % attrs.get((OFFICENS,u'version'),'Unknown').decode('utf-8')) + if name == (METANS,u'user-defined'): + field = attrs.get((METANS,u'name')) + if field in deletefields: + self.output.dowrite = False + elif field in yieldfields: + del addfields[field] + base.startElementNS(self, name, qname, attrs) + else: + base.startElementNS(self, name, qname, attrs) + self._tag = field + + def endElementNS(self, name, qname): + field = name + if name == (METANS,u'user-defined'): + field = self._tag + if name == (OFFICENS,u'meta'): + for k,v in addfields.items(): + if len(v) > 0: + if type(k) == type(''): + base.startElementNS(self,(METANS,u'user-defined'),None,{(METANS,u'name'):k}) + base.characters(self, v) + base.endElementNS(self, (METANS,u'user-defined'),None) + else: + base.startElementNS(self, k, None, {}) + base.characters(self, v) + base.endElementNS(self, k, None) + if name in xfields: + print ("%s" % self.data()) + if name in Xfields: + if isinstance(self._tag, tuple): + texttag = self._tag[1] + else: + texttag = self._tag + print ("%s:%s" % (texttag, self.data())) + if field in deletefields: + self.output.dowrite = True + else: + base.endElementNS(self, name, qname) + + def characters(self, content): + base.characters(self, content) + self._data.append(content) + + def meta(self): + return self.output.content() + + def data(self): + if usenormalize: + return normalize(''.join(self._data)) + else: + return ''.join(self._data) + +now = time.localtime()[:6] +outputfile = "-" +writemeta = False # Do we change any meta data? +usenormalize = False + +try: + opts, args = getopt.getopt(sys.argv[1:], "cdlvVI:A:a:o:x:X:") +except getopt.GetoptError: + exitwithusage() + +if len(opts) == 0: + opts = [ ('-l','') ] + +for o, a in opts: + if o in ('-a','-A','-I'): + writemeta = True + if a.find(":") >= 0: + k,v = a.split(":",1) + else: + k,v = (a, "") + if len(k) == 0: + exitwithusage() + k = fields.get(k,k) + addfields[k] = unicode(v,'utf-8') + if o == '-a': + yieldfields[k] = True + if o == '-I': + deletefields[k] = True + if o == '-d': + writemeta = True + addfields[(DCNS,u'date')] = "%04d-%02d-%02dT%02d:%02d:%02d" % now + deletefields[(DCNS,u'date')] = True + if o == '-c': + usenormalize = True + if o in ('-v', '-V'): + showversion = o + if o == '-l': + Xfields = fields.values() + if o == "-x": + xfields.append(fields.get(a,a)) + if o == "-X": + Xfields.append(fields.get(a,a)) + if o == "-o": + outputfile = a + +# The specification says we should change the element to our own, +# and must not export the original identifier. +if writemeta: + addfields[(METANS,u'generator')] = TOOLSVERSION + deletefields[(METANS,u'generator')] = True + +odfs = odfmetaparser() +parser = xml.sax.make_parser() +parser.setFeature(xml.sax.handler.feature_namespaces, 1) +parser.setContentHandler(odfs) + +if len(args) == 0: + zin = zipfile.ZipFile(sys.stdin,'r') +else: + if not zipfile.is_zipfile(args[0]): + exitwithusage() + zin = zipfile.ZipFile(args[0], 'r') + +try: + content = zin.read('meta.xml').decode('utf-8') +except: + sys.stderr.write("File has no meta data\n") + sys.exit(1) +parser.parse(BytesIO(content.encode('utf-8'))) + +if writemeta: + if outputfile == '-': + if sys.stdout.isatty(): + sys.stderr.write("Won't write ODF file to terminal\n") + sys.exit(1) + zout = zipfile.ZipFile(sys.stdout,"w") + else: + zout = zipfile.ZipFile(outputfile,"w") + + + + # Loop through the input zipfile and copy the content to the output until we + # get to the meta.xml. Then substitute. + for zinfo in zin.infolist(): + if zinfo.filename == "meta.xml": + # Write meta + zi = zipfile.ZipInfo("meta.xml", now) + zi.compress_type = zipfile.ZIP_DEFLATED + zout.writestr(zi,odfs.meta() ) + else: + payload = zin.read(zinfo.filename) + zout.writestr(zinfo, payload) + + zout.close() +zin.close() + + +# Local Variables: *** +# mode: python *** +# End: *** diff --git a/intelliaide/vendor/bin/odfoutline b/intelliaide/vendor/bin/odfoutline new file mode 100755 index 0000000..0c4ddc5 --- /dev/null +++ b/intelliaide/vendor/bin/odfoutline @@ -0,0 +1,144 @@ +#!/opt/app-root/bin/python3.12 +# -*- coding: utf-8 -*- +# Copyright (C) 2006 Søren Roug, European Environment Agency +# +# This is free software. You may redistribute it under the terms +# of the Apache license and the GNU General Public License Version +# 2 or at your option any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Contributor(s): +# +from __future__ import print_function +import zipfile +from xml.sax import make_parser,handler +from xml.sax.xmlreader import InputSource +import xml.sax.saxutils +import sys +from odf.namespaces import TEXTNS, TABLENS, DRAWNS + +try: + from cStringIO import StringIO +except ImportError: + from io import StringIO + + +def getxmlpart(odffile, xmlfile): + """ Get the content out of the ODT file""" + z = zipfile.ZipFile(odffile) + content = z.read(xmlfile) + z.close() + return content + + + +# +# Extract headings from content.xml +# +class ODTHeadingHandler(handler.ContentHandler): + """ Extract headings from content.xml of an ODT file """ + def __init__(self, eater): + self.r = eater + self.data = [] + self.level = 0 + + def characters(self, data): + self.data.append(data) + + def startElementNS(self, tag, qname, attrs): + if tag == (TEXTNS, 'h'): + self.level = 0 + for (att,value) in attrs.items(): + if att == (TEXTNS, 'outline-level'): + self.level = int(value) + self.data = [] + + def endElementNS(self, tag, qname): + if tag == (TEXTNS, 'h'): + str = ''.join(self.data) + self.data = [] + self.r.append("%d%*s%s" % (self.level, self.level, '', str)) + +class ODTSheetHandler(handler.ContentHandler): + """ Extract sheet names from content.xml of an ODS file """ + def __init__(self, eater): + self.r = eater + + def startElementNS(self, tag, qname, attrs): + if tag == (TABLENS, 'table'): + sheetname = attrs.get((TABLENS, 'name')) + if sheetname: + self.r.append(sheetname) + +class ODTSlideHandler(handler.ContentHandler): + """ Extract headings from content.xml of an ODT file """ + def __init__(self, eater): + self.r = eater + self.data = [] + self.pagenum = 0 + + def characters(self, data): + self.data.append(data) + + def startElementNS(self, tag, qname, attrs): + if tag == (DRAWNS, 'page'): + self.pagenum = self.pagenum + 1 + self.r.append("SLIDE %d: %s" % ( self.pagenum, attrs.get((DRAWNS, 'name'),''))) + if tag == (TEXTNS, 'p'): + self.data = [] + + def endElementNS(self, tag, qname): + if tag == (TEXTNS, 'p'): + str = ''.join(self.data) + self.data = [] + if len(str) > 0: + self.r.append(" " + str) + +def odtheadings(odtfile): + mimetype = getxmlpart(odtfile,'mimetype') + content = getxmlpart(odtfile,'content.xml') + lines = [] + parser = make_parser() + parser.setFeature(handler.feature_namespaces, 1) + if not isinstance(mimetype, str): + mimetype=mimetype.decode("utf-8") + if mimetype in ('application/vnd.oasis.opendocument.text', + 'application/vnd.oasis.opendocument.text-template'): + parser.setContentHandler(ODTHeadingHandler(lines)) + elif mimetype in ('application/vnd.oasis.opendocument.spreadsheet', + 'application/vnd.oasis.opendocument.spreadsheet-template'): + parser.setContentHandler(ODTSheetHandler(lines)) + elif mimetype in ('application/vnd.oasis.opendocument.presentation' + 'application/vnd.oasis.opendocument.presentation-template'): + parser.setContentHandler(ODTSlideHandler(lines)) + else: + print ("Unsupported fileformat") + sys.exit(2) + parser.setErrorHandler(handler.ErrorHandler()) + + inpsrc = InputSource() + if not isinstance(content, str): + content=content.decode("utf-8") + inpsrc.setByteStream(StringIO(content)) + parser.parse(inpsrc) + return lines + + +if __name__ == "__main__": + filler = " " + for heading in odtheadings(sys.argv[1]): + print (heading) + + + +# Local Variables: *** +# mode: python *** +# End: *** diff --git a/intelliaide/vendor/bin/odfuserfield b/intelliaide/vendor/bin/odfuserfield new file mode 100755 index 0000000..d97c5ea --- /dev/null +++ b/intelliaide/vendor/bin/odfuserfield @@ -0,0 +1,101 @@ +#!/opt/app-root/bin/python3.12 +# -*- coding: utf-8 -*- +# Copyright (C) 2006-2007 Søren Roug, European Environment Agency +# +# This is free software. You may redistribute it under the terms +# of the Apache license and the GNU General Public License Version +# 2 or at your option any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Contributor(s): Michael Howitz, gocept gmbh & co. kg + +import sys +import getopt + +import odf.userfield + +if sys.version_info[0]==3: unicode=str + +listfields = False +Listfields = False +xfields = [] +Xfields = [] +setfields = {} +outputfile = None +inputfile = None + + +def exitwithusage(exitcode=2): + """ print out usage information """ + sys.stderr.write("Usage: %s [-lL] [-xX metafield] [-s metafield:value]... " + "[-o output] [inputfile]\n" % sys.argv[0]) + sys.stderr.write("\tInputfile must be OpenDocument format\n") + sys.exit(exitcode) + + +try: + opts, args = getopt.getopt(sys.argv[1:], "lLs:o:x:X:") +except getopt.GetoptError: + exitwithusage() + +if len(opts) == 0: + exitwithusage() + +for o, a in opts: + if o == '-s': + if a.find(":") >= 0: + k,v = a.split(":",1) + else: + k,v = (a, "") + if len(k) == 0: + exitwithusage() + setfields[unicode(k)] = unicode(v) + if o == '-l': + listfields = True + Listfields = False + if o == '-L': + Listfields = True + listfields = False + if o == "-x": + xfields.append(unicode(a)) + if o == "-X": + Xfields.append(unicode(a)) + if o == "-o": + outputfile = unicode(a) + +if len(args) != 0: + inputfile = unicode(args[0]) + +user_fields = odf.userfield.UserFields(inputfile, outputfile) + +if xfields: + for value in user_fields.list_values(xfields): + print (value) + +if Listfields or Xfields: + if Listfields: + Xfields = None + for field_name, value_type, value in user_fields.list_fields_and_values( + Xfields): + print ("%s#%s:%s" % (field_name, value_type, value)) + +if listfields: + for value in user_fields.list_fields(): + print (value) + +if setfields: + user_fields.update(setfields) + + + +# Local Variables: *** +# mode: python *** +# End: *** diff --git a/intelliaide/vendor/bin/xml2odf b/intelliaide/vendor/bin/xml2odf new file mode 100755 index 0000000..fcc8770 --- /dev/null +++ b/intelliaide/vendor/bin/xml2odf @@ -0,0 +1,241 @@ +#!/opt/app-root/bin/python3.12 +# -*- coding: utf-8 -*- +# Copyright (C) 2006 Søren Roug, European Environment Agency +# +# This is free software. You may redistribute it under the terms +# of the Apache license and the GNU General Public License Version +# 2 or at your option any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# Contributor(s): +# +# + +# OpenDocument can be a complete office document in a single +# XML document. This script will take such a document and create +# a package +import io +import zipfile,time, sys, getopt +import xml.sax, xml.sax.saxutils +from odf import manifest + +class SplitWriter: + def __init__(self): + self.activefiles = [] + self._content = [] + self._meta = [] + self._styles = [] + self._settings = [] + + self.files = {'content': self._content, 'meta': self._meta, + 'styles':self._styles, 'settings': self._settings } + + def write(self, str): + for f in self.activefiles: + f.append(str) + + def activate(self, filename): + file = self.files[filename] + if file not in self.activefiles: + self.activefiles.append(file) + + def deactivate(self, filename): + file = self.files[filename] + if file in self.activefiles: + self.activefiles.remove(file) + +odmimetypes = { + 'application/vnd.oasis.opendocument.text': '.odt', + 'application/vnd.oasis.opendocument.text-template': '.ott', + 'application/vnd.oasis.opendocument.graphics': '.odg', + 'application/vnd.oasis.opendocument.graphics-template': '.otg', + 'application/vnd.oasis.opendocument.presentation': '.odp', + 'application/vnd.oasis.opendocument.presentation-template': '.otp', + 'application/vnd.oasis.opendocument.spreadsheet': '.ods', + 'application/vnd.oasis.opendocument.spreadsheet-template': '.ots', + 'application/vnd.oasis.opendocument.chart': '.odc', + 'application/vnd.oasis.opendocument.chart-template': '.otc', + 'application/vnd.oasis.opendocument.image': '.odi', + 'application/vnd.oasis.opendocument.image-template': '.oti', + 'application/vnd.oasis.opendocument.formula': '.odf', + 'application/vnd.oasis.opendocument.formula-template': '.otf', + 'application/vnd.oasis.opendocument.text-master': '.odm', + 'application/vnd.oasis.opendocument.text-web': '.oth', +} + +OFFICENS = u"urn:oasis:names:tc:opendocument:xmlns:office:1.0" +base = xml.sax.saxutils.XMLGenerator + +class odfsplitter(base): + + def __init__(self): + self._mimetype = '' + self.output = SplitWriter() + self._prefixes = [] + base.__init__(self, self.output, 'utf-8') + + def startPrefixMapping(self, prefix, uri): + base.startPrefixMapping(self, prefix, uri) + self._prefixes.append('xmlns:%s="%s"' % (prefix, uri)) + + def startElementNS(self, name, qname, attrs): + if name == (OFFICENS, u"document"): + self._mimetype = attrs.get((OFFICENS, "mimetype")) + elif name == (OFFICENS, u"meta"): + self.output.activate('meta') + + elif name == (OFFICENS, u"settings"): + self.output.activate('settings') + elif name == (OFFICENS, u"scripts"): + self.output.activate('content') + elif name == (OFFICENS, u"font-face-decls"): + self.output.activate('content') + self.output.activate('styles') + elif name == (OFFICENS, u"styles"): + self.output.activate('styles') + elif name == (OFFICENS, u"automatic-styles"): + self.output.activate('content') + self.output.activate('styles') + elif name == (OFFICENS, u"master-styles"): + self.output.activate('styles') + elif name == (OFFICENS, u"body"): + self.output.activate('content') + base.startElementNS(self, name, qname, attrs) + + def endElementNS(self, name, qname): + base.endElementNS(self, name, qname) + if name == (OFFICENS, u"meta"): + self.output.deactivate('meta') + elif name == (OFFICENS, u"settings"): + self.output.deactivate('settings') + elif name == (OFFICENS, u"scripts"): + self.output.deactivate('content') + elif name == (OFFICENS, u"font-face-decls"): + self.output.deactivate('content') + self.output.deactivate('styles') + elif name == (OFFICENS, u"styles"): + self.output.deactivate('styles') + elif name == (OFFICENS, u"automatic-styles"): + self.output.deactivate('content') + self.output.deactivate('styles') + elif name == (OFFICENS, u"master-styles"): + self.output.deactivate('styles') + elif name == (OFFICENS, u"body"): + self.output.deactivate('content') + + + def content(self): + """ Return the content inside a wrapper called + """ + prefixes = ' '.join(self._prefixes) + return ''.join(['\n' % prefixes] + list(map(lambda x: x.decode("utf-8"), self.output._content)) + ['']) + + def settings(self): + prefixes = ' '.join(self._prefixes).encode('utf-8') + return ''.join( ['\n' % prefixes] + self.output._settings + ['''''']) + + def styles(self): + prefixes = ' '.join(self._prefixes) + return ''.join( ['\n' % prefixes] + list(map(lambda x: x.decode("utf-8"), self.output._styles)) + ['''''']) + + def meta(self): + prefixes = ' '.join(self._prefixes) + return ''.join( ['\n' % prefixes] + list(map(lambda x: x.decode("utf-8"), self.output._meta)) + ['''''']) + +def usage(): + sys.stderr.write("Usage: %s [-o outputfile] [-s] inputfile\n" % sys.argv[0]) + +def manifestxml(m): + """ Generates the content of the manifest.xml file """ + xml=io.StringIO() + xml.write(u"\n") + m.toXml(0,xml) + return xml.getvalue() + +try: + opts, args = getopt.getopt(sys.argv[1:], "o:s", ["output=","suffix"]) +except getopt.GetoptError: + usage() + sys.exit(2) + +outputfile = '-' +addsuffix = False + +for o, a in opts: + if o in ("-o", "--output"): + outputfile = a + if o in ("-s", "--suffix"): + addsuffix = True + +if len(args) > 1: + usage() + sys.exit(2) + +odfs = odfsplitter() +parser = xml.sax.make_parser() +parser.setFeature(xml.sax.handler.feature_namespaces, 1) +parser.setContentHandler(odfs) +if len(args) == 0: + parser.parse(sys.stdin) +else: + parser.parse(open(args[0],"r")) + +mimetype = odfs._mimetype +suffix = odmimetypes.get(mimetype,'.xxx') + +if outputfile == '-': + if sys.stdout.isatty(): + sys.stderr.write("Won't write ODF file to terminal\n") + sys.exit(1) + z = zipfile.ZipFile(sys.stdout,"w") +else: + if addsuffix: + outputfile = outputfile + suffix + z = zipfile.ZipFile(outputfile,"w") + +now = time.localtime()[:6] + +# Write mimetype +zi = zipfile.ZipInfo('mimetype', now) +zi.compress_type = zipfile.ZIP_STORED +z.writestr(zi,mimetype) + +# Write content +zi = zipfile.ZipInfo("content.xml", now) +zi.compress_type = zipfile.ZIP_DEFLATED +z.writestr(zi,odfs.content() ) +# Write styles +zi = zipfile.ZipInfo("styles.xml", now) +zi.compress_type = zipfile.ZIP_DEFLATED +z.writestr(zi,odfs.styles() ) + +# Write meta +zi = zipfile.ZipInfo("meta.xml", now) +zi.compress_type = zipfile.ZIP_DEFLATED +z.writestr(zi,odfs.meta() ) + +m = manifest.Manifest() +m.addElement(manifest.FileEntry(fullpath="/", mediatype=mimetype)) +m.addElement(manifest.FileEntry(fullpath="content.xml",mediatype="text/xml")) +m.addElement(manifest.FileEntry(fullpath="styles.xml", mediatype="text/xml")) +m.addElement(manifest.FileEntry(fullpath="meta.xml", mediatype="text/xml")) + +# Write manifest +zi = zipfile.ZipInfo("META-INF/manifest.xml", now) +zi.compress_type = zipfile.ZIP_DEFLATED +z.writestr(zi, manifestxml(m).encode("utf-8") ) +z.close() + + + +# Local Variables: *** +# mode: python *** +# End: *** diff --git a/intelliaide/vendor/cachetools-4.2.1.dist-info/INSTALLER b/intelliaide/vendor/cachetools-4.2.1.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/intelliaide/vendor/cachetools-4.2.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/intelliaide/vendor/cachetools-4.2.1.dist-info/LICENSE b/intelliaide/vendor/cachetools-4.2.1.dist-info/LICENSE new file mode 100644 index 0000000..0dc1864 --- /dev/null +++ b/intelliaide/vendor/cachetools-4.2.1.dist-info/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014-2020 Thomas Kemmer + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/intelliaide/vendor/cachetools-4.2.1.dist-info/METADATA b/intelliaide/vendor/cachetools-4.2.1.dist-info/METADATA new file mode 100644 index 0000000..2bf1110 --- /dev/null +++ b/intelliaide/vendor/cachetools-4.2.1.dist-info/METADATA @@ -0,0 +1,129 @@ +Metadata-Version: 2.1 +Name: cachetools +Version: 4.2.1 +Summary: Extensible memoizing collections and decorators +Home-page: https://github.com/tkem/cachetools/ +Author: Thomas Kemmer +Author-email: tkemmer@computer.org +License: MIT +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Other Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: ~=3.5 + +cachetools +======================================================================== + +.. image:: http://img.shields.io/pypi/v/cachetools + :target: https://pypi.org/project/cachetools/ + :alt: Latest PyPI version + +.. image:: https://img.shields.io/readthedocs/cachetools + :target: http://cachetools.readthedocs.io/ + :alt: Documentation build status + +.. image:: http://img.shields.io/travis/tkem/cachetools + :target: https://travis-ci.org/tkem/cachetools/ + :alt: Travis CI build status + +.. image:: http://img.shields.io/coveralls/tkem/cachetools + :target: https://coveralls.io/r/tkem/cachetools + :alt: Test coverage + +.. image:: https://img.shields.io/librariesio/sourcerank/pypi/cachetools + :target: https://libraries.io/pypi/cachetools + :alt: Libraries.io SourceRank + +.. image:: https://img.shields.io/github/license/tkem/cachetools + :target: http://raw.github.com/tkem/cachetools/master/LICENSE + :alt: License + +This module provides various memoizing collections and decorators, +including variants of the Python Standard Library's `@lru_cache`_ +function decorator. + +.. code-block:: python + + from cachetools import cached, LRUCache, TTLCache + + # speed up calculating Fibonacci numbers with dynamic programming + @cached(cache={}) + def fib(n): + return n if n < 2 else fib(n - 1) + fib(n - 2) + + # cache least recently used Python Enhancement Proposals + @cached(cache=LRUCache(maxsize=32)) + def get_pep(num): + url = 'http://www.python.org/dev/peps/pep-%04d/' % num + with urllib.request.urlopen(url) as s: + return s.read() + + # cache weather data for no longer than ten minutes + @cached(cache=TTLCache(maxsize=1024, ttl=600)) + def get_weather(place): + return owm.weather_at_place(place).get_weather() + +For the purpose of this module, a *cache* is a mutable_ mapping_ of a +fixed maximum size. When the cache is full, i.e. by adding another +item the cache would exceed its maximum size, the cache must choose +which item(s) to discard based on a suitable `cache algorithm`_. In +general, a cache's size is the total size of its items, and an item's +size is a property or function of its value, e.g. the result of +``sys.getsizeof(value)``. For the trivial but common case that each +item counts as ``1``, a cache's size is equal to the number of its +items, or ``len(cache)``. + +Multiple cache classes based on different caching algorithms are +implemented, and decorators for easily memoizing function and method +calls are provided, too. + + +Installation +------------------------------------------------------------------------ + +cachetools is available from PyPI_ and can be installed by running:: + + pip install cachetools + + +Project Resources +------------------------------------------------------------------------ + +- `Documentation`_ +- `Issue tracker`_ +- `Source code`_ +- `Change log`_ + + +License +------------------------------------------------------------------------ + +Copyright (c) 2014-2021 Thomas Kemmer. + +Licensed under the `MIT License`_. + + +.. _@lru_cache: http://docs.python.org/3/library/functools.html#functools.lru_cache +.. _mutable: http://docs.python.org/dev/glossary.html#term-mutable +.. _mapping: http://docs.python.org/dev/glossary.html#term-mapping +.. _cache algorithm: http://en.wikipedia.org/wiki/Cache_algorithms + +.. _PyPI: https://pypi.org/project/cachetools/ +.. _Documentation: https://cachetools.readthedocs.io/ +.. _Issue tracker: https://github.com/tkem/cachetools/issues/ +.. _Source code: https://github.com/tkem/cachetools/ +.. _Change log: https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst +.. _MIT License: http://raw.github.com/tkem/cachetools/master/LICENSE + + diff --git a/intelliaide/vendor/cachetools-4.2.1.dist-info/RECORD b/intelliaide/vendor/cachetools-4.2.1.dist-info/RECORD new file mode 100644 index 0000000..6503374 --- /dev/null +++ b/intelliaide/vendor/cachetools-4.2.1.dist-info/RECORD @@ -0,0 +1,28 @@ +cachetools-4.2.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +cachetools-4.2.1.dist-info/LICENSE,sha256=WjqbFSk9D0xU0ftRzw9RpxHwz1gvgKDnMwR4ZwwX9ns,1085 +cachetools-4.2.1.dist-info/METADATA,sha256=aL0ZENHXJKzi45nqyW-LcoOfSy_aHLQQ0OslNa0tlM0,4589 +cachetools-4.2.1.dist-info/RECORD,, +cachetools-4.2.1.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92 +cachetools-4.2.1.dist-info/top_level.txt,sha256=ai2FH78TGwoBcCgVfoqbzk5IQCtnDukdSs4zKuVPvDs,11 +cachetools/__init__.py,sha256=LYI0-c9iPqsMCJqnGiR1lo19xfIHy_P-m0y8esT8FM0,462 +cachetools/__pycache__/__init__.cpython-312.pyc,, +cachetools/__pycache__/cache.cpython-312.pyc,, +cachetools/__pycache__/decorators.cpython-312.pyc,, +cachetools/__pycache__/fifo.cpython-312.pyc,, +cachetools/__pycache__/func.cpython-312.pyc,, +cachetools/__pycache__/keys.cpython-312.pyc,, +cachetools/__pycache__/lfu.cpython-312.pyc,, +cachetools/__pycache__/lru.cpython-312.pyc,, +cachetools/__pycache__/mru.cpython-312.pyc,, +cachetools/__pycache__/rr.cpython-312.pyc,, +cachetools/__pycache__/ttl.cpython-312.pyc,, +cachetools/cache.py,sha256=xu3QB-lPJxzL2Ub5uvCSaDbfwWh4Q13qQ29yTnDLhh8,2894 +cachetools/decorators.py,sha256=5QXP11CRMdN6Hg8fwizWJR8gGUICXxzvDzPWRW9TCRk,2969 +cachetools/fifo.py,sha256=fepNDnzche6g7ZT599xPNRrZ5gM_Jte_U7iBHYO975s,946 +cachetools/func.py,sha256=n__zdaSj_KeXrFfLqkGMsQD-TNSbfoazanli6Yq5NAQ,4975 +cachetools/keys.py,sha256=bKwFwU15s-vKWM1lnNdcJWfyQxu7uqIcRRJNg9hUfFg,1466 +cachetools/lfu.py,sha256=UGcnNIK6so7tw1NvlCo1dLHo5YE60K40S-D9g6HOO98,1101 +cachetools/lru.py,sha256=sAxF0E8I0muYEcNHlRsDaWZ0FABk9m_v1-auHGnRV3E,1224 +cachetools/mru.py,sha256=6jZrJ1eJqxqLoqSET11KrOV9WOUSOtTEiaTCKxobLSQ,1244 +cachetools/rr.py,sha256=QEhTqOD0SqyD4oXkkU2tpQ9UWH_mmp500H4qyOck2vA,948 +cachetools/ttl.py,sha256=GAktYcF273bj0M8LCXaVxpBRuA9ga5Sn0fn6awM17gc,5800 diff --git a/intelliaide/vendor/cachetools-4.2.1.dist-info/WHEEL b/intelliaide/vendor/cachetools-4.2.1.dist-info/WHEEL new file mode 100644 index 0000000..b552003 --- /dev/null +++ b/intelliaide/vendor/cachetools-4.2.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/intelliaide/vendor/cachetools-4.2.1.dist-info/top_level.txt b/intelliaide/vendor/cachetools-4.2.1.dist-info/top_level.txt new file mode 100644 index 0000000..50d1408 --- /dev/null +++ b/intelliaide/vendor/cachetools-4.2.1.dist-info/top_level.txt @@ -0,0 +1 @@ +cachetools diff --git a/intelliaide/vendor/cachetools/__init__.py b/intelliaide/vendor/cachetools/__init__.py new file mode 100644 index 0000000..2e66e20 --- /dev/null +++ b/intelliaide/vendor/cachetools/__init__.py @@ -0,0 +1,24 @@ +"""Extensible memoizing collections and decorators.""" + +from .cache import Cache +from .decorators import cached, cachedmethod +from .fifo import FIFOCache +from .lfu import LFUCache +from .lru import LRUCache +from .mru import MRUCache +from .rr import RRCache +from .ttl import TTLCache + +__all__ = ( + 'Cache', + 'FIFOCache', + 'LFUCache', + 'LRUCache', + 'MRUCache', + 'RRCache', + 'TTLCache', + 'cached', + 'cachedmethod' +) + +__version__ = '4.2.1' diff --git a/intelliaide/vendor/cachetools/cache.py b/intelliaide/vendor/cachetools/cache.py new file mode 100644 index 0000000..ed3d268 --- /dev/null +++ b/intelliaide/vendor/cachetools/cache.py @@ -0,0 +1,117 @@ +from collections.abc import MutableMapping + + +class _DefaultSize(object): + + __slots__ = () + + def __getitem__(self, _): + return 1 + + def __setitem__(self, _, value): + assert value == 1 + + def pop(self, _): + return 1 + + +class Cache(MutableMapping): + """Mutable mapping to serve as a simple cache or cache base class.""" + + __marker = object() + + __size = _DefaultSize() + + def __init__(self, maxsize, getsizeof=None): + if getsizeof: + self.getsizeof = getsizeof + if self.getsizeof is not Cache.getsizeof: + self.__size = dict() + self.__data = dict() + self.__currsize = 0 + self.__maxsize = maxsize + + def __repr__(self): + return '%s(%r, maxsize=%r, currsize=%r)' % ( + self.__class__.__name__, + list(self.__data.items()), + self.__maxsize, + self.__currsize, + ) + + def __getitem__(self, key): + try: + return self.__data[key] + except KeyError: + return self.__missing__(key) + + def __setitem__(self, key, value): + maxsize = self.__maxsize + size = self.getsizeof(value) + if size > maxsize: + raise ValueError('value too large') + if key not in self.__data or self.__size[key] < size: + while self.__currsize + size > maxsize: + self.popitem() + if key in self.__data: + diffsize = size - self.__size[key] + else: + diffsize = size + self.__data[key] = value + self.__size[key] = size + self.__currsize += diffsize + + def __delitem__(self, key): + size = self.__size.pop(key) + del self.__data[key] + self.__currsize -= size + + def __contains__(self, key): + return key in self.__data + + def __missing__(self, key): + raise KeyError(key) + + def __iter__(self): + return iter(self.__data) + + def __len__(self): + return len(self.__data) + + def get(self, key, default=None): + if key in self: + return self[key] + else: + return default + + def pop(self, key, default=__marker): + if key in self: + value = self[key] + del self[key] + elif default is self.__marker: + raise KeyError(key) + else: + value = default + return value + + def setdefault(self, key, default=None): + if key in self: + value = self[key] + else: + self[key] = value = default + return value + + @property + def maxsize(self): + """The maximum size of the cache.""" + return self.__maxsize + + @property + def currsize(self): + """The current size of the cache.""" + return self.__currsize + + @staticmethod + def getsizeof(value): + """Return the size of a cache element's value.""" + return 1 diff --git a/intelliaide/vendor/cachetools/decorators.py b/intelliaide/vendor/cachetools/decorators.py new file mode 100644 index 0000000..217b9a8 --- /dev/null +++ b/intelliaide/vendor/cachetools/decorators.py @@ -0,0 +1,88 @@ +import functools + +from .keys import hashkey + + +def cached(cache, key=hashkey, lock=None): + """Decorator to wrap a function with a memoizing callable that saves + results in a cache. + + """ + def decorator(func): + if cache is None: + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + elif lock is None: + def wrapper(*args, **kwargs): + k = key(*args, **kwargs) + try: + return cache[k] + except KeyError: + pass # key not found + v = func(*args, **kwargs) + try: + cache[k] = v + except ValueError: + pass # value too large + return v + else: + def wrapper(*args, **kwargs): + k = key(*args, **kwargs) + try: + with lock: + return cache[k] + except KeyError: + pass # key not found + v = func(*args, **kwargs) + # in case of a race, prefer the item already in the cache + try: + with lock: + return cache.setdefault(k, v) + except ValueError: + return v # value too large + return functools.update_wrapper(wrapper, func) + return decorator + + +def cachedmethod(cache, key=hashkey, lock=None): + """Decorator to wrap a class or instance method with a memoizing + callable that saves results in a cache. + + """ + def decorator(method): + if lock is None: + def wrapper(self, *args, **kwargs): + c = cache(self) + if c is None: + return method(self, *args, **kwargs) + k = key(*args, **kwargs) + try: + return c[k] + except KeyError: + pass # key not found + v = method(self, *args, **kwargs) + try: + c[k] = v + except ValueError: + pass # value too large + return v + else: + def wrapper(self, *args, **kwargs): + c = cache(self) + if c is None: + return method(self, *args, **kwargs) + k = key(*args, **kwargs) + try: + with lock(self): + return c[k] + except KeyError: + pass # key not found + v = method(self, *args, **kwargs) + # in case of a race, prefer the item already in the cache + try: + with lock(self): + return c.setdefault(k, v) + except ValueError: + return v # value too large + return functools.update_wrapper(wrapper, method) + return decorator diff --git a/intelliaide/vendor/cachetools/fifo.py b/intelliaide/vendor/cachetools/fifo.py new file mode 100644 index 0000000..9f254f1 --- /dev/null +++ b/intelliaide/vendor/cachetools/fifo.py @@ -0,0 +1,31 @@ +import collections + +from .cache import Cache + + +class FIFOCache(Cache): + """First In First Out (FIFO) cache implementation.""" + + def __init__(self, maxsize, getsizeof=None): + Cache.__init__(self, maxsize, getsizeof) + self.__order = collections.OrderedDict() + + def __setitem__(self, key, value, cache_setitem=Cache.__setitem__): + cache_setitem(self, key, value) + try: + self.__order.move_to_end(key) + except KeyError: + self.__order[key] = None + + def __delitem__(self, key, cache_delitem=Cache.__delitem__): + cache_delitem(self, key) + del self.__order[key] + + def popitem(self): + """Remove and return the `(key, value)` pair first inserted.""" + try: + key = next(iter(self.__order)) + except StopIteration: + raise KeyError('%s is empty' % type(self).__name__) from None + else: + return (key, self.pop(key)) diff --git a/intelliaide/vendor/cachetools/func.py b/intelliaide/vendor/cachetools/func.py new file mode 100644 index 0000000..2be517e --- /dev/null +++ b/intelliaide/vendor/cachetools/func.py @@ -0,0 +1,176 @@ +"""`functools.lru_cache` compatible memoizing function decorators.""" + +import collections +import functools +import math +import random +import time + +try: + from threading import RLock +except ImportError: # pragma: no cover + from dummy_threading import RLock + +from . import keys +from .fifo import FIFOCache +from .lfu import LFUCache +from .lru import LRUCache +from .mru import MRUCache +from .rr import RRCache +from .ttl import TTLCache + +__all__ = ('lfu_cache', 'lru_cache', 'mru_cache', 'rr_cache', 'ttl_cache') + + +_CacheInfo = collections.namedtuple('CacheInfo', [ + 'hits', 'misses', 'maxsize', 'currsize' +]) + + +class _UnboundCache(dict): + + @property + def maxsize(self): + return None + + @property + def currsize(self): + return len(self) + + +class _UnboundTTLCache(TTLCache): + def __init__(self, ttl, timer): + TTLCache.__init__(self, math.inf, ttl, timer) + + @property + def maxsize(self): + return None + + +def _cache(cache, typed): + maxsize = cache.maxsize + + def decorator(func): + key = keys.typedkey if typed else keys.hashkey + lock = RLock() + stats = [0, 0] + + def wrapper(*args, **kwargs): + k = key(*args, **kwargs) + with lock: + try: + v = cache[k] + stats[0] += 1 + return v + except KeyError: + stats[1] += 1 + v = func(*args, **kwargs) + # in case of a race, prefer the item already in the cache + try: + with lock: + return cache.setdefault(k, v) + except ValueError: + return v # value too large + + def cache_info(): + with lock: + hits, misses = stats + maxsize = cache.maxsize + currsize = cache.currsize + return _CacheInfo(hits, misses, maxsize, currsize) + + def cache_clear(): + with lock: + try: + cache.clear() + finally: + stats[:] = [0, 0] + + wrapper.cache_info = cache_info + wrapper.cache_clear = cache_clear + wrapper.cache_parameters = lambda: {'maxsize': maxsize, 'typed': typed} + functools.update_wrapper(wrapper, func) + return wrapper + return decorator + + +def fifo_cache(maxsize=128, typed=False): + """Decorator to wrap a function with a memoizing callable that saves + up to `maxsize` results based on a First In First Out (FIFO) + algorithm. + + """ + if maxsize is None: + return _cache(_UnboundCache(), typed) + elif callable(maxsize): + return _cache(FIFOCache(128), typed)(maxsize) + else: + return _cache(FIFOCache(maxsize), typed) + + +def lfu_cache(maxsize=128, typed=False): + """Decorator to wrap a function with a memoizing callable that saves + up to `maxsize` results based on a Least Frequently Used (LFU) + algorithm. + + """ + if maxsize is None: + return _cache(_UnboundCache(), typed) + elif callable(maxsize): + return _cache(LFUCache(128), typed)(maxsize) + else: + return _cache(LFUCache(maxsize), typed) + + +def lru_cache(maxsize=128, typed=False): + """Decorator to wrap a function with a memoizing callable that saves + up to `maxsize` results based on a Least Recently Used (LRU) + algorithm. + + """ + if maxsize is None: + return _cache(_UnboundCache(), typed) + elif callable(maxsize): + return _cache(LRUCache(128), typed)(maxsize) + else: + return _cache(LRUCache(maxsize), typed) + + +def mru_cache(maxsize=128, typed=False): + """Decorator to wrap a function with a memoizing callable that saves + up to `maxsize` results based on a Most Recently Used (MRU) + algorithm. + """ + if maxsize is None: + return _cache(_UnboundCache(), typed) + elif callable(maxsize): + return _cache(MRUCache(128), typed)(maxsize) + else: + return _cache(MRUCache(maxsize), typed) + + +def rr_cache(maxsize=128, choice=random.choice, typed=False): + """Decorator to wrap a function with a memoizing callable that saves + up to `maxsize` results based on a Random Replacement (RR) + algorithm. + + """ + if maxsize is None: + return _cache(_UnboundCache(), typed) + elif callable(maxsize): + return _cache(RRCache(128, choice), typed)(maxsize) + else: + return _cache(RRCache(maxsize, choice), typed) + + +def ttl_cache(maxsize=128, ttl=600, timer=time.monotonic, typed=False): + """Decorator to wrap a function with a memoizing callable that saves + up to `maxsize` results based on a Least Recently Used (LRU) + algorithm with a per-item time-to-live (TTL) value. + """ + if maxsize is None: + return _cache(_UnboundTTLCache(ttl, timer), typed) + elif callable(maxsize): + return _cache(TTLCache(128, ttl, timer), typed)(maxsize) + else: + return _cache(TTLCache(maxsize, ttl, timer), typed) diff --git a/intelliaide/vendor/cachetools/keys.py b/intelliaide/vendor/cachetools/keys.py new file mode 100644 index 0000000..355d742 --- /dev/null +++ b/intelliaide/vendor/cachetools/keys.py @@ -0,0 +1,52 @@ +"""Key functions for memoizing decorators.""" + +__all__ = ('hashkey', 'typedkey') + + +class _HashedTuple(tuple): + """A tuple that ensures that hash() will be called no more than once + per element, since cache decorators will hash the key multiple + times on a cache miss. See also _HashedSeq in the standard + library functools implementation. + + """ + + __hashvalue = None + + def __hash__(self, hash=tuple.__hash__): + hashvalue = self.__hashvalue + if hashvalue is None: + self.__hashvalue = hashvalue = hash(self) + return hashvalue + + def __add__(self, other, add=tuple.__add__): + return _HashedTuple(add(self, other)) + + def __radd__(self, other, add=tuple.__add__): + return _HashedTuple(add(other, self)) + + def __getstate__(self): + return {} + + +# used for separating keyword arguments; we do not use an object +# instance here so identity is preserved when pickling/unpickling +_kwmark = (_HashedTuple,) + + +def hashkey(*args, **kwargs): + """Return a cache key for the specified hashable arguments.""" + + if kwargs: + return _HashedTuple(args + sum(sorted(kwargs.items()), _kwmark)) + else: + return _HashedTuple(args) + + +def typedkey(*args, **kwargs): + """Return a typed cache key for the specified hashable arguments.""" + + key = hashkey(*args, **kwargs) + key += tuple(type(v) for v in args) + key += tuple(type(v) for _, v in sorted(kwargs.items())) + return key diff --git a/intelliaide/vendor/cachetools/lfu.py b/intelliaide/vendor/cachetools/lfu.py new file mode 100644 index 0000000..894a326 --- /dev/null +++ b/intelliaide/vendor/cachetools/lfu.py @@ -0,0 +1,34 @@ +import collections + +from .cache import Cache + + +class LFUCache(Cache): + """Least Frequently Used (LFU) cache implementation.""" + + def __init__(self, maxsize, getsizeof=None): + Cache.__init__(self, maxsize, getsizeof) + self.__counter = collections.Counter() + + def __getitem__(self, key, cache_getitem=Cache.__getitem__): + value = cache_getitem(self, key) + if key in self: # __missing__ may not store item + self.__counter[key] -= 1 + return value + + def __setitem__(self, key, value, cache_setitem=Cache.__setitem__): + cache_setitem(self, key, value) + self.__counter[key] -= 1 + + def __delitem__(self, key, cache_delitem=Cache.__delitem__): + cache_delitem(self, key) + del self.__counter[key] + + def popitem(self): + """Remove and return the `(key, value)` pair least frequently used.""" + try: + (key, _), = self.__counter.most_common(1) + except ValueError: + raise KeyError('%s is empty' % type(self).__name__) from None + else: + return (key, self.pop(key)) diff --git a/intelliaide/vendor/cachetools/lru.py b/intelliaide/vendor/cachetools/lru.py new file mode 100644 index 0000000..33749d1 --- /dev/null +++ b/intelliaide/vendor/cachetools/lru.py @@ -0,0 +1,40 @@ +import collections + +from .cache import Cache + + +class LRUCache(Cache): + """Least Recently Used (LRU) cache implementation.""" + + def __init__(self, maxsize, getsizeof=None): + Cache.__init__(self, maxsize, getsizeof) + self.__order = collections.OrderedDict() + + def __getitem__(self, key, cache_getitem=Cache.__getitem__): + value = cache_getitem(self, key) + if key in self: # __missing__ may not store item + self.__update(key) + return value + + def __setitem__(self, key, value, cache_setitem=Cache.__setitem__): + cache_setitem(self, key, value) + self.__update(key) + + def __delitem__(self, key, cache_delitem=Cache.__delitem__): + cache_delitem(self, key) + del self.__order[key] + + def popitem(self): + """Remove and return the `(key, value)` pair least recently used.""" + try: + key = next(iter(self.__order)) + except StopIteration: + raise KeyError('%s is empty' % type(self).__name__) from None + else: + return (key, self.pop(key)) + + def __update(self, key): + try: + self.__order.move_to_end(key) + except KeyError: + self.__order[key] = None diff --git a/intelliaide/vendor/cachetools/mru.py b/intelliaide/vendor/cachetools/mru.py new file mode 100644 index 0000000..41e2aa1 --- /dev/null +++ b/intelliaide/vendor/cachetools/mru.py @@ -0,0 +1,40 @@ +import collections + +from cachetools.cache import Cache + + +class MRUCache(Cache): + """Most Recently Used (MRU) cache implementation.""" + + def __init__(self, maxsize, getsizeof=None): + Cache.__init__(self, maxsize, getsizeof) + self.__order = collections.OrderedDict() + + def __getitem__(self, key, cache_getitem=Cache.__getitem__): + value = cache_getitem(self, key) + if key in self: # __missing__ may not store item + self.__update(key) + return value + + def __setitem__(self, key, value, cache_setitem=Cache.__setitem__): + cache_setitem(self, key, value) + self.__update(key) + + def __delitem__(self, key, cache_delitem=Cache.__delitem__): + cache_delitem(self, key) + del self.__order[key] + + def popitem(self): + """Remove and return the `(key, value)` pair most recently used.""" + try: + key = next(iter(self.__order)) + except StopIteration: + raise KeyError('%s is empty' % type(self).__name__) from None + else: + return (key, self.pop(key)) + + def __update(self, key): + try: + self.__order.move_to_end(key, last=False) + except KeyError: + self.__order[key] = None diff --git a/intelliaide/vendor/cachetools/rr.py b/intelliaide/vendor/cachetools/rr.py new file mode 100644 index 0000000..5b47e87 --- /dev/null +++ b/intelliaide/vendor/cachetools/rr.py @@ -0,0 +1,34 @@ +import random + +from .cache import Cache + + +# random.choice cannot be pickled in Python 2.7 +def _choice(seq): + return random.choice(seq) + + +class RRCache(Cache): + """Random Replacement (RR) cache implementation.""" + + def __init__(self, maxsize, choice=random.choice, getsizeof=None): + Cache.__init__(self, maxsize, getsizeof) + # TODO: use None as default, assing to self.choice directly? + if choice is random.choice: + self.__choice = _choice + else: + self.__choice = choice + + @property + def choice(self): + """The `choice` function used by the cache.""" + return self.__choice + + def popitem(self): + """Remove and return a random `(key, value)` pair.""" + try: + key = self.__choice(list(self)) + except IndexError: + raise KeyError('%s is empty' % type(self).__name__) from None + else: + return (key, self.pop(key)) diff --git a/intelliaide/vendor/cachetools/ttl.py b/intelliaide/vendor/cachetools/ttl.py new file mode 100644 index 0000000..528a085 --- /dev/null +++ b/intelliaide/vendor/cachetools/ttl.py @@ -0,0 +1,208 @@ +import collections +import time + +from .cache import Cache + + +class _Link(object): + + __slots__ = ('key', 'expire', 'next', 'prev') + + def __init__(self, key=None, expire=None): + self.key = key + self.expire = expire + + def __reduce__(self): + return _Link, (self.key, self.expire) + + def unlink(self): + next = self.next + prev = self.prev + prev.next = next + next.prev = prev + + +class _Timer(object): + + def __init__(self, timer): + self.__timer = timer + self.__nesting = 0 + + def __call__(self): + if self.__nesting == 0: + return self.__timer() + else: + return self.__time + + def __enter__(self): + if self.__nesting == 0: + self.__time = time = self.__timer() + else: + time = self.__time + self.__nesting += 1 + return time + + def __exit__(self, *exc): + self.__nesting -= 1 + + def __reduce__(self): + return _Timer, (self.__timer,) + + def __getattr__(self, name): + return getattr(self.__timer, name) + + +class TTLCache(Cache): + """LRU Cache implementation with per-item time-to-live (TTL) value.""" + + def __init__(self, maxsize, ttl, timer=time.monotonic, getsizeof=None): + Cache.__init__(self, maxsize, getsizeof) + self.__root = root = _Link() + root.prev = root.next = root + self.__links = collections.OrderedDict() + self.__timer = _Timer(timer) + self.__ttl = ttl + + def __contains__(self, key): + try: + link = self.__links[key] # no reordering + except KeyError: + return False + else: + return not (link.expire < self.__timer()) + + def __getitem__(self, key, cache_getitem=Cache.__getitem__): + try: + link = self.__getlink(key) + except KeyError: + expired = False + else: + expired = link.expire < self.__timer() + if expired: + return self.__missing__(key) + else: + return cache_getitem(self, key) + + def __setitem__(self, key, value, cache_setitem=Cache.__setitem__): + with self.__timer as time: + self.expire(time) + cache_setitem(self, key, value) + try: + link = self.__getlink(key) + except KeyError: + self.__links[key] = link = _Link(key) + else: + link.unlink() + link.expire = time + self.__ttl + link.next = root = self.__root + link.prev = prev = root.prev + prev.next = root.prev = link + + def __delitem__(self, key, cache_delitem=Cache.__delitem__): + cache_delitem(self, key) + link = self.__links.pop(key) + link.unlink() + if link.expire < self.__timer(): + raise KeyError(key) + + def __iter__(self): + root = self.__root + curr = root.next + while curr is not root: + # "freeze" time for iterator access + with self.__timer as time: + if not (curr.expire < time): + yield curr.key + curr = curr.next + + def __len__(self): + root = self.__root + curr = root.next + time = self.__timer() + count = len(self.__links) + while curr is not root and curr.expire < time: + count -= 1 + curr = curr.next + return count + + def __setstate__(self, state): + self.__dict__.update(state) + root = self.__root + root.prev = root.next = root + for link in sorted(self.__links.values(), key=lambda obj: obj.expire): + link.next = root + link.prev = prev = root.prev + prev.next = root.prev = link + self.expire(self.__timer()) + + def __repr__(self, cache_repr=Cache.__repr__): + with self.__timer as time: + self.expire(time) + return cache_repr(self) + + @property + def currsize(self): + with self.__timer as time: + self.expire(time) + return super(TTLCache, self).currsize + + @property + def timer(self): + """The timer function used by the cache.""" + return self.__timer + + @property + def ttl(self): + """The time-to-live value of the cache's items.""" + return self.__ttl + + def expire(self, time=None): + """Remove expired items from the cache.""" + if time is None: + time = self.__timer() + root = self.__root + curr = root.next + links = self.__links + cache_delitem = Cache.__delitem__ + while curr is not root and curr.expire < time: + cache_delitem(self, curr.key) + del links[curr.key] + next = curr.next + curr.unlink() + curr = next + + def clear(self): + with self.__timer as time: + self.expire(time) + Cache.clear(self) + + def get(self, *args, **kwargs): + with self.__timer: + return Cache.get(self, *args, **kwargs) + + def pop(self, *args, **kwargs): + with self.__timer: + return Cache.pop(self, *args, **kwargs) + + def setdefault(self, *args, **kwargs): + with self.__timer: + return Cache.setdefault(self, *args, **kwargs) + + def popitem(self): + """Remove and return the `(key, value)` pair least recently used that + has not already expired. + + """ + with self.__timer as time: + self.expire(time) + try: + key = next(iter(self.__links)) + except StopIteration: + raise KeyError('%s is empty' % type(self).__name__) from None + else: + return (key, self.pop(key)) + + def __getlink(self, key): + value = self.__links[key] + self.__links.move_to_end(key) + return value diff --git a/intelliaide/vendor/certifi-2026.5.20.dist-info/INSTALLER b/intelliaide/vendor/certifi-2026.5.20.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/intelliaide/vendor/certifi-2026.5.20.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/intelliaide/vendor/certifi-2026.5.20.dist-info/METADATA b/intelliaide/vendor/certifi-2026.5.20.dist-info/METADATA new file mode 100644 index 0000000..7b7bb30 --- /dev/null +++ b/intelliaide/vendor/certifi-2026.5.20.dist-info/METADATA @@ -0,0 +1,78 @@ +Metadata-Version: 2.4 +Name: certifi +Version: 2026.5.20 +Summary: Python package for providing Mozilla's CA Bundle. +Home-page: https://github.com/certifi/python-certifi +Author: Kenneth Reitz +Author-email: me@kennethreitz.com +License: MPL-2.0 +Project-URL: Source, https://github.com/certifi/python-certifi +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) +Classifier: Natural Language :: English +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Requires-Python: >=3.7 +License-File: LICENSE +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: home-page +Dynamic: license +Dynamic: license-file +Dynamic: project-url +Dynamic: requires-python +Dynamic: summary + +Certifi: Python SSL Certificates +================================ + +Certifi provides Mozilla's carefully curated collection of Root Certificates for +validating the trustworthiness of SSL certificates while verifying the identity +of TLS hosts. It has been extracted from the `Requests`_ project. + +Installation +------------ + +``certifi`` is available on PyPI. Simply install it with ``pip``:: + + $ pip install certifi + +Usage +----- + +To reference the installed certificate authority (CA) bundle, you can use the +built-in function:: + + >>> import certifi + + >>> certifi.where() + '/usr/local/lib/python3.7/site-packages/certifi/cacert.pem' + +Or from the command line:: + + $ python -m certifi + /usr/local/lib/python3.7/site-packages/certifi/cacert.pem + +Enjoy! + +.. _`Requests`: https://requests.readthedocs.io/en/master/ + +Addition/Removal of Certificates +-------------------------------- + +Certifi does not support any addition/removal or other modification of the +CA trust store content. This project is intended to provide a reliable and +highly portable root of trust to python deployments. Look to upstream projects +for methods to use alternate trust. diff --git a/intelliaide/vendor/certifi-2026.5.20.dist-info/RECORD b/intelliaide/vendor/certifi-2026.5.20.dist-info/RECORD new file mode 100644 index 0000000..c69462d --- /dev/null +++ b/intelliaide/vendor/certifi-2026.5.20.dist-info/RECORD @@ -0,0 +1,14 @@ +certifi-2026.5.20.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +certifi-2026.5.20.dist-info/METADATA,sha256=HDiCa8lfd0TGlQFongDjoaLJOhWJiJN84y707Vwd8PY,2474 +certifi-2026.5.20.dist-info/RECORD,, +certifi-2026.5.20.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91 +certifi-2026.5.20.dist-info/licenses/LICENSE,sha256=6TcW2mucDVpKHfYP5pWzcPBpVgPSH2-D8FPkLPwQyvc,989 +certifi-2026.5.20.dist-info/top_level.txt,sha256=KMu4vUCfsjLrkPbSNdgdekS-pVJzBAJFO__nI8NF6-U,8 +certifi/__init__.py,sha256=-8ljc7w7fyi5Kyi06c1_L0lG_kD_J0FvbUTa8mEpdzQ,94 +certifi/__main__.py,sha256=xBBoj905TUWBLRGANOcf7oi6e-3dMP4cEoG9OyMs11g,243 +certifi/__pycache__/__init__.cpython-312.pyc,, +certifi/__pycache__/__main__.cpython-312.pyc,, +certifi/__pycache__/core.cpython-312.pyc,, +certifi/cacert.pem,sha256=fO9UDGrzvwo8fQBwvT_XquNPiqgFtAjRwnoByK6zBd8,236095 +certifi/core.py,sha256=XFXycndG5pf37ayeF8N32HUuDafsyhkVMbO4BAPWHa0,3394 +certifi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/intelliaide/vendor/certifi-2026.5.20.dist-info/WHEEL b/intelliaide/vendor/certifi-2026.5.20.dist-info/WHEEL new file mode 100644 index 0000000..14a883f --- /dev/null +++ b/intelliaide/vendor/certifi-2026.5.20.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (82.0.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/intelliaide/vendor/certifi-2026.5.20.dist-info/licenses/LICENSE b/intelliaide/vendor/certifi-2026.5.20.dist-info/licenses/LICENSE new file mode 100644 index 0000000..62b076c --- /dev/null +++ b/intelliaide/vendor/certifi-2026.5.20.dist-info/licenses/LICENSE @@ -0,0 +1,20 @@ +This package contains a modified version of ca-bundle.crt: + +ca-bundle.crt -- Bundle of CA Root Certificates + +This is a bundle of X.509 certificates of public Certificate Authorities +(CA). These were automatically extracted from Mozilla's root certificates +file (certdata.txt). This file can be found in the mozilla source tree: +https://hg.mozilla.org/mozilla-central/file/tip/security/nss/lib/ckfw/builtins/certdata.txt +It contains the certificates in PEM format and therefore +can be directly used with curl / libcurl / php_curl, or with +an Apache+mod_ssl webserver for SSL client authentication. +Just configure this file as the SSLCACertificateFile.# + +***** BEGIN LICENSE BLOCK ***** +This Source Code Form is subject to the terms of the Mozilla Public License, +v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain +one at http://mozilla.org/MPL/2.0/. + +***** END LICENSE BLOCK ***** +@(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $ diff --git a/intelliaide/vendor/certifi-2026.5.20.dist-info/top_level.txt b/intelliaide/vendor/certifi-2026.5.20.dist-info/top_level.txt new file mode 100644 index 0000000..963eac5 --- /dev/null +++ b/intelliaide/vendor/certifi-2026.5.20.dist-info/top_level.txt @@ -0,0 +1 @@ +certifi diff --git a/intelliaide/vendor/certifi/__init__.py b/intelliaide/vendor/certifi/__init__.py new file mode 100644 index 0000000..004dd55 --- /dev/null +++ b/intelliaide/vendor/certifi/__init__.py @@ -0,0 +1,4 @@ +from .core import contents, where + +__all__ = ["contents", "where"] +__version__ = "2026.05.20" diff --git a/intelliaide/vendor/certifi/__main__.py b/intelliaide/vendor/certifi/__main__.py new file mode 100644 index 0000000..8945b5d --- /dev/null +++ b/intelliaide/vendor/certifi/__main__.py @@ -0,0 +1,12 @@ +import argparse + +from certifi import contents, where + +parser = argparse.ArgumentParser() +parser.add_argument("-c", "--contents", action="store_true") +args = parser.parse_args() + +if args.contents: + print(contents()) +else: + print(where()) diff --git a/intelliaide/vendor/certifi/cacert.pem b/intelliaide/vendor/certifi/cacert.pem new file mode 100644 index 0000000..6faad41 --- /dev/null +++ b/intelliaide/vendor/certifi/cacert.pem @@ -0,0 +1,3892 @@ + +# Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited +# Label: "COMODO ECC Certification Authority" +# Serial: 41578283867086692638256921589707938090 +# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23 +# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11 +# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7 +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT +IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw +MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy +ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N +T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR +FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J +cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW +BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm +fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv +GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +# Issuer: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) +# Subject: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) +# Label: "NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny" +# Serial: 80544274841616 +# MD5 Fingerprint: c5:a1:b7:ff:73:dd:d6:d7:34:32:18:df:fc:3c:ad:88 +# SHA1 Fingerprint: 06:08:3f:59:3f:15:a1:04:a0:69:a4:6b:a9:03:d0:06:b7:97:09:91 +# SHA256 Fingerprint: 6c:61:da:c3:a2:de:f0:31:50:6b:e0:36:d2:a6:fe:40:19:94:fb:d1:3d:f9:c8:d4:66:59:92:74:c4:46:ec:98 +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG +EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 +MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl +cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR +dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB +pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM +b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm +aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz +IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT +lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz +AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 +VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG +ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 +BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG +AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M +U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh +bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C ++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F +uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 +XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +# Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Label: "Microsec e-Szigno Root CA 2009" +# Serial: 14014712776195784473 +# MD5 Fingerprint: f8:49:f4:03:bc:44:2d:83:be:48:69:7d:29:64:fc:b1 +# SHA1 Fingerprint: 89:df:74:fe:5c:f4:0f:4a:80:f9:e3:37:7d:54:da:91:e1:01:31:8e +# SHA256 Fingerprint: 3c:5f:81:fe:a5:fa:b8:2c:64:bf:a2:ea:ec:af:cd:e8:e0:77:fc:86:20:a7:ca:e5:37:16:3d:f3:6e:db:f3:78 +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G +CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y +OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx +FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp +Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP +kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc +cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U +fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 +N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC +xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 ++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM +Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG +SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h +mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk +ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c +2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t +HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Label: "GlobalSign Root CA - R3" +# Serial: 4835703278459759426209954 +# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28 +# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad +# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 +MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 +RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT +gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm +KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd +QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ +XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o +LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU +RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp +jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK +6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX +mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs +Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH +WD9f +-----END CERTIFICATE----- + +# Issuer: CN=Izenpe.com O=IZENPE S.A. +# Subject: CN=Izenpe.com O=IZENPE S.A. +# Label: "Izenpe.com" +# Serial: 917563065490389241595536686991402621 +# MD5 Fingerprint: a6:b0:cd:85:80:da:5c:50:34:a3:39:90:2f:55:67:73 +# SHA1 Fingerprint: 2f:78:3d:25:52:18:a7:4a:65:39:71:b5:2c:a2:9c:45:15:6f:e9:19 +# SHA256 Fingerprint: 25:30:cc:8e:98:32:15:02:ba:d9:6f:9b:1f:ba:1b:09:9e:2d:29:9e:0f:45:48:bb:91:4f:36:3b:c0:d4:53:1f +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 +MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6 +ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD +VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j +b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq +scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO +xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H +LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX +uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD +yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+ +JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q +rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN +BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L +hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB +QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+ +HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu +Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg +QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB +BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA +A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb +laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56 +awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo +JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw +LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT +VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk +LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb +UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/ +QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+ +naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls +QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Label: "Go Daddy Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01 +# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b +# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT +EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp +ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH +EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE +AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD +E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH +/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy +DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh +GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR +tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA +AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX +WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu +9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr +gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo +2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI +4uJEvlz36hz1 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96 +# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e +# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5 +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw +MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp +Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg +nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 +HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N +Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN +dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 +HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G +CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU +sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 +4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg +8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 +mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Services Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 17:35:74:af:7b:61:1c:eb:f4:f9:3c:e2:ee:40:f9:a2 +# SHA1 Fingerprint: 92:5a:8f:8d:2c:6d:04:e0:66:5f:59:6a:ff:22:d8:63:e8:25:6f:3f +# SHA256 Fingerprint: 56:8d:69:05:a2:c8:87:08:a4:b3:02:51:90:ed:cf:ed:b1:97:4a:60:6a:13:c6:e5:29:0f:cb:2a:e6:3e:da:b5 +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs +ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD +VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy +ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy +dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p +OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 +8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K +Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe +hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk +6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q +AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI +bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB +ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z +qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn +0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN +sSi6 +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA" +# Serial: 279744 +# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78 +# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e +# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM +MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D +ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU +cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 +WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg +Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw +IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH +UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM +TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU +BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM +kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x +AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV +HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y +sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL +I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 +J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY +VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Label: "TWCA Root Certification Authority" +# Serial: 1 +# MD5 Fingerprint: aa:08:8f:f6:f9:7b:b7:f2:b1:a7:1e:9b:ea:ea:bd:79 +# SHA1 Fingerprint: cf:9e:87:6d:d3:eb:fc:42:26:97:a3:b5:a3:7a:a0:76:a9:06:23:48 +# SHA256 Fingerprint: bf:d8:8f:e1:10:1c:41:ae:3e:80:1b:f8:be:56:35:0e:e9:ba:d1:a6:b9:bd:51:5e:dc:5c:6d:5b:87:11:ac:44 +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES +MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU +V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz +WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO +LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE +AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH +K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX +RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z +rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx +3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq +hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC +MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls +XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D +lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn +aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ +YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Label: "Security Communication RootCA2" +# Serial: 0 +# MD5 Fingerprint: 6c:39:7d:a4:0e:55:59:b2:3f:d6:41:b1:12:50:de:43 +# SHA1 Fingerprint: 5f:3b:8c:f2:f8:10:b3:7d:78:b4:ce:ec:19:19:c3:73:34:b9:c7:74 +# SHA256 Fingerprint: 51:3b:2c:ec:b8:10:d4:cd:e5:dd:85:39:1a:df:c6:c2:dd:60:d8:7b:b7:36:d2:b5:21:48:4a:a4:7a:0e:be:f6 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl +MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe +U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX +DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy +dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj +YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV +OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr +zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM +VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ +hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO +ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw +awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs +OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF +coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc +okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 +t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy +1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ +SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +# Issuer: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 +# Subject: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 +# Label: "Actalis Authentication Root CA" +# Serial: 6271844772424770508 +# MD5 Fingerprint: 69:c1:0d:4f:07:a3:1b:c3:fe:56:3d:04:bc:11:f6:a6 +# SHA1 Fingerprint: f3:73:b3:87:06:5a:28:84:8a:f2:f3:4a:ce:19:2b:dd:c7:8e:9c:ac +# SHA256 Fingerprint: 55:92:60:84:ec:96:3a:64:b9:6e:2a:be:01:ce:0b:a8:6a:64:fb:fe:bc:c7:aa:b5:af:c1:55:b3:7f:d7:60:66 +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE +BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w +MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC +SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 +ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv +UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX +4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 +KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ +gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb +rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ +51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F +be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe +KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F +v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn +fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 +jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz +ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL +e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 +jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz +WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V +SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j +pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX +X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok +fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R +K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU +ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU +LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT +LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 2 Root CA" +# Serial: 2 +# MD5 Fingerprint: 46:a7:d2:fe:45:fb:64:5a:a8:59:90:9b:78:44:9b:29 +# SHA1 Fingerprint: 49:0a:75:74:de:87:0a:47:fe:58:ee:f6:c7:6b:eb:c6:0b:12:40:99 +# SHA256 Fingerprint: 9a:11:40:25:19:7c:5b:b9:5d:94:e6:3d:55:cd:43:79:08:47:b6:46:b2:3c:df:11:ad:a4:a0:0e:ff:15:fb:48 +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr +6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV +L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 +1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx +MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ +QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB +arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr +Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi +FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS +P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN +9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz +uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h +9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t +OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo ++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 +KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 +DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us +H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ +I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 +5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h +3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz +Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 3 Root CA" +# Serial: 2 +# MD5 Fingerprint: 3d:3b:18:9e:2c:64:5a:e8:d5:88:ce:0e:f9:37:c2:ec +# SHA1 Fingerprint: da:fa:f7:fa:66:84:ec:06:8f:14:50:bd:c7:c2:81:a5:bc:a9:64:57 +# SHA256 Fingerprint: ed:f7:eb:bc:a2:7a:2a:38:4d:38:7b:7d:40:10:c6:66:e2:ed:b4:84:3e:4c:29:b4:ae:1d:5b:93:32:e6:b2:4d +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y +ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E +N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 +tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX +0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c +/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X +KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY +zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS +O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D +34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP +K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv +Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj +QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS +IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 +HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa +O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv +033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u +dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE +kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 +3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD +u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq +4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 3" +# Serial: 1 +# MD5 Fingerprint: ca:fb:40:a8:4e:39:92:8a:1d:fe:8e:2f:c4:27:ea:ef +# SHA1 Fingerprint: 55:a6:72:3e:cb:f2:ec:cd:c3:23:74:70:19:9d:2a:be:11:e3:81:d1 +# SHA256 Fingerprint: fd:73:da:d3:1c:64:4f:f1:b4:3b:ef:0c:cd:da:96:71:0b:9c:d9:87:5e:ca:7e:31:70:7a:f3:e9:6d:52:2b:bd +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN +8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ +RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 +hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 +ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM +EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 +A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy +WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ +1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 +6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT +91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p +TpPDpFQUWw== +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 2009" +# Serial: 623603 +# MD5 Fingerprint: cd:e0:25:69:8d:47:ac:9c:89:35:90:f7:fd:51:3d:2f +# SHA1 Fingerprint: 58:e8:ab:b0:36:15:33:fb:80:f7:9b:1b:6d:29:d3:ff:8d:5f:00:f0 +# SHA256 Fingerprint: 49:e7:a4:42:ac:f0:ea:62:87:05:00:54:b5:25:64:b6:50:e4:f4:9e:42:e3:48:d6:aa:38:e0:39:e9:57:b1:c1 +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha +ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM +HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 +UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 +tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R +ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM +lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp +/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G +A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy +MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl +cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js +L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL +BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni +acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K +zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 +PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y +Johw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 EV 2009" +# Serial: 623604 +# MD5 Fingerprint: aa:c6:43:2c:5e:2d:cd:c4:34:c0:50:4f:11:02:4f:b6 +# SHA1 Fingerprint: 96:c9:1b:0b:95:b4:10:98:42:fa:d0:d8:22:79:fe:60:fa:b9:16:83 +# SHA256 Fingerprint: ee:c5:49:6b:98:8c:e9:86:25:b9:34:09:2e:ec:29:08:be:d0:b0:f3:16:c2:d4:73:0c:84:ea:f1:f3:d3:48:81 +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw +NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV +BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn +ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 +3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z +qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR +p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 +HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw +ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea +HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw +Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh +c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E +RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt +dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku +Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp +3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF +CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na +xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX +KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +# Issuer: CN=CA Disig Root R2 O=Disig a.s. +# Subject: CN=CA Disig Root R2 O=Disig a.s. +# Label: "CA Disig Root R2" +# Serial: 10572350602393338211 +# MD5 Fingerprint: 26:01:fb:d8:27:a7:17:9a:45:54:38:1a:43:01:3b:03 +# SHA1 Fingerprint: b5:61:eb:ea:a4:de:e4:25:4b:69:1a:98:a5:57:47:c2:34:c7:d9:71 +# SHA256 Fingerprint: e2:3d:4a:03:6d:7b:70:e9:f5:95:b1:42:20:79:d2:b9:1e:df:bb:1f:b6:51:a0:63:3e:aa:8a:9d:c5:f8:07:03 +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV +BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu +MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy +MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx +EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe +NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH +PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I +x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe +QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR +yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO +QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 +H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ +QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD +i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs +nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 +rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI +hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf +GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb +lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka ++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal +TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i +nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 +gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr +G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os +zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x +L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +# Issuer: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Subject: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Label: "ACCVRAIZ1" +# Serial: 6828503384748696800 +# MD5 Fingerprint: d0:a0:5a:ee:05:b6:09:94:21:a1:7d:f1:b2:29:82:02 +# SHA1 Fingerprint: 93:05:7a:88:15:c6:4f:ce:88:2f:fa:91:16:52:28:78:bc:53:64:17 +# SHA256 Fingerprint: 9a:6e:c0:12:e1:a7:da:9d:be:34:19:4d:47:8a:d7:c0:db:18:22:fb:07:1d:f1:29:81:49:6e:d1:04:38:41:13 +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE +AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw +CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ +BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND +VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb +qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY +HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo +G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA +lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr +IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ +0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH +k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 +4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO +m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa +cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl +uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI +KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls +ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG +AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT +VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG +CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA +cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA +QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA +7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA +cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA +QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA +czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu +aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt +aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud +DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF +BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp +D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU +JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m +AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD +vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms +tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH +7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA +h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF +d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H +pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Label: "TWCA Global Root CA" +# Serial: 3262 +# MD5 Fingerprint: f9:03:7e:cf:e6:9e:3c:73:7a:2a:90:07:69:ff:2b:96 +# SHA1 Fingerprint: 9c:bb:48:53:f6:a4:f6:d3:52:a4:e8:32:52:55:60:13:f5:ad:af:65 +# SHA256 Fingerprint: 59:76:90:07:f7:68:5d:0f:cd:50:87:2f:9f:95:d5:75:5a:5b:2b:45:7d:81:f3:69:2b:61:0a:98:67:2f:0e:1b +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx +EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT +VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 +NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT +B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF +10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz +0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh +MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH +zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc +46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 +yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi +laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP +oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA +BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE +qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm +4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL +1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF +H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo +RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ +nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh +15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW +6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW +nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j +wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz +aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy +KwbQBM0= +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 2" +# Serial: 1 +# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a +# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9 +# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52 +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd +AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC +FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi +1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq +jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ +wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ +WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy +NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC +uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw +IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 +g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP +BSeOE6Fuwg== +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot 2011 O=Atos +# Subject: CN=Atos TrustedRoot 2011 O=Atos +# Label: "Atos TrustedRoot 2011" +# Serial: 6643877497813316402 +# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 +# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 +# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE +AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG +EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM +FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC +REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp +Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM +VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ +SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ +4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L +cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi +eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG +A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 +DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j +vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP +DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc +maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D +lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv +KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 1 G3" +# Serial: 687049649626669250736271037606554624078720034195 +# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab +# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67 +# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 +MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV +wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe +rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 +68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh +4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp +UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o +abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc +3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G +KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt +hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO +Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt +zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD +ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 +cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN +qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 +YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv +b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 +8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k +NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj +ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp +q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt +nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2 G3" +# Serial: 390156079458959257446133169266079962026824725800 +# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06 +# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36 +# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3 G3" +# Serial: 268090761170461462463995952157327242137089239581 +# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7 +# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d +# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G2" +# Serial: 15385348160840213938643033620894905419 +# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d +# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f +# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85 +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G3" +# Serial: 15459312981008553731928384953135426796 +# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb +# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89 +# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2 +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G2" +# Serial: 4293743540046975378534879503202253541 +# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 +# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 +# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G3" +# Serial: 7089244469030293291760083333884364146 +# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca +# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e +# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Trusted Root G4" +# Serial: 7451500558977370777930084869016614236 +# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49 +# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4 +# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88 +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- + +# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Label: "COMODO RSA Certification Authority" +# Serial: 101909084537582093308941363524873193117 +# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18 +# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4 +# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34 +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Label: "USERTrust RSA Certification Authority" +# Serial: 2645093764781058787591871645665788717 +# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 +# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e +# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Label: "USERTrust ECC Certification Authority" +# Serial: 123013823720199481456569720443997572134 +# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1 +# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0 +# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Label: "GlobalSign ECC Root CA - R5" +# Serial: 32785792099990507226680698011560947931244 +# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08 +# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa +# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24 +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Label: "IdenTrust Commercial Root CA 1" +# Serial: 13298821034946342390520003877796839426 +# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7 +# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25 +# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Label: "IdenTrust Public Sector Root CA 1" +# Serial: 13298821034946342390521976156843933698 +# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba +# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd +# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu +VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN +MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 +MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 +ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy +RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS +bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF +/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R +3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw +EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy +9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V +GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ +2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV +WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD +W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN +AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV +DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 +TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G +lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW +mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df +WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 ++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ +tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA +GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv +8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority +# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority +# Label: "CFCA EV ROOT" +# Serial: 407555286 +# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30 +# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83 +# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD +TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y +aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx +MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j +aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP +T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03 +sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL +TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5 +/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp +7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz +EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt +hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP +a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot +aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg +TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV +PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv +cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL +tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT +ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL +jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS +ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy +P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19 +xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d +Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN +5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe +/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z +AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ +5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GB CA" +# Serial: 157768595616588414422159278966750757568 +# MD5 Fingerprint: a4:eb:b9:61:28:2e:b7:2f:98:b0:35:26:90:99:51:1d +# SHA1 Fingerprint: 0f:f9:40:76:18:d3:d7:6a:4b:98:f0:a8:35:9e:0c:fd:27:ac:cc:ed +# SHA256 Fingerprint: 6b:9c:08:e8:6e:b0:f7:67:cf:ad:65:cd:98:b6:21:49:e5:49:4a:67:f5:84:5e:7b:d1:ed:01:9f:27:b8:6b:d6 +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt +MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg +Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i +YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x +CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG +b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 +HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx +WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX +1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk +u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P +99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r +M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB +BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh +cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 +gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO +ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf +aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +# Issuer: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Subject: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Label: "SZAFIR ROOT CA2" +# Serial: 357043034767186914217277344587386743377558296292 +# MD5 Fingerprint: 11:64:c1:89:b0:24:b1:8c:b1:07:7e:89:9e:51:9e:99 +# SHA1 Fingerprint: e2:52:fa:95:3f:ed:db:24:60:bd:6e:28:f3:9c:cc:cf:5e:b3:3f:de +# SHA256 Fingerprint: a1:33:9d:33:28:1a:0b:56:e5:57:d3:d3:2b:1c:e7:f9:36:7e:b0:94:bd:5f:a7:2a:7e:50:04:c8:de:d7:ca:fe +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 +ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw +NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L +cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg +Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN +QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT +3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw +3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 +3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 +BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN +XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF +AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw +8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG +nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP +oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy +d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg +LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA 2" +# Serial: 44979900017204383099463764357512596969 +# MD5 Fingerprint: 6d:46:9e:d9:25:6d:08:23:5b:5e:74:7d:1e:27:db:f2 +# SHA1 Fingerprint: d3:dd:48:3e:2b:bf:4c:05:e8:af:10:f5:fa:76:26:cf:d3:dc:30:92 +# SHA256 Fingerprint: b6:76:f2:ed:da:e8:77:5c:d3:6c:b0:f6:3c:d1:d4:60:39:61:f4:9e:62:65:ba:01:3a:2f:03:07:b6:d0:b8:04 +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB +gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu +QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG +A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz +OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ +VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 +b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA +DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn +0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB +OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE +fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E +Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m +o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i +sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW +OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez +Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS +adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n +3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ +F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf +CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 +XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm +djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ +WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb +AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq +P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko +b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj +XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P +5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi +DrW5viSP +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: ca:ff:e2:db:03:d9:cb:4b:e9:0f:ad:84:fd:7b:18:ce +# SHA1 Fingerprint: 01:0c:06:95:a6:98:19:14:ff:bf:5f:c6:b0:b6:95:ea:29:e9:12:a6 +# SHA256 Fingerprint: a0:40:92:9a:02:ce:53:b4:ac:f4:f2:ff:c6:98:1c:e4:49:6f:75:5e:6d:45:fe:0b:2a:69:2b:cd:52:52:3f:36 +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix +DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k +IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT +N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v +dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG +A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh +ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx +QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA +4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0 +AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10 +4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C +ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV +9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD +gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6 +Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq +NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko +LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd +ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I +XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI +M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot +9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V +Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea +j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh +X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ +l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf +bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 +pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK +e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 +vm9qp/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions ECC RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: 81:e5:b4:17:eb:c2:f5:e1:4b:0d:41:7b:49:92:fe:ef +# SHA1 Fingerprint: 9f:f1:71:8d:92:d5:9a:f3:7d:74:97:b4:bc:6f:84:68:0b:ba:b6:66 +# SHA256 Fingerprint: 44:b5:45:aa:8a:25:e6:5a:73:ca:15:dc:27:fc:36:d2:4c:1c:b9:95:3a:06:65:39:b1:15:82:dc:48:7b:48:33 +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN +BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl +bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv +b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ +BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj +YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 +MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 +dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg +QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa +jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi +C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep +lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof +TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +# Issuer: CN=ISRG Root X1 O=Internet Security Research Group +# Subject: CN=ISRG Root X1 O=Internet Security Research Group +# Label: "ISRG Root X1" +# Serial: 172886928669790476064670243504169061120 +# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e +# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8 +# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6 +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +# Issuer: O=FNMT-RCM OU=AC RAIZ FNMT-RCM +# Subject: O=FNMT-RCM OU=AC RAIZ FNMT-RCM +# Label: "AC RAIZ FNMT-RCM" +# Serial: 485876308206448804701554682760554759 +# MD5 Fingerprint: e2:09:04:b4:d3:bd:d1:a0:14:fd:1a:d2:47:c4:57:1d +# SHA1 Fingerprint: ec:50:35:07:b2:15:c4:95:62:19:e2:a8:9a:5b:42:99:2c:4c:2c:20 +# SHA256 Fingerprint: eb:c5:57:0c:29:01:8c:4d:67:b1:aa:12:7b:af:12:f7:03:b4:61:1e:bc:17:b7:da:b5:57:38:94:17:9b:93:fa +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx +CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ +WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ +BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG +Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ +yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf +BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz +WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF +tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z +374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC +IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL +mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 +wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS +MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 +ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet +UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H +YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 +LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 +RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM +LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf +77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N +JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm +fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp +6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp +1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B +9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok +RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv +uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 1 O=Amazon +# Subject: CN=Amazon Root CA 1 O=Amazon +# Label: "Amazon Root CA 1" +# Serial: 143266978916655856878034712317230054538369994 +# MD5 Fingerprint: 43:c6:bf:ae:ec:fe:ad:2f:18:c6:88:68:30:fc:c8:e6 +# SHA1 Fingerprint: 8d:a7:f9:65:ec:5e:fc:37:91:0f:1c:6e:59:fd:c1:cc:6a:6e:de:16 +# SHA256 Fingerprint: 8e:cd:e6:88:4f:3d:87:b1:12:5b:a3:1a:c3:fc:b1:3d:70:16:de:7f:57:cc:90:4f:e1:cb:97:c6:ae:98:19:6e +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 2 O=Amazon +# Subject: CN=Amazon Root CA 2 O=Amazon +# Label: "Amazon Root CA 2" +# Serial: 143266982885963551818349160658925006970653239 +# MD5 Fingerprint: c8:e5:8d:ce:a8:42:e2:7a:c0:2a:5c:7c:9e:26:bf:66 +# SHA1 Fingerprint: 5a:8c:ef:45:d7:a6:98:59:76:7a:8c:8b:44:96:b5:78:cf:47:4b:1a +# SHA256 Fingerprint: 1b:a5:b2:aa:8c:65:40:1a:82:96:01:18:f8:0b:ec:4f:62:30:4d:83:ce:c4:71:3a:19:c3:9c:01:1e:a4:6d:b4 +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK +gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ +W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg +1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K +8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r +2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me +z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR +8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj +mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz +7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 ++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI +0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm +UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 +LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS +k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl +7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm +btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl +urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ +fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 +n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE +76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H +9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT +4PsJYGw= +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 3 O=Amazon +# Subject: CN=Amazon Root CA 3 O=Amazon +# Label: "Amazon Root CA 3" +# Serial: 143266986699090766294700635381230934788665930 +# MD5 Fingerprint: a0:d4:ef:0b:f7:b5:d8:49:95:2a:ec:f5:c4:fc:81:87 +# SHA1 Fingerprint: 0d:44:dd:8c:3c:8c:1a:1a:58:75:64:81:e9:0f:2e:2a:ff:b3:d2:6e +# SHA256 Fingerprint: 18:ce:6c:fe:7b:f1:4e:60:b2:e3:47:b8:df:e8:68:cb:31:d0:2e:bb:3a:da:27:15:69:f5:03:43:b4:6d:b3:a4 +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 4 O=Amazon +# Subject: CN=Amazon Root CA 4 O=Amazon +# Label: "Amazon Root CA 4" +# Serial: 143266989758080763974105200630763877849284878 +# MD5 Fingerprint: 89:bc:27:d5:eb:17:8d:06:6a:69:d5:fd:89:47:b4:cd +# SHA1 Fingerprint: f6:10:84:07:d6:f8:bb:67:98:0c:c2:e2:44:c2:eb:ae:1c:ef:63:be +# SHA256 Fingerprint: e3:5d:28:41:9e:d0:20:25:cf:a6:90:38:cd:62:39:62:45:8d:a5:c6:95:fb:de:a3:c2:2b:0b:fb:25:89:70:92 +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi +9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk +M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB +MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw +CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW +1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +# Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM +# Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM +# Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1" +# Serial: 1 +# MD5 Fingerprint: dc:00:81:dc:69:2f:3e:2f:b0:3b:f6:3d:5a:91:8e:49 +# SHA1 Fingerprint: 31:43:64:9b:ec:ce:27:ec:ed:3a:3f:0b:8f:0d:e4:e8:91:dd:ee:ca +# SHA256 Fingerprint: 46:ed:c3:68:90:46:d5:3a:45:3f:b3:10:4a:b8:0d:ca:ec:65:8b:26:60:ea:16:29:dd:7e:86:79:90:64:87:16 +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx +GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp +bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w +KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 +BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy +dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG +EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll +IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU +QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT +TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg +LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 +a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr +LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr +N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X +YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ +iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f +AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH +V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf +IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 +lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c +8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf +lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +# Issuer: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. +# Subject: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. +# Label: "GDCA TrustAUTH R5 ROOT" +# Serial: 9009899650740120186 +# MD5 Fingerprint: 63:cc:d9:3d:34:35:5c:6f:53:a3:e2:08:70:48:1f:b4 +# SHA1 Fingerprint: 0f:36:38:5b:81:1a:25:c3:9b:31:4e:83:ca:e9:34:66:70:cc:74:b4 +# SHA256 Fingerprint: bf:ff:8f:d0:44:33:48:7d:6a:8a:a6:0c:1a:29:76:7a:9f:c2:bb:b0:5e:42:0f:71:3a:13:b9:92:89:1d:38:93 +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE +BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0 +MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVowYjELMAkGA1UEBhMCQ04xMjAwBgNV +BAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8w +HQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJj +Dp6L3TQsAlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBj +TnnEt1u9ol2x8kECK62pOqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+u +KU49tm7srsHwJ5uu4/Ts765/94Y9cnrrpftZTqfrlYwiOXnhLQiPzLyRuEH3FMEj +qcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ9Cy5WmYqsBebnh52nUpm +MUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQxXABZG12 +ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloP +zgsMR6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3Gk +L30SgLdTMEZeS1SZD2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeC +jGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4oR24qoAATILnsn8JuLwwoC8N9VKejveSswoA +HQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx9hoh49pwBiFYFIeFd3mqgnkC +AwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlRMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZm +DRd9FBUb1Ov9H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5 +COmSdI31R9KrO9b7eGZONn356ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ry +L3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd+PwyvzeG5LuOmCd+uh8W4XAR8gPf +JWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQHtZa37dG/OaG+svg +IHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBDF8Io +2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV +09tL7ECQ8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQ +XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq +T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe +MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com Root Certification Authority RSA O=SSL Corporation +# Subject: CN=SSL.com Root Certification Authority RSA O=SSL Corporation +# Label: "SSL.com Root Certification Authority RSA" +# Serial: 8875640296558310041 +# MD5 Fingerprint: 86:69:12:c0:70:f1:ec:ac:ac:c2:d5:bc:a5:5b:a1:29 +# SHA1 Fingerprint: b7:ab:33:08:d1:ea:44:77:ba:14:80:12:5a:6f:bd:a9:36:49:0c:bb +# SHA256 Fingerprint: 85:66:6a:56:2e:e0:be:5c:e9:25:c1:d8:89:0a:6f:76:a8:7e:c1:6d:4d:7d:5f:29:ea:74:19:cf:20:12:3b:69 +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE +BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK +DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz +OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R +xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX +qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC +C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 +6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh +/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF +YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E +JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc +US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 +ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm ++Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi +M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV +cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc +Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs +PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ +q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 +cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr +a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I +H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y +K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu +nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf +oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY +Ic2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com Root Certification Authority ECC O=SSL Corporation +# Subject: CN=SSL.com Root Certification Authority ECC O=SSL Corporation +# Label: "SSL.com Root Certification Authority ECC" +# Serial: 8495723813297216424 +# MD5 Fingerprint: 2e:da:e4:39:7f:9c:8f:37:d1:70:9f:26:17:51:3a:8e +# SHA1 Fingerprint: c3:19:7c:39:24:e6:54:af:1b:c4:ab:20:95:7a:e2:c3:0e:13:02:6a +# SHA256 Fingerprint: 34:17:bb:06:cc:60:07:da:1b:96:1c:92:0b:8a:b4:ce:3f:ad:82:0e:4a:a3:0b:9a:cb:c4:a7:4e:bd:ce:bc:65 +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz +WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 +b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS +b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI +7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg +CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD +VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T +kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ +gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation +# Subject: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation +# Label: "SSL.com EV Root Certification Authority RSA R2" +# Serial: 6248227494352943350 +# MD5 Fingerprint: e1:1e:31:58:1a:ae:54:53:02:f6:17:6a:11:7b:4d:95 +# SHA1 Fingerprint: 74:3a:f0:52:9b:d0:32:a0:f4:4a:83:cd:d4:ba:a9:7b:7c:2e:c4:9a +# SHA256 Fingerprint: 2e:7b:f1:6c:c2:24:85:a7:bb:e2:aa:86:96:75:07:61:b0:ae:39:be:3b:2f:e9:d0:cc:6d:4e:f7:34:91:42:5c +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV +BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE +CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy +MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G +A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD +DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq +M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf +OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa +4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 +HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR +aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA +b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ +Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV +PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO +pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu +UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY +MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 +9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW +s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 +Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg +cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM +79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz +/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt +ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm +Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK +QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ +w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi +S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 +mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation +# Subject: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation +# Label: "SSL.com EV Root Certification Authority ECC" +# Serial: 3182246526754555285 +# MD5 Fingerprint: 59:53:22:65:83:42:01:54:c0:ce:42:b9:5a:7c:f2:90 +# SHA1 Fingerprint: 4c:dd:51:a3:d1:f5:20:32:14:b0:c6:c5:32:23:03:91:c7:46:42:6d +# SHA256 Fingerprint: 22:a2:c1:f7:bd:ed:70:4c:c1:e7:01:b5:f4:08:c3:10:88:0f:e9:56:b5:de:2a:4a:44:f9:9c:87:3a:25:a7:c8 +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx +NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv +bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA +VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku +WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX +5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ +ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg +h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 +# Label: "GlobalSign Root CA - R6" +# Serial: 1417766617973444989252670301619537 +# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae +# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1 +# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69 +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg +MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx +MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET +MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI +xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k +ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD +aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw +LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw +1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX +k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2 +SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h +bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n +WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY +rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce +MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu +bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt +Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61 +55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj +vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf +cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz +oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp +nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs +pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v +JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R +8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 +5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GC CA" +# Serial: 44084345621038548146064804565436152554 +# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23 +# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31 +# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw +CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 +bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg +Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ +BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu +ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS +b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni +eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W +p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T +rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV +57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg +Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +# Issuer: CN=UCA Global G2 Root O=UniTrust +# Subject: CN=UCA Global G2 Root O=UniTrust +# Label: "UCA Global G2 Root" +# Serial: 124779693093741543919145257850076631279 +# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8 +# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a +# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9 +MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH +bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x +CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds +b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr +b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9 +kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm +VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R +VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc +C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj +tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY +D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv +j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl +NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6 +iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP +O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV +ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj +L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl +1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU +b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV +PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj +y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb +EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg +DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI ++Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy +YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX +UB+K+wb1whnw0A== +-----END CERTIFICATE----- + +# Issuer: CN=UCA Extended Validation Root O=UniTrust +# Subject: CN=UCA Extended Validation Root O=UniTrust +# Label: "UCA Extended Validation Root" +# Serial: 106100277556486529736699587978573607008 +# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2 +# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a +# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH +MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF +eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx +MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV +BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog +D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS +sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop +O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk +sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi +c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj +VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz +KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/ +TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G +sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs +1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD +fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN +l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ +VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5 +c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp +4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s +t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj +2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO +vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C +xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx +cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM +fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax +-----END CERTIFICATE----- + +# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 +# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 +# Label: "Certigna Root CA" +# Serial: 269714418870597844693661054334862075617 +# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77 +# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43 +# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68 +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw +WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw +MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x +MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD +VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX +BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO +ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M +CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu +I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm +TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh +C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf +ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz +IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT +Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k +JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5 +hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB +GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov +L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo +dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr +aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq +hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L +6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG +HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6 +0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB +lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi +o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1 +gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v +faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63 +Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh +jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw +3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +# Issuer: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI +# Subject: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI +# Label: "emSign Root CA - G1" +# Serial: 235931866688319308814040 +# MD5 Fingerprint: 9c:42:84:57:dd:cb:0b:a7:2e:95:ad:b6:f3:da:bc:ac +# SHA1 Fingerprint: 8a:c7:ad:8f:73:ac:4e:c1:b5:75:4d:a5:40:f4:fc:cf:7c:b5:8e:8c +# SHA256 Fingerprint: 40:f6:af:03:46:a9:9a:a1:cd:1d:55:5a:4e:9c:ce:62:c7:f9:63:46:03:ee:40:66:15:83:3d:c8:c8:d0:03:67 +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD +VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU +ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH +MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO +MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv +Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz +f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO +8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq +d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM +tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt +Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB +o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x +PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM +wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d +GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH +6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby +RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI +# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI +# Label: "emSign ECC Root CA - G3" +# Serial: 287880440101571086945156 +# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40 +# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1 +# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG +EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo +bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ +TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s +b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0 +WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS +fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB +zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq +hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB +CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD ++JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +# Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI +# Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI +# Label: "emSign Root CA - C1" +# Serial: 825510296613316004955058 +# MD5 Fingerprint: d8:e3:5d:01:21:fa:78:5a:b0:df:ba:d2:ee:2a:5f:68 +# SHA1 Fingerprint: e7:2e:f1:df:fc:b2:09:28:cf:5d:d4:d5:67:37:b1:51:cb:86:4f:01 +# SHA256 Fingerprint: 12:56:09:aa:30:1d:a0:a2:49:b9:7a:82:39:cb:6a:34:21:6f:44:dc:ac:9f:39:54:b1:42:92:f2:e8:c8:60:8f +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG +A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg +SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v +dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ +BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ +HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH +3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH +GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c +xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1 +aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq +TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87 +/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4 +kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG +YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT ++xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo +WXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +# Issuer: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI +# Subject: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI +# Label: "emSign ECC Root CA - C3" +# Serial: 582948710642506000014504 +# MD5 Fingerprint: 3e:53:b3:a3:81:ee:d7:10:f8:d3:b0:1d:17:92:f5:d5 +# SHA1 Fingerprint: b6:af:43:c2:9b:81:53:7d:f6:ef:6b:c3:1f:1f:60:15:0c:ee:48:66 +# SHA256 Fingerprint: bc:4d:80:9b:15:18:9d:78:db:3e:1d:8c:f4:f9:72:6a:79:5d:a1:64:3c:a5:f1:35:8e:1d:db:0e:dc:0d:7e:b3 +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG +EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx +IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND +IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci +MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti +sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O +BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB +Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c +3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J +0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +# Issuer: CN=Hongkong Post Root CA 3 O=Hongkong Post +# Subject: CN=Hongkong Post Root CA 3 O=Hongkong Post +# Label: "Hongkong Post Root CA 3" +# Serial: 46170865288971385588281144162979347873371282084 +# MD5 Fingerprint: 11:fc:9f:bd:73:30:02:8a:fd:3f:f3:58:b9:cb:20:f0 +# SHA1 Fingerprint: 58:a2:d0:ec:20:52:81:5b:c1:f3:f8:64:02:24:4e:c2:8e:02:4b:02 +# SHA256 Fingerprint: 5a:2f:c0:3f:0c:83:b0:90:bb:fa:40:60:4b:09:88:44:6c:76:36:18:3d:f9:84:6e:17:10:1a:44:7f:b8:ef:d6 +-----BEGIN CERTIFICATE----- +MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL +BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ +SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n +a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5 +NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT +CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u +Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO +dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI +VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV +9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY +2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY +vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt +bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb +x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+ +l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK +TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj +Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e +i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw +DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG +7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk +MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr +gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk +GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS +3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm +Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+ +l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c +JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP +L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa +LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG +mpv0 +-----END CERTIFICATE----- + +# Issuer: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation +# Subject: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation +# Label: "Microsoft ECC Root Certificate Authority 2017" +# Serial: 136839042543790627607696632466672567020 +# MD5 Fingerprint: dd:a1:03:e6:4a:93:10:d1:bf:f0:19:42:cb:fe:ed:67 +# SHA1 Fingerprint: 99:9a:64:c3:7f:f4:7d:9f:ab:95:f1:47:69:89:14:60:ee:c4:c3:c5 +# SHA256 Fingerprint: 35:8d:f3:9d:76:4a:f9:e1:b7:66:e9:c9:72:df:35:2e:e1:5c:fa:c2:27:af:6a:d1:d7:0e:8e:4a:6e:dc:ba:02 +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD +VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw +MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy +b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR +ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb +hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3 +FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV +L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB +iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +# Issuer: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation +# Subject: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation +# Label: "Microsoft RSA Root Certificate Authority 2017" +# Serial: 40975477897264996090493496164228220339 +# MD5 Fingerprint: 10:ff:00:ff:cf:c9:f8:c7:7a:c0:ee:35:8e:c9:0f:47 +# SHA1 Fingerprint: 73:a5:e6:4a:3b:ff:83:16:ff:0e:dc:cc:61:8a:90:6e:4e:ae:4d:74 +# SHA256 Fingerprint: c7:41:f7:0f:4b:2a:8d:88:bf:2e:71:c1:41:22:ef:53:ef:10:eb:a0:cf:a5:e6:4c:fa:20:f4:18:85:30:73:e0 +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl +MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N +aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ +Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0 +ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1 +HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm +gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ +jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc +aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG +YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6 +W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K +UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH ++FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q +W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC +LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC +gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6 +tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh +SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2 +TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3 +pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR +xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp +GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9 +dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN +AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB +RA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +# Issuer: CN=e-Szigno Root CA 2017 O=Microsec Ltd. +# Subject: CN=e-Szigno Root CA 2017 O=Microsec Ltd. +# Label: "e-Szigno Root CA 2017" +# Serial: 411379200276854331539784714 +# MD5 Fingerprint: de:1f:f6:9e:84:ae:a7:b4:21:ce:1e:58:7d:d1:84:98 +# SHA1 Fingerprint: 89:d4:83:03:4f:9e:9a:48:80:5f:72:37:d4:a9:a6:ef:cb:7c:1f:d1 +# SHA256 Fingerprint: be:b0:0b:30:83:9b:9b:c3:2c:32:e4:44:79:05:95:06:41:f2:64:21:b1:5e:d0:89:19:8b:51:8a:e2:ea:1b:99 +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV +BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk +LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv +b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ +BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg +THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v +IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv +xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H +Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB +eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo +jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ ++efcMQ== +-----END CERTIFICATE----- + +# Issuer: O=CERTSIGN SA OU=certSIGN ROOT CA G2 +# Subject: O=CERTSIGN SA OU=certSIGN ROOT CA G2 +# Label: "certSIGN Root CA G2" +# Serial: 313609486401300475190 +# MD5 Fingerprint: 8c:f1:75:8a:c6:19:cf:94:b7:f7:65:20:87:c3:97:c7 +# SHA1 Fingerprint: 26:f9:93:b4:ed:3d:28:27:b0:b9:4b:a7:e9:15:1d:a3:8d:92:e5:32 +# SHA256 Fingerprint: 65:7c:fe:2f:a7:3f:aa:38:46:25:71:f3:32:a2:36:3a:46:fc:e7:02:09:51:71:07:02:cd:fb:b6:ee:da:33:05 +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV +BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g +Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ +BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ +R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF +dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw +vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ +uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp +n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs +cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW +xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P +rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF +DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx +DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy +LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C +eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ +d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq +kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl +qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0 +OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c +NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk +ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO +pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj +03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk +PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE +1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX +QRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +# Issuer: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. +# Subject: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. +# Label: "NAVER Global Root Certification Authority" +# Serial: 9013692873798656336226253319739695165984492813 +# MD5 Fingerprint: c8:7e:41:f6:25:3b:f5:09:b3:17:e8:46:3d:bf:d0:9b +# SHA1 Fingerprint: 8f:6b:f2:a9:27:4a:da:14:a0:c4:f4:8e:61:27:f9:c0:1e:78:5d:d1 +# SHA256 Fingerprint: 88:f4:38:dc:f8:ff:d1:fa:8f:42:91:15:ff:e5:f8:2a:e1:e0:6e:0c:70:c3:75:fa:ad:71:7b:34:a4:9e:72:65 +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM +BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG +T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx +CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD +b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA +iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH +38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE +HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz +kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP +szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq +vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf +nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG +YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo +0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a +CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K +AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I +36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN +qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj +cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm ++LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL +hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe +lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7 +p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8 +piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR +LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX +5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO +dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul +9XXeifdy +-----END CERTIFICATE----- + +# Issuer: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres +# Subject: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres +# Label: "AC RAIZ FNMT-RCM SERVIDORES SEGUROS" +# Serial: 131542671362353147877283741781055151509 +# MD5 Fingerprint: 19:36:9c:52:03:2f:d2:d1:bb:23:cc:dd:1e:12:55:bb +# SHA1 Fingerprint: 62:ff:d9:9e:c0:65:0d:03:ce:75:93:d2:ed:3f:2d:32:c9:e3:e5:4a +# SHA256 Fingerprint: 55:41:53:b1:3d:2c:f9:dd:b7:53:bf:be:1a:4e:0a:e0:8d:0a:a4:18:70:58:fe:60:a2:b8:62:b2:e4:b8:7b:cb +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw +CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw +FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S +Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5 +MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL +DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS +QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH +sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK +Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu +SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC +MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy +v+c= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign Root R46 O=GlobalSign nv-sa +# Subject: CN=GlobalSign Root R46 O=GlobalSign nv-sa +# Label: "GlobalSign Root R46" +# Serial: 1552617688466950547958867513931858518042577 +# MD5 Fingerprint: c4:14:30:e4:fa:66:43:94:2a:6a:1b:24:5f:19:d0:ef +# SHA1 Fingerprint: 53:a2:b0:4b:ca:6b:d6:45:e6:39:8a:8e:c4:0d:d2:bf:77:c3:a2:90 +# SHA256 Fingerprint: 4f:a3:12:6d:8d:3a:11:d1:c4:85:5a:4f:80:7c:ba:d6:cf:91:9d:3a:5a:88:b0:3b:ea:2c:63:72:d9:3c:40:c9 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA +MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD +VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy +MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt +c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ +OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG +vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud +316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo +0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE +y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF +zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE ++cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN +I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs +x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa +ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC +4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4 +7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti +2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk +pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF +FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt +rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk +ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5 +u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP +4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6 +N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3 +vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6 +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign Root E46 O=GlobalSign nv-sa +# Subject: CN=GlobalSign Root E46 O=GlobalSign nv-sa +# Label: "GlobalSign Root E46" +# Serial: 1552617690338932563915843282459653771421763 +# MD5 Fingerprint: b5:b8:66:ed:de:08:83:e3:c9:e2:01:34:06:ac:51:6f +# SHA1 Fingerprint: 39:b4:6c:d5:fe:80:06:eb:e2:2f:4a:bb:08:33:a0:af:db:b9:dd:84 +# SHA256 Fingerprint: cb:b9:c4:4d:84:b8:04:3e:10:50:ea:31:a6:9f:51:49:55:d7:bf:d2:e2:c6:b4:93:01:01:9a:d6:1d:9f:50:58 +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx +CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD +ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw +MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex +HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq +R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd +yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ +7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8 ++RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- + +# Issuer: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz +# Subject: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz +# Label: "ANF Secure Server Root CA" +# Serial: 996390341000653745 +# MD5 Fingerprint: 26:a6:44:5a:d9:af:4e:2f:b2:1d:b6:65:b0:4e:e8:96 +# SHA1 Fingerprint: 5b:6e:68:d0:cc:15:b6:a0:5f:1e:c1:5f:ae:02:fc:6b:2f:5d:6f:74 +# SHA256 Fingerprint: fb:8f:ec:75:91:69:b9:10:6b:1e:51:16:44:c6:18:c5:13:04:37:3f:6c:06:43:08:8d:8b:ef:fd:1b:99:75:99 +-----BEGIN CERTIFICATE----- +MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV +BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk +YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV +BAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3QgQ0EwHhcNMTkwOTA0MTAwMDM4WhcN +MzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEwMQswCQYDVQQGEwJF +UzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQwEgYD +VQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9v +dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCj +cqQZAZ2cC4Ffc0m6p6zzBE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9q +yGFOtibBTI3/TO80sh9l2Ll49a2pcbnvT1gdpd50IJeh7WhM3pIXS7yr/2WanvtH +2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcvB2VSAKduyK9o7PQUlrZX +H1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXsezx76W0OL +zc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyR +p1RMVwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQz +W7i1o0TJrH93PB0j7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/ +SiOL9V8BY9KHcyi1Swr1+KuCLH5zJTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJn +LNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe8TZBAQIvfXOn3kLMTOmJDVb3 +n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVOHj1tyRRM4y5B +u8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj +o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC +AgEATh65isagmD9uw2nAalxJUqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L +9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzxj6ptBZNscsdW699QIyjlRRA96Gej +rw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDtdD+4E5UGUcjohybK +pFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM5gf0 +vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjq +OknkJjCb5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ +/zo1PqVUSlJZS2Db7v54EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ9 +2zg/LFis6ELhDtjTO0wugumDLmsx2d1Hhk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI ++PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGyg77FGr8H6lnco4g175x2 +MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3r5+qPeoo +tt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= +-----END CERTIFICATE----- + +# Issuer: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Subject: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Label: "Certum EC-384 CA" +# Serial: 160250656287871593594747141429395092468 +# MD5 Fingerprint: b6:65:b3:96:60:97:12:a1:ec:4e:e1:3d:a3:c6:c9:f1 +# SHA1 Fingerprint: f3:3e:78:3c:ac:df:f4:a2:cc:ac:67:55:69:56:d7:e5:16:3c:e1:ed +# SHA256 Fingerprint: 6b:32:80:85:62:53:18:aa:50:d1:73:c9:8d:8b:da:09:d5:7e:27:41:3d:11:4c:f7:87:a0:f5:d0:6c:03:0c:f6 +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw +CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw +JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT +EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0 +WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT +LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX +BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE +KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm +Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8 +EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J +UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn +nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Root CA" +# Serial: 40870380103424195783807378461123655149 +# MD5 Fingerprint: 51:e1:c2:e7:fe:4c:84:af:59:0e:2f:f4:54:6f:ea:29 +# SHA1 Fingerprint: c8:83:44:c0:18:ae:9f:cc:f1:87:b7:8f:22:d1:c5:d7:45:84:ba:e5 +# SHA256 Fingerprint: fe:76:96:57:38:55:77:3e:37:a9:5e:7a:d4:d9:cc:96:c3:01:57:c1:5d:31:76:5b:a9:b1:57:04:e1:ae:78:fd +-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6 +MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu +MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV +BAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw +MzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg +U3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ +n0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q +p1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq +NwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF +8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3 +HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa +mqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi +7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF +ytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P +qafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ +v3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6 +Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD +ggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4 +WxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo +zMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR +5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ +GfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf +5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq +0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D +P78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM +qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP +0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf +E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE----- + +# Issuer: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique +# Subject: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique +# Label: "TunTrust Root CA" +# Serial: 108534058042236574382096126452369648152337120275 +# MD5 Fingerprint: 85:13:b9:90:5b:36:5c:b6:5e:b8:5a:f8:e0:31:57:b4 +# SHA1 Fingerprint: cf:e9:70:84:0f:e0:73:0f:9d:f6:0c:7f:2c:4b:ee:20:46:34:9c:bb +# SHA256 Fingerprint: 2e:44:10:2a:b5:8c:b8:54:19:45:1c:8e:19:d9:ac:f3:66:2c:af:bc:61:4b:6a:53:96:0a:30:f7:d0:e2:eb:41 +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL +BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg +Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv +b3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG +EwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u +IEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ +n56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd +2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF +VwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ +GoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF +li/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU +r8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2 +eY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb +MlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg +jwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB +7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW +5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE +ITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 +90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z +xiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu +QEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4 +FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH +22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP +xOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn +dFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5 +Xc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b +nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ +CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH +u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj +d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= +-----END CERTIFICATE----- + +# Issuer: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Subject: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Label: "HARICA TLS RSA Root CA 2021" +# Serial: 76817823531813593706434026085292783742 +# MD5 Fingerprint: 65:47:9b:58:86:dd:2c:f0:fc:a2:84:1f:1e:96:c4:91 +# SHA1 Fingerprint: 02:2d:05:82:fa:88:ce:14:0c:06:79:de:7f:14:10:e9:45:d7:a5:6d +# SHA256 Fingerprint: d9:5d:0e:8e:da:79:52:5b:f9:be:b1:1b:14:d2:10:0d:32:94:98:5f:0c:62:d9:fa:bd:9c:d9:99:ec:cb:7b:1d +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs +MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg +Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL +MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv +b3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l +mwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE +4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv +a9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M +pbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw +Kh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b +LW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY +AuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB +AGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq +E613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr +W2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ +CoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU +X15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3 +f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja +H6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP +JzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P +zzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt +jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0 +/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT +BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79 +aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW +xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU +63ZTGI0RmLo= +-----END CERTIFICATE----- + +# Issuer: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Subject: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Label: "HARICA TLS ECC Root CA 2021" +# Serial: 137515985548005187474074462014555733966 +# MD5 Fingerprint: ae:f7:4c:e5:66:35:d1:b7:9b:8c:22:93:74:d3:4b:b0 +# SHA1 Fingerprint: bc:b0:c1:9d:e9:98:92:70:19:38:57:e9:8d:a7:b4:5d:6e:ee:01:48 +# SHA256 Fingerprint: 3f:99:cc:47:4a:cf:ce:4d:fe:d5:87:94:66:5e:47:8d:15:47:73:9f:2e:78:0f:1b:b4:ca:9b:13:30:97:d4:01 +-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw +CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh +cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v +dCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG +A1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg +Q0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7 +KKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y +STHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD +AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw +SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN +nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE----- + +# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068" +# Serial: 1977337328857672817 +# MD5 Fingerprint: 4e:6e:9b:54:4c:ca:b7:fa:48:e4:90:b1:15:4b:1c:a3 +# SHA1 Fingerprint: 0b:be:c2:27:22:49:cb:39:aa:db:35:5c:53:e3:8c:ae:78:ff:b6:fe +# SHA256 Fingerprint: 57:de:05:83:ef:d2:b2:6e:03:61:da:99:da:9d:f4:64:8d:ef:7e:e8:44:1c:3b:72:8a:fa:9b:cd:e0:f9:b2:6a +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE +BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h +cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1 +MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg +Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 +thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM +cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG +L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i +NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h +X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b +m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy +Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja +EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T +KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF +6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh +OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc +tHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd +IAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j +b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC +AG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw +ADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m +iWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF +Sa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ +hfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P +Vf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE +EAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV +1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t +CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR +5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw +f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9 +ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK +GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV +-----END CERTIFICATE----- + +# Issuer: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd. +# Subject: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd. +# Label: "vTrus ECC Root CA" +# Serial: 630369271402956006249506845124680065938238527194 +# MD5 Fingerprint: de:4b:c1:f5:52:8c:9b:43:e1:3e:8f:55:54:17:8d:85 +# SHA1 Fingerprint: f6:9c:db:b0:fc:f6:02:13:b6:52:32:a6:a3:91:3f:16:70:da:c3:e1 +# SHA256 Fingerprint: 30:fb:ba:2c:32:23:8e:2a:98:54:7a:f9:79:31:e5:50:42:8b:9b:3f:1c:8e:eb:66:33:dc:fa:86:c5:b2:7d:d3 +-----BEGIN CERTIFICATE----- +MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMw +RzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAY +BgNVBAMTEXZUcnVzIEVDQyBSb290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDcz +MTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28u +LEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+cToL0 +v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUd +e4BdS49nTPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIw +V53dVvHH4+m4SVBrm2nDb+zDfSXkV5UTQJtS0zvzQBm8JsctBp61ezaf9SXUY2sA +AjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQLYgmRWAD5Tfs0aNoJrSEG +GJTO +-----END CERTIFICATE----- + +# Issuer: CN=vTrus Root CA O=iTrusChina Co.,Ltd. +# Subject: CN=vTrus Root CA O=iTrusChina Co.,Ltd. +# Label: "vTrus Root CA" +# Serial: 387574501246983434957692974888460947164905180485 +# MD5 Fingerprint: b8:c9:37:df:fa:6b:31:84:64:c5:ea:11:6a:1b:75:fc +# SHA1 Fingerprint: 84:1a:69:fb:f5:cd:1a:25:34:13:3d:e3:f8:fc:b8:99:d0:c9:14:b7 +# SHA256 Fingerprint: 8a:71:de:65:59:33:6f:42:6c:26:e5:38:80:d0:0d:88:a1:8d:a4:c6:a9:1f:0d:cb:61:94:e2:06:c5:c9:63:87 +-----BEGIN CERTIFICATE----- +MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQEL +BQAwQzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4x +FjAUBgNVBAMTDXZUcnVzIFJvb3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMx +MDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoGA1UEChMTaVRydXNDaGluYSBDby4s +THRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZotsSKYc +IrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykU +AyyNJJrIZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+ +GrPSbcKvdmaVayqwlHeFXgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z9 +8Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KAYPxMvDVTAWqXcoKv8R1w6Jz1717CbMdH +flqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70kLJrxLT5ZOrpGgrIDajt +J8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2AXPKBlim +0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZN +pGvu/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQ +UqqzApVg+QxMaPnu1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHW +OXSuTEGC2/KmSNGzm/MzqvOmwMVO9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMB +AAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYgscasGrz2iTAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAKbqSSaet +8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd +nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1j +bhd47F18iMjrjld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvM +Kar5CKXiNxTKsbhm7xqC5PD48acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIiv +TDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJnxDHO2zTlJQNgJXtxmOTAGytfdELS +S8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554WgicEFOwE30z9J4nfr +I8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4sEb9 +b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNB +UvupLnKWnyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1P +Ti07NEPhmg4NpGaXutIcSkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929ven +sBxXVsFy6K2ir40zSbofitzmdHxghm+Hl3s= +-----END CERTIFICATE----- + +# Issuer: CN=ISRG Root X2 O=Internet Security Research Group +# Subject: CN=ISRG Root X2 O=Internet Security Research Group +# Label: "ISRG Root X2" +# Serial: 87493402998870891108772069816698636114 +# MD5 Fingerprint: d3:9e:c4:1e:23:3c:a6:df:cf:a3:7e:6d:e0:14:e6:e5 +# SHA1 Fingerprint: bd:b1:b9:3c:d5:97:8d:45:c6:26:14:55:f8:db:95:c7:5a:d1:53:af +# SHA256 Fingerprint: 69:72:9b:8e:15:a8:6e:fc:17:7a:57:af:b7:17:1d:fc:64:ad:d2:8c:2f:ca:8c:f1:50:7e:34:45:3c:cb:14:70 +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw +CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg +R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 +MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT +ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW ++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 +ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI +zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW +tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 +/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- + +# Issuer: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd. +# Subject: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd. +# Label: "HiPKI Root CA - G1" +# Serial: 60966262342023497858655262305426234976 +# MD5 Fingerprint: 69:45:df:16:65:4b:e8:68:9a:8f:76:5f:ff:80:9e:d3 +# SHA1 Fingerprint: 6a:92:e4:a8:ee:1b:ec:96:45:37:e3:29:57:49:cd:96:e3:e5:d2:60 +# SHA256 Fingerprint: f0:15:ce:3c:c2:39:bf:ef:06:4b:e9:f1:d2:c4:17:e1:a0:26:4a:0a:94:be:1f:0c:8d:12:18:64:eb:69:49:cc +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa +Fw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3 +YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw +qNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv +Vcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6 +lZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz +Qs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ +KILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK +FgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj +HluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr +y+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ +/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM +a/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6 +fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG +SIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi +7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc +SE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza +ZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc +XzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg +iLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho +L5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF +Ne85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr +kkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+ +vhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU +YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Label: "GlobalSign ECC Root CA - R4" +# Serial: 159662223612894884239637590694 +# MD5 Fingerprint: 26:29:f8:6d:e1:88:bf:a2:65:7f:aa:c4:cd:0f:7f:fc +# SHA1 Fingerprint: 6b:a0:b0:98:e1:71:ef:5a:ad:fe:48:15:80:77:10:f4:bd:6f:0b:28 +# SHA256 Fingerprint: b0:85:d7:0b:96:4f:19:1a:73:e4:af:0d:54:ae:7a:0e:07:aa:fd:af:9b:71:dd:08:62:13:8a:b7:32:5a:24:a2 +-----BEGIN CERTIFICATE----- +MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD +VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw +MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g +UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT +BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx +uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV +HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/ ++wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147 +bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R1 O=Google Trust Services LLC +# Subject: CN=GTS Root R1 O=Google Trust Services LLC +# Label: "GTS Root R1" +# Serial: 159662320309726417404178440727 +# MD5 Fingerprint: 05:fe:d0:bf:71:a8:a3:76:63:da:01:e0:d8:52:dc:40 +# SHA1 Fingerprint: e5:8c:1c:c4:91:3b:38:63:4b:e9:10:6e:e3:ad:8e:6b:9d:d9:81:4a +# SHA256 Fingerprint: d9:47:43:2a:bd:e7:b7:fa:90:fc:2e:6b:59:10:1b:12:80:e0:e1:c7:e4:e4:0f:a3:c6:88:7f:ff:57:a7:f4:cf +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo +27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w +Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw +TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl +qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH +szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8 +Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk +MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92 +wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p +aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN +VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID +AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb +C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy +h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4 +7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J +ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef +MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/ +Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT +6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ +0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm +2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb +bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R3 O=Google Trust Services LLC +# Subject: CN=GTS Root R3 O=Google Trust Services LLC +# Label: "GTS Root R3" +# Serial: 159662495401136852707857743206 +# MD5 Fingerprint: 3e:e7:9d:58:02:94:46:51:94:e5:e0:22:4a:8b:e7:73 +# SHA1 Fingerprint: ed:e5:71:80:2b:c8:92:b9:5b:83:3c:d2:32:68:3f:09:cd:a0:1e:46 +# SHA256 Fingerprint: 34:d8:a7:3e:e2:08:d9:bc:db:0d:95:65:20:93:4b:4e:40:e6:94:82:59:6e:8b:6f:73:c8:42:6b:01:0a:6f:48 +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G +jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2 +4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7 +VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm +ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R4 O=Google Trust Services LLC +# Subject: CN=GTS Root R4 O=Google Trust Services LLC +# Label: "GTS Root R4" +# Serial: 159662532700760215368942768210 +# MD5 Fingerprint: 43:96:83:77:19:4d:76:b3:9d:65:52:e4:1d:22:a5:e8 +# SHA1 Fingerprint: 77:d3:03:67:b5:e0:0c:15:f6:0c:38:61:df:7c:e1:3b:92:46:4d:47 +# SHA256 Fingerprint: 34:9d:fa:40:58:c5:e2:63:12:3b:39:8a:e7:95:57:3c:4e:13:13:c8:3f:e6:8f:93:55:6c:d5:e8:03:1b:3c:7d +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi +QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR +HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D +9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8 +p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD +-----END CERTIFICATE----- + +# Issuer: CN=Telia Root CA v2 O=Telia Finland Oyj +# Subject: CN=Telia Root CA v2 O=Telia Finland Oyj +# Label: "Telia Root CA v2" +# Serial: 7288924052977061235122729490515358 +# MD5 Fingerprint: 0e:8f:ac:aa:82:df:85:b1:f4:dc:10:1c:fc:99:d9:48 +# SHA1 Fingerprint: b9:99:cd:d1:73:50:8a:c4:47:05:08:9c:8c:88:fb:be:a0:2b:40:cd +# SHA256 Fingerprint: 24:2b:69:74:2f:cb:1e:5b:2a:bf:98:89:8b:94:57:21:87:54:4e:5b:4d:99:11:78:65:73:62:1f:6a:74:b8:2c +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx +CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE +AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1 +NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ +MBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq +AMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9 +vVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9 +lRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD +n3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT +7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o +6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC +TEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6 +WT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R +DolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI +pEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj +YzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy +rOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ +8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi +0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM +A8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS +SRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K +TTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF +6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er +3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt +Ty3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT +VmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW +ysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA +rBPuUBQemMc= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH +# Subject: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH +# Label: "D-TRUST BR Root CA 1 2020" +# Serial: 165870826978392376648679885835942448534 +# MD5 Fingerprint: b5:aa:4b:d5:ed:f7:e3:55:2e:8f:72:0a:f3:75:b8:ed +# SHA1 Fingerprint: 1f:5b:98:f0:e3:b5:f7:74:3c:ed:e6:b0:36:7d:32:cd:f4:09:41:67 +# SHA256 Fingerprint: e5:9a:aa:81:60:09:c2:2b:ff:5b:25:ba:d3:7d:f3:06:f0:49:79:7c:1f:81:d8:5a:b0:89:e6:57:bd:8f:00:44 +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5 +NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS +zuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0 +QVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/ +VbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW +wKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV +dWNbFJWcHwHP2NVypw87 +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH +# Subject: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH +# Label: "D-TRUST EV Root CA 1 2020" +# Serial: 126288379621884218666039612629459926992 +# MD5 Fingerprint: 8c:2d:9d:70:9f:48:99:11:06:11:fb:e9:cb:30:c0:6e +# SHA1 Fingerprint: 61:db:8c:21:59:69:03:90:d8:7c:9c:12:86:54:cf:9d:3d:f4:dd:07 +# SHA256 Fingerprint: 08:17:0d:1a:a3:64:53:90:1a:2f:95:92:45:e3:47:db:0c:8d:37:ab:aa:bc:56:b8:1a:a1:00:dc:95:89:70:db +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5 +NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC +/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD +wpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3 +OqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA +y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb +gfM0agPnIjhQW+0ZT0MW +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc. +# Subject: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc. +# Label: "DigiCert TLS ECC P384 Root G5" +# Serial: 13129116028163249804115411775095713523 +# MD5 Fingerprint: d3:71:04:6a:43:1c:db:a6:59:e1:a8:a3:aa:c5:71:ed +# SHA1 Fingerprint: 17:f3:de:5e:9f:0f:19:e9:8e:f6:1f:32:26:6e:20:c4:07:ae:30:ee +# SHA256 Fingerprint: 01:8e:13:f0:77:25:32:cf:80:9b:d1:b1:72:81:86:72:83:fc:48:c6:e1:3b:e9:c6:98:12:85:4a:49:0c:1b:05 +-----BEGIN CERTIFICATE----- +MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp +Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2 +MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ +bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS +7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp +0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS +B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49 +BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ +LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4 +DXZDjC5Ty3zfDBeWUA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc. +# Subject: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc. +# Label: "DigiCert TLS RSA4096 Root G5" +# Serial: 11930366277458970227240571539258396554 +# MD5 Fingerprint: ac:fe:f7:34:96:a9:f2:b3:b4:12:4b:e4:27:41:6f:e1 +# SHA1 Fingerprint: a7:88:49:dc:5d:7c:75:8c:8c:de:39:98:56:b3:aa:d0:b2:a5:71:35 +# SHA256 Fingerprint: 37:1a:00:dc:05:33:b3:72:1a:7e:eb:40:e8:41:9e:70:79:9d:2b:0a:0f:2c:1d:80:69:31:65:f7:ce:c4:ad:75 +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN +MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT +HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN +NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs +IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+ +ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0 +2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp +wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM +pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD +nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po +sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx +Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd +Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX +KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe +XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL +tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv +TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN +AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw +GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H +PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF +O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ +REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik +AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv +/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+ +p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw +MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF +qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK +ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+ +-----END CERTIFICATE----- + +# Issuer: CN=Certainly Root R1 O=Certainly +# Subject: CN=Certainly Root R1 O=Certainly +# Label: "Certainly Root R1" +# Serial: 188833316161142517227353805653483829216 +# MD5 Fingerprint: 07:70:d4:3e:82:87:a0:fa:33:36:13:f4:fa:33:e7:12 +# SHA1 Fingerprint: a0:50:ee:0f:28:71:f4:27:b2:12:6d:6f:50:96:25:ba:cc:86:42:af +# SHA256 Fingerprint: 77:b8:2c:d8:64:4c:43:05:f7:ac:c5:cb:15:6b:45:67:50:04:03:3d:51:c6:0c:62:02:a8:e0:c3:34:67:d3:a0 +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw +PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy +dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9 +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0 +YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2 +1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT +vqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed +aFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0 +1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5 +r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5 +cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ +wHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ +6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA +2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH +Wyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR +eiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB +/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u +d0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr +PbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d +8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi +1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd +rRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di +taY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7 +lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj +yTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn +Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy +yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n +wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6 +OV+KmalBWQewLK8= +-----END CERTIFICATE----- + +# Issuer: CN=Certainly Root E1 O=Certainly +# Subject: CN=Certainly Root E1 O=Certainly +# Label: "Certainly Root E1" +# Serial: 8168531406727139161245376702891150584 +# MD5 Fingerprint: 0a:9e:ca:cd:3e:52:50:c6:36:f3:4b:a3:ed:a7:53:e9 +# SHA1 Fingerprint: f9:e1:6d:dc:01:89:cf:d5:82:45:63:3e:c5:37:7d:c2:eb:93:6f:2b +# SHA256 Fingerprint: b4:58:5f:22:e4:ac:75:6a:4e:86:12:a1:36:1c:5d:9d:03:1a:93:fd:84:fe:bb:77:8f:a3:06:8b:0f:c4:2d:c2 +-----BEGIN CERTIFICATE----- +MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw +CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu +bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ +BgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s +eSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK ++IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2 +QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4 +hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm +ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG +BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR +-----END CERTIFICATE----- + +# Issuer: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD. +# Subject: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD. +# Label: "Security Communication ECC RootCA1" +# Serial: 15446673492073852651 +# MD5 Fingerprint: 7e:43:b0:92:68:ec:05:43:4c:98:ab:5d:35:2e:7e:86 +# SHA1 Fingerprint: b8:0e:26:a9:bf:d2:b2:3b:c0:ef:46:c9:ba:c7:bb:f6:1d:0d:41:41 +# SHA256 Fingerprint: e7:4f:bd:a5:5b:d5:64:c4:73:a3:6b:44:1a:a7:99:c8:a6:8e:07:74:40:e8:28:8b:9f:a1:e5:0e:4b:ba:ca:11 +-----BEGIN CERTIFICATE----- +MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT +AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD +VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx +NjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTELMAkGA1UEBhMCSlAxJTAjBgNVBAoT +HFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNVBAMTIlNlY3VyaXR5 +IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+Cnnfdl +dB9sELLo5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpK +ULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu +9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O +be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k= +-----END CERTIFICATE----- + +# Issuer: CN=BJCA Global Root CA1 O=BEIJING CERTIFICATE AUTHORITY +# Subject: CN=BJCA Global Root CA1 O=BEIJING CERTIFICATE AUTHORITY +# Label: "BJCA Global Root CA1" +# Serial: 113562791157148395269083148143378328608 +# MD5 Fingerprint: 42:32:99:76:43:33:36:24:35:07:82:9b:28:f9:d0:90 +# SHA1 Fingerprint: d5:ec:8d:7b:4c:ba:79:f4:e7:e8:cb:9d:6b:ae:77:83:10:03:21:6a +# SHA256 Fingerprint: f3:89:6f:88:fe:7c:0a:88:27:66:a7:fa:6a:d2:74:9f:b5:7a:7f:3e:98:fb:76:9c:1f:a7:b0:9c:2c:44:d5:ae +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBU +MQswCQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRI +T1JJVFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAz +MTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJF +SUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2Jh +bCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFmCL3Z +xRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZ +spDyRhySsTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O5 +58dnJCNPYwpj9mZ9S1WnP3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgR +at7GGPZHOiJBhyL8xIkoVNiMpTAK+BcWyqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll +5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRjeulumijWML3mG90Vr4Tq +nMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNnMoH1V6XK +V0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/ +pj+bOT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZO +z2nxbkRs1CTqjSShGL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXn +jSXWgXSHRtQpdaJCbPdzied9v3pKH9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+ +WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMBAAGjQjBAMB0GA1UdDgQWBBTF +7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4 +YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3Kli +awLwQ8hOnThJdMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u ++2D2/VnGKhs/I0qUJDAnyIm860Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88 +X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuhTaRjAv04l5U/BXCga99igUOLtFkN +SoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW4AB+dAb/OMRyHdOo +P2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmpGQrI ++pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRz +znfSxqxx4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9 +eVzYH6Eze9mCUAyTF6ps3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2 +YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4SSPfSKcOYKMryMguTjClPPGAyzQWWYezy +r/6zcCwupvI= +-----END CERTIFICATE----- + +# Issuer: CN=BJCA Global Root CA2 O=BEIJING CERTIFICATE AUTHORITY +# Subject: CN=BJCA Global Root CA2 O=BEIJING CERTIFICATE AUTHORITY +# Label: "BJCA Global Root CA2" +# Serial: 58605626836079930195615843123109055211 +# MD5 Fingerprint: 5e:0a:f6:47:5f:a6:14:e8:11:01:95:3f:4d:01:eb:3c +# SHA1 Fingerprint: f4:27:86:eb:6e:b8:6d:88:31:67:02:fb:ba:66:a4:53:00:aa:7a:a6 +# SHA256 Fingerprint: 57:4d:f6:93:1e:27:80:39:66:7b:72:0a:fd:c1:60:0f:c2:7e:b6:6d:d3:09:29:79:fb:73:85:64:87:21:28:82 +-----BEGIN CERTIFICATE----- +MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQsw +CQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJ +VFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgy +MVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJ +TkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2JhbCBS +b290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jlSR9B +IgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK+ ++kpRuDCK/eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJK +sVF/BvDRgh9Obl+rg/xI1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA +94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8gUXOQwKhbYdDFUDn9hf7B +43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w== +-----END CERTIFICATE----- + +# Issuer: CN=Sectigo Public Server Authentication Root E46 O=Sectigo Limited +# Subject: CN=Sectigo Public Server Authentication Root E46 O=Sectigo Limited +# Label: "Sectigo Public Server Authentication Root E46" +# Serial: 88989738453351742415770396670917916916 +# MD5 Fingerprint: 28:23:f8:b2:98:5c:37:16:3b:3e:46:13:4e:b0:b3:01 +# SHA1 Fingerprint: ec:8a:39:6c:40:f0:2e:bc:42:75:d4:9f:ab:1c:1a:5b:67:be:d2:9a +# SHA256 Fingerprint: c9:0f:26:f0:fb:1b:40:18:b2:22:27:51:9b:5c:a2:b5:3e:2c:a5:b3:be:5c:f1:8e:fe:1b:ef:47:38:0c:53:83 +-----BEGIN CERTIFICATE----- +MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw +CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T +ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN +MjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYG +A1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT +ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccC +WvkEN/U0NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+ +6xnOQ6OjQjBAMB0GA1UdDgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8B +Af8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjAn7qRa +qCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q +4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21USAGKcw== +-----END CERTIFICATE----- + +# Issuer: CN=Sectigo Public Server Authentication Root R46 O=Sectigo Limited +# Subject: CN=Sectigo Public Server Authentication Root R46 O=Sectigo Limited +# Label: "Sectigo Public Server Authentication Root R46" +# Serial: 156256931880233212765902055439220583700 +# MD5 Fingerprint: 32:10:09:52:00:d5:7e:6c:43:df:15:c0:b1:16:93:e5 +# SHA1 Fingerprint: ad:98:f9:f3:e4:7d:75:3b:65:d4:82:b3:a4:52:17:bb:6e:f5:e4:38 +# SHA256 Fingerprint: 7b:b6:47:a6:2a:ee:ac:88:bf:25:7a:a5:22:d0:1f:fe:a3:95:e0:ab:45:c7:3f:93:f6:56:54:ec:38:f2:5a:06 +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD +Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw +HhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEY +MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1Ymxp +YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDa +ef0rty2k1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnz +SDBh+oF8HqcIStw+KxwfGExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xf +iOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMPFF1bFOdLvt30yNoDN9HWOaEhUTCDsG3X +ME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vuZDCQOc2TZYEhMbUjUDM3 +IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5QazYw6A3OAS +VYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgE +SJ/AwSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu ++Zd4KKTIRJLpfSYFplhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt +8uaZFURww3y8nDnAtOFr94MlI1fZEoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+L +HaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW6aWWrL3DkJiy4Pmi1KZHQ3xt +zwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWIIUkwDgYDVR0P +AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQ +YKlJfp/imTYpE0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52 +gDY9hAaLMyZlbcp+nv4fjFg4exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZA +Fv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M0ejf5lG5Nkc/kLnHvALcWxxPDkjB +JYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI84HxZmduTILA7rpX +DhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9mpFui +TdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5 +dHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65 +LvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp +0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY +QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com TLS RSA Root CA 2022 O=SSL Corporation +# Subject: CN=SSL.com TLS RSA Root CA 2022 O=SSL Corporation +# Label: "SSL.com TLS RSA Root CA 2022" +# Serial: 148535279242832292258835760425842727825 +# MD5 Fingerprint: d8:4e:c6:59:30:d8:fe:a0:d6:7a:5a:2c:2c:69:78:da +# SHA1 Fingerprint: ec:2c:83:40:72:af:26:95:10:ff:0e:f2:03:ee:31:70:f6:78:9d:ca +# SHA256 Fingerprint: 8f:af:7d:2e:2c:b4:70:9b:b8:e0:b3:36:66:bf:75:a5:dd:45:b5:de:48:0f:8e:a8:d4:bf:e6:be:bc:17:f2:ed +-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO +MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD +DBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX +DTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw +b3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJvb3QgQ0EgMjAyMjCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u9nTP +L3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OY +t6/wNr/y7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0ins +S657Lb85/bRi3pZ7QcacoOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3 +PnxEX4MN8/HdIGkWCVDi1FW24IBydm5MR7d1VVm0U3TZlMZBrViKMWYPHqIbKUBO +L9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDGD6C1vBdOSHtRwvzpXGk3 +R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEWTO6Af77w +dr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS ++YCk8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYS +d66UNHsef8JmAOSqg+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoG +AtUjHBPW6dvbxrB6y3snm/vg1UYk7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2f +gTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j +BBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsuN+7jhHonLs0Z +NbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt +hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsM +QtfhWsSWTVTNj8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvf +R4iyrT7gJ4eLSYwfqUdYe5byiB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJ +DPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjUo3KUQyxi4U5cMj29TH0ZR6LDSeeW +P4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqoENjwuSfr98t67wVy +lrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7EgkaibMOlq +bLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2w +AgDHbICivRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3q +r5nsLFR+jM4uElZI7xc7P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sji +Mho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU +98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com TLS ECC Root CA 2022 O=SSL Corporation +# Subject: CN=SSL.com TLS ECC Root CA 2022 O=SSL Corporation +# Label: "SSL.com TLS ECC Root CA 2022" +# Serial: 26605119622390491762507526719404364228 +# MD5 Fingerprint: 99:d7:5c:f1:51:36:cc:e9:ce:d9:19:2e:77:71:56:c5 +# SHA1 Fingerprint: 9f:5f:d9:1a:54:6d:f5:0c:71:f0:ee:7a:bd:17:49:98:84:73:e2:39 +# SHA256 Fingerprint: c3:2f:fd:9f:46:f9:36:d1:6c:36:73:99:09:59:43:4b:9a:d6:0a:af:bb:9e:7c:f3:36:54:f1:44:cc:1b:a1:43 +-----BEGIN CERTIFICATE----- +MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT +U0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2 +MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3Jh +dGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3QgQ0EgMjAyMjB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWyJGYm +acCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFN +SeR7T5v15wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME +GDAWgBSJjy+j6CugFFR781a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NW +uCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp +15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w7deedWo1dlJF4AIxAMeN +b0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g== +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot Root CA ECC TLS 2021 O=Atos +# Subject: CN=Atos TrustedRoot Root CA ECC TLS 2021 O=Atos +# Label: "Atos TrustedRoot Root CA ECC TLS 2021" +# Serial: 81873346711060652204712539181482831616 +# MD5 Fingerprint: 16:9f:ad:f1:70:ad:79:d6:ed:29:b4:d1:c5:79:70:a8 +# SHA1 Fingerprint: 9e:bc:75:10:42:b3:02:f3:81:f4:f7:30:62:d4:8f:c3:a7:51:b2:dd +# SHA256 Fingerprint: b2:fa:e5:3e:14:cc:d7:ab:92:12:06:47:01:ae:27:9c:1d:89:88:fa:cb:77:5f:a8:a0:08:91:4e:66:39:88:a8 +-----BEGIN CERTIFICATE----- +MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w +LAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w +CwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0 +MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBF +Q0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMHYwEAYHKoZI +zj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6KDP/X +tXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4 +AjJn8ZQSb+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2 +KCXWfeBmmnoJsmo7jjPXNtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMD +aAAwZQIwW5kp85wxtolrbNa9d+F851F+uDrNozZffPc8dz7kUK2o59JZDCaOMDtu +CCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGYa3cpetskz2VAv9LcjBHo +9H1/IISpQuQo +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot Root CA RSA TLS 2021 O=Atos +# Subject: CN=Atos TrustedRoot Root CA RSA TLS 2021 O=Atos +# Label: "Atos TrustedRoot Root CA RSA TLS 2021" +# Serial: 111436099570196163832749341232207667876 +# MD5 Fingerprint: d4:d3:46:b8:9a:c0:9c:76:5d:9e:3a:c3:b9:99:31:d2 +# SHA1 Fingerprint: 18:52:3b:0d:06:37:e4:d6:3a:df:23:e4:98:fb:5b:16:fb:86:74:48 +# SHA256 Fingerprint: 81:a9:08:8e:a5:9f:b3:64:c5:48:a6:f8:55:59:09:9b:6f:04:05:ef:bf:18:e5:32:4e:c9:f4:57:ba:00:11:2f +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM +MS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx +MQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00 +MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBD +QSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BBl01Z +4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYv +Ye+W/CBGvevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZ +kmGbzSoXfduP9LVq6hdKZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDs +GY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt0xU6kGpn8bRrZtkh68rZYnxGEFzedUln +nkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVKPNe0OwANwI8f4UDErmwh +3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMYsluMWuPD +0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzy +geBYBr3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8 +ANSbhqRAvNncTFd+rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezB +c6eUWsuSZIKmAMFwoW4sKeFYV+xafJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lI +pw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +dEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +DAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS +4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPs +o0UvFJ/1TCplQ3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJ +qM7F78PRreBrAwA0JrRUITWXAdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuyw +xfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9GslA9hGCZcbUztVdF5kJHdWoOsAgM +rr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2VktafcxBPTy+av5EzH4 +AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9qTFsR +0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuY +o7Ey7Nmj1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5 +dDTedk+SKlOxJTnbPP/lPqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcE +oji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ== +-----END CERTIFICATE----- + +# Issuer: CN=TrustAsia Global Root CA G3 O=TrustAsia Technologies, Inc. +# Subject: CN=TrustAsia Global Root CA G3 O=TrustAsia Technologies, Inc. +# Label: "TrustAsia Global Root CA G3" +# Serial: 576386314500428537169965010905813481816650257167 +# MD5 Fingerprint: 30:42:1b:b7:bb:81:75:35:e4:16:4f:53:d2:94:de:04 +# SHA1 Fingerprint: 63:cf:b6:c1:27:2b:56:e4:88:8e:1c:23:9a:b6:2e:81:47:24:c3:c7 +# SHA256 Fingerprint: e0:d3:22:6a:eb:11:63:c2:e4:8f:f9:be:3b:50:b4:c6:43:1b:e7:bb:1e:ac:c5:c3:6b:5d:5e:c5:09:03:9a:08 +-----BEGIN CERTIFICATE----- +MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEM +BQAwWjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dp +ZXMsIEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAe +Fw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEwMTlaMFoxCzAJBgNVBAYTAkNOMSUw +IwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtU +cnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNS +T1QY4SxzlZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqK +AtCWHwDNBSHvBm3dIZwZQ0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1 +nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/VP68czH5GX6zfZBCK70bwkPAPLfSIC7Ep +qq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1AgdB4SQXMeJNnKziyhWTXA +yB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm9WAPzJMs +hH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gX +zhqcD0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAv +kV34PmVACxmZySYgWmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msT +f9FkPz2ccEblooV7WIQn3MSAPmeamseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jA +uPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCFTIcQcf+eQxuulXUtgQIDAQAB +o2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj7zjKsK5Xf/Ih +MBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4 +wM8zAQLpw6o1D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2 +XFNFV1pF1AWZLy4jVe5jaN/TG3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1 +JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNjduMNhXJEIlU/HHzp/LgV6FL6qj6j +ITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstlcHboCoWASzY9M/eV +VHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys+TIx +xHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1on +AX1daBli2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d +7XB4tmBZrOFdRWOPyN9yaFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2Ntjj +gKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsASZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV ++Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFRJQJ6+N1rZdVtTTDIZbpo +FGWsJwt0ivKH +-----END CERTIFICATE----- + +# Issuer: CN=TrustAsia Global Root CA G4 O=TrustAsia Technologies, Inc. +# Subject: CN=TrustAsia Global Root CA G4 O=TrustAsia Technologies, Inc. +# Label: "TrustAsia Global Root CA G4" +# Serial: 451799571007117016466790293371524403291602933463 +# MD5 Fingerprint: 54:dd:b2:d7:5f:d8:3e:ed:7c:e0:0b:2e:cc:ed:eb:eb +# SHA1 Fingerprint: 57:73:a5:61:5d:80:b2:e6:ac:38:82:fc:68:07:31:ac:9f:b5:92:5a +# SHA256 Fingerprint: be:4b:56:cb:50:56:c0:13:6a:52:6d:f4:44:50:8d:aa:36:a0:b5:4f:42:e4:ac:38:f7:2a:f4:70:e4:79:65:4c +-----BEGIN CERTIFICATE----- +MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMw +WjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs +IEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0y +MTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJaMFoxCzAJBgNVBAYTAkNOMSUwIwYD +VQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtUcnVz +dEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATx +s8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbw +LxYI+hW8m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJij +YzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mD +pm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/pDHel4NZg6ZvccveMA4GA1UdDwEB/wQE +AwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AAbbd+NvBNEU/zy4k6LHiR +UKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xkdUfFVZDj +/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA== +-----END CERTIFICATE----- + +# Issuer: CN=Telekom Security TLS ECC Root 2020 O=Deutsche Telekom Security GmbH +# Subject: CN=Telekom Security TLS ECC Root 2020 O=Deutsche Telekom Security GmbH +# Label: "Telekom Security TLS ECC Root 2020" +# Serial: 72082518505882327255703894282316633856 +# MD5 Fingerprint: c1:ab:fe:6a:10:2c:03:8d:bc:1c:22:32:c0:85:a7:fd +# SHA1 Fingerprint: c0:f8:96:c5:a9:3b:01:06:21:07:da:18:42:48:bc:e9:9d:88:d5:ec +# SHA256 Fingerprint: 57:8a:f4:de:d0:85:3f:4e:59:98:db:4a:ea:f9:cb:ea:8d:94:5f:60:b6:20:a3:8d:1a:3c:13:b2:bc:7b:a8:e1 +-----BEGIN CERTIFICATE----- +MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQsw +CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH +bWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIw +MB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIzNTk1OVowYzELMAkGA1UEBhMCREUx +JzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkGA1UE +AwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/O +tdKPD/M12kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDP +f8iAC8GXs7s1J8nCG6NCMEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6f +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA +MGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZMo7k+5Dck2TOrbRBR2Di +z6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdUga/sf+Rn +27iQ7t0l +-----END CERTIFICATE----- + +# Issuer: CN=Telekom Security TLS RSA Root 2023 O=Deutsche Telekom Security GmbH +# Subject: CN=Telekom Security TLS RSA Root 2023 O=Deutsche Telekom Security GmbH +# Label: "Telekom Security TLS RSA Root 2023" +# Serial: 44676229530606711399881795178081572759 +# MD5 Fingerprint: bf:5b:eb:54:40:cd:48:71:c4:20:8d:7d:de:0a:42:f2 +# SHA1 Fingerprint: 54:d3:ac:b3:bd:57:56:f6:85:9d:ce:e5:c3:21:e2:d4:ad:83:d0:93 +# SHA256 Fingerprint: ef:c6:5c:ad:bb:59:ad:b6:ef:e8:4d:a2:23:11:b3:56:24:b7:1b:3b:1e:a0:da:8b:66:55:17:4e:c8:97:86:46 +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBj +MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0 +eSBHbWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAy +MDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMyNzIzNTk1OVowYzELMAkGA1UEBhMC +REUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkG +A1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9 +cUD/h3VCKSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHV +cp6R+SPWcHu79ZvB7JPPGeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMA +U6DksquDOFczJZSfvkgdmOGjup5czQRxUX11eKvzWarE4GC+j4NSuHUaQTXtvPM6 +Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWol8hHD/BeEIvnHRz+sTug +BTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9FIS3R/qy +8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73J +co4vzLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg +8qKrBC7m8kwOFjQgrIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8 +rFEz0ciD0cmfHdRHNCk+y7AO+oMLKFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12 +mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7SWWO/gLCMk3PLNaaZlSJhZQNg ++y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtqeX +gj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2 +p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQ +pGv7qHBFfLp+sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm +9S3ul0A8Yute1hTWjOKWi0FpkzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErw +M807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy/SKE8YXJN3nptT+/XOR0so8RYgDd +GGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4mZqTuXNnQkYRIer+ +CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtzaL1t +xKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+ +w6jv/naaoqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aK +L4x35bcF7DvB7L6Gs4a8wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+lj +X273CXE2whJdV/LItM3z7gLfEdxquVeEHVlNjM7IDiPCtyaaEBRx/pOyiriA8A4Q +ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm +dTdmQRCsu/WU48IxK63nI1bMNSWSs1A= +-----END CERTIFICATE----- + +# Issuer: CN=TWCA CYBER Root CA O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA CYBER Root CA O=TAIWAN-CA OU=Root CA +# Label: "TWCA CYBER Root CA" +# Serial: 85076849864375384482682434040119489222 +# MD5 Fingerprint: 0b:33:a0:97:52:95:d4:a9:fd:bb:db:6e:a3:55:5b:51 +# SHA1 Fingerprint: f6:b1:1c:1a:83:38:e9:7b:db:b3:a8:c8:33:24:e0:2d:9c:7f:26:66 +# SHA256 Fingerprint: 3f:63:bb:28:14:be:17:4e:c8:b6:43:9c:f0:8d:6d:56:f0:b7:c4:05:88:3a:56:48:a3:34:42:4d:6b:3e:c5:58 +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQ +MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290 +IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5 +WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FO +LUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1sTs6P +40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxF +avcokPFhV8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/ +34bKS1PE2Y2yHer43CdTo0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684i +JkXXYJndzk834H/nY62wuFm40AZoNWDTNq5xQwTxaWV4fPMf88oon1oglWa0zbfu +j3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK/c/WMw+f+5eesRycnupf +Xtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkHIuNZW0CP +2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDA +S9TMfAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDA +oS/xUgXJP+92ZuJF2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzC +kHDXShi8fgGwsOsVHkQGzaRP6AzRwyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW +5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83QOGt4A1WNzAd +BgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB +AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0t +tGlTITVX1olNc79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn +68xDiBaiA9a5F/gZbG0jAn/xX9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNn +TKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDRIG4kqIQnoVesqlVYL9zZyvpoBJ7t +RCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq/p1hvIbZv97Tujqx +f36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0RFxbI +Qh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz +8ppy6rBePm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4 +NxKfKjLji7gh7MMrZQzvIt6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzX +xeSDwWrruoBa3lwtcHb4yOWHh8qgnaHlIhInD0Q9HWzq1MKLL295q39QpsQZp6F6 +t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X +-----END CERTIFICATE----- + +# Issuer: CN=SecureSign Root CA12 O=Cybertrust Japan Co., Ltd. +# Subject: CN=SecureSign Root CA12 O=Cybertrust Japan Co., Ltd. +# Label: "SecureSign Root CA12" +# Serial: 587887345431707215246142177076162061960426065942 +# MD5 Fingerprint: c6:89:ca:64:42:9b:62:08:49:0b:1e:7f:e9:07:3d:e8 +# SHA1 Fingerprint: 7a:22:1e:3d:de:1b:06:ac:9e:c8:47:70:16:8e:3c:e5:f7:6b:06:f4 +# SHA256 Fingerprint: 3f:03:4b:b5:70:4d:44:b2:d0:85:45:a0:20:57:de:93:eb:f3:90:5f:ce:72:1a:cb:c7:30:c0:6d:da:ee:90:4e +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u +LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgw +NTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD +eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS +b290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3emhF +KxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mt +p7JIKwccJ/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zd +J1M3s6oYwlkm7Fsf0uZlfO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gur +FzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBFEaCeVESE99g2zvVQR9wsMJvuwPWW0v4J +hscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1UefNzFJM3IFTQy2VYzxV4+K +h9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsF +AAOCAQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6Ld +mmQOmFxv3Y67ilQiLUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJ +mBClnW8Zt7vPemVV2zfrPIpyMpcemik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA +8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPSvWKErI4cqc1avTc7bgoitPQV +55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhgaaaI5gdka9at/ +yOPiZwud9AzqVN/Ssq+xIvEg37xEHA== +-----END CERTIFICATE----- + +# Issuer: CN=SecureSign Root CA14 O=Cybertrust Japan Co., Ltd. +# Subject: CN=SecureSign Root CA14 O=Cybertrust Japan Co., Ltd. +# Label: "SecureSign Root CA14" +# Serial: 575790784512929437950770173562378038616896959179 +# MD5 Fingerprint: 71:0d:72:fa:92:19:65:5e:89:04:ac:16:33:f0:bc:d5 +# SHA1 Fingerprint: dd:50:c0:f7:79:b3:64:2e:74:a2:b8:9d:9f:d3:40:dd:bb:f0:f2:4f +# SHA256 Fingerprint: 4b:00:9c:10:34:49:4f:9a:b5:6b:ba:3b:a1:d6:27:31:fc:4d:20:d8:95:5a:dc:ec:10:a9:25:60:72:61:e3:38 +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEM +BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u +LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgw +NzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD +eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS +b290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh1oq/ +FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOg +vlIfX8xnbacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy +6pJxaeQp8E+BgQQ8sqVb1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo +/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9J +kdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOEkJTRX45zGRBdAuVwpcAQ +0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSxjVIHvXib +y8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac +18izju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs +0Wq2XSqypWa9a4X0dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIAB +SMbHdPTGrMNASRZhdCyvjG817XsYAFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVL +ApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeqYR3r6/wtbyPk +86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E +rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ib +ed87hwriZLoAymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopT +zfFP7ELyk+OZpDc8h7hi2/DsHzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHS +DCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPGFrojutzdfhrGe0K22VoF3Jpf1d+4 +2kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6qnsb58Nn4DSEC5MUo +FlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/OfVy +K4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6 +dB7h7sxaOgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtl +Lor6CZpO2oYofaphNdgOpygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB +365jJ6UeTo3cKXhZ+PmhIIynJkBugnLNeLLIjzwec+fBH7/PzqUqm9tEZDKgu39c +JRNItX+S +-----END CERTIFICATE----- + +# Issuer: CN=SecureSign Root CA15 O=Cybertrust Japan Co., Ltd. +# Subject: CN=SecureSign Root CA15 O=Cybertrust Japan Co., Ltd. +# Label: "SecureSign Root CA15" +# Serial: 126083514594751269499665114766174399806381178503 +# MD5 Fingerprint: 13:30:fc:c4:62:a6:a9:de:b5:c1:68:af:b5:d2:31:47 +# SHA1 Fingerprint: cb:ba:83:c8:c1:5a:5d:f1:f9:73:6f:ca:d7:ef:28:13:06:4a:07:7d +# SHA256 Fingerprint: e7:78:f0:f0:95:fe:84:37:29:cd:1a:00:82:17:9e:53:14:a9:c2:91:44:28:05:e1:fb:1d:8f:b6:b8:88:6c:3a +-----BEGIN CERTIFICATE----- +MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMw +UTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBM +dGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMy +NTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJl +cnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBSb290 +IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5GdCx4 +wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSR +ZHX+AezB2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT +9DAKBggqhkjOPQQDAwNoADBlAjEA2S6Jfl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp +4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJSwdLZrWeqrqgHkHZAXQ6 +bkU6iYAZezKYVWOr62Nuk22rGwlgMU4= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST BR Root CA 2 2023 O=D-Trust GmbH +# Subject: CN=D-TRUST BR Root CA 2 2023 O=D-Trust GmbH +# Label: "D-TRUST BR Root CA 2 2023" +# Serial: 153168538924886464690566649552453098598 +# MD5 Fingerprint: e1:09:ed:d3:60:d4:56:1b:47:1f:b7:0c:5f:1b:5f:85 +# SHA1 Fingerprint: 2d:b0:70:ee:71:94:af:69:68:17:db:79:ce:58:9f:a0:6b:96:f7:87 +# SHA256 Fingerprint: 05:52:e6:f8:3f:df:65:e8:fa:96:70:e6:66:df:28:a4:e2:13:40:b5:10:cb:e5:25:66:f9:7c:4f:b9:4b:2b:d1 +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBI +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE +LVRSVVNUIEJSIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUw +OTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi +MCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCTcfKr +i3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNE +gXtRr90zsWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8 +k12b9py0i4a6Ibn08OhZWiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCT +Rphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl +2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LULQyReS2tNZ9/WtT5PeB+U +cSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIvx9gvdhFP +/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bS +uREVMweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+ +0bpwHJwh5Q8xaRfX/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4N +DfTisl01gLmB1IRpkQLLddCNxbU9CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+ +XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUZ5Dw1t61 +GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG +OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tI +FoE9c+CeJyrrd6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67n +riv6uvw8l5VAk1/DLQOj7aRvU9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTR +VFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4nj8+AybmTNudX0KEPUUDAxxZiMrc +LmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdijYQ6qgYF/6FKC0ULn +4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff/vtD +hQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsG +koHU6XCPpz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46 +ls/pdu4D58JDUjxqgejBWoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aS +Ecr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/5usWDiJFAbzdNpQ0qTUmiteXue4Icr80 +knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jtn/mtd+ArY0+ew+43u3gJ +hJ65bvspmZDogNOfJA== +-----END CERTIFICATE----- + +# Issuer: CN=TrustAsia TLS ECC Root CA O=TrustAsia Technologies, Inc. +# Subject: CN=TrustAsia TLS ECC Root CA O=TrustAsia Technologies, Inc. +# Label: "TrustAsia TLS ECC Root CA" +# Serial: 310892014698942880364840003424242768478804666567 +# MD5 Fingerprint: 09:48:04:77:d2:fc:65:93:71:66:b1:11:95:4f:06:8c +# SHA1 Fingerprint: b5:ec:39:f3:a1:66:37:ae:c3:05:94:57:e2:be:11:be:b7:a1:7f:36 +# SHA256 Fingerprint: c0:07:6b:9e:f0:53:1f:b1:a6:56:d6:7c:4e:be:97:cd:5d:ba:a4:1e:f4:45:98:ac:c2:48:98:78:c9:2d:87:11 +-----BEGIN CERTIFICATE----- +MIICMTCCAbegAwIBAgIUNnThTXxlE8msg1UloD5Sfi9QaMcwCgYIKoZIzj0EAwMw +WDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs +IEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgRUNDIFJvb3QgQ0EwHhcNMjQw +NTE1MDU0MTU2WhcNNDQwNTE1MDU0MTU1WjBYMQswCQYDVQQGEwJDTjElMCMGA1UE +ChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1c3RB +c2lhIFRMUyBFQ0MgUm9vdCBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLh/pVs/ +AT598IhtrimY4ZtcU5nb9wj/1WrgjstEpvDBjL1P1M7UiFPoXlfXTr4sP/MSpwDp +guMqWzJ8S5sUKZ74LYO1644xST0mYekdcouJtgq7nDM1D9rs3qlKH8kzsaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQULIVTu7FDzTLqnqOH/qKYqKaT6RAw +DgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMFRH18MtYYZI9HlaVQ01 +L18N9mdsd0AaRuf4aFtOJx24mH1/k78ITcTaRTChD15KeAIxAKORh/IRM4PDwYqR +OkwrULG9IpRdNYlzg8WbGf60oenUoWa2AaU2+dhoYSi3dOGiMQ== +-----END CERTIFICATE----- + +# Issuer: CN=TrustAsia TLS RSA Root CA O=TrustAsia Technologies, Inc. +# Subject: CN=TrustAsia TLS RSA Root CA O=TrustAsia Technologies, Inc. +# Label: "TrustAsia TLS RSA Root CA" +# Serial: 160405846464868906657516898462547310235378010780 +# MD5 Fingerprint: 3b:9e:c3:86:0f:34:3c:6b:c5:46:c4:8e:1d:e7:19:12 +# SHA1 Fingerprint: a5:46:50:c5:62:ea:95:9a:1a:a7:04:6f:17:58:c7:29:53:3d:03:fa +# SHA256 Fingerprint: 06:c0:8d:7d:af:d8:76:97:1e:b1:12:4f:e6:7f:84:7e:c0:c7:a1:58:d3:ea:53:cb:e9:40:e2:ea:97:91:f4:c3 +-----BEGIN CERTIFICATE----- +MIIFgDCCA2igAwIBAgIUHBjYz+VTPyI1RlNUJDxsR9FcSpwwDQYJKoZIhvcNAQEM +BQAwWDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dp +ZXMsIEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgUlNBIFJvb3QgQ0EwHhcN +MjQwNTE1MDU0MTU3WhcNNDQwNTE1MDU0MTU2WjBYMQswCQYDVQQGEwJDTjElMCMG +A1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1 +c3RBc2lhIFRMUyBSU0EgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC +AgoCggIBAMMWuBtqpERz5dZO9LnPWwvB0ZqB9WOwj0PBuwhaGnrhB3YmH49pVr7+ +NmDQDIPNlOrnxS1cLwUWAp4KqC/lYCZUlviYQB2srp10Zy9U+5RjmOMmSoPGlbYJ +Q1DNDX3eRA5gEk9bNb2/mThtfWza4mhzH/kxpRkQcwUqwzIZheo0qt1CHjCNP561 +HmHVb70AcnKtEj+qpklz8oYVlQwQX1Fkzv93uMltrOXVmPGZLmzjyUT5tUMnCE32 +ft5EebuyjBza00tsLtbDeLdM1aTk2tyKjg7/D8OmYCYozza/+lcK7Fs/6TAWe8Tb +xNRkoDD75f0dcZLdKY9BWN4ArTr9PXwaqLEX8E40eFgl1oUh63kd0Nyrz2I8sMeX +i9bQn9P+PN7F4/w6g3CEIR0JwqH8uyghZVNgepBtljhb//HXeltt08lwSUq6HTrQ +UNoyIBnkiz/r1RYmNzz7dZ6wB3C4FGB33PYPXFIKvF1tjVEK2sUYyJtt3LCDs3+j +TnhMmCWr8n4uIF6CFabW2I+s5c0yhsj55NqJ4js+k8UTav/H9xj8Z7XvGCxUq0DT +bE3txci3OE9kxJRMT6DNrqXGJyV1J23G2pyOsAWZ1SgRxSHUuPzHlqtKZFlhaxP8 +S8ySpg+kUb8OWJDZgoM5pl+z+m6Ss80zDoWo8SnTq1mt1tve1CuBAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLgHkXlcBvRG/XtZylomkadFK/hT +MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAIZtqBSBdGBanEqT3 +Rz/NyjuujsCCztxIJXgXbODgcMTWltnZ9r96nBO7U5WS/8+S4PPFJzVXqDuiGev4 +iqME3mmL5Dw8veWv0BIb5Ylrc5tvJQJLkIKvQMKtuppgJFqBTQUYo+IzeXoLH5Pt +7DlK9RME7I10nYEKqG/odv6LTytpEoYKNDbdgptvT+Bz3Ul/KD7JO6NXBNiT2Twp +2xIQaOHEibgGIOcberyxk2GaGUARtWqFVwHxtlotJnMnlvm5P1vQiJ3koP26TpUJ +g3933FEFlJ0gcXax7PqJtZwuhfG5WyRasQmr2soaB82G39tp27RIGAAtvKLEiUUj +pQ7hRGU+isFqMB3iYPg6qocJQrmBktwliJiJ8Xw18WLK7nn4GS/+X/jbh87qqA8M +pugLoDzga5SYnH+tBuYc6kIQX+ImFTw3OffXvO645e8D7r0i+yiGNFjEWn9hongP +XvPKnbwbPKfILfanIhHKA9jnZwqKDss1jjQ52MjqjZ9k4DewbNfFj8GQYSbbJIwe +SsCI3zWQzj8C9GRh3sfIB5XeMhg6j6JCQCTl1jNdfK7vsU1P1FeQNWrcrgSXSYk0 +ly4wBOeY99sLAZDBHwo/+ML+TvrbmnNzFrwFuHnYWa8G5z9nODmxfKuU4CkUpijy +323imttUQ/hHWKNddBWcwauwxzQ= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST EV Root CA 2 2023 O=D-Trust GmbH +# Subject: CN=D-TRUST EV Root CA 2 2023 O=D-Trust GmbH +# Label: "D-TRUST EV Root CA 2 2023" +# Serial: 139766439402180512324132425437959641711 +# MD5 Fingerprint: 96:b4:78:09:f0:09:cb:77:eb:bb:1b:4d:6f:36:bc:b6 +# SHA1 Fingerprint: a5:5b:d8:47:6c:8f:19:f7:4c:f4:6d:6b:b6:c2:79:82:22:df:54:8b +# SHA256 Fingerprint: 8e:82:21:b2:e7:d4:00:78:36:a1:67:2f:0d:cc:29:9c:33:bc:07:d3:16:f1:32:fa:1a:20:6d:58:71:50:f1:ce +-----BEGIN CERTIFICATE----- +MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBI +MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE +LVRSVVNUIEVWIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUw +OTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi +MCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1sJkK +F8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE +7CUXFId/MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFe +EMbsh2aJgWi6zCudR3Mfvc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6 +lHPTGGkKSv/BAQP/eX+1SH977ugpbzZMlWGG2Pmic4ruri+W7mjNPU0oQvlFKzIb +RlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3YG14C8qKXO0elg6DpkiV +jTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq9107PncjLgc +jmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZx +TnXonMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+ +ARZZaBhDM7DS3LAaQzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nk +hbDhezGdpn9yo7nELC7MmVcOIQxFAZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knF +NXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUqvyREBuH +kV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG +OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y +XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14 +QvBukEdHjqOSMo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4 +pZt+UPJ26oUFKidBK7GB0aL2QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q +3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xDUmPBEcrCRbH0O1P1aa4846XerOhU +t7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V4U/M5d40VxDJI3IX +cI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuodNv8 +ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT +2vFp4LJiTZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs +7dpn1mKmS00PaaLJvOwiS5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNP +gofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAst +Nl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L+KIkBI3Y4WNeApI02phh +XBxvWHZks/wCuPWdCg== +-----END CERTIFICATE----- + +# Issuer: CN=SwissSign RSA TLS Root CA 2022 - 1 O=SwissSign AG +# Subject: CN=SwissSign RSA TLS Root CA 2022 - 1 O=SwissSign AG +# Label: "SwissSign RSA TLS Root CA 2022 - 1" +# Serial: 388078645722908516278762308316089881486363258315 +# MD5 Fingerprint: 16:2e:e4:19:76:81:85:ba:8e:91:58:f1:15:ef:72:39 +# SHA1 Fingerprint: 81:34:0a:be:4c:cd:ce:cc:e7:7d:cc:8a:d4:57:e2:45:a0:77:5d:ce +# SHA256 Fingerprint: 19:31:44:f4:31:e0:fd:db:74:07:17:d4:de:92:6a:57:11:33:88:4b:43:60:d3:0e:27:29:13:cb:e6:60:ce:41 +-----BEGIN CERTIFICATE----- +MIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UE +AxMiU3dpc3NTaWduIFJTQSBUTFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgx +MTA4MjJaFw00NzA2MDgxMTA4MjJaMFExCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxT +d2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0EgVExTIFJvb3QgQ0Eg +MjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLKmjiC8NX +vDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFwB9+zBvKK8i5VUXu7 +LCTLf5ImgKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t8qsCLqSX +5XH8irCRIFucdFJtrhUnWXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyE +EPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlfGUEGjw5NBuBwQCMBauTLE5tzrE0USJIt +/m2n+IdreXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36qOTw7D59Ke4LKa2/KIj4x +0LDQKhySio/YGZxH5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLOEGrOyvi5 +KaM2iYauC8BPY7kGWUleDsFpswrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM +0ZPlEuRU2j7yrTrePjxF8CgPBrnh25d7mUWe3f6VWQQvdT/TromZhqwUtKiE+shd +OxtYk8EXlFXIC+OCeYSf8wCENO7cMdWP8vpPlkwGqnj73mSiI80fPsWMvDdUDrta +clXvyFu1cvh43zcgTFeRc5JzrBh3Q4IgaezprClG5QtO+DdziZaKHG29777YtvTK +wP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQABo2MwYTAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow4UD2p8P98Q+4 +DxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQEL +BQADggIBAKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO3 +10aewCoSPY6WlkDfDDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgz +Hqp41eZUBDqyggmNzhYzWUUo8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQ +iJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK50Zpy1FVCypM9fJkT6lc/2cyjlUtMoIc +gC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8liNr3CjlvrzG4ngRhZi0Rjn9UM +ZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAWpO2Whi4Z2L6MOuhF +LhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3PXtpOpvJp +zv1/THfQwUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/Td +Ao9QAwKxuDdollDruF/UKIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0 +rk4N3hY9A4GzJl5LuEsAz/+MF7psYC0nhzck5npgL7XTgwSqT0N1osGDsieYK7EO +gLrAhV5Cud+xYJHT6xh+cHiudoO+cVrQkOPKwRYlZ0rwtnu64ZzZ +-----END CERTIFICATE----- + +# Issuer: CN=OISTE Server Root ECC G1 O=OISTE Foundation +# Subject: CN=OISTE Server Root ECC G1 O=OISTE Foundation +# Label: "OISTE Server Root ECC G1" +# Serial: 47819833811561661340092227008453318557 +# MD5 Fingerprint: 42:a7:d2:35:ae:02:92:db:19:76:08:de:2f:05:b4:d4 +# SHA1 Fingerprint: 3b:f6:8b:09:ae:2a:92:7b:ba:e3:8d:3f:11:95:d9:e6:44:0c:45:e2 +# SHA256 Fingerprint: ee:c9:97:c0:c3:0f:21:6f:7e:3b:8b:30:7d:2b:ae:42:41:2d:75:3f:c8:21:9d:af:d1:52:0b:25:72:85:0f:49 +-----BEGIN CERTIFICATE----- +MIICNTCCAbqgAwIBAgIQI/nD1jWvjyhLH/BU6n6XnTAKBggqhkjOPQQDAzBLMQsw +CQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwY +T0lTVEUgU2VydmVyIFJvb3QgRUNDIEcxMB4XDTIzMDUzMTE0NDIyOFoXDTQ4MDUy +NDE0NDIyN1owSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5kYXRp +b24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IEVDQyBHMTB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABBcv+hK8rBjzCvRE1nZCnrPoH7d5qVi2+GXROiFPqOujvqQy +cvO2Ackr/XeFblPdreqqLiWStukhEaivtUwL85Zgmjvn6hp4LrQ95SjeHIC6XG4N +2xml4z+cKrhAS93mT6NjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBQ3 +TYhlz/w9itWj8UnATgwQb0K0nDAdBgNVHQ4EFgQUN02IZc/8PYrVo/FJwE4MEG9C +tJwwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2kAMGYCMQCpKjAd0MKfkFFR +QD6VVCHNFmb3U2wIFjnQEnx/Yxvf4zgAOdktUyBFCxxgZzFDJe0CMQCSia7pXGKD +YmH5LVerVrkR3SW+ak5KGoJr3M/TvEqzPNcum9v4KGm8ay3sMaE641c= +-----END CERTIFICATE----- + +# Issuer: CN=OISTE Server Root RSA G1 O=OISTE Foundation +# Subject: CN=OISTE Server Root RSA G1 O=OISTE Foundation +# Label: "OISTE Server Root RSA G1" +# Serial: 113845518112613905024960613408179309848 +# MD5 Fingerprint: 23:a7:9e:d4:70:b8:b9:14:57:41:8a:7e:44:59:e2:68 +# SHA1 Fingerprint: f7:00:34:25:94:88:68:31:e4:34:87:3f:70:fe:86:b3:86:9f:f0:6e +# SHA256 Fingerprint: 9a:e3:62:32:a5:18:9f:fd:db:35:3d:fd:26:52:0c:01:53:95:d2:27:77:da:c5:9d:b5:7b:98:c0:89:a6:51:e6 +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIQVaXZZ5Qoxu0M+ifdWwFNGDANBgkqhkiG9w0BAQwFADBL +MQswCQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UE +AwwYT0lTVEUgU2VydmVyIFJvb3QgUlNBIEcxMB4XDTIzMDUzMTE0MzcxNloXDTQ4 +MDUyNDE0MzcxNVowSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5k +YXRpb24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IFJTQSBHMTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAKqu9KuCz/vlNwvn1ZatkOhLKdxVYOPM +vLO8LZK55KN68YG0nnJyQ98/qwsmtO57Gmn7KNByXEptaZnwYx4M0rH/1ow00O7b +rEi56rAUjtgHqSSY3ekJvqgiG1k50SeH3BzN+Puz6+mTeO0Pzjd8JnduodgsIUzk +ik/HEzxux9UTl7Ko2yRpg1bTacuCErudG/L4NPKYKyqOBGf244ehHa1uzjZ0Dl4z +O8vbUZeUapU8zhhabkvG/AePLhq5SvdkNCncpo1Q4Y2LS+VIG24ugBA/5J8bZT8R +tOpXaZ+0AOuFJJkk9SGdl6r7NH8CaxWQrbueWhl/pIzY+m0o/DjH40ytas7ZTpOS +jswMZ78LS5bOZmdTaMsXEY5Z96ycG7mOaES3GK/m5Q9l3JUJsJMStR8+lKXHiHUh +sd4JJCpM4rzsTGdHwimIuQq6+cF0zowYJmXa92/GjHtoXAvuY8BeS/FOzJ8vD+Ho +mnqT8eDI278n5mUpezbgMxVz8p1rhAhoKzYHKyfMeNhqhw5HdPSqoBNdZH702xSu ++zrkL8Fl47l6QGzwBrd7KJvX4V84c5Ss2XCTLdyEr0YconosP4EmQufU2MVshGYR +i3drVByjtdgQ8K4p92cIiBdcuJd5z+orKu5YM+Vt6SmqZQENghPsJQtdLEByFSnT +kCz3GkPVavBpAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU +8snBDw1jALvsRQ5KH7WxszbNDo0wHQYDVR0OBBYEFPLJwQ8NYwC77EUOSh+1sbM2 +zQ6NMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQwFAAOCAgEANGd5sjrG5T33 +I3K5Ce+SrScfoE4KsvXaFwyihdJ+klH9FWXXXGtkFu6KRcoMQzZENdl//nk6HOjG +5D1rd9QhEOP28yBOqb6J8xycqd+8MDoX0TJD0KqKchxRKEzdNsjkLWd9kYccnbz8 +qyiWXmFcuCIzGEgWUOrKL+mlSdx/PKQZvDatkuK59EvV6wit53j+F8Bdh3foZ3dP +AGav9LEDOr4SfEE15fSmG0eLy3n31r8Xbk5l8PjaV8GUgeV6Vg27Rn9vkf195hfk +gSe7BYhW3SCl95gtkRlpMV+bMPKZrXJAlszYd2abtNUOshD+FKrDgHGdPY3ofRRs +YWSGRqbXVMW215AWRqWFyp464+YTFrYVI8ypKVL9AMb2kI5Wj4kI3Zaq5tNqqYY1 +9tVFeEJKRvwDyF7YZvZFZSS0vod7VSCd9521Kvy5YhnLbDuv0204bKt7ph6N/Ome +/msVuduCmsuY33OhkKCgxeDoAaijFJzIwZqsFVAzje18KotzlUBDJvyBpCpfOZC3 +J8tRd/iWkx7P8nd9H0aTolkelUTFLXVksNb54Dxp6gS1HAviRkRNQzuXSXERvSS2 +wq1yVAb+axj5d9spLFKebXd7Yv0PTY6YMjAwcRLWJTXjn/hvnLXrahut6hDTlhZy +BiElxky8j3C7DOReIoMt0r7+hVu05L0= +-----END CERTIFICATE----- + +# Issuer: CN=e-Szigno TLS Root CA 2023 O=Microsec Ltd. +# Subject: CN=e-Szigno TLS Root CA 2023 O=Microsec Ltd. +# Label: "e-Szigno TLS Root CA 2023" +# Serial: 71934828665710877219916191754 +# MD5 Fingerprint: 6a:e9:99:74:a5:da:5e:f1:d9:2e:f2:c8:d1:86:8b:71 +# SHA1 Fingerprint: 6f:9a:d5:d5:df:e8:2c:eb:be:37:07:ee:4f:4f:52:58:29:41:d1:fe +# SHA256 Fingerprint: b4:91:41:50:2d:00:66:3d:74:0f:2e:7e:c3:40:c5:28:00:96:26:66:12:1a:36:d0:9c:f7:dd:2b:90:38:4f:b4 +-----BEGIN CERTIFICATE----- +MIICzzCCAjGgAwIBAgINAOhvGHvWOWuYSkmYCjAKBggqhkjOPQQDBDB1MQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xFzAVBgNVBGEMDlZBVEhVLTIzNTg0NDk3MSIwIAYDVQQDDBllLVN6aWdubyBU +TFMgUm9vdCBDQSAyMDIzMB4XDTIzMDcxNzE0MDAwMFoXDTM4MDcxNzE0MDAwMFow +dTELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRYwFAYDVQQKDA1NaWNy +b3NlYyBMdGQuMRcwFQYDVQRhDA5WQVRIVS0yMzU4NDQ5NzEiMCAGA1UEAwwZZS1T +emlnbm8gVExTIFJvb3QgQ0EgMjAyMzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAE +AGgP36J8PKp0iGEKjcJMpQEiFNT3YHdCnAo4YKGMZz6zY+n6kbCLS+Y53wLCMAFS +AL/fjO1ZrTJlqwlZULUZwmgcAOAFX9pQJhzDrAQixTpN7+lXWDajwRlTEArRzT/v +SzUaQ49CE0y5LBqcvjC2xN7cS53kpDzLLtmt3999Cd8ukv+ho2MwYTAPBgNVHRMB +Af8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUWYQCYlpGePVd3I8K +ECgj3NXW+0UwHwYDVR0jBBgwFoAUWYQCYlpGePVd3I8KECgj3NXW+0UwCgYIKoZI +zj0EAwQDgYsAMIGHAkIBLdqu9S54tma4n7Zwf2Z0z+yOfP7AAXmazlIC58PRDHpt +y7Ve7hekm9sEdu4pKeiv+62sUvTXK9Z3hBC9xdIoaDQCQTV2WnXzkoYI9bIeCvZl +C9p2x1L/Cx6AcCIwwzPbGO2E14vs7dOoY4G1VnxHx1YwlGhza9IuqbnZLBwpvQy6 +uWWL +-----END CERTIFICATE----- diff --git a/intelliaide/vendor/certifi/core.py b/intelliaide/vendor/certifi/core.py new file mode 100644 index 0000000..1c9661c --- /dev/null +++ b/intelliaide/vendor/certifi/core.py @@ -0,0 +1,83 @@ +""" +certifi.py +~~~~~~~~~~ + +This module returns the installation location of cacert.pem or its contents. +""" +import sys +import atexit + +def exit_cacert_ctx() -> None: + _CACERT_CTX.__exit__(None, None, None) # type: ignore[union-attr] + + +if sys.version_info >= (3, 11): + + from importlib.resources import as_file, files + + _CACERT_CTX = None + _CACERT_PATH = None + + def where() -> str: + # This is slightly terrible, but we want to delay extracting the file + # in cases where we're inside of a zipimport situation until someone + # actually calls where(), but we don't want to re-extract the file + # on every call of where(), so we'll do it once then store it in a + # global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you to + # manage the cleanup of this file, so it doesn't actually return a + # path, it returns a context manager that will give you the path + # when you enter it and will do any cleanup when you leave it. In + # the common case of not needing a temporary file, it will just + # return the file system location and the __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. + _CACERT_CTX = as_file(files("certifi").joinpath("cacert.pem")) + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) + + return _CACERT_PATH + + def contents() -> str: + return files("certifi").joinpath("cacert.pem").read_text(encoding="ascii") + +else: + + from importlib.resources import path as get_path, read_text + + _CACERT_CTX = None + _CACERT_PATH = None + + def where() -> str: + # This is slightly terrible, but we want to delay extracting the + # file in cases where we're inside of a zipimport situation until + # someone actually calls where(), but we don't want to re-extract + # the file on every call of where(), so we'll do it once then store + # it in a global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you + # to manage the cleanup of this file, so it doesn't actually + # return a path, it returns a context manager that will give + # you the path when you enter it and will do any cleanup when + # you leave it. In the common case of not needing a temporary + # file, it will just return the file system location and the + # __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. + _CACERT_CTX = get_path("certifi", "cacert.pem") + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) + + return _CACERT_PATH + + def contents() -> str: + return read_text("certifi", "cacert.pem", encoding="ascii") diff --git a/intelliaide/vendor/certifi/py.typed b/intelliaide/vendor/certifi/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/intelliaide/vendor/cffi-2.0.0.dist-info/INSTALLER b/intelliaide/vendor/cffi-2.0.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/intelliaide/vendor/cffi-2.0.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/intelliaide/vendor/cffi-2.0.0.dist-info/METADATA b/intelliaide/vendor/cffi-2.0.0.dist-info/METADATA new file mode 100644 index 0000000..67508e5 --- /dev/null +++ b/intelliaide/vendor/cffi-2.0.0.dist-info/METADATA @@ -0,0 +1,68 @@ +Metadata-Version: 2.4 +Name: cffi +Version: 2.0.0 +Summary: Foreign Function Interface for Python calling C code. +Author: Armin Rigo, Maciej Fijalkowski +Maintainer: Matt Davis, Matt Clay, Matti Picus +License-Expression: MIT +Project-URL: Documentation, https://cffi.readthedocs.io/ +Project-URL: Changelog, https://cffi.readthedocs.io/en/latest/whatsnew.html +Project-URL: Downloads, https://github.com/python-cffi/cffi/releases +Project-URL: Contact, https://groups.google.com/forum/#!forum/python-cffi +Project-URL: Source Code, https://github.com/python-cffi/cffi +Project-URL: Issue Tracker, https://github.com/python-cffi/cffi/issues +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta +Classifier: Programming Language :: Python :: Implementation :: CPython +Requires-Python: >=3.9 +Description-Content-Type: text/markdown +License-File: LICENSE +License-File: AUTHORS +Requires-Dist: pycparser; implementation_name != "PyPy" +Dynamic: license-file + +[![GitHub Actions Status](https://github.com/python-cffi/cffi/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/python-cffi/cffi/actions/workflows/ci.yaml?query=branch%3Amain++) +[![PyPI version](https://img.shields.io/pypi/v/cffi.svg)](https://pypi.org/project/cffi) +[![Read the Docs](https://img.shields.io/badge/docs-latest-blue.svg)][Documentation] + + +CFFI +==== + +Foreign Function Interface for Python calling C code. + +Please see the [Documentation] or uncompiled in the `doc/` subdirectory. + +Download +-------- + +[Download page](https://github.com/python-cffi/cffi/releases) + +Source Code +----------- + +Source code is publicly available on +[GitHub](https://github.com/python-cffi/cffi). + +Contact +------- + +[Mailing list](https://groups.google.com/forum/#!forum/python-cffi) + +Testing/development tips +------------------------ + +After `git clone` or `wget && tar`, we will get a directory called `cffi` or `cffi-x.x.x`. we call it `repo-directory`. To run tests under CPython, run the following in the `repo-directory`: + + pip install pytest + pip install -e . # editable install of CFFI for local development + pytest src/c/ testing/ + +[Documentation]: http://cffi.readthedocs.org/ diff --git a/intelliaide/vendor/cffi-2.0.0.dist-info/RECORD b/intelliaide/vendor/cffi-2.0.0.dist-info/RECORD new file mode 100644 index 0000000..6f82298 --- /dev/null +++ b/intelliaide/vendor/cffi-2.0.0.dist-info/RECORD @@ -0,0 +1,49 @@ +_cffi_backend.cpython-312-x86_64-linux-gnu.so,sha256=AGLtw5fn9u4Cmwk3BbGlsXG7VZEvQekABMyEGuRZmcE,348808 +cffi-2.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +cffi-2.0.0.dist-info/METADATA,sha256=uYzn40F68Im8EtXHNBLZs7FoPM-OxzyYbDWsjJvhujk,2559 +cffi-2.0.0.dist-info/RECORD,, +cffi-2.0.0.dist-info/WHEEL,sha256=aSgG0F4rGPZtV0iTEIfy6dtHq6g67Lze3uLfk0vWn88,151 +cffi-2.0.0.dist-info/entry_points.txt,sha256=y6jTxnyeuLnL-XJcDv8uML3n6wyYiGRg8MTp_QGJ9Ho,75 +cffi-2.0.0.dist-info/licenses/AUTHORS,sha256=KmemC7-zN1nWfWRf8TG45ta8TK_CMtdR_Kw-2k0xTMg,208 +cffi-2.0.0.dist-info/licenses/LICENSE,sha256=W6JN3FcGf5JJrdZEw6_EGl1tw34jQz73Wdld83Cwr2M,1123 +cffi-2.0.0.dist-info/top_level.txt,sha256=rE7WR3rZfNKxWI9-jn6hsHCAl7MDkB-FmuQbxWjFehQ,19 +cffi/__init__.py,sha256=-ksBQ7MfDzVvbBlV_ftYBWAmEqfA86ljIzMxzaZeAlI,511 +cffi/__pycache__/__init__.cpython-312.pyc,, +cffi/__pycache__/_imp_emulation.cpython-312.pyc,, +cffi/__pycache__/_shimmed_dist_utils.cpython-312.pyc,, +cffi/__pycache__/api.cpython-312.pyc,, +cffi/__pycache__/backend_ctypes.cpython-312.pyc,, +cffi/__pycache__/cffi_opcode.cpython-312.pyc,, +cffi/__pycache__/commontypes.cpython-312.pyc,, +cffi/__pycache__/cparser.cpython-312.pyc,, +cffi/__pycache__/error.cpython-312.pyc,, +cffi/__pycache__/ffiplatform.cpython-312.pyc,, +cffi/__pycache__/lock.cpython-312.pyc,, +cffi/__pycache__/model.cpython-312.pyc,, +cffi/__pycache__/pkgconfig.cpython-312.pyc,, +cffi/__pycache__/recompiler.cpython-312.pyc,, +cffi/__pycache__/setuptools_ext.cpython-312.pyc,, +cffi/__pycache__/vengine_cpy.cpython-312.pyc,, +cffi/__pycache__/vengine_gen.cpython-312.pyc,, +cffi/__pycache__/verifier.cpython-312.pyc,, +cffi/_cffi_errors.h,sha256=zQXt7uR_m8gUW-fI2hJg0KoSkJFwXv8RGUkEDZ177dQ,3908 +cffi/_cffi_include.h,sha256=Exhmgm9qzHWzWivjfTe0D7Xp4rPUkVxdNuwGhMTMzbw,15055 +cffi/_embedding.h,sha256=Ai33FHblE7XSpHOCp8kPcWwN5_9BV14OvN0JVa6ITpw,18786 +cffi/_imp_emulation.py,sha256=RxREG8zAbI2RPGBww90u_5fi8sWdahpdipOoPzkp7C0,2960 +cffi/_shimmed_dist_utils.py,sha256=Bjj2wm8yZbvFvWEx5AEfmqaqZyZFhYfoyLLQHkXZuao,2230 +cffi/api.py,sha256=alBv6hZQkjpmZplBphdaRn2lPO9-CORs_M7ixabvZWI,42169 +cffi/backend_ctypes.py,sha256=h5ZIzLc6BFVXnGyc9xPqZWUS7qGy7yFSDqXe68Sa8z4,42454 +cffi/cffi_opcode.py,sha256=JDV5l0R0_OadBX_uE7xPPTYtMdmpp8I9UYd6av7aiDU,5731 +cffi/commontypes.py,sha256=7N6zPtCFlvxXMWhHV08psUjdYIK2XgsN3yo5dgua_v4,2805 +cffi/cparser.py,sha256=QUTfmlL-aO-MYR8bFGlvAUHc36OQr7XYLe0WLkGFjRo,44790 +cffi/error.py,sha256=v6xTiS4U0kvDcy4h_BDRo5v39ZQuj-IMRYLv5ETddZs,877 +cffi/ffiplatform.py,sha256=avxFjdikYGJoEtmJO7ewVmwG_VEVl6EZ_WaNhZYCqv4,3584 +cffi/lock.py,sha256=l9TTdwMIMpi6jDkJGnQgE9cvTIR7CAntIJr8EGHt3pY,747 +cffi/model.py,sha256=W30UFQZE73jL5Mx5N81YT77us2W2iJjTm0XYfnwz1cg,21797 +cffi/parse_c_type.h,sha256=OdwQfwM9ktq6vlCB43exFQmxDBtj2MBNdK8LYl15tjw,5976 +cffi/pkgconfig.py,sha256=LP1w7vmWvmKwyqLaU1Z243FOWGNQMrgMUZrvgFuOlco,4374 +cffi/recompiler.py,sha256=78J6lMEEOygXNmjN9-fOFFO3j7eW-iFxSrxfvQb54bY,65509 +cffi/setuptools_ext.py,sha256=0rCwBJ1W7FHWtiMKfNXsSST88V8UXrui5oeXFlDNLG8,9411 +cffi/vengine_cpy.py,sha256=oyQKD23kpE0aChUKA8Jg0e723foPiYzLYEdb-J0MiNs,43881 +cffi/vengine_gen.py,sha256=DUlEIrDiVin1Pnhn1sfoamnS5NLqfJcOdhRoeSNeJRg,26939 +cffi/verifier.py,sha256=oX8jpaohg2Qm3aHcznidAdvrVm5N4sQYG0a3Eo5mIl4,11182 diff --git a/intelliaide/vendor/cffi-2.0.0.dist-info/WHEEL b/intelliaide/vendor/cffi-2.0.0.dist-info/WHEEL new file mode 100644 index 0000000..e21e9f2 --- /dev/null +++ b/intelliaide/vendor/cffi-2.0.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: setuptools (80.9.0) +Root-Is-Purelib: false +Tag: cp312-cp312-manylinux_2_17_x86_64 +Tag: cp312-cp312-manylinux2014_x86_64 + diff --git a/intelliaide/vendor/cffi-2.0.0.dist-info/entry_points.txt b/intelliaide/vendor/cffi-2.0.0.dist-info/entry_points.txt new file mode 100644 index 0000000..4b0274f --- /dev/null +++ b/intelliaide/vendor/cffi-2.0.0.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[distutils.setup_keywords] +cffi_modules = cffi.setuptools_ext:cffi_modules diff --git a/intelliaide/vendor/cffi-2.0.0.dist-info/licenses/AUTHORS b/intelliaide/vendor/cffi-2.0.0.dist-info/licenses/AUTHORS new file mode 100644 index 0000000..370a25d --- /dev/null +++ b/intelliaide/vendor/cffi-2.0.0.dist-info/licenses/AUTHORS @@ -0,0 +1,8 @@ +This package has been mostly done by Armin Rigo with help from +Maciej Fijałkowski. The idea is heavily based (although not directly +copied) from LuaJIT ffi by Mike Pall. + + +Other contributors: + + Google Inc. diff --git a/intelliaide/vendor/cffi-2.0.0.dist-info/licenses/LICENSE b/intelliaide/vendor/cffi-2.0.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000..0a1dbfb --- /dev/null +++ b/intelliaide/vendor/cffi-2.0.0.dist-info/licenses/LICENSE @@ -0,0 +1,23 @@ + +Except when otherwise stated (look for LICENSE files in directories or +information at the beginning of each file) all software and +documentation is licensed as follows: + + MIT No Attribution + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the + Software is furnished to do so. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + diff --git a/intelliaide/vendor/cffi-2.0.0.dist-info/top_level.txt b/intelliaide/vendor/cffi-2.0.0.dist-info/top_level.txt new file mode 100644 index 0000000..f645779 --- /dev/null +++ b/intelliaide/vendor/cffi-2.0.0.dist-info/top_level.txt @@ -0,0 +1,2 @@ +_cffi_backend +cffi diff --git a/intelliaide/vendor/cffi/__init__.py b/intelliaide/vendor/cffi/__init__.py new file mode 100644 index 0000000..c99ec3d --- /dev/null +++ b/intelliaide/vendor/cffi/__init__.py @@ -0,0 +1,14 @@ +__all__ = ['FFI', 'VerificationError', 'VerificationMissing', 'CDefError', + 'FFIError'] + +from .api import FFI +from .error import CDefError, FFIError, VerificationError, VerificationMissing +from .error import PkgConfigError + +__version__ = "2.0.0" +__version_info__ = (2, 0, 0) + +# The verifier module file names are based on the CRC32 of a string that +# contains the following version number. It may be older than __version__ +# if nothing is clearly incompatible. +__version_verifier_modules__ = "0.8.6" diff --git a/intelliaide/vendor/cffi/_cffi_errors.h b/intelliaide/vendor/cffi/_cffi_errors.h new file mode 100644 index 0000000..158e059 --- /dev/null +++ b/intelliaide/vendor/cffi/_cffi_errors.h @@ -0,0 +1,149 @@ +#ifndef CFFI_MESSAGEBOX +# ifdef _MSC_VER +# define CFFI_MESSAGEBOX 1 +# else +# define CFFI_MESSAGEBOX 0 +# endif +#endif + + +#if CFFI_MESSAGEBOX +/* Windows only: logic to take the Python-CFFI embedding logic + initialization errors and display them in a background thread + with MessageBox. The idea is that if the whole program closes + as a result of this problem, then likely it is already a console + program and you can read the stderr output in the console too. + If it is not a console program, then it will likely show its own + dialog to complain, or generally not abruptly close, and for this + case the background thread should stay alive. +*/ +static void *volatile _cffi_bootstrap_text; + +static PyObject *_cffi_start_error_capture(void) +{ + PyObject *result = NULL; + PyObject *x, *m, *bi; + + if (InterlockedCompareExchangePointer(&_cffi_bootstrap_text, + (void *)1, NULL) != NULL) + return (PyObject *)1; + + m = PyImport_AddModule("_cffi_error_capture"); + if (m == NULL) + goto error; + + result = PyModule_GetDict(m); + if (result == NULL) + goto error; + +#if PY_MAJOR_VERSION >= 3 + bi = PyImport_ImportModule("builtins"); +#else + bi = PyImport_ImportModule("__builtin__"); +#endif + if (bi == NULL) + goto error; + PyDict_SetItemString(result, "__builtins__", bi); + Py_DECREF(bi); + + x = PyRun_String( + "import sys\n" + "class FileLike:\n" + " def write(self, x):\n" + " try:\n" + " of.write(x)\n" + " except: pass\n" + " self.buf += x\n" + " def flush(self):\n" + " pass\n" + "fl = FileLike()\n" + "fl.buf = ''\n" + "of = sys.stderr\n" + "sys.stderr = fl\n" + "def done():\n" + " sys.stderr = of\n" + " return fl.buf\n", /* make sure the returned value stays alive */ + Py_file_input, + result, result); + Py_XDECREF(x); + + error: + if (PyErr_Occurred()) + { + PyErr_WriteUnraisable(Py_None); + PyErr_Clear(); + } + return result; +} + +#pragma comment(lib, "user32.lib") + +static DWORD WINAPI _cffi_bootstrap_dialog(LPVOID ignored) +{ + Sleep(666); /* may be interrupted if the whole process is closing */ +#if PY_MAJOR_VERSION >= 3 + MessageBoxW(NULL, (wchar_t *)_cffi_bootstrap_text, + L"Python-CFFI error", + MB_OK | MB_ICONERROR); +#else + MessageBoxA(NULL, (char *)_cffi_bootstrap_text, + "Python-CFFI error", + MB_OK | MB_ICONERROR); +#endif + _cffi_bootstrap_text = NULL; + return 0; +} + +static void _cffi_stop_error_capture(PyObject *ecap) +{ + PyObject *s; + void *text; + + if (ecap == (PyObject *)1) + return; + + if (ecap == NULL) + goto error; + + s = PyRun_String("done()", Py_eval_input, ecap, ecap); + if (s == NULL) + goto error; + + /* Show a dialog box, but in a background thread, and + never show multiple dialog boxes at once. */ +#if PY_MAJOR_VERSION >= 3 + text = PyUnicode_AsWideCharString(s, NULL); +#else + text = PyString_AsString(s); +#endif + + _cffi_bootstrap_text = text; + + if (text != NULL) + { + HANDLE h; + h = CreateThread(NULL, 0, _cffi_bootstrap_dialog, + NULL, 0, NULL); + if (h != NULL) + CloseHandle(h); + } + /* decref the string, but it should stay alive as 'fl.buf' + in the small module above. It will really be freed only if + we later get another similar error. So it's a leak of at + most one copy of the small module. That's fine for this + situation which is usually a "fatal error" anyway. */ + Py_DECREF(s); + PyErr_Clear(); + return; + + error: + _cffi_bootstrap_text = NULL; + PyErr_Clear(); +} + +#else + +static PyObject *_cffi_start_error_capture(void) { return NULL; } +static void _cffi_stop_error_capture(PyObject *ecap) { } + +#endif diff --git a/intelliaide/vendor/cffi/_cffi_include.h b/intelliaide/vendor/cffi/_cffi_include.h new file mode 100644 index 0000000..908a1d7 --- /dev/null +++ b/intelliaide/vendor/cffi/_cffi_include.h @@ -0,0 +1,389 @@ +#define _CFFI_ + +/* We try to define Py_LIMITED_API before including Python.h. + + Mess: we can only define it if Py_DEBUG, Py_TRACE_REFS and + Py_REF_DEBUG are not defined. This is a best-effort approximation: + we can learn about Py_DEBUG from pyconfig.h, but it is unclear if + the same works for the other two macros. Py_DEBUG implies them, + but not the other way around. + + The implementation is messy (issue #350): on Windows, with _MSC_VER, + we have to define Py_LIMITED_API even before including pyconfig.h. + In that case, we guess what pyconfig.h will do to the macros above, + and check our guess after the #include. + + Note that on Windows, with CPython 3.x, you need >= 3.5 and virtualenv + version >= 16.0.0. With older versions of either, you don't get a + copy of PYTHON3.DLL in the virtualenv. We can't check the version of + CPython *before* we even include pyconfig.h. ffi.set_source() puts + a ``#define _CFFI_NO_LIMITED_API'' at the start of this file if it is + running on Windows < 3.5, as an attempt at fixing it, but that's + arguably wrong because it may not be the target version of Python. + Still better than nothing I guess. As another workaround, you can + remove the definition of Py_LIMITED_API here. + + See also 'py_limited_api' in cffi/setuptools_ext.py. +*/ +#if !defined(_CFFI_USE_EMBEDDING) && !defined(Py_LIMITED_API) +# ifdef _MSC_VER +# if !defined(_DEBUG) && !defined(Py_DEBUG) && !defined(Py_TRACE_REFS) && !defined(Py_REF_DEBUG) && !defined(_CFFI_NO_LIMITED_API) +# define Py_LIMITED_API +# endif +# include + /* sanity-check: Py_LIMITED_API will cause crashes if any of these + are also defined. Normally, the Python file PC/pyconfig.h does not + cause any of these to be defined, with the exception that _DEBUG + causes Py_DEBUG. Double-check that. */ +# ifdef Py_LIMITED_API +# if defined(Py_DEBUG) +# error "pyconfig.h unexpectedly defines Py_DEBUG, but Py_LIMITED_API is set" +# endif +# if defined(Py_TRACE_REFS) +# error "pyconfig.h unexpectedly defines Py_TRACE_REFS, but Py_LIMITED_API is set" +# endif +# if defined(Py_REF_DEBUG) +# error "pyconfig.h unexpectedly defines Py_REF_DEBUG, but Py_LIMITED_API is set" +# endif +# endif +# else +# include +# if !defined(Py_DEBUG) && !defined(Py_TRACE_REFS) && !defined(Py_REF_DEBUG) && !defined(_CFFI_NO_LIMITED_API) +# define Py_LIMITED_API +# endif +# endif +#endif + +#include +#ifdef __cplusplus +extern "C" { +#endif +#include +#include "parse_c_type.h" + +/* this block of #ifs should be kept exactly identical between + c/_cffi_backend.c, cffi/vengine_cpy.py, cffi/vengine_gen.py + and cffi/_cffi_include.h */ +#if defined(_MSC_VER) +# include /* for alloca() */ +# if _MSC_VER < 1600 /* MSVC < 2010 */ + typedef __int8 int8_t; + typedef __int16 int16_t; + typedef __int32 int32_t; + typedef __int64 int64_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; + typedef unsigned __int64 uint64_t; + typedef __int8 int_least8_t; + typedef __int16 int_least16_t; + typedef __int32 int_least32_t; + typedef __int64 int_least64_t; + typedef unsigned __int8 uint_least8_t; + typedef unsigned __int16 uint_least16_t; + typedef unsigned __int32 uint_least32_t; + typedef unsigned __int64 uint_least64_t; + typedef __int8 int_fast8_t; + typedef __int16 int_fast16_t; + typedef __int32 int_fast32_t; + typedef __int64 int_fast64_t; + typedef unsigned __int8 uint_fast8_t; + typedef unsigned __int16 uint_fast16_t; + typedef unsigned __int32 uint_fast32_t; + typedef unsigned __int64 uint_fast64_t; + typedef __int64 intmax_t; + typedef unsigned __int64 uintmax_t; +# else +# include +# endif +# if _MSC_VER < 1800 /* MSVC < 2013 */ +# ifndef __cplusplus + typedef unsigned char _Bool; +# endif +# endif +# define _cffi_float_complex_t _Fcomplex /* include for it */ +# define _cffi_double_complex_t _Dcomplex /* include for it */ +#else +# include +# if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux) +# include +# endif +# define _cffi_float_complex_t float _Complex +# define _cffi_double_complex_t double _Complex +#endif + +#ifdef __GNUC__ +# define _CFFI_UNUSED_FN __attribute__((unused)) +#else +# define _CFFI_UNUSED_FN /* nothing */ +#endif + +#ifdef __cplusplus +# ifndef _Bool + typedef bool _Bool; /* semi-hackish: C++ has no _Bool; bool is builtin */ +# endif +#endif + +/********** CPython-specific section **********/ +#ifndef PYPY_VERSION + + +#if PY_MAJOR_VERSION >= 3 +# define PyInt_FromLong PyLong_FromLong +#endif + +#define _cffi_from_c_double PyFloat_FromDouble +#define _cffi_from_c_float PyFloat_FromDouble +#define _cffi_from_c_long PyInt_FromLong +#define _cffi_from_c_ulong PyLong_FromUnsignedLong +#define _cffi_from_c_longlong PyLong_FromLongLong +#define _cffi_from_c_ulonglong PyLong_FromUnsignedLongLong +#define _cffi_from_c__Bool PyBool_FromLong + +#define _cffi_to_c_double PyFloat_AsDouble +#define _cffi_to_c_float PyFloat_AsDouble + +#define _cffi_from_c_int(x, type) \ + (((type)-1) > 0 ? /* unsigned */ \ + (sizeof(type) < sizeof(long) ? \ + PyInt_FromLong((long)x) : \ + sizeof(type) == sizeof(long) ? \ + PyLong_FromUnsignedLong((unsigned long)x) : \ + PyLong_FromUnsignedLongLong((unsigned long long)x)) : \ + (sizeof(type) <= sizeof(long) ? \ + PyInt_FromLong((long)x) : \ + PyLong_FromLongLong((long long)x))) + +#define _cffi_to_c_int(o, type) \ + ((type)( \ + sizeof(type) == 1 ? (((type)-1) > 0 ? (type)_cffi_to_c_u8(o) \ + : (type)_cffi_to_c_i8(o)) : \ + sizeof(type) == 2 ? (((type)-1) > 0 ? (type)_cffi_to_c_u16(o) \ + : (type)_cffi_to_c_i16(o)) : \ + sizeof(type) == 4 ? (((type)-1) > 0 ? (type)_cffi_to_c_u32(o) \ + : (type)_cffi_to_c_i32(o)) : \ + sizeof(type) == 8 ? (((type)-1) > 0 ? (type)_cffi_to_c_u64(o) \ + : (type)_cffi_to_c_i64(o)) : \ + (Py_FatalError("unsupported size for type " #type), (type)0))) + +#define _cffi_to_c_i8 \ + ((int(*)(PyObject *))_cffi_exports[1]) +#define _cffi_to_c_u8 \ + ((int(*)(PyObject *))_cffi_exports[2]) +#define _cffi_to_c_i16 \ + ((int(*)(PyObject *))_cffi_exports[3]) +#define _cffi_to_c_u16 \ + ((int(*)(PyObject *))_cffi_exports[4]) +#define _cffi_to_c_i32 \ + ((int(*)(PyObject *))_cffi_exports[5]) +#define _cffi_to_c_u32 \ + ((unsigned int(*)(PyObject *))_cffi_exports[6]) +#define _cffi_to_c_i64 \ + ((long long(*)(PyObject *))_cffi_exports[7]) +#define _cffi_to_c_u64 \ + ((unsigned long long(*)(PyObject *))_cffi_exports[8]) +#define _cffi_to_c_char \ + ((int(*)(PyObject *))_cffi_exports[9]) +#define _cffi_from_c_pointer \ + ((PyObject *(*)(char *, struct _cffi_ctypedescr *))_cffi_exports[10]) +#define _cffi_to_c_pointer \ + ((char *(*)(PyObject *, struct _cffi_ctypedescr *))_cffi_exports[11]) +#define _cffi_get_struct_layout \ + not used any more +#define _cffi_restore_errno \ + ((void(*)(void))_cffi_exports[13]) +#define _cffi_save_errno \ + ((void(*)(void))_cffi_exports[14]) +#define _cffi_from_c_char \ + ((PyObject *(*)(char))_cffi_exports[15]) +#define _cffi_from_c_deref \ + ((PyObject *(*)(char *, struct _cffi_ctypedescr *))_cffi_exports[16]) +#define _cffi_to_c \ + ((int(*)(char *, struct _cffi_ctypedescr *, PyObject *))_cffi_exports[17]) +#define _cffi_from_c_struct \ + ((PyObject *(*)(char *, struct _cffi_ctypedescr *))_cffi_exports[18]) +#define _cffi_to_c_wchar_t \ + ((_cffi_wchar_t(*)(PyObject *))_cffi_exports[19]) +#define _cffi_from_c_wchar_t \ + ((PyObject *(*)(_cffi_wchar_t))_cffi_exports[20]) +#define _cffi_to_c_long_double \ + ((long double(*)(PyObject *))_cffi_exports[21]) +#define _cffi_to_c__Bool \ + ((_Bool(*)(PyObject *))_cffi_exports[22]) +#define _cffi_prepare_pointer_call_argument \ + ((Py_ssize_t(*)(struct _cffi_ctypedescr *, \ + PyObject *, char **))_cffi_exports[23]) +#define _cffi_convert_array_from_object \ + ((int(*)(char *, struct _cffi_ctypedescr *, PyObject *))_cffi_exports[24]) +#define _CFFI_CPIDX 25 +#define _cffi_call_python \ + ((void(*)(struct _cffi_externpy_s *, char *))_cffi_exports[_CFFI_CPIDX]) +#define _cffi_to_c_wchar3216_t \ + ((int(*)(PyObject *))_cffi_exports[26]) +#define _cffi_from_c_wchar3216_t \ + ((PyObject *(*)(int))_cffi_exports[27]) +#define _CFFI_NUM_EXPORTS 28 + +struct _cffi_ctypedescr; + +static void *_cffi_exports[_CFFI_NUM_EXPORTS]; + +#define _cffi_type(index) ( \ + assert((((uintptr_t)_cffi_types[index]) & 1) == 0), \ + (struct _cffi_ctypedescr *)_cffi_types[index]) + +static PyObject *_cffi_init(const char *module_name, Py_ssize_t version, + const struct _cffi_type_context_s *ctx) +{ + PyObject *module, *o_arg, *new_module; + void *raw[] = { + (void *)module_name, + (void *)version, + (void *)_cffi_exports, + (void *)ctx, + }; + + module = PyImport_ImportModule("_cffi_backend"); + if (module == NULL) + goto failure; + + o_arg = PyLong_FromVoidPtr((void *)raw); + if (o_arg == NULL) + goto failure; + + new_module = PyObject_CallMethod( + module, (char *)"_init_cffi_1_0_external_module", (char *)"O", o_arg); + + Py_DECREF(o_arg); + Py_DECREF(module); + return new_module; + + failure: + Py_XDECREF(module); + return NULL; +} + + +#ifdef HAVE_WCHAR_H +typedef wchar_t _cffi_wchar_t; +#else +typedef uint16_t _cffi_wchar_t; /* same random pick as _cffi_backend.c */ +#endif + +_CFFI_UNUSED_FN static uint16_t _cffi_to_c_char16_t(PyObject *o) +{ + if (sizeof(_cffi_wchar_t) == 2) + return (uint16_t)_cffi_to_c_wchar_t(o); + else + return (uint16_t)_cffi_to_c_wchar3216_t(o); +} + +_CFFI_UNUSED_FN static PyObject *_cffi_from_c_char16_t(uint16_t x) +{ + if (sizeof(_cffi_wchar_t) == 2) + return _cffi_from_c_wchar_t((_cffi_wchar_t)x); + else + return _cffi_from_c_wchar3216_t((int)x); +} + +_CFFI_UNUSED_FN static int _cffi_to_c_char32_t(PyObject *o) +{ + if (sizeof(_cffi_wchar_t) == 4) + return (int)_cffi_to_c_wchar_t(o); + else + return (int)_cffi_to_c_wchar3216_t(o); +} + +_CFFI_UNUSED_FN static PyObject *_cffi_from_c_char32_t(unsigned int x) +{ + if (sizeof(_cffi_wchar_t) == 4) + return _cffi_from_c_wchar_t((_cffi_wchar_t)x); + else + return _cffi_from_c_wchar3216_t((int)x); +} + +union _cffi_union_alignment_u { + unsigned char m_char; + unsigned short m_short; + unsigned int m_int; + unsigned long m_long; + unsigned long long m_longlong; + float m_float; + double m_double; + long double m_longdouble; +}; + +struct _cffi_freeme_s { + struct _cffi_freeme_s *next; + union _cffi_union_alignment_u alignment; +}; + +_CFFI_UNUSED_FN static int +_cffi_convert_array_argument(struct _cffi_ctypedescr *ctptr, PyObject *arg, + char **output_data, Py_ssize_t datasize, + struct _cffi_freeme_s **freeme) +{ + char *p; + if (datasize < 0) + return -1; + + p = *output_data; + if (p == NULL) { + struct _cffi_freeme_s *fp = (struct _cffi_freeme_s *)PyObject_Malloc( + offsetof(struct _cffi_freeme_s, alignment) + (size_t)datasize); + if (fp == NULL) + return -1; + fp->next = *freeme; + *freeme = fp; + p = *output_data = (char *)&fp->alignment; + } + memset((void *)p, 0, (size_t)datasize); + return _cffi_convert_array_from_object(p, ctptr, arg); +} + +_CFFI_UNUSED_FN static void +_cffi_free_array_arguments(struct _cffi_freeme_s *freeme) +{ + do { + void *p = (void *)freeme; + freeme = freeme->next; + PyObject_Free(p); + } while (freeme != NULL); +} + +/********** end CPython-specific section **********/ +#else +_CFFI_UNUSED_FN +static void (*_cffi_call_python_org)(struct _cffi_externpy_s *, char *); +# define _cffi_call_python _cffi_call_python_org +#endif + + +#define _cffi_array_len(array) (sizeof(array) / sizeof((array)[0])) + +#define _cffi_prim_int(size, sign) \ + ((size) == 1 ? ((sign) ? _CFFI_PRIM_INT8 : _CFFI_PRIM_UINT8) : \ + (size) == 2 ? ((sign) ? _CFFI_PRIM_INT16 : _CFFI_PRIM_UINT16) : \ + (size) == 4 ? ((sign) ? _CFFI_PRIM_INT32 : _CFFI_PRIM_UINT32) : \ + (size) == 8 ? ((sign) ? _CFFI_PRIM_INT64 : _CFFI_PRIM_UINT64) : \ + _CFFI__UNKNOWN_PRIM) + +#define _cffi_prim_float(size) \ + ((size) == sizeof(float) ? _CFFI_PRIM_FLOAT : \ + (size) == sizeof(double) ? _CFFI_PRIM_DOUBLE : \ + (size) == sizeof(long double) ? _CFFI__UNKNOWN_LONG_DOUBLE : \ + _CFFI__UNKNOWN_FLOAT_PRIM) + +#define _cffi_check_int(got, got_nonpos, expected) \ + ((got_nonpos) == (expected <= 0) && \ + (got) == (unsigned long long)expected) + +#ifdef MS_WIN32 +# define _cffi_stdcall __stdcall +#else +# define _cffi_stdcall /* nothing */ +#endif + +#ifdef __cplusplus +} +#endif diff --git a/intelliaide/vendor/cffi/_embedding.h b/intelliaide/vendor/cffi/_embedding.h new file mode 100644 index 0000000..64c04f6 --- /dev/null +++ b/intelliaide/vendor/cffi/_embedding.h @@ -0,0 +1,550 @@ + +/***** Support code for embedding *****/ + +#ifdef __cplusplus +extern "C" { +#endif + + +#if defined(_WIN32) +# define CFFI_DLLEXPORT __declspec(dllexport) +#elif defined(__GNUC__) +# define CFFI_DLLEXPORT __attribute__((visibility("default"))) +#else +# define CFFI_DLLEXPORT /* nothing */ +#endif + + +/* There are two global variables of type _cffi_call_python_fnptr: + + * _cffi_call_python, which we declare just below, is the one called + by ``extern "Python"`` implementations. + + * _cffi_call_python_org, which on CPython is actually part of the + _cffi_exports[] array, is the function pointer copied from + _cffi_backend. If _cffi_start_python() fails, then this is set + to NULL; otherwise, it should never be NULL. + + After initialization is complete, both are equal. However, the + first one remains equal to &_cffi_start_and_call_python until the + very end of initialization, when we are (or should be) sure that + concurrent threads also see a completely initialized world, and + only then is it changed. +*/ +#undef _cffi_call_python +typedef void (*_cffi_call_python_fnptr)(struct _cffi_externpy_s *, char *); +static void _cffi_start_and_call_python(struct _cffi_externpy_s *, char *); +static _cffi_call_python_fnptr _cffi_call_python = &_cffi_start_and_call_python; + + +#ifndef _MSC_VER + /* --- Assuming a GCC not infinitely old --- */ +# define cffi_compare_and_swap(l,o,n) __sync_bool_compare_and_swap(l,o,n) +# define cffi_write_barrier() __sync_synchronize() +# if !defined(__amd64__) && !defined(__x86_64__) && \ + !defined(__i386__) && !defined(__i386) +# define cffi_read_barrier() __sync_synchronize() +# else +# define cffi_read_barrier() (void)0 +# endif +#else + /* --- Windows threads version --- */ +# include +# define cffi_compare_and_swap(l,o,n) \ + (InterlockedCompareExchangePointer(l,n,o) == (o)) +# define cffi_write_barrier() InterlockedCompareExchange(&_cffi_dummy,0,0) +# define cffi_read_barrier() (void)0 +static volatile LONG _cffi_dummy; +#endif + +#ifdef WITH_THREAD +# ifndef _MSC_VER +# include + static pthread_mutex_t _cffi_embed_startup_lock; +# else + static CRITICAL_SECTION _cffi_embed_startup_lock; +# endif + static char _cffi_embed_startup_lock_ready = 0; +#endif + +static void _cffi_acquire_reentrant_mutex(void) +{ + static void *volatile lock = NULL; + + while (!cffi_compare_and_swap(&lock, NULL, (void *)1)) { + /* should ideally do a spin loop instruction here, but + hard to do it portably and doesn't really matter I + think: pthread_mutex_init() should be very fast, and + this is only run at start-up anyway. */ + } + +#ifdef WITH_THREAD + if (!_cffi_embed_startup_lock_ready) { +# ifndef _MSC_VER + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&_cffi_embed_startup_lock, &attr); +# else + InitializeCriticalSection(&_cffi_embed_startup_lock); +# endif + _cffi_embed_startup_lock_ready = 1; + } +#endif + + while (!cffi_compare_and_swap(&lock, (void *)1, NULL)) + ; + +#ifndef _MSC_VER + pthread_mutex_lock(&_cffi_embed_startup_lock); +#else + EnterCriticalSection(&_cffi_embed_startup_lock); +#endif +} + +static void _cffi_release_reentrant_mutex(void) +{ +#ifndef _MSC_VER + pthread_mutex_unlock(&_cffi_embed_startup_lock); +#else + LeaveCriticalSection(&_cffi_embed_startup_lock); +#endif +} + + +/********** CPython-specific section **********/ +#ifndef PYPY_VERSION + +#include "_cffi_errors.h" + + +#define _cffi_call_python_org _cffi_exports[_CFFI_CPIDX] + +PyMODINIT_FUNC _CFFI_PYTHON_STARTUP_FUNC(void); /* forward */ + +static void _cffi_py_initialize(void) +{ + /* XXX use initsigs=0, which "skips initialization registration of + signal handlers, which might be useful when Python is + embedded" according to the Python docs. But review and think + if it should be a user-controllable setting. + + XXX we should also give a way to write errors to a buffer + instead of to stderr. + + XXX if importing 'site' fails, CPython (any version) calls + exit(). Should we try to work around this behavior here? + */ + Py_InitializeEx(0); +} + +static int _cffi_initialize_python(void) +{ + /* This initializes Python, imports _cffi_backend, and then the + present .dll/.so is set up as a CPython C extension module. + */ + int result; + PyGILState_STATE state; + PyObject *pycode=NULL, *global_dict=NULL, *x; + PyObject *builtins; + + state = PyGILState_Ensure(); + + /* Call the initxxx() function from the present module. It will + create and initialize us as a CPython extension module, instead + of letting the startup Python code do it---it might reimport + the same .dll/.so and get maybe confused on some platforms. + It might also have troubles locating the .dll/.so again for all + I know. + */ + (void)_CFFI_PYTHON_STARTUP_FUNC(); + if (PyErr_Occurred()) + goto error; + + /* Now run the Python code provided to ffi.embedding_init_code(). + */ + pycode = Py_CompileString(_CFFI_PYTHON_STARTUP_CODE, + "", + Py_file_input); + if (pycode == NULL) + goto error; + global_dict = PyDict_New(); + if (global_dict == NULL) + goto error; + builtins = PyEval_GetBuiltins(); + if (builtins == NULL) + goto error; + if (PyDict_SetItemString(global_dict, "__builtins__", builtins) < 0) + goto error; + x = PyEval_EvalCode( +#if PY_MAJOR_VERSION < 3 + (PyCodeObject *) +#endif + pycode, global_dict, global_dict); + if (x == NULL) + goto error; + Py_DECREF(x); + + /* Done! Now if we've been called from + _cffi_start_and_call_python() in an ``extern "Python"``, we can + only hope that the Python code did correctly set up the + corresponding @ffi.def_extern() function. Otherwise, the + general logic of ``extern "Python"`` functions (inside the + _cffi_backend module) will find that the reference is still + missing and print an error. + */ + result = 0; + done: + Py_XDECREF(pycode); + Py_XDECREF(global_dict); + PyGILState_Release(state); + return result; + + error:; + { + /* Print as much information as potentially useful. + Debugging load-time failures with embedding is not fun + */ + PyObject *ecap; + PyObject *exception, *v, *tb, *f, *modules, *mod; + PyErr_Fetch(&exception, &v, &tb); + ecap = _cffi_start_error_capture(); + f = PySys_GetObject((char *)"stderr"); + if (f != NULL && f != Py_None) { + PyFile_WriteString( + "Failed to initialize the Python-CFFI embedding logic:\n\n", f); + } + + if (exception != NULL) { + PyErr_NormalizeException(&exception, &v, &tb); + PyErr_Display(exception, v, tb); + } + Py_XDECREF(exception); + Py_XDECREF(v); + Py_XDECREF(tb); + + if (f != NULL && f != Py_None) { + PyFile_WriteString("\nFrom: " _CFFI_MODULE_NAME + "\ncompiled with cffi version: 2.0.0" + "\n_cffi_backend module: ", f); + modules = PyImport_GetModuleDict(); + mod = PyDict_GetItemString(modules, "_cffi_backend"); + if (mod == NULL) { + PyFile_WriteString("not loaded", f); + } + else { + v = PyObject_GetAttrString(mod, "__file__"); + PyFile_WriteObject(v, f, 0); + Py_XDECREF(v); + } + PyFile_WriteString("\nsys.path: ", f); + PyFile_WriteObject(PySys_GetObject((char *)"path"), f, 0); + PyFile_WriteString("\n\n", f); + } + _cffi_stop_error_capture(ecap); + } + result = -1; + goto done; +} + +#if PY_VERSION_HEX < 0x03080000 +PyAPI_DATA(char *) _PyParser_TokenNames[]; /* from CPython */ +#endif + +static int _cffi_carefully_make_gil(void) +{ + /* This does the basic initialization of Python. It can be called + completely concurrently from unrelated threads. It assumes + that we don't hold the GIL before (if it exists), and we don't + hold it afterwards. + + (What it really does used to be completely different in Python 2 + and Python 3, with the Python 2 solution avoiding the spin-lock + around the Py_InitializeEx() call. However, after recent changes + to CPython 2.7 (issue #358) it no longer works. So we use the + Python 3 solution everywhere.) + + This initializes Python by calling Py_InitializeEx(). + Important: this must not be called concurrently at all. + So we use a global variable as a simple spin lock. This global + variable must be from 'libpythonX.Y.so', not from this + cffi-based extension module, because it must be shared from + different cffi-based extension modules. + + In Python < 3.8, we choose + _PyParser_TokenNames[0] as a completely arbitrary pointer value + that is never written to. The default is to point to the + string "ENDMARKER". We change it temporarily to point to the + next character in that string. (Yes, I know it's REALLY + obscure.) + + In Python >= 3.8, this string array is no longer writable, so + instead we pick PyCapsuleType.tp_version_tag. We can't change + Python < 3.8 because someone might use a mixture of cffi + embedded modules, some of which were compiled before this file + changed. + + In Python >= 3.12, this stopped working because that particular + tp_version_tag gets modified during interpreter startup. It's + arguably a bad idea before 3.12 too, but again we can't change + that because someone might use a mixture of cffi embedded + modules, and no-one reported a bug so far. In Python >= 3.12 + we go instead for PyCapsuleType.tp_as_buffer, which is supposed + to always be NULL. We write to it temporarily a pointer to + a struct full of NULLs, which is semantically the same. + */ + +#ifdef WITH_THREAD +# if PY_VERSION_HEX < 0x03080000 + char *volatile *lock = (char *volatile *)_PyParser_TokenNames; + char *old_value, *locked_value; + + while (1) { /* spin loop */ + old_value = *lock; + locked_value = old_value + 1; + if (old_value[0] == 'E') { + assert(old_value[1] == 'N'); + if (cffi_compare_and_swap(lock, old_value, locked_value)) + break; + } + else { + assert(old_value[0] == 'N'); + /* should ideally do a spin loop instruction here, but + hard to do it portably and doesn't really matter I + think: PyEval_InitThreads() should be very fast, and + this is only run at start-up anyway. */ + } + } +# else +# if PY_VERSION_HEX < 0x030C0000 + int volatile *lock = (int volatile *)&PyCapsule_Type.tp_version_tag; + int old_value, locked_value = -42; + assert(!(PyCapsule_Type.tp_flags & Py_TPFLAGS_HAVE_VERSION_TAG)); +# else + static struct ebp_s { PyBufferProcs buf; int mark; } empty_buffer_procs; + empty_buffer_procs.mark = -42; + PyBufferProcs *volatile *lock = (PyBufferProcs *volatile *) + &PyCapsule_Type.tp_as_buffer; + PyBufferProcs *old_value, *locked_value = &empty_buffer_procs.buf; +# endif + + while (1) { /* spin loop */ + old_value = *lock; + if (old_value == 0) { + if (cffi_compare_and_swap(lock, old_value, locked_value)) + break; + } + else { +# if PY_VERSION_HEX < 0x030C0000 + assert(old_value == locked_value); +# else + /* The pointer should point to a possibly different + empty_buffer_procs from another C extension module */ + assert(((struct ebp_s *)old_value)->mark == -42); +# endif + /* should ideally do a spin loop instruction here, but + hard to do it portably and doesn't really matter I + think: PyEval_InitThreads() should be very fast, and + this is only run at start-up anyway. */ + } + } +# endif +#endif + + /* call Py_InitializeEx() */ + if (!Py_IsInitialized()) { + _cffi_py_initialize(); +#if PY_VERSION_HEX < 0x03070000 + PyEval_InitThreads(); +#endif + PyEval_SaveThread(); /* release the GIL */ + /* the returned tstate must be the one that has been stored into the + autoTLSkey by _PyGILState_Init() called from Py_Initialize(). */ + } + else { +#if PY_VERSION_HEX < 0x03070000 + /* PyEval_InitThreads() is always a no-op from CPython 3.7 */ + PyGILState_STATE state = PyGILState_Ensure(); + PyEval_InitThreads(); + PyGILState_Release(state); +#endif + } + +#ifdef WITH_THREAD + /* release the lock */ + while (!cffi_compare_and_swap(lock, locked_value, old_value)) + ; +#endif + + return 0; +} + +/********** end CPython-specific section **********/ + + +#else + + +/********** PyPy-specific section **********/ + +PyMODINIT_FUNC _CFFI_PYTHON_STARTUP_FUNC(const void *[]); /* forward */ + +static struct _cffi_pypy_init_s { + const char *name; + void *func; /* function pointer */ + const char *code; +} _cffi_pypy_init = { + _CFFI_MODULE_NAME, + _CFFI_PYTHON_STARTUP_FUNC, + _CFFI_PYTHON_STARTUP_CODE, +}; + +extern int pypy_carefully_make_gil(const char *); +extern int pypy_init_embedded_cffi_module(int, struct _cffi_pypy_init_s *); + +static int _cffi_carefully_make_gil(void) +{ + return pypy_carefully_make_gil(_CFFI_MODULE_NAME); +} + +static int _cffi_initialize_python(void) +{ + return pypy_init_embedded_cffi_module(0xB011, &_cffi_pypy_init); +} + +/********** end PyPy-specific section **********/ + + +#endif + + +#ifdef __GNUC__ +__attribute__((noinline)) +#endif +static _cffi_call_python_fnptr _cffi_start_python(void) +{ + /* Delicate logic to initialize Python. This function can be + called multiple times concurrently, e.g. when the process calls + its first ``extern "Python"`` functions in multiple threads at + once. It can also be called recursively, in which case we must + ignore it. We also have to consider what occurs if several + different cffi-based extensions reach this code in parallel + threads---it is a different copy of the code, then, and we + can't have any shared global variable unless it comes from + 'libpythonX.Y.so'. + + Idea: + + * _cffi_carefully_make_gil(): "carefully" call + PyEval_InitThreads() (possibly with Py_InitializeEx() first). + + * then we use a (local) custom lock to make sure that a call to this + cffi-based extension will wait if another call to the *same* + extension is running the initialization in another thread. + It is reentrant, so that a recursive call will not block, but + only one from a different thread. + + * then we grab the GIL and (Python 2) we call Py_InitializeEx(). + At this point, concurrent calls to Py_InitializeEx() are not + possible: we have the GIL. + + * do the rest of the specific initialization, which may + temporarily release the GIL but not the custom lock. + Only release the custom lock when we are done. + */ + static char called = 0; + + if (_cffi_carefully_make_gil() != 0) + return NULL; + + _cffi_acquire_reentrant_mutex(); + + /* Here the GIL exists, but we don't have it. We're only protected + from concurrency by the reentrant mutex. */ + + /* This file only initializes the embedded module once, the first + time this is called, even if there are subinterpreters. */ + if (!called) { + called = 1; /* invoke _cffi_initialize_python() only once, + but don't set '_cffi_call_python' right now, + otherwise concurrent threads won't call + this function at all (we need them to wait) */ + if (_cffi_initialize_python() == 0) { + /* now initialization is finished. Switch to the fast-path. */ + + /* We would like nobody to see the new value of + '_cffi_call_python' without also seeing the rest of the + data initialized. However, this is not possible. But + the new value of '_cffi_call_python' is the function + 'cffi_call_python()' from _cffi_backend. So: */ + cffi_write_barrier(); + /* ^^^ we put a write barrier here, and a corresponding + read barrier at the start of cffi_call_python(). This + ensures that after that read barrier, we see everything + done here before the write barrier. + */ + + assert(_cffi_call_python_org != NULL); + _cffi_call_python = (_cffi_call_python_fnptr)_cffi_call_python_org; + } + else { + /* initialization failed. Reset this to NULL, even if it was + already set to some other value. Future calls to + _cffi_start_python() are still forced to occur, and will + always return NULL from now on. */ + _cffi_call_python_org = NULL; + } + } + + _cffi_release_reentrant_mutex(); + + return (_cffi_call_python_fnptr)_cffi_call_python_org; +} + +static +void _cffi_start_and_call_python(struct _cffi_externpy_s *externpy, char *args) +{ + _cffi_call_python_fnptr fnptr; + int current_err = errno; +#ifdef _MSC_VER + int current_lasterr = GetLastError(); +#endif + fnptr = _cffi_start_python(); + if (fnptr == NULL) { + fprintf(stderr, "function %s() called, but initialization code " + "failed. Returning 0.\n", externpy->name); + memset(args, 0, externpy->size_of_result); + } +#ifdef _MSC_VER + SetLastError(current_lasterr); +#endif + errno = current_err; + + if (fnptr != NULL) + fnptr(externpy, args); +} + + +/* The cffi_start_python() function makes sure Python is initialized + and our cffi module is set up. It can be called manually from the + user C code. The same effect is obtained automatically from any + dll-exported ``extern "Python"`` function. This function returns + -1 if initialization failed, 0 if all is OK. */ +_CFFI_UNUSED_FN +static int cffi_start_python(void) +{ + if (_cffi_call_python == &_cffi_start_and_call_python) { + if (_cffi_start_python() == NULL) + return -1; + } + cffi_read_barrier(); + return 0; +} + +#undef cffi_compare_and_swap +#undef cffi_write_barrier +#undef cffi_read_barrier + +#ifdef __cplusplus +} +#endif diff --git a/intelliaide/vendor/cffi/_imp_emulation.py b/intelliaide/vendor/cffi/_imp_emulation.py new file mode 100644 index 0000000..136abdd --- /dev/null +++ b/intelliaide/vendor/cffi/_imp_emulation.py @@ -0,0 +1,83 @@ + +try: + # this works on Python < 3.12 + from imp import * + +except ImportError: + # this is a limited emulation for Python >= 3.12. + # Note that this is used only for tests or for the old ffi.verify(). + # This is copied from the source code of Python 3.11. + + from _imp import (acquire_lock, release_lock, + is_builtin, is_frozen) + + from importlib._bootstrap import _load + + from importlib import machinery + import os + import sys + import tokenize + + SEARCH_ERROR = 0 + PY_SOURCE = 1 + PY_COMPILED = 2 + C_EXTENSION = 3 + PY_RESOURCE = 4 + PKG_DIRECTORY = 5 + C_BUILTIN = 6 + PY_FROZEN = 7 + PY_CODERESOURCE = 8 + IMP_HOOK = 9 + + def get_suffixes(): + extensions = [(s, 'rb', C_EXTENSION) + for s in machinery.EXTENSION_SUFFIXES] + source = [(s, 'r', PY_SOURCE) for s in machinery.SOURCE_SUFFIXES] + bytecode = [(s, 'rb', PY_COMPILED) for s in machinery.BYTECODE_SUFFIXES] + return extensions + source + bytecode + + def find_module(name, path=None): + if not isinstance(name, str): + raise TypeError("'name' must be a str, not {}".format(type(name))) + elif not isinstance(path, (type(None), list)): + # Backwards-compatibility + raise RuntimeError("'path' must be None or a list, " + "not {}".format(type(path))) + + if path is None: + if is_builtin(name): + return None, None, ('', '', C_BUILTIN) + elif is_frozen(name): + return None, None, ('', '', PY_FROZEN) + else: + path = sys.path + + for entry in path: + package_directory = os.path.join(entry, name) + for suffix in ['.py', machinery.BYTECODE_SUFFIXES[0]]: + package_file_name = '__init__' + suffix + file_path = os.path.join(package_directory, package_file_name) + if os.path.isfile(file_path): + return None, package_directory, ('', '', PKG_DIRECTORY) + for suffix, mode, type_ in get_suffixes(): + file_name = name + suffix + file_path = os.path.join(entry, file_name) + if os.path.isfile(file_path): + break + else: + continue + break # Break out of outer loop when breaking out of inner loop. + else: + raise ImportError(name, name=name) + + encoding = None + if 'b' not in mode: + with open(file_path, 'rb') as file: + encoding = tokenize.detect_encoding(file.readline)[0] + file = open(file_path, mode, encoding=encoding) + return file, file_path, (suffix, mode, type_) + + def load_dynamic(name, path, file=None): + loader = machinery.ExtensionFileLoader(name, path) + spec = machinery.ModuleSpec(name=name, loader=loader, origin=path) + return _load(spec) diff --git a/intelliaide/vendor/cffi/_shimmed_dist_utils.py b/intelliaide/vendor/cffi/_shimmed_dist_utils.py new file mode 100644 index 0000000..c3d2312 --- /dev/null +++ b/intelliaide/vendor/cffi/_shimmed_dist_utils.py @@ -0,0 +1,45 @@ +""" +Temporary shim module to indirect the bits of distutils we need from setuptools/distutils while providing useful +error messages beyond `No module named 'distutils' on Python >= 3.12, or when setuptools' vendored distutils is broken. + +This is a compromise to avoid a hard-dep on setuptools for Python >= 3.12, since many users don't need runtime compilation support from CFFI. +""" +import sys + +try: + # import setuptools first; this is the most robust way to ensure its embedded distutils is available + # (the .pth shim should usually work, but this is even more robust) + import setuptools +except Exception as ex: + if sys.version_info >= (3, 12): + # Python 3.12 has no built-in distutils to fall back on, so any import problem is fatal + raise Exception("This CFFI feature requires setuptools on Python >= 3.12. The setuptools module is missing or non-functional.") from ex + + # silently ignore on older Pythons (support fallback to stdlib distutils where available) +else: + del setuptools + +try: + # bring in just the bits of distutils we need, whether they really came from setuptools or stdlib-embedded distutils + from distutils import log, sysconfig + from distutils.ccompiler import CCompiler + from distutils.command.build_ext import build_ext + from distutils.core import Distribution, Extension + from distutils.dir_util import mkpath + from distutils.errors import DistutilsSetupError, CompileError, LinkError + from distutils.log import set_threshold, set_verbosity + + if sys.platform == 'win32': + try: + # FUTURE: msvc9compiler module was removed in setuptools 74; consider removing, as it's only used by an ancient patch in `recompiler` + from distutils.msvc9compiler import MSVCCompiler + except ImportError: + MSVCCompiler = None +except Exception as ex: + if sys.version_info >= (3, 12): + raise Exception("This CFFI feature requires setuptools on Python >= 3.12. Please install the setuptools package.") from ex + + # anything older, just let the underlying distutils import error fly + raise Exception("This CFFI feature requires distutils. Please install the distutils or setuptools package.") from ex + +del sys diff --git a/intelliaide/vendor/cffi/api.py b/intelliaide/vendor/cffi/api.py new file mode 100644 index 0000000..5a474f3 --- /dev/null +++ b/intelliaide/vendor/cffi/api.py @@ -0,0 +1,967 @@ +import sys, types +from .lock import allocate_lock +from .error import CDefError +from . import model + +try: + callable +except NameError: + # Python 3.1 + from collections import Callable + callable = lambda x: isinstance(x, Callable) + +try: + basestring +except NameError: + # Python 3.x + basestring = str + +_unspecified = object() + + + +class FFI(object): + r''' + The main top-level class that you instantiate once, or once per module. + + Example usage: + + ffi = FFI() + ffi.cdef(""" + int printf(const char *, ...); + """) + + C = ffi.dlopen(None) # standard library + -or- + C = ffi.verify() # use a C compiler: verify the decl above is right + + C.printf("hello, %s!\n", ffi.new("char[]", "world")) + ''' + + def __init__(self, backend=None): + """Create an FFI instance. The 'backend' argument is used to + select a non-default backend, mostly for tests. + """ + if backend is None: + # You need PyPy (>= 2.0 beta), or a CPython (>= 2.6) with + # _cffi_backend.so compiled. + import _cffi_backend as backend + from . import __version__ + if backend.__version__ != __version__: + # bad version! Try to be as explicit as possible. + if hasattr(backend, '__file__'): + # CPython + raise Exception("Version mismatch: this is the 'cffi' package version %s, located in %r. When we import the top-level '_cffi_backend' extension module, we get version %s, located in %r. The two versions should be equal; check your installation." % ( + __version__, __file__, + backend.__version__, backend.__file__)) + else: + # PyPy + raise Exception("Version mismatch: this is the 'cffi' package version %s, located in %r. This interpreter comes with a built-in '_cffi_backend' module, which is version %s. The two versions should be equal; check your installation." % ( + __version__, __file__, backend.__version__)) + # (If you insist you can also try to pass the option + # 'backend=backend_ctypes.CTypesBackend()', but don't + # rely on it! It's probably not going to work well.) + + from . import cparser + self._backend = backend + self._lock = allocate_lock() + self._parser = cparser.Parser() + self._cached_btypes = {} + self._parsed_types = types.ModuleType('parsed_types').__dict__ + self._new_types = types.ModuleType('new_types').__dict__ + self._function_caches = [] + self._libraries = [] + self._cdefsources = [] + self._included_ffis = [] + self._windows_unicode = None + self._init_once_cache = {} + self._cdef_version = None + self._embedding = None + self._typecache = model.get_typecache(backend) + if hasattr(backend, 'set_ffi'): + backend.set_ffi(self) + for name in list(backend.__dict__): + if name.startswith('RTLD_'): + setattr(self, name, getattr(backend, name)) + # + with self._lock: + self.BVoidP = self._get_cached_btype(model.voidp_type) + self.BCharA = self._get_cached_btype(model.char_array_type) + if isinstance(backend, types.ModuleType): + # _cffi_backend: attach these constants to the class + if not hasattr(FFI, 'NULL'): + FFI.NULL = self.cast(self.BVoidP, 0) + FFI.CData, FFI.CType = backend._get_types() + else: + # ctypes backend: attach these constants to the instance + self.NULL = self.cast(self.BVoidP, 0) + self.CData, self.CType = backend._get_types() + self.buffer = backend.buffer + + def cdef(self, csource, override=False, packed=False, pack=None): + """Parse the given C source. This registers all declared functions, + types, and global variables. The functions and global variables can + then be accessed via either 'ffi.dlopen()' or 'ffi.verify()'. + The types can be used in 'ffi.new()' and other functions. + If 'packed' is specified as True, all structs declared inside this + cdef are packed, i.e. laid out without any field alignment at all. + Alternatively, 'pack' can be a small integer, and requests for + alignment greater than that are ignored (pack=1 is equivalent to + packed=True). + """ + self._cdef(csource, override=override, packed=packed, pack=pack) + + def embedding_api(self, csource, packed=False, pack=None): + self._cdef(csource, packed=packed, pack=pack, dllexport=True) + if self._embedding is None: + self._embedding = '' + + def _cdef(self, csource, override=False, **options): + if not isinstance(csource, str): # unicode, on Python 2 + if not isinstance(csource, basestring): + raise TypeError("cdef() argument must be a string") + csource = csource.encode('ascii') + with self._lock: + self._cdef_version = object() + self._parser.parse(csource, override=override, **options) + self._cdefsources.append(csource) + if override: + for cache in self._function_caches: + cache.clear() + finishlist = self._parser._recomplete + if finishlist: + self._parser._recomplete = [] + for tp in finishlist: + tp.finish_backend_type(self, finishlist) + + def dlopen(self, name, flags=0): + """Load and return a dynamic library identified by 'name'. + The standard C library can be loaded by passing None. + Note that functions and types declared by 'ffi.cdef()' are not + linked to a particular library, just like C headers; in the + library we only look for the actual (untyped) symbols. + """ + if not (isinstance(name, basestring) or + name is None or + isinstance(name, self.CData)): + raise TypeError("dlopen(name): name must be a file name, None, " + "or an already-opened 'void *' handle") + with self._lock: + lib, function_cache = _make_ffi_library(self, name, flags) + self._function_caches.append(function_cache) + self._libraries.append(lib) + return lib + + def dlclose(self, lib): + """Close a library obtained with ffi.dlopen(). After this call, + access to functions or variables from the library will fail + (possibly with a segmentation fault). + """ + type(lib).__cffi_close__(lib) + + def _typeof_locked(self, cdecl): + # call me with the lock! + key = cdecl + if key in self._parsed_types: + return self._parsed_types[key] + # + if not isinstance(cdecl, str): # unicode, on Python 2 + cdecl = cdecl.encode('ascii') + # + type = self._parser.parse_type(cdecl) + really_a_function_type = type.is_raw_function + if really_a_function_type: + type = type.as_function_pointer() + btype = self._get_cached_btype(type) + result = btype, really_a_function_type + self._parsed_types[key] = result + return result + + def _typeof(self, cdecl, consider_function_as_funcptr=False): + # string -> ctype object + try: + result = self._parsed_types[cdecl] + except KeyError: + with self._lock: + result = self._typeof_locked(cdecl) + # + btype, really_a_function_type = result + if really_a_function_type and not consider_function_as_funcptr: + raise CDefError("the type %r is a function type, not a " + "pointer-to-function type" % (cdecl,)) + return btype + + def typeof(self, cdecl): + """Parse the C type given as a string and return the + corresponding object. + It can also be used on 'cdata' instance to get its C type. + """ + if isinstance(cdecl, basestring): + return self._typeof(cdecl) + if isinstance(cdecl, self.CData): + return self._backend.typeof(cdecl) + if isinstance(cdecl, types.BuiltinFunctionType): + res = _builtin_function_type(cdecl) + if res is not None: + return res + if (isinstance(cdecl, types.FunctionType) + and hasattr(cdecl, '_cffi_base_type')): + with self._lock: + return self._get_cached_btype(cdecl._cffi_base_type) + raise TypeError(type(cdecl)) + + def sizeof(self, cdecl): + """Return the size in bytes of the argument. It can be a + string naming a C type, or a 'cdata' instance. + """ + if isinstance(cdecl, basestring): + BType = self._typeof(cdecl) + return self._backend.sizeof(BType) + else: + return self._backend.sizeof(cdecl) + + def alignof(self, cdecl): + """Return the natural alignment size in bytes of the C type + given as a string. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._backend.alignof(cdecl) + + def offsetof(self, cdecl, *fields_or_indexes): + """Return the offset of the named field inside the given + structure or array, which must be given as a C type name. + You can give several field names in case of nested structures. + You can also give numeric values which correspond to array + items, in case of an array type. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._typeoffsetof(cdecl, *fields_or_indexes)[1] + + def new(self, cdecl, init=None): + """Allocate an instance according to the specified C type and + return a pointer to it. The specified C type must be either a + pointer or an array: ``new('X *')`` allocates an X and returns + a pointer to it, whereas ``new('X[n]')`` allocates an array of + n X'es and returns an array referencing it (which works + mostly like a pointer, like in C). You can also use + ``new('X[]', n)`` to allocate an array of a non-constant + length n. + + The memory is initialized following the rules of declaring a + global variable in C: by default it is zero-initialized, but + an explicit initializer can be given which can be used to + fill all or part of the memory. + + When the returned object goes out of scope, the memory + is freed. In other words the returned object has + ownership of the value of type 'cdecl' that it points to. This + means that the raw data can be used as long as this object is + kept alive, but must not be used for a longer time. Be careful + about that when copying the pointer to the memory somewhere + else, e.g. into another structure. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._backend.newp(cdecl, init) + + def new_allocator(self, alloc=None, free=None, + should_clear_after_alloc=True): + """Return a new allocator, i.e. a function that behaves like ffi.new() + but uses the provided low-level 'alloc' and 'free' functions. + + 'alloc' is called with the size as argument. If it returns NULL, a + MemoryError is raised. 'free' is called with the result of 'alloc' + as argument. Both can be either Python function or directly C + functions. If 'free' is None, then no free function is called. + If both 'alloc' and 'free' are None, the default is used. + + If 'should_clear_after_alloc' is set to False, then the memory + returned by 'alloc' is assumed to be already cleared (or you are + fine with garbage); otherwise CFFI will clear it. + """ + compiled_ffi = self._backend.FFI() + allocator = compiled_ffi.new_allocator(alloc, free, + should_clear_after_alloc) + def allocate(cdecl, init=None): + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return allocator(cdecl, init) + return allocate + + def cast(self, cdecl, source): + """Similar to a C cast: returns an instance of the named C + type initialized with the given 'source'. The source is + casted between integers or pointers of any type. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._backend.cast(cdecl, source) + + def string(self, cdata, maxlen=-1): + """Return a Python string (or unicode string) from the 'cdata'. + If 'cdata' is a pointer or array of characters or bytes, returns + the null-terminated string. The returned string extends until + the first null character, or at most 'maxlen' characters. If + 'cdata' is an array then 'maxlen' defaults to its length. + + If 'cdata' is a pointer or array of wchar_t, returns a unicode + string following the same rules. + + If 'cdata' is a single character or byte or a wchar_t, returns + it as a string or unicode string. + + If 'cdata' is an enum, returns the value of the enumerator as a + string, or 'NUMBER' if the value is out of range. + """ + return self._backend.string(cdata, maxlen) + + def unpack(self, cdata, length): + """Unpack an array of C data of the given length, + returning a Python string/unicode/list. + + If 'cdata' is a pointer to 'char', returns a byte string. + It does not stop at the first null. This is equivalent to: + ffi.buffer(cdata, length)[:] + + If 'cdata' is a pointer to 'wchar_t', returns a unicode string. + 'length' is measured in wchar_t's; it is not the size in bytes. + + If 'cdata' is a pointer to anything else, returns a list of + 'length' items. This is a faster equivalent to: + [cdata[i] for i in range(length)] + """ + return self._backend.unpack(cdata, length) + + #def buffer(self, cdata, size=-1): + # """Return a read-write buffer object that references the raw C data + # pointed to by the given 'cdata'. The 'cdata' must be a pointer or + # an array. Can be passed to functions expecting a buffer, or directly + # manipulated with: + # + # buf[:] get a copy of it in a regular string, or + # buf[idx] as a single character + # buf[:] = ... + # buf[idx] = ... change the content + # """ + # note that 'buffer' is a type, set on this instance by __init__ + + def from_buffer(self, cdecl, python_buffer=_unspecified, + require_writable=False): + """Return a cdata of the given type pointing to the data of the + given Python object, which must support the buffer interface. + Note that this is not meant to be used on the built-in types + str or unicode (you can build 'char[]' arrays explicitly) + but only on objects containing large quantities of raw data + in some other format, like 'array.array' or numpy arrays. + + The first argument is optional and default to 'char[]'. + """ + if python_buffer is _unspecified: + cdecl, python_buffer = self.BCharA, cdecl + elif isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + return self._backend.from_buffer(cdecl, python_buffer, + require_writable) + + def memmove(self, dest, src, n): + """ffi.memmove(dest, src, n) copies n bytes of memory from src to dest. + + Like the C function memmove(), the memory areas may overlap; + apart from that it behaves like the C function memcpy(). + + 'src' can be any cdata ptr or array, or any Python buffer object. + 'dest' can be any cdata ptr or array, or a writable Python buffer + object. The size to copy, 'n', is always measured in bytes. + + Unlike other methods, this one supports all Python buffer including + byte strings and bytearrays---but it still does not support + non-contiguous buffers. + """ + return self._backend.memmove(dest, src, n) + + def callback(self, cdecl, python_callable=None, error=None, onerror=None): + """Return a callback object or a decorator making such a + callback object. 'cdecl' must name a C function pointer type. + The callback invokes the specified 'python_callable' (which may + be provided either directly or via a decorator). Important: the + callback object must be manually kept alive for as long as the + callback may be invoked from the C level. + """ + def callback_decorator_wrap(python_callable): + if not callable(python_callable): + raise TypeError("the 'python_callable' argument " + "is not callable") + return self._backend.callback(cdecl, python_callable, + error, onerror) + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl, consider_function_as_funcptr=True) + if python_callable is None: + return callback_decorator_wrap # decorator mode + else: + return callback_decorator_wrap(python_callable) # direct mode + + def getctype(self, cdecl, replace_with=''): + """Return a string giving the C type 'cdecl', which may be itself + a string or a object. If 'replace_with' is given, it gives + extra text to append (or insert for more complicated C types), like + a variable name, or '*' to get actually the C type 'pointer-to-cdecl'. + """ + if isinstance(cdecl, basestring): + cdecl = self._typeof(cdecl) + replace_with = replace_with.strip() + if (replace_with.startswith('*') + and '&[' in self._backend.getcname(cdecl, '&')): + replace_with = '(%s)' % replace_with + elif replace_with and not replace_with[0] in '[(': + replace_with = ' ' + replace_with + return self._backend.getcname(cdecl, replace_with) + + def gc(self, cdata, destructor, size=0): + """Return a new cdata object that points to the same + data. Later, when this new cdata object is garbage-collected, + 'destructor(old_cdata_object)' will be called. + + The optional 'size' gives an estimate of the size, used to + trigger the garbage collection more eagerly. So far only used + on PyPy. It tells the GC that the returned object keeps alive + roughly 'size' bytes of external memory. + """ + return self._backend.gcp(cdata, destructor, size) + + def _get_cached_btype(self, type): + assert self._lock.acquire(False) is False + # call me with the lock! + try: + BType = self._cached_btypes[type] + except KeyError: + finishlist = [] + BType = type.get_cached_btype(self, finishlist) + for type in finishlist: + type.finish_backend_type(self, finishlist) + return BType + + def verify(self, source='', tmpdir=None, **kwargs): + """Verify that the current ffi signatures compile on this + machine, and return a dynamic library object. The dynamic + library can be used to call functions and access global + variables declared in this 'ffi'. The library is compiled + by the C compiler: it gives you C-level API compatibility + (including calling macros). This is unlike 'ffi.dlopen()', + which requires binary compatibility in the signatures. + """ + from .verifier import Verifier, _caller_dir_pycache + # + # If set_unicode(True) was called, insert the UNICODE and + # _UNICODE macro declarations + if self._windows_unicode: + self._apply_windows_unicode(kwargs) + # + # Set the tmpdir here, and not in Verifier.__init__: it picks + # up the caller's directory, which we want to be the caller of + # ffi.verify(), as opposed to the caller of Veritier(). + tmpdir = tmpdir or _caller_dir_pycache() + # + # Make a Verifier() and use it to load the library. + self.verifier = Verifier(self, source, tmpdir, **kwargs) + lib = self.verifier.load_library() + # + # Save the loaded library for keep-alive purposes, even + # if the caller doesn't keep it alive itself (it should). + self._libraries.append(lib) + return lib + + def _get_errno(self): + return self._backend.get_errno() + def _set_errno(self, errno): + self._backend.set_errno(errno) + errno = property(_get_errno, _set_errno, None, + "the value of 'errno' from/to the C calls") + + def getwinerror(self, code=-1): + return self._backend.getwinerror(code) + + def _pointer_to(self, ctype): + with self._lock: + return model.pointer_cache(self, ctype) + + def addressof(self, cdata, *fields_or_indexes): + """Return the address of a . + If 'fields_or_indexes' are given, returns the address of that + field or array item in the structure or array, recursively in + case of nested structures. + """ + try: + ctype = self._backend.typeof(cdata) + except TypeError: + if '__addressof__' in type(cdata).__dict__: + return type(cdata).__addressof__(cdata, *fields_or_indexes) + raise + if fields_or_indexes: + ctype, offset = self._typeoffsetof(ctype, *fields_or_indexes) + else: + if ctype.kind == "pointer": + raise TypeError("addressof(pointer)") + offset = 0 + ctypeptr = self._pointer_to(ctype) + return self._backend.rawaddressof(ctypeptr, cdata, offset) + + def _typeoffsetof(self, ctype, field_or_index, *fields_or_indexes): + ctype, offset = self._backend.typeoffsetof(ctype, field_or_index) + for field1 in fields_or_indexes: + ctype, offset1 = self._backend.typeoffsetof(ctype, field1, 1) + offset += offset1 + return ctype, offset + + def include(self, ffi_to_include): + """Includes the typedefs, structs, unions and enums defined + in another FFI instance. Usage is similar to a #include in C, + where a part of the program might include types defined in + another part for its own usage. Note that the include() + method has no effect on functions, constants and global + variables, which must anyway be accessed directly from the + lib object returned by the original FFI instance. + """ + if not isinstance(ffi_to_include, FFI): + raise TypeError("ffi.include() expects an argument that is also of" + " type cffi.FFI, not %r" % ( + type(ffi_to_include).__name__,)) + if ffi_to_include is self: + raise ValueError("self.include(self)") + with ffi_to_include._lock: + with self._lock: + self._parser.include(ffi_to_include._parser) + self._cdefsources.append('[') + self._cdefsources.extend(ffi_to_include._cdefsources) + self._cdefsources.append(']') + self._included_ffis.append(ffi_to_include) + + def new_handle(self, x): + return self._backend.newp_handle(self.BVoidP, x) + + def from_handle(self, x): + return self._backend.from_handle(x) + + def release(self, x): + self._backend.release(x) + + def set_unicode(self, enabled_flag): + """Windows: if 'enabled_flag' is True, enable the UNICODE and + _UNICODE defines in C, and declare the types like TCHAR and LPTCSTR + to be (pointers to) wchar_t. If 'enabled_flag' is False, + declare these types to be (pointers to) plain 8-bit characters. + This is mostly for backward compatibility; you usually want True. + """ + if self._windows_unicode is not None: + raise ValueError("set_unicode() can only be called once") + enabled_flag = bool(enabled_flag) + if enabled_flag: + self.cdef("typedef wchar_t TBYTE;" + "typedef wchar_t TCHAR;" + "typedef const wchar_t *LPCTSTR;" + "typedef const wchar_t *PCTSTR;" + "typedef wchar_t *LPTSTR;" + "typedef wchar_t *PTSTR;" + "typedef TBYTE *PTBYTE;" + "typedef TCHAR *PTCHAR;") + else: + self.cdef("typedef char TBYTE;" + "typedef char TCHAR;" + "typedef const char *LPCTSTR;" + "typedef const char *PCTSTR;" + "typedef char *LPTSTR;" + "typedef char *PTSTR;" + "typedef TBYTE *PTBYTE;" + "typedef TCHAR *PTCHAR;") + self._windows_unicode = enabled_flag + + def _apply_windows_unicode(self, kwds): + defmacros = kwds.get('define_macros', ()) + if not isinstance(defmacros, (list, tuple)): + raise TypeError("'define_macros' must be a list or tuple") + defmacros = list(defmacros) + [('UNICODE', '1'), + ('_UNICODE', '1')] + kwds['define_macros'] = defmacros + + def _apply_embedding_fix(self, kwds): + # must include an argument like "-lpython2.7" for the compiler + def ensure(key, value): + lst = kwds.setdefault(key, []) + if value not in lst: + lst.append(value) + # + if '__pypy__' in sys.builtin_module_names: + import os + if sys.platform == "win32": + # we need 'libpypy-c.lib'. Current distributions of + # pypy (>= 4.1) contain it as 'libs/python27.lib'. + pythonlib = "python{0[0]}{0[1]}".format(sys.version_info) + if hasattr(sys, 'prefix'): + ensure('library_dirs', os.path.join(sys.prefix, 'libs')) + else: + # we need 'libpypy-c.{so,dylib}', which should be by + # default located in 'sys.prefix/bin' for installed + # systems. + if sys.version_info < (3,): + pythonlib = "pypy-c" + else: + pythonlib = "pypy3-c" + if hasattr(sys, 'prefix'): + ensure('library_dirs', os.path.join(sys.prefix, 'bin')) + # On uninstalled pypy's, the libpypy-c is typically found in + # .../pypy/goal/. + if hasattr(sys, 'prefix'): + ensure('library_dirs', os.path.join(sys.prefix, 'pypy', 'goal')) + else: + if sys.platform == "win32": + template = "python%d%d" + if hasattr(sys, 'gettotalrefcount'): + template += '_d' + else: + try: + import sysconfig + except ImportError: # 2.6 + from cffi._shimmed_dist_utils import sysconfig + template = "python%d.%d" + if sysconfig.get_config_var('DEBUG_EXT'): + template += sysconfig.get_config_var('DEBUG_EXT') + pythonlib = (template % + (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) + if hasattr(sys, 'abiflags'): + pythonlib += sys.abiflags + ensure('libraries', pythonlib) + if sys.platform == "win32": + ensure('extra_link_args', '/MANIFEST') + + def set_source(self, module_name, source, source_extension='.c', **kwds): + import os + if hasattr(self, '_assigned_source'): + raise ValueError("set_source() cannot be called several times " + "per ffi object") + if not isinstance(module_name, basestring): + raise TypeError("'module_name' must be a string") + if os.sep in module_name or (os.altsep and os.altsep in module_name): + raise ValueError("'module_name' must not contain '/': use a dotted " + "name to make a 'package.module' location") + self._assigned_source = (str(module_name), source, + source_extension, kwds) + + def set_source_pkgconfig(self, module_name, pkgconfig_libs, source, + source_extension='.c', **kwds): + from . import pkgconfig + if not isinstance(pkgconfig_libs, list): + raise TypeError("the pkgconfig_libs argument must be a list " + "of package names") + kwds2 = pkgconfig.flags_from_pkgconfig(pkgconfig_libs) + pkgconfig.merge_flags(kwds, kwds2) + self.set_source(module_name, source, source_extension, **kwds) + + def distutils_extension(self, tmpdir='build', verbose=True): + from cffi._shimmed_dist_utils import mkpath + from .recompiler import recompile + # + if not hasattr(self, '_assigned_source'): + if hasattr(self, 'verifier'): # fallback, 'tmpdir' ignored + return self.verifier.get_extension() + raise ValueError("set_source() must be called before" + " distutils_extension()") + module_name, source, source_extension, kwds = self._assigned_source + if source is None: + raise TypeError("distutils_extension() is only for C extension " + "modules, not for dlopen()-style pure Python " + "modules") + mkpath(tmpdir) + ext, updated = recompile(self, module_name, + source, tmpdir=tmpdir, extradir=tmpdir, + source_extension=source_extension, + call_c_compiler=False, **kwds) + if verbose: + if updated: + sys.stderr.write("regenerated: %r\n" % (ext.sources[0],)) + else: + sys.stderr.write("not modified: %r\n" % (ext.sources[0],)) + return ext + + def emit_c_code(self, filename): + from .recompiler import recompile + # + if not hasattr(self, '_assigned_source'): + raise ValueError("set_source() must be called before emit_c_code()") + module_name, source, source_extension, kwds = self._assigned_source + if source is None: + raise TypeError("emit_c_code() is only for C extension modules, " + "not for dlopen()-style pure Python modules") + recompile(self, module_name, source, + c_file=filename, call_c_compiler=False, + uses_ffiplatform=False, **kwds) + + def emit_python_code(self, filename): + from .recompiler import recompile + # + if not hasattr(self, '_assigned_source'): + raise ValueError("set_source() must be called before emit_c_code()") + module_name, source, source_extension, kwds = self._assigned_source + if source is not None: + raise TypeError("emit_python_code() is only for dlopen()-style " + "pure Python modules, not for C extension modules") + recompile(self, module_name, source, + c_file=filename, call_c_compiler=False, + uses_ffiplatform=False, **kwds) + + def compile(self, tmpdir='.', verbose=0, target=None, debug=None): + """The 'target' argument gives the final file name of the + compiled DLL. Use '*' to force distutils' choice, suitable for + regular CPython C API modules. Use a file name ending in '.*' + to ask for the system's default extension for dynamic libraries + (.so/.dll/.dylib). + + The default is '*' when building a non-embedded C API extension, + and (module_name + '.*') when building an embedded library. + """ + from .recompiler import recompile + # + if not hasattr(self, '_assigned_source'): + raise ValueError("set_source() must be called before compile()") + module_name, source, source_extension, kwds = self._assigned_source + return recompile(self, module_name, source, tmpdir=tmpdir, + target=target, source_extension=source_extension, + compiler_verbose=verbose, debug=debug, **kwds) + + def init_once(self, func, tag): + # Read _init_once_cache[tag], which is either (False, lock) if + # we're calling the function now in some thread, or (True, result). + # Don't call setdefault() in most cases, to avoid allocating and + # immediately freeing a lock; but still use setdefaut() to avoid + # races. + try: + x = self._init_once_cache[tag] + except KeyError: + x = self._init_once_cache.setdefault(tag, (False, allocate_lock())) + # Common case: we got (True, result), so we return the result. + if x[0]: + return x[1] + # Else, it's a lock. Acquire it to serialize the following tests. + with x[1]: + # Read again from _init_once_cache the current status. + x = self._init_once_cache[tag] + if x[0]: + return x[1] + # Call the function and store the result back. + result = func() + self._init_once_cache[tag] = (True, result) + return result + + def embedding_init_code(self, pysource): + if self._embedding: + raise ValueError("embedding_init_code() can only be called once") + # fix 'pysource' before it gets dumped into the C file: + # - remove empty lines at the beginning, so it starts at "line 1" + # - dedent, if all non-empty lines are indented + # - check for SyntaxErrors + import re + match = re.match(r'\s*\n', pysource) + if match: + pysource = pysource[match.end():] + lines = pysource.splitlines() or [''] + prefix = re.match(r'\s*', lines[0]).group() + for i in range(1, len(lines)): + line = lines[i] + if line.rstrip(): + while not line.startswith(prefix): + prefix = prefix[:-1] + i = len(prefix) + lines = [line[i:]+'\n' for line in lines] + pysource = ''.join(lines) + # + compile(pysource, "cffi_init", "exec") + # + self._embedding = pysource + + def def_extern(self, *args, **kwds): + raise ValueError("ffi.def_extern() is only available on API-mode FFI " + "objects") + + def list_types(self): + """Returns the user type names known to this FFI instance. + This returns a tuple containing three lists of names: + (typedef_names, names_of_structs, names_of_unions) + """ + typedefs = [] + structs = [] + unions = [] + for key in self._parser._declarations: + if key.startswith('typedef '): + typedefs.append(key[8:]) + elif key.startswith('struct '): + structs.append(key[7:]) + elif key.startswith('union '): + unions.append(key[6:]) + typedefs.sort() + structs.sort() + unions.sort() + return (typedefs, structs, unions) + + +def _load_backend_lib(backend, name, flags): + import os + if not isinstance(name, basestring): + if sys.platform != "win32" or name is not None: + return backend.load_library(name, flags) + name = "c" # Windows: load_library(None) fails, but this works + # on Python 2 (backward compatibility hack only) + first_error = None + if '.' in name or '/' in name or os.sep in name: + try: + return backend.load_library(name, flags) + except OSError as e: + first_error = e + import ctypes.util + path = ctypes.util.find_library(name) + if path is None: + if name == "c" and sys.platform == "win32" and sys.version_info >= (3,): + raise OSError("dlopen(None) cannot work on Windows for Python 3 " + "(see http://bugs.python.org/issue23606)") + msg = ("ctypes.util.find_library() did not manage " + "to locate a library called %r" % (name,)) + if first_error is not None: + msg = "%s. Additionally, %s" % (first_error, msg) + raise OSError(msg) + return backend.load_library(path, flags) + +def _make_ffi_library(ffi, libname, flags): + backend = ffi._backend + backendlib = _load_backend_lib(backend, libname, flags) + # + def accessor_function(name): + key = 'function ' + name + tp, _ = ffi._parser._declarations[key] + BType = ffi._get_cached_btype(tp) + value = backendlib.load_function(BType, name) + library.__dict__[name] = value + # + def accessor_variable(name): + key = 'variable ' + name + tp, _ = ffi._parser._declarations[key] + BType = ffi._get_cached_btype(tp) + read_variable = backendlib.read_variable + write_variable = backendlib.write_variable + setattr(FFILibrary, name, property( + lambda self: read_variable(BType, name), + lambda self, value: write_variable(BType, name, value))) + # + def addressof_var(name): + try: + return addr_variables[name] + except KeyError: + with ffi._lock: + if name not in addr_variables: + key = 'variable ' + name + tp, _ = ffi._parser._declarations[key] + BType = ffi._get_cached_btype(tp) + if BType.kind != 'array': + BType = model.pointer_cache(ffi, BType) + p = backendlib.load_function(BType, name) + addr_variables[name] = p + return addr_variables[name] + # + def accessor_constant(name): + raise NotImplementedError("non-integer constant '%s' cannot be " + "accessed from a dlopen() library" % (name,)) + # + def accessor_int_constant(name): + library.__dict__[name] = ffi._parser._int_constants[name] + # + accessors = {} + accessors_version = [False] + addr_variables = {} + # + def update_accessors(): + if accessors_version[0] is ffi._cdef_version: + return + # + for key, (tp, _) in ffi._parser._declarations.items(): + if not isinstance(tp, model.EnumType): + tag, name = key.split(' ', 1) + if tag == 'function': + accessors[name] = accessor_function + elif tag == 'variable': + accessors[name] = accessor_variable + elif tag == 'constant': + accessors[name] = accessor_constant + else: + for i, enumname in enumerate(tp.enumerators): + def accessor_enum(name, tp=tp, i=i): + tp.check_not_partial() + library.__dict__[name] = tp.enumvalues[i] + accessors[enumname] = accessor_enum + for name in ffi._parser._int_constants: + accessors.setdefault(name, accessor_int_constant) + accessors_version[0] = ffi._cdef_version + # + def make_accessor(name): + with ffi._lock: + if name in library.__dict__ or name in FFILibrary.__dict__: + return # added by another thread while waiting for the lock + if name not in accessors: + update_accessors() + if name not in accessors: + raise AttributeError(name) + accessors[name](name) + # + class FFILibrary(object): + def __getattr__(self, name): + make_accessor(name) + return getattr(self, name) + def __setattr__(self, name, value): + try: + property = getattr(self.__class__, name) + except AttributeError: + make_accessor(name) + setattr(self, name, value) + else: + property.__set__(self, value) + def __dir__(self): + with ffi._lock: + update_accessors() + return accessors.keys() + def __addressof__(self, name): + if name in library.__dict__: + return library.__dict__[name] + if name in FFILibrary.__dict__: + return addressof_var(name) + make_accessor(name) + if name in library.__dict__: + return library.__dict__[name] + if name in FFILibrary.__dict__: + return addressof_var(name) + raise AttributeError("cffi library has no function or " + "global variable named '%s'" % (name,)) + def __cffi_close__(self): + backendlib.close_lib() + self.__dict__.clear() + # + if isinstance(libname, basestring): + try: + if not isinstance(libname, str): # unicode, on Python 2 + libname = libname.encode('utf-8') + FFILibrary.__name__ = 'FFILibrary_%s' % libname + except UnicodeError: + pass + library = FFILibrary() + return library, library.__dict__ + +def _builtin_function_type(func): + # a hack to make at least ffi.typeof(builtin_function) work, + # if the builtin function was obtained by 'vengine_cpy'. + import sys + try: + module = sys.modules[func.__module__] + ffi = module._cffi_original_ffi + types_of_builtin_funcs = module._cffi_types_of_builtin_funcs + tp = types_of_builtin_funcs[func] + except (KeyError, AttributeError, TypeError): + return None + else: + with ffi._lock: + return ffi._get_cached_btype(tp) diff --git a/intelliaide/vendor/cffi/backend_ctypes.py b/intelliaide/vendor/cffi/backend_ctypes.py new file mode 100644 index 0000000..e7956a7 --- /dev/null +++ b/intelliaide/vendor/cffi/backend_ctypes.py @@ -0,0 +1,1121 @@ +import ctypes, ctypes.util, operator, sys +from . import model + +if sys.version_info < (3,): + bytechr = chr +else: + unicode = str + long = int + xrange = range + bytechr = lambda num: bytes([num]) + +class CTypesType(type): + pass + +class CTypesData(object): + __metaclass__ = CTypesType + __slots__ = ['__weakref__'] + __name__ = '' + + def __init__(self, *args): + raise TypeError("cannot instantiate %r" % (self.__class__,)) + + @classmethod + def _newp(cls, init): + raise TypeError("expected a pointer or array ctype, got '%s'" + % (cls._get_c_name(),)) + + @staticmethod + def _to_ctypes(value): + raise TypeError + + @classmethod + def _arg_to_ctypes(cls, *value): + try: + ctype = cls._ctype + except AttributeError: + raise TypeError("cannot create an instance of %r" % (cls,)) + if value: + res = cls._to_ctypes(*value) + if not isinstance(res, ctype): + res = cls._ctype(res) + else: + res = cls._ctype() + return res + + @classmethod + def _create_ctype_obj(cls, init): + if init is None: + return cls._arg_to_ctypes() + else: + return cls._arg_to_ctypes(init) + + @staticmethod + def _from_ctypes(ctypes_value): + raise TypeError + + @classmethod + def _get_c_name(cls, replace_with=''): + return cls._reftypename.replace(' &', replace_with) + + @classmethod + def _fix_class(cls): + cls.__name__ = 'CData<%s>' % (cls._get_c_name(),) + cls.__qualname__ = 'CData<%s>' % (cls._get_c_name(),) + cls.__module__ = 'ffi' + + def _get_own_repr(self): + raise NotImplementedError + + def _addr_repr(self, address): + if address == 0: + return 'NULL' + else: + if address < 0: + address += 1 << (8*ctypes.sizeof(ctypes.c_void_p)) + return '0x%x' % address + + def __repr__(self, c_name=None): + own = self._get_own_repr() + return '' % (c_name or self._get_c_name(), own) + + def _convert_to_address(self, BClass): + if BClass is None: + raise TypeError("cannot convert %r to an address" % ( + self._get_c_name(),)) + else: + raise TypeError("cannot convert %r to %r" % ( + self._get_c_name(), BClass._get_c_name())) + + @classmethod + def _get_size(cls): + return ctypes.sizeof(cls._ctype) + + def _get_size_of_instance(self): + return ctypes.sizeof(self._ctype) + + @classmethod + def _cast_from(cls, source): + raise TypeError("cannot cast to %r" % (cls._get_c_name(),)) + + def _cast_to_integer(self): + return self._convert_to_address(None) + + @classmethod + def _alignment(cls): + return ctypes.alignment(cls._ctype) + + def __iter__(self): + raise TypeError("cdata %r does not support iteration" % ( + self._get_c_name()),) + + def _make_cmp(name): + cmpfunc = getattr(operator, name) + def cmp(self, other): + v_is_ptr = not isinstance(self, CTypesGenericPrimitive) + w_is_ptr = (isinstance(other, CTypesData) and + not isinstance(other, CTypesGenericPrimitive)) + if v_is_ptr and w_is_ptr: + return cmpfunc(self._convert_to_address(None), + other._convert_to_address(None)) + elif v_is_ptr or w_is_ptr: + return NotImplemented + else: + if isinstance(self, CTypesGenericPrimitive): + self = self._value + if isinstance(other, CTypesGenericPrimitive): + other = other._value + return cmpfunc(self, other) + cmp.func_name = name + return cmp + + __eq__ = _make_cmp('__eq__') + __ne__ = _make_cmp('__ne__') + __lt__ = _make_cmp('__lt__') + __le__ = _make_cmp('__le__') + __gt__ = _make_cmp('__gt__') + __ge__ = _make_cmp('__ge__') + + def __hash__(self): + return hash(self._convert_to_address(None)) + + def _to_string(self, maxlen): + raise TypeError("string(): %r" % (self,)) + + +class CTypesGenericPrimitive(CTypesData): + __slots__ = [] + + def __hash__(self): + return hash(self._value) + + def _get_own_repr(self): + return repr(self._from_ctypes(self._value)) + + +class CTypesGenericArray(CTypesData): + __slots__ = [] + + @classmethod + def _newp(cls, init): + return cls(init) + + def __iter__(self): + for i in xrange(len(self)): + yield self[i] + + def _get_own_repr(self): + return self._addr_repr(ctypes.addressof(self._blob)) + + +class CTypesGenericPtr(CTypesData): + __slots__ = ['_address', '_as_ctype_ptr'] + _automatic_casts = False + kind = "pointer" + + @classmethod + def _newp(cls, init): + return cls(init) + + @classmethod + def _cast_from(cls, source): + if source is None: + address = 0 + elif isinstance(source, CTypesData): + address = source._cast_to_integer() + elif isinstance(source, (int, long)): + address = source + else: + raise TypeError("bad type for cast to %r: %r" % + (cls, type(source).__name__)) + return cls._new_pointer_at(address) + + @classmethod + def _new_pointer_at(cls, address): + self = cls.__new__(cls) + self._address = address + self._as_ctype_ptr = ctypes.cast(address, cls._ctype) + return self + + def _get_own_repr(self): + try: + return self._addr_repr(self._address) + except AttributeError: + return '???' + + def _cast_to_integer(self): + return self._address + + def __nonzero__(self): + return bool(self._address) + __bool__ = __nonzero__ + + @classmethod + def _to_ctypes(cls, value): + if not isinstance(value, CTypesData): + raise TypeError("unexpected %s object" % type(value).__name__) + address = value._convert_to_address(cls) + return ctypes.cast(address, cls._ctype) + + @classmethod + def _from_ctypes(cls, ctypes_ptr): + address = ctypes.cast(ctypes_ptr, ctypes.c_void_p).value or 0 + return cls._new_pointer_at(address) + + @classmethod + def _initialize(cls, ctypes_ptr, value): + if value: + ctypes_ptr.contents = cls._to_ctypes(value).contents + + def _convert_to_address(self, BClass): + if (BClass in (self.__class__, None) or BClass._automatic_casts + or self._automatic_casts): + return self._address + else: + return CTypesData._convert_to_address(self, BClass) + + +class CTypesBaseStructOrUnion(CTypesData): + __slots__ = ['_blob'] + + @classmethod + def _create_ctype_obj(cls, init): + # may be overridden + raise TypeError("cannot instantiate opaque type %s" % (cls,)) + + def _get_own_repr(self): + return self._addr_repr(ctypes.addressof(self._blob)) + + @classmethod + def _offsetof(cls, fieldname): + return getattr(cls._ctype, fieldname).offset + + def _convert_to_address(self, BClass): + if getattr(BClass, '_BItem', None) is self.__class__: + return ctypes.addressof(self._blob) + else: + return CTypesData._convert_to_address(self, BClass) + + @classmethod + def _from_ctypes(cls, ctypes_struct_or_union): + self = cls.__new__(cls) + self._blob = ctypes_struct_or_union + return self + + @classmethod + def _to_ctypes(cls, value): + return value._blob + + def __repr__(self, c_name=None): + return CTypesData.__repr__(self, c_name or self._get_c_name(' &')) + + +class CTypesBackend(object): + + PRIMITIVE_TYPES = { + 'char': ctypes.c_char, + 'short': ctypes.c_short, + 'int': ctypes.c_int, + 'long': ctypes.c_long, + 'long long': ctypes.c_longlong, + 'signed char': ctypes.c_byte, + 'unsigned char': ctypes.c_ubyte, + 'unsigned short': ctypes.c_ushort, + 'unsigned int': ctypes.c_uint, + 'unsigned long': ctypes.c_ulong, + 'unsigned long long': ctypes.c_ulonglong, + 'float': ctypes.c_float, + 'double': ctypes.c_double, + '_Bool': ctypes.c_bool, + } + + for _name in ['unsigned long long', 'unsigned long', + 'unsigned int', 'unsigned short', 'unsigned char']: + _size = ctypes.sizeof(PRIMITIVE_TYPES[_name]) + PRIMITIVE_TYPES['uint%d_t' % (8*_size)] = PRIMITIVE_TYPES[_name] + if _size == ctypes.sizeof(ctypes.c_void_p): + PRIMITIVE_TYPES['uintptr_t'] = PRIMITIVE_TYPES[_name] + if _size == ctypes.sizeof(ctypes.c_size_t): + PRIMITIVE_TYPES['size_t'] = PRIMITIVE_TYPES[_name] + + for _name in ['long long', 'long', 'int', 'short', 'signed char']: + _size = ctypes.sizeof(PRIMITIVE_TYPES[_name]) + PRIMITIVE_TYPES['int%d_t' % (8*_size)] = PRIMITIVE_TYPES[_name] + if _size == ctypes.sizeof(ctypes.c_void_p): + PRIMITIVE_TYPES['intptr_t'] = PRIMITIVE_TYPES[_name] + PRIMITIVE_TYPES['ptrdiff_t'] = PRIMITIVE_TYPES[_name] + if _size == ctypes.sizeof(ctypes.c_size_t): + PRIMITIVE_TYPES['ssize_t'] = PRIMITIVE_TYPES[_name] + + + def __init__(self): + self.RTLD_LAZY = 0 # not supported anyway by ctypes + self.RTLD_NOW = 0 + self.RTLD_GLOBAL = ctypes.RTLD_GLOBAL + self.RTLD_LOCAL = ctypes.RTLD_LOCAL + + def set_ffi(self, ffi): + self.ffi = ffi + + def _get_types(self): + return CTypesData, CTypesType + + def load_library(self, path, flags=0): + cdll = ctypes.CDLL(path, flags) + return CTypesLibrary(self, cdll) + + def new_void_type(self): + class CTypesVoid(CTypesData): + __slots__ = [] + _reftypename = 'void &' + @staticmethod + def _from_ctypes(novalue): + return None + @staticmethod + def _to_ctypes(novalue): + if novalue is not None: + raise TypeError("None expected, got %s object" % + (type(novalue).__name__,)) + return None + CTypesVoid._fix_class() + return CTypesVoid + + def new_primitive_type(self, name): + if name == 'wchar_t': + raise NotImplementedError(name) + ctype = self.PRIMITIVE_TYPES[name] + if name == 'char': + kind = 'char' + elif name in ('float', 'double'): + kind = 'float' + else: + if name in ('signed char', 'unsigned char'): + kind = 'byte' + elif name == '_Bool': + kind = 'bool' + else: + kind = 'int' + is_signed = (ctype(-1).value == -1) + # + def _cast_source_to_int(source): + if isinstance(source, (int, long, float)): + source = int(source) + elif isinstance(source, CTypesData): + source = source._cast_to_integer() + elif isinstance(source, bytes): + source = ord(source) + elif source is None: + source = 0 + else: + raise TypeError("bad type for cast to %r: %r" % + (CTypesPrimitive, type(source).__name__)) + return source + # + kind1 = kind + class CTypesPrimitive(CTypesGenericPrimitive): + __slots__ = ['_value'] + _ctype = ctype + _reftypename = '%s &' % name + kind = kind1 + + def __init__(self, value): + self._value = value + + @staticmethod + def _create_ctype_obj(init): + if init is None: + return ctype() + return ctype(CTypesPrimitive._to_ctypes(init)) + + if kind == 'int' or kind == 'byte': + @classmethod + def _cast_from(cls, source): + source = _cast_source_to_int(source) + source = ctype(source).value # cast within range + return cls(source) + def __int__(self): + return self._value + + if kind == 'bool': + @classmethod + def _cast_from(cls, source): + if not isinstance(source, (int, long, float)): + source = _cast_source_to_int(source) + return cls(bool(source)) + def __int__(self): + return int(self._value) + + if kind == 'char': + @classmethod + def _cast_from(cls, source): + source = _cast_source_to_int(source) + source = bytechr(source & 0xFF) + return cls(source) + def __int__(self): + return ord(self._value) + + if kind == 'float': + @classmethod + def _cast_from(cls, source): + if isinstance(source, float): + pass + elif isinstance(source, CTypesGenericPrimitive): + if hasattr(source, '__float__'): + source = float(source) + else: + source = int(source) + else: + source = _cast_source_to_int(source) + source = ctype(source).value # fix precision + return cls(source) + def __int__(self): + return int(self._value) + def __float__(self): + return self._value + + _cast_to_integer = __int__ + + if kind == 'int' or kind == 'byte' or kind == 'bool': + @staticmethod + def _to_ctypes(x): + if not isinstance(x, (int, long)): + if isinstance(x, CTypesData): + x = int(x) + else: + raise TypeError("integer expected, got %s" % + type(x).__name__) + if ctype(x).value != x: + if not is_signed and x < 0: + raise OverflowError("%s: negative integer" % name) + else: + raise OverflowError("%s: integer out of bounds" + % name) + return x + + if kind == 'char': + @staticmethod + def _to_ctypes(x): + if isinstance(x, bytes) and len(x) == 1: + return x + if isinstance(x, CTypesPrimitive): # > + return x._value + raise TypeError("character expected, got %s" % + type(x).__name__) + def __nonzero__(self): + return ord(self._value) != 0 + else: + def __nonzero__(self): + return self._value != 0 + __bool__ = __nonzero__ + + if kind == 'float': + @staticmethod + def _to_ctypes(x): + if not isinstance(x, (int, long, float, CTypesData)): + raise TypeError("float expected, got %s" % + type(x).__name__) + return ctype(x).value + + @staticmethod + def _from_ctypes(value): + return getattr(value, 'value', value) + + @staticmethod + def _initialize(blob, init): + blob.value = CTypesPrimitive._to_ctypes(init) + + if kind == 'char': + def _to_string(self, maxlen): + return self._value + if kind == 'byte': + def _to_string(self, maxlen): + return chr(self._value & 0xff) + # + CTypesPrimitive._fix_class() + return CTypesPrimitive + + def new_pointer_type(self, BItem): + getbtype = self.ffi._get_cached_btype + if BItem is getbtype(model.PrimitiveType('char')): + kind = 'charp' + elif BItem in (getbtype(model.PrimitiveType('signed char')), + getbtype(model.PrimitiveType('unsigned char'))): + kind = 'bytep' + elif BItem is getbtype(model.void_type): + kind = 'voidp' + else: + kind = 'generic' + # + class CTypesPtr(CTypesGenericPtr): + __slots__ = ['_own'] + if kind == 'charp': + __slots__ += ['__as_strbuf'] + _BItem = BItem + if hasattr(BItem, '_ctype'): + _ctype = ctypes.POINTER(BItem._ctype) + _bitem_size = ctypes.sizeof(BItem._ctype) + else: + _ctype = ctypes.c_void_p + if issubclass(BItem, CTypesGenericArray): + _reftypename = BItem._get_c_name('(* &)') + else: + _reftypename = BItem._get_c_name(' * &') + + def __init__(self, init): + ctypeobj = BItem._create_ctype_obj(init) + if kind == 'charp': + self.__as_strbuf = ctypes.create_string_buffer( + ctypeobj.value + b'\x00') + self._as_ctype_ptr = ctypes.cast( + self.__as_strbuf, self._ctype) + else: + self._as_ctype_ptr = ctypes.pointer(ctypeobj) + self._address = ctypes.cast(self._as_ctype_ptr, + ctypes.c_void_p).value + self._own = True + + def __add__(self, other): + if isinstance(other, (int, long)): + return self._new_pointer_at(self._address + + other * self._bitem_size) + else: + return NotImplemented + + def __sub__(self, other): + if isinstance(other, (int, long)): + return self._new_pointer_at(self._address - + other * self._bitem_size) + elif type(self) is type(other): + return (self._address - other._address) // self._bitem_size + else: + return NotImplemented + + def __getitem__(self, index): + if getattr(self, '_own', False) and index != 0: + raise IndexError + return BItem._from_ctypes(self._as_ctype_ptr[index]) + + def __setitem__(self, index, value): + self._as_ctype_ptr[index] = BItem._to_ctypes(value) + + if kind == 'charp' or kind == 'voidp': + @classmethod + def _arg_to_ctypes(cls, *value): + if value and isinstance(value[0], bytes): + return ctypes.c_char_p(value[0]) + else: + return super(CTypesPtr, cls)._arg_to_ctypes(*value) + + if kind == 'charp' or kind == 'bytep': + def _to_string(self, maxlen): + if maxlen < 0: + maxlen = sys.maxsize + p = ctypes.cast(self._as_ctype_ptr, + ctypes.POINTER(ctypes.c_char)) + n = 0 + while n < maxlen and p[n] != b'\x00': + n += 1 + return b''.join([p[i] for i in range(n)]) + + def _get_own_repr(self): + if getattr(self, '_own', False): + return 'owning %d bytes' % ( + ctypes.sizeof(self._as_ctype_ptr.contents),) + return super(CTypesPtr, self)._get_own_repr() + # + if (BItem is self.ffi._get_cached_btype(model.void_type) or + BItem is self.ffi._get_cached_btype(model.PrimitiveType('char'))): + CTypesPtr._automatic_casts = True + # + CTypesPtr._fix_class() + return CTypesPtr + + def new_array_type(self, CTypesPtr, length): + if length is None: + brackets = ' &[]' + else: + brackets = ' &[%d]' % length + BItem = CTypesPtr._BItem + getbtype = self.ffi._get_cached_btype + if BItem is getbtype(model.PrimitiveType('char')): + kind = 'char' + elif BItem in (getbtype(model.PrimitiveType('signed char')), + getbtype(model.PrimitiveType('unsigned char'))): + kind = 'byte' + else: + kind = 'generic' + # + class CTypesArray(CTypesGenericArray): + __slots__ = ['_blob', '_own'] + if length is not None: + _ctype = BItem._ctype * length + else: + __slots__.append('_ctype') + _reftypename = BItem._get_c_name(brackets) + _declared_length = length + _CTPtr = CTypesPtr + + def __init__(self, init): + if length is None: + if isinstance(init, (int, long)): + len1 = init + init = None + elif kind == 'char' and isinstance(init, bytes): + len1 = len(init) + 1 # extra null + else: + init = tuple(init) + len1 = len(init) + self._ctype = BItem._ctype * len1 + self._blob = self._ctype() + self._own = True + if init is not None: + self._initialize(self._blob, init) + + @staticmethod + def _initialize(blob, init): + if isinstance(init, bytes): + init = [init[i:i+1] for i in range(len(init))] + else: + if isinstance(init, CTypesGenericArray): + if (len(init) != len(blob) or + not isinstance(init, CTypesArray)): + raise TypeError("length/type mismatch: %s" % (init,)) + init = tuple(init) + if len(init) > len(blob): + raise IndexError("too many initializers") + addr = ctypes.cast(blob, ctypes.c_void_p).value + PTR = ctypes.POINTER(BItem._ctype) + itemsize = ctypes.sizeof(BItem._ctype) + for i, value in enumerate(init): + p = ctypes.cast(addr + i * itemsize, PTR) + BItem._initialize(p.contents, value) + + def __len__(self): + return len(self._blob) + + def __getitem__(self, index): + if not (0 <= index < len(self._blob)): + raise IndexError + return BItem._from_ctypes(self._blob[index]) + + def __setitem__(self, index, value): + if not (0 <= index < len(self._blob)): + raise IndexError + self._blob[index] = BItem._to_ctypes(value) + + if kind == 'char' or kind == 'byte': + def _to_string(self, maxlen): + if maxlen < 0: + maxlen = len(self._blob) + p = ctypes.cast(self._blob, + ctypes.POINTER(ctypes.c_char)) + n = 0 + while n < maxlen and p[n] != b'\x00': + n += 1 + return b''.join([p[i] for i in range(n)]) + + def _get_own_repr(self): + if getattr(self, '_own', False): + return 'owning %d bytes' % (ctypes.sizeof(self._blob),) + return super(CTypesArray, self)._get_own_repr() + + def _convert_to_address(self, BClass): + if BClass in (CTypesPtr, None) or BClass._automatic_casts: + return ctypes.addressof(self._blob) + else: + return CTypesData._convert_to_address(self, BClass) + + @staticmethod + def _from_ctypes(ctypes_array): + self = CTypesArray.__new__(CTypesArray) + self._blob = ctypes_array + return self + + @staticmethod + def _arg_to_ctypes(value): + return CTypesPtr._arg_to_ctypes(value) + + def __add__(self, other): + if isinstance(other, (int, long)): + return CTypesPtr._new_pointer_at( + ctypes.addressof(self._blob) + + other * ctypes.sizeof(BItem._ctype)) + else: + return NotImplemented + + @classmethod + def _cast_from(cls, source): + raise NotImplementedError("casting to %r" % ( + cls._get_c_name(),)) + # + CTypesArray._fix_class() + return CTypesArray + + def _new_struct_or_union(self, kind, name, base_ctypes_class): + # + class struct_or_union(base_ctypes_class): + pass + struct_or_union.__name__ = '%s_%s' % (kind, name) + kind1 = kind + # + class CTypesStructOrUnion(CTypesBaseStructOrUnion): + __slots__ = ['_blob'] + _ctype = struct_or_union + _reftypename = '%s &' % (name,) + _kind = kind = kind1 + # + CTypesStructOrUnion._fix_class() + return CTypesStructOrUnion + + def new_struct_type(self, name): + return self._new_struct_or_union('struct', name, ctypes.Structure) + + def new_union_type(self, name): + return self._new_struct_or_union('union', name, ctypes.Union) + + def complete_struct_or_union(self, CTypesStructOrUnion, fields, tp, + totalsize=-1, totalalignment=-1, sflags=0, + pack=0): + if totalsize >= 0 or totalalignment >= 0: + raise NotImplementedError("the ctypes backend of CFFI does not support " + "structures completed by verify(); please " + "compile and install the _cffi_backend module.") + struct_or_union = CTypesStructOrUnion._ctype + fnames = [fname for (fname, BField, bitsize) in fields] + btypes = [BField for (fname, BField, bitsize) in fields] + bitfields = [bitsize for (fname, BField, bitsize) in fields] + # + bfield_types = {} + cfields = [] + for (fname, BField, bitsize) in fields: + if bitsize < 0: + cfields.append((fname, BField._ctype)) + bfield_types[fname] = BField + else: + cfields.append((fname, BField._ctype, bitsize)) + bfield_types[fname] = Ellipsis + if sflags & 8: + struct_or_union._pack_ = 1 + elif pack: + struct_or_union._pack_ = pack + struct_or_union._fields_ = cfields + CTypesStructOrUnion._bfield_types = bfield_types + # + @staticmethod + def _create_ctype_obj(init): + result = struct_or_union() + if init is not None: + initialize(result, init) + return result + CTypesStructOrUnion._create_ctype_obj = _create_ctype_obj + # + def initialize(blob, init): + if is_union: + if len(init) > 1: + raise ValueError("union initializer: %d items given, but " + "only one supported (use a dict if needed)" + % (len(init),)) + if not isinstance(init, dict): + if isinstance(init, (bytes, unicode)): + raise TypeError("union initializer: got a str") + init = tuple(init) + if len(init) > len(fnames): + raise ValueError("too many values for %s initializer" % + CTypesStructOrUnion._get_c_name()) + init = dict(zip(fnames, init)) + addr = ctypes.addressof(blob) + for fname, value in init.items(): + BField, bitsize = name2fieldtype[fname] + assert bitsize < 0, \ + "not implemented: initializer with bit fields" + offset = CTypesStructOrUnion._offsetof(fname) + PTR = ctypes.POINTER(BField._ctype) + p = ctypes.cast(addr + offset, PTR) + BField._initialize(p.contents, value) + is_union = CTypesStructOrUnion._kind == 'union' + name2fieldtype = dict(zip(fnames, zip(btypes, bitfields))) + # + for fname, BField, bitsize in fields: + if fname == '': + raise NotImplementedError("nested anonymous structs/unions") + if hasattr(CTypesStructOrUnion, fname): + raise ValueError("the field name %r conflicts in " + "the ctypes backend" % fname) + if bitsize < 0: + def getter(self, fname=fname, BField=BField, + offset=CTypesStructOrUnion._offsetof(fname), + PTR=ctypes.POINTER(BField._ctype)): + addr = ctypes.addressof(self._blob) + p = ctypes.cast(addr + offset, PTR) + return BField._from_ctypes(p.contents) + def setter(self, value, fname=fname, BField=BField): + setattr(self._blob, fname, BField._to_ctypes(value)) + # + if issubclass(BField, CTypesGenericArray): + setter = None + if BField._declared_length == 0: + def getter(self, fname=fname, BFieldPtr=BField._CTPtr, + offset=CTypesStructOrUnion._offsetof(fname), + PTR=ctypes.POINTER(BField._ctype)): + addr = ctypes.addressof(self._blob) + p = ctypes.cast(addr + offset, PTR) + return BFieldPtr._from_ctypes(p) + # + else: + def getter(self, fname=fname, BField=BField): + return BField._from_ctypes(getattr(self._blob, fname)) + def setter(self, value, fname=fname, BField=BField): + # xxx obscure workaround + value = BField._to_ctypes(value) + oldvalue = getattr(self._blob, fname) + setattr(self._blob, fname, value) + if value != getattr(self._blob, fname): + setattr(self._blob, fname, oldvalue) + raise OverflowError("value too large for bitfield") + setattr(CTypesStructOrUnion, fname, property(getter, setter)) + # + CTypesPtr = self.ffi._get_cached_btype(model.PointerType(tp)) + for fname in fnames: + if hasattr(CTypesPtr, fname): + raise ValueError("the field name %r conflicts in " + "the ctypes backend" % fname) + def getter(self, fname=fname): + return getattr(self[0], fname) + def setter(self, value, fname=fname): + setattr(self[0], fname, value) + setattr(CTypesPtr, fname, property(getter, setter)) + + def new_function_type(self, BArgs, BResult, has_varargs): + nameargs = [BArg._get_c_name() for BArg in BArgs] + if has_varargs: + nameargs.append('...') + nameargs = ', '.join(nameargs) + # + class CTypesFunctionPtr(CTypesGenericPtr): + __slots__ = ['_own_callback', '_name'] + _ctype = ctypes.CFUNCTYPE(getattr(BResult, '_ctype', None), + *[BArg._ctype for BArg in BArgs], + use_errno=True) + _reftypename = BResult._get_c_name('(* &)(%s)' % (nameargs,)) + + def __init__(self, init, error=None): + # create a callback to the Python callable init() + import traceback + assert not has_varargs, "varargs not supported for callbacks" + if getattr(BResult, '_ctype', None) is not None: + error = BResult._from_ctypes( + BResult._create_ctype_obj(error)) + else: + error = None + def callback(*args): + args2 = [] + for arg, BArg in zip(args, BArgs): + args2.append(BArg._from_ctypes(arg)) + try: + res2 = init(*args2) + res2 = BResult._to_ctypes(res2) + except: + traceback.print_exc() + res2 = error + if issubclass(BResult, CTypesGenericPtr): + if res2: + res2 = ctypes.cast(res2, ctypes.c_void_p).value + # .value: http://bugs.python.org/issue1574593 + else: + res2 = None + #print repr(res2) + return res2 + if issubclass(BResult, CTypesGenericPtr): + # The only pointers callbacks can return are void*s: + # http://bugs.python.org/issue5710 + callback_ctype = ctypes.CFUNCTYPE( + ctypes.c_void_p, + *[BArg._ctype for BArg in BArgs], + use_errno=True) + else: + callback_ctype = CTypesFunctionPtr._ctype + self._as_ctype_ptr = callback_ctype(callback) + self._address = ctypes.cast(self._as_ctype_ptr, + ctypes.c_void_p).value + self._own_callback = init + + @staticmethod + def _initialize(ctypes_ptr, value): + if value: + raise NotImplementedError("ctypes backend: not supported: " + "initializers for function pointers") + + def __repr__(self): + c_name = getattr(self, '_name', None) + if c_name: + i = self._reftypename.index('(* &)') + if self._reftypename[i-1] not in ' )*': + c_name = ' ' + c_name + c_name = self._reftypename.replace('(* &)', c_name) + return CTypesData.__repr__(self, c_name) + + def _get_own_repr(self): + if getattr(self, '_own_callback', None) is not None: + return 'calling %r' % (self._own_callback,) + return super(CTypesFunctionPtr, self)._get_own_repr() + + def __call__(self, *args): + if has_varargs: + assert len(args) >= len(BArgs) + extraargs = args[len(BArgs):] + args = args[:len(BArgs)] + else: + assert len(args) == len(BArgs) + ctypes_args = [] + for arg, BArg in zip(args, BArgs): + ctypes_args.append(BArg._arg_to_ctypes(arg)) + if has_varargs: + for i, arg in enumerate(extraargs): + if arg is None: + ctypes_args.append(ctypes.c_void_p(0)) # NULL + continue + if not isinstance(arg, CTypesData): + raise TypeError( + "argument %d passed in the variadic part " + "needs to be a cdata object (got %s)" % + (1 + len(BArgs) + i, type(arg).__name__)) + ctypes_args.append(arg._arg_to_ctypes(arg)) + result = self._as_ctype_ptr(*ctypes_args) + return BResult._from_ctypes(result) + # + CTypesFunctionPtr._fix_class() + return CTypesFunctionPtr + + def new_enum_type(self, name, enumerators, enumvalues, CTypesInt): + assert isinstance(name, str) + reverse_mapping = dict(zip(reversed(enumvalues), + reversed(enumerators))) + # + class CTypesEnum(CTypesInt): + __slots__ = [] + _reftypename = '%s &' % name + + def _get_own_repr(self): + value = self._value + try: + return '%d: %s' % (value, reverse_mapping[value]) + except KeyError: + return str(value) + + def _to_string(self, maxlen): + value = self._value + try: + return reverse_mapping[value] + except KeyError: + return str(value) + # + CTypesEnum._fix_class() + return CTypesEnum + + def get_errno(self): + return ctypes.get_errno() + + def set_errno(self, value): + ctypes.set_errno(value) + + def string(self, b, maxlen=-1): + return b._to_string(maxlen) + + def buffer(self, bptr, size=-1): + raise NotImplementedError("buffer() with ctypes backend") + + def sizeof(self, cdata_or_BType): + if isinstance(cdata_or_BType, CTypesData): + return cdata_or_BType._get_size_of_instance() + else: + assert issubclass(cdata_or_BType, CTypesData) + return cdata_or_BType._get_size() + + def alignof(self, BType): + assert issubclass(BType, CTypesData) + return BType._alignment() + + def newp(self, BType, source): + if not issubclass(BType, CTypesData): + raise TypeError + return BType._newp(source) + + def cast(self, BType, source): + return BType._cast_from(source) + + def callback(self, BType, source, error, onerror): + assert onerror is None # XXX not implemented + return BType(source, error) + + _weakref_cache_ref = None + + def gcp(self, cdata, destructor, size=0): + if self._weakref_cache_ref is None: + import weakref + class MyRef(weakref.ref): + def __eq__(self, other): + myref = self() + return self is other or ( + myref is not None and myref is other()) + def __ne__(self, other): + return not (self == other) + def __hash__(self): + try: + return self._hash + except AttributeError: + self._hash = hash(self()) + return self._hash + self._weakref_cache_ref = {}, MyRef + weak_cache, MyRef = self._weakref_cache_ref + + if destructor is None: + try: + del weak_cache[MyRef(cdata)] + except KeyError: + raise TypeError("Can remove destructor only on a object " + "previously returned by ffi.gc()") + return None + + def remove(k): + cdata, destructor = weak_cache.pop(k, (None, None)) + if destructor is not None: + destructor(cdata) + + new_cdata = self.cast(self.typeof(cdata), cdata) + assert new_cdata is not cdata + weak_cache[MyRef(new_cdata, remove)] = (cdata, destructor) + return new_cdata + + typeof = type + + def getcname(self, BType, replace_with): + return BType._get_c_name(replace_with) + + def typeoffsetof(self, BType, fieldname, num=0): + if isinstance(fieldname, str): + if num == 0 and issubclass(BType, CTypesGenericPtr): + BType = BType._BItem + if not issubclass(BType, CTypesBaseStructOrUnion): + raise TypeError("expected a struct or union ctype") + BField = BType._bfield_types[fieldname] + if BField is Ellipsis: + raise TypeError("not supported for bitfields") + return (BField, BType._offsetof(fieldname)) + elif isinstance(fieldname, (int, long)): + if issubclass(BType, CTypesGenericArray): + BType = BType._CTPtr + if not issubclass(BType, CTypesGenericPtr): + raise TypeError("expected an array or ptr ctype") + BItem = BType._BItem + offset = BItem._get_size() * fieldname + if offset > sys.maxsize: + raise OverflowError + return (BItem, offset) + else: + raise TypeError(type(fieldname)) + + def rawaddressof(self, BTypePtr, cdata, offset=None): + if isinstance(cdata, CTypesBaseStructOrUnion): + ptr = ctypes.pointer(type(cdata)._to_ctypes(cdata)) + elif isinstance(cdata, CTypesGenericPtr): + if offset is None or not issubclass(type(cdata)._BItem, + CTypesBaseStructOrUnion): + raise TypeError("unexpected cdata type") + ptr = type(cdata)._to_ctypes(cdata) + elif isinstance(cdata, CTypesGenericArray): + ptr = type(cdata)._to_ctypes(cdata) + else: + raise TypeError("expected a ") + if offset: + ptr = ctypes.cast( + ctypes.c_void_p( + ctypes.cast(ptr, ctypes.c_void_p).value + offset), + type(ptr)) + return BTypePtr._from_ctypes(ptr) + + +class CTypesLibrary(object): + + def __init__(self, backend, cdll): + self.backend = backend + self.cdll = cdll + + def load_function(self, BType, name): + c_func = getattr(self.cdll, name) + funcobj = BType._from_ctypes(c_func) + funcobj._name = name + return funcobj + + def read_variable(self, BType, name): + try: + ctypes_obj = BType._ctype.in_dll(self.cdll, name) + except AttributeError as e: + raise NotImplementedError(e) + return BType._from_ctypes(ctypes_obj) + + def write_variable(self, BType, name, value): + new_ctypes_obj = BType._to_ctypes(value) + ctypes_obj = BType._ctype.in_dll(self.cdll, name) + ctypes.memmove(ctypes.addressof(ctypes_obj), + ctypes.addressof(new_ctypes_obj), + ctypes.sizeof(BType._ctype)) diff --git a/intelliaide/vendor/cffi/cffi_opcode.py b/intelliaide/vendor/cffi/cffi_opcode.py new file mode 100644 index 0000000..6421df6 --- /dev/null +++ b/intelliaide/vendor/cffi/cffi_opcode.py @@ -0,0 +1,187 @@ +from .error import VerificationError + +class CffiOp(object): + def __init__(self, op, arg): + self.op = op + self.arg = arg + + def as_c_expr(self): + if self.op is None: + assert isinstance(self.arg, str) + return '(_cffi_opcode_t)(%s)' % (self.arg,) + classname = CLASS_NAME[self.op] + return '_CFFI_OP(_CFFI_OP_%s, %s)' % (classname, self.arg) + + def as_python_bytes(self): + if self.op is None and self.arg.isdigit(): + value = int(self.arg) # non-negative: '-' not in self.arg + if value >= 2**31: + raise OverflowError("cannot emit %r: limited to 2**31-1" + % (self.arg,)) + return format_four_bytes(value) + if isinstance(self.arg, str): + raise VerificationError("cannot emit to Python: %r" % (self.arg,)) + return format_four_bytes((self.arg << 8) | self.op) + + def __str__(self): + classname = CLASS_NAME.get(self.op, self.op) + return '(%s %s)' % (classname, self.arg) + +def format_four_bytes(num): + return '\\x%02X\\x%02X\\x%02X\\x%02X' % ( + (num >> 24) & 0xFF, + (num >> 16) & 0xFF, + (num >> 8) & 0xFF, + (num ) & 0xFF) + +OP_PRIMITIVE = 1 +OP_POINTER = 3 +OP_ARRAY = 5 +OP_OPEN_ARRAY = 7 +OP_STRUCT_UNION = 9 +OP_ENUM = 11 +OP_FUNCTION = 13 +OP_FUNCTION_END = 15 +OP_NOOP = 17 +OP_BITFIELD = 19 +OP_TYPENAME = 21 +OP_CPYTHON_BLTN_V = 23 # varargs +OP_CPYTHON_BLTN_N = 25 # noargs +OP_CPYTHON_BLTN_O = 27 # O (i.e. a single arg) +OP_CONSTANT = 29 +OP_CONSTANT_INT = 31 +OP_GLOBAL_VAR = 33 +OP_DLOPEN_FUNC = 35 +OP_DLOPEN_CONST = 37 +OP_GLOBAL_VAR_F = 39 +OP_EXTERN_PYTHON = 41 + +PRIM_VOID = 0 +PRIM_BOOL = 1 +PRIM_CHAR = 2 +PRIM_SCHAR = 3 +PRIM_UCHAR = 4 +PRIM_SHORT = 5 +PRIM_USHORT = 6 +PRIM_INT = 7 +PRIM_UINT = 8 +PRIM_LONG = 9 +PRIM_ULONG = 10 +PRIM_LONGLONG = 11 +PRIM_ULONGLONG = 12 +PRIM_FLOAT = 13 +PRIM_DOUBLE = 14 +PRIM_LONGDOUBLE = 15 + +PRIM_WCHAR = 16 +PRIM_INT8 = 17 +PRIM_UINT8 = 18 +PRIM_INT16 = 19 +PRIM_UINT16 = 20 +PRIM_INT32 = 21 +PRIM_UINT32 = 22 +PRIM_INT64 = 23 +PRIM_UINT64 = 24 +PRIM_INTPTR = 25 +PRIM_UINTPTR = 26 +PRIM_PTRDIFF = 27 +PRIM_SIZE = 28 +PRIM_SSIZE = 29 +PRIM_INT_LEAST8 = 30 +PRIM_UINT_LEAST8 = 31 +PRIM_INT_LEAST16 = 32 +PRIM_UINT_LEAST16 = 33 +PRIM_INT_LEAST32 = 34 +PRIM_UINT_LEAST32 = 35 +PRIM_INT_LEAST64 = 36 +PRIM_UINT_LEAST64 = 37 +PRIM_INT_FAST8 = 38 +PRIM_UINT_FAST8 = 39 +PRIM_INT_FAST16 = 40 +PRIM_UINT_FAST16 = 41 +PRIM_INT_FAST32 = 42 +PRIM_UINT_FAST32 = 43 +PRIM_INT_FAST64 = 44 +PRIM_UINT_FAST64 = 45 +PRIM_INTMAX = 46 +PRIM_UINTMAX = 47 +PRIM_FLOATCOMPLEX = 48 +PRIM_DOUBLECOMPLEX = 49 +PRIM_CHAR16 = 50 +PRIM_CHAR32 = 51 + +_NUM_PRIM = 52 +_UNKNOWN_PRIM = -1 +_UNKNOWN_FLOAT_PRIM = -2 +_UNKNOWN_LONG_DOUBLE = -3 + +_IO_FILE_STRUCT = -1 + +PRIMITIVE_TO_INDEX = { + 'char': PRIM_CHAR, + 'short': PRIM_SHORT, + 'int': PRIM_INT, + 'long': PRIM_LONG, + 'long long': PRIM_LONGLONG, + 'signed char': PRIM_SCHAR, + 'unsigned char': PRIM_UCHAR, + 'unsigned short': PRIM_USHORT, + 'unsigned int': PRIM_UINT, + 'unsigned long': PRIM_ULONG, + 'unsigned long long': PRIM_ULONGLONG, + 'float': PRIM_FLOAT, + 'double': PRIM_DOUBLE, + 'long double': PRIM_LONGDOUBLE, + '_cffi_float_complex_t': PRIM_FLOATCOMPLEX, + '_cffi_double_complex_t': PRIM_DOUBLECOMPLEX, + '_Bool': PRIM_BOOL, + 'wchar_t': PRIM_WCHAR, + 'char16_t': PRIM_CHAR16, + 'char32_t': PRIM_CHAR32, + 'int8_t': PRIM_INT8, + 'uint8_t': PRIM_UINT8, + 'int16_t': PRIM_INT16, + 'uint16_t': PRIM_UINT16, + 'int32_t': PRIM_INT32, + 'uint32_t': PRIM_UINT32, + 'int64_t': PRIM_INT64, + 'uint64_t': PRIM_UINT64, + 'intptr_t': PRIM_INTPTR, + 'uintptr_t': PRIM_UINTPTR, + 'ptrdiff_t': PRIM_PTRDIFF, + 'size_t': PRIM_SIZE, + 'ssize_t': PRIM_SSIZE, + 'int_least8_t': PRIM_INT_LEAST8, + 'uint_least8_t': PRIM_UINT_LEAST8, + 'int_least16_t': PRIM_INT_LEAST16, + 'uint_least16_t': PRIM_UINT_LEAST16, + 'int_least32_t': PRIM_INT_LEAST32, + 'uint_least32_t': PRIM_UINT_LEAST32, + 'int_least64_t': PRIM_INT_LEAST64, + 'uint_least64_t': PRIM_UINT_LEAST64, + 'int_fast8_t': PRIM_INT_FAST8, + 'uint_fast8_t': PRIM_UINT_FAST8, + 'int_fast16_t': PRIM_INT_FAST16, + 'uint_fast16_t': PRIM_UINT_FAST16, + 'int_fast32_t': PRIM_INT_FAST32, + 'uint_fast32_t': PRIM_UINT_FAST32, + 'int_fast64_t': PRIM_INT_FAST64, + 'uint_fast64_t': PRIM_UINT_FAST64, + 'intmax_t': PRIM_INTMAX, + 'uintmax_t': PRIM_UINTMAX, + } + +F_UNION = 0x01 +F_CHECK_FIELDS = 0x02 +F_PACKED = 0x04 +F_EXTERNAL = 0x08 +F_OPAQUE = 0x10 + +G_FLAGS = dict([('_CFFI_' + _key, globals()[_key]) + for _key in ['F_UNION', 'F_CHECK_FIELDS', 'F_PACKED', + 'F_EXTERNAL', 'F_OPAQUE']]) + +CLASS_NAME = {} +for _name, _value in list(globals().items()): + if _name.startswith('OP_') and isinstance(_value, int): + CLASS_NAME[_value] = _name[3:] diff --git a/intelliaide/vendor/cffi/commontypes.py b/intelliaide/vendor/cffi/commontypes.py new file mode 100644 index 0000000..d4dae35 --- /dev/null +++ b/intelliaide/vendor/cffi/commontypes.py @@ -0,0 +1,82 @@ +import sys +from . import model +from .error import FFIError + + +COMMON_TYPES = {} + +try: + # fetch "bool" and all simple Windows types + from _cffi_backend import _get_common_types + _get_common_types(COMMON_TYPES) +except ImportError: + pass + +COMMON_TYPES['FILE'] = model.unknown_type('FILE', '_IO_FILE') +COMMON_TYPES['bool'] = '_Bool' # in case we got ImportError above +COMMON_TYPES['float _Complex'] = '_cffi_float_complex_t' +COMMON_TYPES['double _Complex'] = '_cffi_double_complex_t' + +for _type in model.PrimitiveType.ALL_PRIMITIVE_TYPES: + if _type.endswith('_t'): + COMMON_TYPES[_type] = _type +del _type + +_CACHE = {} + +def resolve_common_type(parser, commontype): + try: + return _CACHE[commontype] + except KeyError: + cdecl = COMMON_TYPES.get(commontype, commontype) + if not isinstance(cdecl, str): + result, quals = cdecl, 0 # cdecl is already a BaseType + elif cdecl in model.PrimitiveType.ALL_PRIMITIVE_TYPES: + result, quals = model.PrimitiveType(cdecl), 0 + elif cdecl == 'set-unicode-needed': + raise FFIError("The Windows type %r is only available after " + "you call ffi.set_unicode()" % (commontype,)) + else: + if commontype == cdecl: + raise FFIError( + "Unsupported type: %r. Please look at " + "http://cffi.readthedocs.io/en/latest/cdef.html#ffi-cdef-limitations " + "and file an issue if you think this type should really " + "be supported." % (commontype,)) + result, quals = parser.parse_type_and_quals(cdecl) # recursive + + assert isinstance(result, model.BaseTypeByIdentity) + _CACHE[commontype] = result, quals + return result, quals + + +# ____________________________________________________________ +# extra types for Windows (most of them are in commontypes.c) + + +def win_common_types(): + return { + "UNICODE_STRING": model.StructType( + "_UNICODE_STRING", + ["Length", + "MaximumLength", + "Buffer"], + [model.PrimitiveType("unsigned short"), + model.PrimitiveType("unsigned short"), + model.PointerType(model.PrimitiveType("wchar_t"))], + [-1, -1, -1]), + "PUNICODE_STRING": "UNICODE_STRING *", + "PCUNICODE_STRING": "const UNICODE_STRING *", + + "TBYTE": "set-unicode-needed", + "TCHAR": "set-unicode-needed", + "LPCTSTR": "set-unicode-needed", + "PCTSTR": "set-unicode-needed", + "LPTSTR": "set-unicode-needed", + "PTSTR": "set-unicode-needed", + "PTBYTE": "set-unicode-needed", + "PTCHAR": "set-unicode-needed", + } + +if sys.platform == 'win32': + COMMON_TYPES.update(win_common_types()) diff --git a/intelliaide/vendor/cffi/cparser.py b/intelliaide/vendor/cffi/cparser.py new file mode 100644 index 0000000..dd590d8 --- /dev/null +++ b/intelliaide/vendor/cffi/cparser.py @@ -0,0 +1,1015 @@ +from . import model +from .commontypes import COMMON_TYPES, resolve_common_type +from .error import FFIError, CDefError +try: + from . import _pycparser as pycparser +except ImportError: + import pycparser +import weakref, re, sys + +try: + if sys.version_info < (3,): + import thread as _thread + else: + import _thread + lock = _thread.allocate_lock() +except ImportError: + lock = None + +def _workaround_for_static_import_finders(): + # Issue #392: packaging tools like cx_Freeze can not find these + # because pycparser uses exec dynamic import. This is an obscure + # workaround. This function is never called. + import pycparser.yacctab + import pycparser.lextab + +CDEF_SOURCE_STRING = "" +_r_comment = re.compile(r"/\*.*?\*/|//([^\n\\]|\\.)*?$", + re.DOTALL | re.MULTILINE) +_r_define = re.compile(r"^\s*#\s*define\s+([A-Za-z_][A-Za-z_0-9]*)" + r"\b((?:[^\n\\]|\\.)*?)$", + re.DOTALL | re.MULTILINE) +_r_line_directive = re.compile(r"^[ \t]*#[ \t]*(?:line|\d+)\b.*$", re.MULTILINE) +_r_partial_enum = re.compile(r"=\s*\.\.\.\s*[,}]|\.\.\.\s*\}") +_r_enum_dotdotdot = re.compile(r"__dotdotdot\d+__$") +_r_partial_array = re.compile(r"\[\s*\.\.\.\s*\]") +_r_words = re.compile(r"\w+|\S") +_parser_cache = None +_r_int_literal = re.compile(r"-?0?x?[0-9a-f]+[lu]*$", re.IGNORECASE) +_r_stdcall1 = re.compile(r"\b(__stdcall|WINAPI)\b") +_r_stdcall2 = re.compile(r"[(]\s*(__stdcall|WINAPI)\b") +_r_cdecl = re.compile(r"\b__cdecl\b") +_r_extern_python = re.compile(r'\bextern\s*"' + r'(Python|Python\s*\+\s*C|C\s*\+\s*Python)"\s*.') +_r_star_const_space = re.compile( # matches "* const " + r"[*]\s*((const|volatile|restrict)\b\s*)+") +_r_int_dotdotdot = re.compile(r"(\b(int|long|short|signed|unsigned|char)\s*)+" + r"\.\.\.") +_r_float_dotdotdot = re.compile(r"\b(double|float)\s*\.\.\.") + +def _get_parser(): + global _parser_cache + if _parser_cache is None: + _parser_cache = pycparser.CParser() + return _parser_cache + +def _workaround_for_old_pycparser(csource): + # Workaround for a pycparser issue (fixed between pycparser 2.10 and + # 2.14): "char*const***" gives us a wrong syntax tree, the same as + # for "char***(*const)". This means we can't tell the difference + # afterwards. But "char(*const(***))" gives us the right syntax + # tree. The issue only occurs if there are several stars in + # sequence with no parenthesis in between, just possibly qualifiers. + # Attempt to fix it by adding some parentheses in the source: each + # time we see "* const" or "* const *", we add an opening + # parenthesis before each star---the hard part is figuring out where + # to close them. + parts = [] + while True: + match = _r_star_const_space.search(csource) + if not match: + break + #print repr(''.join(parts)+csource), '=>', + parts.append(csource[:match.start()]) + parts.append('('); closing = ')' + parts.append(match.group()) # e.g. "* const " + endpos = match.end() + if csource.startswith('*', endpos): + parts.append('('); closing += ')' + level = 0 + i = endpos + while i < len(csource): + c = csource[i] + if c == '(': + level += 1 + elif c == ')': + if level == 0: + break + level -= 1 + elif c in ',;=': + if level == 0: + break + i += 1 + csource = csource[endpos:i] + closing + csource[i:] + #print repr(''.join(parts)+csource) + parts.append(csource) + return ''.join(parts) + +def _preprocess_extern_python(csource): + # input: `extern "Python" int foo(int);` or + # `extern "Python" { int foo(int); }` + # output: + # void __cffi_extern_python_start; + # int foo(int); + # void __cffi_extern_python_stop; + # + # input: `extern "Python+C" int foo(int);` + # output: + # void __cffi_extern_python_plus_c_start; + # int foo(int); + # void __cffi_extern_python_stop; + parts = [] + while True: + match = _r_extern_python.search(csource) + if not match: + break + endpos = match.end() - 1 + #print + #print ''.join(parts)+csource + #print '=>' + parts.append(csource[:match.start()]) + if 'C' in match.group(1): + parts.append('void __cffi_extern_python_plus_c_start; ') + else: + parts.append('void __cffi_extern_python_start; ') + if csource[endpos] == '{': + # grouping variant + closing = csource.find('}', endpos) + if closing < 0: + raise CDefError("'extern \"Python\" {': no '}' found") + if csource.find('{', endpos + 1, closing) >= 0: + raise NotImplementedError("cannot use { } inside a block " + "'extern \"Python\" { ... }'") + parts.append(csource[endpos+1:closing]) + csource = csource[closing+1:] + else: + # non-grouping variant + semicolon = csource.find(';', endpos) + if semicolon < 0: + raise CDefError("'extern \"Python\": no ';' found") + parts.append(csource[endpos:semicolon+1]) + csource = csource[semicolon+1:] + parts.append(' void __cffi_extern_python_stop;') + #print ''.join(parts)+csource + #print + parts.append(csource) + return ''.join(parts) + +def _warn_for_string_literal(csource): + if '"' not in csource: + return + for line in csource.splitlines(): + if '"' in line and not line.lstrip().startswith('#'): + import warnings + warnings.warn("String literal found in cdef() or type source. " + "String literals are ignored here, but you should " + "remove them anyway because some character sequences " + "confuse pre-parsing.") + break + +def _warn_for_non_extern_non_static_global_variable(decl): + if not decl.storage: + import warnings + warnings.warn("Global variable '%s' in cdef(): for consistency " + "with C it should have a storage class specifier " + "(usually 'extern')" % (decl.name,)) + +def _remove_line_directives(csource): + # _r_line_directive matches whole lines, without the final \n, if they + # start with '#line' with some spacing allowed, or '#NUMBER'. This + # function stores them away and replaces them with exactly the string + # '#line@N', where N is the index in the list 'line_directives'. + line_directives = [] + def replace(m): + i = len(line_directives) + line_directives.append(m.group()) + return '#line@%d' % i + csource = _r_line_directive.sub(replace, csource) + return csource, line_directives + +def _put_back_line_directives(csource, line_directives): + def replace(m): + s = m.group() + if not s.startswith('#line@'): + raise AssertionError("unexpected #line directive " + "(should have been processed and removed") + return line_directives[int(s[6:])] + return _r_line_directive.sub(replace, csource) + +def _preprocess(csource): + # First, remove the lines of the form '#line N "filename"' because + # the "filename" part could confuse the rest + csource, line_directives = _remove_line_directives(csource) + # Remove comments. NOTE: this only work because the cdef() section + # should not contain any string literals (except in line directives)! + def replace_keeping_newlines(m): + return ' ' + m.group().count('\n') * '\n' + csource = _r_comment.sub(replace_keeping_newlines, csource) + # Remove the "#define FOO x" lines + macros = {} + for match in _r_define.finditer(csource): + macroname, macrovalue = match.groups() + macrovalue = macrovalue.replace('\\\n', '').strip() + macros[macroname] = macrovalue + csource = _r_define.sub('', csource) + # + if pycparser.__version__ < '2.14': + csource = _workaround_for_old_pycparser(csource) + # + # BIG HACK: replace WINAPI or __stdcall with "volatile const". + # It doesn't make sense for the return type of a function to be + # "volatile volatile const", so we abuse it to detect __stdcall... + # Hack number 2 is that "int(volatile *fptr)();" is not valid C + # syntax, so we place the "volatile" before the opening parenthesis. + csource = _r_stdcall2.sub(' volatile volatile const(', csource) + csource = _r_stdcall1.sub(' volatile volatile const ', csource) + csource = _r_cdecl.sub(' ', csource) + # + # Replace `extern "Python"` with start/end markers + csource = _preprocess_extern_python(csource) + # + # Now there should not be any string literal left; warn if we get one + _warn_for_string_literal(csource) + # + # Replace "[...]" with "[__dotdotdotarray__]" + csource = _r_partial_array.sub('[__dotdotdotarray__]', csource) + # + # Replace "...}" with "__dotdotdotNUM__}". This construction should + # occur only at the end of enums; at the end of structs we have "...;}" + # and at the end of vararg functions "...);". Also replace "=...[,}]" + # with ",__dotdotdotNUM__[,}]": this occurs in the enums too, when + # giving an unknown value. + matches = list(_r_partial_enum.finditer(csource)) + for number, match in enumerate(reversed(matches)): + p = match.start() + if csource[p] == '=': + p2 = csource.find('...', p, match.end()) + assert p2 > p + csource = '%s,__dotdotdot%d__ %s' % (csource[:p], number, + csource[p2+3:]) + else: + assert csource[p:p+3] == '...' + csource = '%s __dotdotdot%d__ %s' % (csource[:p], number, + csource[p+3:]) + # Replace "int ..." or "unsigned long int..." with "__dotdotdotint__" + csource = _r_int_dotdotdot.sub(' __dotdotdotint__ ', csource) + # Replace "float ..." or "double..." with "__dotdotdotfloat__" + csource = _r_float_dotdotdot.sub(' __dotdotdotfloat__ ', csource) + # Replace all remaining "..." with the same name, "__dotdotdot__", + # which is declared with a typedef for the purpose of C parsing. + csource = csource.replace('...', ' __dotdotdot__ ') + # Finally, put back the line directives + csource = _put_back_line_directives(csource, line_directives) + return csource, macros + +def _common_type_names(csource): + # Look in the source for what looks like usages of types from the + # list of common types. A "usage" is approximated here as the + # appearance of the word, minus a "definition" of the type, which + # is the last word in a "typedef" statement. Approximative only + # but should be fine for all the common types. + look_for_words = set(COMMON_TYPES) + look_for_words.add(';') + look_for_words.add(',') + look_for_words.add('(') + look_for_words.add(')') + look_for_words.add('typedef') + words_used = set() + is_typedef = False + paren = 0 + previous_word = '' + for word in _r_words.findall(csource): + if word in look_for_words: + if word == ';': + if is_typedef: + words_used.discard(previous_word) + look_for_words.discard(previous_word) + is_typedef = False + elif word == 'typedef': + is_typedef = True + paren = 0 + elif word == '(': + paren += 1 + elif word == ')': + paren -= 1 + elif word == ',': + if is_typedef and paren == 0: + words_used.discard(previous_word) + look_for_words.discard(previous_word) + else: # word in COMMON_TYPES + words_used.add(word) + previous_word = word + return words_used + + +class Parser(object): + + def __init__(self): + self._declarations = {} + self._included_declarations = set() + self._anonymous_counter = 0 + self._structnode2type = weakref.WeakKeyDictionary() + self._options = {} + self._int_constants = {} + self._recomplete = [] + self._uses_new_feature = None + + def _parse(self, csource): + csource, macros = _preprocess(csource) + # XXX: for more efficiency we would need to poke into the + # internals of CParser... the following registers the + # typedefs, because their presence or absence influences the + # parsing itself (but what they are typedef'ed to plays no role) + ctn = _common_type_names(csource) + typenames = [] + for name in sorted(self._declarations): + if name.startswith('typedef '): + name = name[8:] + typenames.append(name) + ctn.discard(name) + typenames += sorted(ctn) + # + csourcelines = [] + csourcelines.append('# 1 ""') + for typename in typenames: + csourcelines.append('typedef int %s;' % typename) + csourcelines.append('typedef int __dotdotdotint__, __dotdotdotfloat__,' + ' __dotdotdot__;') + # this forces pycparser to consider the following in the file + # called from line 1 + csourcelines.append('# 1 "%s"' % (CDEF_SOURCE_STRING,)) + csourcelines.append(csource) + csourcelines.append('') # see test_missing_newline_bug + fullcsource = '\n'.join(csourcelines) + if lock is not None: + lock.acquire() # pycparser is not thread-safe... + try: + ast = _get_parser().parse(fullcsource) + except pycparser.c_parser.ParseError as e: + self.convert_pycparser_error(e, csource) + finally: + if lock is not None: + lock.release() + # csource will be used to find buggy source text + return ast, macros, csource + + def _convert_pycparser_error(self, e, csource): + # xxx look for ":NUM:" at the start of str(e) + # and interpret that as a line number. This will not work if + # the user gives explicit ``# NUM "FILE"`` directives. + line = None + msg = str(e) + match = re.match(r"%s:(\d+):" % (CDEF_SOURCE_STRING,), msg) + if match: + linenum = int(match.group(1), 10) + csourcelines = csource.splitlines() + if 1 <= linenum <= len(csourcelines): + line = csourcelines[linenum-1] + return line + + def convert_pycparser_error(self, e, csource): + line = self._convert_pycparser_error(e, csource) + + msg = str(e) + if line: + msg = 'cannot parse "%s"\n%s' % (line.strip(), msg) + else: + msg = 'parse error\n%s' % (msg,) + raise CDefError(msg) + + def parse(self, csource, override=False, packed=False, pack=None, + dllexport=False): + if packed: + if packed != True: + raise ValueError("'packed' should be False or True; use " + "'pack' to give another value") + if pack: + raise ValueError("cannot give both 'pack' and 'packed'") + pack = 1 + elif pack: + if pack & (pack - 1): + raise ValueError("'pack' must be a power of two, not %r" % + (pack,)) + else: + pack = 0 + prev_options = self._options + try: + self._options = {'override': override, + 'packed': pack, + 'dllexport': dllexport} + self._internal_parse(csource) + finally: + self._options = prev_options + + def _internal_parse(self, csource): + ast, macros, csource = self._parse(csource) + # add the macros + self._process_macros(macros) + # find the first "__dotdotdot__" and use that as a separator + # between the repeated typedefs and the real csource + iterator = iter(ast.ext) + for decl in iterator: + if decl.name == '__dotdotdot__': + break + else: + assert 0 + current_decl = None + # + try: + self._inside_extern_python = '__cffi_extern_python_stop' + for decl in iterator: + current_decl = decl + if isinstance(decl, pycparser.c_ast.Decl): + self._parse_decl(decl) + elif isinstance(decl, pycparser.c_ast.Typedef): + if not decl.name: + raise CDefError("typedef does not declare any name", + decl) + quals = 0 + if (isinstance(decl.type.type, pycparser.c_ast.IdentifierType) and + decl.type.type.names[-1].startswith('__dotdotdot')): + realtype = self._get_unknown_type(decl) + elif (isinstance(decl.type, pycparser.c_ast.PtrDecl) and + isinstance(decl.type.type, pycparser.c_ast.TypeDecl) and + isinstance(decl.type.type.type, + pycparser.c_ast.IdentifierType) and + decl.type.type.type.names[-1].startswith('__dotdotdot')): + realtype = self._get_unknown_ptr_type(decl) + else: + realtype, quals = self._get_type_and_quals( + decl.type, name=decl.name, partial_length_ok=True, + typedef_example="*(%s *)0" % (decl.name,)) + self._declare('typedef ' + decl.name, realtype, quals=quals) + elif decl.__class__.__name__ == 'Pragma': + # skip pragma, only in pycparser 2.15 + import warnings + warnings.warn( + "#pragma in cdef() are entirely ignored. " + "They should be removed for now, otherwise your " + "code might behave differently in a future version " + "of CFFI if #pragma support gets added. Note that " + "'#pragma pack' needs to be replaced with the " + "'packed' keyword argument to cdef().") + else: + raise CDefError("unexpected <%s>: this construct is valid " + "C but not valid in cdef()" % + decl.__class__.__name__, decl) + except CDefError as e: + if len(e.args) == 1: + e.args = e.args + (current_decl,) + raise + except FFIError as e: + msg = self._convert_pycparser_error(e, csource) + if msg: + e.args = (e.args[0] + "\n *** Err: %s" % msg,) + raise + + def _add_constants(self, key, val): + if key in self._int_constants: + if self._int_constants[key] == val: + return # ignore identical double declarations + raise FFIError( + "multiple declarations of constant: %s" % (key,)) + self._int_constants[key] = val + + def _add_integer_constant(self, name, int_str): + int_str = int_str.lower().rstrip("ul") + neg = int_str.startswith('-') + if neg: + int_str = int_str[1:] + # "010" is not valid oct in py3 + if (int_str.startswith("0") and int_str != '0' + and not int_str.startswith("0x")): + int_str = "0o" + int_str[1:] + pyvalue = int(int_str, 0) + if neg: + pyvalue = -pyvalue + self._add_constants(name, pyvalue) + self._declare('macro ' + name, pyvalue) + + def _process_macros(self, macros): + for key, value in macros.items(): + value = value.strip() + if _r_int_literal.match(value): + self._add_integer_constant(key, value) + elif value == '...': + self._declare('macro ' + key, value) + else: + raise CDefError( + 'only supports one of the following syntax:\n' + ' #define %s ... (literally dot-dot-dot)\n' + ' #define %s NUMBER (with NUMBER an integer' + ' constant, decimal/hex/octal)\n' + 'got:\n' + ' #define %s %s' + % (key, key, key, value)) + + def _declare_function(self, tp, quals, decl): + tp = self._get_type_pointer(tp, quals) + if self._options.get('dllexport'): + tag = 'dllexport_python ' + elif self._inside_extern_python == '__cffi_extern_python_start': + tag = 'extern_python ' + elif self._inside_extern_python == '__cffi_extern_python_plus_c_start': + tag = 'extern_python_plus_c ' + else: + tag = 'function ' + self._declare(tag + decl.name, tp) + + def _parse_decl(self, decl): + node = decl.type + if isinstance(node, pycparser.c_ast.FuncDecl): + tp, quals = self._get_type_and_quals(node, name=decl.name) + assert isinstance(tp, model.RawFunctionType) + self._declare_function(tp, quals, decl) + else: + if isinstance(node, pycparser.c_ast.Struct): + self._get_struct_union_enum_type('struct', node) + elif isinstance(node, pycparser.c_ast.Union): + self._get_struct_union_enum_type('union', node) + elif isinstance(node, pycparser.c_ast.Enum): + self._get_struct_union_enum_type('enum', node) + elif not decl.name: + raise CDefError("construct does not declare any variable", + decl) + # + if decl.name: + tp, quals = self._get_type_and_quals(node, + partial_length_ok=True) + if tp.is_raw_function: + self._declare_function(tp, quals, decl) + elif (tp.is_integer_type() and + hasattr(decl, 'init') and + hasattr(decl.init, 'value') and + _r_int_literal.match(decl.init.value)): + self._add_integer_constant(decl.name, decl.init.value) + elif (tp.is_integer_type() and + isinstance(decl.init, pycparser.c_ast.UnaryOp) and + decl.init.op == '-' and + hasattr(decl.init.expr, 'value') and + _r_int_literal.match(decl.init.expr.value)): + self._add_integer_constant(decl.name, + '-' + decl.init.expr.value) + elif (tp is model.void_type and + decl.name.startswith('__cffi_extern_python_')): + # hack: `extern "Python"` in the C source is replaced + # with "void __cffi_extern_python_start;" and + # "void __cffi_extern_python_stop;" + self._inside_extern_python = decl.name + else: + if self._inside_extern_python !='__cffi_extern_python_stop': + raise CDefError( + "cannot declare constants or " + "variables with 'extern \"Python\"'") + if (quals & model.Q_CONST) and not tp.is_array_type: + self._declare('constant ' + decl.name, tp, quals=quals) + else: + _warn_for_non_extern_non_static_global_variable(decl) + self._declare('variable ' + decl.name, tp, quals=quals) + + def parse_type(self, cdecl): + return self.parse_type_and_quals(cdecl)[0] + + def parse_type_and_quals(self, cdecl): + ast, macros = self._parse('void __dummy(\n%s\n);' % cdecl)[:2] + assert not macros + exprnode = ast.ext[-1].type.args.params[0] + if isinstance(exprnode, pycparser.c_ast.ID): + raise CDefError("unknown identifier '%s'" % (exprnode.name,)) + return self._get_type_and_quals(exprnode.type) + + def _declare(self, name, obj, included=False, quals=0): + if name in self._declarations: + prevobj, prevquals = self._declarations[name] + if prevobj is obj and prevquals == quals: + return + if not self._options.get('override'): + raise FFIError( + "multiple declarations of %s (for interactive usage, " + "try cdef(xx, override=True))" % (name,)) + assert '__dotdotdot__' not in name.split() + self._declarations[name] = (obj, quals) + if included: + self._included_declarations.add(obj) + + def _extract_quals(self, type): + quals = 0 + if isinstance(type, (pycparser.c_ast.TypeDecl, + pycparser.c_ast.PtrDecl)): + if 'const' in type.quals: + quals |= model.Q_CONST + if 'volatile' in type.quals: + quals |= model.Q_VOLATILE + if 'restrict' in type.quals: + quals |= model.Q_RESTRICT + return quals + + def _get_type_pointer(self, type, quals, declname=None): + if isinstance(type, model.RawFunctionType): + return type.as_function_pointer() + if (isinstance(type, model.StructOrUnionOrEnum) and + type.name.startswith('$') and type.name[1:].isdigit() and + type.forcename is None and declname is not None): + return model.NamedPointerType(type, declname, quals) + return model.PointerType(type, quals) + + def _get_type_and_quals(self, typenode, name=None, partial_length_ok=False, + typedef_example=None): + # first, dereference typedefs, if we have it already parsed, we're good + if (isinstance(typenode, pycparser.c_ast.TypeDecl) and + isinstance(typenode.type, pycparser.c_ast.IdentifierType) and + len(typenode.type.names) == 1 and + ('typedef ' + typenode.type.names[0]) in self._declarations): + tp, quals = self._declarations['typedef ' + typenode.type.names[0]] + quals |= self._extract_quals(typenode) + return tp, quals + # + if isinstance(typenode, pycparser.c_ast.ArrayDecl): + # array type + if typenode.dim is None: + length = None + else: + length = self._parse_constant( + typenode.dim, partial_length_ok=partial_length_ok) + # a hack: in 'typedef int foo_t[...][...];', don't use '...' as + # the length but use directly the C expression that would be + # generated by recompiler.py. This lets the typedef be used in + # many more places within recompiler.py + if typedef_example is not None: + if length == '...': + length = '_cffi_array_len(%s)' % (typedef_example,) + typedef_example = "*" + typedef_example + # + tp, quals = self._get_type_and_quals(typenode.type, + partial_length_ok=partial_length_ok, + typedef_example=typedef_example) + return model.ArrayType(tp, length), quals + # + if isinstance(typenode, pycparser.c_ast.PtrDecl): + # pointer type + itemtype, itemquals = self._get_type_and_quals(typenode.type) + tp = self._get_type_pointer(itemtype, itemquals, declname=name) + quals = self._extract_quals(typenode) + return tp, quals + # + if isinstance(typenode, pycparser.c_ast.TypeDecl): + quals = self._extract_quals(typenode) + type = typenode.type + if isinstance(type, pycparser.c_ast.IdentifierType): + # assume a primitive type. get it from .names, but reduce + # synonyms to a single chosen combination + names = list(type.names) + if names != ['signed', 'char']: # keep this unmodified + prefixes = {} + while names: + name = names[0] + if name in ('short', 'long', 'signed', 'unsigned'): + prefixes[name] = prefixes.get(name, 0) + 1 + del names[0] + else: + break + # ignore the 'signed' prefix below, and reorder the others + newnames = [] + for prefix in ('unsigned', 'short', 'long'): + for i in range(prefixes.get(prefix, 0)): + newnames.append(prefix) + if not names: + names = ['int'] # implicitly + if names == ['int']: # but kill it if 'short' or 'long' + if 'short' in prefixes or 'long' in prefixes: + names = [] + names = newnames + names + ident = ' '.join(names) + if ident == 'void': + return model.void_type, quals + if ident == '__dotdotdot__': + raise FFIError(':%d: bad usage of "..."' % + typenode.coord.line) + tp0, quals0 = resolve_common_type(self, ident) + return tp0, (quals | quals0) + # + if isinstance(type, pycparser.c_ast.Struct): + # 'struct foobar' + tp = self._get_struct_union_enum_type('struct', type, name) + return tp, quals + # + if isinstance(type, pycparser.c_ast.Union): + # 'union foobar' + tp = self._get_struct_union_enum_type('union', type, name) + return tp, quals + # + if isinstance(type, pycparser.c_ast.Enum): + # 'enum foobar' + tp = self._get_struct_union_enum_type('enum', type, name) + return tp, quals + # + if isinstance(typenode, pycparser.c_ast.FuncDecl): + # a function type + return self._parse_function_type(typenode, name), 0 + # + # nested anonymous structs or unions end up here + if isinstance(typenode, pycparser.c_ast.Struct): + return self._get_struct_union_enum_type('struct', typenode, name, + nested=True), 0 + if isinstance(typenode, pycparser.c_ast.Union): + return self._get_struct_union_enum_type('union', typenode, name, + nested=True), 0 + # + raise FFIError(":%d: bad or unsupported type declaration" % + typenode.coord.line) + + def _parse_function_type(self, typenode, funcname=None): + params = list(getattr(typenode.args, 'params', [])) + for i, arg in enumerate(params): + if not hasattr(arg, 'type'): + raise CDefError("%s arg %d: unknown type '%s'" + " (if you meant to use the old C syntax of giving" + " untyped arguments, it is not supported)" + % (funcname or 'in expression', i + 1, + getattr(arg, 'name', '?'))) + ellipsis = ( + len(params) > 0 and + isinstance(params[-1].type, pycparser.c_ast.TypeDecl) and + isinstance(params[-1].type.type, + pycparser.c_ast.IdentifierType) and + params[-1].type.type.names == ['__dotdotdot__']) + if ellipsis: + params.pop() + if not params: + raise CDefError( + "%s: a function with only '(...)' as argument" + " is not correct C" % (funcname or 'in expression')) + args = [self._as_func_arg(*self._get_type_and_quals(argdeclnode.type)) + for argdeclnode in params] + if not ellipsis and args == [model.void_type]: + args = [] + result, quals = self._get_type_and_quals(typenode.type) + # the 'quals' on the result type are ignored. HACK: we absure them + # to detect __stdcall functions: we textually replace "__stdcall" + # with "volatile volatile const" above. + abi = None + if hasattr(typenode.type, 'quals'): # else, probable syntax error anyway + if typenode.type.quals[-3:] == ['volatile', 'volatile', 'const']: + abi = '__stdcall' + return model.RawFunctionType(tuple(args), result, ellipsis, abi) + + def _as_func_arg(self, type, quals): + if isinstance(type, model.ArrayType): + return model.PointerType(type.item, quals) + elif isinstance(type, model.RawFunctionType): + return type.as_function_pointer() + else: + return type + + def _get_struct_union_enum_type(self, kind, type, name=None, nested=False): + # First, a level of caching on the exact 'type' node of the AST. + # This is obscure, but needed because pycparser "unrolls" declarations + # such as "typedef struct { } foo_t, *foo_p" and we end up with + # an AST that is not a tree, but a DAG, with the "type" node of the + # two branches foo_t and foo_p of the trees being the same node. + # It's a bit silly but detecting "DAG-ness" in the AST tree seems + # to be the only way to distinguish this case from two independent + # structs. See test_struct_with_two_usages. + try: + return self._structnode2type[type] + except KeyError: + pass + # + # Note that this must handle parsing "struct foo" any number of + # times and always return the same StructType object. Additionally, + # one of these times (not necessarily the first), the fields of + # the struct can be specified with "struct foo { ...fields... }". + # If no name is given, then we have to create a new anonymous struct + # with no caching; in this case, the fields are either specified + # right now or never. + # + force_name = name + name = type.name + # + # get the type or create it if needed + if name is None: + # 'force_name' is used to guess a more readable name for + # anonymous structs, for the common case "typedef struct { } foo". + if force_name is not None: + explicit_name = '$%s' % force_name + else: + self._anonymous_counter += 1 + explicit_name = '$%d' % self._anonymous_counter + tp = None + else: + explicit_name = name + key = '%s %s' % (kind, name) + tp, _ = self._declarations.get(key, (None, None)) + # + if tp is None: + if kind == 'struct': + tp = model.StructType(explicit_name, None, None, None) + elif kind == 'union': + tp = model.UnionType(explicit_name, None, None, None) + elif kind == 'enum': + if explicit_name == '__dotdotdot__': + raise CDefError("Enums cannot be declared with ...") + tp = self._build_enum_type(explicit_name, type.values) + else: + raise AssertionError("kind = %r" % (kind,)) + if name is not None: + self._declare(key, tp) + else: + if kind == 'enum' and type.values is not None: + raise NotImplementedError( + "enum %s: the '{}' declaration should appear on the first " + "time the enum is mentioned, not later" % explicit_name) + if not tp.forcename: + tp.force_the_name(force_name) + if tp.forcename and '$' in tp.name: + self._declare('anonymous %s' % tp.forcename, tp) + # + self._structnode2type[type] = tp + # + # enums: done here + if kind == 'enum': + return tp + # + # is there a 'type.decls'? If yes, then this is the place in the + # C sources that declare the fields. If no, then just return the + # existing type, possibly still incomplete. + if type.decls is None: + return tp + # + if tp.fldnames is not None: + raise CDefError("duplicate declaration of struct %s" % name) + fldnames = [] + fldtypes = [] + fldbitsize = [] + fldquals = [] + for decl in type.decls: + if (isinstance(decl.type, pycparser.c_ast.IdentifierType) and + ''.join(decl.type.names) == '__dotdotdot__'): + # XXX pycparser is inconsistent: 'names' should be a list + # of strings, but is sometimes just one string. Use + # str.join() as a way to cope with both. + self._make_partial(tp, nested) + continue + if decl.bitsize is None: + bitsize = -1 + else: + bitsize = self._parse_constant(decl.bitsize) + self._partial_length = False + type, fqual = self._get_type_and_quals(decl.type, + partial_length_ok=True) + if self._partial_length: + self._make_partial(tp, nested) + if isinstance(type, model.StructType) and type.partial: + self._make_partial(tp, nested) + fldnames.append(decl.name or '') + fldtypes.append(type) + fldbitsize.append(bitsize) + fldquals.append(fqual) + tp.fldnames = tuple(fldnames) + tp.fldtypes = tuple(fldtypes) + tp.fldbitsize = tuple(fldbitsize) + tp.fldquals = tuple(fldquals) + if fldbitsize != [-1] * len(fldbitsize): + if isinstance(tp, model.StructType) and tp.partial: + raise NotImplementedError("%s: using both bitfields and '...;'" + % (tp,)) + tp.packed = self._options.get('packed') + if tp.completed: # must be re-completed: it is not opaque any more + tp.completed = 0 + self._recomplete.append(tp) + return tp + + def _make_partial(self, tp, nested): + if not isinstance(tp, model.StructOrUnion): + raise CDefError("%s cannot be partial" % (tp,)) + if not tp.has_c_name() and not nested: + raise NotImplementedError("%s is partial but has no C name" %(tp,)) + tp.partial = True + + def _parse_constant(self, exprnode, partial_length_ok=False): + # for now, limited to expressions that are an immediate number + # or positive/negative number + if isinstance(exprnode, pycparser.c_ast.Constant): + s = exprnode.value + if '0' <= s[0] <= '9': + s = s.rstrip('uUlL') + try: + if s.startswith('0'): + return int(s, 8) + else: + return int(s, 10) + except ValueError: + if len(s) > 1: + if s.lower()[0:2] == '0x': + return int(s, 16) + elif s.lower()[0:2] == '0b': + return int(s, 2) + raise CDefError("invalid constant %r" % (s,)) + elif s[0] == "'" and s[-1] == "'" and ( + len(s) == 3 or (len(s) == 4 and s[1] == "\\")): + return ord(s[-2]) + else: + raise CDefError("invalid constant %r" % (s,)) + # + if (isinstance(exprnode, pycparser.c_ast.UnaryOp) and + exprnode.op == '+'): + return self._parse_constant(exprnode.expr) + # + if (isinstance(exprnode, pycparser.c_ast.UnaryOp) and + exprnode.op == '-'): + return -self._parse_constant(exprnode.expr) + # load previously defined int constant + if (isinstance(exprnode, pycparser.c_ast.ID) and + exprnode.name in self._int_constants): + return self._int_constants[exprnode.name] + # + if (isinstance(exprnode, pycparser.c_ast.ID) and + exprnode.name == '__dotdotdotarray__'): + if partial_length_ok: + self._partial_length = True + return '...' + raise FFIError(":%d: unsupported '[...]' here, cannot derive " + "the actual array length in this context" + % exprnode.coord.line) + # + if isinstance(exprnode, pycparser.c_ast.BinaryOp): + left = self._parse_constant(exprnode.left) + right = self._parse_constant(exprnode.right) + if exprnode.op == '+': + return left + right + elif exprnode.op == '-': + return left - right + elif exprnode.op == '*': + return left * right + elif exprnode.op == '/': + return self._c_div(left, right) + elif exprnode.op == '%': + return left - self._c_div(left, right) * right + elif exprnode.op == '<<': + return left << right + elif exprnode.op == '>>': + return left >> right + elif exprnode.op == '&': + return left & right + elif exprnode.op == '|': + return left | right + elif exprnode.op == '^': + return left ^ right + # + raise FFIError(":%d: unsupported expression: expected a " + "simple numeric constant" % exprnode.coord.line) + + def _c_div(self, a, b): + result = a // b + if ((a < 0) ^ (b < 0)) and (a % b) != 0: + result += 1 + return result + + def _build_enum_type(self, explicit_name, decls): + if decls is not None: + partial = False + enumerators = [] + enumvalues = [] + nextenumvalue = 0 + for enum in decls.enumerators: + if _r_enum_dotdotdot.match(enum.name): + partial = True + continue + if enum.value is not None: + nextenumvalue = self._parse_constant(enum.value) + enumerators.append(enum.name) + enumvalues.append(nextenumvalue) + self._add_constants(enum.name, nextenumvalue) + nextenumvalue += 1 + enumerators = tuple(enumerators) + enumvalues = tuple(enumvalues) + tp = model.EnumType(explicit_name, enumerators, enumvalues) + tp.partial = partial + else: # opaque enum + tp = model.EnumType(explicit_name, (), ()) + return tp + + def include(self, other): + for name, (tp, quals) in other._declarations.items(): + if name.startswith('anonymous $enum_$'): + continue # fix for test_anonymous_enum_include + kind = name.split(' ', 1)[0] + if kind in ('struct', 'union', 'enum', 'anonymous', 'typedef'): + self._declare(name, tp, included=True, quals=quals) + for k, v in other._int_constants.items(): + self._add_constants(k, v) + + def _get_unknown_type(self, decl): + typenames = decl.type.type.names + if typenames == ['__dotdotdot__']: + return model.unknown_type(decl.name) + + if typenames == ['__dotdotdotint__']: + if self._uses_new_feature is None: + self._uses_new_feature = "'typedef int... %s'" % decl.name + return model.UnknownIntegerType(decl.name) + + if typenames == ['__dotdotdotfloat__']: + # note: not for 'long double' so far + if self._uses_new_feature is None: + self._uses_new_feature = "'typedef float... %s'" % decl.name + return model.UnknownFloatType(decl.name) + + raise FFIError(':%d: unsupported usage of "..." in typedef' + % decl.coord.line) + + def _get_unknown_ptr_type(self, decl): + if decl.type.type.type.names == ['__dotdotdot__']: + return model.unknown_ptr_type(decl.name) + raise FFIError(':%d: unsupported usage of "..." in typedef' + % decl.coord.line) diff --git a/intelliaide/vendor/cffi/error.py b/intelliaide/vendor/cffi/error.py new file mode 100644 index 0000000..0a27247 --- /dev/null +++ b/intelliaide/vendor/cffi/error.py @@ -0,0 +1,31 @@ + +class FFIError(Exception): + __module__ = 'cffi' + +class CDefError(Exception): + __module__ = 'cffi' + def __str__(self): + try: + current_decl = self.args[1] + filename = current_decl.coord.file + linenum = current_decl.coord.line + prefix = '%s:%d: ' % (filename, linenum) + except (AttributeError, TypeError, IndexError): + prefix = '' + return '%s%s' % (prefix, self.args[0]) + +class VerificationError(Exception): + """ An error raised when verification fails + """ + __module__ = 'cffi' + +class VerificationMissing(Exception): + """ An error raised when incomplete structures are passed into + cdef, but no verification has been done + """ + __module__ = 'cffi' + +class PkgConfigError(Exception): + """ An error raised for missing modules in pkg-config + """ + __module__ = 'cffi' diff --git a/intelliaide/vendor/cffi/ffiplatform.py b/intelliaide/vendor/cffi/ffiplatform.py new file mode 100644 index 0000000..adca28f --- /dev/null +++ b/intelliaide/vendor/cffi/ffiplatform.py @@ -0,0 +1,113 @@ +import sys, os +from .error import VerificationError + + +LIST_OF_FILE_NAMES = ['sources', 'include_dirs', 'library_dirs', + 'extra_objects', 'depends'] + +def get_extension(srcfilename, modname, sources=(), **kwds): + from cffi._shimmed_dist_utils import Extension + allsources = [srcfilename] + for src in sources: + allsources.append(os.path.normpath(src)) + return Extension(name=modname, sources=allsources, **kwds) + +def compile(tmpdir, ext, compiler_verbose=0, debug=None): + """Compile a C extension module using distutils.""" + + saved_environ = os.environ.copy() + try: + outputfilename = _build(tmpdir, ext, compiler_verbose, debug) + outputfilename = os.path.abspath(outputfilename) + finally: + # workaround for a distutils bugs where some env vars can + # become longer and longer every time it is used + for key, value in saved_environ.items(): + if os.environ.get(key) != value: + os.environ[key] = value + return outputfilename + +def _build(tmpdir, ext, compiler_verbose=0, debug=None): + # XXX compact but horrible :-( + from cffi._shimmed_dist_utils import Distribution, CompileError, LinkError, set_threshold, set_verbosity + + dist = Distribution({'ext_modules': [ext]}) + dist.parse_config_files() + options = dist.get_option_dict('build_ext') + if debug is None: + debug = sys.flags.debug + options['debug'] = ('ffiplatform', debug) + options['force'] = ('ffiplatform', True) + options['build_lib'] = ('ffiplatform', tmpdir) + options['build_temp'] = ('ffiplatform', tmpdir) + # + try: + old_level = set_threshold(0) or 0 + try: + set_verbosity(compiler_verbose) + dist.run_command('build_ext') + cmd_obj = dist.get_command_obj('build_ext') + [soname] = cmd_obj.get_outputs() + finally: + set_threshold(old_level) + except (CompileError, LinkError) as e: + raise VerificationError('%s: %s' % (e.__class__.__name__, e)) + # + return soname + +try: + from os.path import samefile +except ImportError: + def samefile(f1, f2): + return os.path.abspath(f1) == os.path.abspath(f2) + +def maybe_relative_path(path): + if not os.path.isabs(path): + return path # already relative + dir = path + names = [] + while True: + prevdir = dir + dir, name = os.path.split(prevdir) + if dir == prevdir or not dir: + return path # failed to make it relative + names.append(name) + try: + if samefile(dir, os.curdir): + names.reverse() + return os.path.join(*names) + except OSError: + pass + +# ____________________________________________________________ + +try: + int_or_long = (int, long) + import cStringIO +except NameError: + int_or_long = int # Python 3 + import io as cStringIO + +def _flatten(x, f): + if isinstance(x, str): + f.write('%ds%s' % (len(x), x)) + elif isinstance(x, dict): + keys = sorted(x.keys()) + f.write('%dd' % len(keys)) + for key in keys: + _flatten(key, f) + _flatten(x[key], f) + elif isinstance(x, (list, tuple)): + f.write('%dl' % len(x)) + for value in x: + _flatten(value, f) + elif isinstance(x, int_or_long): + f.write('%di' % (x,)) + else: + raise TypeError( + "the keywords to verify() contains unsupported object %r" % (x,)) + +def flatten(x): + f = cStringIO.StringIO() + _flatten(x, f) + return f.getvalue() diff --git a/intelliaide/vendor/cffi/lock.py b/intelliaide/vendor/cffi/lock.py new file mode 100644 index 0000000..db91b71 --- /dev/null +++ b/intelliaide/vendor/cffi/lock.py @@ -0,0 +1,30 @@ +import sys + +if sys.version_info < (3,): + try: + from thread import allocate_lock + except ImportError: + from dummy_thread import allocate_lock +else: + try: + from _thread import allocate_lock + except ImportError: + from _dummy_thread import allocate_lock + + +##import sys +##l1 = allocate_lock + +##class allocate_lock(object): +## def __init__(self): +## self._real = l1() +## def __enter__(self): +## for i in range(4, 0, -1): +## print sys._getframe(i).f_code +## print +## return self._real.__enter__() +## def __exit__(self, *args): +## return self._real.__exit__(*args) +## def acquire(self, f): +## assert f is False +## return self._real.acquire(f) diff --git a/intelliaide/vendor/cffi/model.py b/intelliaide/vendor/cffi/model.py new file mode 100644 index 0000000..e5f4cae --- /dev/null +++ b/intelliaide/vendor/cffi/model.py @@ -0,0 +1,618 @@ +import types +import weakref + +from .lock import allocate_lock +from .error import CDefError, VerificationError, VerificationMissing + +# type qualifiers +Q_CONST = 0x01 +Q_RESTRICT = 0x02 +Q_VOLATILE = 0x04 + +def qualify(quals, replace_with): + if quals & Q_CONST: + replace_with = ' const ' + replace_with.lstrip() + if quals & Q_VOLATILE: + replace_with = ' volatile ' + replace_with.lstrip() + if quals & Q_RESTRICT: + # It seems that __restrict is supported by gcc and msvc. + # If you hit some different compiler, add a #define in + # _cffi_include.h for it (and in its copies, documented there) + replace_with = ' __restrict ' + replace_with.lstrip() + return replace_with + + +class BaseTypeByIdentity(object): + is_array_type = False + is_raw_function = False + + def get_c_name(self, replace_with='', context='a C file', quals=0): + result = self.c_name_with_marker + assert result.count('&') == 1 + # some logic duplication with ffi.getctype()... :-( + replace_with = replace_with.strip() + if replace_with: + if replace_with.startswith('*') and '&[' in result: + replace_with = '(%s)' % replace_with + elif not replace_with[0] in '[(': + replace_with = ' ' + replace_with + replace_with = qualify(quals, replace_with) + result = result.replace('&', replace_with) + if '$' in result: + raise VerificationError( + "cannot generate '%s' in %s: unknown type name" + % (self._get_c_name(), context)) + return result + + def _get_c_name(self): + return self.c_name_with_marker.replace('&', '') + + def has_c_name(self): + return '$' not in self._get_c_name() + + def is_integer_type(self): + return False + + def get_cached_btype(self, ffi, finishlist, can_delay=False): + try: + BType = ffi._cached_btypes[self] + except KeyError: + BType = self.build_backend_type(ffi, finishlist) + BType2 = ffi._cached_btypes.setdefault(self, BType) + assert BType2 is BType + return BType + + def __repr__(self): + return '<%s>' % (self._get_c_name(),) + + def _get_items(self): + return [(name, getattr(self, name)) for name in self._attrs_] + + +class BaseType(BaseTypeByIdentity): + + def __eq__(self, other): + return (self.__class__ == other.__class__ and + self._get_items() == other._get_items()) + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((self.__class__, tuple(self._get_items()))) + + +class VoidType(BaseType): + _attrs_ = () + + def __init__(self): + self.c_name_with_marker = 'void&' + + def build_backend_type(self, ffi, finishlist): + return global_cache(self, ffi, 'new_void_type') + +void_type = VoidType() + + +class BasePrimitiveType(BaseType): + def is_complex_type(self): + return False + + +class PrimitiveType(BasePrimitiveType): + _attrs_ = ('name',) + + ALL_PRIMITIVE_TYPES = { + 'char': 'c', + 'short': 'i', + 'int': 'i', + 'long': 'i', + 'long long': 'i', + 'signed char': 'i', + 'unsigned char': 'i', + 'unsigned short': 'i', + 'unsigned int': 'i', + 'unsigned long': 'i', + 'unsigned long long': 'i', + 'float': 'f', + 'double': 'f', + 'long double': 'f', + '_cffi_float_complex_t': 'j', + '_cffi_double_complex_t': 'j', + '_Bool': 'i', + # the following types are not primitive in the C sense + 'wchar_t': 'c', + 'char16_t': 'c', + 'char32_t': 'c', + 'int8_t': 'i', + 'uint8_t': 'i', + 'int16_t': 'i', + 'uint16_t': 'i', + 'int32_t': 'i', + 'uint32_t': 'i', + 'int64_t': 'i', + 'uint64_t': 'i', + 'int_least8_t': 'i', + 'uint_least8_t': 'i', + 'int_least16_t': 'i', + 'uint_least16_t': 'i', + 'int_least32_t': 'i', + 'uint_least32_t': 'i', + 'int_least64_t': 'i', + 'uint_least64_t': 'i', + 'int_fast8_t': 'i', + 'uint_fast8_t': 'i', + 'int_fast16_t': 'i', + 'uint_fast16_t': 'i', + 'int_fast32_t': 'i', + 'uint_fast32_t': 'i', + 'int_fast64_t': 'i', + 'uint_fast64_t': 'i', + 'intptr_t': 'i', + 'uintptr_t': 'i', + 'intmax_t': 'i', + 'uintmax_t': 'i', + 'ptrdiff_t': 'i', + 'size_t': 'i', + 'ssize_t': 'i', + } + + def __init__(self, name): + assert name in self.ALL_PRIMITIVE_TYPES + self.name = name + self.c_name_with_marker = name + '&' + + def is_char_type(self): + return self.ALL_PRIMITIVE_TYPES[self.name] == 'c' + def is_integer_type(self): + return self.ALL_PRIMITIVE_TYPES[self.name] == 'i' + def is_float_type(self): + return self.ALL_PRIMITIVE_TYPES[self.name] == 'f' + def is_complex_type(self): + return self.ALL_PRIMITIVE_TYPES[self.name] == 'j' + + def build_backend_type(self, ffi, finishlist): + return global_cache(self, ffi, 'new_primitive_type', self.name) + + +class UnknownIntegerType(BasePrimitiveType): + _attrs_ = ('name',) + + def __init__(self, name): + self.name = name + self.c_name_with_marker = name + '&' + + def is_integer_type(self): + return True + + def build_backend_type(self, ffi, finishlist): + raise NotImplementedError("integer type '%s' can only be used after " + "compilation" % self.name) + +class UnknownFloatType(BasePrimitiveType): + _attrs_ = ('name', ) + + def __init__(self, name): + self.name = name + self.c_name_with_marker = name + '&' + + def build_backend_type(self, ffi, finishlist): + raise NotImplementedError("float type '%s' can only be used after " + "compilation" % self.name) + + +class BaseFunctionType(BaseType): + _attrs_ = ('args', 'result', 'ellipsis', 'abi') + + def __init__(self, args, result, ellipsis, abi=None): + self.args = args + self.result = result + self.ellipsis = ellipsis + self.abi = abi + # + reprargs = [arg._get_c_name() for arg in self.args] + if self.ellipsis: + reprargs.append('...') + reprargs = reprargs or ['void'] + replace_with = self._base_pattern % (', '.join(reprargs),) + if abi is not None: + replace_with = replace_with[:1] + abi + ' ' + replace_with[1:] + self.c_name_with_marker = ( + self.result.c_name_with_marker.replace('&', replace_with)) + + +class RawFunctionType(BaseFunctionType): + # Corresponds to a C type like 'int(int)', which is the C type of + # a function, but not a pointer-to-function. The backend has no + # notion of such a type; it's used temporarily by parsing. + _base_pattern = '(&)(%s)' + is_raw_function = True + + def build_backend_type(self, ffi, finishlist): + raise CDefError("cannot render the type %r: it is a function " + "type, not a pointer-to-function type" % (self,)) + + def as_function_pointer(self): + return FunctionPtrType(self.args, self.result, self.ellipsis, self.abi) + + +class FunctionPtrType(BaseFunctionType): + _base_pattern = '(*&)(%s)' + + def build_backend_type(self, ffi, finishlist): + result = self.result.get_cached_btype(ffi, finishlist) + args = [] + for tp in self.args: + args.append(tp.get_cached_btype(ffi, finishlist)) + abi_args = () + if self.abi == "__stdcall": + if not self.ellipsis: # __stdcall ignored for variadic funcs + try: + abi_args = (ffi._backend.FFI_STDCALL,) + except AttributeError: + pass + return global_cache(self, ffi, 'new_function_type', + tuple(args), result, self.ellipsis, *abi_args) + + def as_raw_function(self): + return RawFunctionType(self.args, self.result, self.ellipsis, self.abi) + + +class PointerType(BaseType): + _attrs_ = ('totype', 'quals') + + def __init__(self, totype, quals=0): + self.totype = totype + self.quals = quals + extra = " *&" + if totype.is_array_type: + extra = "(%s)" % (extra.lstrip(),) + extra = qualify(quals, extra) + self.c_name_with_marker = totype.c_name_with_marker.replace('&', extra) + + def build_backend_type(self, ffi, finishlist): + BItem = self.totype.get_cached_btype(ffi, finishlist, can_delay=True) + return global_cache(self, ffi, 'new_pointer_type', BItem) + +voidp_type = PointerType(void_type) + +def ConstPointerType(totype): + return PointerType(totype, Q_CONST) + +const_voidp_type = ConstPointerType(void_type) + + +class NamedPointerType(PointerType): + _attrs_ = ('totype', 'name') + + def __init__(self, totype, name, quals=0): + PointerType.__init__(self, totype, quals) + self.name = name + self.c_name_with_marker = name + '&' + + +class ArrayType(BaseType): + _attrs_ = ('item', 'length') + is_array_type = True + + def __init__(self, item, length): + self.item = item + self.length = length + # + if length is None: + brackets = '&[]' + elif length == '...': + brackets = '&[/*...*/]' + else: + brackets = '&[%s]' % length + self.c_name_with_marker = ( + self.item.c_name_with_marker.replace('&', brackets)) + + def length_is_unknown(self): + return isinstance(self.length, str) + + def resolve_length(self, newlength): + return ArrayType(self.item, newlength) + + def build_backend_type(self, ffi, finishlist): + if self.length_is_unknown(): + raise CDefError("cannot render the type %r: unknown length" % + (self,)) + self.item.get_cached_btype(ffi, finishlist) # force the item BType + BPtrItem = PointerType(self.item).get_cached_btype(ffi, finishlist) + return global_cache(self, ffi, 'new_array_type', BPtrItem, self.length) + +char_array_type = ArrayType(PrimitiveType('char'), None) + + +class StructOrUnionOrEnum(BaseTypeByIdentity): + _attrs_ = ('name',) + forcename = None + + def build_c_name_with_marker(self): + name = self.forcename or '%s %s' % (self.kind, self.name) + self.c_name_with_marker = name + '&' + + def force_the_name(self, forcename): + self.forcename = forcename + self.build_c_name_with_marker() + + def get_official_name(self): + assert self.c_name_with_marker.endswith('&') + return self.c_name_with_marker[:-1] + + +class StructOrUnion(StructOrUnionOrEnum): + fixedlayout = None + completed = 0 + partial = False + packed = 0 + + def __init__(self, name, fldnames, fldtypes, fldbitsize, fldquals=None): + self.name = name + self.fldnames = fldnames + self.fldtypes = fldtypes + self.fldbitsize = fldbitsize + self.fldquals = fldquals + self.build_c_name_with_marker() + + def anonymous_struct_fields(self): + if self.fldtypes is not None: + for name, type in zip(self.fldnames, self.fldtypes): + if name == '' and isinstance(type, StructOrUnion): + yield type + + def enumfields(self, expand_anonymous_struct_union=True): + fldquals = self.fldquals + if fldquals is None: + fldquals = (0,) * len(self.fldnames) + for name, type, bitsize, quals in zip(self.fldnames, self.fldtypes, + self.fldbitsize, fldquals): + if (name == '' and isinstance(type, StructOrUnion) + and expand_anonymous_struct_union): + # nested anonymous struct/union + for result in type.enumfields(): + yield result + else: + yield (name, type, bitsize, quals) + + def force_flatten(self): + # force the struct or union to have a declaration that lists + # directly all fields returned by enumfields(), flattening + # nested anonymous structs/unions. + names = [] + types = [] + bitsizes = [] + fldquals = [] + for name, type, bitsize, quals in self.enumfields(): + names.append(name) + types.append(type) + bitsizes.append(bitsize) + fldquals.append(quals) + self.fldnames = tuple(names) + self.fldtypes = tuple(types) + self.fldbitsize = tuple(bitsizes) + self.fldquals = tuple(fldquals) + + def get_cached_btype(self, ffi, finishlist, can_delay=False): + BType = StructOrUnionOrEnum.get_cached_btype(self, ffi, finishlist, + can_delay) + if not can_delay: + self.finish_backend_type(ffi, finishlist) + return BType + + def finish_backend_type(self, ffi, finishlist): + if self.completed: + if self.completed != 2: + raise NotImplementedError("recursive structure declaration " + "for '%s'" % (self.name,)) + return + BType = ffi._cached_btypes[self] + # + self.completed = 1 + # + if self.fldtypes is None: + pass # not completing it: it's an opaque struct + # + elif self.fixedlayout is None: + fldtypes = [tp.get_cached_btype(ffi, finishlist) + for tp in self.fldtypes] + lst = list(zip(self.fldnames, fldtypes, self.fldbitsize)) + extra_flags = () + if self.packed: + if self.packed == 1: + extra_flags = (8,) # SF_PACKED + else: + extra_flags = (0, self.packed) + ffi._backend.complete_struct_or_union(BType, lst, self, + -1, -1, *extra_flags) + # + else: + fldtypes = [] + fieldofs, fieldsize, totalsize, totalalignment = self.fixedlayout + for i in range(len(self.fldnames)): + fsize = fieldsize[i] + ftype = self.fldtypes[i] + # + if isinstance(ftype, ArrayType) and ftype.length_is_unknown(): + # fix the length to match the total size + BItemType = ftype.item.get_cached_btype(ffi, finishlist) + nlen, nrest = divmod(fsize, ffi.sizeof(BItemType)) + if nrest != 0: + self._verification_error( + "field '%s.%s' has a bogus size?" % ( + self.name, self.fldnames[i] or '{}')) + ftype = ftype.resolve_length(nlen) + self.fldtypes = (self.fldtypes[:i] + (ftype,) + + self.fldtypes[i+1:]) + # + BFieldType = ftype.get_cached_btype(ffi, finishlist) + if isinstance(ftype, ArrayType) and ftype.length is None: + assert fsize == 0 + else: + bitemsize = ffi.sizeof(BFieldType) + if bitemsize != fsize: + self._verification_error( + "field '%s.%s' is declared as %d bytes, but is " + "really %d bytes" % (self.name, + self.fldnames[i] or '{}', + bitemsize, fsize)) + fldtypes.append(BFieldType) + # + lst = list(zip(self.fldnames, fldtypes, self.fldbitsize, fieldofs)) + ffi._backend.complete_struct_or_union(BType, lst, self, + totalsize, totalalignment) + self.completed = 2 + + def _verification_error(self, msg): + raise VerificationError(msg) + + def check_not_partial(self): + if self.partial and self.fixedlayout is None: + raise VerificationMissing(self._get_c_name()) + + def build_backend_type(self, ffi, finishlist): + self.check_not_partial() + finishlist.append(self) + # + return global_cache(self, ffi, 'new_%s_type' % self.kind, + self.get_official_name(), key=self) + + +class StructType(StructOrUnion): + kind = 'struct' + + +class UnionType(StructOrUnion): + kind = 'union' + + +class EnumType(StructOrUnionOrEnum): + kind = 'enum' + partial = False + partial_resolved = False + + def __init__(self, name, enumerators, enumvalues, baseinttype=None): + self.name = name + self.enumerators = enumerators + self.enumvalues = enumvalues + self.baseinttype = baseinttype + self.build_c_name_with_marker() + + def force_the_name(self, forcename): + StructOrUnionOrEnum.force_the_name(self, forcename) + if self.forcename is None: + name = self.get_official_name() + self.forcename = '$' + name.replace(' ', '_') + + def check_not_partial(self): + if self.partial and not self.partial_resolved: + raise VerificationMissing(self._get_c_name()) + + def build_backend_type(self, ffi, finishlist): + self.check_not_partial() + base_btype = self.build_baseinttype(ffi, finishlist) + return global_cache(self, ffi, 'new_enum_type', + self.get_official_name(), + self.enumerators, self.enumvalues, + base_btype, key=self) + + def build_baseinttype(self, ffi, finishlist): + if self.baseinttype is not None: + return self.baseinttype.get_cached_btype(ffi, finishlist) + # + if self.enumvalues: + smallest_value = min(self.enumvalues) + largest_value = max(self.enumvalues) + else: + import warnings + try: + # XXX! The goal is to ensure that the warnings.warn() + # will not suppress the warning. We want to get it + # several times if we reach this point several times. + __warningregistry__.clear() + except NameError: + pass + warnings.warn("%r has no values explicitly defined; " + "guessing that it is equivalent to 'unsigned int'" + % self._get_c_name()) + smallest_value = largest_value = 0 + if smallest_value < 0: # needs a signed type + sign = 1 + candidate1 = PrimitiveType("int") + candidate2 = PrimitiveType("long") + else: + sign = 0 + candidate1 = PrimitiveType("unsigned int") + candidate2 = PrimitiveType("unsigned long") + btype1 = candidate1.get_cached_btype(ffi, finishlist) + btype2 = candidate2.get_cached_btype(ffi, finishlist) + size1 = ffi.sizeof(btype1) + size2 = ffi.sizeof(btype2) + if (smallest_value >= ((-1) << (8*size1-1)) and + largest_value < (1 << (8*size1-sign))): + return btype1 + if (smallest_value >= ((-1) << (8*size2-1)) and + largest_value < (1 << (8*size2-sign))): + return btype2 + raise CDefError("%s values don't all fit into either 'long' " + "or 'unsigned long'" % self._get_c_name()) + +def unknown_type(name, structname=None): + if structname is None: + structname = '$%s' % name + tp = StructType(structname, None, None, None) + tp.force_the_name(name) + tp.origin = "unknown_type" + return tp + +def unknown_ptr_type(name, structname=None): + if structname is None: + structname = '$$%s' % name + tp = StructType(structname, None, None, None) + return NamedPointerType(tp, name) + + +global_lock = allocate_lock() +_typecache_cffi_backend = weakref.WeakValueDictionary() + +def get_typecache(backend): + # returns _typecache_cffi_backend if backend is the _cffi_backend + # module, or type(backend).__typecache if backend is an instance of + # CTypesBackend (or some FakeBackend class during tests) + if isinstance(backend, types.ModuleType): + return _typecache_cffi_backend + with global_lock: + if not hasattr(type(backend), '__typecache'): + type(backend).__typecache = weakref.WeakValueDictionary() + return type(backend).__typecache + +def global_cache(srctype, ffi, funcname, *args, **kwds): + key = kwds.pop('key', (funcname, args)) + assert not kwds + try: + return ffi._typecache[key] + except KeyError: + pass + try: + res = getattr(ffi._backend, funcname)(*args) + except NotImplementedError as e: + raise NotImplementedError("%s: %r: %s" % (funcname, srctype, e)) + # note that setdefault() on WeakValueDictionary is not atomic + # and contains a rare bug (http://bugs.python.org/issue19542); + # we have to use a lock and do it ourselves + cache = ffi._typecache + with global_lock: + res1 = cache.get(key) + if res1 is None: + cache[key] = res + return res + else: + return res1 + +def pointer_cache(ffi, BType): + return global_cache('?', ffi, 'new_pointer_type', BType) + +def attach_exception_info(e, name): + if e.args and type(e.args[0]) is str: + e.args = ('%s: %s' % (name, e.args[0]),) + e.args[1:] diff --git a/intelliaide/vendor/cffi/parse_c_type.h b/intelliaide/vendor/cffi/parse_c_type.h new file mode 100644 index 0000000..84e4ef8 --- /dev/null +++ b/intelliaide/vendor/cffi/parse_c_type.h @@ -0,0 +1,181 @@ + +/* This part is from file 'cffi/parse_c_type.h'. It is copied at the + beginning of C sources generated by CFFI's ffi.set_source(). */ + +typedef void *_cffi_opcode_t; + +#define _CFFI_OP(opcode, arg) (_cffi_opcode_t)(opcode | (((uintptr_t)(arg)) << 8)) +#define _CFFI_GETOP(cffi_opcode) ((unsigned char)(uintptr_t)cffi_opcode) +#define _CFFI_GETARG(cffi_opcode) (((intptr_t)cffi_opcode) >> 8) + +#define _CFFI_OP_PRIMITIVE 1 +#define _CFFI_OP_POINTER 3 +#define _CFFI_OP_ARRAY 5 +#define _CFFI_OP_OPEN_ARRAY 7 +#define _CFFI_OP_STRUCT_UNION 9 +#define _CFFI_OP_ENUM 11 +#define _CFFI_OP_FUNCTION 13 +#define _CFFI_OP_FUNCTION_END 15 +#define _CFFI_OP_NOOP 17 +#define _CFFI_OP_BITFIELD 19 +#define _CFFI_OP_TYPENAME 21 +#define _CFFI_OP_CPYTHON_BLTN_V 23 // varargs +#define _CFFI_OP_CPYTHON_BLTN_N 25 // noargs +#define _CFFI_OP_CPYTHON_BLTN_O 27 // O (i.e. a single arg) +#define _CFFI_OP_CONSTANT 29 +#define _CFFI_OP_CONSTANT_INT 31 +#define _CFFI_OP_GLOBAL_VAR 33 +#define _CFFI_OP_DLOPEN_FUNC 35 +#define _CFFI_OP_DLOPEN_CONST 37 +#define _CFFI_OP_GLOBAL_VAR_F 39 +#define _CFFI_OP_EXTERN_PYTHON 41 + +#define _CFFI_PRIM_VOID 0 +#define _CFFI_PRIM_BOOL 1 +#define _CFFI_PRIM_CHAR 2 +#define _CFFI_PRIM_SCHAR 3 +#define _CFFI_PRIM_UCHAR 4 +#define _CFFI_PRIM_SHORT 5 +#define _CFFI_PRIM_USHORT 6 +#define _CFFI_PRIM_INT 7 +#define _CFFI_PRIM_UINT 8 +#define _CFFI_PRIM_LONG 9 +#define _CFFI_PRIM_ULONG 10 +#define _CFFI_PRIM_LONGLONG 11 +#define _CFFI_PRIM_ULONGLONG 12 +#define _CFFI_PRIM_FLOAT 13 +#define _CFFI_PRIM_DOUBLE 14 +#define _CFFI_PRIM_LONGDOUBLE 15 + +#define _CFFI_PRIM_WCHAR 16 +#define _CFFI_PRIM_INT8 17 +#define _CFFI_PRIM_UINT8 18 +#define _CFFI_PRIM_INT16 19 +#define _CFFI_PRIM_UINT16 20 +#define _CFFI_PRIM_INT32 21 +#define _CFFI_PRIM_UINT32 22 +#define _CFFI_PRIM_INT64 23 +#define _CFFI_PRIM_UINT64 24 +#define _CFFI_PRIM_INTPTR 25 +#define _CFFI_PRIM_UINTPTR 26 +#define _CFFI_PRIM_PTRDIFF 27 +#define _CFFI_PRIM_SIZE 28 +#define _CFFI_PRIM_SSIZE 29 +#define _CFFI_PRIM_INT_LEAST8 30 +#define _CFFI_PRIM_UINT_LEAST8 31 +#define _CFFI_PRIM_INT_LEAST16 32 +#define _CFFI_PRIM_UINT_LEAST16 33 +#define _CFFI_PRIM_INT_LEAST32 34 +#define _CFFI_PRIM_UINT_LEAST32 35 +#define _CFFI_PRIM_INT_LEAST64 36 +#define _CFFI_PRIM_UINT_LEAST64 37 +#define _CFFI_PRIM_INT_FAST8 38 +#define _CFFI_PRIM_UINT_FAST8 39 +#define _CFFI_PRIM_INT_FAST16 40 +#define _CFFI_PRIM_UINT_FAST16 41 +#define _CFFI_PRIM_INT_FAST32 42 +#define _CFFI_PRIM_UINT_FAST32 43 +#define _CFFI_PRIM_INT_FAST64 44 +#define _CFFI_PRIM_UINT_FAST64 45 +#define _CFFI_PRIM_INTMAX 46 +#define _CFFI_PRIM_UINTMAX 47 +#define _CFFI_PRIM_FLOATCOMPLEX 48 +#define _CFFI_PRIM_DOUBLECOMPLEX 49 +#define _CFFI_PRIM_CHAR16 50 +#define _CFFI_PRIM_CHAR32 51 + +#define _CFFI__NUM_PRIM 52 +#define _CFFI__UNKNOWN_PRIM (-1) +#define _CFFI__UNKNOWN_FLOAT_PRIM (-2) +#define _CFFI__UNKNOWN_LONG_DOUBLE (-3) + +#define _CFFI__IO_FILE_STRUCT (-1) + + +struct _cffi_global_s { + const char *name; + void *address; + _cffi_opcode_t type_op; + void *size_or_direct_fn; // OP_GLOBAL_VAR: size, or 0 if unknown + // OP_CPYTHON_BLTN_*: addr of direct function +}; + +struct _cffi_getconst_s { + unsigned long long value; + const struct _cffi_type_context_s *ctx; + int gindex; +}; + +struct _cffi_struct_union_s { + const char *name; + int type_index; // -> _cffi_types, on a OP_STRUCT_UNION + int flags; // _CFFI_F_* flags below + size_t size; + int alignment; + int first_field_index; // -> _cffi_fields array + int num_fields; +}; +#define _CFFI_F_UNION 0x01 // is a union, not a struct +#define _CFFI_F_CHECK_FIELDS 0x02 // complain if fields are not in the + // "standard layout" or if some are missing +#define _CFFI_F_PACKED 0x04 // for CHECK_FIELDS, assume a packed struct +#define _CFFI_F_EXTERNAL 0x08 // in some other ffi.include() +#define _CFFI_F_OPAQUE 0x10 // opaque + +struct _cffi_field_s { + const char *name; + size_t field_offset; + size_t field_size; + _cffi_opcode_t field_type_op; +}; + +struct _cffi_enum_s { + const char *name; + int type_index; // -> _cffi_types, on a OP_ENUM + int type_prim; // _CFFI_PRIM_xxx + const char *enumerators; // comma-delimited string +}; + +struct _cffi_typename_s { + const char *name; + int type_index; /* if opaque, points to a possibly artificial + OP_STRUCT which is itself opaque */ +}; + +struct _cffi_type_context_s { + _cffi_opcode_t *types; + const struct _cffi_global_s *globals; + const struct _cffi_field_s *fields; + const struct _cffi_struct_union_s *struct_unions; + const struct _cffi_enum_s *enums; + const struct _cffi_typename_s *typenames; + int num_globals; + int num_struct_unions; + int num_enums; + int num_typenames; + const char *const *includes; + int num_types; + int flags; /* future extension */ +}; + +struct _cffi_parse_info_s { + const struct _cffi_type_context_s *ctx; + _cffi_opcode_t *output; + unsigned int output_size; + size_t error_location; + const char *error_message; +}; + +struct _cffi_externpy_s { + const char *name; + size_t size_of_result; + void *reserved1, *reserved2; +}; + +#ifdef _CFFI_INTERNAL +static int parse_c_type(struct _cffi_parse_info_s *info, const char *input); +static int search_in_globals(const struct _cffi_type_context_s *ctx, + const char *search, size_t search_len); +static int search_in_struct_unions(const struct _cffi_type_context_s *ctx, + const char *search, size_t search_len); +#endif diff --git a/intelliaide/vendor/cffi/pkgconfig.py b/intelliaide/vendor/cffi/pkgconfig.py new file mode 100644 index 0000000..5c93f15 --- /dev/null +++ b/intelliaide/vendor/cffi/pkgconfig.py @@ -0,0 +1,121 @@ +# pkg-config, https://www.freedesktop.org/wiki/Software/pkg-config/ integration for cffi +import sys, os, subprocess + +from .error import PkgConfigError + + +def merge_flags(cfg1, cfg2): + """Merge values from cffi config flags cfg2 to cf1 + + Example: + merge_flags({"libraries": ["one"]}, {"libraries": ["two"]}) + {"libraries": ["one", "two"]} + """ + for key, value in cfg2.items(): + if key not in cfg1: + cfg1[key] = value + else: + if not isinstance(cfg1[key], list): + raise TypeError("cfg1[%r] should be a list of strings" % (key,)) + if not isinstance(value, list): + raise TypeError("cfg2[%r] should be a list of strings" % (key,)) + cfg1[key].extend(value) + return cfg1 + + +def call(libname, flag, encoding=sys.getfilesystemencoding()): + """Calls pkg-config and returns the output if found + """ + a = ["pkg-config", "--print-errors"] + a.append(flag) + a.append(libname) + try: + pc = subprocess.Popen(a, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except EnvironmentError as e: + raise PkgConfigError("cannot run pkg-config: %s" % (str(e).strip(),)) + + bout, berr = pc.communicate() + if pc.returncode != 0: + try: + berr = berr.decode(encoding) + except Exception: + pass + raise PkgConfigError(berr.strip()) + + if sys.version_info >= (3,) and not isinstance(bout, str): # Python 3.x + try: + bout = bout.decode(encoding) + except UnicodeDecodeError: + raise PkgConfigError("pkg-config %s %s returned bytes that cannot " + "be decoded with encoding %r:\n%r" % + (flag, libname, encoding, bout)) + + if os.altsep != '\\' and '\\' in bout: + raise PkgConfigError("pkg-config %s %s returned an unsupported " + "backslash-escaped output:\n%r" % + (flag, libname, bout)) + return bout + + +def flags_from_pkgconfig(libs): + r"""Return compiler line flags for FFI.set_source based on pkg-config output + + Usage + ... + ffibuilder.set_source("_foo", pkgconfig = ["libfoo", "libbar >= 1.8.3"]) + + If pkg-config is installed on build machine, then arguments include_dirs, + library_dirs, libraries, define_macros, extra_compile_args and + extra_link_args are extended with an output of pkg-config for libfoo and + libbar. + + Raises PkgConfigError in case the pkg-config call fails. + """ + + def get_include_dirs(string): + return [x[2:] for x in string.split() if x.startswith("-I")] + + def get_library_dirs(string): + return [x[2:] for x in string.split() if x.startswith("-L")] + + def get_libraries(string): + return [x[2:] for x in string.split() if x.startswith("-l")] + + # convert -Dfoo=bar to list of tuples [("foo", "bar")] expected by distutils + def get_macros(string): + def _macro(x): + x = x[2:] # drop "-D" + if '=' in x: + return tuple(x.split("=", 1)) # "-Dfoo=bar" => ("foo", "bar") + else: + return (x, None) # "-Dfoo" => ("foo", None) + return [_macro(x) for x in string.split() if x.startswith("-D")] + + def get_other_cflags(string): + return [x for x in string.split() if not x.startswith("-I") and + not x.startswith("-D")] + + def get_other_libs(string): + return [x for x in string.split() if not x.startswith("-L") and + not x.startswith("-l")] + + # return kwargs for given libname + def kwargs(libname): + fse = sys.getfilesystemencoding() + all_cflags = call(libname, "--cflags") + all_libs = call(libname, "--libs") + return { + "include_dirs": get_include_dirs(all_cflags), + "library_dirs": get_library_dirs(all_libs), + "libraries": get_libraries(all_libs), + "define_macros": get_macros(all_cflags), + "extra_compile_args": get_other_cflags(all_cflags), + "extra_link_args": get_other_libs(all_libs), + } + + # merge all arguments together + ret = {} + for libname in libs: + lib_flags = kwargs(libname) + merge_flags(ret, lib_flags) + return ret diff --git a/intelliaide/vendor/cffi/recompiler.py b/intelliaide/vendor/cffi/recompiler.py new file mode 100644 index 0000000..7734a34 --- /dev/null +++ b/intelliaide/vendor/cffi/recompiler.py @@ -0,0 +1,1598 @@ +import io, os, sys, sysconfig +from . import ffiplatform, model +from .error import VerificationError +from .cffi_opcode import * + +VERSION_BASE = 0x2601 +VERSION_EMBEDDED = 0x2701 +VERSION_CHAR16CHAR32 = 0x2801 + +USE_LIMITED_API = ((sys.platform != 'win32' or sys.version_info < (3, 0) or + sys.version_info >= (3, 5)) and + not sysconfig.get_config_var("Py_GIL_DISABLED")) # free-threaded doesn't yet support limited API + +class GlobalExpr: + def __init__(self, name, address, type_op, size=0, check_value=0): + self.name = name + self.address = address + self.type_op = type_op + self.size = size + self.check_value = check_value + + def as_c_expr(self): + return ' { "%s", (void *)%s, %s, (void *)%s },' % ( + self.name, self.address, self.type_op.as_c_expr(), self.size) + + def as_python_expr(self): + return "b'%s%s',%d" % (self.type_op.as_python_bytes(), self.name, + self.check_value) + +class FieldExpr: + def __init__(self, name, field_offset, field_size, fbitsize, field_type_op): + self.name = name + self.field_offset = field_offset + self.field_size = field_size + self.fbitsize = fbitsize + self.field_type_op = field_type_op + + def as_c_expr(self): + spaces = " " * len(self.name) + return (' { "%s", %s,\n' % (self.name, self.field_offset) + + ' %s %s,\n' % (spaces, self.field_size) + + ' %s %s },' % (spaces, self.field_type_op.as_c_expr())) + + def as_python_expr(self): + raise NotImplementedError + + def as_field_python_expr(self): + if self.field_type_op.op == OP_NOOP: + size_expr = '' + elif self.field_type_op.op == OP_BITFIELD: + size_expr = format_four_bytes(self.fbitsize) + else: + raise NotImplementedError + return "b'%s%s%s'" % (self.field_type_op.as_python_bytes(), + size_expr, + self.name) + +class StructUnionExpr: + def __init__(self, name, type_index, flags, size, alignment, comment, + first_field_index, c_fields): + self.name = name + self.type_index = type_index + self.flags = flags + self.size = size + self.alignment = alignment + self.comment = comment + self.first_field_index = first_field_index + self.c_fields = c_fields + + def as_c_expr(self): + return (' { "%s", %d, %s,' % (self.name, self.type_index, self.flags) + + '\n %s, %s, ' % (self.size, self.alignment) + + '%d, %d ' % (self.first_field_index, len(self.c_fields)) + + ('/* %s */ ' % self.comment if self.comment else '') + + '},') + + def as_python_expr(self): + flags = eval(self.flags, G_FLAGS) + fields_expr = [c_field.as_field_python_expr() + for c_field in self.c_fields] + return "(b'%s%s%s',%s)" % ( + format_four_bytes(self.type_index), + format_four_bytes(flags), + self.name, + ','.join(fields_expr)) + +class EnumExpr: + def __init__(self, name, type_index, size, signed, allenums): + self.name = name + self.type_index = type_index + self.size = size + self.signed = signed + self.allenums = allenums + + def as_c_expr(self): + return (' { "%s", %d, _cffi_prim_int(%s, %s),\n' + ' "%s" },' % (self.name, self.type_index, + self.size, self.signed, self.allenums)) + + def as_python_expr(self): + prim_index = { + (1, 0): PRIM_UINT8, (1, 1): PRIM_INT8, + (2, 0): PRIM_UINT16, (2, 1): PRIM_INT16, + (4, 0): PRIM_UINT32, (4, 1): PRIM_INT32, + (8, 0): PRIM_UINT64, (8, 1): PRIM_INT64, + }[self.size, self.signed] + return "b'%s%s%s\\x00%s'" % (format_four_bytes(self.type_index), + format_four_bytes(prim_index), + self.name, self.allenums) + +class TypenameExpr: + def __init__(self, name, type_index): + self.name = name + self.type_index = type_index + + def as_c_expr(self): + return ' { "%s", %d },' % (self.name, self.type_index) + + def as_python_expr(self): + return "b'%s%s'" % (format_four_bytes(self.type_index), self.name) + + +# ____________________________________________________________ + + +class Recompiler: + _num_externpy = 0 + + def __init__(self, ffi, module_name, target_is_python=False): + self.ffi = ffi + self.module_name = module_name + self.target_is_python = target_is_python + self._version = VERSION_BASE + + def needs_version(self, ver): + self._version = max(self._version, ver) + + def collect_type_table(self): + self._typesdict = {} + self._generate("collecttype") + # + all_decls = sorted(self._typesdict, key=str) + # + # prepare all FUNCTION bytecode sequences first + self.cffi_types = [] + for tp in all_decls: + if tp.is_raw_function: + assert self._typesdict[tp] is None + self._typesdict[tp] = len(self.cffi_types) + self.cffi_types.append(tp) # placeholder + for tp1 in tp.args: + assert isinstance(tp1, (model.VoidType, + model.BasePrimitiveType, + model.PointerType, + model.StructOrUnionOrEnum, + model.FunctionPtrType)) + if self._typesdict[tp1] is None: + self._typesdict[tp1] = len(self.cffi_types) + self.cffi_types.append(tp1) # placeholder + self.cffi_types.append('END') # placeholder + # + # prepare all OTHER bytecode sequences + for tp in all_decls: + if not tp.is_raw_function and self._typesdict[tp] is None: + self._typesdict[tp] = len(self.cffi_types) + self.cffi_types.append(tp) # placeholder + if tp.is_array_type and tp.length is not None: + self.cffi_types.append('LEN') # placeholder + assert None not in self._typesdict.values() + # + # collect all structs and unions and enums + self._struct_unions = {} + self._enums = {} + for tp in all_decls: + if isinstance(tp, model.StructOrUnion): + self._struct_unions[tp] = None + elif isinstance(tp, model.EnumType): + self._enums[tp] = None + for i, tp in enumerate(sorted(self._struct_unions, + key=lambda tp: tp.name)): + self._struct_unions[tp] = i + for i, tp in enumerate(sorted(self._enums, + key=lambda tp: tp.name)): + self._enums[tp] = i + # + # emit all bytecode sequences now + for tp in all_decls: + method = getattr(self, '_emit_bytecode_' + tp.__class__.__name__) + method(tp, self._typesdict[tp]) + # + # consistency check + for op in self.cffi_types: + assert isinstance(op, CffiOp) + self.cffi_types = tuple(self.cffi_types) # don't change any more + + def _enum_fields(self, tp): + # When producing C, expand all anonymous struct/union fields. + # That's necessary to have C code checking the offsets of the + # individual fields contained in them. When producing Python, + # don't do it and instead write it like it is, with the + # corresponding fields having an empty name. Empty names are + # recognized at runtime when we import the generated Python + # file. + expand_anonymous_struct_union = not self.target_is_python + return tp.enumfields(expand_anonymous_struct_union) + + def _do_collect_type(self, tp): + if not isinstance(tp, model.BaseTypeByIdentity): + if isinstance(tp, tuple): + for x in tp: + self._do_collect_type(x) + return + if tp not in self._typesdict: + self._typesdict[tp] = None + if isinstance(tp, model.FunctionPtrType): + self._do_collect_type(tp.as_raw_function()) + elif isinstance(tp, model.StructOrUnion): + if tp.fldtypes is not None and ( + tp not in self.ffi._parser._included_declarations): + for name1, tp1, _, _ in self._enum_fields(tp): + self._do_collect_type(self._field_type(tp, name1, tp1)) + else: + for _, x in tp._get_items(): + self._do_collect_type(x) + + def _generate(self, step_name): + lst = self.ffi._parser._declarations.items() + for name, (tp, quals) in sorted(lst): + kind, realname = name.split(' ', 1) + try: + method = getattr(self, '_generate_cpy_%s_%s' % (kind, + step_name)) + except AttributeError: + raise VerificationError( + "not implemented in recompile(): %r" % name) + try: + self._current_quals = quals + method(tp, realname) + except Exception as e: + model.attach_exception_info(e, name) + raise + + # ---------- + + ALL_STEPS = ["global", "field", "struct_union", "enum", "typename"] + + def collect_step_tables(self): + # collect the declarations for '_cffi_globals', '_cffi_typenames', etc. + self._lsts = {} + for step_name in self.ALL_STEPS: + self._lsts[step_name] = [] + self._seen_struct_unions = set() + self._generate("ctx") + self._add_missing_struct_unions() + # + for step_name in self.ALL_STEPS: + lst = self._lsts[step_name] + if step_name != "field": + lst.sort(key=lambda entry: entry.name) + self._lsts[step_name] = tuple(lst) # don't change any more + # + # check for a possible internal inconsistency: _cffi_struct_unions + # should have been generated with exactly self._struct_unions + lst = self._lsts["struct_union"] + for tp, i in self._struct_unions.items(): + assert i < len(lst) + assert lst[i].name == tp.name + assert len(lst) == len(self._struct_unions) + # same with enums + lst = self._lsts["enum"] + for tp, i in self._enums.items(): + assert i < len(lst) + assert lst[i].name == tp.name + assert len(lst) == len(self._enums) + + # ---------- + + def _prnt(self, what=''): + self._f.write(what + '\n') + + def write_source_to_f(self, f, preamble): + if self.target_is_python: + assert preamble is None + self.write_py_source_to_f(f) + else: + assert preamble is not None + self.write_c_source_to_f(f, preamble) + + def _rel_readlines(self, filename): + g = open(os.path.join(os.path.dirname(__file__), filename), 'r') + lines = g.readlines() + g.close() + return lines + + def write_c_source_to_f(self, f, preamble): + self._f = f + prnt = self._prnt + if self.ffi._embedding is not None: + prnt('#define _CFFI_USE_EMBEDDING') + if not USE_LIMITED_API: + prnt('#define _CFFI_NO_LIMITED_API') + # + # first the '#include' (actually done by inlining the file's content) + lines = self._rel_readlines('_cffi_include.h') + i = lines.index('#include "parse_c_type.h"\n') + lines[i:i+1] = self._rel_readlines('parse_c_type.h') + prnt(''.join(lines)) + # + # if we have ffi._embedding != None, we give it here as a macro + # and include an extra file + base_module_name = self.module_name.split('.')[-1] + if self.ffi._embedding is not None: + prnt('#define _CFFI_MODULE_NAME "%s"' % (self.module_name,)) + prnt('static const char _CFFI_PYTHON_STARTUP_CODE[] = {') + self._print_string_literal_in_array(self.ffi._embedding) + prnt('0 };') + prnt('#ifdef PYPY_VERSION') + prnt('# define _CFFI_PYTHON_STARTUP_FUNC _cffi_pypyinit_%s' % ( + base_module_name,)) + prnt('#elif PY_MAJOR_VERSION >= 3') + prnt('# define _CFFI_PYTHON_STARTUP_FUNC PyInit_%s' % ( + base_module_name,)) + prnt('#else') + prnt('# define _CFFI_PYTHON_STARTUP_FUNC init%s' % ( + base_module_name,)) + prnt('#endif') + lines = self._rel_readlines('_embedding.h') + i = lines.index('#include "_cffi_errors.h"\n') + lines[i:i+1] = self._rel_readlines('_cffi_errors.h') + prnt(''.join(lines)) + self.needs_version(VERSION_EMBEDDED) + # + # then paste the C source given by the user, verbatim. + prnt('/************************************************************/') + prnt() + prnt(preamble) + prnt() + prnt('/************************************************************/') + prnt() + # + # the declaration of '_cffi_types' + prnt('static void *_cffi_types[] = {') + typeindex2type = dict([(i, tp) for (tp, i) in self._typesdict.items()]) + for i, op in enumerate(self.cffi_types): + comment = '' + if i in typeindex2type: + comment = ' // ' + typeindex2type[i]._get_c_name() + prnt('/* %2d */ %s,%s' % (i, op.as_c_expr(), comment)) + if not self.cffi_types: + prnt(' 0') + prnt('};') + prnt() + # + # call generate_cpy_xxx_decl(), for every xxx found from + # ffi._parser._declarations. This generates all the functions. + self._seen_constants = set() + self._generate("decl") + # + # the declaration of '_cffi_globals' and '_cffi_typenames' + nums = {} + for step_name in self.ALL_STEPS: + lst = self._lsts[step_name] + nums[step_name] = len(lst) + if nums[step_name] > 0: + prnt('static const struct _cffi_%s_s _cffi_%ss[] = {' % ( + step_name, step_name)) + for entry in lst: + prnt(entry.as_c_expr()) + prnt('};') + prnt() + # + # the declaration of '_cffi_includes' + if self.ffi._included_ffis: + prnt('static const char * const _cffi_includes[] = {') + for ffi_to_include in self.ffi._included_ffis: + try: + included_module_name, included_source = ( + ffi_to_include._assigned_source[:2]) + except AttributeError: + raise VerificationError( + "ffi object %r includes %r, but the latter has not " + "been prepared with set_source()" % ( + self.ffi, ffi_to_include,)) + if included_source is None: + raise VerificationError( + "not implemented yet: ffi.include() of a Python-based " + "ffi inside a C-based ffi") + prnt(' "%s",' % (included_module_name,)) + prnt(' NULL') + prnt('};') + prnt() + # + # the declaration of '_cffi_type_context' + prnt('static const struct _cffi_type_context_s _cffi_type_context = {') + prnt(' _cffi_types,') + for step_name in self.ALL_STEPS: + if nums[step_name] > 0: + prnt(' _cffi_%ss,' % step_name) + else: + prnt(' NULL, /* no %ss */' % step_name) + for step_name in self.ALL_STEPS: + if step_name != "field": + prnt(' %d, /* num_%ss */' % (nums[step_name], step_name)) + if self.ffi._included_ffis: + prnt(' _cffi_includes,') + else: + prnt(' NULL, /* no includes */') + prnt(' %d, /* num_types */' % (len(self.cffi_types),)) + flags = 0 + if self._num_externpy > 0 or self.ffi._embedding is not None: + flags |= 1 # set to mean that we use extern "Python" + prnt(' %d, /* flags */' % flags) + prnt('};') + prnt() + # + # the init function + prnt('#ifdef __GNUC__') + prnt('# pragma GCC visibility push(default) /* for -fvisibility= */') + prnt('#endif') + prnt() + prnt('#ifdef PYPY_VERSION') + prnt('PyMODINIT_FUNC') + prnt('_cffi_pypyinit_%s(const void *p[])' % (base_module_name,)) + prnt('{') + if flags & 1: + prnt(' if (((intptr_t)p[0]) >= 0x0A03) {') + prnt(' _cffi_call_python_org = ' + '(void(*)(struct _cffi_externpy_s *, char *))p[1];') + prnt(' }') + prnt(' p[0] = (const void *)0x%x;' % self._version) + prnt(' p[1] = &_cffi_type_context;') + prnt('#if PY_MAJOR_VERSION >= 3') + prnt(' return NULL;') + prnt('#endif') + prnt('}') + # on Windows, distutils insists on putting init_cffi_xyz in + # 'export_symbols', so instead of fighting it, just give up and + # give it one + prnt('# ifdef _MSC_VER') + prnt(' PyMODINIT_FUNC') + prnt('# if PY_MAJOR_VERSION >= 3') + prnt(' PyInit_%s(void) { return NULL; }' % (base_module_name,)) + prnt('# else') + prnt(' init%s(void) { }' % (base_module_name,)) + prnt('# endif') + prnt('# endif') + prnt('#elif PY_MAJOR_VERSION >= 3') + prnt('PyMODINIT_FUNC') + prnt('PyInit_%s(void)' % (base_module_name,)) + prnt('{') + prnt(' return _cffi_init("%s", 0x%x, &_cffi_type_context);' % ( + self.module_name, self._version)) + prnt('}') + prnt('#else') + prnt('PyMODINIT_FUNC') + prnt('init%s(void)' % (base_module_name,)) + prnt('{') + prnt(' _cffi_init("%s", 0x%x, &_cffi_type_context);' % ( + self.module_name, self._version)) + prnt('}') + prnt('#endif') + prnt() + prnt('#ifdef __GNUC__') + prnt('# pragma GCC visibility pop') + prnt('#endif') + self._version = None + + def _to_py(self, x): + if isinstance(x, str): + return "b'%s'" % (x,) + if isinstance(x, (list, tuple)): + rep = [self._to_py(item) for item in x] + if len(rep) == 1: + rep.append('') + return "(%s)" % (','.join(rep),) + return x.as_python_expr() # Py2: unicode unexpected; Py3: bytes unexp. + + def write_py_source_to_f(self, f): + self._f = f + prnt = self._prnt + # + # header + prnt("# auto-generated file") + prnt("import _cffi_backend") + # + # the 'import' of the included ffis + num_includes = len(self.ffi._included_ffis or ()) + for i in range(num_includes): + ffi_to_include = self.ffi._included_ffis[i] + try: + included_module_name, included_source = ( + ffi_to_include._assigned_source[:2]) + except AttributeError: + raise VerificationError( + "ffi object %r includes %r, but the latter has not " + "been prepared with set_source()" % ( + self.ffi, ffi_to_include,)) + if included_source is not None: + raise VerificationError( + "not implemented yet: ffi.include() of a C-based " + "ffi inside a Python-based ffi") + prnt('from %s import ffi as _ffi%d' % (included_module_name, i)) + prnt() + prnt("ffi = _cffi_backend.FFI('%s'," % (self.module_name,)) + prnt(" _version = 0x%x," % (self._version,)) + self._version = None + # + # the '_types' keyword argument + self.cffi_types = tuple(self.cffi_types) # don't change any more + types_lst = [op.as_python_bytes() for op in self.cffi_types] + prnt(' _types = %s,' % (self._to_py(''.join(types_lst)),)) + typeindex2type = dict([(i, tp) for (tp, i) in self._typesdict.items()]) + # + # the keyword arguments from ALL_STEPS + for step_name in self.ALL_STEPS: + lst = self._lsts[step_name] + if len(lst) > 0 and step_name != "field": + prnt(' _%ss = %s,' % (step_name, self._to_py(lst))) + # + # the '_includes' keyword argument + if num_includes > 0: + prnt(' _includes = (%s,),' % ( + ', '.join(['_ffi%d' % i for i in range(num_includes)]),)) + # + # the footer + prnt(')') + + # ---------- + + def _gettypenum(self, type): + # a KeyError here is a bug. please report it! :-) + return self._typesdict[type] + + def _convert_funcarg_to_c(self, tp, fromvar, tovar, errcode): + extraarg = '' + if isinstance(tp, model.BasePrimitiveType) and not tp.is_complex_type(): + if tp.is_integer_type() and tp.name != '_Bool': + converter = '_cffi_to_c_int' + extraarg = ', %s' % tp.name + elif isinstance(tp, model.UnknownFloatType): + # don't check with is_float_type(): it may be a 'long + # double' here, and _cffi_to_c_double would loose precision + converter = '(%s)_cffi_to_c_double' % (tp.get_c_name(''),) + else: + cname = tp.get_c_name('') + converter = '(%s)_cffi_to_c_%s' % (cname, + tp.name.replace(' ', '_')) + if cname in ('char16_t', 'char32_t'): + self.needs_version(VERSION_CHAR16CHAR32) + errvalue = '-1' + # + elif isinstance(tp, model.PointerType): + self._convert_funcarg_to_c_ptr_or_array(tp, fromvar, + tovar, errcode) + return + # + elif (isinstance(tp, model.StructOrUnionOrEnum) or + isinstance(tp, model.BasePrimitiveType)): + # a struct (not a struct pointer) as a function argument; + # or, a complex (the same code works) + self._prnt(' if (_cffi_to_c((char *)&%s, _cffi_type(%d), %s) < 0)' + % (tovar, self._gettypenum(tp), fromvar)) + self._prnt(' %s;' % errcode) + return + # + elif isinstance(tp, model.FunctionPtrType): + converter = '(%s)_cffi_to_c_pointer' % tp.get_c_name('') + extraarg = ', _cffi_type(%d)' % self._gettypenum(tp) + errvalue = 'NULL' + # + else: + raise NotImplementedError(tp) + # + self._prnt(' %s = %s(%s%s);' % (tovar, converter, fromvar, extraarg)) + self._prnt(' if (%s == (%s)%s && PyErr_Occurred())' % ( + tovar, tp.get_c_name(''), errvalue)) + self._prnt(' %s;' % errcode) + + def _extra_local_variables(self, tp, localvars, freelines): + if isinstance(tp, model.PointerType): + localvars.add('Py_ssize_t datasize') + localvars.add('struct _cffi_freeme_s *large_args_free = NULL') + freelines.add('if (large_args_free != NULL)' + ' _cffi_free_array_arguments(large_args_free);') + + def _convert_funcarg_to_c_ptr_or_array(self, tp, fromvar, tovar, errcode): + self._prnt(' datasize = _cffi_prepare_pointer_call_argument(') + self._prnt(' _cffi_type(%d), %s, (char **)&%s);' % ( + self._gettypenum(tp), fromvar, tovar)) + self._prnt(' if (datasize != 0) {') + self._prnt(' %s = ((size_t)datasize) <= 640 ? ' + '(%s)alloca((size_t)datasize) : NULL;' % ( + tovar, tp.get_c_name(''))) + self._prnt(' if (_cffi_convert_array_argument(_cffi_type(%d), %s, ' + '(char **)&%s,' % (self._gettypenum(tp), fromvar, tovar)) + self._prnt(' datasize, &large_args_free) < 0)') + self._prnt(' %s;' % errcode) + self._prnt(' }') + + def _convert_expr_from_c(self, tp, var, context): + if isinstance(tp, model.BasePrimitiveType): + if tp.is_integer_type() and tp.name != '_Bool': + return '_cffi_from_c_int(%s, %s)' % (var, tp.name) + elif isinstance(tp, model.UnknownFloatType): + return '_cffi_from_c_double(%s)' % (var,) + elif tp.name != 'long double' and not tp.is_complex_type(): + cname = tp.name.replace(' ', '_') + if cname in ('char16_t', 'char32_t'): + self.needs_version(VERSION_CHAR16CHAR32) + return '_cffi_from_c_%s(%s)' % (cname, var) + else: + return '_cffi_from_c_deref((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, (model.PointerType, model.FunctionPtrType)): + return '_cffi_from_c_pointer((char *)%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, model.ArrayType): + return '_cffi_from_c_pointer((char *)%s, _cffi_type(%d))' % ( + var, self._gettypenum(model.PointerType(tp.item))) + elif isinstance(tp, model.StructOrUnion): + if tp.fldnames is None: + raise TypeError("'%s' is used as %s, but is opaque" % ( + tp._get_c_name(), context)) + return '_cffi_from_c_struct((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, model.EnumType): + return '_cffi_from_c_deref((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + else: + raise NotImplementedError(tp) + + # ---------- + # typedefs + + def _typedef_type(self, tp, name): + return self._global_type(tp, "(*(%s *)0)" % (name,)) + + def _generate_cpy_typedef_collecttype(self, tp, name): + self._do_collect_type(self._typedef_type(tp, name)) + + def _generate_cpy_typedef_decl(self, tp, name): + pass + + def _typedef_ctx(self, tp, name): + type_index = self._typesdict[tp] + self._lsts["typename"].append(TypenameExpr(name, type_index)) + + def _generate_cpy_typedef_ctx(self, tp, name): + tp = self._typedef_type(tp, name) + self._typedef_ctx(tp, name) + if getattr(tp, "origin", None) == "unknown_type": + self._struct_ctx(tp, tp.name, approxname=None) + elif isinstance(tp, model.NamedPointerType): + self._struct_ctx(tp.totype, tp.totype.name, approxname=tp.name, + named_ptr=tp) + + # ---------- + # function declarations + + def _generate_cpy_function_collecttype(self, tp, name): + self._do_collect_type(tp.as_raw_function()) + if tp.ellipsis and not self.target_is_python: + self._do_collect_type(tp) + + def _generate_cpy_function_decl(self, tp, name): + assert not self.target_is_python + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + # cannot support vararg functions better than this: check for its + # exact type (including the fixed arguments), and build it as a + # constant function pointer (no CPython wrapper) + self._generate_cpy_constant_decl(tp, name) + return + prnt = self._prnt + numargs = len(tp.args) + if numargs == 0: + argname = 'noarg' + elif numargs == 1: + argname = 'arg0' + else: + argname = 'args' + # + # ------------------------------ + # the 'd' version of the function, only for addressof(lib, 'func') + arguments = [] + call_arguments = [] + context = 'argument of %s' % name + for i, type in enumerate(tp.args): + arguments.append(type.get_c_name(' x%d' % i, context)) + call_arguments.append('x%d' % i) + repr_arguments = ', '.join(arguments) + repr_arguments = repr_arguments or 'void' + if tp.abi: + abi = tp.abi + ' ' + else: + abi = '' + name_and_arguments = '%s_cffi_d_%s(%s)' % (abi, name, repr_arguments) + prnt('static %s' % (tp.result.get_c_name(name_and_arguments),)) + prnt('{') + call_arguments = ', '.join(call_arguments) + result_code = 'return ' + if isinstance(tp.result, model.VoidType): + result_code = '' + prnt(' %s%s(%s);' % (result_code, name, call_arguments)) + prnt('}') + # + prnt('#ifndef PYPY_VERSION') # ------------------------------ + # + prnt('static PyObject *') + prnt('_cffi_f_%s(PyObject *self, PyObject *%s)' % (name, argname)) + prnt('{') + # + context = 'argument of %s' % name + for i, type in enumerate(tp.args): + arg = type.get_c_name(' x%d' % i, context) + prnt(' %s;' % arg) + # + localvars = set() + freelines = set() + for type in tp.args: + self._extra_local_variables(type, localvars, freelines) + for decl in sorted(localvars): + prnt(' %s;' % (decl,)) + # + if not isinstance(tp.result, model.VoidType): + result_code = 'result = ' + context = 'result of %s' % name + result_decl = ' %s;' % tp.result.get_c_name(' result', context) + prnt(result_decl) + prnt(' PyObject *pyresult;') + else: + result_decl = None + result_code = '' + # + if len(tp.args) > 1: + rng = range(len(tp.args)) + for i in rng: + prnt(' PyObject *arg%d;' % i) + prnt() + prnt(' if (!PyArg_UnpackTuple(args, "%s", %d, %d, %s))' % ( + name, len(rng), len(rng), + ', '.join(['&arg%d' % i for i in rng]))) + prnt(' return NULL;') + prnt() + # + for i, type in enumerate(tp.args): + self._convert_funcarg_to_c(type, 'arg%d' % i, 'x%d' % i, + 'return NULL') + prnt() + # + prnt(' Py_BEGIN_ALLOW_THREADS') + prnt(' _cffi_restore_errno();') + call_arguments = ['x%d' % i for i in range(len(tp.args))] + call_arguments = ', '.join(call_arguments) + prnt(' { %s%s(%s); }' % (result_code, name, call_arguments)) + prnt(' _cffi_save_errno();') + prnt(' Py_END_ALLOW_THREADS') + prnt() + # + prnt(' (void)self; /* unused */') + if numargs == 0: + prnt(' (void)noarg; /* unused */') + if result_code: + prnt(' pyresult = %s;' % + self._convert_expr_from_c(tp.result, 'result', 'result type')) + for freeline in freelines: + prnt(' ' + freeline) + prnt(' return pyresult;') + else: + for freeline in freelines: + prnt(' ' + freeline) + prnt(' Py_INCREF(Py_None);') + prnt(' return Py_None;') + prnt('}') + # + prnt('#else') # ------------------------------ + # + # the PyPy version: need to replace struct/union arguments with + # pointers, and if the result is a struct/union, insert a first + # arg that is a pointer to the result. We also do that for + # complex args and return type. + def need_indirection(type): + return (isinstance(type, model.StructOrUnion) or + (isinstance(type, model.PrimitiveType) and + type.is_complex_type())) + difference = False + arguments = [] + call_arguments = [] + context = 'argument of %s' % name + for i, type in enumerate(tp.args): + indirection = '' + if need_indirection(type): + indirection = '*' + difference = True + arg = type.get_c_name(' %sx%d' % (indirection, i), context) + arguments.append(arg) + call_arguments.append('%sx%d' % (indirection, i)) + tp_result = tp.result + if need_indirection(tp_result): + context = 'result of %s' % name + arg = tp_result.get_c_name(' *result', context) + arguments.insert(0, arg) + tp_result = model.void_type + result_decl = None + result_code = '*result = ' + difference = True + if difference: + repr_arguments = ', '.join(arguments) + repr_arguments = repr_arguments or 'void' + name_and_arguments = '%s_cffi_f_%s(%s)' % (abi, name, + repr_arguments) + prnt('static %s' % (tp_result.get_c_name(name_and_arguments),)) + prnt('{') + if result_decl: + prnt(result_decl) + call_arguments = ', '.join(call_arguments) + prnt(' { %s%s(%s); }' % (result_code, name, call_arguments)) + if result_decl: + prnt(' return result;') + prnt('}') + else: + prnt('# define _cffi_f_%s _cffi_d_%s' % (name, name)) + # + prnt('#endif') # ------------------------------ + prnt() + + def _generate_cpy_function_ctx(self, tp, name): + if tp.ellipsis and not self.target_is_python: + self._generate_cpy_constant_ctx(tp, name) + return + type_index = self._typesdict[tp.as_raw_function()] + numargs = len(tp.args) + if self.target_is_python: + meth_kind = OP_DLOPEN_FUNC + elif numargs == 0: + meth_kind = OP_CPYTHON_BLTN_N # 'METH_NOARGS' + elif numargs == 1: + meth_kind = OP_CPYTHON_BLTN_O # 'METH_O' + else: + meth_kind = OP_CPYTHON_BLTN_V # 'METH_VARARGS' + self._lsts["global"].append( + GlobalExpr(name, '_cffi_f_%s' % name, + CffiOp(meth_kind, type_index), + size='_cffi_d_%s' % name)) + + # ---------- + # named structs or unions + + def _field_type(self, tp_struct, field_name, tp_field): + if isinstance(tp_field, model.ArrayType): + actual_length = tp_field.length + if actual_length == '...': + ptr_struct_name = tp_struct.get_c_name('*') + actual_length = '_cffi_array_len(((%s)0)->%s)' % ( + ptr_struct_name, field_name) + tp_item = self._field_type(tp_struct, '%s[0]' % field_name, + tp_field.item) + tp_field = model.ArrayType(tp_item, actual_length) + return tp_field + + def _struct_collecttype(self, tp): + self._do_collect_type(tp) + if self.target_is_python: + # also requires nested anon struct/unions in ABI mode, recursively + for fldtype in tp.anonymous_struct_fields(): + self._struct_collecttype(fldtype) + + def _struct_decl(self, tp, cname, approxname): + if tp.fldtypes is None: + return + prnt = self._prnt + checkfuncname = '_cffi_checkfld_%s' % (approxname,) + prnt('_CFFI_UNUSED_FN') + prnt('static void %s(%s *p)' % (checkfuncname, cname)) + prnt('{') + prnt(' /* only to generate compile-time warnings or errors */') + prnt(' (void)p;') + for fname, ftype, fbitsize, fqual in self._enum_fields(tp): + try: + if ftype.is_integer_type() or fbitsize >= 0: + # accept all integers, but complain on float or double + if fname != '': + prnt(" (void)((p->%s) | 0); /* check that '%s.%s' is " + "an integer */" % (fname, cname, fname)) + continue + # only accept exactly the type declared, except that '[]' + # is interpreted as a '*' and so will match any array length. + # (It would also match '*', but that's harder to detect...) + while (isinstance(ftype, model.ArrayType) + and (ftype.length is None or ftype.length == '...')): + ftype = ftype.item + fname = fname + '[0]' + prnt(' { %s = &p->%s; (void)tmp; }' % ( + ftype.get_c_name('*tmp', 'field %r'%fname, quals=fqual), + fname)) + except VerificationError as e: + prnt(' /* %s */' % str(e)) # cannot verify it, ignore + prnt('}') + prnt('struct _cffi_align_%s { char x; %s y; };' % (approxname, cname)) + prnt() + + def _struct_ctx(self, tp, cname, approxname, named_ptr=None): + type_index = self._typesdict[tp] + reason_for_not_expanding = None + flags = [] + if isinstance(tp, model.UnionType): + flags.append("_CFFI_F_UNION") + if tp.fldtypes is None: + flags.append("_CFFI_F_OPAQUE") + reason_for_not_expanding = "opaque" + if (tp not in self.ffi._parser._included_declarations and + (named_ptr is None or + named_ptr not in self.ffi._parser._included_declarations)): + if tp.fldtypes is None: + pass # opaque + elif tp.partial or any(tp.anonymous_struct_fields()): + pass # field layout obtained silently from the C compiler + else: + flags.append("_CFFI_F_CHECK_FIELDS") + if tp.packed: + if tp.packed > 1: + raise NotImplementedError( + "%r is declared with 'pack=%r'; only 0 or 1 are " + "supported in API mode (try to use \"...;\", which " + "does not require a 'pack' declaration)" % + (tp, tp.packed)) + flags.append("_CFFI_F_PACKED") + else: + flags.append("_CFFI_F_EXTERNAL") + reason_for_not_expanding = "external" + flags = '|'.join(flags) or '0' + c_fields = [] + if reason_for_not_expanding is None: + enumfields = list(self._enum_fields(tp)) + for fldname, fldtype, fbitsize, fqual in enumfields: + fldtype = self._field_type(tp, fldname, fldtype) + self._check_not_opaque(fldtype, + "field '%s.%s'" % (tp.name, fldname)) + # cname is None for _add_missing_struct_unions() only + op = OP_NOOP + if fbitsize >= 0: + op = OP_BITFIELD + size = '%d /* bits */' % fbitsize + elif cname is None or ( + isinstance(fldtype, model.ArrayType) and + fldtype.length is None): + size = '(size_t)-1' + else: + size = 'sizeof(((%s)0)->%s)' % ( + tp.get_c_name('*') if named_ptr is None + else named_ptr.name, + fldname) + if cname is None or fbitsize >= 0: + offset = '(size_t)-1' + elif named_ptr is not None: + offset = '(size_t)(((char *)&((%s)4096)->%s) - (char *)4096)' % ( + named_ptr.name, fldname) + else: + offset = 'offsetof(%s, %s)' % (tp.get_c_name(''), fldname) + c_fields.append( + FieldExpr(fldname, offset, size, fbitsize, + CffiOp(op, self._typesdict[fldtype]))) + first_field_index = len(self._lsts["field"]) + self._lsts["field"].extend(c_fields) + # + if cname is None: # unknown name, for _add_missing_struct_unions + size = '(size_t)-2' + align = -2 + comment = "unnamed" + else: + if named_ptr is not None: + size = 'sizeof(*(%s)0)' % (named_ptr.name,) + align = '-1 /* unknown alignment */' + else: + size = 'sizeof(%s)' % (cname,) + align = 'offsetof(struct _cffi_align_%s, y)' % (approxname,) + comment = None + else: + size = '(size_t)-1' + align = -1 + first_field_index = -1 + comment = reason_for_not_expanding + self._lsts["struct_union"].append( + StructUnionExpr(tp.name, type_index, flags, size, align, comment, + first_field_index, c_fields)) + self._seen_struct_unions.add(tp) + + def _check_not_opaque(self, tp, location): + while isinstance(tp, model.ArrayType): + tp = tp.item + if isinstance(tp, model.StructOrUnion) and tp.fldtypes is None: + raise TypeError( + "%s is of an opaque type (not declared in cdef())" % location) + + def _add_missing_struct_unions(self): + # not very nice, but some struct declarations might be missing + # because they don't have any known C name. Check that they are + # not partial (we can't complete or verify them!) and emit them + # anonymously. + lst = list(self._struct_unions.items()) + lst.sort(key=lambda tp_order: tp_order[1]) + for tp, order in lst: + if tp not in self._seen_struct_unions: + if tp.partial: + raise NotImplementedError("internal inconsistency: %r is " + "partial but was not seen at " + "this point" % (tp,)) + if tp.name.startswith('$') and tp.name[1:].isdigit(): + approxname = tp.name[1:] + elif tp.name == '_IO_FILE' and tp.forcename == 'FILE': + approxname = 'FILE' + self._typedef_ctx(tp, 'FILE') + else: + raise NotImplementedError("internal inconsistency: %r" % + (tp,)) + self._struct_ctx(tp, None, approxname) + + def _generate_cpy_struct_collecttype(self, tp, name): + self._struct_collecttype(tp) + _generate_cpy_union_collecttype = _generate_cpy_struct_collecttype + + def _struct_names(self, tp): + cname = tp.get_c_name('') + if ' ' in cname: + return cname, cname.replace(' ', '_') + else: + return cname, '_' + cname + + def _generate_cpy_struct_decl(self, tp, name): + self._struct_decl(tp, *self._struct_names(tp)) + _generate_cpy_union_decl = _generate_cpy_struct_decl + + def _generate_cpy_struct_ctx(self, tp, name): + self._struct_ctx(tp, *self._struct_names(tp)) + _generate_cpy_union_ctx = _generate_cpy_struct_ctx + + # ---------- + # 'anonymous' declarations. These are produced for anonymous structs + # or unions; the 'name' is obtained by a typedef. + + def _generate_cpy_anonymous_collecttype(self, tp, name): + if isinstance(tp, model.EnumType): + self._generate_cpy_enum_collecttype(tp, name) + else: + self._struct_collecttype(tp) + + def _generate_cpy_anonymous_decl(self, tp, name): + if isinstance(tp, model.EnumType): + self._generate_cpy_enum_decl(tp) + else: + self._struct_decl(tp, name, 'typedef_' + name) + + def _generate_cpy_anonymous_ctx(self, tp, name): + if isinstance(tp, model.EnumType): + self._enum_ctx(tp, name) + else: + self._struct_ctx(tp, name, 'typedef_' + name) + + # ---------- + # constants, declared with "static const ..." + + def _generate_cpy_const(self, is_int, name, tp=None, category='const', + check_value=None): + if (category, name) in self._seen_constants: + raise VerificationError( + "duplicate declaration of %s '%s'" % (category, name)) + self._seen_constants.add((category, name)) + # + prnt = self._prnt + funcname = '_cffi_%s_%s' % (category, name) + if is_int: + prnt('static int %s(unsigned long long *o)' % funcname) + prnt('{') + prnt(' int n = (%s) <= 0;' % (name,)) + prnt(' *o = (unsigned long long)((%s) | 0);' + ' /* check that %s is an integer */' % (name, name)) + if check_value is not None: + if check_value > 0: + check_value = '%dU' % (check_value,) + prnt(' if (!_cffi_check_int(*o, n, %s))' % (check_value,)) + prnt(' n |= 2;') + prnt(' return n;') + prnt('}') + else: + assert check_value is None + prnt('static void %s(char *o)' % funcname) + prnt('{') + prnt(' *(%s)o = %s;' % (tp.get_c_name('*'), name)) + prnt('}') + prnt() + + def _generate_cpy_constant_collecttype(self, tp, name): + is_int = tp.is_integer_type() + if not is_int or self.target_is_python: + self._do_collect_type(tp) + + def _generate_cpy_constant_decl(self, tp, name): + is_int = tp.is_integer_type() + self._generate_cpy_const(is_int, name, tp) + + def _generate_cpy_constant_ctx(self, tp, name): + if not self.target_is_python and tp.is_integer_type(): + type_op = CffiOp(OP_CONSTANT_INT, -1) + else: + if self.target_is_python: + const_kind = OP_DLOPEN_CONST + else: + const_kind = OP_CONSTANT + type_index = self._typesdict[tp] + type_op = CffiOp(const_kind, type_index) + self._lsts["global"].append( + GlobalExpr(name, '_cffi_const_%s' % name, type_op)) + + # ---------- + # enums + + def _generate_cpy_enum_collecttype(self, tp, name): + self._do_collect_type(tp) + + def _generate_cpy_enum_decl(self, tp, name=None): + for enumerator in tp.enumerators: + self._generate_cpy_const(True, enumerator) + + def _enum_ctx(self, tp, cname): + type_index = self._typesdict[tp] + type_op = CffiOp(OP_ENUM, -1) + if self.target_is_python: + tp.check_not_partial() + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + self._lsts["global"].append( + GlobalExpr(enumerator, '_cffi_const_%s' % enumerator, type_op, + check_value=enumvalue)) + # + if cname is not None and '$' not in cname and not self.target_is_python: + size = "sizeof(%s)" % cname + signed = "((%s)-1) <= 0" % cname + else: + basetp = tp.build_baseinttype(self.ffi, []) + size = self.ffi.sizeof(basetp) + signed = int(int(self.ffi.cast(basetp, -1)) < 0) + allenums = ",".join(tp.enumerators) + self._lsts["enum"].append( + EnumExpr(tp.name, type_index, size, signed, allenums)) + + def _generate_cpy_enum_ctx(self, tp, name): + self._enum_ctx(tp, tp._get_c_name()) + + # ---------- + # macros: for now only for integers + + def _generate_cpy_macro_collecttype(self, tp, name): + pass + + def _generate_cpy_macro_decl(self, tp, name): + if tp == '...': + check_value = None + else: + check_value = tp # an integer + self._generate_cpy_const(True, name, check_value=check_value) + + def _generate_cpy_macro_ctx(self, tp, name): + if tp == '...': + if self.target_is_python: + raise VerificationError( + "cannot use the syntax '...' in '#define %s ...' when " + "using the ABI mode" % (name,)) + check_value = None + else: + check_value = tp # an integer + type_op = CffiOp(OP_CONSTANT_INT, -1) + self._lsts["global"].append( + GlobalExpr(name, '_cffi_const_%s' % name, type_op, + check_value=check_value)) + + # ---------- + # global variables + + def _global_type(self, tp, global_name): + if isinstance(tp, model.ArrayType): + actual_length = tp.length + if actual_length == '...': + actual_length = '_cffi_array_len(%s)' % (global_name,) + tp_item = self._global_type(tp.item, '%s[0]' % global_name) + tp = model.ArrayType(tp_item, actual_length) + return tp + + def _generate_cpy_variable_collecttype(self, tp, name): + self._do_collect_type(self._global_type(tp, name)) + + def _generate_cpy_variable_decl(self, tp, name): + prnt = self._prnt + tp = self._global_type(tp, name) + if isinstance(tp, model.ArrayType) and tp.length is None: + tp = tp.item + ampersand = '' + else: + ampersand = '&' + # This code assumes that casts from "tp *" to "void *" is a + # no-op, i.e. a function that returns a "tp *" can be called + # as if it returned a "void *". This should be generally true + # on any modern machine. The only exception to that rule (on + # uncommon architectures, and as far as I can tell) might be + # if 'tp' were a function type, but that is not possible here. + # (If 'tp' is a function _pointer_ type, then casts from "fn_t + # **" to "void *" are again no-ops, as far as I can tell.) + decl = '*_cffi_var_%s(void)' % (name,) + prnt('static ' + tp.get_c_name(decl, quals=self._current_quals)) + prnt('{') + prnt(' return %s(%s);' % (ampersand, name)) + prnt('}') + prnt() + + def _generate_cpy_variable_ctx(self, tp, name): + tp = self._global_type(tp, name) + type_index = self._typesdict[tp] + if self.target_is_python: + op = OP_GLOBAL_VAR + else: + op = OP_GLOBAL_VAR_F + self._lsts["global"].append( + GlobalExpr(name, '_cffi_var_%s' % name, CffiOp(op, type_index))) + + # ---------- + # extern "Python" + + def _generate_cpy_extern_python_collecttype(self, tp, name): + assert isinstance(tp, model.FunctionPtrType) + self._do_collect_type(tp) + _generate_cpy_dllexport_python_collecttype = \ + _generate_cpy_extern_python_plus_c_collecttype = \ + _generate_cpy_extern_python_collecttype + + def _extern_python_decl(self, tp, name, tag_and_space): + prnt = self._prnt + if isinstance(tp.result, model.VoidType): + size_of_result = '0' + else: + context = 'result of %s' % name + size_of_result = '(int)sizeof(%s)' % ( + tp.result.get_c_name('', context),) + prnt('static struct _cffi_externpy_s _cffi_externpy__%s =' % name) + prnt(' { "%s.%s", %s, 0, 0 };' % ( + self.module_name, name, size_of_result)) + prnt() + # + arguments = [] + context = 'argument of %s' % name + for i, type in enumerate(tp.args): + arg = type.get_c_name(' a%d' % i, context) + arguments.append(arg) + # + repr_arguments = ', '.join(arguments) + repr_arguments = repr_arguments or 'void' + name_and_arguments = '%s(%s)' % (name, repr_arguments) + if tp.abi == "__stdcall": + name_and_arguments = '_cffi_stdcall ' + name_and_arguments + # + def may_need_128_bits(tp): + return (isinstance(tp, model.PrimitiveType) and + tp.name == 'long double') + # + size_of_a = max(len(tp.args)*8, 8) + if may_need_128_bits(tp.result): + size_of_a = max(size_of_a, 16) + if isinstance(tp.result, model.StructOrUnion): + size_of_a = 'sizeof(%s) > %d ? sizeof(%s) : %d' % ( + tp.result.get_c_name(''), size_of_a, + tp.result.get_c_name(''), size_of_a) + prnt('%s%s' % (tag_and_space, tp.result.get_c_name(name_and_arguments))) + prnt('{') + prnt(' char a[%s];' % size_of_a) + prnt(' char *p = a;') + for i, type in enumerate(tp.args): + arg = 'a%d' % i + if (isinstance(type, model.StructOrUnion) or + may_need_128_bits(type)): + arg = '&' + arg + type = model.PointerType(type) + prnt(' *(%s)(p + %d) = %s;' % (type.get_c_name('*'), i*8, arg)) + prnt(' _cffi_call_python(&_cffi_externpy__%s, p);' % name) + if not isinstance(tp.result, model.VoidType): + prnt(' return *(%s)p;' % (tp.result.get_c_name('*'),)) + prnt('}') + prnt() + self._num_externpy += 1 + + def _generate_cpy_extern_python_decl(self, tp, name): + self._extern_python_decl(tp, name, 'static ') + + def _generate_cpy_dllexport_python_decl(self, tp, name): + self._extern_python_decl(tp, name, 'CFFI_DLLEXPORT ') + + def _generate_cpy_extern_python_plus_c_decl(self, tp, name): + self._extern_python_decl(tp, name, '') + + def _generate_cpy_extern_python_ctx(self, tp, name): + if self.target_is_python: + raise VerificationError( + "cannot use 'extern \"Python\"' in the ABI mode") + if tp.ellipsis: + raise NotImplementedError("a vararg function is extern \"Python\"") + type_index = self._typesdict[tp] + type_op = CffiOp(OP_EXTERN_PYTHON, type_index) + self._lsts["global"].append( + GlobalExpr(name, '&_cffi_externpy__%s' % name, type_op, name)) + + _generate_cpy_dllexport_python_ctx = \ + _generate_cpy_extern_python_plus_c_ctx = \ + _generate_cpy_extern_python_ctx + + def _print_string_literal_in_array(self, s): + prnt = self._prnt + prnt('// # NB. this is not a string because of a size limit in MSVC') + if not isinstance(s, bytes): # unicode + s = s.encode('utf-8') # -> bytes + else: + s.decode('utf-8') # got bytes, check for valid utf-8 + try: + s.decode('ascii') + except UnicodeDecodeError: + s = b'# -*- encoding: utf8 -*-\n' + s + for line in s.splitlines(True): + comment = line + if type('//') is bytes: # python2 + line = map(ord, line) # make a list of integers + else: # python3 + # type(line) is bytes, which enumerates like a list of integers + comment = ascii(comment)[1:-1] + prnt(('// ' + comment).rstrip()) + printed_line = '' + for c in line: + if len(printed_line) >= 76: + prnt(printed_line) + printed_line = '' + printed_line += '%d,' % (c,) + prnt(printed_line) + + # ---------- + # emitting the opcodes for individual types + + def _emit_bytecode_VoidType(self, tp, index): + self.cffi_types[index] = CffiOp(OP_PRIMITIVE, PRIM_VOID) + + def _emit_bytecode_PrimitiveType(self, tp, index): + prim_index = PRIMITIVE_TO_INDEX[tp.name] + self.cffi_types[index] = CffiOp(OP_PRIMITIVE, prim_index) + + def _emit_bytecode_UnknownIntegerType(self, tp, index): + s = ('_cffi_prim_int(sizeof(%s), (\n' + ' ((%s)-1) | 0 /* check that %s is an integer type */\n' + ' ) <= 0)' % (tp.name, tp.name, tp.name)) + self.cffi_types[index] = CffiOp(OP_PRIMITIVE, s) + + def _emit_bytecode_UnknownFloatType(self, tp, index): + s = ('_cffi_prim_float(sizeof(%s) *\n' + ' (((%s)1) / 2) * 2 /* integer => 0, float => 1 */\n' + ' )' % (tp.name, tp.name)) + self.cffi_types[index] = CffiOp(OP_PRIMITIVE, s) + + def _emit_bytecode_RawFunctionType(self, tp, index): + self.cffi_types[index] = CffiOp(OP_FUNCTION, self._typesdict[tp.result]) + index += 1 + for tp1 in tp.args: + realindex = self._typesdict[tp1] + if index != realindex: + if isinstance(tp1, model.PrimitiveType): + self._emit_bytecode_PrimitiveType(tp1, index) + else: + self.cffi_types[index] = CffiOp(OP_NOOP, realindex) + index += 1 + flags = int(tp.ellipsis) + if tp.abi is not None: + if tp.abi == '__stdcall': + flags |= 2 + else: + raise NotImplementedError("abi=%r" % (tp.abi,)) + self.cffi_types[index] = CffiOp(OP_FUNCTION_END, flags) + + def _emit_bytecode_PointerType(self, tp, index): + self.cffi_types[index] = CffiOp(OP_POINTER, self._typesdict[tp.totype]) + + _emit_bytecode_ConstPointerType = _emit_bytecode_PointerType + _emit_bytecode_NamedPointerType = _emit_bytecode_PointerType + + def _emit_bytecode_FunctionPtrType(self, tp, index): + raw = tp.as_raw_function() + self.cffi_types[index] = CffiOp(OP_POINTER, self._typesdict[raw]) + + def _emit_bytecode_ArrayType(self, tp, index): + item_index = self._typesdict[tp.item] + if tp.length is None: + self.cffi_types[index] = CffiOp(OP_OPEN_ARRAY, item_index) + elif tp.length == '...': + raise VerificationError( + "type %s badly placed: the '...' array length can only be " + "used on global arrays or on fields of structures" % ( + str(tp).replace('/*...*/', '...'),)) + else: + assert self.cffi_types[index + 1] == 'LEN' + self.cffi_types[index] = CffiOp(OP_ARRAY, item_index) + self.cffi_types[index + 1] = CffiOp(None, str(tp.length)) + + def _emit_bytecode_StructType(self, tp, index): + struct_index = self._struct_unions[tp] + self.cffi_types[index] = CffiOp(OP_STRUCT_UNION, struct_index) + _emit_bytecode_UnionType = _emit_bytecode_StructType + + def _emit_bytecode_EnumType(self, tp, index): + enum_index = self._enums[tp] + self.cffi_types[index] = CffiOp(OP_ENUM, enum_index) + + +if sys.version_info >= (3,): + NativeIO = io.StringIO +else: + class NativeIO(io.BytesIO): + def write(self, s): + if isinstance(s, unicode): + s = s.encode('ascii') + super(NativeIO, self).write(s) + +def _is_file_like(maybefile): + # compare to xml.etree.ElementTree._get_writer + return hasattr(maybefile, 'write') + +def _make_c_or_py_source(ffi, module_name, preamble, target_file, verbose): + if verbose: + print("generating %s" % (target_file,)) + recompiler = Recompiler(ffi, module_name, + target_is_python=(preamble is None)) + recompiler.collect_type_table() + recompiler.collect_step_tables() + if _is_file_like(target_file): + recompiler.write_source_to_f(target_file, preamble) + return True + f = NativeIO() + recompiler.write_source_to_f(f, preamble) + output = f.getvalue() + try: + with open(target_file, 'r') as f1: + if f1.read(len(output) + 1) != output: + raise IOError + if verbose: + print("(already up-to-date)") + return False # already up-to-date + except IOError: + tmp_file = '%s.~%d' % (target_file, os.getpid()) + with open(tmp_file, 'w') as f1: + f1.write(output) + try: + os.rename(tmp_file, target_file) + except OSError: + os.unlink(target_file) + os.rename(tmp_file, target_file) + return True + +def make_c_source(ffi, module_name, preamble, target_c_file, verbose=False): + assert preamble is not None + return _make_c_or_py_source(ffi, module_name, preamble, target_c_file, + verbose) + +def make_py_source(ffi, module_name, target_py_file, verbose=False): + return _make_c_or_py_source(ffi, module_name, None, target_py_file, + verbose) + +def _modname_to_file(outputdir, modname, extension): + parts = modname.split('.') + try: + os.makedirs(os.path.join(outputdir, *parts[:-1])) + except OSError: + pass + parts[-1] += extension + return os.path.join(outputdir, *parts), parts + + +# Aaargh. Distutils is not tested at all for the purpose of compiling +# DLLs that are not extension modules. Here are some hacks to work +# around that, in the _patch_for_*() functions... + +def _patch_meth(patchlist, cls, name, new_meth): + old = getattr(cls, name) + patchlist.append((cls, name, old)) + setattr(cls, name, new_meth) + return old + +def _unpatch_meths(patchlist): + for cls, name, old_meth in reversed(patchlist): + setattr(cls, name, old_meth) + +def _patch_for_embedding(patchlist): + if sys.platform == 'win32': + # we must not remove the manifest when building for embedding! + # FUTURE: this module was removed in setuptools 74; this is likely dead code and should be removed, + # since the toolchain it supports (VS2005-2008) is also long dead. + from cffi._shimmed_dist_utils import MSVCCompiler + if MSVCCompiler is not None: + _patch_meth(patchlist, MSVCCompiler, '_remove_visual_c_ref', + lambda self, manifest_file: manifest_file) + + if sys.platform == 'darwin': + # we must not make a '-bundle', but a '-dynamiclib' instead + from cffi._shimmed_dist_utils import CCompiler + def my_link_shared_object(self, *args, **kwds): + if '-bundle' in self.linker_so: + self.linker_so = list(self.linker_so) + i = self.linker_so.index('-bundle') + self.linker_so[i] = '-dynamiclib' + return old_link_shared_object(self, *args, **kwds) + old_link_shared_object = _patch_meth(patchlist, CCompiler, + 'link_shared_object', + my_link_shared_object) + +def _patch_for_target(patchlist, target): + from cffi._shimmed_dist_utils import build_ext + # if 'target' is different from '*', we need to patch some internal + # method to just return this 'target' value, instead of having it + # built from module_name + if target.endswith('.*'): + target = target[:-2] + if sys.platform == 'win32': + target += '.dll' + elif sys.platform == 'darwin': + target += '.dylib' + else: + target += '.so' + _patch_meth(patchlist, build_ext, 'get_ext_filename', + lambda self, ext_name: target) + + +def recompile(ffi, module_name, preamble, tmpdir='.', call_c_compiler=True, + c_file=None, source_extension='.c', extradir=None, + compiler_verbose=1, target=None, debug=None, + uses_ffiplatform=True, **kwds): + if not isinstance(module_name, str): + module_name = module_name.encode('ascii') + if ffi._windows_unicode: + ffi._apply_windows_unicode(kwds) + if preamble is not None: + if call_c_compiler and _is_file_like(c_file): + raise TypeError("Writing to file-like objects is not supported " + "with call_c_compiler=True") + embedding = (ffi._embedding is not None) + if embedding: + ffi._apply_embedding_fix(kwds) + if c_file is None: + c_file, parts = _modname_to_file(tmpdir, module_name, + source_extension) + if extradir: + parts = [extradir] + parts + ext_c_file = os.path.join(*parts) + else: + ext_c_file = c_file + # + if target is None: + if embedding: + target = '%s.*' % module_name + else: + target = '*' + # + if uses_ffiplatform: + ext = ffiplatform.get_extension(ext_c_file, module_name, **kwds) + else: + ext = None + updated = make_c_source(ffi, module_name, preamble, c_file, + verbose=compiler_verbose) + if call_c_compiler: + patchlist = [] + cwd = os.getcwd() + try: + if embedding: + _patch_for_embedding(patchlist) + if target != '*': + _patch_for_target(patchlist, target) + if compiler_verbose: + if tmpdir == '.': + msg = 'the current directory is' + else: + msg = 'setting the current directory to' + print('%s %r' % (msg, os.path.abspath(tmpdir))) + os.chdir(tmpdir) + outputfilename = ffiplatform.compile('.', ext, + compiler_verbose, debug) + finally: + os.chdir(cwd) + _unpatch_meths(patchlist) + return outputfilename + else: + return ext, updated + else: + if c_file is None: + c_file, _ = _modname_to_file(tmpdir, module_name, '.py') + updated = make_py_source(ffi, module_name, c_file, + verbose=compiler_verbose) + if call_c_compiler: + return c_file + else: + return None, updated + diff --git a/intelliaide/vendor/cffi/setuptools_ext.py b/intelliaide/vendor/cffi/setuptools_ext.py new file mode 100644 index 0000000..5cdd246 --- /dev/null +++ b/intelliaide/vendor/cffi/setuptools_ext.py @@ -0,0 +1,229 @@ +import os +import sys +import sysconfig + +try: + basestring +except NameError: + # Python 3.x + basestring = str + +def error(msg): + from cffi._shimmed_dist_utils import DistutilsSetupError + raise DistutilsSetupError(msg) + + +def execfile(filename, glob): + # We use execfile() (here rewritten for Python 3) instead of + # __import__() to load the build script. The problem with + # a normal import is that in some packages, the intermediate + # __init__.py files may already try to import the file that + # we are generating. + with open(filename) as f: + src = f.read() + src += '\n' # Python 2.6 compatibility + code = compile(src, filename, 'exec') + exec(code, glob, glob) + + +def add_cffi_module(dist, mod_spec): + from cffi.api import FFI + + if not isinstance(mod_spec, basestring): + error("argument to 'cffi_modules=...' must be a str or a list of str," + " not %r" % (type(mod_spec).__name__,)) + mod_spec = str(mod_spec) + try: + build_file_name, ffi_var_name = mod_spec.split(':') + except ValueError: + error("%r must be of the form 'path/build.py:ffi_variable'" % + (mod_spec,)) + if not os.path.exists(build_file_name): + ext = '' + rewritten = build_file_name.replace('.', '/') + '.py' + if os.path.exists(rewritten): + ext = ' (rewrite cffi_modules to [%r])' % ( + rewritten + ':' + ffi_var_name,) + error("%r does not name an existing file%s" % (build_file_name, ext)) + + mod_vars = {'__name__': '__cffi__', '__file__': build_file_name} + execfile(build_file_name, mod_vars) + + try: + ffi = mod_vars[ffi_var_name] + except KeyError: + error("%r: object %r not found in module" % (mod_spec, + ffi_var_name)) + if not isinstance(ffi, FFI): + ffi = ffi() # maybe it's a function instead of directly an ffi + if not isinstance(ffi, FFI): + error("%r is not an FFI instance (got %r)" % (mod_spec, + type(ffi).__name__)) + if not hasattr(ffi, '_assigned_source'): + error("%r: the set_source() method was not called" % (mod_spec,)) + module_name, source, source_extension, kwds = ffi._assigned_source + if ffi._windows_unicode: + kwds = kwds.copy() + ffi._apply_windows_unicode(kwds) + + if source is None: + _add_py_module(dist, ffi, module_name) + else: + _add_c_module(dist, ffi, module_name, source, source_extension, kwds) + +def _set_py_limited_api(Extension, kwds): + """ + Add py_limited_api to kwds if setuptools >= 26 is in use. + Do not alter the setting if it already exists. + Setuptools takes care of ignoring the flag on Python 2 and PyPy. + + CPython itself should ignore the flag in a debugging version + (by not listing .abi3.so in the extensions it supports), but + it doesn't so far, creating troubles. That's why we check + for "not hasattr(sys, 'gettotalrefcount')" (the 2.7 compatible equivalent + of 'd' not in sys.abiflags). (http://bugs.python.org/issue28401) + + On Windows, with CPython <= 3.4, it's better not to use py_limited_api + because virtualenv *still* doesn't copy PYTHON3.DLL on these versions. + Recently (2020) we started shipping only >= 3.5 wheels, though. So + we'll give it another try and set py_limited_api on Windows >= 3.5. + """ + from cffi._shimmed_dist_utils import log + from cffi import recompiler + + if ('py_limited_api' not in kwds and not hasattr(sys, 'gettotalrefcount') + and recompiler.USE_LIMITED_API): + import setuptools + try: + setuptools_major_version = int(setuptools.__version__.partition('.')[0]) + if setuptools_major_version >= 26: + kwds['py_limited_api'] = True + except ValueError: # certain development versions of setuptools + # If we don't know the version number of setuptools, we + # try to set 'py_limited_api' anyway. At worst, we get a + # warning. + kwds['py_limited_api'] = True + + if sysconfig.get_config_var("Py_GIL_DISABLED"): + if kwds.get('py_limited_api'): + log.info("Ignoring py_limited_api=True for free-threaded build.") + + kwds['py_limited_api'] = False + + if kwds.get('py_limited_api') is False: + # avoid setting Py_LIMITED_API if py_limited_api=False + # which _cffi_include.h does unless _CFFI_NO_LIMITED_API is defined + kwds.setdefault("define_macros", []).append(("_CFFI_NO_LIMITED_API", None)) + return kwds + +def _add_c_module(dist, ffi, module_name, source, source_extension, kwds): + # We are a setuptools extension. Need this build_ext for py_limited_api. + from setuptools.command.build_ext import build_ext + from cffi._shimmed_dist_utils import Extension, log, mkpath + from cffi import recompiler + + allsources = ['$PLACEHOLDER'] + allsources.extend(kwds.pop('sources', [])) + kwds = _set_py_limited_api(Extension, kwds) + ext = Extension(name=module_name, sources=allsources, **kwds) + + def make_mod(tmpdir, pre_run=None): + c_file = os.path.join(tmpdir, module_name + source_extension) + log.info("generating cffi module %r" % c_file) + mkpath(tmpdir) + # a setuptools-only, API-only hook: called with the "ext" and "ffi" + # arguments just before we turn the ffi into C code. To use it, + # subclass the 'distutils.command.build_ext.build_ext' class and + # add a method 'def pre_run(self, ext, ffi)'. + if pre_run is not None: + pre_run(ext, ffi) + updated = recompiler.make_c_source(ffi, module_name, source, c_file) + if not updated: + log.info("already up-to-date") + return c_file + + if dist.ext_modules is None: + dist.ext_modules = [] + dist.ext_modules.append(ext) + + base_class = dist.cmdclass.get('build_ext', build_ext) + class build_ext_make_mod(base_class): + def run(self): + if ext.sources[0] == '$PLACEHOLDER': + pre_run = getattr(self, 'pre_run', None) + ext.sources[0] = make_mod(self.build_temp, pre_run) + base_class.run(self) + dist.cmdclass['build_ext'] = build_ext_make_mod + # NB. multiple runs here will create multiple 'build_ext_make_mod' + # classes. Even in this case the 'build_ext' command should be + # run once; but just in case, the logic above does nothing if + # called again. + + +def _add_py_module(dist, ffi, module_name): + from setuptools.command.build_py import build_py + from setuptools.command.build_ext import build_ext + from cffi._shimmed_dist_utils import log, mkpath + from cffi import recompiler + + def generate_mod(py_file): + log.info("generating cffi module %r" % py_file) + mkpath(os.path.dirname(py_file)) + updated = recompiler.make_py_source(ffi, module_name, py_file) + if not updated: + log.info("already up-to-date") + + base_class = dist.cmdclass.get('build_py', build_py) + class build_py_make_mod(base_class): + def run(self): + base_class.run(self) + module_path = module_name.split('.') + module_path[-1] += '.py' + generate_mod(os.path.join(self.build_lib, *module_path)) + def get_source_files(self): + # This is called from 'setup.py sdist' only. Exclude + # the generate .py module in this case. + saved_py_modules = self.py_modules + try: + if saved_py_modules: + self.py_modules = [m for m in saved_py_modules + if m != module_name] + return base_class.get_source_files(self) + finally: + self.py_modules = saved_py_modules + dist.cmdclass['build_py'] = build_py_make_mod + + # distutils and setuptools have no notion I could find of a + # generated python module. If we don't add module_name to + # dist.py_modules, then things mostly work but there are some + # combination of options (--root and --record) that will miss + # the module. So we add it here, which gives a few apparently + # harmless warnings about not finding the file outside the + # build directory. + # Then we need to hack more in get_source_files(); see above. + if dist.py_modules is None: + dist.py_modules = [] + dist.py_modules.append(module_name) + + # the following is only for "build_ext -i" + base_class_2 = dist.cmdclass.get('build_ext', build_ext) + class build_ext_make_mod(base_class_2): + def run(self): + base_class_2.run(self) + if self.inplace: + # from get_ext_fullpath() in distutils/command/build_ext.py + module_path = module_name.split('.') + package = '.'.join(module_path[:-1]) + build_py = self.get_finalized_command('build_py') + package_dir = build_py.get_package_dir(package) + file_name = module_path[-1] + '.py' + generate_mod(os.path.join(package_dir, file_name)) + dist.cmdclass['build_ext'] = build_ext_make_mod + +def cffi_modules(dist, attr, value): + assert attr == 'cffi_modules' + if isinstance(value, basestring): + value = [value] + + for cffi_module in value: + add_cffi_module(dist, cffi_module) diff --git a/intelliaide/vendor/cffi/vengine_cpy.py b/intelliaide/vendor/cffi/vengine_cpy.py new file mode 100644 index 0000000..02e6a47 --- /dev/null +++ b/intelliaide/vendor/cffi/vengine_cpy.py @@ -0,0 +1,1087 @@ +# +# DEPRECATED: implementation for ffi.verify() +# +import sys +from . import model +from .error import VerificationError +from . import _imp_emulation as imp + + +class VCPythonEngine(object): + _class_key = 'x' + _gen_python_module = True + + def __init__(self, verifier): + self.verifier = verifier + self.ffi = verifier.ffi + self._struct_pending_verification = {} + self._types_of_builtin_functions = {} + + def patch_extension_kwds(self, kwds): + pass + + def find_module(self, module_name, path, so_suffixes): + try: + f, filename, descr = imp.find_module(module_name, path) + except ImportError: + return None + if f is not None: + f.close() + # Note that after a setuptools installation, there are both .py + # and .so files with the same basename. The code here relies on + # imp.find_module() locating the .so in priority. + if descr[0] not in so_suffixes: + return None + return filename + + def collect_types(self): + self._typesdict = {} + self._generate("collecttype") + + def _prnt(self, what=''): + self._f.write(what + '\n') + + def _gettypenum(self, type): + # a KeyError here is a bug. please report it! :-) + return self._typesdict[type] + + def _do_collect_type(self, tp): + if ((not isinstance(tp, model.PrimitiveType) + or tp.name == 'long double') + and tp not in self._typesdict): + num = len(self._typesdict) + self._typesdict[tp] = num + + def write_source_to_f(self): + self.collect_types() + # + # The new module will have a _cffi_setup() function that receives + # objects from the ffi world, and that calls some setup code in + # the module. This setup code is split in several independent + # functions, e.g. one per constant. The functions are "chained" + # by ending in a tail call to each other. + # + # This is further split in two chained lists, depending on if we + # can do it at import-time or if we must wait for _cffi_setup() to + # provide us with the objects. This is needed because we + # need the values of the enum constants in order to build the + # that we may have to pass to _cffi_setup(). + # + # The following two 'chained_list_constants' items contains + # the head of these two chained lists, as a string that gives the + # call to do, if any. + self._chained_list_constants = ['((void)lib,0)', '((void)lib,0)'] + # + prnt = self._prnt + # first paste some standard set of lines that are mostly '#define' + prnt(cffimod_header) + prnt() + # then paste the C source given by the user, verbatim. + prnt(self.verifier.preamble) + prnt() + # + # call generate_cpy_xxx_decl(), for every xxx found from + # ffi._parser._declarations. This generates all the functions. + self._generate("decl") + # + # implement the function _cffi_setup_custom() as calling the + # head of the chained list. + self._generate_setup_custom() + prnt() + # + # produce the method table, including the entries for the + # generated Python->C function wrappers, which are done + # by generate_cpy_function_method(). + prnt('static PyMethodDef _cffi_methods[] = {') + self._generate("method") + prnt(' {"_cffi_setup", _cffi_setup, METH_VARARGS, NULL},') + prnt(' {NULL, NULL, 0, NULL} /* Sentinel */') + prnt('};') + prnt() + # + # standard init. + modname = self.verifier.get_module_name() + constants = self._chained_list_constants[False] + prnt('#if PY_MAJOR_VERSION >= 3') + prnt() + prnt('static struct PyModuleDef _cffi_module_def = {') + prnt(' PyModuleDef_HEAD_INIT,') + prnt(' "%s",' % modname) + prnt(' NULL,') + prnt(' -1,') + prnt(' _cffi_methods,') + prnt(' NULL, NULL, NULL, NULL') + prnt('};') + prnt() + prnt('PyMODINIT_FUNC') + prnt('PyInit_%s(void)' % modname) + prnt('{') + prnt(' PyObject *lib;') + prnt(' lib = PyModule_Create(&_cffi_module_def);') + prnt(' if (lib == NULL)') + prnt(' return NULL;') + prnt(' if (%s < 0 || _cffi_init() < 0) {' % (constants,)) + prnt(' Py_DECREF(lib);') + prnt(' return NULL;') + prnt(' }') + prnt('#if Py_GIL_DISABLED') + prnt(' PyUnstable_Module_SetGIL(lib, Py_MOD_GIL_NOT_USED);') + prnt('#endif') + prnt(' return lib;') + prnt('}') + prnt() + prnt('#else') + prnt() + prnt('PyMODINIT_FUNC') + prnt('init%s(void)' % modname) + prnt('{') + prnt(' PyObject *lib;') + prnt(' lib = Py_InitModule("%s", _cffi_methods);' % modname) + prnt(' if (lib == NULL)') + prnt(' return;') + prnt(' if (%s < 0 || _cffi_init() < 0)' % (constants,)) + prnt(' return;') + prnt(' return;') + prnt('}') + prnt() + prnt('#endif') + + def load_library(self, flags=None): + # XXX review all usages of 'self' here! + # import it as a new extension module + imp.acquire_lock() + try: + if hasattr(sys, "getdlopenflags"): + previous_flags = sys.getdlopenflags() + try: + if hasattr(sys, "setdlopenflags") and flags is not None: + sys.setdlopenflags(flags) + module = imp.load_dynamic(self.verifier.get_module_name(), + self.verifier.modulefilename) + except ImportError as e: + error = "importing %r: %s" % (self.verifier.modulefilename, e) + raise VerificationError(error) + finally: + if hasattr(sys, "setdlopenflags"): + sys.setdlopenflags(previous_flags) + finally: + imp.release_lock() + # + # call loading_cpy_struct() to get the struct layout inferred by + # the C compiler + self._load(module, 'loading') + # + # the C code will need the objects. Collect them in + # order in a list. + revmapping = dict([(value, key) + for (key, value) in self._typesdict.items()]) + lst = [revmapping[i] for i in range(len(revmapping))] + lst = list(map(self.ffi._get_cached_btype, lst)) + # + # build the FFILibrary class and instance and call _cffi_setup(). + # this will set up some fields like '_cffi_types', and only then + # it will invoke the chained list of functions that will really + # build (notably) the constant objects, as if they are + # pointers, and store them as attributes on the 'library' object. + class FFILibrary(object): + _cffi_python_module = module + _cffi_ffi = self.ffi + _cffi_dir = [] + def __dir__(self): + return FFILibrary._cffi_dir + list(self.__dict__) + library = FFILibrary() + if module._cffi_setup(lst, VerificationError, library): + import warnings + warnings.warn("reimporting %r might overwrite older definitions" + % (self.verifier.get_module_name())) + # + # finally, call the loaded_cpy_xxx() functions. This will perform + # the final adjustments, like copying the Python->C wrapper + # functions from the module to the 'library' object, and setting + # up the FFILibrary class with properties for the global C variables. + self._load(module, 'loaded', library=library) + module._cffi_original_ffi = self.ffi + module._cffi_types_of_builtin_funcs = self._types_of_builtin_functions + return library + + def _get_declarations(self): + lst = [(key, tp) for (key, (tp, qual)) in + self.ffi._parser._declarations.items()] + lst.sort() + return lst + + def _generate(self, step_name): + for name, tp in self._get_declarations(): + kind, realname = name.split(' ', 1) + try: + method = getattr(self, '_generate_cpy_%s_%s' % (kind, + step_name)) + except AttributeError: + raise VerificationError( + "not implemented in verify(): %r" % name) + try: + method(tp, realname) + except Exception as e: + model.attach_exception_info(e, name) + raise + + def _load(self, module, step_name, **kwds): + for name, tp in self._get_declarations(): + kind, realname = name.split(' ', 1) + method = getattr(self, '_%s_cpy_%s' % (step_name, kind)) + try: + method(tp, realname, module, **kwds) + except Exception as e: + model.attach_exception_info(e, name) + raise + + def _generate_nothing(self, tp, name): + pass + + def _loaded_noop(self, tp, name, module, **kwds): + pass + + # ---------- + + def _convert_funcarg_to_c(self, tp, fromvar, tovar, errcode): + extraarg = '' + if isinstance(tp, model.PrimitiveType): + if tp.is_integer_type() and tp.name != '_Bool': + converter = '_cffi_to_c_int' + extraarg = ', %s' % tp.name + elif tp.is_complex_type(): + raise VerificationError( + "not implemented in verify(): complex types") + else: + converter = '(%s)_cffi_to_c_%s' % (tp.get_c_name(''), + tp.name.replace(' ', '_')) + errvalue = '-1' + # + elif isinstance(tp, model.PointerType): + self._convert_funcarg_to_c_ptr_or_array(tp, fromvar, + tovar, errcode) + return + # + elif isinstance(tp, (model.StructOrUnion, model.EnumType)): + # a struct (not a struct pointer) as a function argument + self._prnt(' if (_cffi_to_c((char *)&%s, _cffi_type(%d), %s) < 0)' + % (tovar, self._gettypenum(tp), fromvar)) + self._prnt(' %s;' % errcode) + return + # + elif isinstance(tp, model.FunctionPtrType): + converter = '(%s)_cffi_to_c_pointer' % tp.get_c_name('') + extraarg = ', _cffi_type(%d)' % self._gettypenum(tp) + errvalue = 'NULL' + # + else: + raise NotImplementedError(tp) + # + self._prnt(' %s = %s(%s%s);' % (tovar, converter, fromvar, extraarg)) + self._prnt(' if (%s == (%s)%s && PyErr_Occurred())' % ( + tovar, tp.get_c_name(''), errvalue)) + self._prnt(' %s;' % errcode) + + def _extra_local_variables(self, tp, localvars, freelines): + if isinstance(tp, model.PointerType): + localvars.add('Py_ssize_t datasize') + localvars.add('struct _cffi_freeme_s *large_args_free = NULL') + freelines.add('if (large_args_free != NULL)' + ' _cffi_free_array_arguments(large_args_free);') + + def _convert_funcarg_to_c_ptr_or_array(self, tp, fromvar, tovar, errcode): + self._prnt(' datasize = _cffi_prepare_pointer_call_argument(') + self._prnt(' _cffi_type(%d), %s, (char **)&%s);' % ( + self._gettypenum(tp), fromvar, tovar)) + self._prnt(' if (datasize != 0) {') + self._prnt(' %s = ((size_t)datasize) <= 640 ? ' + 'alloca((size_t)datasize) : NULL;' % (tovar,)) + self._prnt(' if (_cffi_convert_array_argument(_cffi_type(%d), %s, ' + '(char **)&%s,' % (self._gettypenum(tp), fromvar, tovar)) + self._prnt(' datasize, &large_args_free) < 0)') + self._prnt(' %s;' % errcode) + self._prnt(' }') + + def _convert_expr_from_c(self, tp, var, context): + if isinstance(tp, model.PrimitiveType): + if tp.is_integer_type() and tp.name != '_Bool': + return '_cffi_from_c_int(%s, %s)' % (var, tp.name) + elif tp.name != 'long double': + return '_cffi_from_c_%s(%s)' % (tp.name.replace(' ', '_'), var) + else: + return '_cffi_from_c_deref((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, (model.PointerType, model.FunctionPtrType)): + return '_cffi_from_c_pointer((char *)%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, model.ArrayType): + return '_cffi_from_c_pointer((char *)%s, _cffi_type(%d))' % ( + var, self._gettypenum(model.PointerType(tp.item))) + elif isinstance(tp, model.StructOrUnion): + if tp.fldnames is None: + raise TypeError("'%s' is used as %s, but is opaque" % ( + tp._get_c_name(), context)) + return '_cffi_from_c_struct((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, model.EnumType): + return '_cffi_from_c_deref((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + else: + raise NotImplementedError(tp) + + # ---------- + # typedefs: generates no code so far + + _generate_cpy_typedef_collecttype = _generate_nothing + _generate_cpy_typedef_decl = _generate_nothing + _generate_cpy_typedef_method = _generate_nothing + _loading_cpy_typedef = _loaded_noop + _loaded_cpy_typedef = _loaded_noop + + # ---------- + # function declarations + + def _generate_cpy_function_collecttype(self, tp, name): + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + self._do_collect_type(tp) + else: + # don't call _do_collect_type(tp) in this common case, + # otherwise test_autofilled_struct_as_argument fails + for type in tp.args: + self._do_collect_type(type) + self._do_collect_type(tp.result) + + def _generate_cpy_function_decl(self, tp, name): + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + # cannot support vararg functions better than this: check for its + # exact type (including the fixed arguments), and build it as a + # constant function pointer (no CPython wrapper) + self._generate_cpy_const(False, name, tp) + return + prnt = self._prnt + numargs = len(tp.args) + if numargs == 0: + argname = 'noarg' + elif numargs == 1: + argname = 'arg0' + else: + argname = 'args' + prnt('static PyObject *') + prnt('_cffi_f_%s(PyObject *self, PyObject *%s)' % (name, argname)) + prnt('{') + # + context = 'argument of %s' % name + for i, type in enumerate(tp.args): + prnt(' %s;' % type.get_c_name(' x%d' % i, context)) + # + localvars = set() + freelines = set() + for type in tp.args: + self._extra_local_variables(type, localvars, freelines) + for decl in sorted(localvars): + prnt(' %s;' % (decl,)) + # + if not isinstance(tp.result, model.VoidType): + result_code = 'result = ' + context = 'result of %s' % name + prnt(' %s;' % tp.result.get_c_name(' result', context)) + prnt(' PyObject *pyresult;') + else: + result_code = '' + # + if len(tp.args) > 1: + rng = range(len(tp.args)) + for i in rng: + prnt(' PyObject *arg%d;' % i) + prnt() + prnt(' if (!PyArg_ParseTuple(args, "%s:%s", %s))' % ( + 'O' * numargs, name, ', '.join(['&arg%d' % i for i in rng]))) + prnt(' return NULL;') + prnt() + # + for i, type in enumerate(tp.args): + self._convert_funcarg_to_c(type, 'arg%d' % i, 'x%d' % i, + 'return NULL') + prnt() + # + prnt(' Py_BEGIN_ALLOW_THREADS') + prnt(' _cffi_restore_errno();') + prnt(' { %s%s(%s); }' % ( + result_code, name, + ', '.join(['x%d' % i for i in range(len(tp.args))]))) + prnt(' _cffi_save_errno();') + prnt(' Py_END_ALLOW_THREADS') + prnt() + # + prnt(' (void)self; /* unused */') + if numargs == 0: + prnt(' (void)noarg; /* unused */') + if result_code: + prnt(' pyresult = %s;' % + self._convert_expr_from_c(tp.result, 'result', 'result type')) + for freeline in freelines: + prnt(' ' + freeline) + prnt(' return pyresult;') + else: + for freeline in freelines: + prnt(' ' + freeline) + prnt(' Py_INCREF(Py_None);') + prnt(' return Py_None;') + prnt('}') + prnt() + + def _generate_cpy_function_method(self, tp, name): + if tp.ellipsis: + return + numargs = len(tp.args) + if numargs == 0: + meth = 'METH_NOARGS' + elif numargs == 1: + meth = 'METH_O' + else: + meth = 'METH_VARARGS' + self._prnt(' {"%s", _cffi_f_%s, %s, NULL},' % (name, name, meth)) + + _loading_cpy_function = _loaded_noop + + def _loaded_cpy_function(self, tp, name, module, library): + if tp.ellipsis: + return + func = getattr(module, name) + setattr(library, name, func) + self._types_of_builtin_functions[func] = tp + + # ---------- + # named structs + + _generate_cpy_struct_collecttype = _generate_nothing + def _generate_cpy_struct_decl(self, tp, name): + assert name == tp.name + self._generate_struct_or_union_decl(tp, 'struct', name) + def _generate_cpy_struct_method(self, tp, name): + self._generate_struct_or_union_method(tp, 'struct', name) + def _loading_cpy_struct(self, tp, name, module): + self._loading_struct_or_union(tp, 'struct', name, module) + def _loaded_cpy_struct(self, tp, name, module, **kwds): + self._loaded_struct_or_union(tp) + + _generate_cpy_union_collecttype = _generate_nothing + def _generate_cpy_union_decl(self, tp, name): + assert name == tp.name + self._generate_struct_or_union_decl(tp, 'union', name) + def _generate_cpy_union_method(self, tp, name): + self._generate_struct_or_union_method(tp, 'union', name) + def _loading_cpy_union(self, tp, name, module): + self._loading_struct_or_union(tp, 'union', name, module) + def _loaded_cpy_union(self, tp, name, module, **kwds): + self._loaded_struct_or_union(tp) + + def _generate_struct_or_union_decl(self, tp, prefix, name): + if tp.fldnames is None: + return # nothing to do with opaque structs + checkfuncname = '_cffi_check_%s_%s' % (prefix, name) + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + cname = ('%s %s' % (prefix, name)).strip() + # + prnt = self._prnt + prnt('static void %s(%s *p)' % (checkfuncname, cname)) + prnt('{') + prnt(' /* only to generate compile-time warnings or errors */') + prnt(' (void)p;') + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if (isinstance(ftype, model.PrimitiveType) + and ftype.is_integer_type()) or fbitsize >= 0: + # accept all integers, but complain on float or double + prnt(' (void)((p->%s) << 1);' % fname) + else: + # only accept exactly the type declared. + try: + prnt(' { %s = &p->%s; (void)tmp; }' % ( + ftype.get_c_name('*tmp', 'field %r'%fname, quals=fqual), + fname)) + except VerificationError as e: + prnt(' /* %s */' % str(e)) # cannot verify it, ignore + prnt('}') + prnt('static PyObject *') + prnt('%s(PyObject *self, PyObject *noarg)' % (layoutfuncname,)) + prnt('{') + prnt(' struct _cffi_aligncheck { char x; %s y; };' % cname) + prnt(' static Py_ssize_t nums[] = {') + prnt(' sizeof(%s),' % cname) + prnt(' offsetof(struct _cffi_aligncheck, y),') + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if fbitsize >= 0: + continue # xxx ignore fbitsize for now + prnt(' offsetof(%s, %s),' % (cname, fname)) + if isinstance(ftype, model.ArrayType) and ftype.length is None: + prnt(' 0, /* %s */' % ftype._get_c_name()) + else: + prnt(' sizeof(((%s *)0)->%s),' % (cname, fname)) + prnt(' -1') + prnt(' };') + prnt(' (void)self; /* unused */') + prnt(' (void)noarg; /* unused */') + prnt(' return _cffi_get_struct_layout(nums);') + prnt(' /* the next line is not executed, but compiled */') + prnt(' %s(0);' % (checkfuncname,)) + prnt('}') + prnt() + + def _generate_struct_or_union_method(self, tp, prefix, name): + if tp.fldnames is None: + return # nothing to do with opaque structs + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + self._prnt(' {"%s", %s, METH_NOARGS, NULL},' % (layoutfuncname, + layoutfuncname)) + + def _loading_struct_or_union(self, tp, prefix, name, module): + if tp.fldnames is None: + return # nothing to do with opaque structs + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + # + function = getattr(module, layoutfuncname) + layout = function() + if isinstance(tp, model.StructOrUnion) and tp.partial: + # use the function()'s sizes and offsets to guide the + # layout of the struct + totalsize = layout[0] + totalalignment = layout[1] + fieldofs = layout[2::2] + fieldsize = layout[3::2] + tp.force_flatten() + assert len(fieldofs) == len(fieldsize) == len(tp.fldnames) + tp.fixedlayout = fieldofs, fieldsize, totalsize, totalalignment + else: + cname = ('%s %s' % (prefix, name)).strip() + self._struct_pending_verification[tp] = layout, cname + + def _loaded_struct_or_union(self, tp): + if tp.fldnames is None: + return # nothing to do with opaque structs + self.ffi._get_cached_btype(tp) # force 'fixedlayout' to be considered + + if tp in self._struct_pending_verification: + # check that the layout sizes and offsets match the real ones + def check(realvalue, expectedvalue, msg): + if realvalue != expectedvalue: + raise VerificationError( + "%s (we have %d, but C compiler says %d)" + % (msg, expectedvalue, realvalue)) + ffi = self.ffi + BStruct = ffi._get_cached_btype(tp) + layout, cname = self._struct_pending_verification.pop(tp) + check(layout[0], ffi.sizeof(BStruct), "wrong total size") + check(layout[1], ffi.alignof(BStruct), "wrong total alignment") + i = 2 + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if fbitsize >= 0: + continue # xxx ignore fbitsize for now + check(layout[i], ffi.offsetof(BStruct, fname), + "wrong offset for field %r" % (fname,)) + if layout[i+1] != 0: + BField = ffi._get_cached_btype(ftype) + check(layout[i+1], ffi.sizeof(BField), + "wrong size for field %r" % (fname,)) + i += 2 + assert i == len(layout) + + # ---------- + # 'anonymous' declarations. These are produced for anonymous structs + # or unions; the 'name' is obtained by a typedef. + + _generate_cpy_anonymous_collecttype = _generate_nothing + + def _generate_cpy_anonymous_decl(self, tp, name): + if isinstance(tp, model.EnumType): + self._generate_cpy_enum_decl(tp, name, '') + else: + self._generate_struct_or_union_decl(tp, '', name) + + def _generate_cpy_anonymous_method(self, tp, name): + if not isinstance(tp, model.EnumType): + self._generate_struct_or_union_method(tp, '', name) + + def _loading_cpy_anonymous(self, tp, name, module): + if isinstance(tp, model.EnumType): + self._loading_cpy_enum(tp, name, module) + else: + self._loading_struct_or_union(tp, '', name, module) + + def _loaded_cpy_anonymous(self, tp, name, module, **kwds): + if isinstance(tp, model.EnumType): + self._loaded_cpy_enum(tp, name, module, **kwds) + else: + self._loaded_struct_or_union(tp) + + # ---------- + # constants, likely declared with '#define' + + def _generate_cpy_const(self, is_int, name, tp=None, category='const', + vartp=None, delayed=True, size_too=False, + check_value=None): + prnt = self._prnt + funcname = '_cffi_%s_%s' % (category, name) + prnt('static int %s(PyObject *lib)' % funcname) + prnt('{') + prnt(' PyObject *o;') + prnt(' int res;') + if not is_int: + prnt(' %s;' % (vartp or tp).get_c_name(' i', name)) + else: + assert category == 'const' + # + if check_value is not None: + self._check_int_constant_value(name, check_value) + # + if not is_int: + if category == 'var': + realexpr = '&' + name + else: + realexpr = name + prnt(' i = (%s);' % (realexpr,)) + prnt(' o = %s;' % (self._convert_expr_from_c(tp, 'i', + 'variable type'),)) + assert delayed + else: + prnt(' o = _cffi_from_c_int_const(%s);' % name) + prnt(' if (o == NULL)') + prnt(' return -1;') + if size_too: + prnt(' {') + prnt(' PyObject *o1 = o;') + prnt(' o = Py_BuildValue("On", o1, (Py_ssize_t)sizeof(%s));' + % (name,)) + prnt(' Py_DECREF(o1);') + prnt(' if (o == NULL)') + prnt(' return -1;') + prnt(' }') + prnt(' res = PyObject_SetAttrString(lib, "%s", o);' % name) + prnt(' Py_DECREF(o);') + prnt(' if (res < 0)') + prnt(' return -1;') + prnt(' return %s;' % self._chained_list_constants[delayed]) + self._chained_list_constants[delayed] = funcname + '(lib)' + prnt('}') + prnt() + + def _generate_cpy_constant_collecttype(self, tp, name): + is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() + if not is_int: + self._do_collect_type(tp) + + def _generate_cpy_constant_decl(self, tp, name): + is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() + self._generate_cpy_const(is_int, name, tp) + + _generate_cpy_constant_method = _generate_nothing + _loading_cpy_constant = _loaded_noop + _loaded_cpy_constant = _loaded_noop + + # ---------- + # enums + + def _check_int_constant_value(self, name, value, err_prefix=''): + prnt = self._prnt + if value <= 0: + prnt(' if ((%s) > 0 || (long)(%s) != %dL) {' % ( + name, name, value)) + else: + prnt(' if ((%s) <= 0 || (unsigned long)(%s) != %dUL) {' % ( + name, name, value)) + prnt(' char buf[64];') + prnt(' if ((%s) <= 0)' % name) + prnt(' snprintf(buf, 63, "%%ld", (long)(%s));' % name) + prnt(' else') + prnt(' snprintf(buf, 63, "%%lu", (unsigned long)(%s));' % + name) + prnt(' PyErr_Format(_cffi_VerificationError,') + prnt(' "%s%s has the real value %s, not %s",') + prnt(' "%s", "%s", buf, "%d");' % ( + err_prefix, name, value)) + prnt(' return -1;') + prnt(' }') + + def _enum_funcname(self, prefix, name): + # "$enum_$1" => "___D_enum____D_1" + name = name.replace('$', '___D_') + return '_cffi_e_%s_%s' % (prefix, name) + + def _generate_cpy_enum_decl(self, tp, name, prefix='enum'): + if tp.partial: + for enumerator in tp.enumerators: + self._generate_cpy_const(True, enumerator, delayed=False) + return + # + funcname = self._enum_funcname(prefix, name) + prnt = self._prnt + prnt('static int %s(PyObject *lib)' % funcname) + prnt('{') + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + self._check_int_constant_value(enumerator, enumvalue, + "enum %s: " % name) + prnt(' return %s;' % self._chained_list_constants[True]) + self._chained_list_constants[True] = funcname + '(lib)' + prnt('}') + prnt() + + _generate_cpy_enum_collecttype = _generate_nothing + _generate_cpy_enum_method = _generate_nothing + + def _loading_cpy_enum(self, tp, name, module): + if tp.partial: + enumvalues = [getattr(module, enumerator) + for enumerator in tp.enumerators] + tp.enumvalues = tuple(enumvalues) + tp.partial_resolved = True + + def _loaded_cpy_enum(self, tp, name, module, library): + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + setattr(library, enumerator, enumvalue) + + # ---------- + # macros: for now only for integers + + def _generate_cpy_macro_decl(self, tp, name): + if tp == '...': + check_value = None + else: + check_value = tp # an integer + self._generate_cpy_const(True, name, check_value=check_value) + + _generate_cpy_macro_collecttype = _generate_nothing + _generate_cpy_macro_method = _generate_nothing + _loading_cpy_macro = _loaded_noop + _loaded_cpy_macro = _loaded_noop + + # ---------- + # global variables + + def _generate_cpy_variable_collecttype(self, tp, name): + if isinstance(tp, model.ArrayType): + tp_ptr = model.PointerType(tp.item) + else: + tp_ptr = model.PointerType(tp) + self._do_collect_type(tp_ptr) + + def _generate_cpy_variable_decl(self, tp, name): + if isinstance(tp, model.ArrayType): + tp_ptr = model.PointerType(tp.item) + self._generate_cpy_const(False, name, tp, vartp=tp_ptr, + size_too = tp.length_is_unknown()) + else: + tp_ptr = model.PointerType(tp) + self._generate_cpy_const(False, name, tp_ptr, category='var') + + _generate_cpy_variable_method = _generate_nothing + _loading_cpy_variable = _loaded_noop + + def _loaded_cpy_variable(self, tp, name, module, library): + value = getattr(library, name) + if isinstance(tp, model.ArrayType): # int a[5] is "constant" in the + # sense that "a=..." is forbidden + if tp.length_is_unknown(): + assert isinstance(value, tuple) + (value, size) = value + BItemType = self.ffi._get_cached_btype(tp.item) + length, rest = divmod(size, self.ffi.sizeof(BItemType)) + if rest != 0: + raise VerificationError( + "bad size: %r does not seem to be an array of %s" % + (name, tp.item)) + tp = tp.resolve_length(length) + # 'value' is a which we have to replace with + # a if the N is actually known + if tp.length is not None: + BArray = self.ffi._get_cached_btype(tp) + value = self.ffi.cast(BArray, value) + setattr(library, name, value) + return + # remove ptr= from the library instance, and replace + # it by a property on the class, which reads/writes into ptr[0]. + ptr = value + delattr(library, name) + def getter(library): + return ptr[0] + def setter(library, value): + ptr[0] = value + setattr(type(library), name, property(getter, setter)) + type(library)._cffi_dir.append(name) + + # ---------- + + def _generate_setup_custom(self): + prnt = self._prnt + prnt('static int _cffi_setup_custom(PyObject *lib)') + prnt('{') + prnt(' return %s;' % self._chained_list_constants[True]) + prnt('}') + +cffimod_header = r''' +#include +#include + +/* this block of #ifs should be kept exactly identical between + c/_cffi_backend.c, cffi/vengine_cpy.py, cffi/vengine_gen.py + and cffi/_cffi_include.h */ +#if defined(_MSC_VER) +# include /* for alloca() */ +# if _MSC_VER < 1600 /* MSVC < 2010 */ + typedef __int8 int8_t; + typedef __int16 int16_t; + typedef __int32 int32_t; + typedef __int64 int64_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; + typedef unsigned __int64 uint64_t; + typedef __int8 int_least8_t; + typedef __int16 int_least16_t; + typedef __int32 int_least32_t; + typedef __int64 int_least64_t; + typedef unsigned __int8 uint_least8_t; + typedef unsigned __int16 uint_least16_t; + typedef unsigned __int32 uint_least32_t; + typedef unsigned __int64 uint_least64_t; + typedef __int8 int_fast8_t; + typedef __int16 int_fast16_t; + typedef __int32 int_fast32_t; + typedef __int64 int_fast64_t; + typedef unsigned __int8 uint_fast8_t; + typedef unsigned __int16 uint_fast16_t; + typedef unsigned __int32 uint_fast32_t; + typedef unsigned __int64 uint_fast64_t; + typedef __int64 intmax_t; + typedef unsigned __int64 uintmax_t; +# else +# include +# endif +# if _MSC_VER < 1800 /* MSVC < 2013 */ +# ifndef __cplusplus + typedef unsigned char _Bool; +# endif +# endif +# define _cffi_float_complex_t _Fcomplex /* include for it */ +# define _cffi_double_complex_t _Dcomplex /* include for it */ +#else +# include +# if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux) +# include +# endif +# define _cffi_float_complex_t float _Complex +# define _cffi_double_complex_t double _Complex +#endif + +#if PY_MAJOR_VERSION < 3 +# undef PyCapsule_CheckExact +# undef PyCapsule_GetPointer +# define PyCapsule_CheckExact(capsule) (PyCObject_Check(capsule)) +# define PyCapsule_GetPointer(capsule, name) \ + (PyCObject_AsVoidPtr(capsule)) +#endif + +#if PY_MAJOR_VERSION >= 3 +# define PyInt_FromLong PyLong_FromLong +#endif + +#define _cffi_from_c_double PyFloat_FromDouble +#define _cffi_from_c_float PyFloat_FromDouble +#define _cffi_from_c_long PyInt_FromLong +#define _cffi_from_c_ulong PyLong_FromUnsignedLong +#define _cffi_from_c_longlong PyLong_FromLongLong +#define _cffi_from_c_ulonglong PyLong_FromUnsignedLongLong +#define _cffi_from_c__Bool PyBool_FromLong + +#define _cffi_to_c_double PyFloat_AsDouble +#define _cffi_to_c_float PyFloat_AsDouble + +#define _cffi_from_c_int_const(x) \ + (((x) > 0) ? \ + ((unsigned long long)(x) <= (unsigned long long)LONG_MAX) ? \ + PyInt_FromLong((long)(x)) : \ + PyLong_FromUnsignedLongLong((unsigned long long)(x)) : \ + ((long long)(x) >= (long long)LONG_MIN) ? \ + PyInt_FromLong((long)(x)) : \ + PyLong_FromLongLong((long long)(x))) + +#define _cffi_from_c_int(x, type) \ + (((type)-1) > 0 ? /* unsigned */ \ + (sizeof(type) < sizeof(long) ? \ + PyInt_FromLong((long)x) : \ + sizeof(type) == sizeof(long) ? \ + PyLong_FromUnsignedLong((unsigned long)x) : \ + PyLong_FromUnsignedLongLong((unsigned long long)x)) : \ + (sizeof(type) <= sizeof(long) ? \ + PyInt_FromLong((long)x) : \ + PyLong_FromLongLong((long long)x))) + +#define _cffi_to_c_int(o, type) \ + ((type)( \ + sizeof(type) == 1 ? (((type)-1) > 0 ? (type)_cffi_to_c_u8(o) \ + : (type)_cffi_to_c_i8(o)) : \ + sizeof(type) == 2 ? (((type)-1) > 0 ? (type)_cffi_to_c_u16(o) \ + : (type)_cffi_to_c_i16(o)) : \ + sizeof(type) == 4 ? (((type)-1) > 0 ? (type)_cffi_to_c_u32(o) \ + : (type)_cffi_to_c_i32(o)) : \ + sizeof(type) == 8 ? (((type)-1) > 0 ? (type)_cffi_to_c_u64(o) \ + : (type)_cffi_to_c_i64(o)) : \ + (Py_FatalError("unsupported size for type " #type), (type)0))) + +#define _cffi_to_c_i8 \ + ((int(*)(PyObject *))_cffi_exports[1]) +#define _cffi_to_c_u8 \ + ((int(*)(PyObject *))_cffi_exports[2]) +#define _cffi_to_c_i16 \ + ((int(*)(PyObject *))_cffi_exports[3]) +#define _cffi_to_c_u16 \ + ((int(*)(PyObject *))_cffi_exports[4]) +#define _cffi_to_c_i32 \ + ((int(*)(PyObject *))_cffi_exports[5]) +#define _cffi_to_c_u32 \ + ((unsigned int(*)(PyObject *))_cffi_exports[6]) +#define _cffi_to_c_i64 \ + ((long long(*)(PyObject *))_cffi_exports[7]) +#define _cffi_to_c_u64 \ + ((unsigned long long(*)(PyObject *))_cffi_exports[8]) +#define _cffi_to_c_char \ + ((int(*)(PyObject *))_cffi_exports[9]) +#define _cffi_from_c_pointer \ + ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[10]) +#define _cffi_to_c_pointer \ + ((char *(*)(PyObject *, CTypeDescrObject *))_cffi_exports[11]) +#define _cffi_get_struct_layout \ + ((PyObject *(*)(Py_ssize_t[]))_cffi_exports[12]) +#define _cffi_restore_errno \ + ((void(*)(void))_cffi_exports[13]) +#define _cffi_save_errno \ + ((void(*)(void))_cffi_exports[14]) +#define _cffi_from_c_char \ + ((PyObject *(*)(char))_cffi_exports[15]) +#define _cffi_from_c_deref \ + ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[16]) +#define _cffi_to_c \ + ((int(*)(char *, CTypeDescrObject *, PyObject *))_cffi_exports[17]) +#define _cffi_from_c_struct \ + ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[18]) +#define _cffi_to_c_wchar_t \ + ((wchar_t(*)(PyObject *))_cffi_exports[19]) +#define _cffi_from_c_wchar_t \ + ((PyObject *(*)(wchar_t))_cffi_exports[20]) +#define _cffi_to_c_long_double \ + ((long double(*)(PyObject *))_cffi_exports[21]) +#define _cffi_to_c__Bool \ + ((_Bool(*)(PyObject *))_cffi_exports[22]) +#define _cffi_prepare_pointer_call_argument \ + ((Py_ssize_t(*)(CTypeDescrObject *, PyObject *, char **))_cffi_exports[23]) +#define _cffi_convert_array_from_object \ + ((int(*)(char *, CTypeDescrObject *, PyObject *))_cffi_exports[24]) +#define _CFFI_NUM_EXPORTS 25 + +typedef struct _ctypedescr CTypeDescrObject; + +static void *_cffi_exports[_CFFI_NUM_EXPORTS]; +static PyObject *_cffi_types, *_cffi_VerificationError; + +static int _cffi_setup_custom(PyObject *lib); /* forward */ + +static PyObject *_cffi_setup(PyObject *self, PyObject *args) +{ + PyObject *library; + int was_alive = (_cffi_types != NULL); + (void)self; /* unused */ + if (!PyArg_ParseTuple(args, "OOO", &_cffi_types, &_cffi_VerificationError, + &library)) + return NULL; + Py_INCREF(_cffi_types); + Py_INCREF(_cffi_VerificationError); + if (_cffi_setup_custom(library) < 0) + return NULL; + return PyBool_FromLong(was_alive); +} + +union _cffi_union_alignment_u { + unsigned char m_char; + unsigned short m_short; + unsigned int m_int; + unsigned long m_long; + unsigned long long m_longlong; + float m_float; + double m_double; + long double m_longdouble; +}; + +struct _cffi_freeme_s { + struct _cffi_freeme_s *next; + union _cffi_union_alignment_u alignment; +}; + +#ifdef __GNUC__ + __attribute__((unused)) +#endif +static int _cffi_convert_array_argument(CTypeDescrObject *ctptr, PyObject *arg, + char **output_data, Py_ssize_t datasize, + struct _cffi_freeme_s **freeme) +{ + char *p; + if (datasize < 0) + return -1; + + p = *output_data; + if (p == NULL) { + struct _cffi_freeme_s *fp = (struct _cffi_freeme_s *)PyObject_Malloc( + offsetof(struct _cffi_freeme_s, alignment) + (size_t)datasize); + if (fp == NULL) + return -1; + fp->next = *freeme; + *freeme = fp; + p = *output_data = (char *)&fp->alignment; + } + memset((void *)p, 0, (size_t)datasize); + return _cffi_convert_array_from_object(p, ctptr, arg); +} + +#ifdef __GNUC__ + __attribute__((unused)) +#endif +static void _cffi_free_array_arguments(struct _cffi_freeme_s *freeme) +{ + do { + void *p = (void *)freeme; + freeme = freeme->next; + PyObject_Free(p); + } while (freeme != NULL); +} + +static int _cffi_init(void) +{ + PyObject *module, *c_api_object = NULL; + + module = PyImport_ImportModule("_cffi_backend"); + if (module == NULL) + goto failure; + + c_api_object = PyObject_GetAttrString(module, "_C_API"); + if (c_api_object == NULL) + goto failure; + if (!PyCapsule_CheckExact(c_api_object)) { + PyErr_SetNone(PyExc_ImportError); + goto failure; + } + memcpy(_cffi_exports, PyCapsule_GetPointer(c_api_object, "cffi"), + _CFFI_NUM_EXPORTS * sizeof(void *)); + + Py_DECREF(module); + Py_DECREF(c_api_object); + return 0; + + failure: + Py_XDECREF(module); + Py_XDECREF(c_api_object); + return -1; +} + +#define _cffi_type(num) ((CTypeDescrObject *)PyList_GET_ITEM(_cffi_types, num)) + +/**********/ +''' diff --git a/intelliaide/vendor/cffi/vengine_gen.py b/intelliaide/vendor/cffi/vengine_gen.py new file mode 100644 index 0000000..bffc821 --- /dev/null +++ b/intelliaide/vendor/cffi/vengine_gen.py @@ -0,0 +1,679 @@ +# +# DEPRECATED: implementation for ffi.verify() +# +import sys, os +import types + +from . import model +from .error import VerificationError + + +class VGenericEngine(object): + _class_key = 'g' + _gen_python_module = False + + def __init__(self, verifier): + self.verifier = verifier + self.ffi = verifier.ffi + self.export_symbols = [] + self._struct_pending_verification = {} + + def patch_extension_kwds(self, kwds): + # add 'export_symbols' to the dictionary. Note that we add the + # list before filling it. When we fill it, it will thus also show + # up in kwds['export_symbols']. + kwds.setdefault('export_symbols', self.export_symbols) + + def find_module(self, module_name, path, so_suffixes): + for so_suffix in so_suffixes: + basename = module_name + so_suffix + if path is None: + path = sys.path + for dirname in path: + filename = os.path.join(dirname, basename) + if os.path.isfile(filename): + return filename + + def collect_types(self): + pass # not needed in the generic engine + + def _prnt(self, what=''): + self._f.write(what + '\n') + + def write_source_to_f(self): + prnt = self._prnt + # first paste some standard set of lines that are mostly '#include' + prnt(cffimod_header) + # then paste the C source given by the user, verbatim. + prnt(self.verifier.preamble) + # + # call generate_gen_xxx_decl(), for every xxx found from + # ffi._parser._declarations. This generates all the functions. + self._generate('decl') + # + # on Windows, distutils insists on putting init_cffi_xyz in + # 'export_symbols', so instead of fighting it, just give up and + # give it one + if sys.platform == 'win32': + if sys.version_info >= (3,): + prefix = 'PyInit_' + else: + prefix = 'init' + modname = self.verifier.get_module_name() + prnt("void %s%s(void) { }\n" % (prefix, modname)) + + def load_library(self, flags=0): + # import it with the CFFI backend + backend = self.ffi._backend + # needs to make a path that contains '/', on Posix + filename = os.path.join(os.curdir, self.verifier.modulefilename) + module = backend.load_library(filename, flags) + # + # call loading_gen_struct() to get the struct layout inferred by + # the C compiler + self._load(module, 'loading') + + # build the FFILibrary class and instance, this is a module subclass + # because modules are expected to have usually-constant-attributes and + # in PyPy this means the JIT is able to treat attributes as constant, + # which we want. + class FFILibrary(types.ModuleType): + _cffi_generic_module = module + _cffi_ffi = self.ffi + _cffi_dir = [] + def __dir__(self): + return FFILibrary._cffi_dir + library = FFILibrary("") + # + # finally, call the loaded_gen_xxx() functions. This will set + # up the 'library' object. + self._load(module, 'loaded', library=library) + return library + + def _get_declarations(self): + lst = [(key, tp) for (key, (tp, qual)) in + self.ffi._parser._declarations.items()] + lst.sort() + return lst + + def _generate(self, step_name): + for name, tp in self._get_declarations(): + kind, realname = name.split(' ', 1) + try: + method = getattr(self, '_generate_gen_%s_%s' % (kind, + step_name)) + except AttributeError: + raise VerificationError( + "not implemented in verify(): %r" % name) + try: + method(tp, realname) + except Exception as e: + model.attach_exception_info(e, name) + raise + + def _load(self, module, step_name, **kwds): + for name, tp in self._get_declarations(): + kind, realname = name.split(' ', 1) + method = getattr(self, '_%s_gen_%s' % (step_name, kind)) + try: + method(tp, realname, module, **kwds) + except Exception as e: + model.attach_exception_info(e, name) + raise + + def _generate_nothing(self, tp, name): + pass + + def _loaded_noop(self, tp, name, module, **kwds): + pass + + # ---------- + # typedefs: generates no code so far + + _generate_gen_typedef_decl = _generate_nothing + _loading_gen_typedef = _loaded_noop + _loaded_gen_typedef = _loaded_noop + + # ---------- + # function declarations + + def _generate_gen_function_decl(self, tp, name): + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + # cannot support vararg functions better than this: check for its + # exact type (including the fixed arguments), and build it as a + # constant function pointer (no _cffi_f_%s wrapper) + self._generate_gen_const(False, name, tp) + return + prnt = self._prnt + numargs = len(tp.args) + argnames = [] + for i, type in enumerate(tp.args): + indirection = '' + if isinstance(type, model.StructOrUnion): + indirection = '*' + argnames.append('%sx%d' % (indirection, i)) + context = 'argument of %s' % name + arglist = [type.get_c_name(' %s' % arg, context) + for type, arg in zip(tp.args, argnames)] + tpresult = tp.result + if isinstance(tpresult, model.StructOrUnion): + arglist.insert(0, tpresult.get_c_name(' *r', context)) + tpresult = model.void_type + arglist = ', '.join(arglist) or 'void' + wrappername = '_cffi_f_%s' % name + self.export_symbols.append(wrappername) + if tp.abi: + abi = tp.abi + ' ' + else: + abi = '' + funcdecl = ' %s%s(%s)' % (abi, wrappername, arglist) + context = 'result of %s' % name + prnt(tpresult.get_c_name(funcdecl, context)) + prnt('{') + # + if isinstance(tp.result, model.StructOrUnion): + result_code = '*r = ' + elif not isinstance(tp.result, model.VoidType): + result_code = 'return ' + else: + result_code = '' + prnt(' %s%s(%s);' % (result_code, name, ', '.join(argnames))) + prnt('}') + prnt() + + _loading_gen_function = _loaded_noop + + def _loaded_gen_function(self, tp, name, module, library): + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + newfunction = self._load_constant(False, tp, name, module) + else: + indirections = [] + base_tp = tp + if (any(isinstance(typ, model.StructOrUnion) for typ in tp.args) + or isinstance(tp.result, model.StructOrUnion)): + indirect_args = [] + for i, typ in enumerate(tp.args): + if isinstance(typ, model.StructOrUnion): + typ = model.PointerType(typ) + indirections.append((i, typ)) + indirect_args.append(typ) + indirect_result = tp.result + if isinstance(indirect_result, model.StructOrUnion): + if indirect_result.fldtypes is None: + raise TypeError("'%s' is used as result type, " + "but is opaque" % ( + indirect_result._get_c_name(),)) + indirect_result = model.PointerType(indirect_result) + indirect_args.insert(0, indirect_result) + indirections.insert(0, ("result", indirect_result)) + indirect_result = model.void_type + tp = model.FunctionPtrType(tuple(indirect_args), + indirect_result, tp.ellipsis) + BFunc = self.ffi._get_cached_btype(tp) + wrappername = '_cffi_f_%s' % name + newfunction = module.load_function(BFunc, wrappername) + for i, typ in indirections: + newfunction = self._make_struct_wrapper(newfunction, i, typ, + base_tp) + setattr(library, name, newfunction) + type(library)._cffi_dir.append(name) + + def _make_struct_wrapper(self, oldfunc, i, tp, base_tp): + backend = self.ffi._backend + BType = self.ffi._get_cached_btype(tp) + if i == "result": + ffi = self.ffi + def newfunc(*args): + res = ffi.new(BType) + oldfunc(res, *args) + return res[0] + else: + def newfunc(*args): + args = args[:i] + (backend.newp(BType, args[i]),) + args[i+1:] + return oldfunc(*args) + newfunc._cffi_base_type = base_tp + return newfunc + + # ---------- + # named structs + + def _generate_gen_struct_decl(self, tp, name): + assert name == tp.name + self._generate_struct_or_union_decl(tp, 'struct', name) + + def _loading_gen_struct(self, tp, name, module): + self._loading_struct_or_union(tp, 'struct', name, module) + + def _loaded_gen_struct(self, tp, name, module, **kwds): + self._loaded_struct_or_union(tp) + + def _generate_gen_union_decl(self, tp, name): + assert name == tp.name + self._generate_struct_or_union_decl(tp, 'union', name) + + def _loading_gen_union(self, tp, name, module): + self._loading_struct_or_union(tp, 'union', name, module) + + def _loaded_gen_union(self, tp, name, module, **kwds): + self._loaded_struct_or_union(tp) + + def _generate_struct_or_union_decl(self, tp, prefix, name): + if tp.fldnames is None: + return # nothing to do with opaque structs + checkfuncname = '_cffi_check_%s_%s' % (prefix, name) + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + cname = ('%s %s' % (prefix, name)).strip() + # + prnt = self._prnt + prnt('static void %s(%s *p)' % (checkfuncname, cname)) + prnt('{') + prnt(' /* only to generate compile-time warnings or errors */') + prnt(' (void)p;') + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if (isinstance(ftype, model.PrimitiveType) + and ftype.is_integer_type()) or fbitsize >= 0: + # accept all integers, but complain on float or double + prnt(' (void)((p->%s) << 1);' % fname) + else: + # only accept exactly the type declared. + try: + prnt(' { %s = &p->%s; (void)tmp; }' % ( + ftype.get_c_name('*tmp', 'field %r'%fname, quals=fqual), + fname)) + except VerificationError as e: + prnt(' /* %s */' % str(e)) # cannot verify it, ignore + prnt('}') + self.export_symbols.append(layoutfuncname) + prnt('intptr_t %s(intptr_t i)' % (layoutfuncname,)) + prnt('{') + prnt(' struct _cffi_aligncheck { char x; %s y; };' % cname) + prnt(' static intptr_t nums[] = {') + prnt(' sizeof(%s),' % cname) + prnt(' offsetof(struct _cffi_aligncheck, y),') + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if fbitsize >= 0: + continue # xxx ignore fbitsize for now + prnt(' offsetof(%s, %s),' % (cname, fname)) + if isinstance(ftype, model.ArrayType) and ftype.length is None: + prnt(' 0, /* %s */' % ftype._get_c_name()) + else: + prnt(' sizeof(((%s *)0)->%s),' % (cname, fname)) + prnt(' -1') + prnt(' };') + prnt(' return nums[i];') + prnt(' /* the next line is not executed, but compiled */') + prnt(' %s(0);' % (checkfuncname,)) + prnt('}') + prnt() + + def _loading_struct_or_union(self, tp, prefix, name, module): + if tp.fldnames is None: + return # nothing to do with opaque structs + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + # + BFunc = self.ffi._typeof_locked("intptr_t(*)(intptr_t)")[0] + function = module.load_function(BFunc, layoutfuncname) + layout = [] + num = 0 + while True: + x = function(num) + if x < 0: break + layout.append(x) + num += 1 + if isinstance(tp, model.StructOrUnion) and tp.partial: + # use the function()'s sizes and offsets to guide the + # layout of the struct + totalsize = layout[0] + totalalignment = layout[1] + fieldofs = layout[2::2] + fieldsize = layout[3::2] + tp.force_flatten() + assert len(fieldofs) == len(fieldsize) == len(tp.fldnames) + tp.fixedlayout = fieldofs, fieldsize, totalsize, totalalignment + else: + cname = ('%s %s' % (prefix, name)).strip() + self._struct_pending_verification[tp] = layout, cname + + def _loaded_struct_or_union(self, tp): + if tp.fldnames is None: + return # nothing to do with opaque structs + self.ffi._get_cached_btype(tp) # force 'fixedlayout' to be considered + + if tp in self._struct_pending_verification: + # check that the layout sizes and offsets match the real ones + def check(realvalue, expectedvalue, msg): + if realvalue != expectedvalue: + raise VerificationError( + "%s (we have %d, but C compiler says %d)" + % (msg, expectedvalue, realvalue)) + ffi = self.ffi + BStruct = ffi._get_cached_btype(tp) + layout, cname = self._struct_pending_verification.pop(tp) + check(layout[0], ffi.sizeof(BStruct), "wrong total size") + check(layout[1], ffi.alignof(BStruct), "wrong total alignment") + i = 2 + for fname, ftype, fbitsize, fqual in tp.enumfields(): + if fbitsize >= 0: + continue # xxx ignore fbitsize for now + check(layout[i], ffi.offsetof(BStruct, fname), + "wrong offset for field %r" % (fname,)) + if layout[i+1] != 0: + BField = ffi._get_cached_btype(ftype) + check(layout[i+1], ffi.sizeof(BField), + "wrong size for field %r" % (fname,)) + i += 2 + assert i == len(layout) + + # ---------- + # 'anonymous' declarations. These are produced for anonymous structs + # or unions; the 'name' is obtained by a typedef. + + def _generate_gen_anonymous_decl(self, tp, name): + if isinstance(tp, model.EnumType): + self._generate_gen_enum_decl(tp, name, '') + else: + self._generate_struct_or_union_decl(tp, '', name) + + def _loading_gen_anonymous(self, tp, name, module): + if isinstance(tp, model.EnumType): + self._loading_gen_enum(tp, name, module, '') + else: + self._loading_struct_or_union(tp, '', name, module) + + def _loaded_gen_anonymous(self, tp, name, module, **kwds): + if isinstance(tp, model.EnumType): + self._loaded_gen_enum(tp, name, module, **kwds) + else: + self._loaded_struct_or_union(tp) + + # ---------- + # constants, likely declared with '#define' + + def _generate_gen_const(self, is_int, name, tp=None, category='const', + check_value=None): + prnt = self._prnt + funcname = '_cffi_%s_%s' % (category, name) + self.export_symbols.append(funcname) + if check_value is not None: + assert is_int + assert category == 'const' + prnt('int %s(char *out_error)' % funcname) + prnt('{') + self._check_int_constant_value(name, check_value) + prnt(' return 0;') + prnt('}') + elif is_int: + assert category == 'const' + prnt('int %s(long long *out_value)' % funcname) + prnt('{') + prnt(' *out_value = (long long)(%s);' % (name,)) + prnt(' return (%s) <= 0;' % (name,)) + prnt('}') + else: + assert tp is not None + assert check_value is None + if category == 'var': + ampersand = '&' + else: + ampersand = '' + extra = '' + if category == 'const' and isinstance(tp, model.StructOrUnion): + extra = 'const *' + ampersand = '&' + prnt(tp.get_c_name(' %s%s(void)' % (extra, funcname), name)) + prnt('{') + prnt(' return (%s%s);' % (ampersand, name)) + prnt('}') + prnt() + + def _generate_gen_constant_decl(self, tp, name): + is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() + self._generate_gen_const(is_int, name, tp) + + _loading_gen_constant = _loaded_noop + + def _load_constant(self, is_int, tp, name, module, check_value=None): + funcname = '_cffi_const_%s' % name + if check_value is not None: + assert is_int + self._load_known_int_constant(module, funcname) + value = check_value + elif is_int: + BType = self.ffi._typeof_locked("long long*")[0] + BFunc = self.ffi._typeof_locked("int(*)(long long*)")[0] + function = module.load_function(BFunc, funcname) + p = self.ffi.new(BType) + negative = function(p) + value = int(p[0]) + if value < 0 and not negative: + BLongLong = self.ffi._typeof_locked("long long")[0] + value += (1 << (8*self.ffi.sizeof(BLongLong))) + else: + assert check_value is None + fntypeextra = '(*)(void)' + if isinstance(tp, model.StructOrUnion): + fntypeextra = '*' + fntypeextra + BFunc = self.ffi._typeof_locked(tp.get_c_name(fntypeextra, name))[0] + function = module.load_function(BFunc, funcname) + value = function() + if isinstance(tp, model.StructOrUnion): + value = value[0] + return value + + def _loaded_gen_constant(self, tp, name, module, library): + is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() + value = self._load_constant(is_int, tp, name, module) + setattr(library, name, value) + type(library)._cffi_dir.append(name) + + # ---------- + # enums + + def _check_int_constant_value(self, name, value): + prnt = self._prnt + if value <= 0: + prnt(' if ((%s) > 0 || (long)(%s) != %dL) {' % ( + name, name, value)) + else: + prnt(' if ((%s) <= 0 || (unsigned long)(%s) != %dUL) {' % ( + name, name, value)) + prnt(' char buf[64];') + prnt(' if ((%s) <= 0)' % name) + prnt(' sprintf(buf, "%%ld", (long)(%s));' % name) + prnt(' else') + prnt(' sprintf(buf, "%%lu", (unsigned long)(%s));' % + name) + prnt(' sprintf(out_error, "%s has the real value %s, not %s",') + prnt(' "%s", buf, "%d");' % (name[:100], value)) + prnt(' return -1;') + prnt(' }') + + def _load_known_int_constant(self, module, funcname): + BType = self.ffi._typeof_locked("char[]")[0] + BFunc = self.ffi._typeof_locked("int(*)(char*)")[0] + function = module.load_function(BFunc, funcname) + p = self.ffi.new(BType, 256) + if function(p) < 0: + error = self.ffi.string(p) + if sys.version_info >= (3,): + error = str(error, 'utf-8') + raise VerificationError(error) + + def _enum_funcname(self, prefix, name): + # "$enum_$1" => "___D_enum____D_1" + name = name.replace('$', '___D_') + return '_cffi_e_%s_%s' % (prefix, name) + + def _generate_gen_enum_decl(self, tp, name, prefix='enum'): + if tp.partial: + for enumerator in tp.enumerators: + self._generate_gen_const(True, enumerator) + return + # + funcname = self._enum_funcname(prefix, name) + self.export_symbols.append(funcname) + prnt = self._prnt + prnt('int %s(char *out_error)' % funcname) + prnt('{') + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + self._check_int_constant_value(enumerator, enumvalue) + prnt(' return 0;') + prnt('}') + prnt() + + def _loading_gen_enum(self, tp, name, module, prefix='enum'): + if tp.partial: + enumvalues = [self._load_constant(True, tp, enumerator, module) + for enumerator in tp.enumerators] + tp.enumvalues = tuple(enumvalues) + tp.partial_resolved = True + else: + funcname = self._enum_funcname(prefix, name) + self._load_known_int_constant(module, funcname) + + def _loaded_gen_enum(self, tp, name, module, library): + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + setattr(library, enumerator, enumvalue) + type(library)._cffi_dir.append(enumerator) + + # ---------- + # macros: for now only for integers + + def _generate_gen_macro_decl(self, tp, name): + if tp == '...': + check_value = None + else: + check_value = tp # an integer + self._generate_gen_const(True, name, check_value=check_value) + + _loading_gen_macro = _loaded_noop + + def _loaded_gen_macro(self, tp, name, module, library): + if tp == '...': + check_value = None + else: + check_value = tp # an integer + value = self._load_constant(True, tp, name, module, + check_value=check_value) + setattr(library, name, value) + type(library)._cffi_dir.append(name) + + # ---------- + # global variables + + def _generate_gen_variable_decl(self, tp, name): + if isinstance(tp, model.ArrayType): + if tp.length_is_unknown(): + prnt = self._prnt + funcname = '_cffi_sizeof_%s' % (name,) + self.export_symbols.append(funcname) + prnt("size_t %s(void)" % funcname) + prnt("{") + prnt(" return sizeof(%s);" % (name,)) + prnt("}") + tp_ptr = model.PointerType(tp.item) + self._generate_gen_const(False, name, tp_ptr) + else: + tp_ptr = model.PointerType(tp) + self._generate_gen_const(False, name, tp_ptr, category='var') + + _loading_gen_variable = _loaded_noop + + def _loaded_gen_variable(self, tp, name, module, library): + if isinstance(tp, model.ArrayType): # int a[5] is "constant" in the + # sense that "a=..." is forbidden + if tp.length_is_unknown(): + funcname = '_cffi_sizeof_%s' % (name,) + BFunc = self.ffi._typeof_locked('size_t(*)(void)')[0] + function = module.load_function(BFunc, funcname) + size = function() + BItemType = self.ffi._get_cached_btype(tp.item) + length, rest = divmod(size, self.ffi.sizeof(BItemType)) + if rest != 0: + raise VerificationError( + "bad size: %r does not seem to be an array of %s" % + (name, tp.item)) + tp = tp.resolve_length(length) + tp_ptr = model.PointerType(tp.item) + value = self._load_constant(False, tp_ptr, name, module) + # 'value' is a which we have to replace with + # a if the N is actually known + if tp.length is not None: + BArray = self.ffi._get_cached_btype(tp) + value = self.ffi.cast(BArray, value) + setattr(library, name, value) + type(library)._cffi_dir.append(name) + return + # remove ptr= from the library instance, and replace + # it by a property on the class, which reads/writes into ptr[0]. + funcname = '_cffi_var_%s' % name + BFunc = self.ffi._typeof_locked(tp.get_c_name('*(*)(void)', name))[0] + function = module.load_function(BFunc, funcname) + ptr = function() + def getter(library): + return ptr[0] + def setter(library, value): + ptr[0] = value + setattr(type(library), name, property(getter, setter)) + type(library)._cffi_dir.append(name) + +cffimod_header = r''' +#include +#include +#include +#include +#include /* XXX for ssize_t on some platforms */ + +/* this block of #ifs should be kept exactly identical between + c/_cffi_backend.c, cffi/vengine_cpy.py, cffi/vengine_gen.py + and cffi/_cffi_include.h */ +#if defined(_MSC_VER) +# include /* for alloca() */ +# if _MSC_VER < 1600 /* MSVC < 2010 */ + typedef __int8 int8_t; + typedef __int16 int16_t; + typedef __int32 int32_t; + typedef __int64 int64_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; + typedef unsigned __int64 uint64_t; + typedef __int8 int_least8_t; + typedef __int16 int_least16_t; + typedef __int32 int_least32_t; + typedef __int64 int_least64_t; + typedef unsigned __int8 uint_least8_t; + typedef unsigned __int16 uint_least16_t; + typedef unsigned __int32 uint_least32_t; + typedef unsigned __int64 uint_least64_t; + typedef __int8 int_fast8_t; + typedef __int16 int_fast16_t; + typedef __int32 int_fast32_t; + typedef __int64 int_fast64_t; + typedef unsigned __int8 uint_fast8_t; + typedef unsigned __int16 uint_fast16_t; + typedef unsigned __int32 uint_fast32_t; + typedef unsigned __int64 uint_fast64_t; + typedef __int64 intmax_t; + typedef unsigned __int64 uintmax_t; +# else +# include +# endif +# if _MSC_VER < 1800 /* MSVC < 2013 */ +# ifndef __cplusplus + typedef unsigned char _Bool; +# endif +# endif +# define _cffi_float_complex_t _Fcomplex /* include for it */ +# define _cffi_double_complex_t _Dcomplex /* include for it */ +#else +# include +# if (defined (__SVR4) && defined (__sun)) || defined(_AIX) || defined(__hpux) +# include +# endif +# define _cffi_float_complex_t float _Complex +# define _cffi_double_complex_t double _Complex +#endif +''' diff --git a/intelliaide/vendor/cffi/verifier.py b/intelliaide/vendor/cffi/verifier.py new file mode 100644 index 0000000..e392a2b --- /dev/null +++ b/intelliaide/vendor/cffi/verifier.py @@ -0,0 +1,306 @@ +# +# DEPRECATED: implementation for ffi.verify() +# +import sys, os, binascii, shutil, io +from . import __version_verifier_modules__ +from . import ffiplatform +from .error import VerificationError + +if sys.version_info >= (3, 3): + import importlib.machinery + def _extension_suffixes(): + return importlib.machinery.EXTENSION_SUFFIXES[:] +else: + import imp + def _extension_suffixes(): + return [suffix for suffix, _, type in imp.get_suffixes() + if type == imp.C_EXTENSION] + + +if sys.version_info >= (3,): + NativeIO = io.StringIO +else: + class NativeIO(io.BytesIO): + def write(self, s): + if isinstance(s, unicode): + s = s.encode('ascii') + super(NativeIO, self).write(s) + + +class Verifier(object): + + def __init__(self, ffi, preamble, tmpdir=None, modulename=None, + ext_package=None, tag='', force_generic_engine=False, + source_extension='.c', flags=None, relative_to=None, **kwds): + if ffi._parser._uses_new_feature: + raise VerificationError( + "feature not supported with ffi.verify(), but only " + "with ffi.set_source(): %s" % (ffi._parser._uses_new_feature,)) + self.ffi = ffi + self.preamble = preamble + if not modulename: + flattened_kwds = ffiplatform.flatten(kwds) + vengine_class = _locate_engine_class(ffi, force_generic_engine) + self._vengine = vengine_class(self) + self._vengine.patch_extension_kwds(kwds) + self.flags = flags + self.kwds = self.make_relative_to(kwds, relative_to) + # + if modulename: + if tag: + raise TypeError("can't specify both 'modulename' and 'tag'") + else: + key = '\x00'.join(['%d.%d' % sys.version_info[:2], + __version_verifier_modules__, + preamble, flattened_kwds] + + ffi._cdefsources) + if sys.version_info >= (3,): + key = key.encode('utf-8') + k1 = hex(binascii.crc32(key[0::2]) & 0xffffffff) + k1 = k1.lstrip('0x').rstrip('L') + k2 = hex(binascii.crc32(key[1::2]) & 0xffffffff) + k2 = k2.lstrip('0').rstrip('L') + modulename = '_cffi_%s_%s%s%s' % (tag, self._vengine._class_key, + k1, k2) + suffix = _get_so_suffixes()[0] + self.tmpdir = tmpdir or _caller_dir_pycache() + self.sourcefilename = os.path.join(self.tmpdir, modulename + source_extension) + self.modulefilename = os.path.join(self.tmpdir, modulename + suffix) + self.ext_package = ext_package + self._has_source = False + self._has_module = False + + def write_source(self, file=None): + """Write the C source code. It is produced in 'self.sourcefilename', + which can be tweaked beforehand.""" + with self.ffi._lock: + if self._has_source and file is None: + raise VerificationError( + "source code already written") + self._write_source(file) + + def compile_module(self): + """Write the C source code (if not done already) and compile it. + This produces a dynamic link library in 'self.modulefilename'.""" + with self.ffi._lock: + if self._has_module: + raise VerificationError("module already compiled") + if not self._has_source: + self._write_source() + self._compile_module() + + def load_library(self): + """Get a C module from this Verifier instance. + Returns an instance of a FFILibrary class that behaves like the + objects returned by ffi.dlopen(), but that delegates all + operations to the C module. If necessary, the C code is written + and compiled first. + """ + with self.ffi._lock: + if not self._has_module: + self._locate_module() + if not self._has_module: + if not self._has_source: + self._write_source() + self._compile_module() + return self._load_library() + + def get_module_name(self): + basename = os.path.basename(self.modulefilename) + # kill both the .so extension and the other .'s, as introduced + # by Python 3: 'basename.cpython-33m.so' + basename = basename.split('.', 1)[0] + # and the _d added in Python 2 debug builds --- but try to be + # conservative and not kill a legitimate _d + if basename.endswith('_d') and hasattr(sys, 'gettotalrefcount'): + basename = basename[:-2] + return basename + + def get_extension(self): + if not self._has_source: + with self.ffi._lock: + if not self._has_source: + self._write_source() + sourcename = ffiplatform.maybe_relative_path(self.sourcefilename) + modname = self.get_module_name() + return ffiplatform.get_extension(sourcename, modname, **self.kwds) + + def generates_python_module(self): + return self._vengine._gen_python_module + + def make_relative_to(self, kwds, relative_to): + if relative_to and os.path.dirname(relative_to): + dirname = os.path.dirname(relative_to) + kwds = kwds.copy() + for key in ffiplatform.LIST_OF_FILE_NAMES: + if key in kwds: + lst = kwds[key] + if not isinstance(lst, (list, tuple)): + raise TypeError("keyword '%s' should be a list or tuple" + % (key,)) + lst = [os.path.join(dirname, fn) for fn in lst] + kwds[key] = lst + return kwds + + # ---------- + + def _locate_module(self): + if not os.path.isfile(self.modulefilename): + if self.ext_package: + try: + pkg = __import__(self.ext_package, None, None, ['__doc__']) + except ImportError: + return # cannot import the package itself, give up + # (e.g. it might be called differently before installation) + path = pkg.__path__ + else: + path = None + filename = self._vengine.find_module(self.get_module_name(), path, + _get_so_suffixes()) + if filename is None: + return + self.modulefilename = filename + self._vengine.collect_types() + self._has_module = True + + def _write_source_to(self, file): + self._vengine._f = file + try: + self._vengine.write_source_to_f() + finally: + del self._vengine._f + + def _write_source(self, file=None): + if file is not None: + self._write_source_to(file) + else: + # Write our source file to an in memory file. + f = NativeIO() + self._write_source_to(f) + source_data = f.getvalue() + + # Determine if this matches the current file + if os.path.exists(self.sourcefilename): + with open(self.sourcefilename, "r") as fp: + needs_written = not (fp.read() == source_data) + else: + needs_written = True + + # Actually write the file out if it doesn't match + if needs_written: + _ensure_dir(self.sourcefilename) + with open(self.sourcefilename, "w") as fp: + fp.write(source_data) + + # Set this flag + self._has_source = True + + def _compile_module(self): + # compile this C source + tmpdir = os.path.dirname(self.sourcefilename) + outputfilename = ffiplatform.compile(tmpdir, self.get_extension()) + try: + same = ffiplatform.samefile(outputfilename, self.modulefilename) + except OSError: + same = False + if not same: + _ensure_dir(self.modulefilename) + shutil.move(outputfilename, self.modulefilename) + self._has_module = True + + def _load_library(self): + assert self._has_module + if self.flags is not None: + return self._vengine.load_library(self.flags) + else: + return self._vengine.load_library() + +# ____________________________________________________________ + +_FORCE_GENERIC_ENGINE = False # for tests + +def _locate_engine_class(ffi, force_generic_engine): + if _FORCE_GENERIC_ENGINE: + force_generic_engine = True + if not force_generic_engine: + if '__pypy__' in sys.builtin_module_names: + force_generic_engine = True + else: + try: + import _cffi_backend + except ImportError: + _cffi_backend = '?' + if ffi._backend is not _cffi_backend: + force_generic_engine = True + if force_generic_engine: + from . import vengine_gen + return vengine_gen.VGenericEngine + else: + from . import vengine_cpy + return vengine_cpy.VCPythonEngine + +# ____________________________________________________________ + +_TMPDIR = None + +def _caller_dir_pycache(): + if _TMPDIR: + return _TMPDIR + result = os.environ.get('CFFI_TMPDIR') + if result: + return result + filename = sys._getframe(2).f_code.co_filename + return os.path.abspath(os.path.join(os.path.dirname(filename), + '__pycache__')) + +def set_tmpdir(dirname): + """Set the temporary directory to use instead of __pycache__.""" + global _TMPDIR + _TMPDIR = dirname + +def cleanup_tmpdir(tmpdir=None, keep_so=False): + """Clean up the temporary directory by removing all files in it + called `_cffi_*.{c,so}` as well as the `build` subdirectory.""" + tmpdir = tmpdir or _caller_dir_pycache() + try: + filelist = os.listdir(tmpdir) + except OSError: + return + if keep_so: + suffix = '.c' # only remove .c files + else: + suffix = _get_so_suffixes()[0].lower() + for fn in filelist: + if fn.lower().startswith('_cffi_') and ( + fn.lower().endswith(suffix) or fn.lower().endswith('.c')): + try: + os.unlink(os.path.join(tmpdir, fn)) + except OSError: + pass + clean_dir = [os.path.join(tmpdir, 'build')] + for dir in clean_dir: + try: + for fn in os.listdir(dir): + fn = os.path.join(dir, fn) + if os.path.isdir(fn): + clean_dir.append(fn) + else: + os.unlink(fn) + except OSError: + pass + +def _get_so_suffixes(): + suffixes = _extension_suffixes() + if not suffixes: + # bah, no C_EXTENSION available. Occurs on pypy without cpyext + if sys.platform == 'win32': + suffixes = [".pyd"] + else: + suffixes = [".so"] + + return suffixes + +def _ensure_dir(filename): + dirname = os.path.dirname(filename) + if dirname and not os.path.isdir(dirname): + os.makedirs(dirname) diff --git a/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/INSTALLER b/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/METADATA b/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/METADATA new file mode 100644 index 0000000..6b5a360 --- /dev/null +++ b/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/METADATA @@ -0,0 +1,808 @@ +Metadata-Version: 2.4 +Name: charset-normalizer +Version: 3.4.7 +Summary: The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet. +Author-email: "Ahmed R. TAHRI" +Maintainer-email: "Ahmed R. TAHRI" +License: MIT +Project-URL: Changelog, https://github.com/jawah/charset_normalizer/blob/master/CHANGELOG.md +Project-URL: Documentation, https://charset-normalizer.readthedocs.io/ +Project-URL: Code, https://github.com/jawah/charset_normalizer +Project-URL: Issue tracker, https://github.com/jawah/charset_normalizer/issues +Keywords: encoding,charset,charset-detector,detector,normalization,unicode,chardet,detect +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Programming Language :: Python :: Free Threading :: 4 - Resilient +Classifier: Topic :: Text Processing :: Linguistic +Classifier: Topic :: Utilities +Classifier: Typing :: Typed +Requires-Python: >=3.7 +Description-Content-Type: text/markdown +License-File: LICENSE +Provides-Extra: unicode-backport +Dynamic: license-file + +

Charset Detection, for Everyone 👋

+ +

+ The Real First Universal Charset Detector
+ + + + + Download Count Total + + + + +

+

+ Featured Packages
+ + Static Badge + + + Static Badge + +

+

+ In other language (unofficial port - by the community)
+ + Static Badge + +

+ +> A library that helps you read text from an unknown charset encoding.
Motivated by `chardet`, +> I'm trying to resolve the issue by taking a new approach. +> All IANA character set names for which the Python core library provides codecs are supported. +> You can also register your own set of codecs, and yes, it would work as-is. + +

+ >>>>> 👉 Try Me Online Now, Then Adopt Me 👈 <<<<< +

+ +This project offers you an alternative to **Universal Charset Encoding Detector**, also known as **Chardet**. + +| Feature | [Chardet](https://github.com/chardet/chardet) | Charset Normalizer | [cChardet](https://github.com/PyYoshi/cChardet) | +|--------------------------------------------------|:---------------------------------------------:|:-----------------------------------------------------------------------------------------------:|:-----------------------------------------------:| +| `Fast` | ✅ | ✅ | ✅ | +| `Universal`[^1] | ❌ | ✅ | ❌ | +| `Reliable` **without** distinguishable standards | ✅ | ✅ | ✅ | +| `Reliable` **with** distinguishable standards | ✅ | ✅ | ✅ | +| `License` | _Disputed_[^2]
_restrictive_ | MIT | MPL-1.1
_restrictive_ | +| `Native Python` | ✅ | ✅ | ❌ | +| `Detect spoken language` | ✅ | ✅ | N/A | +| `UnicodeDecodeError Safety` | ✅ | ✅ | ❌ | +| `Whl Size (min)` | 500 kB | 150 kB | ~200 kB | +| `Supported Encoding` | 99 | [99](https://charset-normalizer.readthedocs.io/en/latest/user/support.html#supported-encodings) | 40 | +| `Can register custom encoding` | ❌ | ✅ | ❌ | + +

+Reading Normalized TextCat Reading Text +

+ +[^1]: They are clearly using specific code for a specific encoding even if covering most of used one. +[^2]: Chardet 7.0+ was relicensed from LGPL-2.1 to MIT following an AI-assisted rewrite. This relicensing is disputed on two independent grounds: **(a)** the original author [contests](https://github.com/chardet/chardet/issues/327) that the maintainer had the right to relicense, arguing the rewrite is a derivative work of the LGPL-licensed codebase since it was not a clean room implementation; **(b)** the copyright claim itself is [questionable](https://github.com/chardet/chardet/issues/334) given the code was primarily generated by an LLM, and AI-generated output may not be copyrightable under most jurisdictions. Either issue alone could undermine the MIT license. Beyond licensing, the rewrite raises questions about responsible use of AI in open source: key architectural ideas pioneered by charset-normalizer - notably decode-first validity filtering (our foundational approach since v1) and encoding pairwise similarity with the same algorithm and threshold — surfaced in chardet 7 without acknowledgment. The project also imported test files from charset-normalizer to train and benchmark against it, then claimed superior accuracy on those very files. Charset-normalizer has always been MIT-licensed, encoding-agnostic by design, and built on a verifiable human-authored history. + +## ⚡ Performance + +This package offer better performances (99th, and 95th) against Chardet. Here are some numbers. + +| Package | Accuracy | Mean per file (ms) | File per sec (est) | +|---------------------------------------------------|:--------:|:------------------:|:------------------:| +| [chardet 7.1](https://github.com/chardet/chardet) | 89 % | 3 ms | 333 file/sec | +| charset-normalizer | **97 %** | 3 ms | 333 file/sec | + +| Package | 99th percentile | 95th percentile | 50th percentile | +|---------------------------------------------------|:---------------:|:---------------:|:---------------:| +| [chardet 7.1](https://github.com/chardet/chardet) | 32 ms | 17 ms | < 1 ms | +| charset-normalizer | 16 ms | 10 ms | 1 ms | + +_updated as of March 2026 using CPython 3.12, Charset-Normalizer 3.4.6, and Chardet 7.1.0_ + +~Chardet's performance on larger file (1MB+) are very poor. Expect huge difference on large payload.~ No longer the case since Chardet 7.0+ + +> Stats are generated using 400+ files using default parameters. More details on used files, see GHA workflows. +> And yes, these results might change at any time. The dataset can be updated to include more files. +> The actual delays heavily depends on your CPU capabilities. The factors should remain the same. +> Chardet claims on his documentation to have a greater accuracy than us based on the dataset they trained Chardet on(...) +> Well, it's normal, the opposite would have been worrying. Whereas charset-normalizer don't train on anything, our solution +> is based on a completely different algorithm, still heuristic through, it does not need weights across every encoding tables. + +## ✨ Installation + +Using pip: + +```sh +pip install charset-normalizer -U +``` + +## 🚀 Basic Usage + +### CLI +This package comes with a CLI. + +``` +usage: normalizer [-h] [-v] [-a] [-n] [-m] [-r] [-f] [-t THRESHOLD] + file [file ...] + +The Real First Universal Charset Detector. Discover originating encoding used +on text file. Normalize text to unicode. + +positional arguments: + files File(s) to be analysed + +optional arguments: + -h, --help show this help message and exit + -v, --verbose Display complementary information about file if any. + Stdout will contain logs about the detection process. + -a, --with-alternative + Output complementary possibilities if any. Top-level + JSON WILL be a list. + -n, --normalize Permit to normalize input file. If not set, program + does not write anything. + -m, --minimal Only output the charset detected to STDOUT. Disabling + JSON output. + -r, --replace Replace file when trying to normalize it instead of + creating a new one. + -f, --force Replace file without asking if you are sure, use this + flag with caution. + -t THRESHOLD, --threshold THRESHOLD + Define a custom maximum amount of chaos allowed in + decoded content. 0. <= chaos <= 1. + --version Show version information and exit. +``` + +```bash +normalizer ./data/sample.1.fr.srt +``` + +or + +```bash +python -m charset_normalizer ./data/sample.1.fr.srt +``` + +🎉 Since version 1.4.0 the CLI produce easily usable stdout result in JSON format. + +```json +{ + "path": "/home/default/projects/charset_normalizer/data/sample.1.fr.srt", + "encoding": "cp1252", + "encoding_aliases": [ + "1252", + "windows_1252" + ], + "alternative_encodings": [ + "cp1254", + "cp1256", + "cp1258", + "iso8859_14", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_9", + "latin_1", + "mbcs" + ], + "language": "French", + "alphabets": [ + "Basic Latin", + "Latin-1 Supplement" + ], + "has_sig_or_bom": false, + "chaos": 0.149, + "coherence": 97.152, + "unicode_path": null, + "is_preferred": true +} +``` + +### Python +*Just print out normalized text* +```python +from charset_normalizer import from_path + +results = from_path('./my_subtitle.srt') + +print(str(results.best())) +``` + +*Upgrade your code without effort* +```python +from charset_normalizer import detect +``` + +The above code will behave the same as **chardet**. We ensure that we offer the best (reasonable) BC result possible. + +See the docs for advanced usage : [readthedocs.io](https://charset-normalizer.readthedocs.io/en/latest/) + +## 😇 Why + +When I started using Chardet, I noticed that it was not suited to my expectations, and I wanted to propose a +reliable alternative using a completely different method. Also! I never back down on a good challenge! + +I **don't care** about the **originating charset** encoding, because **two different tables** can +produce **two identical rendered string.** +What I want is to get readable text, the best I can. + +In a way, **I'm brute forcing text decoding.** How cool is that ? 😎 + +Don't confuse package **ftfy** with charset-normalizer or chardet. ftfy goal is to repair Unicode string whereas charset-normalizer to convert raw file in unknown encoding to unicode. + +## 🍰 How + + - Discard all charset encoding table that could not fit the binary content. + - Measure noise, or the mess once opened (by chunks) with a corresponding charset encoding. + - Extract matches with the lowest mess detected. + - Additionally, we measure coherence / probe for a language. + +**Wait a minute**, what is noise/mess and coherence according to **YOU ?** + +*Noise :* I opened hundred of text files, **written by humans**, with the wrong encoding table. **I observed**, then +**I established** some ground rules about **what is obvious** when **it seems like** a mess (aka. defining noise in rendered text). + I know that my interpretation of what is noise is probably incomplete, feel free to contribute in order to + improve or rewrite it. + +*Coherence :* For each language there is on earth, we have computed ranked letter appearance occurrences (the best we can). So I thought +that intel is worth something here. So I use those records against decoded text to check if I can detect intelligent design. + +## ⚡ Known limitations + + - Language detection is unreliable when text contains two or more languages sharing identical letters. (eg. HTML (english tags) + Turkish content (Sharing Latin characters)) + - Every charset detector heavily depends on sufficient content. In common cases, do not bother run detection on very tiny content. + +## ⚠️ About Python EOLs + +**If you are running:** + +- Python >=2.7,<3.5: Unsupported +- Python 3.5: charset-normalizer < 2.1 +- Python 3.6: charset-normalizer < 3.1 + +Upgrade your Python interpreter as soon as possible. + +## 👤 Contributing + +Contributions, issues and feature requests are very much welcome.
+Feel free to check [issues page](https://github.com/ousret/charset_normalizer/issues) if you want to contribute. + +## 📝 License + +Copyright © [Ahmed TAHRI @Ousret](https://github.com/Ousret).
+This project is [MIT](https://github.com/Ousret/charset_normalizer/blob/master/LICENSE) licensed. + +Characters frequencies used in this project © 2012 [Denny Vrandečić](http://simia.net/letters/) + +## 💼 For Enterprise + +Professional support for charset-normalizer is available as part of the [Tidelift +Subscription][1]. Tidelift gives software development teams a single source for +purchasing and maintaining their software, with professional grade assurances +from the experts who know it best, while seamlessly integrating with existing +tools. + +[1]: https://tidelift.com/subscription/pkg/pypi-charset-normalizer?utm_source=pypi-charset-normalizer&utm_medium=readme + +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/7297/badge)](https://www.bestpractices.dev/projects/7297) + +# Changelog +All notable changes to charset-normalizer will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## [3.4.7](https://github.com/Ousret/charset_normalizer/compare/3.4.6...3.4.7) (2026-04-02) + +### Changed +- Pre-built optimized version using mypy[c] v1.20. +- Relax `setuptools` constraint to `setuptools>=68,<82.1`. + +### Fixed +- Correctly remove SIG remnant in utf-7 decoded string. (#718) (#716) + +## [3.4.6](https://github.com/Ousret/charset_normalizer/compare/3.4.5...3.4.6) (2026-03-15) + +### Changed +- Flattened the logic in `charset_normalizer.md` for higher performance. Removed `eligible(..)` and `feed(...)` + in favor of `feed_info(...)`. +- Raised upper bound for mypy[c] to 1.20, for our optimized version. +- Updated `UNICODE_RANGES_COMBINED` using Unicode blocks v17. + +### Fixed +- Edge case where noise difference between two candidates can be almost insignificant. (#672) +- CLI `--normalize` writing to wrong path when passing multiple files in. (#702) + +### Misc +- Freethreaded pre-built wheels now shipped in PyPI starting with 3.14t. (#616) + +## [3.4.5](https://github.com/Ousret/charset_normalizer/compare/3.4.4...3.4.5) (2026-03-06) + +### Changed +- Update `setuptools` constraint to `setuptools>=68,<=82`. +- Raised upper bound of mypyc for the optional pre-built extension to v1.19.1 + +### Fixed +- Add explicit link to lib math in our optimized build. (#692) +- Logger level not restored correctly for empty byte sequences. (#701) +- TypeError when passing bytearray to from_bytes. (#703) + +### Misc +- Applied safe micro-optimizations in both our noise detector and language detector. +- Rewrote the `query_yes_no` function (inside CLI) to avoid using ambiguous licensed code. +- Added `cd.py` submodule into mypyc optional compilation to reduce further the performance impact. + +## [3.4.4](https://github.com/Ousret/charset_normalizer/compare/3.4.2...3.4.4) (2025-10-13) + +### Changed +- Bound `setuptools` to a specific constraint `setuptools>=68,<=81`. +- Raised upper bound of mypyc for the optional pre-built extension to v1.18.2 + +### Removed +- `setuptools-scm` as a build dependency. + +### Misc +- Enforced hashes in `dev-requirements.txt` and created `ci-requirements.txt` for security purposes. +- Additional pre-built wheels for riscv64, s390x, and armv7l architectures. +- Restore ` multiple.intoto.jsonl` in GitHub releases in addition to individual attestation file per wheel. + +## [3.4.3](https://github.com/Ousret/charset_normalizer/compare/3.4.2...3.4.3) (2025-08-09) + +### Changed +- mypy(c) is no longer a required dependency at build time if `CHARSET_NORMALIZER_USE_MYPYC` isn't set to `1`. (#595) (#583) +- automatically lower confidence on small bytes samples that are not Unicode in `detect` output legacy function. (#391) + +### Added +- Custom build backend to overcome inability to mark mypy as an optional dependency in the build phase. +- Support for Python 3.14 + +### Fixed +- sdist archive contained useless directories. +- automatically fallback on valid UTF-16 or UTF-32 even if the md says it's noisy. (#633) + +### Misc +- SBOM are automatically published to the relevant GitHub release to comply with regulatory changes. + Each published wheel comes with its SBOM. We choose CycloneDX as the format. +- Prebuilt optimized wheel are no longer distributed by default for CPython 3.7 due to a change in cibuildwheel. + +## [3.4.2](https://github.com/Ousret/charset_normalizer/compare/3.4.1...3.4.2) (2025-05-02) + +### Fixed +- Addressed the DeprecationWarning in our CLI regarding `argparse.FileType` by backporting the target class into the package. (#591) +- Improved the overall reliability of the detector with CJK Ideographs. (#605) (#587) + +### Changed +- Optional mypyc compilation upgraded to version 1.15 for Python >= 3.8 + +## [3.4.1](https://github.com/Ousret/charset_normalizer/compare/3.4.0...3.4.1) (2024-12-24) + +### Changed +- Project metadata are now stored using `pyproject.toml` instead of `setup.cfg` using setuptools as the build backend. +- Enforce annotation delayed loading for a simpler and consistent types in the project. +- Optional mypyc compilation upgraded to version 1.14 for Python >= 3.8 + +### Added +- pre-commit configuration. +- noxfile. + +### Removed +- `build-requirements.txt` as per using `pyproject.toml` native build configuration. +- `bin/integration.py` and `bin/serve.py` in favor of downstream integration test (see noxfile). +- `setup.cfg` in favor of `pyproject.toml` metadata configuration. +- Unused `utils.range_scan` function. + +### Fixed +- Converting content to Unicode bytes may insert `utf_8` instead of preferred `utf-8`. (#572) +- Deprecation warning "'count' is passed as positional argument" when converting to Unicode bytes on Python 3.13+ + +## [3.4.0](https://github.com/Ousret/charset_normalizer/compare/3.3.2...3.4.0) (2024-10-08) + +### Added +- Argument `--no-preemptive` in the CLI to prevent the detector to search for hints. +- Support for Python 3.13 (#512) + +### Fixed +- Relax the TypeError exception thrown when trying to compare a CharsetMatch with anything else than a CharsetMatch. +- Improved the general reliability of the detector based on user feedbacks. (#520) (#509) (#498) (#407) (#537) +- Declared charset in content (preemptive detection) not changed when converting to utf-8 bytes. (#381) + +## [3.3.2](https://github.com/Ousret/charset_normalizer/compare/3.3.1...3.3.2) (2023-10-31) + +### Fixed +- Unintentional memory usage regression when using large payload that match several encoding (#376) +- Regression on some detection case showcased in the documentation (#371) + +### Added +- Noise (md) probe that identify malformed arabic representation due to the presence of letters in isolated form (credit to my wife) + +## [3.3.1](https://github.com/Ousret/charset_normalizer/compare/3.3.0...3.3.1) (2023-10-22) + +### Changed +- Optional mypyc compilation upgraded to version 1.6.1 for Python >= 3.8 +- Improved the general detection reliability based on reports from the community + +## [3.3.0](https://github.com/Ousret/charset_normalizer/compare/3.2.0...3.3.0) (2023-09-30) + +### Added +- Allow to execute the CLI (e.g. normalizer) through `python -m charset_normalizer.cli` or `python -m charset_normalizer` +- Support for 9 forgotten encoding that are supported by Python but unlisted in `encoding.aliases` as they have no alias (#323) + +### Removed +- (internal) Redundant utils.is_ascii function and unused function is_private_use_only +- (internal) charset_normalizer.assets is moved inside charset_normalizer.constant + +### Changed +- (internal) Unicode code blocks in constants are updated using the latest v15.0.0 definition to improve detection +- Optional mypyc compilation upgraded to version 1.5.1 for Python >= 3.8 + +### Fixed +- Unable to properly sort CharsetMatch when both chaos/noise and coherence were close due to an unreachable condition in \_\_lt\_\_ (#350) + +## [3.2.0](https://github.com/Ousret/charset_normalizer/compare/3.1.0...3.2.0) (2023-06-07) + +### Changed +- Typehint for function `from_path` no longer enforce `PathLike` as its first argument +- Minor improvement over the global detection reliability + +### Added +- Introduce function `is_binary` that relies on main capabilities, and optimized to detect binaries +- Propagate `enable_fallback` argument throughout `from_bytes`, `from_path`, and `from_fp` that allow a deeper control over the detection (default True) +- Explicit support for Python 3.12 + +### Fixed +- Edge case detection failure where a file would contain 'very-long' camel cased word (Issue #289) + +## [3.1.0](https://github.com/Ousret/charset_normalizer/compare/3.0.1...3.1.0) (2023-03-06) + +### Added +- Argument `should_rename_legacy` for legacy function `detect` and disregard any new arguments without errors (PR #262) + +### Removed +- Support for Python 3.6 (PR #260) + +### Changed +- Optional speedup provided by mypy/c 1.0.1 + +## [3.0.1](https://github.com/Ousret/charset_normalizer/compare/3.0.0...3.0.1) (2022-11-18) + +### Fixed +- Multi-bytes cutter/chunk generator did not always cut correctly (PR #233) + +### Changed +- Speedup provided by mypy/c 0.990 on Python >= 3.7 + +## [3.0.0](https://github.com/Ousret/charset_normalizer/compare/2.1.1...3.0.0) (2022-10-20) + +### Added +- Extend the capability of explain=True when cp_isolation contains at most two entries (min one), will log in details of the Mess-detector results +- Support for alternative language frequency set in charset_normalizer.assets.FREQUENCIES +- Add parameter `language_threshold` in `from_bytes`, `from_path` and `from_fp` to adjust the minimum expected coherence ratio +- `normalizer --version` now specify if current version provide extra speedup (meaning mypyc compilation whl) + +### Changed +- Build with static metadata using 'build' frontend +- Make the language detection stricter +- Optional: Module `md.py` can be compiled using Mypyc to provide an extra speedup up to 4x faster than v2.1 + +### Fixed +- CLI with opt --normalize fail when using full path for files +- TooManyAccentuatedPlugin induce false positive on the mess detection when too few alpha character have been fed to it +- Sphinx warnings when generating the documentation + +### Removed +- Coherence detector no longer return 'Simple English' instead return 'English' +- Coherence detector no longer return 'Classical Chinese' instead return 'Chinese' +- Breaking: Method `first()` and `best()` from CharsetMatch +- UTF-7 will no longer appear as "detected" without a recognized SIG/mark (is unreliable/conflict with ASCII) +- Breaking: Class aliases CharsetDetector, CharsetDoctor, CharsetNormalizerMatch and CharsetNormalizerMatches +- Breaking: Top-level function `normalize` +- Breaking: Properties `chaos_secondary_pass`, `coherence_non_latin` and `w_counter` from CharsetMatch +- Support for the backport `unicodedata2` + +## [3.0.0rc1](https://github.com/Ousret/charset_normalizer/compare/3.0.0b2...3.0.0rc1) (2022-10-18) + +### Added +- Extend the capability of explain=True when cp_isolation contains at most two entries (min one), will log in details of the Mess-detector results +- Support for alternative language frequency set in charset_normalizer.assets.FREQUENCIES +- Add parameter `language_threshold` in `from_bytes`, `from_path` and `from_fp` to adjust the minimum expected coherence ratio + +### Changed +- Build with static metadata using 'build' frontend +- Make the language detection stricter + +### Fixed +- CLI with opt --normalize fail when using full path for files +- TooManyAccentuatedPlugin induce false positive on the mess detection when too few alpha character have been fed to it + +### Removed +- Coherence detector no longer return 'Simple English' instead return 'English' +- Coherence detector no longer return 'Classical Chinese' instead return 'Chinese' + +## [3.0.0b2](https://github.com/Ousret/charset_normalizer/compare/3.0.0b1...3.0.0b2) (2022-08-21) + +### Added +- `normalizer --version` now specify if current version provide extra speedup (meaning mypyc compilation whl) + +### Removed +- Breaking: Method `first()` and `best()` from CharsetMatch +- UTF-7 will no longer appear as "detected" without a recognized SIG/mark (is unreliable/conflict with ASCII) + +### Fixed +- Sphinx warnings when generating the documentation + +## [3.0.0b1](https://github.com/Ousret/charset_normalizer/compare/2.1.0...3.0.0b1) (2022-08-15) + +### Changed +- Optional: Module `md.py` can be compiled using Mypyc to provide an extra speedup up to 4x faster than v2.1 + +### Removed +- Breaking: Class aliases CharsetDetector, CharsetDoctor, CharsetNormalizerMatch and CharsetNormalizerMatches +- Breaking: Top-level function `normalize` +- Breaking: Properties `chaos_secondary_pass`, `coherence_non_latin` and `w_counter` from CharsetMatch +- Support for the backport `unicodedata2` + +## [2.1.1](https://github.com/Ousret/charset_normalizer/compare/2.1.0...2.1.1) (2022-08-19) + +### Deprecated +- Function `normalize` scheduled for removal in 3.0 + +### Changed +- Removed useless call to decode in fn is_unprintable (#206) + +### Fixed +- Third-party library (i18n xgettext) crashing not recognizing utf_8 (PEP 263) with underscore from [@aleksandernovikov](https://github.com/aleksandernovikov) (#204) + +## [2.1.0](https://github.com/Ousret/charset_normalizer/compare/2.0.12...2.1.0) (2022-06-19) + +### Added +- Output the Unicode table version when running the CLI with `--version` (PR #194) + +### Changed +- Re-use decoded buffer for single byte character sets from [@nijel](https://github.com/nijel) (PR #175) +- Fixing some performance bottlenecks from [@deedy5](https://github.com/deedy5) (PR #183) + +### Fixed +- Workaround potential bug in cpython with Zero Width No-Break Space located in Arabic Presentation Forms-B, Unicode 1.1 not acknowledged as space (PR #175) +- CLI default threshold aligned with the API threshold from [@oleksandr-kuzmenko](https://github.com/oleksandr-kuzmenko) (PR #181) + +### Removed +- Support for Python 3.5 (PR #192) + +### Deprecated +- Use of backport unicodedata from `unicodedata2` as Python is quickly catching up, scheduled for removal in 3.0 (PR #194) + +## [2.0.12](https://github.com/Ousret/charset_normalizer/compare/2.0.11...2.0.12) (2022-02-12) + +### Fixed +- ASCII miss-detection on rare cases (PR #170) + +## [2.0.11](https://github.com/Ousret/charset_normalizer/compare/2.0.10...2.0.11) (2022-01-30) + +### Added +- Explicit support for Python 3.11 (PR #164) + +### Changed +- The logging behavior have been completely reviewed, now using only TRACE and DEBUG levels (PR #163 #165) + +## [2.0.10](https://github.com/Ousret/charset_normalizer/compare/2.0.9...2.0.10) (2022-01-04) + +### Fixed +- Fallback match entries might lead to UnicodeDecodeError for large bytes sequence (PR #154) + +### Changed +- Skipping the language-detection (CD) on ASCII (PR #155) + +## [2.0.9](https://github.com/Ousret/charset_normalizer/compare/2.0.8...2.0.9) (2021-12-03) + +### Changed +- Moderating the logging impact (since 2.0.8) for specific environments (PR #147) + +### Fixed +- Wrong logging level applied when setting kwarg `explain` to True (PR #146) + +## [2.0.8](https://github.com/Ousret/charset_normalizer/compare/2.0.7...2.0.8) (2021-11-24) +### Changed +- Improvement over Vietnamese detection (PR #126) +- MD improvement on trailing data and long foreign (non-pure latin) data (PR #124) +- Efficiency improvements in cd/alphabet_languages from [@adbar](https://github.com/adbar) (PR #122) +- call sum() without an intermediary list following PEP 289 recommendations from [@adbar](https://github.com/adbar) (PR #129) +- Code style as refactored by Sourcery-AI (PR #131) +- Minor adjustment on the MD around european words (PR #133) +- Remove and replace SRTs from assets / tests (PR #139) +- Initialize the library logger with a `NullHandler` by default from [@nmaynes](https://github.com/nmaynes) (PR #135) +- Setting kwarg `explain` to True will add provisionally (bounded to function lifespan) a specific stream handler (PR #135) + +### Fixed +- Fix large (misleading) sequence giving UnicodeDecodeError (PR #137) +- Avoid using too insignificant chunk (PR #137) + +### Added +- Add and expose function `set_logging_handler` to configure a specific StreamHandler from [@nmaynes](https://github.com/nmaynes) (PR #135) +- Add `CHANGELOG.md` entries, format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) (PR #141) + +## [2.0.7](https://github.com/Ousret/charset_normalizer/compare/2.0.6...2.0.7) (2021-10-11) +### Added +- Add support for Kazakh (Cyrillic) language detection (PR #109) + +### Changed +- Further, improve inferring the language from a given single-byte code page (PR #112) +- Vainly trying to leverage PEP263 when PEP3120 is not supported (PR #116) +- Refactoring for potential performance improvements in loops from [@adbar](https://github.com/adbar) (PR #113) +- Various detection improvement (MD+CD) (PR #117) + +### Removed +- Remove redundant logging entry about detected language(s) (PR #115) + +### Fixed +- Fix a minor inconsistency between Python 3.5 and other versions regarding language detection (PR #117 #102) + +## [2.0.6](https://github.com/Ousret/charset_normalizer/compare/2.0.5...2.0.6) (2021-09-18) +### Fixed +- Unforeseen regression with the loss of the backward-compatibility with some older minor of Python 3.5.x (PR #100) +- Fix CLI crash when using --minimal output in certain cases (PR #103) + +### Changed +- Minor improvement to the detection efficiency (less than 1%) (PR #106 #101) + +## [2.0.5](https://github.com/Ousret/charset_normalizer/compare/2.0.4...2.0.5) (2021-09-14) +### Changed +- The project now comply with: flake8, mypy, isort and black to ensure a better overall quality (PR #81) +- The BC-support with v1.x was improved, the old staticmethods are restored (PR #82) +- The Unicode detection is slightly improved (PR #93) +- Add syntax sugar \_\_bool\_\_ for results CharsetMatches list-container (PR #91) + +### Removed +- The project no longer raise warning on tiny content given for detection, will be simply logged as warning instead (PR #92) + +### Fixed +- In some rare case, the chunks extractor could cut in the middle of a multi-byte character and could mislead the mess detection (PR #95) +- Some rare 'space' characters could trip up the UnprintablePlugin/Mess detection (PR #96) +- The MANIFEST.in was not exhaustive (PR #78) + +## [2.0.4](https://github.com/Ousret/charset_normalizer/compare/2.0.3...2.0.4) (2021-07-30) +### Fixed +- The CLI no longer raise an unexpected exception when no encoding has been found (PR #70) +- Fix accessing the 'alphabets' property when the payload contains surrogate characters (PR #68) +- The logger could mislead (explain=True) on detected languages and the impact of one MBCS match (PR #72) +- Submatch factoring could be wrong in rare edge cases (PR #72) +- Multiple files given to the CLI were ignored when publishing results to STDOUT. (After the first path) (PR #72) +- Fix line endings from CRLF to LF for certain project files (PR #67) + +### Changed +- Adjust the MD to lower the sensitivity, thus improving the global detection reliability (PR #69 #76) +- Allow fallback on specified encoding if any (PR #71) + +## [2.0.3](https://github.com/Ousret/charset_normalizer/compare/2.0.2...2.0.3) (2021-07-16) +### Changed +- Part of the detection mechanism has been improved to be less sensitive, resulting in more accurate detection results. Especially ASCII. (PR #63) +- According to the community wishes, the detection will fall back on ASCII or UTF-8 in a last-resort case. (PR #64) + +## [2.0.2](https://github.com/Ousret/charset_normalizer/compare/2.0.1...2.0.2) (2021-07-15) +### Fixed +- Empty/Too small JSON payload miss-detection fixed. Report from [@tseaver](https://github.com/tseaver) (PR #59) + +### Changed +- Don't inject unicodedata2 into sys.modules from [@akx](https://github.com/akx) (PR #57) + +## [2.0.1](https://github.com/Ousret/charset_normalizer/compare/2.0.0...2.0.1) (2021-07-13) +### Fixed +- Make it work where there isn't a filesystem available, dropping assets frequencies.json. Report from [@sethmlarson](https://github.com/sethmlarson). (PR #55) +- Using explain=False permanently disable the verbose output in the current runtime (PR #47) +- One log entry (language target preemptive) was not show in logs when using explain=True (PR #47) +- Fix undesired exception (ValueError) on getitem of instance CharsetMatches (PR #52) + +### Changed +- Public function normalize default args values were not aligned with from_bytes (PR #53) + +### Added +- You may now use charset aliases in cp_isolation and cp_exclusion arguments (PR #47) + +## [2.0.0](https://github.com/Ousret/charset_normalizer/compare/1.4.1...2.0.0) (2021-07-02) +### Changed +- 4x to 5 times faster than the previous 1.4.0 release. At least 2x faster than Chardet. +- Accent has been made on UTF-8 detection, should perform rather instantaneous. +- The backward compatibility with Chardet has been greatly improved. The legacy detect function returns an identical charset name whenever possible. +- The detection mechanism has been slightly improved, now Turkish content is detected correctly (most of the time) +- The program has been rewritten to ease the readability and maintainability. (+Using static typing)+ +- utf_7 detection has been reinstated. + +### Removed +- This package no longer require anything when used with Python 3.5 (Dropped cached_property) +- Removed support for these languages: Catalan, Esperanto, Kazakh, Baque, Volapük, Azeri, Galician, Nynorsk, Macedonian, and Serbocroatian. +- The exception hook on UnicodeDecodeError has been removed. + +### Deprecated +- Methods coherence_non_latin, w_counter, chaos_secondary_pass of the class CharsetMatch are now deprecated and scheduled for removal in v3.0 + +### Fixed +- The CLI output used the relative path of the file(s). Should be absolute. + +## [1.4.1](https://github.com/Ousret/charset_normalizer/compare/1.4.0...1.4.1) (2021-05-28) +### Fixed +- Logger configuration/usage no longer conflict with others (PR #44) + +## [1.4.0](https://github.com/Ousret/charset_normalizer/compare/1.3.9...1.4.0) (2021-05-21) +### Removed +- Using standard logging instead of using the package loguru. +- Dropping nose test framework in favor of the maintained pytest. +- Choose to not use dragonmapper package to help with gibberish Chinese/CJK text. +- Require cached_property only for Python 3.5 due to constraint. Dropping for every other interpreter version. +- Stop support for UTF-7 that does not contain a SIG. +- Dropping PrettyTable, replaced with pure JSON output in CLI. + +### Fixed +- BOM marker in a CharsetNormalizerMatch instance could be False in rare cases even if obviously present. Due to the sub-match factoring process. +- Not searching properly for the BOM when trying utf32/16 parent codec. + +### Changed +- Improving the package final size by compressing frequencies.json. +- Huge improvement over the larges payload. + +### Added +- CLI now produces JSON consumable output. +- Return ASCII if given sequences fit. Given reasonable confidence. + +## [1.3.9](https://github.com/Ousret/charset_normalizer/compare/1.3.8...1.3.9) (2021-05-13) + +### Fixed +- In some very rare cases, you may end up getting encode/decode errors due to a bad bytes payload (PR #40) + +## [1.3.8](https://github.com/Ousret/charset_normalizer/compare/1.3.7...1.3.8) (2021-05-12) + +### Fixed +- Empty given payload for detection may cause an exception if trying to access the `alphabets` property. (PR #39) + +## [1.3.7](https://github.com/Ousret/charset_normalizer/compare/1.3.6...1.3.7) (2021-05-12) + +### Fixed +- The legacy detect function should return UTF-8-SIG if sig is present in the payload. (PR #38) + +## [1.3.6](https://github.com/Ousret/charset_normalizer/compare/1.3.5...1.3.6) (2021-02-09) + +### Changed +- Amend the previous release to allow prettytable 2.0 (PR #35) + +## [1.3.5](https://github.com/Ousret/charset_normalizer/compare/1.3.4...1.3.5) (2021-02-08) + +### Fixed +- Fix error while using the package with a python pre-release interpreter (PR #33) + +### Changed +- Dependencies refactoring, constraints revised. + +### Added +- Add python 3.9 and 3.10 to the supported interpreters + +MIT License + +Copyright (c) 2025 TAHRI Ahmed R. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/RECORD b/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/RECORD new file mode 100644 index 0000000..f63c9ce --- /dev/null +++ b/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/RECORD @@ -0,0 +1,36 @@ +../../bin/normalizer,sha256=PzdTuNC86gjdRjVHeBYgJPKgNLWHvNkYNyydtmVP-5o,245 +81d243bd2c585b0f4821__mypyc.cpython-312-x86_64-linux-gnu.so,sha256=xPTGB-9iuOqJ5RfI3qaB1WzuFAm1oYWbgN1Jz9U1wn0,433312 +charset_normalizer-3.4.7.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +charset_normalizer-3.4.7.dist-info/METADATA,sha256=K8lK8L8LaZ1YmKvWLt3zEkpIxiCOC58xNhzFQrfQJxQ,40931 +charset_normalizer-3.4.7.dist-info/RECORD,, +charset_normalizer-3.4.7.dist-info/WHEEL,sha256=Tc3fF66yn9Kh-hkUUsdKQyuB9Lw0CDoeANnEbSVc3f4,190 +charset_normalizer-3.4.7.dist-info/entry_points.txt,sha256=ADSTKrkXZ3hhdOVFi6DcUEHQRS0xfxDIE_pEz4wLIXA,65 +charset_normalizer-3.4.7.dist-info/licenses/LICENSE,sha256=bQ1Bv-FwrGx9wkjJpj4lTQ-0WmDVCoJX0K-SxuJJuIc,1071 +charset_normalizer-3.4.7.dist-info/top_level.txt,sha256=c_vZbitqecT2GfK3zdxSTLCn8C-6pGnHQY5o_5Y32M0,47 +charset_normalizer/__init__.py,sha256=OKRxRv2Zhnqk00tqkN0c1BtJjm165fWXLydE52IKuHc,1590 +charset_normalizer/__main__.py,sha256=yzYxMR-IhKRHYwcSlavEv8oGdwxsR89mr2X09qXGdps,109 +charset_normalizer/__pycache__/__init__.cpython-312.pyc,, +charset_normalizer/__pycache__/__main__.cpython-312.pyc,, +charset_normalizer/__pycache__/api.cpython-312.pyc,, +charset_normalizer/__pycache__/cd.cpython-312.pyc,, +charset_normalizer/__pycache__/constant.cpython-312.pyc,, +charset_normalizer/__pycache__/legacy.cpython-312.pyc,, +charset_normalizer/__pycache__/md.cpython-312.pyc,, +charset_normalizer/__pycache__/models.cpython-312.pyc,, +charset_normalizer/__pycache__/utils.cpython-312.pyc,, +charset_normalizer/__pycache__/version.cpython-312.pyc,, +charset_normalizer/api.py,sha256=387F3n23MlMu-xfSbFULW2DLGsBmVrZVGhnkiGXeKBo,38844 +charset_normalizer/cd.cpython-312-x86_64-linux-gnu.so,sha256=gOe65H__3O8_4a-aSVMB8gxHsRxVyQDUqqaIurPmIhE,15912 +charset_normalizer/cd.py,sha256=v0iPJweGsRegXywrM1LzUgqW9bJ1KFvIblQHP1jm5FQ,15174 +charset_normalizer/cli/__init__.py,sha256=D8I86lFk2-py45JvqxniTirSj_sFyE6sjaY_0-G1shc,136 +charset_normalizer/cli/__main__.py,sha256=E9FFSV1E2iOE_B2B1tJHQT9ExJqc60Ks_c-08sNawh8,11940 +charset_normalizer/cli/__pycache__/__init__.cpython-312.pyc,, +charset_normalizer/cli/__pycache__/__main__.cpython-312.pyc,, +charset_normalizer/constant.py,sha256=yvLAWDrdSC743Cu4amhwHLIO-FGuRTOTZouCzZKGikc,44431 +charset_normalizer/legacy.py,sha256=yBIFMNABNPE5JkdKOWyVo36fZtV9nm8bf37LrDWulz8,2661 +charset_normalizer/md.cpython-312-x86_64-linux-gnu.so,sha256=iYaQbya7NVRR7xg5FtK1yAKS5shmTFwmtkqqQbbvEWs,15912 +charset_normalizer/md.py,sha256=AYCdfDX79FrgoId3zXqmbCuDcbGr1NRuGqgJN94Rx9Q,30441 +charset_normalizer/models.py,sha256=FbaQnI6ECmVmyHRSvVM5fHNeMAQ3KSGdwLjGcQqWDws,12821 +charset_normalizer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +charset_normalizer/utils.py,sha256=9cpi-_0-vC9pGDfuoarhC6VlF_Jxwx5Jsa_8I4w2D8k,12282 +charset_normalizer/version.py,sha256=2LxFuGp3BBuIwt95cp64y7v8bCNHcMAi08IfXt_47Co,115 diff --git a/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/WHEEL b/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/WHEEL new file mode 100644 index 0000000..0493eaf --- /dev/null +++ b/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/WHEEL @@ -0,0 +1,7 @@ +Wheel-Version: 1.0 +Generator: setuptools (82.0.1) +Root-Is-Purelib: false +Tag: cp312-cp312-manylinux_2_17_x86_64 +Tag: cp312-cp312-manylinux2014_x86_64 +Tag: cp312-cp312-manylinux_2_28_x86_64 + diff --git a/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/entry_points.txt b/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/entry_points.txt new file mode 100644 index 0000000..65619e7 --- /dev/null +++ b/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +normalizer = charset_normalizer.cli:cli_detect diff --git a/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/licenses/LICENSE b/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/licenses/LICENSE new file mode 100644 index 0000000..9725772 --- /dev/null +++ b/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 TAHRI Ahmed R. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/top_level.txt b/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/top_level.txt new file mode 100644 index 0000000..89847be --- /dev/null +++ b/intelliaide/vendor/charset_normalizer-3.4.7.dist-info/top_level.txt @@ -0,0 +1,2 @@ +81d243bd2c585b0f4821__mypyc +charset_normalizer diff --git a/intelliaide/vendor/charset_normalizer/__init__.py b/intelliaide/vendor/charset_normalizer/__init__.py new file mode 100644 index 0000000..0d3a379 --- /dev/null +++ b/intelliaide/vendor/charset_normalizer/__init__.py @@ -0,0 +1,48 @@ +""" +Charset-Normalizer +~~~~~~~~~~~~~~ +The Real First Universal Charset Detector. +A library that helps you read text from an unknown charset encoding. +Motivated by chardet, This package is trying to resolve the issue by taking a new approach. +All IANA character set names for which the Python core library provides codecs are supported. + +Basic usage: + >>> from charset_normalizer import from_bytes + >>> results = from_bytes('Bсеки човек има право на образование. Oбразованието!'.encode('utf_8')) + >>> best_guess = results.best() + >>> str(best_guess) + 'Bсеки човек има право на образование. Oбразованието!' + +Others methods and usages are available - see the full documentation +at . +:copyright: (c) 2021 by Ahmed TAHRI +:license: MIT, see LICENSE for more details. +""" + +from __future__ import annotations + +import logging + +from .api import from_bytes, from_fp, from_path, is_binary +from .legacy import detect +from .models import CharsetMatch, CharsetMatches +from .utils import set_logging_handler +from .version import VERSION, __version__ + +__all__ = ( + "from_fp", + "from_path", + "from_bytes", + "is_binary", + "detect", + "CharsetMatch", + "CharsetMatches", + "__version__", + "VERSION", + "set_logging_handler", +) + +# Attach a NullHandler to the top level logger by default +# https://docs.python.org/3.3/howto/logging.html#configuring-logging-for-a-library + +logging.getLogger("charset_normalizer").addHandler(logging.NullHandler()) diff --git a/intelliaide/vendor/charset_normalizer/__main__.py b/intelliaide/vendor/charset_normalizer/__main__.py new file mode 100644 index 0000000..e0e76f7 --- /dev/null +++ b/intelliaide/vendor/charset_normalizer/__main__.py @@ -0,0 +1,6 @@ +from __future__ import annotations + +from .cli import cli_detect + +if __name__ == "__main__": + cli_detect() diff --git a/intelliaide/vendor/charset_normalizer/api.py b/intelliaide/vendor/charset_normalizer/api.py new file mode 100644 index 0000000..50cb955 --- /dev/null +++ b/intelliaide/vendor/charset_normalizer/api.py @@ -0,0 +1,988 @@ +from __future__ import annotations + +import logging +from os import PathLike +from typing import BinaryIO + +from .cd import ( + coherence_ratio, + encoding_languages, + mb_encoding_languages, + merge_coherence_ratios, +) +from .constant import ( + IANA_SUPPORTED, + IANA_SUPPORTED_SIMILAR, + TOO_BIG_SEQUENCE, + TOO_SMALL_SEQUENCE, + TRACE, +) +from .md import mess_ratio +from .models import CharsetMatch, CharsetMatches +from .utils import ( + any_specified_encoding, + cut_sequence_chunks, + iana_name, + identify_sig_or_bom, + is_multi_byte_encoding, + should_strip_sig_or_bom, +) + +logger = logging.getLogger("charset_normalizer") +explain_handler = logging.StreamHandler() +explain_handler.setFormatter( + logging.Formatter("%(asctime)s | %(levelname)s | %(message)s") +) + +# Pre-compute a reordered encoding list: multibyte first, then single-byte. +# This allows the mb_definitive_match optimization to fire earlier, skipping +# all single-byte encodings for genuine CJK content. Multibyte codecs +# hard-fail (UnicodeDecodeError) on single-byte data almost instantly, so +# testing them first costs negligible time for non-CJK files. +_mb_supported: list[str] = [] +_sb_supported: list[str] = [] + +for _supported_enc in IANA_SUPPORTED: + try: + if is_multi_byte_encoding(_supported_enc): + _mb_supported.append(_supported_enc) + else: + _sb_supported.append(_supported_enc) + except ImportError: + _sb_supported.append(_supported_enc) + +IANA_SUPPORTED_MB_FIRST: list[str] = _mb_supported + _sb_supported + + +def from_bytes( + sequences: bytes | bytearray, + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.2, + cp_isolation: list[str] | None = None, + cp_exclusion: list[str] | None = None, + preemptive_behaviour: bool = True, + explain: bool = False, + language_threshold: float = 0.1, + enable_fallback: bool = True, +) -> CharsetMatches: + """ + Given a raw bytes sequence, return the best possibles charset usable to render str objects. + If there is no results, it is a strong indicator that the source is binary/not text. + By default, the process will extract 5 blocks of 512o each to assess the mess and coherence of a given sequence. + And will give up a particular code page after 20% of measured mess. Those criteria are customizable at will. + + The preemptive behavior DOES NOT replace the traditional detection workflow, it prioritize a particular code page + but never take it for granted. Can improve the performance. + + You may want to focus your attention to some code page or/and not others, use cp_isolation and cp_exclusion for that + purpose. + + This function will strip the SIG in the payload/sequence every time except on UTF-16, UTF-32. + By default the library does not setup any handler other than the NullHandler, if you choose to set the 'explain' + toggle to True it will alter the logger configuration to add a StreamHandler that is suitable for debugging. + Custom logging format and handler can be set manually. + """ + + if not isinstance(sequences, (bytearray, bytes)): + raise TypeError( + "Expected object of type bytes or bytearray, got: {}".format( + type(sequences) + ) + ) + + if explain: + previous_logger_level: int = logger.level + logger.addHandler(explain_handler) + logger.setLevel(TRACE) + + length: int = len(sequences) + + if length == 0: + logger.debug("Encoding detection on empty bytes, assuming utf_8 intention.") + if explain: # Defensive: ensure exit path clean handler + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + return CharsetMatches([CharsetMatch(sequences, "utf_8", 0.0, False, [], "")]) + + if cp_isolation is not None: + logger.log( + TRACE, + "cp_isolation is set. use this flag for debugging purpose. " + "limited list of encoding allowed : %s.", + ", ".join(cp_isolation), + ) + cp_isolation = [iana_name(cp, False) for cp in cp_isolation] + else: + cp_isolation = [] + + if cp_exclusion is not None: + logger.log( + TRACE, + "cp_exclusion is set. use this flag for debugging purpose. " + "limited list of encoding excluded : %s.", + ", ".join(cp_exclusion), + ) + cp_exclusion = [iana_name(cp, False) for cp in cp_exclusion] + else: + cp_exclusion = [] + + if length <= (chunk_size * steps): + logger.log( + TRACE, + "override steps (%i) and chunk_size (%i) as content does not fit (%i byte(s) given) parameters.", + steps, + chunk_size, + length, + ) + steps = 1 + chunk_size = length + + if steps > 1 and length / steps < chunk_size: + chunk_size = int(length / steps) + + is_too_small_sequence: bool = len(sequences) < TOO_SMALL_SEQUENCE + is_too_large_sequence: bool = len(sequences) >= TOO_BIG_SEQUENCE + + if is_too_small_sequence: + logger.log( + TRACE, + "Trying to detect encoding from a tiny portion of ({}) byte(s).".format( + length + ), + ) + elif is_too_large_sequence: + logger.log( + TRACE, + "Using lazy str decoding because the payload is quite large, ({}) byte(s).".format( + length + ), + ) + + prioritized_encodings: list[str] = [] + + specified_encoding: str | None = ( + any_specified_encoding(sequences) if preemptive_behaviour else None + ) + + if specified_encoding is not None: + prioritized_encodings.append(specified_encoding) + logger.log( + TRACE, + "Detected declarative mark in sequence. Priority +1 given for %s.", + specified_encoding, + ) + + tested: set[str] = set() + tested_but_hard_failure: list[str] = [] + tested_but_soft_failure: list[str] = [] + soft_failure_skip: set[str] = set() + success_fast_tracked: set[str] = set() + + # Cache for decoded payload deduplication: hash(decoded_payload) -> (mean_mess_ratio, cd_ratios_merged, passed) + # When multiple encodings decode to the exact same string, we can skip the expensive + # mess_ratio and coherence_ratio analysis and reuse the results from the first encoding. + payload_result_cache: dict[int, tuple[float, list[tuple[str, float]], bool]] = {} + + # When a definitive result (chaos=0.0 and good coherence) is found after testing + # the prioritized encodings (ascii, utf_8), we can significantly reduce the remaining + # work. Encodings that target completely different language families (e.g., Cyrillic + # when the definitive match is Latin) are skipped entirely. + # Additionally, for same-family encodings that pass chaos probing, we reuse the + # definitive match's coherence ratios instead of recomputing them — a major savings + # since coherence_ratio accounts for ~30% of total time on slow Latin files. + definitive_match_found: bool = False + definitive_target_languages: set[str] = set() + # After the definitive match fires, we cap the number of additional same-family + # single-byte encodings that pass chaos probing. Once we've accumulated enough + # good candidates (N), further same-family SB encodings are unlikely to produce + # a better best() result and just waste mess_ratio + coherence_ratio time. + # The first encoding to trigger the definitive match is NOT counted (it's already in). + post_definitive_sb_success_count: int = 0 + POST_DEFINITIVE_SB_CAP: int = 7 + + # When a non-UTF multibyte encoding passes chaos probing with significant multibyte + # content (decoded length < 98% of raw length), skip all remaining single-byte encodings. + # Rationale: multi-byte decoders (CJK) have strict byte-sequence validation — if they + # decode without error AND pass chaos probing with substantial multibyte content, the + # data is genuinely multibyte encoded. Single-byte encodings will always decode (every + # byte maps to something) but waste time on mess_ratio before failing. + # The 98% threshold prevents false triggers on files that happen to have a few valid + # multibyte pairs (e.g., cp424/_ude_1.txt where big5 decodes with 99% ratio). + mb_definitive_match_found: bool = False + + fallback_ascii: CharsetMatch | None = None + fallback_u8: CharsetMatch | None = None + fallback_specified: CharsetMatch | None = None + + results: CharsetMatches = CharsetMatches() + + early_stop_results: CharsetMatches = CharsetMatches() + + sig_encoding, sig_payload = identify_sig_or_bom(sequences) + + if sig_encoding is not None: + prioritized_encodings.append(sig_encoding) + logger.log( + TRACE, + "Detected a SIG or BOM mark on first %i byte(s). Priority +1 given for %s.", + len(sig_payload), + sig_encoding, + ) + + prioritized_encodings.append("ascii") + + if "utf_8" not in prioritized_encodings: + prioritized_encodings.append("utf_8") + + for encoding_iana in prioritized_encodings + IANA_SUPPORTED_MB_FIRST: + if cp_isolation and encoding_iana not in cp_isolation: + continue + + if cp_exclusion and encoding_iana in cp_exclusion: + continue + + if encoding_iana in tested: + continue + + tested.add(encoding_iana) + + decoded_payload: str | None = None + bom_or_sig_available: bool = sig_encoding == encoding_iana + strip_sig_or_bom: bool = bom_or_sig_available and should_strip_sig_or_bom( + encoding_iana + ) + + if encoding_iana in {"utf_16", "utf_32"} and not bom_or_sig_available: + logger.log( + TRACE, + "Encoding %s won't be tested as-is because it require a BOM. Will try some sub-encoder LE/BE.", + encoding_iana, + ) + continue + if encoding_iana in {"utf_7"} and not bom_or_sig_available: + logger.log( + TRACE, + "Encoding %s won't be tested as-is because detection is unreliable without BOM/SIG.", + encoding_iana, + ) + continue + + # Skip encodings similar to ones that already soft-failed (high mess ratio). + # Checked BEFORE the expensive decode attempt. + if encoding_iana in soft_failure_skip: + logger.log( + TRACE, + "%s is deemed too similar to a code page that was already considered unsuited. Continuing!", + encoding_iana, + ) + continue + + # Skip encodings that were already fast-tracked from a similar successful encoding. + if encoding_iana in success_fast_tracked: + logger.log( + TRACE, + "Skipping %s: already fast-tracked from a similar successful encoding.", + encoding_iana, + ) + continue + + try: + is_multi_byte_decoder: bool = is_multi_byte_encoding(encoding_iana) + except (ModuleNotFoundError, ImportError): # Defensive: + logger.log( + TRACE, + "Encoding %s does not provide an IncrementalDecoder", + encoding_iana, + ) + continue + + # When we've already found a definitive match (chaos=0.0 with good coherence) + # after testing the prioritized encodings, skip encodings that target + # completely different language families. This avoids running expensive + # mess_ratio + coherence_ratio on clearly unrelated candidates (e.g., Cyrillic + # when the definitive match is Latin-based). + if definitive_match_found: + if not is_multi_byte_decoder: + enc_languages = set(encoding_languages(encoding_iana)) + else: + enc_languages = set(mb_encoding_languages(encoding_iana)) + if not enc_languages.intersection(definitive_target_languages): + logger.log( + TRACE, + "Skipping %s: definitive match already found, this encoding targets different languages (%s vs %s).", + encoding_iana, + enc_languages, + definitive_target_languages, + ) + continue + + # After the definitive match, cap the number of additional same-family + # single-byte encodings that pass chaos probing. This avoids testing the + # tail of rare, low-value same-family encodings (mac_iceland, cp860, etc.) + # that almost never change best() but each cost ~1-2ms of mess_ratio + coherence. + if ( + definitive_match_found + and not is_multi_byte_decoder + and post_definitive_sb_success_count >= POST_DEFINITIVE_SB_CAP + ): + logger.log( + TRACE, + "Skipping %s: already accumulated %d same-family results after definitive match (cap=%d).", + encoding_iana, + post_definitive_sb_success_count, + POST_DEFINITIVE_SB_CAP, + ) + continue + + # When a multibyte encoding with significant multibyte content has already + # passed chaos probing, skip all single-byte encodings. They will either fail + # chaos probing (wasting mess_ratio time) or produce inferior results. + if mb_definitive_match_found and not is_multi_byte_decoder: + logger.log( + TRACE, + "Skipping single-byte %s: multi-byte definitive match already found.", + encoding_iana, + ) + continue + + try: + if is_too_large_sequence and is_multi_byte_decoder is False: + str( + ( + sequences[: int(50e4)] + if strip_sig_or_bom is False + else sequences[len(sig_payload) : int(50e4)] + ), + encoding=encoding_iana, + ) + else: + # UTF-7 BOM is encoded in modified Base64 whose byte boundary + # can overlap with the next character. Stripping raw SIG bytes + # before decoding may leave stray bytes that decode as garbage. + # Decode the full sequence and remove the leading BOM char instead. + # see https://github.com/jawah/charset_normalizer/issues/718 + # and https://github.com/jawah/charset_normalizer/issues/716 + if encoding_iana == "utf_7" and bom_or_sig_available: + decoded_payload = str( + sequences, + encoding=encoding_iana, + ) + if decoded_payload and decoded_payload[0] == "\ufeff": + decoded_payload = decoded_payload[1:] + else: + decoded_payload = str( + ( + sequences + if strip_sig_or_bom is False + else sequences[len(sig_payload) :] + ), + encoding=encoding_iana, + ) + except (UnicodeDecodeError, LookupError) as e: + if not isinstance(e, LookupError): + logger.log( + TRACE, + "Code page %s does not fit given bytes sequence at ALL. %s", + encoding_iana, + str(e), + ) + tested_but_hard_failure.append(encoding_iana) + continue + + r_ = range( + 0 if not bom_or_sig_available else len(sig_payload), + length, + int(length / steps), + ) + + multi_byte_bonus: bool = ( + is_multi_byte_decoder + and decoded_payload is not None + and len(decoded_payload) < length + ) + + if multi_byte_bonus: + logger.log( + TRACE, + "Code page %s is a multi byte encoding table and it appear that at least one character " + "was encoded using n-bytes.", + encoding_iana, + ) + + # Payload-hash deduplication: if another encoding already decoded to the + # exact same string, reuse its mess_ratio and coherence results entirely. + # This is strictly more general than the old IANA_SUPPORTED_SIMILAR approach + # because it catches ALL identical decoding, not just pre-mapped ones. + if decoded_payload is not None and not is_multi_byte_decoder: + payload_hash: int = hash(decoded_payload) + cached = payload_result_cache.get(payload_hash) + if cached is not None: + cached_mess, cached_cd, cached_passed = cached + if cached_passed: + # The previous encoding with identical output passed chaos probing. + fast_match = CharsetMatch( + sequences, + encoding_iana, + cached_mess, + bom_or_sig_available, + cached_cd, + ( + decoded_payload + if ( + is_too_large_sequence is False + or encoding_iana + in [specified_encoding, "ascii", "utf_8"] + ) + else None + ), + preemptive_declaration=specified_encoding, + ) + results.append(fast_match) + success_fast_tracked.add(encoding_iana) + logger.log( + TRACE, + "%s fast-tracked (identical decoded payload to a prior encoding, chaos=%f %%).", + encoding_iana, + round(cached_mess * 100, ndigits=3), + ) + + if ( + encoding_iana in [specified_encoding, "ascii", "utf_8"] + and cached_mess < 0.1 + ): + if cached_mess == 0.0: + logger.debug( + "Encoding detection: %s is most likely the one.", + fast_match.encoding, + ) + if explain: + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + return CharsetMatches([fast_match]) + early_stop_results.append(fast_match) + + if ( + len(early_stop_results) + and (specified_encoding is None or specified_encoding in tested) + and "ascii" in tested + and "utf_8" in tested + ): + probable_result: CharsetMatch = early_stop_results.best() # type: ignore[assignment] + logger.debug( + "Encoding detection: %s is most likely the one.", + probable_result.encoding, + ) + if explain: + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + return CharsetMatches([probable_result]) + + continue + else: + # The previous encoding with identical output failed chaos probing. + tested_but_soft_failure.append(encoding_iana) + logger.log( + TRACE, + "%s fast-skipped (identical decoded payload to a prior encoding that failed chaos probing).", + encoding_iana, + ) + # Prepare fallbacks for special encodings even when skipped. + if enable_fallback and encoding_iana in [ + "ascii", + "utf_8", + specified_encoding, + "utf_16", + "utf_32", + ]: + fallback_entry = CharsetMatch( + sequences, + encoding_iana, + threshold, + bom_or_sig_available, + [], + decoded_payload, + preemptive_declaration=specified_encoding, + ) + if encoding_iana == specified_encoding: + fallback_specified = fallback_entry + elif encoding_iana == "ascii": + fallback_ascii = fallback_entry + else: + fallback_u8 = fallback_entry + continue + + max_chunk_gave_up: int = int(len(r_) / 4) + + max_chunk_gave_up = max(max_chunk_gave_up, 2) + early_stop_count: int = 0 + lazy_str_hard_failure = False + + md_chunks: list[str] = [] + md_ratios = [] + + try: + for chunk in cut_sequence_chunks( + sequences, + encoding_iana, + r_, + chunk_size, + bom_or_sig_available, + strip_sig_or_bom, + sig_payload, + is_multi_byte_decoder, + decoded_payload, + ): + md_chunks.append(chunk) + + md_ratios.append( + mess_ratio( + chunk, + threshold, + explain is True and 1 <= len(cp_isolation) <= 2, + ) + ) + + if md_ratios[-1] >= threshold: + early_stop_count += 1 + + if (early_stop_count >= max_chunk_gave_up) or ( + bom_or_sig_available and strip_sig_or_bom is False + ): + break + except ( + UnicodeDecodeError + ) as e: # Lazy str loading may have missed something there + logger.log( + TRACE, + "LazyStr Loading: After MD chunk decode, code page %s does not fit given bytes sequence at ALL. %s", + encoding_iana, + str(e), + ) + early_stop_count = max_chunk_gave_up + lazy_str_hard_failure = True + + # We might want to check the sequence again with the whole content + # Only if initial MD tests passes + if ( + not lazy_str_hard_failure + and is_too_large_sequence + and not is_multi_byte_decoder + ): + try: + sequences[int(50e3) :].decode(encoding_iana, errors="strict") + except UnicodeDecodeError as e: + logger.log( + TRACE, + "LazyStr Loading: After final lookup, code page %s does not fit given bytes sequence at ALL. %s", + encoding_iana, + str(e), + ) + tested_but_hard_failure.append(encoding_iana) + continue + + mean_mess_ratio: float = sum(md_ratios) / len(md_ratios) if md_ratios else 0.0 + if mean_mess_ratio >= threshold or early_stop_count >= max_chunk_gave_up: + tested_but_soft_failure.append(encoding_iana) + if encoding_iana in IANA_SUPPORTED_SIMILAR: + soft_failure_skip.update(IANA_SUPPORTED_SIMILAR[encoding_iana]) + # Cache this soft-failure so identical decoding from other encodings + # can be skipped immediately. + if decoded_payload is not None and not is_multi_byte_decoder: + payload_result_cache.setdefault( + hash(decoded_payload), (mean_mess_ratio, [], False) + ) + logger.log( + TRACE, + "%s was excluded because of initial chaos probing. Gave up %i time(s). " + "Computed mean chaos is %f %%.", + encoding_iana, + early_stop_count, + round(mean_mess_ratio * 100, ndigits=3), + ) + # Preparing those fallbacks in case we got nothing. + if ( + enable_fallback + and encoding_iana + in ["ascii", "utf_8", specified_encoding, "utf_16", "utf_32"] + and not lazy_str_hard_failure + ): + fallback_entry = CharsetMatch( + sequences, + encoding_iana, + threshold, + bom_or_sig_available, + [], + decoded_payload, + preemptive_declaration=specified_encoding, + ) + if encoding_iana == specified_encoding: + fallback_specified = fallback_entry + elif encoding_iana == "ascii": + fallback_ascii = fallback_entry + else: + fallback_u8 = fallback_entry + continue + + logger.log( + TRACE, + "%s passed initial chaos probing. Mean measured chaos is %f %%", + encoding_iana, + round(mean_mess_ratio * 100, ndigits=3), + ) + + if not is_multi_byte_decoder: + target_languages: list[str] = encoding_languages(encoding_iana) + else: + target_languages = mb_encoding_languages(encoding_iana) + + if target_languages: + logger.log( + TRACE, + "{} should target any language(s) of {}".format( + encoding_iana, str(target_languages) + ), + ) + + cd_ratios = [] + + # Run coherence detection on all chunks. We previously tried limiting to + # 1-2 chunks for post-definitive encodings to save time, but this caused + # coverage regressions by producing unrepresentative coherence scores. + # The SB cap and language-family skip optimizations provide sufficient + # speedup without sacrificing coherence accuracy. + if encoding_iana != "ascii": + # We shall skip the CD when its about ASCII + # Most of the time its not relevant to run "language-detection" on it. + for chunk in md_chunks: + chunk_languages = coherence_ratio( + chunk, + language_threshold, + ",".join(target_languages) if target_languages else None, + ) + + cd_ratios.append(chunk_languages) + cd_ratios_merged = merge_coherence_ratios(cd_ratios) + else: + cd_ratios_merged = merge_coherence_ratios(cd_ratios) + + if cd_ratios_merged: + logger.log( + TRACE, + "We detected language {} using {}".format( + cd_ratios_merged, encoding_iana + ), + ) + + current_match = CharsetMatch( + sequences, + encoding_iana, + mean_mess_ratio, + bom_or_sig_available, + cd_ratios_merged, + ( + decoded_payload + if ( + is_too_large_sequence is False + or encoding_iana in [specified_encoding, "ascii", "utf_8"] + ) + else None + ), + preemptive_declaration=specified_encoding, + ) + + results.append(current_match) + + # Cache the successful result for payload-hash deduplication. + if decoded_payload is not None and not is_multi_byte_decoder: + payload_result_cache.setdefault( + hash(decoded_payload), + (mean_mess_ratio, cd_ratios_merged, True), + ) + + # Count post-definitive same-family SB successes for the early termination cap. + # Only count low-mess encodings (< 2%) toward the cap. High-mess encodings are + # marginal results that shouldn't prevent better-quality candidates from being + # tested. For example, iso8859_4 (mess=0%) should not be skipped just because + # 7 high-mess Latin encodings (cp1252 at 8%, etc.) were tried first. + if ( + definitive_match_found + and not is_multi_byte_decoder + and mean_mess_ratio < 0.02 + ): + post_definitive_sb_success_count += 1 + + if ( + encoding_iana in [specified_encoding, "ascii", "utf_8"] + and mean_mess_ratio < 0.1 + ): + # If md says nothing to worry about, then... stop immediately! + if mean_mess_ratio == 0.0: + logger.debug( + "Encoding detection: %s is most likely the one.", + current_match.encoding, + ) + if explain: # Defensive: ensure exit path clean handler + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + return CharsetMatches([current_match]) + + early_stop_results.append(current_match) + + if ( + len(early_stop_results) + and (specified_encoding is None or specified_encoding in tested) + and "ascii" in tested + and "utf_8" in tested + ): + probable_result = early_stop_results.best() # type: ignore[assignment] + logger.debug( + "Encoding detection: %s is most likely the one.", + probable_result.encoding, # type: ignore[union-attr] + ) + if explain: # Defensive: ensure exit path clean handler + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + + return CharsetMatches([probable_result]) + + # Once we find a result with good coherence (>= 0.5) after testing the + # prioritized encodings (ascii, utf_8), activate "definitive mode": skip + # encodings that target completely different language families. This avoids + # running expensive mess_ratio + coherence_ratio on clearly unrelated + # candidates (e.g., Cyrillic encodings when the match is Latin-based). + # We require coherence >= 0.5 to avoid false positives (e.g., cp1251 decoding + # Hebrew text with 0.0 chaos but wrong language detection at coherence 0.33). + if not definitive_match_found and not is_multi_byte_decoder: + best_coherence = ( + max((v for _, v in cd_ratios_merged), default=0.0) + if cd_ratios_merged + else 0.0 + ) + if best_coherence >= 0.5 and "ascii" in tested and "utf_8" in tested: + definitive_match_found = True + definitive_target_languages.update(target_languages) + logger.log( + TRACE, + "Definitive match found: %s (chaos=%.3f, coherence=%.2f). Encodings targeting different language families will be skipped.", + encoding_iana, + mean_mess_ratio, + best_coherence, + ) + + # When a non-UTF multibyte encoding passes chaos probing with significant + # multibyte content (decoded < 98% of raw), activate mb_definitive_match. + # This skips all remaining single-byte encodings which would either soft-fail + # (running expensive mess_ratio for nothing) or produce inferior results. + if ( + not mb_definitive_match_found + and is_multi_byte_decoder + and multi_byte_bonus + and decoded_payload is not None + and len(decoded_payload) < length * 0.98 + and encoding_iana + not in { + "utf_8", + "utf_8_sig", + "utf_16", + "utf_16_be", + "utf_16_le", + "utf_32", + "utf_32_be", + "utf_32_le", + "utf_7", + } + and "ascii" in tested + and "utf_8" in tested + ): + mb_definitive_match_found = True + logger.log( + TRACE, + "Multi-byte definitive match: %s (chaos=%.3f, decoded=%d/%d=%.1f%%). Single-byte encodings will be skipped.", + encoding_iana, + mean_mess_ratio, + len(decoded_payload), + length, + len(decoded_payload) / length * 100, + ) + + if encoding_iana == sig_encoding: + logger.debug( + "Encoding detection: %s is most likely the one as we detected a BOM or SIG within " + "the beginning of the sequence.", + encoding_iana, + ) + if explain: # Defensive: ensure exit path clean handler + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + return CharsetMatches([results[encoding_iana]]) + + if len(results) == 0: + if fallback_u8 or fallback_ascii or fallback_specified: + logger.log( + TRACE, + "Nothing got out of the detection process. Using ASCII/UTF-8/Specified fallback.", + ) + + if fallback_specified: + logger.debug( + "Encoding detection: %s will be used as a fallback match", + fallback_specified.encoding, + ) + results.append(fallback_specified) + elif ( + (fallback_u8 and fallback_ascii is None) + or ( + fallback_u8 + and fallback_ascii + and fallback_u8.fingerprint != fallback_ascii.fingerprint + ) + or (fallback_u8 is not None) + ): + logger.debug("Encoding detection: utf_8 will be used as a fallback match") + results.append(fallback_u8) + elif fallback_ascii: + logger.debug("Encoding detection: ascii will be used as a fallback match") + results.append(fallback_ascii) + + if results: + logger.debug( + "Encoding detection: Found %s as plausible (best-candidate) for content. With %i alternatives.", + results.best().encoding, # type: ignore + len(results) - 1, + ) + else: + logger.debug("Encoding detection: Unable to determine any suitable charset.") + + if explain: + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + + return results + + +def from_fp( + fp: BinaryIO, + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.20, + cp_isolation: list[str] | None = None, + cp_exclusion: list[str] | None = None, + preemptive_behaviour: bool = True, + explain: bool = False, + language_threshold: float = 0.1, + enable_fallback: bool = True, +) -> CharsetMatches: + """ + Same thing than the function from_bytes but using a file pointer that is already ready. + Will not close the file pointer. + """ + return from_bytes( + fp.read(), + steps, + chunk_size, + threshold, + cp_isolation, + cp_exclusion, + preemptive_behaviour, + explain, + language_threshold, + enable_fallback, + ) + + +def from_path( + path: str | bytes | PathLike, # type: ignore[type-arg] + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.20, + cp_isolation: list[str] | None = None, + cp_exclusion: list[str] | None = None, + preemptive_behaviour: bool = True, + explain: bool = False, + language_threshold: float = 0.1, + enable_fallback: bool = True, +) -> CharsetMatches: + """ + Same thing than the function from_bytes but with one extra step. Opening and reading given file path in binary mode. + Can raise IOError. + """ + with open(path, "rb") as fp: + return from_fp( + fp, + steps, + chunk_size, + threshold, + cp_isolation, + cp_exclusion, + preemptive_behaviour, + explain, + language_threshold, + enable_fallback, + ) + + +def is_binary( + fp_or_path_or_payload: PathLike | str | BinaryIO | bytes, # type: ignore[type-arg] + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.20, + cp_isolation: list[str] | None = None, + cp_exclusion: list[str] | None = None, + preemptive_behaviour: bool = True, + explain: bool = False, + language_threshold: float = 0.1, + enable_fallback: bool = False, +) -> bool: + """ + Detect if the given input (file, bytes, or path) points to a binary file. aka. not a string. + Based on the same main heuristic algorithms and default kwargs at the sole exception that fallbacks match + are disabled to be stricter around ASCII-compatible but unlikely to be a string. + """ + if isinstance(fp_or_path_or_payload, (str, PathLike)): + guesses = from_path( + fp_or_path_or_payload, + steps=steps, + chunk_size=chunk_size, + threshold=threshold, + cp_isolation=cp_isolation, + cp_exclusion=cp_exclusion, + preemptive_behaviour=preemptive_behaviour, + explain=explain, + language_threshold=language_threshold, + enable_fallback=enable_fallback, + ) + elif isinstance( + fp_or_path_or_payload, + ( + bytes, + bytearray, + ), + ): + guesses = from_bytes( + fp_or_path_or_payload, + steps=steps, + chunk_size=chunk_size, + threshold=threshold, + cp_isolation=cp_isolation, + cp_exclusion=cp_exclusion, + preemptive_behaviour=preemptive_behaviour, + explain=explain, + language_threshold=language_threshold, + enable_fallback=enable_fallback, + ) + else: + guesses = from_fp( + fp_or_path_or_payload, + steps=steps, + chunk_size=chunk_size, + threshold=threshold, + cp_isolation=cp_isolation, + cp_exclusion=cp_exclusion, + preemptive_behaviour=preemptive_behaviour, + explain=explain, + language_threshold=language_threshold, + enable_fallback=enable_fallback, + ) + + return not guesses diff --git a/intelliaide/vendor/charset_normalizer/cd.cpython-312-x86_64-linux-gnu.so b/intelliaide/vendor/charset_normalizer/cd.cpython-312-x86_64-linux-gnu.so new file mode 100755 index 0000000..dffe3e4 Binary files /dev/null and b/intelliaide/vendor/charset_normalizer/cd.cpython-312-x86_64-linux-gnu.so differ diff --git a/intelliaide/vendor/charset_normalizer/cd.py b/intelliaide/vendor/charset_normalizer/cd.py new file mode 100644 index 0000000..9545d35 --- /dev/null +++ b/intelliaide/vendor/charset_normalizer/cd.py @@ -0,0 +1,454 @@ +from __future__ import annotations + +import importlib +from codecs import IncrementalDecoder +from collections import Counter +from functools import lru_cache +from typing import Counter as TypeCounter + +from .constant import ( + FREQUENCIES, + KO_NAMES, + LANGUAGE_SUPPORTED_COUNT, + TOO_SMALL_SEQUENCE, + ZH_NAMES, + _FREQUENCIES_SET, + _FREQUENCIES_RANK, +) +from .md import is_suspiciously_successive_range +from .models import CoherenceMatches +from .utils import ( + is_accentuated, + is_latin, + is_multi_byte_encoding, + is_unicode_range_secondary, + unicode_range, +) + + +def encoding_unicode_range(iana_name: str) -> list[str]: + """ + Return associated unicode ranges in a single byte code page. + """ + if is_multi_byte_encoding(iana_name): + raise OSError( # Defensive: + "Function not supported on multi-byte code page" + ) + + decoder = importlib.import_module(f"encodings.{iana_name}").IncrementalDecoder + + p: IncrementalDecoder = decoder(errors="ignore") + seen_ranges: dict[str, int] = {} + character_count: int = 0 + + for i in range(0x40, 0xFF): + chunk: str = p.decode(bytes([i])) + + if chunk: + character_range: str | None = unicode_range(chunk) + + if character_range is None: + continue + + if is_unicode_range_secondary(character_range) is False: + if character_range not in seen_ranges: + seen_ranges[character_range] = 0 + seen_ranges[character_range] += 1 + character_count += 1 + + return sorted( + [ + character_range + for character_range in seen_ranges + if seen_ranges[character_range] / character_count >= 0.15 + ] + ) + + +def unicode_range_languages(primary_range: str) -> list[str]: + """ + Return inferred languages used with a unicode range. + """ + languages: list[str] = [] + + for language, characters in FREQUENCIES.items(): + for character in characters: + if unicode_range(character) == primary_range: + languages.append(language) + break + + return languages + + +@lru_cache() +def encoding_languages(iana_name: str) -> list[str]: + """ + Single-byte encoding language association. Some code page are heavily linked to particular language(s). + This function does the correspondence. + """ + unicode_ranges: list[str] = encoding_unicode_range(iana_name) + primary_range: str | None = None + + for specified_range in unicode_ranges: + if "Latin" not in specified_range: + primary_range = specified_range + break + + if primary_range is None: + return ["Latin Based"] + + return unicode_range_languages(primary_range) + + +@lru_cache() +def mb_encoding_languages(iana_name: str) -> list[str]: + """ + Multi-byte encoding language association. Some code page are heavily linked to particular language(s). + This function does the correspondence. + """ + if ( + iana_name.startswith("shift_") + or iana_name.startswith("iso2022_jp") + or iana_name.startswith("euc_j") + or iana_name == "cp932" + ): + return ["Japanese"] + if iana_name.startswith("gb") or iana_name in ZH_NAMES: + return ["Chinese"] + if iana_name.startswith("iso2022_kr") or iana_name in KO_NAMES: + return ["Korean"] + + return [] + + +@lru_cache(maxsize=LANGUAGE_SUPPORTED_COUNT) +def get_target_features(language: str) -> tuple[bool, bool]: + """ + Determine main aspects from a supported language if it contains accents and if is pure Latin. + """ + target_have_accents: bool = False + target_pure_latin: bool = True + + for character in FREQUENCIES[language]: + if not target_have_accents and is_accentuated(character): + target_have_accents = True + if target_pure_latin and is_latin(character) is False: + target_pure_latin = False + + return target_have_accents, target_pure_latin + + +def alphabet_languages( + characters: list[str], ignore_non_latin: bool = False +) -> list[str]: + """ + Return associated languages associated to given characters. + """ + languages: list[tuple[str, float]] = [] + + characters_set: frozenset[str] = frozenset(characters) + source_have_accents = any(is_accentuated(character) for character in characters) + + for language, language_characters in FREQUENCIES.items(): + target_have_accents, target_pure_latin = get_target_features(language) + + if ignore_non_latin and target_pure_latin is False: + continue + + if target_have_accents is False and source_have_accents: + continue + + character_count: int = len(language_characters) + + character_match_count: int = len(_FREQUENCIES_SET[language] & characters_set) + + ratio: float = character_match_count / character_count + + if ratio >= 0.2: + languages.append((language, ratio)) + + languages = sorted(languages, key=lambda x: x[1], reverse=True) + + return [compatible_language[0] for compatible_language in languages] + + +def characters_popularity_compare( + language: str, ordered_characters: list[str] +) -> float: + """ + Determine if a ordered characters list (by occurrence from most appearance to rarest) match a particular language. + The result is a ratio between 0. (absolutely no correspondence) and 1. (near perfect fit). + Beware that is function is not strict on the match in order to ease the detection. (Meaning close match is 1.) + """ + if language not in FREQUENCIES: + raise ValueError(f"{language} not available") # Defensive: + + character_approved_count: int = 0 + frequencies_language_set: frozenset[str] = _FREQUENCIES_SET[language] + lang_rank: dict[str, int] = _FREQUENCIES_RANK[language] + + ordered_characters_count: int = len(ordered_characters) + target_language_characters_count: int = len(FREQUENCIES[language]) + + large_alphabet: bool = target_language_characters_count > 26 + + expected_projection_ratio: float = ( + target_language_characters_count / ordered_characters_count + ) + + # Pre-built rank dict for ordered_characters (avoids repeated list slicing). + ordered_rank: dict[str, int] = { + char: rank for rank, char in enumerate(ordered_characters) + } + + # Pre-compute characters common to both orderings. + # Avoids repeated `c in ordered_rank` dict lookups in the inner counts. + common_chars: list[tuple[int, int]] = [ + (lr, ordered_rank[c]) for c, lr in lang_rank.items() if c in ordered_rank + ] + + # Pre-extract lr and orr arrays for faster iteration in the inner loop. + # Plain integer loops with local arrays are much faster under mypyc than + # generator expression sums over a list of tuples. + common_count: int = len(common_chars) + common_lr: list[int] = [p[0] for p in common_chars] + common_orr: list[int] = [p[1] for p in common_chars] + + for character, character_rank in zip( + ordered_characters, range(0, ordered_characters_count) + ): + if character not in frequencies_language_set: + continue + + character_rank_in_language: int = lang_rank[character] + character_rank_projection: int = int(character_rank * expected_projection_ratio) + + if ( + large_alphabet is False + and abs(character_rank_projection - character_rank_in_language) > 4 + ): + continue + + if ( + large_alphabet is True + and abs(character_rank_projection - character_rank_in_language) + < target_language_characters_count / 3 + ): + character_approved_count += 1 + continue + + # Count how many characters appear "before" in both orderings, + # and how many appear "at or after" in both orderings. + # Single pass over pre-extracted arrays — much faster under mypyc + # than two generator expression sums. + before_match_count: int = 0 + after_match_count: int = 0 + for i in range(common_count): + lr_i: int = common_lr[i] + orr_i: int = common_orr[i] + if lr_i < character_rank_in_language: + if orr_i < character_rank: + before_match_count += 1 + else: + if orr_i >= character_rank: + after_match_count += 1 + + after_len: int = target_language_characters_count - character_rank_in_language + + if character_rank_in_language == 0 and before_match_count <= 4: + character_approved_count += 1 + continue + + if after_len == 0 and after_match_count <= 4: + character_approved_count += 1 + continue + + if ( + character_rank_in_language > 0 + and before_match_count / character_rank_in_language >= 0.4 + ) or (after_len > 0 and after_match_count / after_len >= 0.4): + character_approved_count += 1 + continue + + return character_approved_count / len(ordered_characters) + + +def alpha_unicode_split(decoded_sequence: str) -> list[str]: + """ + Given a decoded text sequence, return a list of str. Unicode range / alphabet separation. + Ex. a text containing English/Latin with a bit a Hebrew will return two items in the resulting list; + One containing the latin letters and the other hebrew. + """ + layers: dict[str, list[str]] = {} + + # Fast path: track single-layer key to skip dict iteration for single-script text. + single_layer_key: str | None = None + multi_layer: bool = False + + # Cache the last character_range and its resolved layer to avoid repeated + # is_suspiciously_successive_range calls for consecutive same-range chars. + prev_character_range: str | None = None + prev_layer_target: str | None = None + + for character in decoded_sequence: + if character.isalpha() is False: + continue + + # ASCII fast-path: a-z and A-Z are always "Basic Latin". + # Avoids unicode_range() function call overhead for the most common case. + character_ord: int = ord(character) + if character_ord < 128: + character_range: str | None = "Basic Latin" + else: + character_range = unicode_range(character) + + if character_range is None: + continue + + # Fast path: same range as previous character → reuse cached layer target. + if character_range == prev_character_range: + if prev_layer_target is not None: + layers[prev_layer_target].append(character) + continue + + layer_target_range: str | None = None + + if multi_layer: + for discovered_range in layers: + if ( + is_suspiciously_successive_range(discovered_range, character_range) + is False + ): + layer_target_range = discovered_range + break + elif single_layer_key is not None: + if ( + is_suspiciously_successive_range(single_layer_key, character_range) + is False + ): + layer_target_range = single_layer_key + + if layer_target_range is None: + layer_target_range = character_range + + if layer_target_range not in layers: + layers[layer_target_range] = [] + if single_layer_key is None: + single_layer_key = layer_target_range + else: + multi_layer = True + + layers[layer_target_range].append(character) + + # Cache for next iteration + prev_character_range = character_range + prev_layer_target = layer_target_range + + return ["".join(chars).lower() for chars in layers.values()] + + +def merge_coherence_ratios(results: list[CoherenceMatches]) -> CoherenceMatches: + """ + This function merge results previously given by the function coherence_ratio. + The return type is the same as coherence_ratio. + """ + per_language_ratios: dict[str, list[float]] = {} + for result in results: + for sub_result in result: + language, ratio = sub_result + if language not in per_language_ratios: + per_language_ratios[language] = [ratio] + continue + per_language_ratios[language].append(ratio) + + merge = [ + ( + language, + round( + sum(per_language_ratios[language]) / len(per_language_ratios[language]), + 4, + ), + ) + for language in per_language_ratios + ] + + return sorted(merge, key=lambda x: x[1], reverse=True) + + +def filter_alt_coherence_matches(results: CoherenceMatches) -> CoherenceMatches: + """ + We shall NOT return "English—" in CoherenceMatches because it is an alternative + of "English". This function only keeps the best match and remove the em-dash in it. + """ + index_results: dict[str, list[float]] = dict() + + for result in results: + language, ratio = result + no_em_name: str = language.replace("—", "") + + if no_em_name not in index_results: + index_results[no_em_name] = [] + + index_results[no_em_name].append(ratio) + + if any(len(index_results[e]) > 1 for e in index_results): + filtered_results: CoherenceMatches = [] + + for language in index_results: + filtered_results.append((language, max(index_results[language]))) + + return filtered_results + + return results + + +@lru_cache(maxsize=2048) +def coherence_ratio( + decoded_sequence: str, threshold: float = 0.1, lg_inclusion: str | None = None +) -> CoherenceMatches: + """ + Detect ANY language that can be identified in given sequence. The sequence will be analysed by layers. + A layer = Character extraction by alphabets/ranges. + """ + + results: list[tuple[str, float]] = [] + ignore_non_latin: bool = False + + sufficient_match_count: int = 0 + + lg_inclusion_list = lg_inclusion.split(",") if lg_inclusion is not None else [] + if "Latin Based" in lg_inclusion_list: + ignore_non_latin = True + lg_inclusion_list.remove("Latin Based") + + for layer in alpha_unicode_split(decoded_sequence): + sequence_frequencies: TypeCounter[str] = Counter(layer) + most_common = sequence_frequencies.most_common() + + character_count: int = len(layer) + + if character_count <= TOO_SMALL_SEQUENCE: + continue + + popular_character_ordered: list[str] = [c for c, o in most_common] + + for language in lg_inclusion_list or alphabet_languages( + popular_character_ordered, ignore_non_latin + ): + ratio: float = characters_popularity_compare( + language, popular_character_ordered + ) + + if ratio < threshold: + continue + elif ratio >= 0.8: + sufficient_match_count += 1 + + results.append((language, round(ratio, 4))) + + if sufficient_match_count >= 3: + break + + return sorted( + filter_alt_coherence_matches(results), key=lambda x: x[1], reverse=True + ) diff --git a/intelliaide/vendor/charset_normalizer/cli/__init__.py b/intelliaide/vendor/charset_normalizer/cli/__init__.py new file mode 100644 index 0000000..543a5a4 --- /dev/null +++ b/intelliaide/vendor/charset_normalizer/cli/__init__.py @@ -0,0 +1,8 @@ +from __future__ import annotations + +from .__main__ import cli_detect, query_yes_no + +__all__ = ( + "cli_detect", + "query_yes_no", +) diff --git a/intelliaide/vendor/charset_normalizer/cli/__main__.py b/intelliaide/vendor/charset_normalizer/cli/__main__.py new file mode 100644 index 0000000..ad843c1 --- /dev/null +++ b/intelliaide/vendor/charset_normalizer/cli/__main__.py @@ -0,0 +1,362 @@ +from __future__ import annotations + +import argparse +import sys +import typing +from json import dumps +from os.path import abspath, basename, dirname, join, realpath +from platform import python_version +from unicodedata import unidata_version + +import charset_normalizer.md as md_module +from charset_normalizer import from_fp +from charset_normalizer.models import CliDetectionResult +from charset_normalizer.version import __version__ + + +def query_yes_no(question: str, default: str = "yes") -> bool: # Defensive: + """Ask a yes/no question via input() and return the answer as a bool.""" + prompt = " [Y/n] " if default == "yes" else " [y/N] " + + while True: + choice = input(question + prompt).strip().lower() + if not choice: + return default == "yes" + if choice in ("y", "yes"): + return True + if choice in ("n", "no"): + return False + print("Please respond with 'y' or 'n'.") + + +class FileType: + """Factory for creating file object types + + Instances of FileType are typically passed as type= arguments to the + ArgumentParser add_argument() method. + + Keyword Arguments: + - mode -- A string indicating how the file is to be opened. Accepts the + same values as the builtin open() function. + - bufsize -- The file's desired buffer size. Accepts the same values as + the builtin open() function. + - encoding -- The file's encoding. Accepts the same values as the + builtin open() function. + - errors -- A string indicating how encoding and decoding errors are to + be handled. Accepts the same value as the builtin open() function. + + Backported from CPython 3.12 + """ + + def __init__( + self, + mode: str = "r", + bufsize: int = -1, + encoding: str | None = None, + errors: str | None = None, + ): + self._mode = mode + self._bufsize = bufsize + self._encoding = encoding + self._errors = errors + + def __call__(self, string: str) -> typing.IO: # type: ignore[type-arg] + # the special argument "-" means sys.std{in,out} + if string == "-": + if "r" in self._mode: + return sys.stdin.buffer if "b" in self._mode else sys.stdin + elif any(c in self._mode for c in "wax"): + return sys.stdout.buffer if "b" in self._mode else sys.stdout + else: + msg = f'argument "-" with mode {self._mode}' + raise ValueError(msg) + + # all other arguments are used as file names + try: + return open(string, self._mode, self._bufsize, self._encoding, self._errors) + except OSError as e: + message = f"can't open '{string}': {e}" + raise argparse.ArgumentTypeError(message) + + def __repr__(self) -> str: + args = self._mode, self._bufsize + kwargs = [("encoding", self._encoding), ("errors", self._errors)] + args_str = ", ".join( + [repr(arg) for arg in args if arg != -1] + + [f"{kw}={arg!r}" for kw, arg in kwargs if arg is not None] + ) + return f"{type(self).__name__}({args_str})" + + +def cli_detect(argv: list[str] | None = None) -> int: + """ + CLI assistant using ARGV and ArgumentParser + :param argv: + :return: 0 if everything is fine, anything else equal trouble + """ + parser = argparse.ArgumentParser( + description="The Real First Universal Charset Detector. " + "Discover originating encoding used on text file. " + "Normalize text to unicode." + ) + + parser.add_argument( + "files", type=FileType("rb"), nargs="+", help="File(s) to be analysed" + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + default=False, + dest="verbose", + help="Display complementary information about file if any. " + "Stdout will contain logs about the detection process.", + ) + parser.add_argument( + "-a", + "--with-alternative", + action="store_true", + default=False, + dest="alternatives", + help="Output complementary possibilities if any. Top-level JSON WILL be a list.", + ) + parser.add_argument( + "-n", + "--normalize", + action="store_true", + default=False, + dest="normalize", + help="Permit to normalize input file. If not set, program does not write anything.", + ) + parser.add_argument( + "-m", + "--minimal", + action="store_true", + default=False, + dest="minimal", + help="Only output the charset detected to STDOUT. Disabling JSON output.", + ) + parser.add_argument( + "-r", + "--replace", + action="store_true", + default=False, + dest="replace", + help="Replace file when trying to normalize it instead of creating a new one.", + ) + parser.add_argument( + "-f", + "--force", + action="store_true", + default=False, + dest="force", + help="Replace file without asking if you are sure, use this flag with caution.", + ) + parser.add_argument( + "-i", + "--no-preemptive", + action="store_true", + default=False, + dest="no_preemptive", + help="Disable looking at a charset declaration to hint the detector.", + ) + parser.add_argument( + "-t", + "--threshold", + action="store", + default=0.2, + type=float, + dest="threshold", + help="Define a custom maximum amount of noise allowed in decoded content. 0. <= noise <= 1.", + ) + parser.add_argument( + "--version", + action="version", + version="Charset-Normalizer {} - Python {} - Unicode {} - SpeedUp {}".format( + __version__, + python_version(), + unidata_version, + "OFF" if md_module.__file__.lower().endswith(".py") else "ON", + ), + help="Show version information and exit.", + ) + + args = parser.parse_args(argv) + + if args.replace is True and args.normalize is False: + if args.files: + for my_file in args.files: + my_file.close() + print("Use --replace in addition of --normalize only.", file=sys.stderr) + return 1 + + if args.force is True and args.replace is False: + if args.files: + for my_file in args.files: + my_file.close() + print("Use --force in addition of --replace only.", file=sys.stderr) + return 1 + + if args.threshold < 0.0 or args.threshold > 1.0: + if args.files: + for my_file in args.files: + my_file.close() + print("--threshold VALUE should be between 0. AND 1.", file=sys.stderr) + return 1 + + x_ = [] + + for my_file in args.files: + matches = from_fp( + my_file, + threshold=args.threshold, + explain=args.verbose, + preemptive_behaviour=args.no_preemptive is False, + ) + + best_guess = matches.best() + + if best_guess is None: + print( + 'Unable to identify originating encoding for "{}". {}'.format( + my_file.name, + ( + "Maybe try increasing maximum amount of chaos." + if args.threshold < 1.0 + else "" + ), + ), + file=sys.stderr, + ) + x_.append( + CliDetectionResult( + abspath(my_file.name), + None, + [], + [], + "Unknown", + [], + False, + 1.0, + 0.0, + None, + True, + ) + ) + else: + cli_result = CliDetectionResult( + abspath(my_file.name), + best_guess.encoding, + best_guess.encoding_aliases, + [ + cp + for cp in best_guess.could_be_from_charset + if cp != best_guess.encoding + ], + best_guess.language, + best_guess.alphabets, + best_guess.bom, + best_guess.percent_chaos, + best_guess.percent_coherence, + None, + True, + ) + x_.append(cli_result) + + if len(matches) > 1 and args.alternatives: + for el in matches: + if el != best_guess: + x_.append( + CliDetectionResult( + abspath(my_file.name), + el.encoding, + el.encoding_aliases, + [ + cp + for cp in el.could_be_from_charset + if cp != el.encoding + ], + el.language, + el.alphabets, + el.bom, + el.percent_chaos, + el.percent_coherence, + None, + False, + ) + ) + + if args.normalize is True: + if best_guess.encoding.startswith("utf") is True: + print( + '"{}" file does not need to be normalized, as it already came from unicode.'.format( + my_file.name + ), + file=sys.stderr, + ) + if my_file.closed is False: + my_file.close() + continue + + dir_path = dirname(realpath(my_file.name)) + file_name = basename(realpath(my_file.name)) + + o_: list[str] = file_name.split(".") + + if args.replace is False: + o_.insert(-1, best_guess.encoding) + if my_file.closed is False: + my_file.close() + elif ( + args.force is False + and query_yes_no( + 'Are you sure to normalize "{}" by replacing it ?'.format( + my_file.name + ), + "no", + ) + is False + ): + if my_file.closed is False: + my_file.close() + continue + + try: + cli_result.unicode_path = join(dir_path, ".".join(o_)) + + with open(cli_result.unicode_path, "wb") as fp: + fp.write(best_guess.output()) + except OSError as e: # Defensive: + print(str(e), file=sys.stderr) + if my_file.closed is False: + my_file.close() + return 2 + + if my_file.closed is False: + my_file.close() + + if args.minimal is False: + print( + dumps( + [el.__dict__ for el in x_] if len(x_) > 1 else x_[0].__dict__, + ensure_ascii=True, + indent=4, + ) + ) + else: + for my_file in args.files: + print( + ", ".join( + [ + el.encoding or "undefined" + for el in x_ + if el.path == abspath(my_file.name) + ] + ) + ) + + return 0 + + +if __name__ == "__main__": # Defensive: + cli_detect() diff --git a/intelliaide/vendor/charset_normalizer/constant.py b/intelliaide/vendor/charset_normalizer/constant.py new file mode 100644 index 0000000..e1297d2 --- /dev/null +++ b/intelliaide/vendor/charset_normalizer/constant.py @@ -0,0 +1,2050 @@ +from __future__ import annotations + +from codecs import BOM_UTF8, BOM_UTF16_BE, BOM_UTF16_LE, BOM_UTF32_BE, BOM_UTF32_LE +from encodings.aliases import aliases +from re import IGNORECASE +from re import compile as re_compile + +# Contain for each eligible encoding a list of/item bytes SIG/BOM +ENCODING_MARKS: dict[str, bytes | list[bytes]] = { + "utf_8": BOM_UTF8, + "utf_7": [ + b"\x2b\x2f\x76\x38\x2d", + b"\x2b\x2f\x76\x38", + b"\x2b\x2f\x76\x39", + b"\x2b\x2f\x76\x2b", + b"\x2b\x2f\x76\x2f", + ], + "gb18030": b"\x84\x31\x95\x33", + "utf_32": [BOM_UTF32_BE, BOM_UTF32_LE], + "utf_16": [BOM_UTF16_BE, BOM_UTF16_LE], +} + +TOO_SMALL_SEQUENCE: int = 32 +TOO_BIG_SEQUENCE: int = int(10e6) + +UTF8_MAXIMAL_ALLOCATION: int = 1_112_064 + +# Up-to-date Unicode ucd/17.0.0 +UNICODE_RANGES_COMBINED: dict[str, range] = { + "Control character": range(32), + "Basic Latin": range(32, 128), + "Latin-1 Supplement": range(128, 256), + "Latin Extended-A": range(256, 384), + "Latin Extended-B": range(384, 592), + "IPA Extensions": range(592, 688), + "Spacing Modifier Letters": range(688, 768), + "Combining Diacritical Marks": range(768, 880), + "Greek and Coptic": range(880, 1024), + "Cyrillic": range(1024, 1280), + "Cyrillic Supplement": range(1280, 1328), + "Armenian": range(1328, 1424), + "Hebrew": range(1424, 1536), + "Arabic": range(1536, 1792), + "Syriac": range(1792, 1872), + "Arabic Supplement": range(1872, 1920), + "Thaana": range(1920, 1984), + "NKo": range(1984, 2048), + "Samaritan": range(2048, 2112), + "Mandaic": range(2112, 2144), + "Syriac Supplement": range(2144, 2160), + "Arabic Extended-B": range(2160, 2208), + "Arabic Extended-A": range(2208, 2304), + "Devanagari": range(2304, 2432), + "Bengali": range(2432, 2560), + "Gurmukhi": range(2560, 2688), + "Gujarati": range(2688, 2816), + "Oriya": range(2816, 2944), + "Tamil": range(2944, 3072), + "Telugu": range(3072, 3200), + "Kannada": range(3200, 3328), + "Malayalam": range(3328, 3456), + "Sinhala": range(3456, 3584), + "Thai": range(3584, 3712), + "Lao": range(3712, 3840), + "Tibetan": range(3840, 4096), + "Myanmar": range(4096, 4256), + "Georgian": range(4256, 4352), + "Hangul Jamo": range(4352, 4608), + "Ethiopic": range(4608, 4992), + "Ethiopic Supplement": range(4992, 5024), + "Cherokee": range(5024, 5120), + "Unified Canadian Aboriginal Syllabics": range(5120, 5760), + "Ogham": range(5760, 5792), + "Runic": range(5792, 5888), + "Tagalog": range(5888, 5920), + "Hanunoo": range(5920, 5952), + "Buhid": range(5952, 5984), + "Tagbanwa": range(5984, 6016), + "Khmer": range(6016, 6144), + "Mongolian": range(6144, 6320), + "Unified Canadian Aboriginal Syllabics Extended": range(6320, 6400), + "Limbu": range(6400, 6480), + "Tai Le": range(6480, 6528), + "New Tai Lue": range(6528, 6624), + "Khmer Symbols": range(6624, 6656), + "Buginese": range(6656, 6688), + "Tai Tham": range(6688, 6832), + "Combining Diacritical Marks Extended": range(6832, 6912), + "Balinese": range(6912, 7040), + "Sundanese": range(7040, 7104), + "Batak": range(7104, 7168), + "Lepcha": range(7168, 7248), + "Ol Chiki": range(7248, 7296), + "Cyrillic Extended-C": range(7296, 7312), + "Georgian Extended": range(7312, 7360), + "Sundanese Supplement": range(7360, 7376), + "Vedic Extensions": range(7376, 7424), + "Phonetic Extensions": range(7424, 7552), + "Phonetic Extensions Supplement": range(7552, 7616), + "Combining Diacritical Marks Supplement": range(7616, 7680), + "Latin Extended Additional": range(7680, 7936), + "Greek Extended": range(7936, 8192), + "General Punctuation": range(8192, 8304), + "Superscripts and Subscripts": range(8304, 8352), + "Currency Symbols": range(8352, 8400), + "Combining Diacritical Marks for Symbols": range(8400, 8448), + "Letterlike Symbols": range(8448, 8528), + "Number Forms": range(8528, 8592), + "Arrows": range(8592, 8704), + "Mathematical Operators": range(8704, 8960), + "Miscellaneous Technical": range(8960, 9216), + "Control Pictures": range(9216, 9280), + "Optical Character Recognition": range(9280, 9312), + "Enclosed Alphanumerics": range(9312, 9472), + "Box Drawing": range(9472, 9600), + "Block Elements": range(9600, 9632), + "Geometric Shapes": range(9632, 9728), + "Miscellaneous Symbols": range(9728, 9984), + "Dingbats": range(9984, 10176), + "Miscellaneous Mathematical Symbols-A": range(10176, 10224), + "Supplemental Arrows-A": range(10224, 10240), + "Braille Patterns": range(10240, 10496), + "Supplemental Arrows-B": range(10496, 10624), + "Miscellaneous Mathematical Symbols-B": range(10624, 10752), + "Supplemental Mathematical Operators": range(10752, 11008), + "Miscellaneous Symbols and Arrows": range(11008, 11264), + "Glagolitic": range(11264, 11360), + "Latin Extended-C": range(11360, 11392), + "Coptic": range(11392, 11520), + "Georgian Supplement": range(11520, 11568), + "Tifinagh": range(11568, 11648), + "Ethiopic Extended": range(11648, 11744), + "Cyrillic Extended-A": range(11744, 11776), + "Supplemental Punctuation": range(11776, 11904), + "CJK Radicals Supplement": range(11904, 12032), + "Kangxi Radicals": range(12032, 12256), + "Ideographic Description Characters": range(12272, 12288), + "CJK Symbols and Punctuation": range(12288, 12352), + "Hiragana": range(12352, 12448), + "Katakana": range(12448, 12544), + "Bopomofo": range(12544, 12592), + "Hangul Compatibility Jamo": range(12592, 12688), + "Kanbun": range(12688, 12704), + "Bopomofo Extended": range(12704, 12736), + "CJK Strokes": range(12736, 12784), + "Katakana Phonetic Extensions": range(12784, 12800), + "Enclosed CJK Letters and Months": range(12800, 13056), + "CJK Compatibility": range(13056, 13312), + "CJK Unified Ideographs Extension A": range(13312, 19904), + "Yijing Hexagram Symbols": range(19904, 19968), + "CJK Unified Ideographs": range(19968, 40960), + "Yi Syllables": range(40960, 42128), + "Yi Radicals": range(42128, 42192), + "Lisu": range(42192, 42240), + "Vai": range(42240, 42560), + "Cyrillic Extended-B": range(42560, 42656), + "Bamum": range(42656, 42752), + "Modifier Tone Letters": range(42752, 42784), + "Latin Extended-D": range(42784, 43008), + "Syloti Nagri": range(43008, 43056), + "Common Indic Number Forms": range(43056, 43072), + "Phags-pa": range(43072, 43136), + "Saurashtra": range(43136, 43232), + "Devanagari Extended": range(43232, 43264), + "Kayah Li": range(43264, 43312), + "Rejang": range(43312, 43360), + "Hangul Jamo Extended-A": range(43360, 43392), + "Javanese": range(43392, 43488), + "Myanmar Extended-B": range(43488, 43520), + "Cham": range(43520, 43616), + "Myanmar Extended-A": range(43616, 43648), + "Tai Viet": range(43648, 43744), + "Meetei Mayek Extensions": range(43744, 43776), + "Ethiopic Extended-A": range(43776, 43824), + "Latin Extended-E": range(43824, 43888), + "Cherokee Supplement": range(43888, 43968), + "Meetei Mayek": range(43968, 44032), + "Hangul Syllables": range(44032, 55216), + "Hangul Jamo Extended-B": range(55216, 55296), + "High Surrogates": range(55296, 56192), + "High Private Use Surrogates": range(56192, 56320), + "Low Surrogates": range(56320, 57344), + "Private Use Area": range(57344, 63744), + "CJK Compatibility Ideographs": range(63744, 64256), + "Alphabetic Presentation Forms": range(64256, 64336), + "Arabic Presentation Forms-A": range(64336, 65024), + "Variation Selectors": range(65024, 65040), + "Vertical Forms": range(65040, 65056), + "Combining Half Marks": range(65056, 65072), + "CJK Compatibility Forms": range(65072, 65104), + "Small Form Variants": range(65104, 65136), + "Arabic Presentation Forms-B": range(65136, 65280), + "Halfwidth and Fullwidth Forms": range(65280, 65520), + "Specials": range(65520, 65536), + "Linear B Syllabary": range(65536, 65664), + "Linear B Ideograms": range(65664, 65792), + "Aegean Numbers": range(65792, 65856), + "Ancient Greek Numbers": range(65856, 65936), + "Ancient Symbols": range(65936, 66000), + "Phaistos Disc": range(66000, 66048), + "Lycian": range(66176, 66208), + "Carian": range(66208, 66272), + "Coptic Epact Numbers": range(66272, 66304), + "Old Italic": range(66304, 66352), + "Gothic": range(66352, 66384), + "Old Permic": range(66384, 66432), + "Ugaritic": range(66432, 66464), + "Old Persian": range(66464, 66528), + "Deseret": range(66560, 66640), + "Shavian": range(66640, 66688), + "Osmanya": range(66688, 66736), + "Osage": range(66736, 66816), + "Elbasan": range(66816, 66864), + "Caucasian Albanian": range(66864, 66928), + "Vithkuqi": range(66928, 67008), + "Todhri": range(67008, 67072), + "Linear A": range(67072, 67456), + "Latin Extended-F": range(67456, 67520), + "Cypriot Syllabary": range(67584, 67648), + "Imperial Aramaic": range(67648, 67680), + "Palmyrene": range(67680, 67712), + "Nabataean": range(67712, 67760), + "Hatran": range(67808, 67840), + "Phoenician": range(67840, 67872), + "Lydian": range(67872, 67904), + "Sidetic": range(67904, 67936), + "Meroitic Hieroglyphs": range(67968, 68000), + "Meroitic Cursive": range(68000, 68096), + "Kharoshthi": range(68096, 68192), + "Old South Arabian": range(68192, 68224), + "Old North Arabian": range(68224, 68256), + "Manichaean": range(68288, 68352), + "Avestan": range(68352, 68416), + "Inscriptional Parthian": range(68416, 68448), + "Inscriptional Pahlavi": range(68448, 68480), + "Psalter Pahlavi": range(68480, 68528), + "Old Turkic": range(68608, 68688), + "Old Hungarian": range(68736, 68864), + "Hanifi Rohingya": range(68864, 68928), + "Garay": range(68928, 69008), + "Rumi Numeral Symbols": range(69216, 69248), + "Yezidi": range(69248, 69312), + "Arabic Extended-C": range(69312, 69376), + "Old Sogdian": range(69376, 69424), + "Sogdian": range(69424, 69488), + "Old Uyghur": range(69488, 69552), + "Chorasmian": range(69552, 69600), + "Elymaic": range(69600, 69632), + "Brahmi": range(69632, 69760), + "Kaithi": range(69760, 69840), + "Sora Sompeng": range(69840, 69888), + "Chakma": range(69888, 69968), + "Mahajani": range(69968, 70016), + "Sharada": range(70016, 70112), + "Sinhala Archaic Numbers": range(70112, 70144), + "Khojki": range(70144, 70224), + "Multani": range(70272, 70320), + "Khudawadi": range(70320, 70400), + "Grantha": range(70400, 70528), + "Tulu-Tigalari": range(70528, 70656), + "Newa": range(70656, 70784), + "Tirhuta": range(70784, 70880), + "Siddham": range(71040, 71168), + "Modi": range(71168, 71264), + "Mongolian Supplement": range(71264, 71296), + "Takri": range(71296, 71376), + "Myanmar Extended-C": range(71376, 71424), + "Ahom": range(71424, 71504), + "Dogra": range(71680, 71760), + "Warang Citi": range(71840, 71936), + "Dives Akuru": range(71936, 72032), + "Nandinagari": range(72096, 72192), + "Zanabazar Square": range(72192, 72272), + "Soyombo": range(72272, 72368), + "Unified Canadian Aboriginal Syllabics Extended-A": range(72368, 72384), + "Pau Cin Hau": range(72384, 72448), + "Devanagari Extended-A": range(72448, 72544), + "Sharada Supplement": range(72544, 72576), + "Sunuwar": range(72640, 72704), + "Bhaiksuki": range(72704, 72816), + "Marchen": range(72816, 72896), + "Masaram Gondi": range(72960, 73056), + "Gunjala Gondi": range(73056, 73136), + "Tolong Siki": range(73136, 73200), + "Makasar": range(73440, 73472), + "Kawi": range(73472, 73568), + "Lisu Supplement": range(73648, 73664), + "Tamil Supplement": range(73664, 73728), + "Cuneiform": range(73728, 74752), + "Cuneiform Numbers and Punctuation": range(74752, 74880), + "Early Dynastic Cuneiform": range(74880, 75088), + "Cypro-Minoan": range(77712, 77824), + "Egyptian Hieroglyphs": range(77824, 78896), + "Egyptian Hieroglyph Format Controls": range(78896, 78944), + "Egyptian Hieroglyphs Extended-A": range(78944, 82944), + "Anatolian Hieroglyphs": range(82944, 83584), + "Gurung Khema": range(90368, 90432), + "Bamum Supplement": range(92160, 92736), + "Mro": range(92736, 92784), + "Tangsa": range(92784, 92880), + "Bassa Vah": range(92880, 92928), + "Pahawh Hmong": range(92928, 93072), + "Kirat Rai": range(93504, 93568), + "Medefaidrin": range(93760, 93856), + "Beria Erfe": range(93856, 93920), + "Miao": range(93952, 94112), + "Ideographic Symbols and Punctuation": range(94176, 94208), + "Tangut": range(94208, 100352), + "Tangut Components": range(100352, 101120), + "Khitan Small Script": range(101120, 101632), + "Tangut Supplement": range(101632, 101760), + "Tangut Components Supplement": range(101760, 101888), + "Kana Extended-B": range(110576, 110592), + "Kana Supplement": range(110592, 110848), + "Kana Extended-A": range(110848, 110896), + "Small Kana Extension": range(110896, 110960), + "Nushu": range(110960, 111360), + "Duployan": range(113664, 113824), + "Shorthand Format Controls": range(113824, 113840), + "Symbols for Legacy Computing Supplement": range(117760, 118464), + "Miscellaneous Symbols Supplement": range(118464, 118528), + "Znamenny Musical Notation": range(118528, 118736), + "Byzantine Musical Symbols": range(118784, 119040), + "Musical Symbols": range(119040, 119296), + "Ancient Greek Musical Notation": range(119296, 119376), + "Kaktovik Numerals": range(119488, 119520), + "Mayan Numerals": range(119520, 119552), + "Tai Xuan Jing Symbols": range(119552, 119648), + "Counting Rod Numerals": range(119648, 119680), + "Mathematical Alphanumeric Symbols": range(119808, 120832), + "Sutton SignWriting": range(120832, 121520), + "Latin Extended-G": range(122624, 122880), + "Glagolitic Supplement": range(122880, 122928), + "Cyrillic Extended-D": range(122928, 123024), + "Nyiakeng Puachue Hmong": range(123136, 123216), + "Toto": range(123536, 123584), + "Wancho": range(123584, 123648), + "Nag Mundari": range(124112, 124160), + "Ol Onal": range(124368, 124416), + "Tai Yo": range(124608, 124672), + "Ethiopic Extended-B": range(124896, 124928), + "Mende Kikakui": range(124928, 125152), + "Adlam": range(125184, 125280), + "Indic Siyaq Numbers": range(126064, 126144), + "Ottoman Siyaq Numbers": range(126208, 126288), + "Arabic Mathematical Alphabetic Symbols": range(126464, 126720), + "Mahjong Tiles": range(126976, 127024), + "Domino Tiles": range(127024, 127136), + "Playing Cards": range(127136, 127232), + "Enclosed Alphanumeric Supplement": range(127232, 127488), + "Enclosed Ideographic Supplement": range(127488, 127744), + "Miscellaneous Symbols and Pictographs": range(127744, 128512), + "Emoticons": range(128512, 128592), + "Ornamental Dingbats": range(128592, 128640), + "Transport and Map Symbols": range(128640, 128768), + "Alchemical Symbols": range(128768, 128896), + "Geometric Shapes Extended": range(128896, 129024), + "Supplemental Arrows-C": range(129024, 129280), + "Supplemental Symbols and Pictographs": range(129280, 129536), + "Chess Symbols": range(129536, 129648), + "Symbols and Pictographs Extended-A": range(129648, 129792), + "Symbols for Legacy Computing": range(129792, 130048), + "CJK Unified Ideographs Extension B": range(131072, 173792), + "CJK Unified Ideographs Extension C": range(173824, 177984), + "CJK Unified Ideographs Extension D": range(177984, 178208), + "CJK Unified Ideographs Extension E": range(178208, 183984), + "CJK Unified Ideographs Extension F": range(183984, 191472), + "CJK Unified Ideographs Extension I": range(191472, 192096), + "CJK Compatibility Ideographs Supplement": range(194560, 195104), + "CJK Unified Ideographs Extension G": range(196608, 201552), + "CJK Unified Ideographs Extension H": range(201552, 205744), + "CJK Unified Ideographs Extension J": range(205744, 210048), + "Tags": range(917504, 917632), + "Variation Selectors Supplement": range(917760, 918000), + "Supplementary Private Use Area-A": range(983040, 1048576), + "Supplementary Private Use Area-B": range(1048576, 1114112), +} + + +UNICODE_SECONDARY_RANGE_KEYWORD: list[str] = [ + "Supplement", + "Extended", + "Extensions", + "Modifier", + "Marks", + "Punctuation", + "Symbols", + "Forms", + "Operators", + "Miscellaneous", + "Drawing", + "Block", + "Shapes", + "Supplemental", + "Tags", +] + +RE_POSSIBLE_ENCODING_INDICATION = re_compile( + r"(?:(?:encoding)|(?:charset)|(?:coding))(?:[\:= ]{1,10})(?:[\"\']?)([a-zA-Z0-9\-_]+)(?:[\"\']?)", + IGNORECASE, +) + +IANA_NO_ALIASES = [ + "cp720", + "cp737", + "cp856", + "cp874", + "cp875", + "cp1006", + "koi8_r", + "koi8_t", + "koi8_u", +] + +IANA_SUPPORTED: list[str] = sorted( + filter( + lambda x: x.endswith("_codec") is False + and x not in {"rot_13", "tactis", "mbcs"}, + list(set(aliases.values())) + IANA_NO_ALIASES, + ) +) + +IANA_SUPPORTED_COUNT: int = len(IANA_SUPPORTED) + +# pre-computed code page that are similar using the function cp_similarity. +IANA_SUPPORTED_SIMILAR: dict[str, list[str]] = { + "cp037": ["cp1026", "cp1140", "cp273", "cp500"], + "cp1026": ["cp037", "cp1140", "cp273", "cp500"], + "cp1125": ["cp866"], + "cp1140": ["cp037", "cp1026", "cp273", "cp500"], + "cp1250": ["iso8859_2"], + "cp1251": ["kz1048", "ptcp154"], + "cp1252": ["iso8859_15", "iso8859_9", "latin_1"], + "cp1253": ["iso8859_7"], + "cp1254": ["iso8859_15", "iso8859_9", "latin_1"], + "cp1257": ["iso8859_13"], + "cp273": ["cp037", "cp1026", "cp1140", "cp500"], + "cp437": ["cp850", "cp858", "cp860", "cp861", "cp862", "cp863", "cp865"], + "cp500": ["cp037", "cp1026", "cp1140", "cp273"], + "cp850": ["cp437", "cp857", "cp858", "cp865"], + "cp857": ["cp850", "cp858", "cp865"], + "cp858": ["cp437", "cp850", "cp857", "cp865"], + "cp860": ["cp437", "cp861", "cp862", "cp863", "cp865"], + "cp861": ["cp437", "cp860", "cp862", "cp863", "cp865"], + "cp862": ["cp437", "cp860", "cp861", "cp863", "cp865"], + "cp863": ["cp437", "cp860", "cp861", "cp862", "cp865"], + "cp865": ["cp437", "cp850", "cp857", "cp858", "cp860", "cp861", "cp862", "cp863"], + "cp866": ["cp1125"], + "iso8859_10": ["iso8859_14", "iso8859_15", "iso8859_4", "iso8859_9", "latin_1"], + "iso8859_11": ["tis_620"], + "iso8859_13": ["cp1257"], + "iso8859_14": [ + "iso8859_10", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_9", + "latin_1", + ], + "iso8859_15": [ + "cp1252", + "cp1254", + "iso8859_10", + "iso8859_14", + "iso8859_16", + "iso8859_3", + "iso8859_9", + "latin_1", + ], + "iso8859_16": [ + "iso8859_14", + "iso8859_15", + "iso8859_2", + "iso8859_3", + "iso8859_9", + "latin_1", + ], + "iso8859_2": ["cp1250", "iso8859_16", "iso8859_4"], + "iso8859_3": ["iso8859_14", "iso8859_15", "iso8859_16", "iso8859_9", "latin_1"], + "iso8859_4": ["iso8859_10", "iso8859_2", "iso8859_9", "latin_1"], + "iso8859_7": ["cp1253"], + "iso8859_9": [ + "cp1252", + "cp1254", + "cp1258", + "iso8859_10", + "iso8859_14", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_4", + "latin_1", + ], + "kz1048": ["cp1251", "ptcp154"], + "latin_1": [ + "cp1252", + "cp1254", + "cp1258", + "iso8859_10", + "iso8859_14", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_4", + "iso8859_9", + ], + "mac_iceland": ["mac_roman", "mac_turkish"], + "mac_roman": ["mac_iceland", "mac_turkish"], + "mac_turkish": ["mac_iceland", "mac_roman"], + "ptcp154": ["cp1251", "kz1048"], + "tis_620": ["iso8859_11"], +} + + +CHARDET_CORRESPONDENCE: dict[str, str] = { + "iso2022_kr": "ISO-2022-KR", + "iso2022_jp": "ISO-2022-JP", + "euc_kr": "EUC-KR", + "tis_620": "TIS-620", + "utf_32": "UTF-32", + "euc_jp": "EUC-JP", + "koi8_r": "KOI8-R", + "iso8859_1": "ISO-8859-1", + "iso8859_2": "ISO-8859-2", + "iso8859_5": "ISO-8859-5", + "iso8859_6": "ISO-8859-6", + "iso8859_7": "ISO-8859-7", + "iso8859_8": "ISO-8859-8", + "utf_16": "UTF-16", + "cp855": "IBM855", + "mac_cyrillic": "MacCyrillic", + "gb2312": "GB2312", + "gb18030": "GB18030", + "cp932": "CP932", + "cp866": "IBM866", + "utf_8": "utf-8", + "utf_8_sig": "UTF-8-SIG", + "shift_jis": "SHIFT_JIS", + "big5": "Big5", + "cp1250": "windows-1250", + "cp1251": "windows-1251", + "cp1252": "Windows-1252", + "cp1253": "windows-1253", + "cp1255": "windows-1255", + "cp1256": "windows-1256", + "cp1254": "Windows-1254", + "cp949": "CP949", +} + + +COMMON_SAFE_ASCII_CHARACTERS: frozenset[str] = frozenset( + { + "<", + ">", + "=", + ":", + "/", + "&", + ";", + "{", + "}", + "[", + "]", + ",", + "|", + '"', + "-", + "(", + ")", + } +) + +# Sample character sets — replace with full lists if needed +COMMON_CHINESE_CHARACTERS = "的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应开它合还因由其些然前外天政四日那社义事平形相全表间样与关各重新线内数正心反你明看原又么利比或但质气第向道命此变条只没结解问意建月公无系军很情者最立代想已通并提直题党程展五果料象员革位入常文总次品式活设及管特件长求老头基资边流路级少图山统接知较将组见计别她手角期根论运农指几九区强放决西被干做必战先回则任取据处队南给色光门即保治北造百规热领七海口东导器压志世金增争济阶油思术极交受联什认六共权收证改清己美再采转更单风切打白教速花带安场身车例真务具万每目至达走积示议声报斗完类八离华名确才科张信马节话米整空元况今集温传土许步群广石记需段研界拉林律叫且究观越织装影算低持音众书布复容儿须际商非验连断深难近矿千周委素技备半办青省列习响约支般史感劳便团往酸历市克何除消构府太准精值号率族维划选标写存候毛亲快效斯院查江型眼王按格养易置派层片始却专状育厂京识适属圆包火住调满县局照参红细引听该铁价严龙飞" + +COMMON_JAPANESE_CHARACTERS = "日一国年大十二本中長出三時行見月分後前生五間上東四今金九入学高円子外八六下来気小七山話女北午百書先名川千水半男西電校語土木聞食車何南万毎白天母火右読友左休父雨" + +COMMON_KOREAN_CHARACTERS = "一二三四五六七八九十百千萬上下左右中人女子大小山川日月火水木金土父母天地國名年時文校學生" + +# Combine all into a frozenset +COMMON_CJK_CHARACTERS = frozenset( + "".join( + [ + COMMON_CHINESE_CHARACTERS, + COMMON_JAPANESE_CHARACTERS, + COMMON_KOREAN_CHARACTERS, + ] + ) +) + +KO_NAMES: frozenset[str] = frozenset({"johab", "cp949", "euc_kr"}) +ZH_NAMES: frozenset[str] = frozenset({"big5", "cp950", "big5hkscs", "hz"}) + +# Logging LEVEL below DEBUG +TRACE: int = 5 + + +# Language label that contain the em dash "—" +# character are to be considered alternative seq to origin +FREQUENCIES: dict[str, list[str]] = { + "English": [ + "e", + "a", + "t", + "i", + "o", + "n", + "s", + "r", + "h", + "l", + "d", + "c", + "u", + "m", + "f", + "p", + "g", + "w", + "y", + "b", + "v", + "k", + "x", + "j", + "z", + "q", + ], + "English—": [ + "e", + "a", + "t", + "i", + "o", + "n", + "s", + "r", + "h", + "l", + "d", + "c", + "m", + "u", + "f", + "p", + "g", + "w", + "b", + "y", + "v", + "k", + "j", + "x", + "z", + "q", + ], + "German": [ + "e", + "n", + "i", + "r", + "s", + "t", + "a", + "d", + "h", + "u", + "l", + "g", + "o", + "c", + "m", + "b", + "f", + "k", + "w", + "z", + "p", + "v", + "ü", + "ä", + "ö", + "j", + ], + "French": [ + "e", + "a", + "s", + "n", + "i", + "t", + "r", + "l", + "u", + "o", + "d", + "c", + "p", + "m", + "é", + "v", + "g", + "f", + "b", + "h", + "q", + "à", + "x", + "è", + "y", + "j", + ], + "Dutch": [ + "e", + "n", + "a", + "i", + "r", + "t", + "o", + "d", + "s", + "l", + "g", + "h", + "v", + "m", + "u", + "k", + "c", + "p", + "b", + "w", + "j", + "z", + "f", + "y", + "x", + "ë", + ], + "Italian": [ + "e", + "i", + "a", + "o", + "n", + "l", + "t", + "r", + "s", + "c", + "d", + "u", + "p", + "m", + "g", + "v", + "f", + "b", + "z", + "h", + "q", + "è", + "à", + "k", + "y", + "ò", + ], + "Polish": [ + "a", + "i", + "o", + "e", + "n", + "r", + "z", + "w", + "s", + "c", + "t", + "k", + "y", + "d", + "p", + "m", + "u", + "l", + "j", + "ł", + "g", + "b", + "h", + "ą", + "ę", + "ó", + ], + "Spanish": [ + "e", + "a", + "o", + "n", + "s", + "r", + "i", + "l", + "d", + "t", + "c", + "u", + "m", + "p", + "b", + "g", + "v", + "f", + "y", + "ó", + "h", + "q", + "í", + "j", + "z", + "á", + ], + "Russian": [ + "о", + "е", + "а", + "и", + "н", + "т", + "с", + "р", + "в", + "л", + "к", + "м", + "д", + "п", + "у", + "г", + "я", + "ы", + "з", + "б", + "й", + "ь", + "ч", + "х", + "ж", + "ц", + ], + # Jap-Kanji + "Japanese": [ + "日", + "一", + "人", + "年", + "大", + "十", + "二", + "本", + "中", + "長", + "出", + "三", + "時", + "行", + "見", + "月", + "分", + "後", + "前", + "生", + "五", + "間", + "上", + "東", + "四", + "今", + "金", + "九", + "入", + "学", + "高", + "円", + "子", + "外", + "八", + "六", + "下", + "来", + "気", + "小", + "七", + "山", + "話", + "女", + "北", + "午", + "百", + "書", + "先", + "名", + "川", + "千", + "水", + "半", + "男", + "西", + "電", + "校", + "語", + "土", + "木", + "聞", + "食", + "車", + "何", + "南", + "万", + "毎", + "白", + "天", + "母", + "火", + "右", + "読", + "友", + "左", + "休", + "父", + "雨", + ], + # Jap-Katakana + "Japanese—": [ + "ー", + "ン", + "ス", + "・", + "ル", + "ト", + "リ", + "イ", + "ア", + "ラ", + "ッ", + "ク", + "ド", + "シ", + "レ", + "ジ", + "タ", + "フ", + "ロ", + "カ", + "テ", + "マ", + "ィ", + "グ", + "バ", + "ム", + "プ", + "オ", + "コ", + "デ", + "ニ", + "ウ", + "メ", + "サ", + "ビ", + "ナ", + "ブ", + "ャ", + "エ", + "ュ", + "チ", + "キ", + "ズ", + "ダ", + "パ", + "ミ", + "ェ", + "ョ", + "ハ", + "セ", + "ベ", + "ガ", + "モ", + "ツ", + "ネ", + "ボ", + "ソ", + "ノ", + "ァ", + "ヴ", + "ワ", + "ポ", + "ペ", + "ピ", + "ケ", + "ゴ", + "ギ", + "ザ", + "ホ", + "ゲ", + "ォ", + "ヤ", + "ヒ", + "ユ", + "ヨ", + "ヘ", + "ゼ", + "ヌ", + "ゥ", + "ゾ", + "ヶ", + "ヂ", + "ヲ", + "ヅ", + "ヵ", + "ヱ", + "ヰ", + "ヮ", + "ヽ", + "゠", + "ヾ", + "ヷ", + "ヿ", + "ヸ", + "ヹ", + "ヺ", + ], + # Jap-Hiragana + "Japanese——": [ + "の", + "に", + "る", + "た", + "と", + "は", + "し", + "い", + "を", + "で", + "て", + "が", + "な", + "れ", + "か", + "ら", + "さ", + "っ", + "り", + "す", + "あ", + "も", + "こ", + "ま", + "う", + "く", + "よ", + "き", + "ん", + "め", + "お", + "け", + "そ", + "つ", + "だ", + "や", + "え", + "ど", + "わ", + "ち", + "み", + "せ", + "じ", + "ば", + "へ", + "び", + "ず", + "ろ", + "ほ", + "げ", + "む", + "べ", + "ひ", + "ょ", + "ゆ", + "ぶ", + "ご", + "ゃ", + "ね", + "ふ", + "ぐ", + "ぎ", + "ぼ", + "ゅ", + "づ", + "ざ", + "ぞ", + "ぬ", + "ぜ", + "ぱ", + "ぽ", + "ぷ", + "ぴ", + "ぃ", + "ぁ", + "ぇ", + "ぺ", + "ゞ", + "ぢ", + "ぉ", + "ぅ", + "ゐ", + "ゝ", + "ゑ", + "゛", + "゜", + "ゎ", + "ゔ", + "゚", + "ゟ", + "゙", + "ゕ", + "ゖ", + ], + "Portuguese": [ + "a", + "e", + "o", + "s", + "i", + "r", + "d", + "n", + "t", + "m", + "u", + "c", + "l", + "p", + "g", + "v", + "b", + "f", + "h", + "ã", + "q", + "é", + "ç", + "á", + "z", + "í", + ], + "Swedish": [ + "e", + "a", + "n", + "r", + "t", + "s", + "i", + "l", + "d", + "o", + "m", + "k", + "g", + "v", + "h", + "f", + "u", + "p", + "ä", + "c", + "b", + "ö", + "å", + "y", + "j", + "x", + ], + "Chinese": [ + "的", + "一", + "是", + "不", + "了", + "在", + "人", + "有", + "我", + "他", + "这", + "个", + "们", + "中", + "来", + "上", + "大", + "为", + "和", + "国", + "地", + "到", + "以", + "说", + "时", + "要", + "就", + "出", + "会", + "可", + "也", + "你", + "对", + "生", + "能", + "而", + "子", + "那", + "得", + "于", + "着", + "下", + "自", + "之", + "年", + "过", + "发", + "后", + "作", + "里", + "用", + "道", + "行", + "所", + "然", + "家", + "种", + "事", + "成", + "方", + "多", + "经", + "么", + "去", + "法", + "学", + "如", + "都", + "同", + "现", + "当", + "没", + "动", + "面", + "起", + "看", + "定", + "天", + "分", + "还", + "进", + "好", + "小", + "部", + "其", + "些", + "主", + "样", + "理", + "心", + "她", + "本", + "前", + "开", + "但", + "因", + "只", + "从", + "想", + "实", + ], + "Ukrainian": [ + "о", + "а", + "н", + "і", + "и", + "р", + "в", + "т", + "е", + "с", + "к", + "л", + "у", + "д", + "м", + "п", + "з", + "я", + "ь", + "б", + "г", + "й", + "ч", + "х", + "ц", + "ї", + ], + "Norwegian": [ + "e", + "r", + "n", + "t", + "a", + "s", + "i", + "o", + "l", + "d", + "g", + "k", + "m", + "v", + "f", + "p", + "u", + "b", + "h", + "å", + "y", + "j", + "ø", + "c", + "æ", + "w", + ], + "Finnish": [ + "a", + "i", + "n", + "t", + "e", + "s", + "l", + "o", + "u", + "k", + "ä", + "m", + "r", + "v", + "j", + "h", + "p", + "y", + "d", + "ö", + "g", + "c", + "b", + "f", + "w", + "z", + ], + "Vietnamese": [ + "n", + "h", + "t", + "i", + "c", + "g", + "a", + "o", + "u", + "m", + "l", + "r", + "à", + "đ", + "s", + "e", + "v", + "p", + "b", + "y", + "ư", + "d", + "á", + "k", + "ộ", + "ế", + ], + "Czech": [ + "o", + "e", + "a", + "n", + "t", + "s", + "i", + "l", + "v", + "r", + "k", + "d", + "u", + "m", + "p", + "í", + "c", + "h", + "z", + "á", + "y", + "j", + "b", + "ě", + "é", + "ř", + ], + "Hungarian": [ + "e", + "a", + "t", + "l", + "s", + "n", + "k", + "r", + "i", + "o", + "z", + "á", + "é", + "g", + "m", + "b", + "y", + "v", + "d", + "h", + "u", + "p", + "j", + "ö", + "f", + "c", + ], + "Korean": [ + "이", + "다", + "에", + "의", + "는", + "로", + "하", + "을", + "가", + "고", + "지", + "서", + "한", + "은", + "기", + "으", + "년", + "대", + "사", + "시", + "를", + "리", + "도", + "인", + "스", + "일", + ], + "Indonesian": [ + "a", + "n", + "e", + "i", + "r", + "t", + "u", + "s", + "d", + "k", + "m", + "l", + "g", + "p", + "b", + "o", + "h", + "y", + "j", + "c", + "w", + "f", + "v", + "z", + "x", + "q", + ], + "Turkish": [ + "a", + "e", + "i", + "n", + "r", + "l", + "ı", + "k", + "d", + "t", + "s", + "m", + "y", + "u", + "o", + "b", + "ü", + "ş", + "v", + "g", + "z", + "h", + "c", + "p", + "ç", + "ğ", + ], + "Romanian": [ + "e", + "i", + "a", + "r", + "n", + "t", + "u", + "l", + "o", + "c", + "s", + "d", + "p", + "m", + "ă", + "f", + "v", + "î", + "g", + "b", + "ș", + "ț", + "z", + "h", + "â", + "j", + ], + "Farsi": [ + "ا", + "ی", + "ر", + "د", + "ن", + "ه", + "و", + "م", + "ت", + "ب", + "س", + "ل", + "ک", + "ش", + "ز", + "ف", + "گ", + "ع", + "خ", + "ق", + "ج", + "آ", + "پ", + "ح", + "ط", + "ص", + ], + "Arabic": [ + "ا", + "ل", + "ي", + "م", + "و", + "ن", + "ر", + "ت", + "ب", + "ة", + "ع", + "د", + "س", + "ف", + "ه", + "ك", + "ق", + "أ", + "ح", + "ج", + "ش", + "ط", + "ص", + "ى", + "خ", + "إ", + ], + "Danish": [ + "e", + "r", + "n", + "t", + "a", + "i", + "s", + "d", + "l", + "o", + "g", + "m", + "k", + "f", + "v", + "u", + "b", + "h", + "p", + "å", + "y", + "ø", + "æ", + "c", + "j", + "w", + ], + "Serbian": [ + "а", + "и", + "о", + "е", + "н", + "р", + "с", + "у", + "т", + "к", + "ј", + "в", + "д", + "м", + "п", + "л", + "г", + "з", + "б", + "a", + "i", + "e", + "o", + "n", + "ц", + "ш", + ], + "Lithuanian": [ + "i", + "a", + "s", + "o", + "r", + "e", + "t", + "n", + "u", + "k", + "m", + "l", + "p", + "v", + "d", + "j", + "g", + "ė", + "b", + "y", + "ų", + "š", + "ž", + "c", + "ą", + "į", + ], + "Slovene": [ + "e", + "a", + "i", + "o", + "n", + "r", + "s", + "l", + "t", + "j", + "v", + "k", + "d", + "p", + "m", + "u", + "z", + "b", + "g", + "h", + "č", + "c", + "š", + "ž", + "f", + "y", + ], + "Slovak": [ + "o", + "a", + "e", + "n", + "i", + "r", + "v", + "t", + "s", + "l", + "k", + "d", + "m", + "p", + "u", + "c", + "h", + "j", + "b", + "z", + "á", + "y", + "ý", + "í", + "č", + "é", + ], + "Hebrew": [ + "י", + "ו", + "ה", + "ל", + "ר", + "ב", + "ת", + "מ", + "א", + "ש", + "נ", + "ע", + "ם", + "ד", + "ק", + "ח", + "פ", + "ס", + "כ", + "ג", + "ט", + "צ", + "ן", + "ז", + "ך", + ], + "Bulgarian": [ + "а", + "и", + "о", + "е", + "н", + "т", + "р", + "с", + "в", + "л", + "к", + "д", + "п", + "м", + "з", + "г", + "я", + "ъ", + "у", + "б", + "ч", + "ц", + "й", + "ж", + "щ", + "х", + ], + "Croatian": [ + "a", + "i", + "o", + "e", + "n", + "r", + "j", + "s", + "t", + "u", + "k", + "l", + "v", + "d", + "m", + "p", + "g", + "z", + "b", + "c", + "č", + "h", + "š", + "ž", + "ć", + "f", + ], + "Hindi": [ + "क", + "र", + "स", + "न", + "त", + "म", + "ह", + "प", + "य", + "ल", + "व", + "ज", + "द", + "ग", + "ब", + "श", + "ट", + "अ", + "ए", + "थ", + "भ", + "ड", + "च", + "ध", + "ष", + "इ", + ], + "Estonian": [ + "a", + "i", + "e", + "s", + "t", + "l", + "u", + "n", + "o", + "k", + "r", + "d", + "m", + "v", + "g", + "p", + "j", + "h", + "ä", + "b", + "õ", + "ü", + "f", + "c", + "ö", + "y", + ], + "Thai": [ + "า", + "น", + "ร", + "อ", + "ก", + "เ", + "ง", + "ม", + "ย", + "ล", + "ว", + "ด", + "ท", + "ส", + "ต", + "ะ", + "ป", + "บ", + "ค", + "ห", + "แ", + "จ", + "พ", + "ช", + "ข", + "ใ", + ], + "Greek": [ + "α", + "τ", + "ο", + "ι", + "ε", + "ν", + "ρ", + "σ", + "κ", + "η", + "π", + "ς", + "υ", + "μ", + "λ", + "ί", + "ό", + "ά", + "γ", + "έ", + "δ", + "ή", + "ω", + "χ", + "θ", + "ύ", + ], + "Tamil": [ + "க", + "த", + "ப", + "ட", + "ர", + "ம", + "ல", + "ன", + "வ", + "ற", + "ய", + "ள", + "ச", + "ந", + "இ", + "ண", + "அ", + "ஆ", + "ழ", + "ங", + "எ", + "உ", + "ஒ", + "ஸ", + ], + "Kazakh": [ + "а", + "ы", + "е", + "н", + "т", + "р", + "л", + "і", + "д", + "с", + "м", + "қ", + "к", + "о", + "б", + "и", + "у", + "ғ", + "ж", + "ң", + "з", + "ш", + "й", + "п", + "г", + "ө", + ], +} + +LANGUAGE_SUPPORTED_COUNT: int = len(FREQUENCIES) + +# Bit flags for unified character classification. +# A single unicodedata.name() call sets all relevant flags at once. +_LATIN: int = 1 +_ACCENTUATED: int = 1 << 1 +_CJK: int = 1 << 2 +_HANGUL: int = 1 << 3 +_KATAKANA: int = 1 << 4 +_HIRAGANA: int = 1 << 5 +_THAI: int = 1 << 6 +_ARABIC: int = 1 << 7 +_ARABIC_ISOLATED_FORM: int = 1 << 8 + +_ACCENT_KEYWORDS: tuple[str, ...] = ( + "WITH GRAVE", + "WITH ACUTE", + "WITH CEDILLA", + "WITH DIAERESIS", + "WITH CIRCUMFLEX", + "WITH TILDE", + "WITH MACRON", + "WITH RING ABOVE", +) + +# Pre-built lookup structures for FREQUENCIES (computed once at import time). +# character -> rank mapping per language (replaces list .index() calls). +_FREQUENCIES_RANK: dict[str, dict[str, int]] = { + lang: {char: rank for rank, char in enumerate(chars)} + for lang, chars in FREQUENCIES.items() +} + +# frozenset per language (avoids rebuilding set() per call). +_FREQUENCIES_SET: dict[str, frozenset[str]] = { + lang: frozenset(chars) for lang, chars in FREQUENCIES.items() +} diff --git a/intelliaide/vendor/charset_normalizer/legacy.py b/intelliaide/vendor/charset_normalizer/legacy.py new file mode 100644 index 0000000..293c1ef --- /dev/null +++ b/intelliaide/vendor/charset_normalizer/legacy.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any +from warnings import warn + +from .api import from_bytes +from .constant import CHARDET_CORRESPONDENCE, TOO_SMALL_SEQUENCE + +if TYPE_CHECKING: + from typing import TypedDict + + class ResultDict(TypedDict): + encoding: str | None + language: str + confidence: float | None + + +def detect( + byte_str: bytes, should_rename_legacy: bool = False, **kwargs: Any +) -> ResultDict: + """ + chardet legacy method + Detect the encoding of the given byte string. It should be mostly backward-compatible. + Encoding name will match Chardet own writing whenever possible. (Not on encoding name unsupported by it) + This function is deprecated and should be used to migrate your project easily, consult the documentation for + further information. Not planned for removal. + + :param byte_str: The byte sequence to examine. + :param should_rename_legacy: Should we rename legacy encodings + to their more modern equivalents? + """ + if len(kwargs): + warn( + f"charset-normalizer disregard arguments '{','.join(list(kwargs.keys()))}' in legacy function detect()" + ) + + if not isinstance(byte_str, (bytearray, bytes)): + raise TypeError( # pragma: nocover + f"Expected object of type bytes or bytearray, got: {type(byte_str)}" + ) + + if isinstance(byte_str, bytearray): + byte_str = bytes(byte_str) + + r = from_bytes(byte_str).best() + + encoding = r.encoding if r is not None else None + language = r.language if r is not None and r.language != "Unknown" else "" + confidence = 1.0 - r.chaos if r is not None else None + + # automatically lower confidence + # on small bytes samples. + # https://github.com/jawah/charset_normalizer/issues/391 + if ( + confidence is not None + and confidence >= 0.9 + and encoding + not in { + "utf_8", + "ascii", + } + and r.bom is False # type: ignore[union-attr] + and len(byte_str) < TOO_SMALL_SEQUENCE + ): + confidence -= 0.2 + + # Note: CharsetNormalizer does not return 'UTF-8-SIG' as the sig get stripped in the detection/normalization process + # but chardet does return 'utf-8-sig' and it is a valid codec name. + if r is not None and encoding == "utf_8" and r.bom: + encoding += "_sig" + + if should_rename_legacy is False and encoding in CHARDET_CORRESPONDENCE: + encoding = CHARDET_CORRESPONDENCE[encoding] + + return { + "encoding": encoding, + "language": language, + "confidence": confidence, + } diff --git a/intelliaide/vendor/charset_normalizer/md.cpython-312-x86_64-linux-gnu.so b/intelliaide/vendor/charset_normalizer/md.cpython-312-x86_64-linux-gnu.so new file mode 100755 index 0000000..87d31c7 Binary files /dev/null and b/intelliaide/vendor/charset_normalizer/md.cpython-312-x86_64-linux-gnu.so differ diff --git a/intelliaide/vendor/charset_normalizer/md.py b/intelliaide/vendor/charset_normalizer/md.py new file mode 100644 index 0000000..b41d9cf --- /dev/null +++ b/intelliaide/vendor/charset_normalizer/md.py @@ -0,0 +1,936 @@ +from __future__ import annotations + +import sys +from functools import lru_cache +from logging import getLogger + +if sys.version_info >= (3, 8): + from typing import final +else: + try: + from typing_extensions import final + except ImportError: + + def final(cls): # type: ignore[misc,no-untyped-def] + return cls + + +from .constant import ( + COMMON_CJK_CHARACTERS, + COMMON_SAFE_ASCII_CHARACTERS, + TRACE, + UNICODE_SECONDARY_RANGE_KEYWORD, + _ACCENTUATED, + _ARABIC, + _ARABIC_ISOLATED_FORM, + _CJK, + _HANGUL, + _HIRAGANA, + _KATAKANA, + _LATIN, + _THAI, +) +from .utils import ( + _character_flags, + is_emoticon, + is_punctuation, + is_separator, + is_symbol, + remove_accent, + unicode_range, +) + +# Combined bitmask for CJK/Hangul/Katakana/Hiragana/Thai glyph detection. +_GLYPH_MASK: int = _CJK | _HANGUL | _KATAKANA | _HIRAGANA | _THAI + + +@final +class CharInfo: + """Pre-computed character properties shared across all detectors. + + Instantiated once and reused via :meth:`update` on every character + in the hot loop so that redundant calls to str methods + (``isalpha``, ``isupper``, …) and cached utility functions + (``_character_flags``, ``is_punctuation``, …) are avoided when + several plugins need the same information. + """ + + __slots__ = ( + "character", + "printable", + "alpha", + "upper", + "lower", + "space", + "digit", + "is_ascii", + "case_variable", + "flags", + "accentuated", + "latin", + "is_cjk", + "is_arabic", + "is_glyph", + "punct", + "sym", + ) + + def __init__(self) -> None: + self.character: str = "" + self.printable: bool = False + self.alpha: bool = False + self.upper: bool = False + self.lower: bool = False + self.space: bool = False + self.digit: bool = False + self.is_ascii: bool = False + self.case_variable: bool = False + self.flags: int = 0 + self.accentuated: bool = False + self.latin: bool = False + self.is_cjk: bool = False + self.is_arabic: bool = False + self.is_glyph: bool = False + self.punct: bool = False + self.sym: bool = False + + def update(self, character: str) -> None: + """Update all properties for *character* (called once per character).""" + self.character = character + + # ASCII fast-path: for characters with ord < 128, we can skip + # _character_flags() entirely and derive most properties from ord. + o: int = ord(character) + if o < 128: + self.is_ascii = True + self.accentuated = False + self.is_cjk = False + self.is_arabic = False + self.is_glyph = False + # ASCII alpha: a-z (97-122) or A-Z (65-90) + if 65 <= o <= 90: + # Uppercase ASCII letter + self.alpha = True + self.upper = True + self.lower = False + self.space = False + self.digit = False + self.printable = True + self.case_variable = True + self.flags = _LATIN + self.latin = True + self.punct = False + self.sym = False + elif 97 <= o <= 122: + # Lowercase ASCII letter + self.alpha = True + self.upper = False + self.lower = True + self.space = False + self.digit = False + self.printable = True + self.case_variable = True + self.flags = _LATIN + self.latin = True + self.punct = False + self.sym = False + elif 48 <= o <= 57: + # ASCII digit 0-9 + self.alpha = False + self.upper = False + self.lower = False + self.space = False + self.digit = True + self.printable = True + self.case_variable = False + self.flags = 0 + self.latin = False + self.punct = False + self.sym = False + elif o == 32 or (9 <= o <= 13): + # Space, tab, newline, etc. + self.alpha = False + self.upper = False + self.lower = False + self.space = True + self.digit = False + self.printable = o == 32 + self.case_variable = False + self.flags = 0 + self.latin = False + self.punct = False + self.sym = False + else: + # Other ASCII (punctuation, symbols, control chars) + self.printable = character.isprintable() + self.alpha = False + self.upper = False + self.lower = False + self.space = False + self.digit = False + self.case_variable = False + self.flags = 0 + self.latin = False + self.punct = is_punctuation(character) if self.printable else False + self.sym = is_symbol(character) if self.printable else False + else: + # Non-ASCII path + self.is_ascii = False + self.printable = character.isprintable() + self.alpha = character.isalpha() + self.upper = character.isupper() + self.lower = character.islower() + self.space = character.isspace() + self.digit = character.isdigit() + self.case_variable = self.lower != self.upper + + # Flag-based classification (single unicodedata.name() call, lru-cached) + flags: int + if self.alpha: + flags = _character_flags(character) + else: + flags = 0 + self.flags = flags + self.accentuated = bool(flags & _ACCENTUATED) + self.latin = bool(flags & _LATIN) + self.is_cjk = bool(flags & _CJK) + self.is_arabic = bool(flags & _ARABIC) + self.is_glyph = bool(flags & _GLYPH_MASK) + + # Eagerly compute punct and sym (avoids property dispatch overhead + # on 300K+ accesses in the hot loop). + self.punct = is_punctuation(character) if self.printable else False + self.sym = is_symbol(character) if self.printable else False + + +class MessDetectorPlugin: + """ + Base abstract class used for mess detection plugins. + All detectors MUST extend and implement given methods. + """ + + __slots__ = () + + def feed_info(self, character: str, info: CharInfo) -> None: + """ + The main routine to be executed upon character. + Insert the logic in witch the text would be considered chaotic. + """ + raise NotImplementedError # Defensive: + + def reset(self) -> None: # Defensive: + """ + Permit to reset the plugin to the initial state. + """ + raise NotImplementedError + + @property + def ratio(self) -> float: + """ + Compute the chaos ratio based on what your feed() has seen. + Must NOT be lower than 0.; No restriction gt 0. + """ + raise NotImplementedError # Defensive: + + +@final +class TooManySymbolOrPunctuationPlugin(MessDetectorPlugin): + __slots__ = ( + "_punctuation_count", + "_symbol_count", + "_character_count", + "_last_printable_char", + "_frenzy_symbol_in_word", + ) + + def __init__(self) -> None: + self._punctuation_count: int = 0 + self._symbol_count: int = 0 + self._character_count: int = 0 + + self._last_printable_char: str | None = None + self._frenzy_symbol_in_word: bool = False + + def feed_info(self, character: str, info: CharInfo) -> None: + """Optimized feed using pre-computed character info.""" + self._character_count += 1 + + if ( + character != self._last_printable_char + and character not in COMMON_SAFE_ASCII_CHARACTERS + ): + if info.punct: + self._punctuation_count += 1 + elif not info.digit and info.sym and not is_emoticon(character): + self._symbol_count += 2 + + self._last_printable_char = character + + def reset(self) -> None: # Abstract + self._punctuation_count = 0 + self._character_count = 0 + self._symbol_count = 0 + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + ratio_of_punctuation: float = ( + self._punctuation_count + self._symbol_count + ) / self._character_count + + return ratio_of_punctuation if ratio_of_punctuation >= 0.3 else 0.0 + + +@final +class TooManyAccentuatedPlugin(MessDetectorPlugin): + __slots__ = ("_character_count", "_accentuated_count") + + def __init__(self) -> None: + self._character_count: int = 0 + self._accentuated_count: int = 0 + + def feed_info(self, character: str, info: CharInfo) -> None: + """Optimized feed using pre-computed character info.""" + self._character_count += 1 + + if info.accentuated: + self._accentuated_count += 1 + + def reset(self) -> None: # Abstract + self._character_count = 0 + self._accentuated_count = 0 + + @property + def ratio(self) -> float: + if self._character_count < 8: + return 0.0 + + ratio_of_accentuation: float = self._accentuated_count / self._character_count + return ratio_of_accentuation if ratio_of_accentuation >= 0.35 else 0.0 + + +@final +class UnprintablePlugin(MessDetectorPlugin): + __slots__ = ("_unprintable_count", "_character_count") + + def __init__(self) -> None: + self._unprintable_count: int = 0 + self._character_count: int = 0 + + def feed_info(self, character: str, info: CharInfo) -> None: + """Optimized feed using pre-computed character info.""" + if ( + not info.space + and not info.printable + and character != "\x1a" + and character != "\ufeff" + ): + self._unprintable_count += 1 + self._character_count += 1 + + def reset(self) -> None: # Abstract + self._unprintable_count = 0 + + @property + def ratio(self) -> float: + if self._character_count == 0: # Defensive: + return 0.0 + + return (self._unprintable_count * 8) / self._character_count + + +@final +class SuspiciousDuplicateAccentPlugin(MessDetectorPlugin): + __slots__ = ( + "_successive_count", + "_character_count", + "_last_latin_character", + "_last_was_accentuated", + ) + + def __init__(self) -> None: + self._successive_count: int = 0 + self._character_count: int = 0 + + self._last_latin_character: str | None = None + self._last_was_accentuated: bool = False + + def feed_info(self, character: str, info: CharInfo) -> None: + """Optimized feed using pre-computed character info.""" + self._character_count += 1 + if ( + self._last_latin_character is not None + and info.accentuated + and self._last_was_accentuated + ): + if info.upper and self._last_latin_character.isupper(): + self._successive_count += 1 + if remove_accent(character) == remove_accent(self._last_latin_character): + self._successive_count += 1 + self._last_latin_character = character + self._last_was_accentuated = info.accentuated + + def reset(self) -> None: # Abstract + self._successive_count = 0 + self._character_count = 0 + self._last_latin_character = None + self._last_was_accentuated = False + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + return (self._successive_count * 2) / self._character_count + + +@final +class SuspiciousRange(MessDetectorPlugin): + __slots__ = ( + "_suspicious_successive_range_count", + "_character_count", + "_last_printable_seen", + "_last_printable_range", + ) + + def __init__(self) -> None: + self._suspicious_successive_range_count: int = 0 + self._character_count: int = 0 + self._last_printable_seen: str | None = None + self._last_printable_range: str | None = None + + def feed_info(self, character: str, info: CharInfo) -> None: + """Optimized feed using pre-computed character info.""" + self._character_count += 1 + + if info.space or info.punct or character in COMMON_SAFE_ASCII_CHARACTERS: + self._last_printable_seen = None + self._last_printable_range = None + return + + if self._last_printable_seen is None: + self._last_printable_seen = character + self._last_printable_range = unicode_range(character) + return + + unicode_range_a: str | None = self._last_printable_range + unicode_range_b: str | None = unicode_range(character) + + if is_suspiciously_successive_range(unicode_range_a, unicode_range_b): + self._suspicious_successive_range_count += 1 + + self._last_printable_seen = character + self._last_printable_range = unicode_range_b + + def reset(self) -> None: # Abstract + self._character_count = 0 + self._suspicious_successive_range_count = 0 + self._last_printable_seen = None + self._last_printable_range = None + + @property + def ratio(self) -> float: + if self._character_count <= 13: + return 0.0 + + ratio_of_suspicious_range_usage: float = ( + self._suspicious_successive_range_count * 2 + ) / self._character_count + + return ratio_of_suspicious_range_usage + + +@final +class SuperWeirdWordPlugin(MessDetectorPlugin): + __slots__ = ( + "_word_count", + "_bad_word_count", + "_foreign_long_count", + "_is_current_word_bad", + "_foreign_long_watch", + "_character_count", + "_bad_character_count", + "_buffer_length", + "_buffer_last_char", + "_buffer_last_char_accentuated", + "_buffer_accent_count", + "_buffer_glyph_count", + "_buffer_upper_count", + ) + + def __init__(self) -> None: + self._word_count: int = 0 + self._bad_word_count: int = 0 + self._foreign_long_count: int = 0 + + self._is_current_word_bad: bool = False + self._foreign_long_watch: bool = False + + self._character_count: int = 0 + self._bad_character_count: int = 0 + + self._buffer_length: int = 0 + self._buffer_last_char: str | None = None + self._buffer_last_char_accentuated: bool = False + self._buffer_accent_count: int = 0 + self._buffer_glyph_count: int = 0 + self._buffer_upper_count: int = 0 + + def feed_info(self, character: str, info: CharInfo) -> None: + """Optimized feed using pre-computed character info.""" + if info.alpha: + self._buffer_length += 1 + self._buffer_last_char = character + + if info.upper: + self._buffer_upper_count += 1 + + self._buffer_last_char_accentuated = info.accentuated + + if info.accentuated: + self._buffer_accent_count += 1 + if ( + not self._foreign_long_watch + and (not info.latin or info.accentuated) + and not info.is_glyph + ): + self._foreign_long_watch = True + if info.is_glyph: + self._buffer_glyph_count += 1 + return + if not self._buffer_length: + return + if info.space or info.punct or is_separator(character): + self._word_count += 1 + buffer_length: int = self._buffer_length + + self._character_count += buffer_length + + if buffer_length >= 4: + if self._buffer_accent_count / buffer_length >= 0.5: + self._is_current_word_bad = True + elif ( + self._buffer_last_char_accentuated + and self._buffer_last_char.isupper() # type: ignore[union-attr] + and self._buffer_upper_count != buffer_length + ): + self._foreign_long_count += 1 + self._is_current_word_bad = True + elif self._buffer_glyph_count == 1: + self._is_current_word_bad = True + self._foreign_long_count += 1 + if buffer_length >= 24 and self._foreign_long_watch: + probable_camel_cased: bool = ( + self._buffer_upper_count > 0 + and self._buffer_upper_count / buffer_length <= 0.3 + ) + + if not probable_camel_cased: + self._foreign_long_count += 1 + self._is_current_word_bad = True + + if self._is_current_word_bad: + self._bad_word_count += 1 + self._bad_character_count += buffer_length + self._is_current_word_bad = False + + self._foreign_long_watch = False + self._buffer_length = 0 + self._buffer_last_char = None + self._buffer_last_char_accentuated = False + self._buffer_accent_count = 0 + self._buffer_glyph_count = 0 + self._buffer_upper_count = 0 + elif ( + character not in {"<", ">", "-", "=", "~", "|", "_"} + and not info.digit + and info.sym + ): + self._is_current_word_bad = True + self._buffer_length += 1 + self._buffer_last_char = character + self._buffer_last_char_accentuated = False + + def reset(self) -> None: # Abstract + self._buffer_length = 0 + self._buffer_last_char = None + self._buffer_last_char_accentuated = False + self._is_current_word_bad = False + self._foreign_long_watch = False + self._bad_word_count = 0 + self._word_count = 0 + self._character_count = 0 + self._bad_character_count = 0 + self._foreign_long_count = 0 + self._buffer_accent_count = 0 + self._buffer_glyph_count = 0 + self._buffer_upper_count = 0 + + @property + def ratio(self) -> float: + if self._word_count <= 10 and self._foreign_long_count == 0: + return 0.0 + + return self._bad_character_count / self._character_count + + +@final +class CjkUncommonPlugin(MessDetectorPlugin): + """ + Detect messy CJK text that probably means nothing. + """ + + __slots__ = ("_character_count", "_uncommon_count") + + def __init__(self) -> None: + self._character_count: int = 0 + self._uncommon_count: int = 0 + + def feed_info(self, character: str, info: CharInfo) -> None: + """Optimized feed using pre-computed character info.""" + self._character_count += 1 + + if character not in COMMON_CJK_CHARACTERS: + self._uncommon_count += 1 + + def reset(self) -> None: # Abstract + self._character_count = 0 + self._uncommon_count = 0 + + @property + def ratio(self) -> float: + if self._character_count < 8: + return 0.0 + + uncommon_form_usage: float = self._uncommon_count / self._character_count + + # we can be pretty sure it's garbage when uncommon characters are widely + # used. otherwise it could just be traditional chinese for example. + return uncommon_form_usage / 10 if uncommon_form_usage > 0.5 else 0.0 + + +@final +class ArchaicUpperLowerPlugin(MessDetectorPlugin): + __slots__ = ( + "_buf", + "_character_count_since_last_sep", + "_successive_upper_lower_count", + "_successive_upper_lower_count_final", + "_character_count", + "_last_alpha_seen", + "_last_alpha_seen_upper", + "_last_alpha_seen_lower", + "_current_ascii_only", + ) + + def __init__(self) -> None: + self._buf: bool = False + + self._character_count_since_last_sep: int = 0 + + self._successive_upper_lower_count: int = 0 + self._successive_upper_lower_count_final: int = 0 + + self._character_count: int = 0 + + self._last_alpha_seen: str | None = None + self._last_alpha_seen_upper: bool = False + self._last_alpha_seen_lower: bool = False + self._current_ascii_only: bool = True + + def feed_info(self, character: str, info: CharInfo) -> None: + """Optimized feed using pre-computed character info.""" + is_concerned: bool = info.alpha and info.case_variable + chunk_sep: bool = not is_concerned + + if chunk_sep and self._character_count_since_last_sep > 0: + if ( + self._character_count_since_last_sep <= 64 + and not info.digit + and not self._current_ascii_only + ): + self._successive_upper_lower_count_final += ( + self._successive_upper_lower_count + ) + + self._successive_upper_lower_count = 0 + self._character_count_since_last_sep = 0 + self._last_alpha_seen = None + self._buf = False + self._character_count += 1 + self._current_ascii_only = True + + return + + if self._current_ascii_only and not info.is_ascii: + self._current_ascii_only = False + + if self._last_alpha_seen is not None: + if (info.upper and self._last_alpha_seen_lower) or ( + info.lower and self._last_alpha_seen_upper + ): + if self._buf: + self._successive_upper_lower_count += 2 + self._buf = False + else: + self._buf = True + else: + self._buf = False + + self._character_count += 1 + self._character_count_since_last_sep += 1 + self._last_alpha_seen = character + self._last_alpha_seen_upper = info.upper + self._last_alpha_seen_lower = info.lower + + def reset(self) -> None: # Abstract + self._character_count = 0 + self._character_count_since_last_sep = 0 + self._successive_upper_lower_count = 0 + self._successive_upper_lower_count_final = 0 + self._last_alpha_seen = None + self._last_alpha_seen_upper = False + self._last_alpha_seen_lower = False + self._buf = False + self._current_ascii_only = True + + @property + def ratio(self) -> float: + if self._character_count == 0: # Defensive: + return 0.0 + + return self._successive_upper_lower_count_final / self._character_count + + +@final +class ArabicIsolatedFormPlugin(MessDetectorPlugin): + __slots__ = ("_character_count", "_isolated_form_count") + + def __init__(self) -> None: + self._character_count: int = 0 + self._isolated_form_count: int = 0 + + def reset(self) -> None: # Abstract + self._character_count = 0 + self._isolated_form_count = 0 + + def feed_info(self, character: str, info: CharInfo) -> None: + """Optimized feed using pre-computed character info.""" + self._character_count += 1 + + if info.flags & _ARABIC_ISOLATED_FORM: + self._isolated_form_count += 1 + + @property + def ratio(self) -> float: + if self._character_count < 8: + return 0.0 + + isolated_form_usage: float = self._isolated_form_count / self._character_count + + return isolated_form_usage + + +@lru_cache(maxsize=1024) +def is_suspiciously_successive_range( + unicode_range_a: str | None, unicode_range_b: str | None +) -> bool: + """ + Determine if two Unicode range seen next to each other can be considered as suspicious. + """ + if unicode_range_a is None or unicode_range_b is None: + return True + + if unicode_range_a == unicode_range_b: + return False + + if "Latin" in unicode_range_a and "Latin" in unicode_range_b: + return False + + if "Emoticons" in unicode_range_a or "Emoticons" in unicode_range_b: + return False + + # Latin characters can be accompanied with a combining diacritical mark + # eg. Vietnamese. + if ("Latin" in unicode_range_a or "Latin" in unicode_range_b) and ( + "Combining" in unicode_range_a or "Combining" in unicode_range_b + ): + return False + + keywords_range_a, keywords_range_b = ( + unicode_range_a.split(" "), + unicode_range_b.split(" "), + ) + + for el in keywords_range_a: + if el in UNICODE_SECONDARY_RANGE_KEYWORD: + continue + if el in keywords_range_b: + return False + + # Japanese Exception + range_a_jp_chars, range_b_jp_chars = ( + unicode_range_a + in ( + "Hiragana", + "Katakana", + ), + unicode_range_b in ("Hiragana", "Katakana"), + ) + if (range_a_jp_chars or range_b_jp_chars) and ( + "CJK" in unicode_range_a or "CJK" in unicode_range_b + ): + return False + if range_a_jp_chars and range_b_jp_chars: + return False + + if "Hangul" in unicode_range_a or "Hangul" in unicode_range_b: + if "CJK" in unicode_range_a or "CJK" in unicode_range_b: + return False + if unicode_range_a == "Basic Latin" or unicode_range_b == "Basic Latin": + return False + + # Chinese/Japanese use dedicated range for punctuation and/or separators. + if ("CJK" in unicode_range_a or "CJK" in unicode_range_b) or ( + unicode_range_a in ["Katakana", "Hiragana"] + and unicode_range_b in ["Katakana", "Hiragana"] + ): + if "Punctuation" in unicode_range_a or "Punctuation" in unicode_range_b: + return False + if "Forms" in unicode_range_a or "Forms" in unicode_range_b: + return False + if unicode_range_a == "Basic Latin" or unicode_range_b == "Basic Latin": + return False + + return True + + +@lru_cache(maxsize=2048) +def mess_ratio( + decoded_sequence: str, maximum_threshold: float = 0.2, debug: bool = False +) -> float: + """ + Compute a mess ratio given a decoded bytes sequence. The maximum threshold does stop the computation earlier. + """ + + seq_len: int = len(decoded_sequence) + + if seq_len < 511: + step: int = 32 + elif seq_len < 1024: + step = 64 + else: + step = 128 + + # Create each detector as a named local variable (unrolled from the generic loop). + # This eliminates per-character iteration over the detector list and + # per-character eligible() virtual dispatch, while keeping every plugin class + # intact and fully readable. + d_sp: TooManySymbolOrPunctuationPlugin = TooManySymbolOrPunctuationPlugin() + d_ta: TooManyAccentuatedPlugin = TooManyAccentuatedPlugin() + d_up: UnprintablePlugin = UnprintablePlugin() + d_sda: SuspiciousDuplicateAccentPlugin = SuspiciousDuplicateAccentPlugin() + d_sr: SuspiciousRange = SuspiciousRange() + d_sw: SuperWeirdWordPlugin = SuperWeirdWordPlugin() + d_cu: CjkUncommonPlugin = CjkUncommonPlugin() + d_au: ArchaicUpperLowerPlugin = ArchaicUpperLowerPlugin() + d_ai: ArabicIsolatedFormPlugin = ArabicIsolatedFormPlugin() + + # Local references for feed_info methods called in the hot loop. + d_sp_feed = d_sp.feed_info + d_ta_feed = d_ta.feed_info + d_up_feed = d_up.feed_info + d_sda_feed = d_sda.feed_info + d_sr_feed = d_sr.feed_info + d_sw_feed = d_sw.feed_info + d_cu_feed = d_cu.feed_info + d_au_feed = d_au.feed_info + d_ai_feed = d_ai.feed_info + + # Single reusable CharInfo object (avoids per-character allocation). + info: CharInfo = CharInfo() + info_update = info.update + + mean_mess_ratio: float + + for block_start in range(0, seq_len, step): + for character in decoded_sequence[block_start : block_start + step]: + # Pre-compute all character properties once (shared across all plugins). + info_update(character) + + # Detectors with eligible() == always True + d_up_feed(character, info) + d_sw_feed(character, info) + d_au_feed(character, info) + + # Detectors with eligible() == isprintable + if info.printable: + d_sp_feed(character, info) + d_sr_feed(character, info) + + # Detectors with eligible() == isalpha + if info.alpha: + d_ta_feed(character, info) + # SuspiciousDuplicateAccent: isalpha() and is_latin() + if info.latin: + d_sda_feed(character, info) + # CjkUncommon: is_cjk() + if info.is_cjk: + d_cu_feed(character, info) + # ArabicIsolatedForm: is_arabic() + if info.is_arabic: + d_ai_feed(character, info) + + mean_mess_ratio = ( + d_sp.ratio + + d_ta.ratio + + d_up.ratio + + d_sda.ratio + + d_sr.ratio + + d_sw.ratio + + d_cu.ratio + + d_au.ratio + + d_ai.ratio + ) + + if mean_mess_ratio >= maximum_threshold: + break + else: + # Flush last word buffer in SuperWeirdWordPlugin via trailing newline. + info_update("\n") + d_sw_feed("\n", info) + d_au_feed("\n", info) + d_up_feed("\n", info) + + mean_mess_ratio = ( + d_sp.ratio + + d_ta.ratio + + d_up.ratio + + d_sda.ratio + + d_sr.ratio + + d_sw.ratio + + d_cu.ratio + + d_au.ratio + + d_ai.ratio + ) + + if debug: # Defensive: + logger = getLogger("charset_normalizer") + + logger.log( + TRACE, + "Mess-detector extended-analysis start. " + f"intermediary_mean_mess_ratio_calc={step} mean_mess_ratio={mean_mess_ratio} " + f"maximum_threshold={maximum_threshold}", + ) + + if seq_len > 16: + logger.log(TRACE, f"Starting with: {decoded_sequence[:16]}") + logger.log(TRACE, f"Ending with: {decoded_sequence[-16::]}") + + for dt in [d_sp, d_ta, d_up, d_sda, d_sr, d_sw, d_cu, d_au, d_ai]: + logger.log(TRACE, f"{dt.__class__}: {dt.ratio}") + + return round(mean_mess_ratio, 3) diff --git a/intelliaide/vendor/charset_normalizer/models.py b/intelliaide/vendor/charset_normalizer/models.py new file mode 100644 index 0000000..382de15 --- /dev/null +++ b/intelliaide/vendor/charset_normalizer/models.py @@ -0,0 +1,369 @@ +from __future__ import annotations + +from encodings.aliases import aliases +from json import dumps +from re import sub +from typing import Any, Iterator, List, Tuple + +from .constant import RE_POSSIBLE_ENCODING_INDICATION, TOO_BIG_SEQUENCE +from .utils import iana_name, is_multi_byte_encoding, unicode_range + + +class CharsetMatch: + def __init__( + self, + payload: bytes | bytearray, + guessed_encoding: str, + mean_mess_ratio: float, + has_sig_or_bom: bool, + languages: CoherenceMatches, + decoded_payload: str | None = None, + preemptive_declaration: str | None = None, + ): + self._payload: bytes | bytearray = payload + + self._encoding: str = guessed_encoding + self._mean_mess_ratio: float = mean_mess_ratio + self._languages: CoherenceMatches = languages + self._has_sig_or_bom: bool = has_sig_or_bom + self._unicode_ranges: list[str] | None = None + + self._leaves: list[CharsetMatch] = [] + self._mean_coherence_ratio: float = 0.0 + + self._output_payload: bytes | None = None + self._output_encoding: str | None = None + + self._string: str | None = decoded_payload + + self._preemptive_declaration: str | None = preemptive_declaration + + def __eq__(self, other: object) -> bool: + if not isinstance(other, CharsetMatch): + if isinstance(other, str): + return iana_name(other) == self.encoding + return False + return self.encoding == other.encoding and self.fingerprint == other.fingerprint + + def __lt__(self, other: object) -> bool: + """ + Implemented to make sorted available upon CharsetMatches items. + """ + if not isinstance(other, CharsetMatch): + raise ValueError + + chaos_difference: float = abs(self.chaos - other.chaos) + coherence_difference: float = abs(self.coherence - other.coherence) + + # Below 0.5% difference --> Use Coherence + if chaos_difference < 0.005 and coherence_difference > 0.02: + return self.coherence > other.coherence + elif chaos_difference < 0.005 and coherence_difference <= 0.02: + # When having a difficult decision, use the result that decoded as many multi-byte as possible. + # preserve RAM usage! + if len(self._payload) >= TOO_BIG_SEQUENCE: + return self.chaos < other.chaos + return self.multi_byte_usage > other.multi_byte_usage + + return self.chaos < other.chaos + + @property + def multi_byte_usage(self) -> float: + return 1.0 - (len(str(self)) / len(self.raw)) + + def __str__(self) -> str: + # Lazy Str Loading + if self._string is None: + self._string = str(self._payload, self._encoding, "strict") + # UTF-7 BOM is encoded in modified Base64 whose byte boundary + # can overlap with the next character, so raw-byte stripping + # is unreliable. Strip the decoded BOM character instead. + if ( + self._has_sig_or_bom + and self._encoding == "utf_7" + and self._string + and self._string[0] == "\ufeff" + ): + self._string = self._string[1:] + return self._string + + def __repr__(self) -> str: + return f"" + + def add_submatch(self, other: CharsetMatch) -> None: + if not isinstance(other, CharsetMatch) or other == self: + raise ValueError( + "Unable to add instance <{}> as a submatch of a CharsetMatch".format( + other.__class__ + ) + ) + + other._string = None # Unload RAM usage; dirty trick. + self._leaves.append(other) + + @property + def encoding(self) -> str: + return self._encoding + + @property + def encoding_aliases(self) -> list[str]: + """ + Encoding name are known by many name, using this could help when searching for IBM855 when it's listed as CP855. + """ + also_known_as: list[str] = [] + for u, p in aliases.items(): + if self.encoding == u: + also_known_as.append(p) + elif self.encoding == p: + also_known_as.append(u) + return also_known_as + + @property + def bom(self) -> bool: + return self._has_sig_or_bom + + @property + def byte_order_mark(self) -> bool: + return self._has_sig_or_bom + + @property + def languages(self) -> list[str]: + """ + Return the complete list of possible languages found in decoded sequence. + Usually not really useful. Returned list may be empty even if 'language' property return something != 'Unknown'. + """ + return [e[0] for e in self._languages] + + @property + def language(self) -> str: + """ + Most probable language found in decoded sequence. If none were detected or inferred, the property will return + "Unknown". + """ + if not self._languages: + # Trying to infer the language based on the given encoding + # Its either English or we should not pronounce ourselves in certain cases. + if "ascii" in self.could_be_from_charset: + return "English" + + # doing it there to avoid circular import + from charset_normalizer.cd import encoding_languages, mb_encoding_languages + + languages = ( + mb_encoding_languages(self.encoding) + if is_multi_byte_encoding(self.encoding) + else encoding_languages(self.encoding) + ) + + if len(languages) == 0 or "Latin Based" in languages: + return "Unknown" + + return languages[0] + + return self._languages[0][0] + + @property + def chaos(self) -> float: + return self._mean_mess_ratio + + @property + def coherence(self) -> float: + if not self._languages: + return 0.0 + return self._languages[0][1] + + @property + def percent_chaos(self) -> float: + return round(self.chaos * 100, ndigits=3) + + @property + def percent_coherence(self) -> float: + return round(self.coherence * 100, ndigits=3) + + @property + def raw(self) -> bytes | bytearray: + """ + Original untouched bytes. + """ + return self._payload + + @property + def submatch(self) -> list[CharsetMatch]: + return self._leaves + + @property + def has_submatch(self) -> bool: + return len(self._leaves) > 0 + + @property + def alphabets(self) -> list[str]: + if self._unicode_ranges is not None: + return self._unicode_ranges + # list detected ranges + detected_ranges: list[str | None] = [unicode_range(char) for char in str(self)] + # filter and sort + self._unicode_ranges = sorted(list({r for r in detected_ranges if r})) + return self._unicode_ranges + + @property + def could_be_from_charset(self) -> list[str]: + """ + The complete list of encoding that output the exact SAME str result and therefore could be the originating + encoding. + This list does include the encoding available in property 'encoding'. + """ + return [self._encoding] + [m.encoding for m in self._leaves] + + def output(self, encoding: str = "utf_8") -> bytes: + """ + Method to get re-encoded bytes payload using given target encoding. Default to UTF-8. + Any errors will be simply ignored by the encoder NOT replaced. + """ + if self._output_encoding is None or self._output_encoding != encoding: + self._output_encoding = encoding + decoded_string = str(self) + if ( + self._preemptive_declaration is not None + and self._preemptive_declaration.lower() + not in ["utf-8", "utf8", "utf_8"] + ): + patched_header = sub( + RE_POSSIBLE_ENCODING_INDICATION, + lambda m: m.string[m.span()[0] : m.span()[1]].replace( + m.groups()[0], + iana_name(self._output_encoding).replace("_", "-"), # type: ignore[arg-type] + ), + decoded_string[:8192], + count=1, + ) + + decoded_string = patched_header + decoded_string[8192:] + + self._output_payload = decoded_string.encode(encoding, "replace") + + return self._output_payload # type: ignore + + @property + def fingerprint(self) -> int: + """ + Retrieve a hash fingerprint of the decoded payload, used for deduplication. + """ + return hash(str(self)) + + +class CharsetMatches: + """ + Container with every CharsetMatch items ordered by default from most probable to the less one. + Act like a list(iterable) but does not implements all related methods. + """ + + def __init__(self, results: list[CharsetMatch] | None = None): + self._results: list[CharsetMatch] = sorted(results) if results else [] + + def __iter__(self) -> Iterator[CharsetMatch]: + yield from self._results + + def __getitem__(self, item: int | str) -> CharsetMatch: + """ + Retrieve a single item either by its position or encoding name (alias may be used here). + Raise KeyError upon invalid index or encoding not present in results. + """ + if isinstance(item, int): + return self._results[item] + if isinstance(item, str): + item = iana_name(item, False) + for result in self._results: + if item in result.could_be_from_charset: + return result + raise KeyError + + def __len__(self) -> int: + return len(self._results) + + def __bool__(self) -> bool: + return len(self._results) > 0 + + def append(self, item: CharsetMatch) -> None: + """ + Insert a single match. Will be inserted accordingly to preserve sort. + Can be inserted as a submatch. + """ + if not isinstance(item, CharsetMatch): + raise ValueError( + "Cannot append instance '{}' to CharsetMatches".format( + str(item.__class__) + ) + ) + # We should disable the submatch factoring when the input file is too heavy (conserve RAM usage) + if len(item.raw) < TOO_BIG_SEQUENCE: + for match in self._results: + if match.fingerprint == item.fingerprint and match.chaos == item.chaos: + match.add_submatch(item) + return + self._results.append(item) + self._results = sorted(self._results) + + def best(self) -> CharsetMatch | None: + """ + Simply return the first match. Strict equivalent to matches[0]. + """ + if not self._results: + return None + return self._results[0] + + def first(self) -> CharsetMatch | None: + """ + Redundant method, call the method best(). Kept for BC reasons. + """ + return self.best() + + +CoherenceMatch = Tuple[str, float] +CoherenceMatches = List[CoherenceMatch] + + +class CliDetectionResult: + def __init__( + self, + path: str, + encoding: str | None, + encoding_aliases: list[str], + alternative_encodings: list[str], + language: str, + alphabets: list[str], + has_sig_or_bom: bool, + chaos: float, + coherence: float, + unicode_path: str | None, + is_preferred: bool, + ): + self.path: str = path + self.unicode_path: str | None = unicode_path + self.encoding: str | None = encoding + self.encoding_aliases: list[str] = encoding_aliases + self.alternative_encodings: list[str] = alternative_encodings + self.language: str = language + self.alphabets: list[str] = alphabets + self.has_sig_or_bom: bool = has_sig_or_bom + self.chaos: float = chaos + self.coherence: float = coherence + self.is_preferred: bool = is_preferred + + @property + def __dict__(self) -> dict[str, Any]: # type: ignore + return { + "path": self.path, + "encoding": self.encoding, + "encoding_aliases": self.encoding_aliases, + "alternative_encodings": self.alternative_encodings, + "language": self.language, + "alphabets": self.alphabets, + "has_sig_or_bom": self.has_sig_or_bom, + "chaos": self.chaos, + "coherence": self.coherence, + "unicode_path": self.unicode_path, + "is_preferred": self.is_preferred, + } + + def to_json(self) -> str: + return dumps(self.__dict__, ensure_ascii=True, indent=4) diff --git a/intelliaide/vendor/charset_normalizer/py.typed b/intelliaide/vendor/charset_normalizer/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/intelliaide/vendor/charset_normalizer/utils.py b/intelliaide/vendor/charset_normalizer/utils.py new file mode 100644 index 0000000..0f529b5 --- /dev/null +++ b/intelliaide/vendor/charset_normalizer/utils.py @@ -0,0 +1,422 @@ +from __future__ import annotations + +import importlib +import logging +import unicodedata +from bisect import bisect_right +from codecs import IncrementalDecoder +from encodings.aliases import aliases +from functools import lru_cache +from re import findall +from typing import Generator + +from _multibytecodec import ( # type: ignore[import-not-found,import] + MultibyteIncrementalDecoder, +) + +from .constant import ( + ENCODING_MARKS, + IANA_SUPPORTED_SIMILAR, + RE_POSSIBLE_ENCODING_INDICATION, + UNICODE_RANGES_COMBINED, + UNICODE_SECONDARY_RANGE_KEYWORD, + UTF8_MAXIMAL_ALLOCATION, + COMMON_CJK_CHARACTERS, + _LATIN, + _CJK, + _HANGUL, + _KATAKANA, + _HIRAGANA, + _THAI, + _ARABIC, + _ARABIC_ISOLATED_FORM, + _ACCENT_KEYWORDS, + _ACCENTUATED, +) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def _character_flags(character: str) -> int: + """Compute all name-based classification flags with a single unicodedata.name() call.""" + try: + desc: str = unicodedata.name(character) + except ValueError: + return 0 + + flags: int = 0 + + if "LATIN" in desc: + flags |= _LATIN + if "CJK" in desc: + flags |= _CJK + if "HANGUL" in desc: + flags |= _HANGUL + if "KATAKANA" in desc: + flags |= _KATAKANA + if "HIRAGANA" in desc: + flags |= _HIRAGANA + if "THAI" in desc: + flags |= _THAI + if "ARABIC" in desc: + flags |= _ARABIC + if "ISOLATED FORM" in desc: + flags |= _ARABIC_ISOLATED_FORM + + for kw in _ACCENT_KEYWORDS: + if kw in desc: + flags |= _ACCENTUATED + break + + return flags + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_accentuated(character: str) -> bool: + return bool(_character_flags(character) & _ACCENTUATED) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def remove_accent(character: str) -> str: + decomposed: str = unicodedata.decomposition(character) + if not decomposed: + return character + + codes: list[str] = decomposed.split(" ") + + return chr(int(codes[0], 16)) + + +# Pre-built sorted lookup table for O(log n) binary search in unicode_range(). +# Each entry is (range_start, range_end_exclusive, range_name). +_UNICODE_RANGES_SORTED: list[tuple[int, int, str]] = sorted( + (ord_range.start, ord_range.stop, name) + for name, ord_range in UNICODE_RANGES_COMBINED.items() +) +_UNICODE_RANGE_STARTS: list[int] = [e[0] for e in _UNICODE_RANGES_SORTED] + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def unicode_range(character: str) -> str | None: + """ + Retrieve the Unicode range official name from a single character. + """ + character_ord: int = ord(character) + + # Binary search: find the rightmost range whose start <= character_ord + idx = bisect_right(_UNICODE_RANGE_STARTS, character_ord) - 1 + if idx >= 0: + start, stop, name = _UNICODE_RANGES_SORTED[idx] + if character_ord < stop: + return name + + return None + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_latin(character: str) -> bool: + return bool(_character_flags(character) & _LATIN) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_punctuation(character: str) -> bool: + character_category: str = unicodedata.category(character) + + if "P" in character_category: + return True + + character_range: str | None = unicode_range(character) + + if character_range is None: + return False + + return "Punctuation" in character_range + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_symbol(character: str) -> bool: + character_category: str = unicodedata.category(character) + + if "S" in character_category or "N" in character_category: + return True + + character_range: str | None = unicode_range(character) + + if character_range is None: + return False + + return "Forms" in character_range and character_category != "Lo" + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_emoticon(character: str) -> bool: + character_range: str | None = unicode_range(character) + + if character_range is None: + return False + + return "Emoticons" in character_range or "Pictographs" in character_range + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_separator(character: str) -> bool: + if character.isspace() or character in {"|", "+", "<", ">"}: + return True + + character_category: str = unicodedata.category(character) + + return "Z" in character_category or character_category in {"Po", "Pd", "Pc"} + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_case_variable(character: str) -> bool: + return character.islower() != character.isupper() + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_cjk(character: str) -> bool: + return bool(_character_flags(character) & _CJK) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_hiragana(character: str) -> bool: + return bool(_character_flags(character) & _HIRAGANA) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_katakana(character: str) -> bool: + return bool(_character_flags(character) & _KATAKANA) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_hangul(character: str) -> bool: + return bool(_character_flags(character) & _HANGUL) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_thai(character: str) -> bool: + return bool(_character_flags(character) & _THAI) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_arabic(character: str) -> bool: + return bool(_character_flags(character) & _ARABIC) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_arabic_isolated_form(character: str) -> bool: + return bool(_character_flags(character) & _ARABIC_ISOLATED_FORM) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_cjk_uncommon(character: str) -> bool: + return character not in COMMON_CJK_CHARACTERS + + +@lru_cache(maxsize=len(UNICODE_RANGES_COMBINED)) +def is_unicode_range_secondary(range_name: str) -> bool: + return any(keyword in range_name for keyword in UNICODE_SECONDARY_RANGE_KEYWORD) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_unprintable(character: str) -> bool: + return ( + character.isspace() is False # includes \n \t \r \v + and character.isprintable() is False + and character != "\x1a" # Why? Its the ASCII substitute character. + and character != "\ufeff" # bug discovered in Python, + # Zero Width No-Break Space located in Arabic Presentation Forms-B, Unicode 1.1 not acknowledged as space. + ) + + +def any_specified_encoding( + sequence: bytes | bytearray, search_zone: int = 8192 +) -> str | None: + """ + Extract using ASCII-only decoder any specified encoding in the first n-bytes. + """ + if not isinstance(sequence, (bytes, bytearray)): + raise TypeError + + seq_len: int = len(sequence) + + results: list[str] = findall( + RE_POSSIBLE_ENCODING_INDICATION, + sequence[: min(seq_len, search_zone)].decode("ascii", errors="ignore"), + ) + + if len(results) == 0: + return None + + for specified_encoding in results: + specified_encoding = specified_encoding.lower().replace("-", "_") + + encoding_alias: str + encoding_iana: str + + for encoding_alias, encoding_iana in aliases.items(): + if encoding_alias == specified_encoding: + return encoding_iana + if encoding_iana == specified_encoding: + return encoding_iana + + return None + + +@lru_cache(maxsize=128) +def is_multi_byte_encoding(name: str) -> bool: + """ + Verify is a specific encoding is a multi byte one based on it IANA name + """ + return name in { + "utf_8", + "utf_8_sig", + "utf_16", + "utf_16_be", + "utf_16_le", + "utf_32", + "utf_32_le", + "utf_32_be", + "utf_7", + } or issubclass( + importlib.import_module(f"encodings.{name}").IncrementalDecoder, + MultibyteIncrementalDecoder, + ) + + +def identify_sig_or_bom(sequence: bytes | bytearray) -> tuple[str | None, bytes]: + """ + Identify and extract SIG/BOM in given sequence. + """ + + for iana_encoding in ENCODING_MARKS: + marks: bytes | list[bytes] = ENCODING_MARKS[iana_encoding] + + if isinstance(marks, bytes): + marks = [marks] + + for mark in marks: + if sequence.startswith(mark): + return iana_encoding, mark + + return None, b"" + + +def should_strip_sig_or_bom(iana_encoding: str) -> bool: + return iana_encoding not in {"utf_16", "utf_32"} + + +def iana_name(cp_name: str, strict: bool = True) -> str: + """Returns the Python normalized encoding name (Not the IANA official name).""" + cp_name = cp_name.lower().replace("-", "_") + + encoding_alias: str + encoding_iana: str + + for encoding_alias, encoding_iana in aliases.items(): + if cp_name in [encoding_alias, encoding_iana]: + return encoding_iana + + if strict: + raise ValueError(f"Unable to retrieve IANA for '{cp_name}'") + + return cp_name + + +def cp_similarity(iana_name_a: str, iana_name_b: str) -> float: + if is_multi_byte_encoding(iana_name_a) or is_multi_byte_encoding(iana_name_b): + return 0.0 + + decoder_a = importlib.import_module(f"encodings.{iana_name_a}").IncrementalDecoder + decoder_b = importlib.import_module(f"encodings.{iana_name_b}").IncrementalDecoder + + id_a: IncrementalDecoder = decoder_a(errors="ignore") + id_b: IncrementalDecoder = decoder_b(errors="ignore") + + character_match_count: int = 0 + + for i in range(256): + to_be_decoded: bytes = bytes([i]) + if id_a.decode(to_be_decoded) == id_b.decode(to_be_decoded): + character_match_count += 1 + + return character_match_count / 256 + + +def is_cp_similar(iana_name_a: str, iana_name_b: str) -> bool: + """ + Determine if two code page are at least 80% similar. IANA_SUPPORTED_SIMILAR dict was generated using + the function cp_similarity. + """ + return ( + iana_name_a in IANA_SUPPORTED_SIMILAR + and iana_name_b in IANA_SUPPORTED_SIMILAR[iana_name_a] + ) + + +def set_logging_handler( + name: str = "charset_normalizer", + level: int = logging.INFO, + format_string: str = "%(asctime)s | %(levelname)s | %(message)s", +) -> None: + logger = logging.getLogger(name) + logger.setLevel(level) + + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter(format_string)) + logger.addHandler(handler) + + +def cut_sequence_chunks( + sequences: bytes | bytearray, + encoding_iana: str, + offsets: range, + chunk_size: int, + bom_or_sig_available: bool, + strip_sig_or_bom: bool, + sig_payload: bytes, + is_multi_byte_decoder: bool, + decoded_payload: str | None = None, +) -> Generator[str, None, None]: + if decoded_payload and is_multi_byte_decoder is False: + for i in offsets: + chunk = decoded_payload[i : i + chunk_size] + if not chunk: + break + yield chunk + else: + for i in offsets: + chunk_end = i + chunk_size + if chunk_end > len(sequences) + 8: + continue + + cut_sequence = sequences[i : i + chunk_size] + + if bom_or_sig_available and strip_sig_or_bom is False: + cut_sequence = sig_payload + cut_sequence + + chunk = cut_sequence.decode( + encoding_iana, + errors="ignore" if is_multi_byte_decoder else "strict", + ) + + # multi-byte bad cutting detector and adjustment + # not the cleanest way to perform that fix but clever enough for now. + if is_multi_byte_decoder and i > 0: + chunk_partial_size_chk: int = min(chunk_size, 16) + + if ( + decoded_payload + and chunk[:chunk_partial_size_chk] not in decoded_payload + ): + for j in range(i, i - 4, -1): + cut_sequence = sequences[j:chunk_end] + + if bom_or_sig_available and strip_sig_or_bom is False: + cut_sequence = sig_payload + cut_sequence + + chunk = cut_sequence.decode(encoding_iana, errors="ignore") + + if chunk[:chunk_partial_size_chk] in decoded_payload: + break + + yield chunk diff --git a/intelliaide/vendor/charset_normalizer/version.py b/intelliaide/vendor/charset_normalizer/version.py new file mode 100644 index 0000000..a93d367 --- /dev/null +++ b/intelliaide/vendor/charset_normalizer/version.py @@ -0,0 +1,8 @@ +""" +Expose version +""" + +from __future__ import annotations + +__version__ = "3.4.7" +VERSION = __version__.split(".") diff --git a/intelliaide/vendor/cryptography-48.0.0.dist-info/INSTALLER b/intelliaide/vendor/cryptography-48.0.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/intelliaide/vendor/cryptography-48.0.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/intelliaide/vendor/cryptography-48.0.0.dist-info/METADATA b/intelliaide/vendor/cryptography-48.0.0.dist-info/METADATA new file mode 100644 index 0000000..fbea0aa --- /dev/null +++ b/intelliaide/vendor/cryptography-48.0.0.dist-info/METADATA @@ -0,0 +1,110 @@ +Metadata-Version: 2.4 +Name: cryptography +Version: 48.0.0 +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Natural Language :: English +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: POSIX +Classifier: Operating System :: POSIX :: BSD +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: Microsoft :: Windows +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable +Classifier: Topic :: Security :: Cryptography +Requires-Dist: cffi>=2.0.0 ; platform_python_implementation != 'PyPy' +Requires-Dist: typing-extensions>=4.13.2 ; python_full_version < '3.11' +Requires-Dist: bcrypt>=3.1.5 ; extra == 'ssh' +Provides-Extra: ssh +License-File: LICENSE +License-File: LICENSE.APACHE +License-File: LICENSE.BSD +Summary: cryptography is a package which provides cryptographic recipes and primitives to Python developers. +Author-email: The Python Cryptographic Authority and individual contributors +License-Expression: Apache-2.0 OR BSD-3-Clause +Requires-Python: >=3.9, !=3.9.0, !=3.9.1 +Description-Content-Type: text/x-rst; charset=UTF-8 +Project-URL: changelog, https://cryptography.io/en/latest/changelog/ +Project-URL: documentation, https://cryptography.io/ +Project-URL: homepage, https://github.com/pyca/cryptography +Project-URL: issues, https://github.com/pyca/cryptography/issues +Project-URL: source, https://github.com/pyca/cryptography/ + +pyca/cryptography +================= + +.. image:: https://img.shields.io/pypi/v/cryptography.svg + :target: https://pypi.org/project/cryptography/ + :alt: Latest Version + +.. image:: https://readthedocs.org/projects/cryptography/badge/?version=latest + :target: https://cryptography.io + :alt: Latest Docs + +.. image:: https://github.com/pyca/cryptography/actions/workflows/ci.yml/badge.svg + :target: https://github.com/pyca/cryptography/actions/workflows/ci.yml?query=branch%3Amain + +``cryptography`` is a package which provides cryptographic recipes and +primitives to Python developers. Our goal is for it to be your "cryptographic +standard library". It supports Python 3.9+ and PyPy3 7.3.11+. + +``cryptography`` includes both high level recipes and low level interfaces to +common cryptographic algorithms such as symmetric ciphers, message digests, and +key derivation functions. For example, to encrypt something with +``cryptography``'s high level symmetric encryption recipe: + +.. code-block:: pycon + + >>> from cryptography.fernet import Fernet + >>> # Put this somewhere safe! + >>> key = Fernet.generate_key() + >>> f = Fernet(key) + >>> token = f.encrypt(b"A really secret message. Not for prying eyes.") + >>> token + b'...' + >>> f.decrypt(token) + b'A really secret message. Not for prying eyes.' + +You can find more information in the `documentation`_. + +You can install ``cryptography`` with: + +.. code-block:: console + + $ pip install cryptography + +For full details see `the installation documentation`_. + +Discussion +~~~~~~~~~~ + +If you run into bugs, you can file them in our `issue tracker`_. + +We maintain a `cryptography-dev`_ mailing list for development discussion. + +You can also join ``#pyca`` on ``irc.libera.chat`` to ask questions or get +involved. + +Security +~~~~~~~~ + +Need to report a security issue? Please consult our `security reporting`_ +documentation. + + +.. _`documentation`: https://cryptography.io/ +.. _`the installation documentation`: https://cryptography.io/en/latest/installation/ +.. _`issue tracker`: https://github.com/pyca/cryptography/issues +.. _`cryptography-dev`: https://mail.python.org/mailman/listinfo/cryptography-dev +.. _`security reporting`: https://cryptography.io/en/latest/security/ + diff --git a/intelliaide/vendor/cryptography-48.0.0.dist-info/RECORD b/intelliaide/vendor/cryptography-48.0.0.dist-info/RECORD new file mode 100644 index 0000000..fd2dfe1 --- /dev/null +++ b/intelliaide/vendor/cryptography-48.0.0.dist-info/RECORD @@ -0,0 +1,195 @@ +cryptography-48.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +cryptography-48.0.0.dist-info/METADATA,sha256=Q_COX-Utpsgl5e4Ym9mF7hU3hgAgMy3Ly8XaVnneyQY,4343 +cryptography-48.0.0.dist-info/RECORD,, +cryptography-48.0.0.dist-info/WHEEL,sha256=59dZFnCZ9Ah4-4Hov4_Ij4OuERDplrmrGrO0EnwpCG0,109 +cryptography-48.0.0.dist-info/licenses/LICENSE,sha256=Pgx8CRqUi4JTO6mP18u0BDLW8amsv4X1ki0vmak65rs,197 +cryptography-48.0.0.dist-info/licenses/LICENSE.APACHE,sha256=qsc7MUj20dcRHbyjIJn2jSbGRMaBOuHk8F9leaomY_4,11360 +cryptography-48.0.0.dist-info/licenses/LICENSE.BSD,sha256=YCxMdILeZHndLpeTzaJ15eY9dz2s0eymiSMqtwCPtPs,1532 +cryptography-48.0.0.dist-info/sboms/cryptography-rust.cyclonedx.json,sha256=1fzfm56UYqOyUDjSaZ_PR1FgbE0pmZk5bxNk6uJedaI,45613 +cryptography-48.0.0.dist-info/sboms/sbom.json,sha256=hj41wZWnr0WU1kaHtI0VS_cPesX716EgqQjDnxMp0yI,1206 +cryptography/__about__.py,sha256=4Ay52_5LrlJ_oTPcjtV4NXOcNzJhpo6iDZPEYbndzJI,445 +cryptography/__init__.py,sha256=mthuUrTd4FROCpUYrTIqhjz6s6T9djAZrV7nZ1oMm2o,364 +cryptography/__pycache__/__about__.cpython-312.pyc,, +cryptography/__pycache__/__init__.cpython-312.pyc,, +cryptography/__pycache__/exceptions.cpython-312.pyc,, +cryptography/__pycache__/fernet.cpython-312.pyc,, +cryptography/__pycache__/utils.cpython-312.pyc,, +cryptography/exceptions.py,sha256=835EWILc2fwxw-gyFMriciC2SqhViETB10LBSytnDIc,1087 +cryptography/fernet.py,sha256=3Cvxkh0KJSbX8HbnCHu4wfCW7U0GgfUA3v_qQ8a8iWc,6963 +cryptography/hazmat/__init__.py,sha256=5IwrLWrVp0AjEr_4FdWG_V057NSJGY_W4egNNsuct0g,455 +cryptography/hazmat/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/__pycache__/_oid.cpython-312.pyc,, +cryptography/hazmat/_oid.py,sha256=p8ThjwJB56Ci_rAIrjyJ1f8VjgD6e39es2dh8JIUBOw,17240 +cryptography/hazmat/asn1/__init__.py,sha256=AZVmLA09Q9-bq27G8fYr3dof-iRkh5KSX3zEPmNb-9w,744 +cryptography/hazmat/asn1/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/asn1/__pycache__/asn1.cpython-312.pyc,, +cryptography/hazmat/asn1/asn1.py,sha256=FZ6nZr3N1kKqNBQfh0qKtJ0cLUjwtGFemG82KNWOcfk,14724 +cryptography/hazmat/backends/__init__.py,sha256=O5jvKFQdZnXhKeqJ-HtulaEL9Ni7mr1mDzZY5kHlYhI,361 +cryptography/hazmat/backends/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/backends/openssl/__init__.py,sha256=p3jmJfnCag9iE5sdMrN6VvVEu55u46xaS_IjoI0SrmA,305 +cryptography/hazmat/backends/openssl/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/backends/openssl/__pycache__/backend.cpython-312.pyc,, +cryptography/hazmat/backends/openssl/backend.py,sha256=7A8JKJlQjG3-Fqdcyat2Eb7kHLYAAtGF0j9ooaWyaE0,10520 +cryptography/hazmat/bindings/__init__.py,sha256=s9oKCQ2ycFdXoERdS1imafueSkBsL9kvbyfghaauZ9Y,180 +cryptography/hazmat/bindings/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/bindings/_rust.abi3.so,sha256=UVbEIvZcX5Eb4cvajr3IhuE7RL74hDZf3cObn80DKeY,13844864 +cryptography/hazmat/bindings/_rust/__init__.pyi,sha256=uUnnvOq_uameYnBNpmsv3SRAVO0WQGpjI58aR1lL4Kw,2285 +cryptography/hazmat/bindings/_rust/_openssl.pyi,sha256=T_5TpYO8bghIPjAQ3BtAABqw-CcYskHp92wm5PNY6dc,228 +cryptography/hazmat/bindings/_rust/asn1.pyi,sha256=BrGjC8J6nwuS-r3EVcdXJB8ndotfY9mbQYOfpbPG0HA,354 +cryptography/hazmat/bindings/_rust/declarative_asn1.pyi,sha256=iSeRyyd3hN0VIUKg_p424AzQ-b7hW1b_53Yx50yniYY,3661 +cryptography/hazmat/bindings/_rust/exceptions.pyi,sha256=exXr2xw_0pB1kk93cYbM3MohbzoUkjOms1ZMUi0uQZE,640 +cryptography/hazmat/bindings/_rust/ocsp.pyi,sha256=VPVWuKHI9EMs09ZLRYAGvR0Iz0mCMmEzXAkgJHovpoM,4020 +cryptography/hazmat/bindings/_rust/openssl/__init__.pyi,sha256=JDCef29vXnrEMvF13JH9z1-W9ahiTZQAk5xhu_KH6po,1550 +cryptography/hazmat/bindings/_rust/openssl/aead.pyi,sha256=7d--xdc0vuzRe4S2uuQaGm-9GGKv4NcSLQcLyWcaD6s,4582 +cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi,sha256=LhPzHWSXJq4grAJXn6zSvSSdV-aYIIscHDwIPlJGGPs,1315 +cryptography/hazmat/bindings/_rust/openssl/cmac.pyi,sha256=nPH0X57RYpsAkRowVpjQiHE566ThUTx7YXrsadmrmHk,564 +cryptography/hazmat/bindings/_rust/openssl/dh.pyi,sha256=Z3TC-G04-THtSdAOPLM1h2G7ml5bda1ElZUcn5wpuhk,1564 +cryptography/hazmat/bindings/_rust/openssl/dsa.pyi,sha256=qBtkgj2albt2qFcnZ9UDrhzoNhCVO7HTby5VSf1EXMI,1299 +cryptography/hazmat/bindings/_rust/openssl/ec.pyi,sha256=zJy0pRa5n-_p2dm45PxECB_-B6SVZyNKfjxFDpPqT38,1691 +cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi,sha256=VXfXd5G6hUivg399R1DYdmW3eTb0EebzDTqjRC2gaRw,532 +cryptography/hazmat/bindings/_rust/openssl/ed448.pyi,sha256=Yx49lqdnjsD7bxiDV1kcaMrDktug5evi5a6zerMiy2s,514 +cryptography/hazmat/bindings/_rust/openssl/hashes.pyi,sha256=bFe3T13UeyRInUJMD-R5ArGTGLqWDiWo2Lv2KGzUbf4,1076 +cryptography/hazmat/bindings/_rust/openssl/hmac.pyi,sha256=BXZn7NDjL3JAbYW0SQ8pg1iyC5DbQXVhUAiwsi8DFR8,702 +cryptography/hazmat/bindings/_rust/openssl/hpke.pyi,sha256=wrz8c12gBNBAItz9jorr_W4s8rFYDE1Yel1z2I-Wd2s,2877 +cryptography/hazmat/bindings/_rust/openssl/kdf.pyi,sha256=HiiLdEB9nqMsTvMwiFppQpfPz6fHUz9_gpjEB_XAOXA,6619 +cryptography/hazmat/bindings/_rust/openssl/keys.pyi,sha256=teIt8M6ZEMJrn4s3W0UnW0DZ-30Jd68WnSsKKG124l0,912 +cryptography/hazmat/bindings/_rust/openssl/mldsa.pyi,sha256=HChNsjW0C_vO9OVD4FhEZrXO3_FiEq2ZTF59hlfcEdM,1073 +cryptography/hazmat/bindings/_rust/openssl/mlkem.pyi,sha256=byoeus4-lIOlWlEiRQdCc00qCkXpOB5peBY6yo9ik8s,835 +cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi,sha256=_SW9NtQ5FDlAbdclFtWpT4lGmxKIKHpN-4j8J2BzYfQ,585 +cryptography/hazmat/bindings/_rust/openssl/rsa.pyi,sha256=2OQCNSXkxgc-3uw1xiCCloIQTV6p9_kK79Yu0rhZgPc,1364 +cryptography/hazmat/bindings/_rust/openssl/x25519.pyi,sha256=ewn4GpQyb7zPwE-ni7GtyQgMC0A1mLuqYsSyqv6nI_s,523 +cryptography/hazmat/bindings/_rust/openssl/x448.pyi,sha256=juTZTmli8jO_5Vcufg-vHvx_tCyezmSLIh_9PU3TczI,505 +cryptography/hazmat/bindings/_rust/pkcs12.pyi,sha256=vEEd5wDiZvb8ZGFaziLCaWLzAwoG_tvPUxLQw5_uOl8,1605 +cryptography/hazmat/bindings/_rust/pkcs7.pyi,sha256=txGBJijqZshEcqra6byPNbnisIdlxzOSIHP2hl9arPs,1601 +cryptography/hazmat/bindings/_rust/test_support.pyi,sha256=PPhld-WkO743iXFPebeG0LtgK0aTzGdjcIsay1Gm5GE,757 +cryptography/hazmat/bindings/_rust/x509.pyi,sha256=rmt4enscdvDQ7lBEHb2cASwN0d3TP3ymEVZcEJ-Rf3A,10152 +cryptography/hazmat/bindings/openssl/__init__.py,sha256=s9oKCQ2ycFdXoERdS1imafueSkBsL9kvbyfghaauZ9Y,180 +cryptography/hazmat/bindings/openssl/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/bindings/openssl/__pycache__/_conditional.cpython-312.pyc,, +cryptography/hazmat/bindings/openssl/__pycache__/binding.cpython-312.pyc,, +cryptography/hazmat/bindings/openssl/_conditional.py,sha256=vfzi-xHdStAcJQjLqqOlSos94L8D_tqFtBe7Ovw-KtY,5611 +cryptography/hazmat/bindings/openssl/binding.py,sha256=GnoZLYvCzXbKf4KldUXec-oz-hdVSa_sinZnqXgdS2s,4096 +cryptography/hazmat/decrepit/__init__.py,sha256=wHCbWfaefa-fk6THSw9th9fJUsStJo7245wfFBqmduA,216 +cryptography/hazmat/decrepit/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/decrepit/ciphers/__init__.py,sha256=wHCbWfaefa-fk6THSw9th9fJUsStJo7245wfFBqmduA,216 +cryptography/hazmat/decrepit/ciphers/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/decrepit/ciphers/__pycache__/algorithms.cpython-312.pyc,, +cryptography/hazmat/decrepit/ciphers/__pycache__/modes.cpython-312.pyc,, +cryptography/hazmat/decrepit/ciphers/algorithms.py,sha256=yqHt7k5OzF_KuvO8M9vTlNLtG7EotbvShyYKIaUJqhg,3566 +cryptography/hazmat/decrepit/ciphers/modes.py,sha256=Oq_PEwCke5OLczOfr_vzAOJ6wPx-rlsvHAXPBuh5b9o,1649 +cryptography/hazmat/primitives/__init__.py,sha256=s9oKCQ2ycFdXoERdS1imafueSkBsL9kvbyfghaauZ9Y,180 +cryptography/hazmat/primitives/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/_asymmetric.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/_cipheralgorithm.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/_modes.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/_serialization.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/cmac.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/constant_time.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/hashes.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/hmac.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/hpke.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/keywrap.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/padding.cpython-312.pyc,, +cryptography/hazmat/primitives/__pycache__/poly1305.cpython-312.pyc,, +cryptography/hazmat/primitives/_asymmetric.py,sha256=RhgcouUB6HTiFDBrR1LxqkMjpUxIiNvQ1r_zJjRG6qQ,532 +cryptography/hazmat/primitives/_cipheralgorithm.py,sha256=Eh3i7lwedHfi0eLSsH93PZxQKzY9I6lkK67vL4V5tOc,1522 +cryptography/hazmat/primitives/_modes.py,sha256=_EiAOD8Jb6WpFXluwkcUT3ECz_TH8xjGvbQlGkNm59Q,3075 +cryptography/hazmat/primitives/_serialization.py,sha256=hi0xJblBAZ8pmZx2lWa-lruphxvvvG3g9DNK0G8Odfs,4440 +cryptography/hazmat/primitives/asymmetric/__init__.py,sha256=s9oKCQ2ycFdXoERdS1imafueSkBsL9kvbyfghaauZ9Y,180 +cryptography/hazmat/primitives/asymmetric/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/dh.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/dsa.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/ec.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/ed25519.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/ed448.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/mldsa.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/mlkem.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/padding.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/rsa.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/types.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/utils.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/x25519.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/__pycache__/x448.cpython-312.pyc,, +cryptography/hazmat/primitives/asymmetric/dh.py,sha256=klvHVaxAFkUyaWXLytqYjBTh81Zx1ByCJNZVRcSDYX8,3912 +cryptography/hazmat/primitives/asymmetric/dsa.py,sha256=kQzTViqAI9PiELNOBco_n6MfP3N9ehIPkSduOpupt0M,4482 +cryptography/hazmat/primitives/asymmetric/ec.py,sha256=LklVCLLxbbQzqiE6HRaYTRauLMphYUw3tE6c8oBIxYQ,10183 +cryptography/hazmat/primitives/asymmetric/ed25519.py,sha256=mbffLvs-3PgCDeg1rQXVXh7GLntlVL4ATjkIEc4pt5M,2998 +cryptography/hazmat/primitives/asymmetric/ed448.py,sha256=n0dK6y-VLxuw-zvrDxuPT4598hymgX-1Ry_jq9aUQhE,4002 +cryptography/hazmat/primitives/asymmetric/mldsa.py,sha256=49DPIhoHEyfGVwL6DRc3WlJ20Rinq-ldtjsp91NpyMM,12226 +cryptography/hazmat/primitives/asymmetric/mlkem.py,sha256=wvVkeFT-4of-VcUAER2-zI_5hXT6rwTJPMCQ8gF9GTA,7901 +cryptography/hazmat/primitives/asymmetric/padding.py,sha256=vQ6l6gOg9HqcbOsvHrSiJRVLdEj9L4m4HkRGYziTyFA,2854 +cryptography/hazmat/primitives/asymmetric/rsa.py,sha256=lIE7lGe449W5URLTKCpvFGerITouKoZV9GwuTzScsSo,8492 +cryptography/hazmat/primitives/asymmetric/types.py,sha256=q3glpax4EHgNpkRSEXlhK43a07F3Dsk-rQ3bbjL9ZnM,3309 +cryptography/hazmat/primitives/asymmetric/utils.py,sha256=Qs8Re9GFPjW_tNp_73IeJEjPCf0slOIsWOxo6qymT6k,821 +cryptography/hazmat/primitives/asymmetric/x25519.py,sha256=lQcgUk-Piubj8ynFNWQeYme3tYZMgHQqkRKkIDOHLzo,3888 +cryptography/hazmat/primitives/asymmetric/x448.py,sha256=b37ig7k7poG6SJDry42j-ElCj5q4eQ0X5CFmN0Sn8z8,3913 +cryptography/hazmat/primitives/ciphers/__init__.py,sha256=eyEXmjk6_CZXaOPYDr7vAYGXr29QvzgWL2-4CSolLFs,680 +cryptography/hazmat/primitives/ciphers/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/primitives/ciphers/__pycache__/aead.cpython-312.pyc,, +cryptography/hazmat/primitives/ciphers/__pycache__/algorithms.cpython-312.pyc,, +cryptography/hazmat/primitives/ciphers/__pycache__/base.cpython-312.pyc,, +cryptography/hazmat/primitives/ciphers/__pycache__/modes.cpython-312.pyc,, +cryptography/hazmat/primitives/ciphers/aead.py,sha256=Fzlyx7w8KYQakzDp1zWgJnIr62zgZrgVh1u2h4exB54,634 +cryptography/hazmat/primitives/ciphers/algorithms.py,sha256=IGfCycYJOh1TvoI34rfw8KfJ_xmt1p_DpBht2Fghceo,3496 +cryptography/hazmat/primitives/ciphers/base.py,sha256=aBC7HHBBoixebmparVr0UlODs3VD0A7B6oz_AaRjDv8,4253 +cryptography/hazmat/primitives/ciphers/modes.py,sha256=pNI0cSLCLxr7lz7ApfUpVpFH5q_bzOpKW-QggmgQB74,6007 +cryptography/hazmat/primitives/cmac.py,sha256=sz_s6H_cYnOvx-VNWdIKhRhe3Ymp8z8J0D3CBqOX3gg,338 +cryptography/hazmat/primitives/constant_time.py,sha256=xdunWT0nf8OvKdcqUhhlFKayGp4_PgVJRU2W1wLSr_A,422 +cryptography/hazmat/primitives/hashes.py,sha256=M8BrlKB3U6DEtHvWTV5VRjpteHv1kS3Zxm_Bsk04cr8,5184 +cryptography/hazmat/primitives/hmac.py,sha256=RpB3z9z5skirCQrm7zQbtnp9pLMnAjrlTUvKqF5aDDc,423 +cryptography/hazmat/primitives/hpke.py,sha256=RsHissC5l-dTPn1p2JbcrIrAnDBrLqM5ugBFSGpmKu4,865 +cryptography/hazmat/primitives/kdf/__init__.py,sha256=v3yiYBGU272EojNXbwfYZdbbfI9cVOCCG3nXhTDda3k,1037 +cryptography/hazmat/primitives/kdf/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/primitives/kdf/__pycache__/argon2.cpython-312.pyc,, +cryptography/hazmat/primitives/kdf/__pycache__/concatkdf.cpython-312.pyc,, +cryptography/hazmat/primitives/kdf/__pycache__/hkdf.cpython-312.pyc,, +cryptography/hazmat/primitives/kdf/__pycache__/kbkdf.cpython-312.pyc,, +cryptography/hazmat/primitives/kdf/__pycache__/pbkdf2.cpython-312.pyc,, +cryptography/hazmat/primitives/kdf/__pycache__/scrypt.cpython-312.pyc,, +cryptography/hazmat/primitives/kdf/__pycache__/x963kdf.cpython-312.pyc,, +cryptography/hazmat/primitives/kdf/argon2.py,sha256=ZJx-enUlAA4o7b46C1wlzG_XAQQW0wIkFgUKkrM_wA8,632 +cryptography/hazmat/primitives/kdf/concatkdf.py,sha256=BFnOKS72txKF0yQvASr0Na7uhzvLHpUCckK5iNAC_aQ,591 +cryptography/hazmat/primitives/kdf/hkdf.py,sha256=M0lAEfRoc4kpp4-nwDj9yB-vNZukIOYEQrUlWsBNn9o,543 +cryptography/hazmat/primitives/kdf/kbkdf.py,sha256=C3w99vjFqhqzAxzFOH4zzvVoHkb5AC6Lz6AEQGV7d_g,737 +cryptography/hazmat/primitives/kdf/pbkdf2.py,sha256=Xchkk99s-Mk6b6KSCouqdk8FVyiNVOSFIenmnW3tLgQ,468 +cryptography/hazmat/primitives/kdf/scrypt.py,sha256=XyWUdUUmhuI9V6TqAPOvujCSMGv1XQdg0a21IWCmO-U,590 +cryptography/hazmat/primitives/kdf/x963kdf.py,sha256=QKjhRehuTsAstL384bKfvjuv1yfdamAHVhsBRWen2Vw,456 +cryptography/hazmat/primitives/keywrap.py,sha256=UI-0UESQxBXTKU1HrrRoUwDiFsrWif81qyXlV1e7kyY,5776 +cryptography/hazmat/primitives/padding.py,sha256=QT-U-NvV2eQGO1wVPbDiNGNSc9keRDS-ig5cQOrLz0E,1865 +cryptography/hazmat/primitives/poly1305.py,sha256=P5EPQV-RB_FJPahpg01u0Ts4S_PnAmsroxIGXbGeRRo,355 +cryptography/hazmat/primitives/serialization/__init__.py,sha256=Q7uTgDlt7n3WfsMT6jYwutC6DIg_7SEeoAm1GHZ5B5E,1705 +cryptography/hazmat/primitives/serialization/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/primitives/serialization/__pycache__/base.cpython-312.pyc,, +cryptography/hazmat/primitives/serialization/__pycache__/pkcs12.cpython-312.pyc,, +cryptography/hazmat/primitives/serialization/__pycache__/pkcs7.cpython-312.pyc,, +cryptography/hazmat/primitives/serialization/__pycache__/ssh.cpython-312.pyc,, +cryptography/hazmat/primitives/serialization/base.py,sha256=ikq5MJIwp_oUnjiaBco_PmQwOTYuGi-XkYUYHKy8Vo0,615 +cryptography/hazmat/primitives/serialization/pkcs12.py,sha256=mS9cFNG4afzvseoc5e1MWoY2VskfL8N8Y_OFjl67luY,5104 +cryptography/hazmat/primitives/serialization/pkcs7.py,sha256=mFM7OFuZ8cCxUGUoo4Cof6BnCHc_Ibx1AQjjfxnPlxo,13998 +cryptography/hazmat/primitives/serialization/ssh.py,sha256=HV6ZqIjqNNaNUL6M4gQyNJJtZ75EsiSZs6yxddqBrWI,53789 +cryptography/hazmat/primitives/twofactor/__init__.py,sha256=tmMZGB-g4IU1r7lIFqASU019zr0uPp_wEBYcwdDCKCA,258 +cryptography/hazmat/primitives/twofactor/__pycache__/__init__.cpython-312.pyc,, +cryptography/hazmat/primitives/twofactor/__pycache__/hotp.cpython-312.pyc,, +cryptography/hazmat/primitives/twofactor/__pycache__/totp.cpython-312.pyc,, +cryptography/hazmat/primitives/twofactor/hotp.py,sha256=ivZo5BrcCGWLsqql4nZV0XXCjyGPi_iHfDFltGlOJwk,3256 +cryptography/hazmat/primitives/twofactor/totp.py,sha256=m5LPpRL00kp4zY8gTjr55Hfz9aMlPS53kHmVkSQCmdY,1652 +cryptography/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +cryptography/utils.py,sha256=wCnykfmWmp6L5zUFou715snYL9_YLLHjpj4_NHmlhwU,4281 +cryptography/x509/__init__.py,sha256=zciyadRgnCp2DNPPm6ujEKd6EX-8_zOnwtCjJbZBatg,8273 +cryptography/x509/__pycache__/__init__.cpython-312.pyc,, +cryptography/x509/__pycache__/base.cpython-312.pyc,, +cryptography/x509/__pycache__/certificate_transparency.cpython-312.pyc,, +cryptography/x509/__pycache__/extensions.cpython-312.pyc,, +cryptography/x509/__pycache__/general_name.cpython-312.pyc,, +cryptography/x509/__pycache__/name.cpython-312.pyc,, +cryptography/x509/__pycache__/ocsp.cpython-312.pyc,, +cryptography/x509/__pycache__/oid.cpython-312.pyc,, +cryptography/x509/__pycache__/verification.cpython-312.pyc,, +cryptography/x509/base.py,sha256=8322G4opJ1a2juXU14Qi2y-G1V-cQHaudo7gAFbTPlM,25998 +cryptography/x509/certificate_transparency.py,sha256=JqoOIDhlwInrYMFW6IFn77WJ0viF-PB_rlZV3vs9MYc,797 +cryptography/x509/extensions.py,sha256=0AUgutLe26SLg1KaxSeo0fG7fUBAyK2tEgJl9c_AQRM,77968 +cryptography/x509/general_name.py,sha256=sP_rV11Qlpsk4x3XXGJY_Mv0Q_s9dtjeLckHsjpLQoQ,7836 +cryptography/x509/name.py,sha256=H0Bix4uAxk5Ndkmee3Lxv3UdXbdph2XBr-nTPxHo54U,15356 +cryptography/x509/ocsp.py,sha256=Yey6NdFV1MPjop24Mj_VenjEpg3kUaMopSWOK0AbeBs,12699 +cryptography/x509/oid.py,sha256=BUzgXXGVWilkBkdKPTm9R4qElE9gAGHgdYPMZAp7PJo,931 +cryptography/x509/verification.py,sha256=gR2C2c-XZQtblZhT5T5vjSKOtCb74ef2alPVmEcwFlM,958 diff --git a/intelliaide/vendor/cryptography-48.0.0.dist-info/WHEEL b/intelliaide/vendor/cryptography-48.0.0.dist-info/WHEEL new file mode 100644 index 0000000..4e7840d --- /dev/null +++ b/intelliaide/vendor/cryptography-48.0.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: maturin (1.13.1) +Root-Is-Purelib: false +Tag: cp311-abi3-manylinux_2_34_x86_64 + diff --git a/intelliaide/vendor/cryptography-48.0.0.dist-info/licenses/LICENSE b/intelliaide/vendor/cryptography-48.0.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000..b11f379 --- /dev/null +++ b/intelliaide/vendor/cryptography-48.0.0.dist-info/licenses/LICENSE @@ -0,0 +1,3 @@ +This software is made available under the terms of *either* of the licenses +found in LICENSE.APACHE or LICENSE.BSD. Contributions to cryptography are made +under the terms of *both* these licenses. diff --git a/intelliaide/vendor/cryptography-48.0.0.dist-info/licenses/LICENSE.APACHE b/intelliaide/vendor/cryptography-48.0.0.dist-info/licenses/LICENSE.APACHE new file mode 100644 index 0000000..62589ed --- /dev/null +++ b/intelliaide/vendor/cryptography-48.0.0.dist-info/licenses/LICENSE.APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/intelliaide/vendor/cryptography-48.0.0.dist-info/licenses/LICENSE.BSD b/intelliaide/vendor/cryptography-48.0.0.dist-info/licenses/LICENSE.BSD new file mode 100644 index 0000000..ec1a29d --- /dev/null +++ b/intelliaide/vendor/cryptography-48.0.0.dist-info/licenses/LICENSE.BSD @@ -0,0 +1,27 @@ +Copyright (c) Individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of PyCA Cryptography nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/intelliaide/vendor/cryptography-48.0.0.dist-info/sboms/cryptography-rust.cyclonedx.json b/intelliaide/vendor/cryptography-48.0.0.dist-info/sboms/cryptography-rust.cyclonedx.json new file mode 100644 index 0000000..0e28622 --- /dev/null +++ b/intelliaide/vendor/cryptography-48.0.0.dist-info/sboms/cryptography-rust.cyclonedx.json @@ -0,0 +1,1362 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "version": 1, + "serialNumber": "urn:uuid:b0f888ff-baac-404b-ad7b-a394cb3cc7f7", + "metadata": { + "timestamp": "2026-05-04T22:46:52.633241040Z", + "tools": [ + { + "vendor": "CycloneDX", + "name": "cargo-cyclonedx", + "version": "0.5.9" + } + ], + "authors": [ + { + "name": "The cryptography developers", + "email": "cryptography-dev@python.org" + } + ], + "component": { + "type": "library", + "bom-ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust#cryptography-rust@0.1.0", + "author": "The cryptography developers ", + "name": "cryptography-rust", + "version": "0.1.0", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR BSD-3-Clause" + } + ], + "purl": "pkg:cargo/cryptography-rust@0.1.0?download_url=file://.", + "components": [ + { + "type": "library", + "bom-ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust#cryptography-rust@0.1.0 bin-target-0", + "name": "cryptography_rust", + "version": "0.1.0", + "purl": "pkg:cargo/cryptography-rust@0.1.0?download_url=file://.#src/lib.rs" + } + ] + }, + "properties": [ + { + "name": "cdx:rustc:sbom:target:all_targets", + "value": "true" + } + ] + }, + "components": [ + { + "type": "library", + "bom-ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-cffi#0.1.0", + "author": "The cryptography developers ", + "name": "cryptography-cffi", + "version": "0.1.0", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR BSD-3-Clause" + } + ], + "purl": "pkg:cargo/cryptography-cffi@0.1.0?download_url=file://cryptography-cffi" + }, + { + "type": "library", + "bom-ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-crypto#0.1.0", + "author": "The cryptography developers ", + "name": "cryptography-crypto", + "version": "0.1.0", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR BSD-3-Clause" + } + ], + "purl": "pkg:cargo/cryptography-crypto@0.1.0?download_url=file://cryptography-crypto" + }, + { + "type": "library", + "bom-ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-keepalive#0.1.0", + "author": "The cryptography developers ", + "name": "cryptography-keepalive", + "version": "0.1.0", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR BSD-3-Clause" + } + ], + "purl": "pkg:cargo/cryptography-keepalive@0.1.0?download_url=file://cryptography-keepalive" + }, + { + "type": "library", + "bom-ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-key-parsing#0.1.0", + "author": "The cryptography developers ", + "name": "cryptography-key-parsing", + "version": "0.1.0", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR BSD-3-Clause" + } + ], + "purl": "pkg:cargo/cryptography-key-parsing@0.1.0?download_url=file://cryptography-key-parsing" + }, + { + "type": "library", + "bom-ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-openssl#0.1.0", + "author": "The cryptography developers ", + "name": "cryptography-openssl", + "version": "0.1.0", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR BSD-3-Clause" + } + ], + "purl": "pkg:cargo/cryptography-openssl@0.1.0?download_url=file://cryptography-openssl" + }, + { + "type": "library", + "bom-ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-x509#0.1.0", + "author": "The cryptography developers ", + "name": "cryptography-x509", + "version": "0.1.0", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR BSD-3-Clause" + } + ], + "purl": "pkg:cargo/cryptography-x509@0.1.0?download_url=file://cryptography-x509" + }, + { + "type": "library", + "bom-ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-x509-verification#0.1.0", + "author": "The cryptography developers ", + "name": "cryptography-x509-verification", + "version": "0.1.0", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR BSD-3-Clause" + } + ], + "purl": "pkg:cargo/cryptography-x509-verification@0.1.0?download_url=file://cryptography-x509-verification" + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#asn1@0.24.1", + "author": "Alex Gaynor ", + "name": "asn1", + "version": "0.24.1", + "description": "ASN.1 (DER) parser and writer for Rust.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "c9795210620c0cb3f9a7ce4f882808c38e1ef7b347c90591dceae0886e031fb1" + } + ], + "licenses": [ + { + "expression": "BSD-3-Clause" + } + ], + "purl": "pkg:cargo/asn1@0.24.1", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/alex/rust-asn1" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#asn1_derive@0.24.1", + "author": "Alex Gaynor ", + "name": "asn1_derive", + "version": "0.24.1", + "description": "#[derive] support for asn1", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "909e307f1cc32bb8bccbd98f446e6d1bf03fa30f7b53a4337da7181ad30fa11a" + } + ], + "licenses": [ + { + "expression": "BSD-3-Clause" + } + ], + "purl": "pkg:cargo/asn1_derive@0.24.1", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/alex/rust-asn1" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#base64@0.22.1", + "author": "Marshall Pierce ", + "name": "base64", + "version": "0.22.1", + "description": "encodes and decodes base64 as bytes or utf8", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/base64@0.22.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/base64" + }, + { + "type": "vcs", + "url": "https://github.com/marshallpierce/rust-base64" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.11.1", + "author": "The Rust Project Developers", + "name": "bitflags", + "version": "2.11.1", + "description": "A macro to generate structures which behave like bitflags. ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/bitflags@2.11.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/bitflags" + }, + { + "type": "website", + "url": "https://github.com/bitflags/bitflags" + }, + { + "type": "vcs", + "url": "https://github.com/bitflags/bitflags" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#cc@1.2.61", + "author": "Alex Crichton ", + "name": "cc", + "version": "1.2.61", + "description": "A build-time dependency for Cargo build scripts to assist in invoking the native C compiler to compile native C code into a static archive to be linked into Rust code. ", + "scope": "excluded", + "hashes": [ + { + "alg": "SHA-256", + "content": "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/cc@1.2.61", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/cc" + }, + { + "type": "website", + "url": "https://github.com/rust-lang/cc-rs" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/cc-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "author": "Alex Crichton ", + "name": "cfg-if", + "version": "1.0.4", + "description": "A macro to ergonomically define an item depending on a large number of #[cfg] parameters. Structured like an if-else chain, the first matching branch is the item that gets emitted. ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/cfg-if@1.0.4", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-lang/cfg-if" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#find-msvc-tools@0.1.9", + "name": "find-msvc-tools", + "version": "0.1.9", + "description": "Find windows-specific tools, read MSVC versions from the registry and from COM interfaces", + "scope": "excluded", + "hashes": [ + { + "alg": "SHA-256", + "content": "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/find-msvc-tools@0.1.9", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/find-msvc-tools" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/cc-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#foreign-types-shared@0.1.1", + "author": "Steven Fackler ", + "name": "foreign-types-shared", + "version": "0.1.1", + "description": "An internal crate used by foreign-types", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/foreign-types-shared@0.1.1", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/sfackler/foreign-types" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#foreign-types@0.3.2", + "author": "Steven Fackler ", + "name": "foreign-types", + "version": "0.3.2", + "description": "A framework for Rust wrappers over C APIs", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/foreign-types@0.3.2", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/sfackler/foreign-types" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0", + "name": "heck", + "version": "0.5.0", + "description": "heck is a case conversion library.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/heck@0.5.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/withoutboats/heck" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.18", + "author": "David Tolnay ", + "name": "itoa", + "version": "1.0.18", + "description": "Fast integer primitive to string conversion", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/itoa@1.0.18", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/itoa" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/itoa" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.186", + "author": "The Rust Project Developers", + "name": "libc", + "version": "0.2.186", + "description": "Raw FFI bindings to platform libraries like libc.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/libc@0.2.186", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-lang/libc" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.4", + "author": "Aleksey Kladov ", + "name": "once_cell", + "version": "1.21.4", + "description": "Single assignment cells and lazy values.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/once_cell@1.21.4", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/once_cell" + }, + { + "type": "vcs", + "url": "https://github.com/matklad/once_cell" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#openssl-macros@0.1.1", + "name": "openssl-macros", + "version": "0.1.1", + "description": "Internal macros used by the openssl crate.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/openssl-macros@0.1.1" + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.115", + "author": "Alex Crichton , Steven Fackler ", + "name": "openssl-sys", + "version": "0.9.115", + "description": "FFI bindings to OpenSSL", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "158fe5b292746440aa6e7a7e690e55aeb72d41505e2804c23c6973ad0e9c9781" + } + ], + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/openssl-sys@0.9.115", + "externalReferences": [ + { + "type": "other", + "url": "openssl" + }, + { + "type": "vcs", + "url": "https://github.com/rust-openssl/rust-openssl" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#openssl@0.10.79", + "author": "Steven Fackler ", + "name": "openssl", + "version": "0.10.79", + "description": "OpenSSL bindings", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "bf0b434746ee2832f4f0baf10137e1cabb18cbe6912c69e2e33263c45250f542" + } + ], + "licenses": [ + { + "expression": "Apache-2.0" + } + ], + "purl": "pkg:cargo/openssl@0.10.79", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-openssl/rust-openssl" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pem@3.0.6", + "author": "Jonathan Creekmore ", + "name": "pem", + "version": "3.0.6", + "description": "Parse and encode PEM-encoded data.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" + } + ], + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/pem@3.0.6", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/pem/" + }, + { + "type": "website", + "url": "https://github.com/jcreekmore/pem-rs.git" + }, + { + "type": "vcs", + "url": "https://github.com/jcreekmore/pem-rs.git" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pkg-config@0.3.33", + "author": "Alex Crichton ", + "name": "pkg-config", + "version": "0.3.33", + "description": "A library to run the pkg-config system tool at build time in order to be used in Cargo build scripts. ", + "scope": "excluded", + "hashes": [ + { + "alg": "SHA-256", + "content": "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pkg-config@0.3.33", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/pkg-config" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/pkg-config-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#portable-atomic@1.13.1", + "name": "portable-atomic", + "version": "1.13.1", + "description": "Portable atomic types including support for 128-bit atomics, atomic float, etc. ", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/portable-atomic@1.13.1", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/taiki-e/portable-atomic" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "author": "David Tolnay , Alex Crichton ", + "name": "proc-macro2", + "version": "1.0.106", + "description": "A substitute implementation of the compiler's `proc_macro` API to decouple token-based libraries from the procedural macro use case.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/proc-macro2@1.0.106", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/proc-macro2" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/proc-macro2" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.28.3", + "author": "PyO3 Project and Contributors ", + "name": "pyo3-build-config", + "version": "0.28.3", + "description": "Build configuration for the PyO3 ecosystem", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3-build-config@0.28.3", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-ffi@0.28.3", + "author": "PyO3 Project and Contributors ", + "name": "pyo3-ffi", + "version": "0.28.3", + "description": "Python-API bindings for the PyO3 ecosystem", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3-ffi@0.28.3", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "other", + "url": "python" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros-backend@0.28.3", + "author": "PyO3 Project and Contributors ", + "name": "pyo3-macros-backend", + "version": "0.28.3", + "description": "Code generation for PyO3 package", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3-macros-backend@0.28.3", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros@0.28.3", + "author": "PyO3 Project and Contributors ", + "name": "pyo3-macros", + "version": "0.28.3", + "description": "Proc macros for PyO3 package", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3-macros@0.28.3", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3@0.28.3", + "author": "PyO3 Project and Contributors ", + "name": "pyo3", + "version": "0.28.3", + "description": "Bindings to Python interpreter", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/pyo3@0.28.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/crate/pyo3/" + }, + { + "type": "website", + "url": "https://github.com/pyo3/pyo3" + }, + { + "type": "vcs", + "url": "https://github.com/pyo3/pyo3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.45", + "author": "David Tolnay ", + "name": "quote", + "version": "1.0.45", + "description": "Quasi-quoting macro quote!(...)", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/quote@1.0.45", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/quote/" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/quote" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#self_cell@1.2.2", + "author": "Lukas Bergdoll ", + "name": "self_cell", + "version": "1.2.2", + "description": "Safe-to-use proc-macro-free self-referential structs in stable Rust.", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 OR GPL-2.0-only" + } + ], + "purl": "pkg:cargo/self_cell@1.2.2", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/self_cell" + }, + { + "type": "vcs", + "url": "https://github.com/Voultapher/self_cell" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#shlex@1.3.0", + "author": "comex , Fenhl , Adrian Taylor , Alex Touchet , Daniel Parks , Garrett Berg ", + "name": "shlex", + "version": "1.3.0", + "description": "Split a string into shell words, like Python's shlex.", + "scope": "excluded", + "hashes": [ + { + "alg": "SHA-256", + "content": "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/shlex@1.3.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/comex/rust-shlex" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117", + "author": "David Tolnay ", + "name": "syn", + "version": "2.0.117", + "description": "Parser for Rust source code", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/syn@2.0.117", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/syn" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/syn" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.13.5", + "author": "Dan Gohman ", + "name": "target-lexicon", + "version": "0.13.5", + "description": "LLVM target triple types", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + } + ], + "licenses": [ + { + "expression": "Apache-2.0 WITH LLVM-exception" + } + ], + "purl": "pkg:cargo/target-lexicon@0.13.5", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/target-lexicon/" + }, + { + "type": "vcs", + "url": "https://github.com/bytecodealliance/target-lexicon" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.24", + "author": "David Tolnay ", + "name": "unicode-ident", + "version": "1.0.24", + "description": "Determine whether characters have the XID_Start or XID_Continue properties according to Unicode Standard Annex #31", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + } + ], + "licenses": [ + { + "expression": "(MIT OR Apache-2.0) AND Unicode-3.0" + } + ], + "purl": "pkg:cargo/unicode-ident@1.0.24", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/unicode-ident" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/unicode-ident" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#vcpkg@0.2.15", + "author": "Jim McGrath ", + "name": "vcpkg", + "version": "0.2.15", + "description": "A library to find native dependencies in a vcpkg tree at build time in order to be used in Cargo build scripts. ", + "scope": "excluded", + "hashes": [ + { + "alg": "SHA-256", + "content": "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + } + ], + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/vcpkg@0.2.15", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/vcpkg" + }, + { + "type": "vcs", + "url": "https://github.com/mcgoo/vcpkg-rs" + } + ] + } + ], + "dependencies": [ + { + "ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust#cryptography-rust@0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#asn1@0.24.1", + "registry+https://github.com/rust-lang/crates.io-index#base64@0.22.1", + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-cffi#0.1.0", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-crypto#0.1.0", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-keepalive#0.1.0", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-key-parsing#0.1.0", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-openssl#0.1.0", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-x509#0.1.0", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-x509-verification#0.1.0", + "registry+https://github.com/rust-lang/crates.io-index#foreign-types-shared@0.1.1", + "registry+https://github.com/rust-lang/crates.io-index#openssl@0.10.79", + "registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.115", + "registry+https://github.com/rust-lang/crates.io-index#pem@3.0.6", + "registry+https://github.com/rust-lang/crates.io-index#pyo3@0.28.3", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.28.3", + "registry+https://github.com/rust-lang/crates.io-index#self_cell@1.2.2" + ] + }, + { + "ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-cffi#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cc@1.2.61", + "registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.115", + "registry+https://github.com/rust-lang/crates.io-index#pyo3@0.28.3" + ] + }, + { + "ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-crypto#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#openssl@0.10.79" + ] + }, + { + "ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-keepalive#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#pyo3@0.28.3" + ] + }, + { + "ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-key-parsing#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#asn1@0.24.1", + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-crypto#0.1.0", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-openssl#0.1.0", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-x509#0.1.0", + "registry+https://github.com/rust-lang/crates.io-index#openssl@0.10.79", + "registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.115", + "registry+https://github.com/rust-lang/crates.io-index#pem@3.0.6" + ] + }, + { + "ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-openssl#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#foreign-types@0.3.2", + "registry+https://github.com/rust-lang/crates.io-index#foreign-types-shared@0.1.1", + "registry+https://github.com/rust-lang/crates.io-index#openssl@0.10.79", + "registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.115" + ] + }, + { + "ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-x509#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#asn1@0.24.1" + ] + }, + { + "ref": "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-x509-verification#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#asn1@0.24.1", + "path+file:///__w/cryptography/cryptography/tmpwheelhouse/.tmpXdQ78X/cryptography-48.0.0/src/rust/cryptography-x509#0.1.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#asn1@0.24.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#asn1_derive@0.24.1", + "registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.18" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#asn1_derive@0.24.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.45", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#base64@0.22.1" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.11.1" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#cc@1.2.61", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#find-msvc-tools@0.1.9", + "registry+https://github.com/rust-lang/crates.io-index#shlex@1.3.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#find-msvc-tools@0.1.9" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#foreign-types-shared@0.1.1" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#foreign-types@0.3.2", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#foreign-types-shared@0.1.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.18" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.186" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.4" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#openssl-macros@0.1.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.45", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.115", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cc@1.2.61", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.186", + "registry+https://github.com/rust-lang/crates.io-index#pkg-config@0.3.33", + "registry+https://github.com/rust-lang/crates.io-index#vcpkg@0.2.15" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#openssl@0.10.79", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.11.1", + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#foreign-types@0.3.2", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.186", + "registry+https://github.com/rust-lang/crates.io-index#openssl-macros@0.1.1", + "registry+https://github.com/rust-lang/crates.io-index#openssl-sys@0.9.115" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pem@3.0.6", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#base64@0.22.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pkg-config@0.3.33" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#portable-atomic@1.13.1" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.24" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.28.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.13.5" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-ffi@0.28.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.186", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.28.3" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros-backend@0.28.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0", + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.28.3", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.45", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros@0.28.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros-backend@0.28.3", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.45", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pyo3@0.28.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.186", + "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.4", + "registry+https://github.com/rust-lang/crates.io-index#portable-atomic@1.13.1", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-build-config@0.28.3", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-ffi@0.28.3", + "registry+https://github.com/rust-lang/crates.io-index#pyo3-macros@0.28.3" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.45", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#self_cell@1.2.2" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#shlex@1.3.0" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.45", + "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.24" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#target-lexicon@0.13.5" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.24" + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#vcpkg@0.2.15" + } + ] +} \ No newline at end of file diff --git a/intelliaide/vendor/cryptography-48.0.0.dist-info/sboms/sbom.json b/intelliaide/vendor/cryptography-48.0.0.dist-info/sboms/sbom.json new file mode 100644 index 0000000..f8e47c1 --- /dev/null +++ b/intelliaide/vendor/cryptography-48.0.0.dist-info/sboms/sbom.json @@ -0,0 +1,43 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "version": 1, + "serialNumber": "urn:uuid:7b8d231f-c05f-4fe0-9e12-5dba4b373c50", + "metadata": { + "timestamp": "2026-05-02T09:34:54Z" + }, + "components": [ + { + "type": "library", + "name": "openssl", + "version": "4.0.0", + "purl": "pkg:generic/openssl@4.0.0?download_url=https://github.com/openssl/openssl/releases/download/openssl-4.0.0/openssl-4.0.0.tar.gz", + "hashes": [ + { + "alg": "SHA-256", + "content": "c32cf49a959c4f345f9606982dd36e7d28f7c58b19c2e25d75624d2b3d2f79ac" + } + ], + "externalReferences": [ + { + "type": "distribution", + "url": "https://github.com/openssl/openssl/releases/download/openssl-4.0.0/openssl-4.0.0.tar.gz" + } + ], + "properties": [ + { + "name": "build:operating-system", + "value": "linux" + }, + { + "name": "build:architecture", + "value": "x86_64" + }, + { + "name": "build:flags", + "value": "no-zlib no-shared no-module no-comp no-apps no-docs no-sm2-precomp no-atexit enable-ec_nistp_64_gcc_128" + } + ] + } + ] +} diff --git a/intelliaide/vendor/cryptography/__about__.py b/intelliaide/vendor/cryptography/__about__.py new file mode 100644 index 0000000..c81901c --- /dev/null +++ b/intelliaide/vendor/cryptography/__about__.py @@ -0,0 +1,17 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +__all__ = [ + "__author__", + "__copyright__", + "__version__", +] + +__version__ = "48.0.0" + + +__author__ = "The Python Cryptographic Authority and individual contributors" +__copyright__ = f"Copyright 2013-2026 {__author__}" diff --git a/intelliaide/vendor/cryptography/__init__.py b/intelliaide/vendor/cryptography/__init__.py new file mode 100644 index 0000000..d374f75 --- /dev/null +++ b/intelliaide/vendor/cryptography/__init__.py @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.__about__ import __author__, __copyright__, __version__ + +__all__ = [ + "__author__", + "__copyright__", + "__version__", +] diff --git a/intelliaide/vendor/cryptography/exceptions.py b/intelliaide/vendor/cryptography/exceptions.py new file mode 100644 index 0000000..fe125ea --- /dev/null +++ b/intelliaide/vendor/cryptography/exceptions.py @@ -0,0 +1,52 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import typing + +from cryptography.hazmat.bindings._rust import exceptions as rust_exceptions + +if typing.TYPE_CHECKING: + from cryptography.hazmat.bindings._rust import openssl as rust_openssl + +_Reasons = rust_exceptions._Reasons + + +class UnsupportedAlgorithm(Exception): + def __init__(self, message: str, reason: _Reasons | None = None) -> None: + super().__init__(message) + self._reason = reason + + +class AlreadyFinalized(Exception): + pass + + +class AlreadyUpdated(Exception): + pass + + +class NotYetFinalized(Exception): + pass + + +class InvalidTag(Exception): + pass + + +class InvalidSignature(Exception): + pass + + +class InternalError(Exception): + def __init__( + self, msg: str, err_code: list[rust_openssl.OpenSSLError] + ) -> None: + super().__init__(msg) + self.err_code = err_code + + +class InvalidKey(Exception): + pass diff --git a/intelliaide/vendor/cryptography/fernet.py b/intelliaide/vendor/cryptography/fernet.py new file mode 100644 index 0000000..c6744ae --- /dev/null +++ b/intelliaide/vendor/cryptography/fernet.py @@ -0,0 +1,224 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import base64 +import binascii +import os +import time +import typing +from collections.abc import Iterable + +from cryptography import utils +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives import hashes, padding +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives.hmac import HMAC + + +class InvalidToken(Exception): + pass + + +_MAX_CLOCK_SKEW = 60 + + +class Fernet: + def __init__( + self, + key: bytes | str, + backend: typing.Any = None, + ) -> None: + try: + key = base64.urlsafe_b64decode(key) + except binascii.Error as exc: + raise ValueError( + "Fernet key must be 32 url-safe base64-encoded bytes." + ) from exc + if len(key) != 32: + raise ValueError( + "Fernet key must be 32 url-safe base64-encoded bytes." + ) + + self._signing_key = key[:16] + self._encryption_key = key[16:] + + @classmethod + def generate_key(cls) -> bytes: + return base64.urlsafe_b64encode(os.urandom(32)) + + def encrypt(self, data: bytes) -> bytes: + return self.encrypt_at_time(data, int(time.time())) + + def encrypt_at_time(self, data: bytes, current_time: int) -> bytes: + iv = os.urandom(16) + return self._encrypt_from_parts(data, current_time, iv) + + def _encrypt_from_parts( + self, data: bytes, current_time: int, iv: bytes + ) -> bytes: + utils._check_bytes("data", data) + + padder = padding.PKCS7(algorithms.AES.block_size).padder() + padded_data = padder.update(data) + padder.finalize() + encryptor = Cipher( + algorithms.AES(self._encryption_key), + modes.CBC(iv), + ).encryptor() + ciphertext = encryptor.update(padded_data) + encryptor.finalize() + + basic_parts = ( + b"\x80" + + current_time.to_bytes(length=8, byteorder="big") + + iv + + ciphertext + ) + + h = HMAC(self._signing_key, hashes.SHA256()) + h.update(basic_parts) + hmac = h.finalize() + return base64.urlsafe_b64encode(basic_parts + hmac) + + def decrypt(self, token: bytes | str, ttl: int | None = None) -> bytes: + timestamp, data = Fernet._get_unverified_token_data(token) + if ttl is None: + time_info = None + else: + time_info = (ttl, int(time.time())) + return self._decrypt_data(data, timestamp, time_info) + + def decrypt_at_time( + self, token: bytes | str, ttl: int, current_time: int + ) -> bytes: + if ttl is None: + raise ValueError( + "decrypt_at_time() can only be used with a non-None ttl" + ) + timestamp, data = Fernet._get_unverified_token_data(token) + return self._decrypt_data(data, timestamp, (ttl, current_time)) + + def extract_timestamp(self, token: bytes | str) -> int: + timestamp, data = Fernet._get_unverified_token_data(token) + # Verify the token was not tampered with. + self._verify_signature(data) + return timestamp + + @staticmethod + def _get_unverified_token_data(token: bytes | str) -> tuple[int, bytes]: + if not isinstance(token, (str, bytes)): + raise TypeError("token must be bytes or str") + + try: + data = base64.urlsafe_b64decode(token) + except (TypeError, binascii.Error): + raise InvalidToken + + if not data or data[0] != 0x80: + raise InvalidToken + + if len(data) < 9: + raise InvalidToken + + timestamp = int.from_bytes(data[1:9], byteorder="big") + return timestamp, data + + def _verify_signature(self, data: bytes) -> None: + h = HMAC(self._signing_key, hashes.SHA256()) + h.update(data[:-32]) + try: + h.verify(data[-32:]) + except InvalidSignature: + raise InvalidToken + + def _decrypt_data( + self, + data: bytes, + timestamp: int, + time_info: tuple[int, int] | None, + ) -> bytes: + if time_info is not None: + ttl, current_time = time_info + if timestamp + ttl < current_time: + raise InvalidToken + + if current_time + _MAX_CLOCK_SKEW < timestamp: + raise InvalidToken + + self._verify_signature(data) + + iv = data[9:25] + ciphertext = data[25:-32] + decryptor = Cipher( + algorithms.AES(self._encryption_key), modes.CBC(iv) + ).decryptor() + plaintext_padded = decryptor.update(ciphertext) + try: + plaintext_padded += decryptor.finalize() + except ValueError: + raise InvalidToken + unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() + + unpadded = unpadder.update(plaintext_padded) + try: + unpadded += unpadder.finalize() + except ValueError: + raise InvalidToken + return unpadded + + +class MultiFernet: + def __init__(self, fernets: Iterable[Fernet]): + fernets = list(fernets) + if not fernets: + raise ValueError( + "MultiFernet requires at least one Fernet instance" + ) + self._fernets = fernets + + def encrypt(self, msg: bytes) -> bytes: + return self.encrypt_at_time(msg, int(time.time())) + + def encrypt_at_time(self, msg: bytes, current_time: int) -> bytes: + return self._fernets[0].encrypt_at_time(msg, current_time) + + def rotate(self, msg: bytes | str) -> bytes: + timestamp, data = Fernet._get_unverified_token_data(msg) + for f in self._fernets: + try: + p = f._decrypt_data(data, timestamp, None) + break + except InvalidToken: + pass + else: + raise InvalidToken + + iv = os.urandom(16) + return self._fernets[0]._encrypt_from_parts(p, timestamp, iv) + + def decrypt(self, msg: bytes | str, ttl: int | None = None) -> bytes: + for f in self._fernets: + try: + return f.decrypt(msg, ttl) + except InvalidToken: + pass + raise InvalidToken + + def decrypt_at_time( + self, msg: bytes | str, ttl: int, current_time: int + ) -> bytes: + for f in self._fernets: + try: + return f.decrypt_at_time(msg, ttl, current_time) + except InvalidToken: + pass + raise InvalidToken + + def extract_timestamp(self, msg: bytes | str) -> int: + for f in self._fernets: + try: + return f.extract_timestamp(msg) + except InvalidToken: + pass + raise InvalidToken diff --git a/intelliaide/vendor/cryptography/hazmat/__init__.py b/intelliaide/vendor/cryptography/hazmat/__init__.py new file mode 100644 index 0000000..b9f1187 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/__init__.py @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +""" +Hazardous Materials + +This is a "Hazardous Materials" module. You should ONLY use it if you're +100% absolutely sure that you know what you're doing because this module +is full of land mines, dragons, and dinosaurs with laser guns. +""" diff --git a/intelliaide/vendor/cryptography/hazmat/_oid.py b/intelliaide/vendor/cryptography/hazmat/_oid.py new file mode 100644 index 0000000..4bf138d --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/_oid.py @@ -0,0 +1,356 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import ( + ObjectIdentifier as ObjectIdentifier, +) +from cryptography.hazmat.primitives import hashes + + +class ExtensionOID: + SUBJECT_DIRECTORY_ATTRIBUTES = ObjectIdentifier("2.5.29.9") + SUBJECT_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.14") + KEY_USAGE = ObjectIdentifier("2.5.29.15") + PRIVATE_KEY_USAGE_PERIOD = ObjectIdentifier("2.5.29.16") + SUBJECT_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.17") + ISSUER_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.18") + BASIC_CONSTRAINTS = ObjectIdentifier("2.5.29.19") + NAME_CONSTRAINTS = ObjectIdentifier("2.5.29.30") + CRL_DISTRIBUTION_POINTS = ObjectIdentifier("2.5.29.31") + CERTIFICATE_POLICIES = ObjectIdentifier("2.5.29.32") + POLICY_MAPPINGS = ObjectIdentifier("2.5.29.33") + AUTHORITY_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.35") + POLICY_CONSTRAINTS = ObjectIdentifier("2.5.29.36") + EXTENDED_KEY_USAGE = ObjectIdentifier("2.5.29.37") + FRESHEST_CRL = ObjectIdentifier("2.5.29.46") + INHIBIT_ANY_POLICY = ObjectIdentifier("2.5.29.54") + ISSUING_DISTRIBUTION_POINT = ObjectIdentifier("2.5.29.28") + AUTHORITY_INFORMATION_ACCESS = ObjectIdentifier("1.3.6.1.5.5.7.1.1") + SUBJECT_INFORMATION_ACCESS = ObjectIdentifier("1.3.6.1.5.5.7.1.11") + OCSP_NO_CHECK = ObjectIdentifier("1.3.6.1.5.5.7.48.1.5") + TLS_FEATURE = ObjectIdentifier("1.3.6.1.5.5.7.1.24") + CRL_NUMBER = ObjectIdentifier("2.5.29.20") + DELTA_CRL_INDICATOR = ObjectIdentifier("2.5.29.27") + PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS = ObjectIdentifier( + "1.3.6.1.4.1.11129.2.4.2" + ) + PRECERT_POISON = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.3") + SIGNED_CERTIFICATE_TIMESTAMPS = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.5") + MS_CERTIFICATE_TEMPLATE = ObjectIdentifier("1.3.6.1.4.1.311.21.7") + ADMISSIONS = ObjectIdentifier("1.3.36.8.3.3") + + +class OCSPExtensionOID: + NONCE = ObjectIdentifier("1.3.6.1.5.5.7.48.1.2") + ACCEPTABLE_RESPONSES = ObjectIdentifier("1.3.6.1.5.5.7.48.1.4") + + +class CRLEntryExtensionOID: + CERTIFICATE_ISSUER = ObjectIdentifier("2.5.29.29") + CRL_REASON = ObjectIdentifier("2.5.29.21") + INVALIDITY_DATE = ObjectIdentifier("2.5.29.24") + + +class NameOID: + COMMON_NAME = ObjectIdentifier("2.5.4.3") + COUNTRY_NAME = ObjectIdentifier("2.5.4.6") + LOCALITY_NAME = ObjectIdentifier("2.5.4.7") + STATE_OR_PROVINCE_NAME = ObjectIdentifier("2.5.4.8") + STREET_ADDRESS = ObjectIdentifier("2.5.4.9") + ORGANIZATION_IDENTIFIER = ObjectIdentifier("2.5.4.97") + ORGANIZATION_NAME = ObjectIdentifier("2.5.4.10") + ORGANIZATIONAL_UNIT_NAME = ObjectIdentifier("2.5.4.11") + SERIAL_NUMBER = ObjectIdentifier("2.5.4.5") + SURNAME = ObjectIdentifier("2.5.4.4") + GIVEN_NAME = ObjectIdentifier("2.5.4.42") + TITLE = ObjectIdentifier("2.5.4.12") + INITIALS = ObjectIdentifier("2.5.4.43") + GENERATION_QUALIFIER = ObjectIdentifier("2.5.4.44") + X500_UNIQUE_IDENTIFIER = ObjectIdentifier("2.5.4.45") + DN_QUALIFIER = ObjectIdentifier("2.5.4.46") + PSEUDONYM = ObjectIdentifier("2.5.4.65") + USER_ID = ObjectIdentifier("0.9.2342.19200300.100.1.1") + DOMAIN_COMPONENT = ObjectIdentifier("0.9.2342.19200300.100.1.25") + EMAIL_ADDRESS = ObjectIdentifier("1.2.840.113549.1.9.1") + JURISDICTION_COUNTRY_NAME = ObjectIdentifier("1.3.6.1.4.1.311.60.2.1.3") + JURISDICTION_LOCALITY_NAME = ObjectIdentifier("1.3.6.1.4.1.311.60.2.1.1") + JURISDICTION_STATE_OR_PROVINCE_NAME = ObjectIdentifier( + "1.3.6.1.4.1.311.60.2.1.2" + ) + BUSINESS_CATEGORY = ObjectIdentifier("2.5.4.15") + POSTAL_ADDRESS = ObjectIdentifier("2.5.4.16") + POSTAL_CODE = ObjectIdentifier("2.5.4.17") + INN = ObjectIdentifier("1.2.643.3.131.1.1") + OGRN = ObjectIdentifier("1.2.643.100.1") + SNILS = ObjectIdentifier("1.2.643.100.3") + UNSTRUCTURED_NAME = ObjectIdentifier("1.2.840.113549.1.9.2") + + +class SignatureAlgorithmOID: + RSA_WITH_MD5 = ObjectIdentifier("1.2.840.113549.1.1.4") + RSA_WITH_SHA1 = ObjectIdentifier("1.2.840.113549.1.1.5") + # This is an alternate OID for RSA with SHA1 that is occasionally seen + _RSA_WITH_SHA1 = ObjectIdentifier("1.3.14.3.2.29") + RSA_WITH_SHA224 = ObjectIdentifier("1.2.840.113549.1.1.14") + RSA_WITH_SHA256 = ObjectIdentifier("1.2.840.113549.1.1.11") + RSA_WITH_SHA384 = ObjectIdentifier("1.2.840.113549.1.1.12") + RSA_WITH_SHA512 = ObjectIdentifier("1.2.840.113549.1.1.13") + RSA_WITH_SHA3_224 = ObjectIdentifier("2.16.840.1.101.3.4.3.13") + RSA_WITH_SHA3_256 = ObjectIdentifier("2.16.840.1.101.3.4.3.14") + RSA_WITH_SHA3_384 = ObjectIdentifier("2.16.840.1.101.3.4.3.15") + RSA_WITH_SHA3_512 = ObjectIdentifier("2.16.840.1.101.3.4.3.16") + RSASSA_PSS = ObjectIdentifier("1.2.840.113549.1.1.10") + ECDSA_WITH_SHA1 = ObjectIdentifier("1.2.840.10045.4.1") + ECDSA_WITH_SHA224 = ObjectIdentifier("1.2.840.10045.4.3.1") + ECDSA_WITH_SHA256 = ObjectIdentifier("1.2.840.10045.4.3.2") + ECDSA_WITH_SHA384 = ObjectIdentifier("1.2.840.10045.4.3.3") + ECDSA_WITH_SHA512 = ObjectIdentifier("1.2.840.10045.4.3.4") + ECDSA_WITH_SHA3_224 = ObjectIdentifier("2.16.840.1.101.3.4.3.9") + ECDSA_WITH_SHA3_256 = ObjectIdentifier("2.16.840.1.101.3.4.3.10") + ECDSA_WITH_SHA3_384 = ObjectIdentifier("2.16.840.1.101.3.4.3.11") + ECDSA_WITH_SHA3_512 = ObjectIdentifier("2.16.840.1.101.3.4.3.12") + DSA_WITH_SHA1 = ObjectIdentifier("1.2.840.10040.4.3") + DSA_WITH_SHA224 = ObjectIdentifier("2.16.840.1.101.3.4.3.1") + DSA_WITH_SHA256 = ObjectIdentifier("2.16.840.1.101.3.4.3.2") + DSA_WITH_SHA384 = ObjectIdentifier("2.16.840.1.101.3.4.3.3") + DSA_WITH_SHA512 = ObjectIdentifier("2.16.840.1.101.3.4.3.4") + ED25519 = ObjectIdentifier("1.3.101.112") + ED448 = ObjectIdentifier("1.3.101.113") + GOSTR3411_94_WITH_3410_2001 = ObjectIdentifier("1.2.643.2.2.3") + GOSTR3410_2012_WITH_3411_2012_256 = ObjectIdentifier("1.2.643.7.1.1.3.2") + GOSTR3410_2012_WITH_3411_2012_512 = ObjectIdentifier("1.2.643.7.1.1.3.3") + + +_SIG_OIDS_TO_HASH: dict[ObjectIdentifier, hashes.HashAlgorithm | None] = { + SignatureAlgorithmOID.RSA_WITH_MD5: hashes.MD5(), + SignatureAlgorithmOID.RSA_WITH_SHA1: hashes.SHA1(), + SignatureAlgorithmOID._RSA_WITH_SHA1: hashes.SHA1(), + SignatureAlgorithmOID.RSA_WITH_SHA224: hashes.SHA224(), + SignatureAlgorithmOID.RSA_WITH_SHA256: hashes.SHA256(), + SignatureAlgorithmOID.RSA_WITH_SHA384: hashes.SHA384(), + SignatureAlgorithmOID.RSA_WITH_SHA512: hashes.SHA512(), + SignatureAlgorithmOID.RSA_WITH_SHA3_224: hashes.SHA3_224(), + SignatureAlgorithmOID.RSA_WITH_SHA3_256: hashes.SHA3_256(), + SignatureAlgorithmOID.RSA_WITH_SHA3_384: hashes.SHA3_384(), + SignatureAlgorithmOID.RSA_WITH_SHA3_512: hashes.SHA3_512(), + SignatureAlgorithmOID.ECDSA_WITH_SHA1: hashes.SHA1(), + SignatureAlgorithmOID.ECDSA_WITH_SHA224: hashes.SHA224(), + SignatureAlgorithmOID.ECDSA_WITH_SHA256: hashes.SHA256(), + SignatureAlgorithmOID.ECDSA_WITH_SHA384: hashes.SHA384(), + SignatureAlgorithmOID.ECDSA_WITH_SHA512: hashes.SHA512(), + SignatureAlgorithmOID.ECDSA_WITH_SHA3_224: hashes.SHA3_224(), + SignatureAlgorithmOID.ECDSA_WITH_SHA3_256: hashes.SHA3_256(), + SignatureAlgorithmOID.ECDSA_WITH_SHA3_384: hashes.SHA3_384(), + SignatureAlgorithmOID.ECDSA_WITH_SHA3_512: hashes.SHA3_512(), + SignatureAlgorithmOID.DSA_WITH_SHA1: hashes.SHA1(), + SignatureAlgorithmOID.DSA_WITH_SHA224: hashes.SHA224(), + SignatureAlgorithmOID.DSA_WITH_SHA256: hashes.SHA256(), + SignatureAlgorithmOID.ED25519: None, + SignatureAlgorithmOID.ED448: None, + SignatureAlgorithmOID.GOSTR3411_94_WITH_3410_2001: None, + SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_256: None, + SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_512: None, +} + + +class HashAlgorithmOID: + SHA1 = ObjectIdentifier("1.3.14.3.2.26") + SHA224 = ObjectIdentifier("2.16.840.1.101.3.4.2.4") + SHA256 = ObjectIdentifier("2.16.840.1.101.3.4.2.1") + SHA384 = ObjectIdentifier("2.16.840.1.101.3.4.2.2") + SHA512 = ObjectIdentifier("2.16.840.1.101.3.4.2.3") + SHA3_224 = ObjectIdentifier("1.3.6.1.4.1.37476.3.2.1.99.7.224") + SHA3_256 = ObjectIdentifier("1.3.6.1.4.1.37476.3.2.1.99.7.256") + SHA3_384 = ObjectIdentifier("1.3.6.1.4.1.37476.3.2.1.99.7.384") + SHA3_512 = ObjectIdentifier("1.3.6.1.4.1.37476.3.2.1.99.7.512") + SHA3_224_NIST = ObjectIdentifier("2.16.840.1.101.3.4.2.7") + SHA3_256_NIST = ObjectIdentifier("2.16.840.1.101.3.4.2.8") + SHA3_384_NIST = ObjectIdentifier("2.16.840.1.101.3.4.2.9") + SHA3_512_NIST = ObjectIdentifier("2.16.840.1.101.3.4.2.10") + + +class PublicKeyAlgorithmOID: + DSA = ObjectIdentifier("1.2.840.10040.4.1") + EC_PUBLIC_KEY = ObjectIdentifier("1.2.840.10045.2.1") + RSAES_PKCS1_v1_5 = ObjectIdentifier("1.2.840.113549.1.1.1") + RSASSA_PSS = ObjectIdentifier("1.2.840.113549.1.1.10") + X25519 = ObjectIdentifier("1.3.101.110") + X448 = ObjectIdentifier("1.3.101.111") + ED25519 = ObjectIdentifier("1.3.101.112") + ED448 = ObjectIdentifier("1.3.101.113") + + +class ExtendedKeyUsageOID: + SERVER_AUTH = ObjectIdentifier("1.3.6.1.5.5.7.3.1") + CLIENT_AUTH = ObjectIdentifier("1.3.6.1.5.5.7.3.2") + CODE_SIGNING = ObjectIdentifier("1.3.6.1.5.5.7.3.3") + EMAIL_PROTECTION = ObjectIdentifier("1.3.6.1.5.5.7.3.4") + TIME_STAMPING = ObjectIdentifier("1.3.6.1.5.5.7.3.8") + OCSP_SIGNING = ObjectIdentifier("1.3.6.1.5.5.7.3.9") + ANY_EXTENDED_KEY_USAGE = ObjectIdentifier("2.5.29.37.0") + SMARTCARD_LOGON = ObjectIdentifier("1.3.6.1.4.1.311.20.2.2") + KERBEROS_PKINIT_KDC = ObjectIdentifier("1.3.6.1.5.2.3.5") + IPSEC_IKE = ObjectIdentifier("1.3.6.1.5.5.7.3.17") + BUNDLE_SECURITY = ObjectIdentifier("1.3.6.1.5.5.7.3.35") + CERTIFICATE_TRANSPARENCY = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.4") + + +class OtherNameFormOID: + PERMANENT_IDENTIFIER = ObjectIdentifier("1.3.6.1.5.5.7.8.3") + HW_MODULE_NAME = ObjectIdentifier("1.3.6.1.5.5.7.8.4") + DNS_SRV = ObjectIdentifier("1.3.6.1.5.5.7.8.7") + NAI_REALM = ObjectIdentifier("1.3.6.1.5.5.7.8.8") + SMTP_UTF8_MAILBOX = ObjectIdentifier("1.3.6.1.5.5.7.8.9") + ACP_NODE_NAME = ObjectIdentifier("1.3.6.1.5.5.7.8.10") + BUNDLE_EID = ObjectIdentifier("1.3.6.1.5.5.7.8.11") + + +class AuthorityInformationAccessOID: + CA_ISSUERS = ObjectIdentifier("1.3.6.1.5.5.7.48.2") + OCSP = ObjectIdentifier("1.3.6.1.5.5.7.48.1") + + +class SubjectInformationAccessOID: + CA_REPOSITORY = ObjectIdentifier("1.3.6.1.5.5.7.48.5") + + +class CertificatePoliciesOID: + CPS_QUALIFIER = ObjectIdentifier("1.3.6.1.5.5.7.2.1") + CPS_USER_NOTICE = ObjectIdentifier("1.3.6.1.5.5.7.2.2") + ANY_POLICY = ObjectIdentifier("2.5.29.32.0") + + +class AttributeOID: + CHALLENGE_PASSWORD = ObjectIdentifier("1.2.840.113549.1.9.7") + UNSTRUCTURED_NAME = ObjectIdentifier("1.2.840.113549.1.9.2") + + +_OID_NAMES = { + NameOID.COMMON_NAME: "commonName", + NameOID.COUNTRY_NAME: "countryName", + NameOID.LOCALITY_NAME: "localityName", + NameOID.STATE_OR_PROVINCE_NAME: "stateOrProvinceName", + NameOID.STREET_ADDRESS: "streetAddress", + NameOID.ORGANIZATION_NAME: "organizationName", + NameOID.ORGANIZATIONAL_UNIT_NAME: "organizationalUnitName", + NameOID.SERIAL_NUMBER: "serialNumber", + NameOID.SURNAME: "surname", + NameOID.GIVEN_NAME: "givenName", + NameOID.TITLE: "title", + NameOID.GENERATION_QUALIFIER: "generationQualifier", + NameOID.X500_UNIQUE_IDENTIFIER: "x500UniqueIdentifier", + NameOID.DN_QUALIFIER: "dnQualifier", + NameOID.PSEUDONYM: "pseudonym", + NameOID.USER_ID: "userID", + NameOID.DOMAIN_COMPONENT: "domainComponent", + NameOID.EMAIL_ADDRESS: "emailAddress", + NameOID.JURISDICTION_COUNTRY_NAME: "jurisdictionCountryName", + NameOID.JURISDICTION_LOCALITY_NAME: "jurisdictionLocalityName", + NameOID.JURISDICTION_STATE_OR_PROVINCE_NAME: ( + "jurisdictionStateOrProvinceName" + ), + NameOID.BUSINESS_CATEGORY: "businessCategory", + NameOID.POSTAL_ADDRESS: "postalAddress", + NameOID.POSTAL_CODE: "postalCode", + NameOID.INN: "INN", + NameOID.OGRN: "OGRN", + NameOID.SNILS: "SNILS", + NameOID.UNSTRUCTURED_NAME: "unstructuredName", + SignatureAlgorithmOID.RSA_WITH_MD5: "md5WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA1: "sha1WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA224: "sha224WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA256: "sha256WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA384: "sha384WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA512: "sha512WithRSAEncryption", + SignatureAlgorithmOID.RSASSA_PSS: "rsassaPss", + SignatureAlgorithmOID.ECDSA_WITH_SHA1: "ecdsa-with-SHA1", + SignatureAlgorithmOID.ECDSA_WITH_SHA224: "ecdsa-with-SHA224", + SignatureAlgorithmOID.ECDSA_WITH_SHA256: "ecdsa-with-SHA256", + SignatureAlgorithmOID.ECDSA_WITH_SHA384: "ecdsa-with-SHA384", + SignatureAlgorithmOID.ECDSA_WITH_SHA512: "ecdsa-with-SHA512", + SignatureAlgorithmOID.DSA_WITH_SHA1: "dsa-with-sha1", + SignatureAlgorithmOID.DSA_WITH_SHA224: "dsa-with-sha224", + SignatureAlgorithmOID.DSA_WITH_SHA256: "dsa-with-sha256", + SignatureAlgorithmOID.ED25519: "ed25519", + SignatureAlgorithmOID.ED448: "ed448", + SignatureAlgorithmOID.GOSTR3411_94_WITH_3410_2001: ( + "GOST R 34.11-94 with GOST R 34.10-2001" + ), + SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_256: ( + "GOST R 34.10-2012 with GOST R 34.11-2012 (256 bit)" + ), + SignatureAlgorithmOID.GOSTR3410_2012_WITH_3411_2012_512: ( + "GOST R 34.10-2012 with GOST R 34.11-2012 (512 bit)" + ), + HashAlgorithmOID.SHA1: "sha1", + HashAlgorithmOID.SHA224: "sha224", + HashAlgorithmOID.SHA256: "sha256", + HashAlgorithmOID.SHA384: "sha384", + HashAlgorithmOID.SHA512: "sha512", + HashAlgorithmOID.SHA3_224: "sha3_224", + HashAlgorithmOID.SHA3_256: "sha3_256", + HashAlgorithmOID.SHA3_384: "sha3_384", + HashAlgorithmOID.SHA3_512: "sha3_512", + HashAlgorithmOID.SHA3_224_NIST: "sha3_224", + HashAlgorithmOID.SHA3_256_NIST: "sha3_256", + HashAlgorithmOID.SHA3_384_NIST: "sha3_384", + HashAlgorithmOID.SHA3_512_NIST: "sha3_512", + PublicKeyAlgorithmOID.DSA: "dsaEncryption", + PublicKeyAlgorithmOID.EC_PUBLIC_KEY: "id-ecPublicKey", + PublicKeyAlgorithmOID.RSAES_PKCS1_v1_5: "rsaEncryption", + PublicKeyAlgorithmOID.X25519: "X25519", + PublicKeyAlgorithmOID.X448: "X448", + ExtendedKeyUsageOID.SERVER_AUTH: "serverAuth", + ExtendedKeyUsageOID.CLIENT_AUTH: "clientAuth", + ExtendedKeyUsageOID.CODE_SIGNING: "codeSigning", + ExtendedKeyUsageOID.EMAIL_PROTECTION: "emailProtection", + ExtendedKeyUsageOID.TIME_STAMPING: "timeStamping", + ExtendedKeyUsageOID.OCSP_SIGNING: "OCSPSigning", + ExtendedKeyUsageOID.SMARTCARD_LOGON: "msSmartcardLogin", + ExtendedKeyUsageOID.KERBEROS_PKINIT_KDC: "pkInitKDC", + ExtensionOID.SUBJECT_DIRECTORY_ATTRIBUTES: "subjectDirectoryAttributes", + ExtensionOID.SUBJECT_KEY_IDENTIFIER: "subjectKeyIdentifier", + ExtensionOID.KEY_USAGE: "keyUsage", + ExtensionOID.PRIVATE_KEY_USAGE_PERIOD: "privateKeyUsagePeriod", + ExtensionOID.SUBJECT_ALTERNATIVE_NAME: "subjectAltName", + ExtensionOID.ISSUER_ALTERNATIVE_NAME: "issuerAltName", + ExtensionOID.BASIC_CONSTRAINTS: "basicConstraints", + ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS: ( + "signedCertificateTimestampList" + ), + ExtensionOID.SIGNED_CERTIFICATE_TIMESTAMPS: ( + "signedCertificateTimestampList" + ), + ExtensionOID.PRECERT_POISON: "ctPoison", + ExtensionOID.MS_CERTIFICATE_TEMPLATE: "msCertificateTemplate", + ExtensionOID.ADMISSIONS: "Admissions", + CRLEntryExtensionOID.CRL_REASON: "cRLReason", + CRLEntryExtensionOID.INVALIDITY_DATE: "invalidityDate", + CRLEntryExtensionOID.CERTIFICATE_ISSUER: "certificateIssuer", + ExtensionOID.NAME_CONSTRAINTS: "nameConstraints", + ExtensionOID.CRL_DISTRIBUTION_POINTS: "cRLDistributionPoints", + ExtensionOID.CERTIFICATE_POLICIES: "certificatePolicies", + ExtensionOID.POLICY_MAPPINGS: "policyMappings", + ExtensionOID.AUTHORITY_KEY_IDENTIFIER: "authorityKeyIdentifier", + ExtensionOID.POLICY_CONSTRAINTS: "policyConstraints", + ExtensionOID.EXTENDED_KEY_USAGE: "extendedKeyUsage", + ExtensionOID.FRESHEST_CRL: "freshestCRL", + ExtensionOID.INHIBIT_ANY_POLICY: "inhibitAnyPolicy", + ExtensionOID.ISSUING_DISTRIBUTION_POINT: "issuingDistributionPoint", + ExtensionOID.AUTHORITY_INFORMATION_ACCESS: "authorityInfoAccess", + ExtensionOID.SUBJECT_INFORMATION_ACCESS: "subjectInfoAccess", + ExtensionOID.OCSP_NO_CHECK: "OCSPNoCheck", + ExtensionOID.CRL_NUMBER: "cRLNumber", + ExtensionOID.DELTA_CRL_INDICATOR: "deltaCRLIndicator", + ExtensionOID.TLS_FEATURE: "TLSFeature", + AuthorityInformationAccessOID.OCSP: "OCSP", + AuthorityInformationAccessOID.CA_ISSUERS: "caIssuers", + SubjectInformationAccessOID.CA_REPOSITORY: "caRepository", + CertificatePoliciesOID.CPS_QUALIFIER: "id-qt-cps", + CertificatePoliciesOID.CPS_USER_NOTICE: "id-qt-unotice", + OCSPExtensionOID.NONCE: "OCSPNonce", + AttributeOID.CHALLENGE_PASSWORD: "challengePassword", +} diff --git a/intelliaide/vendor/cryptography/hazmat/asn1/__init__.py b/intelliaide/vendor/cryptography/hazmat/asn1/__init__.py new file mode 100644 index 0000000..ac3d4bd --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/asn1/__init__.py @@ -0,0 +1,43 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.hazmat.asn1.asn1 import ( + TLV, + BitString, + Default, + Explicit, + GeneralizedTime, + IA5String, + Implicit, + Null, + PrintableString, + SetOf, + Size, + UTCTime, + Variant, + decode_der, + encode_der, + sequence, + set, +) + +__all__ = [ + "TLV", + "BitString", + "Default", + "Explicit", + "GeneralizedTime", + "IA5String", + "Implicit", + "Null", + "PrintableString", + "SetOf", + "Size", + "UTCTime", + "Variant", + "decode_der", + "encode_der", + "sequence", + "set", +] diff --git a/intelliaide/vendor/cryptography/hazmat/asn1/asn1.py b/intelliaide/vendor/cryptography/hazmat/asn1/asn1.py new file mode 100644 index 0000000..aa18d7e --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/asn1/asn1.py @@ -0,0 +1,419 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import builtins +import dataclasses +import sys +import types +import typing + +if sys.version_info < (3, 11): + import typing_extensions + + LiteralString = typing_extensions.LiteralString +else: + LiteralString = typing.LiteralString + +from cryptography.hazmat.bindings._rust import declarative_asn1 + +if sys.version_info < (3, 10): + NoneType = type(None) +else: + NoneType = types.NoneType # type: ignore[nonetype-type] + +T = typing.TypeVar("T", covariant=True) +U = typing.TypeVar("U") +Tag = typing.TypeVar("Tag", bound=LiteralString) + + +@dataclasses.dataclass(frozen=True) +class Variant(typing.Generic[U, Tag]): + """ + A tagged variant for CHOICE fields with the same underlying type. + + Use this when you have multiple CHOICE alternatives with the same type + and need to distinguish between them: + + foo: ( + Annotated[Variant[int, typing.Literal["IntA"]], Implicit(0)] + | Annotated[Variant[int, typing.Literal["IntB"]], Implicit(1)] + ) + + Usage: + example = Example(foo=Variant(5, "IntA")) + decoded.foo.value # The int value + decoded.foo.tag # "IntA" or "IntB" + """ + + value: U + tag: str + + +decode_der = declarative_asn1.decode_der +encode_der = declarative_asn1.encode_der + + +def _is_union(field_type: type) -> bool: + # NOTE: types.UnionType for `T | U`, typing.Union for `Union[T, U]`. + # TODO: Drop the `hasattr()` once the minimum supported Python version + # is >= 3.10. + union_types = ( + (types.UnionType, typing.Union) + if hasattr(types, "UnionType") + else (typing.Union,) + ) + return typing.get_origin(field_type) in union_types + + +def _extract_annotation( + metadata: tuple, field_name: str +) -> declarative_asn1.Annotation: + default = None + encoding = None + size = None + for raw_annotation in metadata: + if isinstance(raw_annotation, Default): + if default is not None: + raise TypeError( + f"multiple DEFAULT annotations found in field " + f"'{field_name}'" + ) + default = raw_annotation.value + elif isinstance(raw_annotation, declarative_asn1.Encoding): + if encoding is not None: + raise TypeError( + f"multiple IMPLICIT/EXPLICIT annotations found in field " + f"'{field_name}'" + ) + encoding = raw_annotation + elif isinstance(raw_annotation, declarative_asn1.Size): + if size is not None: + raise TypeError( + f"multiple SIZE annotations found in field '{field_name}'" + ) + size = raw_annotation + else: + raise TypeError(f"unsupported annotation: {raw_annotation}") + + return declarative_asn1.Annotation( + default=default, encoding=encoding, size=size + ) + + +def _normalize_field_type( + field_type: typing.Any, field_name: str +) -> declarative_asn1.AnnotatedType: + # Strip the `Annotated[...]` off, and populate the annotation + # from it if it exists. + if typing.get_origin(field_type) is typing.Annotated: + annotation = _extract_annotation(field_type.__metadata__, field_name) + field_type, *_ = typing.get_args(field_type) + else: + annotation = declarative_asn1.Annotation() + + if annotation.size is not None and ( + typing.get_origin(field_type) not in (builtins.list, SetOf) + and field_type + not in ( + builtins.bytes, + builtins.str, + BitString, + IA5String, + PrintableString, + ) + ): + raise TypeError( + f"field '{field_name}' has a SIZE annotation, but SIZE " + "annotations are only supported for fields of types: " + "[SEQUENCE OF, SET OF, BIT STRING, OCTET STRING, UTF8String, " + "PrintableString, IA5String]" + ) + + if field_type is TLV: + if isinstance(annotation.encoding, Implicit): + raise TypeError( + f"field '{field_name}' has an IMPLICIT annotation, but " + "IMPLICIT annotations are not supported for TLV types." + ) + elif annotation.default is not None: + raise TypeError( + f"field '{field_name}' has a DEFAULT annotation, but " + "DEFAULT annotations are not supported for TLV types." + ) + + if hasattr(field_type, "__asn1_root__"): + root_type = field_type.__asn1_root__ + if not isinstance( + root_type, + (declarative_asn1.Type.Sequence, declarative_asn1.Type.Set), + ): + raise TypeError(f"unsupported root type: {root_type}") + return declarative_asn1.AnnotatedType( + typing.cast(declarative_asn1.Type, root_type), annotation + ) + elif _is_union(field_type): + union_args = typing.get_args(field_type) + if len(union_args) == 2 and NoneType in union_args: + # A Union between a type and None is an OPTIONAL + optional_type = ( + union_args[0] if union_args[1] is type(None) else union_args[1] + ) + if optional_type is TLV: + raise TypeError( + "optional TLV types (`TLV | None`) are not " + "currently supported" + ) + annotated_type = _normalize_field_type(optional_type, field_name) + + if not annotated_type.annotation.is_empty(): + raise TypeError( + "optional (`X | None`) types cannot have `X` " + "annotated: annotations must apply to the union " + "(i.e: `Annotated[X | None, annotation]`)" + ) + + if annotation.default is not None: + raise TypeError( + "optional (`X | None`) types should not have a DEFAULT " + "annotation" + ) + + rust_field_type = declarative_asn1.Type.Option(annotated_type) + + else: + # Otherwise, the Union is a CHOICE + if isinstance(annotation.encoding, Implicit): + # CHOICEs cannot be IMPLICIT. See X.680 section 31.2.9. + raise TypeError( + "CHOICE (`X | Y | ...`) types should not have an IMPLICIT " + "annotation" + ) + variants = [ + _type_to_variant(arg, field_name) + for arg in union_args + if arg is not type(None) + ] + + # Union types should either be all Variants + # (`Variant[..] | Variant[..] | etc`) or all non Variants + are_union_types_tagged = variants[0].tag_name is not None + if any( + (v.tag_name is not None) != are_union_types_tagged + for v in variants + ): + raise TypeError( + "When using `asn1.Variant` in a union, all the other " + "types in the union must also be `asn1.Variant`" + ) + + if are_union_types_tagged: + tags = {v.tag_name for v in variants} + if len(variants) != len(tags): + raise TypeError( + "When using `asn1.Variant` in a union, the tags used " + "must be unique" + ) + + rust_choice_type = declarative_asn1.Type.Choice(variants) + # If None is part of the union types, this is an OPTIONAL CHOICE + rust_field_type = ( + declarative_asn1.Type.Option( + declarative_asn1.AnnotatedType( + rust_choice_type, declarative_asn1.Annotation() + ) + ) + if NoneType in union_args + else rust_choice_type + ) + + elif typing.get_origin(field_type) is builtins.list: + inner_type = _normalize_field_type( + typing.get_args(field_type)[0], field_name + ) + rust_field_type = declarative_asn1.Type.SequenceOf(inner_type) + elif typing.get_origin(field_type) is SetOf: + inner_type = _normalize_field_type( + typing.get_args(field_type)[0], field_name + ) + rust_field_type = declarative_asn1.Type.SetOf(inner_type) + else: + rust_field_type = declarative_asn1.non_root_python_to_rust(field_type) + + return declarative_asn1.AnnotatedType(rust_field_type, annotation) + + +# Convert a type to a Variant. Used with types inside Union +# annotations (T1, T2, etc in `Union[T1, T2, ...]`). +def _type_to_variant( + t: typing.Any, field_name: str +) -> declarative_asn1.Variant: + is_annotated = typing.get_origin(t) is typing.Annotated + inner_type = typing.get_args(t)[0] if is_annotated else t + + # Check if this is a Variant[T, Tag] type + if typing.get_origin(inner_type) is Variant: + value_type, tag_literal = typing.get_args(inner_type) + if typing.get_origin(tag_literal) is not typing.Literal: + raise TypeError( + "When using `asn1.Variant` in a type annotation, the second " + "type parameter must be a `typing.Literal` type. E.g: " + '`Variant[int, typing.Literal["MyInt"]]`.' + ) + tag_name = typing.get_args(tag_literal)[0] + + if hasattr(value_type, "__asn1_root__"): + rust_type = value_type.__asn1_root__ + else: + rust_type = declarative_asn1.non_root_python_to_rust(value_type) + + if is_annotated: + ann_type = declarative_asn1.AnnotatedType( + rust_type, + _extract_annotation(t.__metadata__, field_name), + ) + else: + ann_type = declarative_asn1.AnnotatedType( + rust_type, + declarative_asn1.Annotation(), + ) + + return declarative_asn1.Variant(Variant, ann_type, tag_name) + else: + # Plain type (not a tagged Variant) + return declarative_asn1.Variant( + inner_type, + _normalize_field_type(t, field_name), + None, + ) + + +def _annotate_fields( + raw_fields: dict[str, type], +) -> dict[str, declarative_asn1.AnnotatedType]: + fields = {} + for field_name, field_type in raw_fields.items(): + # Recursively normalize the field type into something that the + # Rust code can understand. + annotated_field_type = _normalize_field_type(field_type, field_name) + fields[field_name] = annotated_field_type + + return fields + + +def _register_asn1_sequence(cls: type[U]) -> None: + raw_fields = typing.get_type_hints(cls, include_extras=True) + root = declarative_asn1.Type.Sequence(cls, _annotate_fields(raw_fields)) + + setattr(cls, "__asn1_root__", root) + + +def _register_asn1_set(cls: type[U]) -> None: + raw_fields = typing.get_type_hints(cls, include_extras=True) + root = declarative_asn1.Type.Set(cls, _annotate_fields(raw_fields)) + + setattr(cls, "__asn1_root__", root) + + +# Due to https://github.com/python/mypy/issues/19731, we can't define an alias +# for `dataclass_transform` that conditionally points to `typing` or +# `typing_extensions` depending on the Python version. We work around it by +# making the whole decorated class conditional on the Python version. +if sys.version_info < (3, 11): + + @typing_extensions.dataclass_transform(kw_only_default=True) + def sequence(cls: type[U]) -> type[U]: + # We use `dataclasses.dataclass` to add an __init__ method + # to the class with keyword-only parameters. + if sys.version_info >= (3, 10): + dataclass_cls = dataclasses.dataclass( + repr=False, + eq=False, + # `match_args` was added in Python 3.10 and defaults + # to True + match_args=False, + # `kw_only` was added in Python 3.10 and defaults to + # False + kw_only=True, + )(cls) + else: + dataclass_cls = dataclasses.dataclass( + repr=False, + eq=False, + )(cls) + _register_asn1_sequence(dataclass_cls) + return dataclass_cls + + @typing_extensions.dataclass_transform(kw_only_default=True) + def set(cls: type[U]) -> type[U]: + # We use `dataclasses.dataclass` to add an __init__ method + # to the class with keyword-only parameters. + if sys.version_info >= (3, 10): + dataclass_cls = dataclasses.dataclass( + repr=False, + eq=False, + # `match_args` was added in Python 3.10 and defaults + # to True + match_args=False, + # `kw_only` was added in Python 3.10 and defaults to + # False + kw_only=True, + )(cls) + else: + dataclass_cls = dataclasses.dataclass( + repr=False, + eq=False, + )(cls) + _register_asn1_set(dataclass_cls) + return dataclass_cls + +else: + + @typing.dataclass_transform(kw_only_default=True) + def sequence(cls: type[U]) -> type[U]: + # Only add an __init__ method, with keyword-only + # parameters. + dataclass_cls = dataclasses.dataclass( + repr=False, + eq=False, + match_args=False, + kw_only=True, + )(cls) + _register_asn1_sequence(dataclass_cls) + return dataclass_cls + + @typing.dataclass_transform(kw_only_default=True) + def set(cls: type[U]) -> type[U]: + # Only add an __init__ method, with keyword-only + # parameters. + dataclass_cls = dataclasses.dataclass( + repr=False, + eq=False, + match_args=False, + kw_only=True, + )(cls) + _register_asn1_set(dataclass_cls) + return dataclass_cls + + +# TODO: replace with `Default[U]` once the min Python version is >= 3.12 +@dataclasses.dataclass(frozen=True) +class Default(typing.Generic[U]): + value: U + + +SetOf = declarative_asn1.SetOf + +Explicit = declarative_asn1.Encoding.Explicit +Implicit = declarative_asn1.Encoding.Implicit +Size = declarative_asn1.Size + +PrintableString = declarative_asn1.PrintableString +IA5String = declarative_asn1.IA5String +UTCTime = declarative_asn1.UTCTime +GeneralizedTime = declarative_asn1.GeneralizedTime +BitString = declarative_asn1.BitString +TLV = declarative_asn1.Tlv +Null = declarative_asn1.Null diff --git a/intelliaide/vendor/cryptography/hazmat/backends/__init__.py b/intelliaide/vendor/cryptography/hazmat/backends/__init__.py new file mode 100644 index 0000000..b4400aa --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/backends/__init__.py @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from typing import Any + + +def default_backend() -> Any: + from cryptography.hazmat.backends.openssl.backend import backend + + return backend diff --git a/intelliaide/vendor/cryptography/hazmat/backends/openssl/__init__.py b/intelliaide/vendor/cryptography/hazmat/backends/openssl/__init__.py new file mode 100644 index 0000000..51b0447 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/backends/openssl/__init__.py @@ -0,0 +1,9 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.backends.openssl.backend import backend + +__all__ = ["backend"] diff --git a/intelliaide/vendor/cryptography/hazmat/backends/openssl/backend.py b/intelliaide/vendor/cryptography/hazmat/backends/openssl/backend.py new file mode 100644 index 0000000..ad64a2a --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/backends/openssl/backend.py @@ -0,0 +1,312 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.bindings.openssl import binding +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils +from cryptography.hazmat.primitives.asymmetric.padding import ( + MGF1, + OAEP, + PSS, + PKCS1v15, +) +from cryptography.hazmat.primitives.ciphers import ( + CipherAlgorithm, +) +from cryptography.hazmat.primitives.ciphers.algorithms import ( + AES, +) +from cryptography.hazmat.primitives.ciphers.modes import ( + CBC, + Mode, +) + + +class Backend: + """ + OpenSSL API binding interfaces. + """ + + name = "openssl" + + # TripleDES encryption is disallowed/deprecated throughout 2023 in + # FIPS 140-3. To keep it simple we denylist any use of TripleDES (TDEA). + _fips_ciphers = (AES,) + # Sometimes SHA1 is still permissible. That logic is contained + # within the various *_supported methods. + _fips_hashes = ( + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, + hashes.SHA512_224, + hashes.SHA512_256, + hashes.SHA3_224, + hashes.SHA3_256, + hashes.SHA3_384, + hashes.SHA3_512, + hashes.SHAKE128, + hashes.SHAKE256, + ) + _fips_ecdh_curves = ( + ec.SECP224R1, + ec.SECP256R1, + ec.SECP384R1, + ec.SECP521R1, + ) + _fips_rsa_min_key_size = 2048 + _fips_rsa_min_public_exponent = 65537 + _fips_dsa_min_modulus = 1 << 2048 + _fips_dh_min_key_size = 2048 + _fips_dh_min_modulus = 1 << _fips_dh_min_key_size + + def __init__(self) -> None: + self._binding = binding.Binding() + self._ffi = self._binding.ffi + self._lib = self._binding.lib + self._fips_enabled = rust_openssl.is_fips_enabled() + + def __repr__(self) -> str: + return ( + f"" + ) + + def openssl_assert(self, ok: bool) -> None: + return binding._openssl_assert(ok) + + def _enable_fips(self) -> None: + # This function enables FIPS mode for OpenSSL 3.0.0 on installs that + # have the FIPS provider installed properly. + rust_openssl.enable_fips(rust_openssl._providers) + assert rust_openssl.is_fips_enabled() + self._fips_enabled = rust_openssl.is_fips_enabled() + + def openssl_version_text(self) -> str: + """ + Friendly string name of the loaded OpenSSL library. This is not + necessarily the same version as it was compiled against. + + Example: OpenSSL 3.2.1 30 Jan 2024 + """ + return rust_openssl.openssl_version_text() + + def openssl_version_number(self) -> int: + return rust_openssl.openssl_version() + + def hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: + if self._fips_enabled and not isinstance(algorithm, self._fips_hashes): + return False + + return rust_openssl.hashes.hash_supported(algorithm) + + def signature_hash_supported( + self, algorithm: hashes.HashAlgorithm + ) -> bool: + # Dedicated check for hashing algorithm use in message digest for + # signatures, e.g. RSA PKCS#1 v1.5 SHA1 (sha1WithRSAEncryption). + if self._fips_enabled and isinstance(algorithm, hashes.SHA1): + return False + return self.hash_supported(algorithm) + + def scrypt_supported(self) -> bool: + if self._fips_enabled: + return False + else: + return hasattr(rust_openssl.kdf.Scrypt, "derive") + + def argon2_supported(self) -> bool: + if self._fips_enabled: + return False + else: + return hasattr(rust_openssl.kdf.Argon2id, "derive") + + def hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool: + # FIPS mode still allows SHA1 for HMAC + if self._fips_enabled and isinstance(algorithm, hashes.SHA1): + return True + if rust_openssl.CRYPTOGRAPHY_IS_AWSLC: + return isinstance( + algorithm, + ( + hashes.MD5, + hashes.SHA1, + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, + hashes.SHA512_224, + hashes.SHA512_256, + ), + ) + return self.hash_supported(algorithm) + + def cipher_supported(self, cipher: CipherAlgorithm, mode: Mode) -> bool: + if self._fips_enabled: + # FIPS mode requires AES. TripleDES is disallowed/deprecated in + # FIPS 140-3. + if not isinstance(cipher, self._fips_ciphers): + return False + + return rust_openssl.ciphers.cipher_supported(cipher, mode) + + def pbkdf2_hmac_supported(self, algorithm: hashes.HashAlgorithm) -> bool: + return self.hmac_supported(algorithm) + + def _consume_errors(self) -> list[rust_openssl.OpenSSLError]: + return rust_openssl.capture_error_stack() + + def _oaep_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: + if self._fips_enabled and isinstance(algorithm, hashes.SHA1): + return False + + return isinstance( + algorithm, + ( + hashes.SHA1, + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, + ), + ) + + def rsa_padding_supported(self, padding: AsymmetricPadding) -> bool: + if isinstance(padding, PKCS1v15): + return True + elif isinstance(padding, PSS) and isinstance(padding._mgf, MGF1): + # FIPS 186-4 only allows salt length == digest length for PSS + # It is technically acceptable to set an explicit salt length + # equal to the digest length and this will incorrectly fail, but + # since we don't do that in the tests and this method is + # private, we'll ignore that until we need to do otherwise. + if ( + self._fips_enabled + and padding._salt_length != PSS.DIGEST_LENGTH + ): + return False + return self.hash_supported(padding._mgf._algorithm) + elif isinstance(padding, OAEP) and isinstance(padding._mgf, MGF1): + return self._oaep_hash_supported( + padding._mgf._algorithm + ) and self._oaep_hash_supported(padding._algorithm) + else: + return False + + def rsa_encryption_supported(self, padding: AsymmetricPadding) -> bool: + if self._fips_enabled and isinstance(padding, PKCS1v15): + return False + else: + return self.rsa_padding_supported(padding) + + def dsa_supported(self) -> bool: + return ( + not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + and not self._fips_enabled + ) + + def dsa_hash_supported(self, algorithm: hashes.HashAlgorithm) -> bool: + if not self.dsa_supported(): + return False + return self.signature_hash_supported(algorithm) + + def cmac_algorithm_supported(self, algorithm) -> bool: + return self.cipher_supported( + algorithm, CBC(b"\x00" * algorithm.block_size) + ) + + def elliptic_curve_supported(self, curve: ec.EllipticCurve) -> bool: + if self._fips_enabled and not isinstance( + curve, self._fips_ecdh_curves + ): + return False + + return rust_openssl.ec.curve_supported(curve) + + def elliptic_curve_signature_algorithm_supported( + self, + signature_algorithm: ec.EllipticCurveSignatureAlgorithm, + curve: ec.EllipticCurve, + ) -> bool: + # We only support ECDSA right now. + if not isinstance(signature_algorithm, ec.ECDSA): + return False + + return self.elliptic_curve_supported(curve) and ( + isinstance(signature_algorithm.algorithm, asym_utils.Prehashed) + or self.hash_supported(signature_algorithm.algorithm) + ) + + def elliptic_curve_exchange_algorithm_supported( + self, algorithm: ec.ECDH, curve: ec.EllipticCurve + ) -> bool: + return self.elliptic_curve_supported(curve) and isinstance( + algorithm, ec.ECDH + ) + + def dh_supported(self) -> bool: + return ( + not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC + ) + + def dh_x942_serialization_supported(self) -> bool: + return self._lib.Cryptography_HAS_EVP_PKEY_DHX == 1 + + def x25519_supported(self) -> bool: + return not self._fips_enabled + + def x448_supported(self) -> bool: + if self._fips_enabled: + return False + return ( + not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL + and not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC + ) + + def mlkem_supported(self) -> bool: + return ( + rust_openssl.CRYPTOGRAPHY_IS_AWSLC + or rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + or rust_openssl.CRYPTOGRAPHY_OPENSSL_350_OR_GREATER + ) + + def mldsa_supported(self) -> bool: + return ( + rust_openssl.CRYPTOGRAPHY_IS_AWSLC + or rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + or rust_openssl.CRYPTOGRAPHY_OPENSSL_350_OR_GREATER + ) + + def ed25519_supported(self) -> bool: + return True + + def ed448_supported(self) -> bool: + return ( + not rust_openssl.CRYPTOGRAPHY_IS_LIBRESSL + and not rust_openssl.CRYPTOGRAPHY_IS_BORINGSSL + and not rust_openssl.CRYPTOGRAPHY_IS_AWSLC + ) + + def ecdsa_deterministic_supported(self) -> bool: + return ( + rust_openssl.CRYPTOGRAPHY_OPENSSL_320_OR_GREATER + and not self._fips_enabled + ) + + def poly1305_supported(self) -> bool: + return not self._fips_enabled + + def pkcs7_supported(self) -> bool: + return True + + +backend = Backend() diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/__init__.py b/intelliaide/vendor/cryptography/hazmat/bindings/__init__.py new file mode 100644 index 0000000..b509336 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/__init__.py @@ -0,0 +1,3 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust.abi3.so b/intelliaide/vendor/cryptography/hazmat/bindings/_rust.abi3.so new file mode 100755 index 0000000..2ab847a Binary files /dev/null and b/intelliaide/vendor/cryptography/hazmat/bindings/_rust.abi3.so differ diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/__init__.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/__init__.pyi new file mode 100644 index 0000000..c3148f1 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/__init__.pyi @@ -0,0 +1,67 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives import padding +from cryptography.hazmat.primitives._serialization import ( + KeySerializationEncryptionBuilder, +) +from cryptography.utils import Buffer + +class PKCS7PaddingContext(padding.PaddingContext): + def __init__(self, block_size: int) -> None: ... + def update(self, data: Buffer) -> bytes: ... + def finalize(self) -> bytes: ... + +class ANSIX923PaddingContext(padding.PaddingContext): + def __init__(self, block_size: int) -> None: ... + def update(self, data: Buffer) -> bytes: ... + def finalize(self) -> bytes: ... + +class PKCS7UnpaddingContext(padding.PaddingContext): + def __init__(self, block_size: int) -> None: ... + def update(self, data: Buffer) -> bytes: ... + def finalize(self) -> bytes: ... + +class ANSIX923UnpaddingContext(padding.PaddingContext): + def __init__(self, block_size: int) -> None: ... + def update(self, data: Buffer) -> bytes: ... + def finalize(self) -> bytes: ... + +class Encoding: + PEM: typing.ClassVar[Encoding] + DER: typing.ClassVar[Encoding] + OpenSSH: typing.ClassVar[Encoding] + Raw: typing.ClassVar[Encoding] + X962: typing.ClassVar[Encoding] + SMIME: typing.ClassVar[Encoding] + +class PrivateFormat: + PKCS8: typing.ClassVar[PrivateFormat] + TraditionalOpenSSL: typing.ClassVar[PrivateFormat] + Raw: typing.ClassVar[PrivateFormat] + OpenSSH: typing.ClassVar[PrivateFormat] + PKCS12: typing.ClassVar[PrivateFormat] + def encryption_builder(self) -> KeySerializationEncryptionBuilder: ... + +class PublicFormat: + SubjectPublicKeyInfo: typing.ClassVar[PublicFormat] + PKCS1: typing.ClassVar[PublicFormat] + OpenSSH: typing.ClassVar[PublicFormat] + Raw: typing.ClassVar[PublicFormat] + CompressedPoint: typing.ClassVar[PublicFormat] + UncompressedPoint: typing.ClassVar[PublicFormat] + +class ParameterFormat: + PKCS3: typing.ClassVar[ParameterFormat] + +class ObjectIdentifier: + def __init__(self, value: str) -> None: ... + @property + def dotted_string(self) -> str: ... + @property + def _name(self) -> str: ... + +T = typing.TypeVar("T") diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/_openssl.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/_openssl.pyi new file mode 100644 index 0000000..3d4ea4e --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/_openssl.pyi @@ -0,0 +1,8 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +lib: typing.Any +ffi: typing.Any diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/asn1.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/asn1.pyi new file mode 100644 index 0000000..3b5f208 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/asn1.pyi @@ -0,0 +1,7 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +def decode_dss_signature(signature: bytes) -> tuple[int, int]: ... +def encode_dss_signature(r: int, s: int) -> bytes: ... +def parse_spki_for_data(data: bytes) -> bytes: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi new file mode 100644 index 0000000..a4c0cb6 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/declarative_asn1.pyi @@ -0,0 +1,127 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import datetime +import typing + +def decode_der(cls: type, value: bytes) -> typing.Any: ... +def encode_der(value: typing.Any) -> bytes: ... +def non_root_python_to_rust(cls: type) -> Type: ... + +# Type is a Rust enum with tuple variants. For now, we express the type +# annotations like this: +class Type: + Sequence: typing.ClassVar[type] + SequenceOf: typing.ClassVar[type] + Set: typing.ClassVar[type] + SetOf: typing.ClassVar[type] + Option: typing.ClassVar[type] + Choice: typing.ClassVar[type] + PyBool: typing.ClassVar[type] + PyInt: typing.ClassVar[type] + PyBytes: typing.ClassVar[type] + PyStr: typing.ClassVar[type] + +class Annotation: + default: typing.Any | None + encoding: Encoding | None + size: Size | None + def __new__( + cls, + default: typing.Any | None = None, + encoding: Encoding | None = None, + size: Size | None = None, + ) -> Annotation: ... + def is_empty(self) -> bool: ... + +# Encoding is a Rust enum with tuple variants. For now, we express the type +# annotations like this: +class Encoding: + Implicit: typing.ClassVar[type] + Explicit: typing.ClassVar[type] + +class Size: + min: int + max: int | None + + def __new__(cls, min: int, max: int | None) -> Size: ... + @staticmethod + def exact(n: int) -> Size: ... + +class AnnotatedType: + inner: Type + annotation: Annotation + + def __new__(cls, inner: Type, annotation: Annotation) -> AnnotatedType: ... + +class AnnotatedTypeObject: + annotated_type: AnnotatedType + value: typing.Any + + def __new__( + cls, annotated_type: AnnotatedType, value: typing.Any + ) -> AnnotatedTypeObject: ... + +class Variant: + python_class: type + ann_type: AnnotatedType + tag_name: str | None + + def __new__( + cls, + python_class: type, + ann_type: AnnotatedType, + tag_name: str | None, + ) -> Variant: ... + +class PrintableString: + def __new__(cls, inner: str) -> PrintableString: ... + def __repr__(self) -> str: ... + def __eq__(self, other: object) -> bool: ... + def as_str(self) -> str: ... + +class IA5String: + def __new__(cls, inner: str) -> IA5String: ... + def __repr__(self) -> str: ... + def __eq__(self, other: object) -> bool: ... + def as_str(self) -> str: ... + +class UTCTime: + def __new__(cls, inner: datetime.datetime) -> UTCTime: ... + def __repr__(self) -> str: ... + def __eq__(self, other: object) -> bool: ... + def as_datetime(self) -> datetime.datetime: ... + +class GeneralizedTime: + def __new__(cls, inner: datetime.datetime) -> GeneralizedTime: ... + def __repr__(self) -> str: ... + def __eq__(self, other: object) -> bool: ... + def as_datetime(self) -> datetime.datetime: ... + +class BitString: + def __new__(cls, data: bytes, padding_bits: int) -> BitString: ... + def __repr__(self) -> str: ... + def __eq__(self, other: object) -> bool: ... + def as_bytes(self) -> bytes: ... + def padding_bits(self) -> int: ... + +class Tlv: + @property + def tag_bytes(self) -> bytes: ... + @property + def data(self) -> memoryview: ... + def parse(self, cls: type): ... + +T = typing.TypeVar("T") + +class SetOf(typing.Generic[T]): + def __new__(cls, inner: list[T]) -> SetOf[T]: ... + def as_list(self) -> list[T]: ... + def __eq__(self, other: object) -> bool: ... + def __repr__(self) -> str: ... + +class Null: + def __new__(cls) -> Null: ... + def __repr__(self) -> str: ... + def __eq__(self, other: object) -> bool: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/exceptions.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/exceptions.pyi new file mode 100644 index 0000000..09f46b1 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/exceptions.pyi @@ -0,0 +1,17 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +class _Reasons: + BACKEND_MISSING_INTERFACE: _Reasons + UNSUPPORTED_HASH: _Reasons + UNSUPPORTED_CIPHER: _Reasons + UNSUPPORTED_PADDING: _Reasons + UNSUPPORTED_MGF: _Reasons + UNSUPPORTED_PUBLIC_KEY_ALGORITHM: _Reasons + UNSUPPORTED_ELLIPTIC_CURVE: _Reasons + UNSUPPORTED_SERIALIZATION: _Reasons + UNSUPPORTED_X509: _Reasons + UNSUPPORTED_EXCHANGE_ALGORITHM: _Reasons + UNSUPPORTED_DIFFIE_HELLMAN: _Reasons + UNSUPPORTED_MAC: _Reasons diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/ocsp.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/ocsp.pyi new file mode 100644 index 0000000..103e96c --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/ocsp.pyi @@ -0,0 +1,117 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import datetime +from collections.abc import Iterator + +from cryptography import x509 +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes +from cryptography.x509 import ocsp + +class OCSPRequest: + @property + def issuer_key_hash(self) -> bytes: ... + @property + def issuer_name_hash(self) -> bytes: ... + @property + def hash_algorithm(self) -> hashes.HashAlgorithm: ... + @property + def serial_number(self) -> int: ... + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + @property + def extensions(self) -> x509.Extensions: ... + +class OCSPResponse: + @property + def responses(self) -> Iterator[OCSPSingleResponse]: ... + @property + def response_status(self) -> ocsp.OCSPResponseStatus: ... + @property + def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def signature_hash_algorithm( + self, + ) -> hashes.HashAlgorithm | None: ... + @property + def signature(self) -> bytes: ... + @property + def tbs_response_bytes(self) -> bytes: ... + @property + def certificates(self) -> list[x509.Certificate]: ... + @property + def responder_key_hash(self) -> bytes | None: ... + @property + def responder_name(self) -> x509.Name | None: ... + @property + def produced_at(self) -> datetime.datetime: ... + @property + def produced_at_utc(self) -> datetime.datetime: ... + @property + def certificate_status(self) -> ocsp.OCSPCertStatus: ... + @property + def revocation_time(self) -> datetime.datetime | None: ... + @property + def revocation_time_utc(self) -> datetime.datetime | None: ... + @property + def revocation_reason(self) -> x509.ReasonFlags | None: ... + @property + def this_update(self) -> datetime.datetime: ... + @property + def this_update_utc(self) -> datetime.datetime: ... + @property + def next_update(self) -> datetime.datetime | None: ... + @property + def next_update_utc(self) -> datetime.datetime | None: ... + @property + def issuer_key_hash(self) -> bytes: ... + @property + def issuer_name_hash(self) -> bytes: ... + @property + def hash_algorithm(self) -> hashes.HashAlgorithm: ... + @property + def serial_number(self) -> int: ... + @property + def extensions(self) -> x509.Extensions: ... + @property + def single_extensions(self) -> x509.Extensions: ... + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + +class OCSPSingleResponse: + @property + def certificate_status(self) -> ocsp.OCSPCertStatus: ... + @property + def revocation_time(self) -> datetime.datetime | None: ... + @property + def revocation_time_utc(self) -> datetime.datetime | None: ... + @property + def revocation_reason(self) -> x509.ReasonFlags | None: ... + @property + def this_update(self) -> datetime.datetime: ... + @property + def this_update_utc(self) -> datetime.datetime: ... + @property + def next_update(self) -> datetime.datetime | None: ... + @property + def next_update_utc(self) -> datetime.datetime | None: ... + @property + def issuer_key_hash(self) -> bytes: ... + @property + def issuer_name_hash(self) -> bytes: ... + @property + def hash_algorithm(self) -> hashes.HashAlgorithm: ... + @property + def serial_number(self) -> int: ... + +def load_der_ocsp_request(data: bytes) -> ocsp.OCSPRequest: ... +def load_der_ocsp_response(data: bytes) -> ocsp.OCSPResponse: ... +def create_ocsp_request( + builder: ocsp.OCSPRequestBuilder, +) -> ocsp.OCSPRequest: ... +def create_ocsp_response( + status: ocsp.OCSPResponseStatus, + builder: ocsp.OCSPResponseBuilder | None, + private_key: PrivateKeyTypes | None, + hash_algorithm: hashes.HashAlgorithm | None, +) -> ocsp.OCSPResponse: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi new file mode 100644 index 0000000..404a300 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/__init__.pyi @@ -0,0 +1,80 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.bindings._rust.openssl import ( + aead, + ciphers, + cmac, + dh, + dsa, + ec, + ed448, + ed25519, + hashes, + hmac, + hpke, + kdf, + keys, + mldsa, + mlkem, + poly1305, + rsa, + x448, + x25519, +) + +__all__ = [ + "aead", + "ciphers", + "cmac", + "dh", + "dsa", + "ec", + "ed448", + "ed25519", + "hashes", + "hmac", + "hpke", + "kdf", + "keys", + "mldsa", + "mlkem", + "openssl_version", + "openssl_version_text", + "poly1305", + "raise_openssl_error", + "rsa", + "x448", + "x25519", +] + +CRYPTOGRAPHY_IS_LIBRESSL: bool +CRYPTOGRAPHY_IS_BORINGSSL: bool +CRYPTOGRAPHY_IS_AWSLC: bool +CRYPTOGRAPHY_OPENSSL_309_OR_GREATER: bool +CRYPTOGRAPHY_OPENSSL_320_OR_GREATER: bool +CRYPTOGRAPHY_OPENSSL_330_OR_GREATER: bool +CRYPTOGRAPHY_OPENSSL_350_OR_GREATER: bool + +class Providers: ... + +_legacy_provider_loaded: bool +_providers: Providers + +def openssl_version() -> int: ... +def openssl_version_text() -> str: ... +def raise_openssl_error() -> typing.NoReturn: ... +def capture_error_stack() -> list[OpenSSLError]: ... +def is_fips_enabled() -> bool: ... +def enable_fips(providers: Providers) -> None: ... + +class OpenSSLError: + @property + def lib(self) -> int: ... + @property + def reason(self) -> int: ... + @property + def reason_text(self) -> bytes: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/aead.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/aead.pyi new file mode 100644 index 0000000..eb44608 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/aead.pyi @@ -0,0 +1,189 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from collections.abc import Sequence + +from cryptography.utils import Buffer + +class AESGCM: + def __init__(self, key: Buffer) -> None: ... + @staticmethod + def generate_key(bit_length: int) -> bytes: ... + def encrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def decrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def encrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... + def decrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... + +class ChaCha20Poly1305: + def __init__(self, key: Buffer) -> None: ... + @staticmethod + def generate_key() -> bytes: ... + def encrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def encrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... + def decrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def decrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... + +class AESCCM: + def __init__(self, key: Buffer, tag_length: int = 16) -> None: ... + @staticmethod + def generate_key(bit_length: int) -> bytes: ... + def encrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def encrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... + def decrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def decrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... + +class AESSIV: + def __init__(self, key: Buffer) -> None: ... + @staticmethod + def generate_key(bit_length: int) -> bytes: ... + def encrypt( + self, + data: Buffer, + associated_data: Sequence[Buffer] | None, + ) -> bytes: ... + def encrypt_into( + self, + data: Buffer, + associated_data: Sequence[Buffer] | None, + buf: Buffer, + ) -> int: ... + def decrypt( + self, + data: Buffer, + associated_data: Sequence[Buffer] | None, + ) -> bytes: ... + def decrypt_into( + self, + data: Buffer, + associated_data: Sequence[Buffer] | None, + buf: Buffer, + ) -> int: ... + +class AESOCB3: + def __init__(self, key: Buffer) -> None: ... + @staticmethod + def generate_key(bit_length: int) -> bytes: ... + def encrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def encrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... + def decrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def decrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... + +class AESGCMSIV: + def __init__(self, key: Buffer) -> None: ... + @staticmethod + def generate_key(bit_length: int) -> bytes: ... + def encrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def encrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... + def decrypt( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + ) -> bytes: ... + def decrypt_into( + self, + nonce: Buffer, + data: Buffer, + associated_data: Buffer | None, + buf: Buffer, + ) -> int: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi new file mode 100644 index 0000000..a48fb01 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi @@ -0,0 +1,38 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives import ciphers +from cryptography.hazmat.primitives.ciphers import modes + +@typing.overload +def create_encryption_ctx( + algorithm: ciphers.CipherAlgorithm, mode: modes.ModeWithAuthenticationTag +) -> ciphers.AEADEncryptionContext: ... +@typing.overload +def create_encryption_ctx( + algorithm: ciphers.CipherAlgorithm, mode: modes.Mode | None +) -> ciphers.CipherContext: ... +@typing.overload +def create_decryption_ctx( + algorithm: ciphers.CipherAlgorithm, mode: modes.ModeWithAuthenticationTag +) -> ciphers.AEADDecryptionContext: ... +@typing.overload +def create_decryption_ctx( + algorithm: ciphers.CipherAlgorithm, mode: modes.Mode | None +) -> ciphers.CipherContext: ... +def cipher_supported( + algorithm: ciphers.CipherAlgorithm, mode: modes.Mode +) -> bool: ... +def _advance( + ctx: ciphers.AEADEncryptionContext | ciphers.AEADDecryptionContext, n: int +) -> None: ... +def _advance_aad( + ctx: ciphers.AEADEncryptionContext | ciphers.AEADDecryptionContext, n: int +) -> None: ... + +class CipherContext: ... +class AEADEncryptionContext: ... +class AEADDecryptionContext: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi new file mode 100644 index 0000000..9c03508 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/cmac.pyi @@ -0,0 +1,18 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives import ciphers + +class CMAC: + def __init__( + self, + algorithm: ciphers.BlockCipherAlgorithm, + backend: typing.Any = None, + ) -> None: ... + def update(self, data: bytes) -> None: ... + def finalize(self) -> bytes: ... + def verify(self, signature: bytes) -> None: ... + def copy(self) -> CMAC: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/dh.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/dh.pyi new file mode 100644 index 0000000..08733d7 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/dh.pyi @@ -0,0 +1,51 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives.asymmetric import dh + +MIN_MODULUS_SIZE: int + +class DHPrivateKey: ... +class DHPublicKey: ... +class DHParameters: ... + +class DHPrivateNumbers: + def __init__(self, x: int, public_numbers: DHPublicNumbers) -> None: ... + def private_key(self, backend: typing.Any = None) -> dh.DHPrivateKey: ... + @property + def x(self) -> int: ... + @property + def public_numbers(self) -> DHPublicNumbers: ... + +class DHPublicNumbers: + def __init__( + self, y: int, parameter_numbers: DHParameterNumbers + ) -> None: ... + def public_key(self, backend: typing.Any = None) -> dh.DHPublicKey: ... + @property + def y(self) -> int: ... + @property + def parameter_numbers(self) -> DHParameterNumbers: ... + +class DHParameterNumbers: + def __init__(self, p: int, g: int, q: int | None = None) -> None: ... + def parameters(self, backend: typing.Any = None) -> dh.DHParameters: ... + @property + def p(self) -> int: ... + @property + def g(self) -> int: ... + @property + def q(self) -> int | None: ... + +def generate_parameters( + generator: int, key_size: int, backend: typing.Any = None +) -> dh.DHParameters: ... +def from_pem_parameters( + data: bytes, backend: typing.Any = None +) -> dh.DHParameters: ... +def from_der_parameters( + data: bytes, backend: typing.Any = None +) -> dh.DHParameters: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi new file mode 100644 index 0000000..0922a4c --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/dsa.pyi @@ -0,0 +1,41 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives.asymmetric import dsa + +class DSAPrivateKey: ... +class DSAPublicKey: ... +class DSAParameters: ... + +class DSAPrivateNumbers: + def __init__(self, x: int, public_numbers: DSAPublicNumbers) -> None: ... + @property + def x(self) -> int: ... + @property + def public_numbers(self) -> DSAPublicNumbers: ... + def private_key(self, backend: typing.Any = None) -> dsa.DSAPrivateKey: ... + +class DSAPublicNumbers: + def __init__( + self, y: int, parameter_numbers: DSAParameterNumbers + ) -> None: ... + @property + def y(self) -> int: ... + @property + def parameter_numbers(self) -> DSAParameterNumbers: ... + def public_key(self, backend: typing.Any = None) -> dsa.DSAPublicKey: ... + +class DSAParameterNumbers: + def __init__(self, p: int, q: int, g: int) -> None: ... + @property + def p(self) -> int: ... + @property + def q(self) -> int: ... + @property + def g(self) -> int: ... + def parameters(self, backend: typing.Any = None) -> dsa.DSAParameters: ... + +def generate_parameters(key_size: int) -> dsa.DSAParameters: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/ec.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/ec.pyi new file mode 100644 index 0000000..5c3b7bf --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/ec.pyi @@ -0,0 +1,52 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives.asymmetric import ec + +class ECPrivateKey: ... +class ECPublicKey: ... + +class EllipticCurvePrivateNumbers: + def __init__( + self, private_value: int, public_numbers: EllipticCurvePublicNumbers + ) -> None: ... + def private_key( + self, backend: typing.Any = None + ) -> ec.EllipticCurvePrivateKey: ... + @property + def private_value(self) -> int: ... + @property + def public_numbers(self) -> EllipticCurvePublicNumbers: ... + +class EllipticCurvePublicNumbers: + def __init__(self, x: int, y: int, curve: ec.EllipticCurve) -> None: ... + def public_key( + self, backend: typing.Any = None + ) -> ec.EllipticCurvePublicKey: ... + @property + def x(self) -> int: ... + @property + def y(self) -> int: ... + @property + def curve(self) -> ec.EllipticCurve: ... + def __eq__(self, other: object) -> bool: ... + +def curve_supported(curve: ec.EllipticCurve) -> bool: ... +def generate_private_key( + curve: ec.EllipticCurve, backend: typing.Any = None +) -> ec.EllipticCurvePrivateKey: ... +def from_private_numbers( + numbers: ec.EllipticCurvePrivateNumbers, +) -> ec.EllipticCurvePrivateKey: ... +def from_public_numbers( + numbers: ec.EllipticCurvePublicNumbers, +) -> ec.EllipticCurvePublicKey: ... +def from_public_bytes( + curve: ec.EllipticCurve, data: bytes +) -> ec.EllipticCurvePublicKey: ... +def derive_private_key( + private_value: int, curve: ec.EllipticCurve +) -> ec.EllipticCurvePrivateKey: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi new file mode 100644 index 0000000..f85b3d1 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.hazmat.primitives.asymmetric import ed25519 +from cryptography.utils import Buffer + +class Ed25519PrivateKey: ... +class Ed25519PublicKey: ... + +def generate_key() -> ed25519.Ed25519PrivateKey: ... +def from_private_bytes(data: Buffer) -> ed25519.Ed25519PrivateKey: ... +def from_public_bytes(data: bytes) -> ed25519.Ed25519PublicKey: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi new file mode 100644 index 0000000..c8ca0ec --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/ed448.pyi @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.hazmat.primitives.asymmetric import ed448 +from cryptography.utils import Buffer + +class Ed448PrivateKey: ... +class Ed448PublicKey: ... + +def generate_key() -> ed448.Ed448PrivateKey: ... +def from_private_bytes(data: Buffer) -> ed448.Ed448PrivateKey: ... +def from_public_bytes(data: bytes) -> ed448.Ed448PublicKey: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi new file mode 100644 index 0000000..106b531 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/hashes.pyi @@ -0,0 +1,30 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives import hashes +from cryptography.utils import Buffer + +class Hash(hashes.HashContext): + def __init__( + self, algorithm: hashes.HashAlgorithm, backend: typing.Any = None + ) -> None: ... + @property + def algorithm(self) -> hashes.HashAlgorithm: ... + def update(self, data: Buffer) -> None: ... + def finalize(self) -> bytes: ... + def copy(self) -> Hash: ... + @staticmethod + def hash(algorithm: hashes.HashAlgorithm, data: Buffer) -> bytes: ... + +def hash_supported(algorithm: hashes.HashAlgorithm) -> bool: ... + +class XOFHash: + def __init__(self, algorithm: hashes.ExtendableOutputFunction) -> None: ... + @property + def algorithm(self) -> hashes.ExtendableOutputFunction: ... + def update(self, data: Buffer) -> None: ... + def squeeze(self, length: int) -> bytes: ... + def copy(self) -> XOFHash: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi new file mode 100644 index 0000000..3883d1b --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/hmac.pyi @@ -0,0 +1,22 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives import hashes +from cryptography.utils import Buffer + +class HMAC(hashes.HashContext): + def __init__( + self, + key: Buffer, + algorithm: hashes.HashAlgorithm, + backend: typing.Any = None, + ) -> None: ... + @property + def algorithm(self) -> hashes.HashAlgorithm: ... + def update(self, data: Buffer) -> None: ... + def finalize(self) -> bytes: ... + def verify(self, signature: bytes) -> None: ... + def copy(self) -> HMAC: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi new file mode 100644 index 0000000..a9d8a11 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/hpke.pyi @@ -0,0 +1,108 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.hazmat.primitives.asymmetric import ec, mlkem, x25519 +from cryptography.utils import Buffer + +class KEM: + X25519: KEM + P256: KEM + P384: KEM + P521: KEM + MLKEM768: KEM + MLKEM1024: KEM + MLKEM768_X25519: KEM + MLKEM1024_P384: KEM + +class KDF: + HKDF_SHA256: KDF + HKDF_SHA384: KDF + HKDF_SHA512: KDF + SHAKE128: KDF + SHAKE256: KDF + +class AEAD: + AES_128_GCM: AEAD + AES_256_GCM: AEAD + CHACHA20_POLY1305: AEAD + +class MLKEM768X25519PrivateKey: + def __init__( + self, + mlkem_key: mlkem.MLKEM768PrivateKey, + x25519_key: x25519.X25519PrivateKey, + ) -> None: ... + def public_key(self) -> MLKEM768X25519PublicKey: ... + +class MLKEM768X25519PublicKey: + def __init__( + self, + mlkem_key: mlkem.MLKEM768PublicKey, + x25519_key: x25519.X25519PublicKey, + ) -> None: ... + +class MLKEM1024P384PrivateKey: + def __init__( + self, + mlkem_key: mlkem.MLKEM1024PrivateKey, + p384_key: ec.EllipticCurvePrivateKey, + ) -> None: ... + def public_key(self) -> MLKEM1024P384PublicKey: ... + +class MLKEM1024P384PublicKey: + def __init__( + self, + mlkem_key: mlkem.MLKEM1024PublicKey, + p384_key: ec.EllipticCurvePublicKey, + ) -> None: ... + +class Suite: + def __init__(self, kem: KEM, kdf: KDF, aead: AEAD) -> None: ... + def encrypt( + self, + plaintext: Buffer, + public_key: x25519.X25519PublicKey + | ec.EllipticCurvePublicKey + | mlkem.MLKEM768PublicKey + | mlkem.MLKEM1024PublicKey + | MLKEM768X25519PublicKey + | MLKEM1024P384PublicKey, + info: Buffer | None = None, + ) -> bytes: ... + def decrypt( + self, + ciphertext: Buffer, + private_key: x25519.X25519PrivateKey + | ec.EllipticCurvePrivateKey + | mlkem.MLKEM768PrivateKey + | mlkem.MLKEM1024PrivateKey + | MLKEM768X25519PrivateKey + | MLKEM1024P384PrivateKey, + info: Buffer | None = None, + ) -> bytes: ... + +def _encrypt_with_aad( + suite: Suite, + plaintext: Buffer, + public_key: x25519.X25519PublicKey + | ec.EllipticCurvePublicKey + | mlkem.MLKEM768PublicKey + | mlkem.MLKEM1024PublicKey + | MLKEM768X25519PublicKey + | MLKEM1024P384PublicKey, + info: Buffer | None = None, + aad: Buffer | None = None, +) -> bytes: ... +def _decrypt_with_aad( + suite: Suite, + ciphertext: Buffer, + private_key: x25519.X25519PrivateKey + | ec.EllipticCurvePrivateKey + | mlkem.MLKEM768PrivateKey + | mlkem.MLKEM1024PrivateKey + | MLKEM768X25519PrivateKey + | MLKEM1024P384PrivateKey, + info: Buffer | None = None, + aad: Buffer | None = None, +) -> bytes: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi new file mode 100644 index 0000000..bb37b96 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/kdf.pyi @@ -0,0 +1,205 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives.hashes import HashAlgorithm +from cryptography.hazmat.primitives.kdf.kbkdf import CounterLocation, Mode +from cryptography.utils import Buffer + +class PBKDF2HMAC: + def __init__( + self, + algorithm: HashAlgorithm, + length: int, + salt: bytes, + iterations: int, + backend: typing.Any = None, + ) -> None: ... + def derive(self, key_material: Buffer) -> bytes: ... + def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + +class Scrypt: + def __init__( + self, + salt: bytes, + length: int, + n: int, + r: int, + p: int, + backend: typing.Any = None, + ) -> None: ... + def derive(self, key_material: Buffer) -> bytes: ... + def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + +class Argon2d: + def __init__( + self, + *, + salt: bytes, + length: int, + iterations: int, + lanes: int, + memory_cost: int, + ad: bytes | None = None, + secret: bytes | None = None, + ) -> None: ... + def derive(self, key_material: bytes) -> bytes: ... + def derive_into(self, key_material: bytes, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + def derive_phc_encoded(self, key_material: bytes) -> str: ... + @classmethod + def verify_phc_encoded( + cls, key_material: bytes, phc_encoded: str, secret: bytes | None = None + ) -> None: ... + +class Argon2i: + def __init__( + self, + *, + salt: bytes, + length: int, + iterations: int, + lanes: int, + memory_cost: int, + ad: bytes | None = None, + secret: bytes | None = None, + ) -> None: ... + def derive(self, key_material: bytes) -> bytes: ... + def derive_into(self, key_material: bytes, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + def derive_phc_encoded(self, key_material: bytes) -> str: ... + @classmethod + def verify_phc_encoded( + cls, key_material: bytes, phc_encoded: str, secret: bytes | None = None + ) -> None: ... + +class Argon2id: + def __init__( + self, + *, + salt: bytes, + length: int, + iterations: int, + lanes: int, + memory_cost: int, + ad: bytes | None = None, + secret: bytes | None = None, + ) -> None: ... + def derive(self, key_material: bytes) -> bytes: ... + def derive_into(self, key_material: bytes, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + def derive_phc_encoded(self, key_material: bytes) -> str: ... + @classmethod + def verify_phc_encoded( + cls, key_material: bytes, phc_encoded: str, secret: bytes | None = None + ) -> None: ... + +class HKDF: + def __init__( + self, + algorithm: HashAlgorithm, + length: int, + salt: bytes | None, + info: bytes | None, + backend: typing.Any = None, + ): ... + @staticmethod + def extract( + algorithm: HashAlgorithm, salt: bytes | None, key_material: Buffer + ) -> bytes: ... + def derive(self, key_material: Buffer) -> bytes: ... + def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + +class HKDFExpand: + def __init__( + self, + algorithm: HashAlgorithm, + length: int, + info: bytes | None, + backend: typing.Any = None, + ): ... + def derive(self, key_material: Buffer) -> bytes: ... + def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + +class X963KDF: + def __init__( + self, + algorithm: HashAlgorithm, + length: int, + sharedinfo: bytes | None, + backend: typing.Any = None, + ) -> None: ... + def derive(self, key_material: Buffer) -> bytes: ... + def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + +class ConcatKDFHash: + def __init__( + self, + algorithm: HashAlgorithm, + length: int, + otherinfo: bytes | None, + backend: typing.Any = None, + ) -> None: ... + def derive(self, key_material: Buffer) -> bytes: ... + def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + +class ConcatKDFHMAC: + def __init__( + self, + algorithm: HashAlgorithm, + length: int, + salt: bytes | None, + otherinfo: bytes | None, + backend: typing.Any = None, + ) -> None: ... + def derive(self, key_material: Buffer) -> bytes: ... + def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + +class KBKDFHMAC: + def __init__( + self, + algorithm: HashAlgorithm, + mode: Mode, + length: int, + rlen: int, + llen: int | None, + location: CounterLocation, + label: bytes | None, + context: bytes | None, + fixed: bytes | None, + backend: typing.Any = None, + *, + break_location: int | None = None, + ) -> None: ... + def derive(self, key_material: Buffer) -> bytes: ... + def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... + +class KBKDFCMAC: + def __init__( + self, + algorithm: typing.Any, + mode: Mode, + length: int, + rlen: int, + llen: int | None, + location: CounterLocation, + label: bytes | None, + context: bytes | None, + fixed: bytes | None, + backend: typing.Any = None, + *, + break_location: int | None = None, + ) -> None: ... + def derive(self, key_material: Buffer) -> bytes: ... + def derive_into(self, key_material: Buffer, buffer: Buffer) -> int: ... + def verify(self, key_material: bytes, expected_key: bytes) -> None: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/keys.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/keys.pyi new file mode 100644 index 0000000..404057e --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/keys.pyi @@ -0,0 +1,34 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives.asymmetric.types import ( + PrivateKeyTypes, + PublicKeyTypes, +) +from cryptography.utils import Buffer + +def load_der_private_key( + data: Buffer, + password: bytes | None, + backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, +) -> PrivateKeyTypes: ... +def load_pem_private_key( + data: Buffer, + password: bytes | None, + backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, +) -> PrivateKeyTypes: ... +def load_der_public_key( + data: bytes, + backend: typing.Any = None, +) -> PublicKeyTypes: ... +def load_pem_public_key( + data: bytes, + backend: typing.Any = None, +) -> PublicKeyTypes: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/mldsa.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/mldsa.pyi new file mode 100644 index 0000000..232469a --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/mldsa.pyi @@ -0,0 +1,23 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.hazmat.primitives.asymmetric import mldsa +from cryptography.utils import Buffer + +class MLDSA44PrivateKey: ... +class MLDSA44PublicKey: ... +class MLDSA65PrivateKey: ... +class MLDSA65PublicKey: ... +class MLDSA87PrivateKey: ... +class MLDSA87PublicKey: ... + +def generate_mldsa44_key() -> mldsa.MLDSA44PrivateKey: ... +def from_mldsa44_public_bytes(data: bytes) -> mldsa.MLDSA44PublicKey: ... +def from_mldsa44_seed_bytes(data: Buffer) -> mldsa.MLDSA44PrivateKey: ... +def generate_mldsa65_key() -> mldsa.MLDSA65PrivateKey: ... +def from_mldsa65_public_bytes(data: bytes) -> mldsa.MLDSA65PublicKey: ... +def from_mldsa65_seed_bytes(data: Buffer) -> mldsa.MLDSA65PrivateKey: ... +def generate_mldsa87_key() -> mldsa.MLDSA87PrivateKey: ... +def from_mldsa87_public_bytes(data: bytes) -> mldsa.MLDSA87PublicKey: ... +def from_mldsa87_seed_bytes(data: Buffer) -> mldsa.MLDSA87PrivateKey: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/mlkem.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/mlkem.pyi new file mode 100644 index 0000000..768a340 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/mlkem.pyi @@ -0,0 +1,18 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.hazmat.primitives.asymmetric import mlkem +from cryptography.utils import Buffer + +class MLKEM768PrivateKey: ... +class MLKEM768PublicKey: ... +class MLKEM1024PrivateKey: ... +class MLKEM1024PublicKey: ... + +def generate_mlkem768_key() -> mlkem.MLKEM768PrivateKey: ... +def from_mlkem768_seed_bytes(data: Buffer) -> mlkem.MLKEM768PrivateKey: ... +def from_mlkem768_public_bytes(data: Buffer) -> mlkem.MLKEM768PublicKey: ... +def generate_mlkem1024_key() -> mlkem.MLKEM1024PrivateKey: ... +def from_mlkem1024_seed_bytes(data: Buffer) -> mlkem.MLKEM1024PrivateKey: ... +def from_mlkem1024_public_bytes(data: Buffer) -> mlkem.MLKEM1024PublicKey: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi new file mode 100644 index 0000000..45a2a39 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi @@ -0,0 +1,15 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.utils import Buffer + +class Poly1305: + def __init__(self, key: Buffer) -> None: ... + @staticmethod + def generate_tag(key: Buffer, data: Buffer) -> bytes: ... + @staticmethod + def verify_tag(key: Buffer, data: Buffer, tag: bytes) -> None: ... + def update(self, data: Buffer) -> None: ... + def finalize(self) -> bytes: ... + def verify(self, tag: bytes) -> None: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi new file mode 100644 index 0000000..ef7752d --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/rsa.pyi @@ -0,0 +1,55 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing + +from cryptography.hazmat.primitives.asymmetric import rsa + +class RSAPrivateKey: ... +class RSAPublicKey: ... + +class RSAPrivateNumbers: + def __init__( + self, + p: int, + q: int, + d: int, + dmp1: int, + dmq1: int, + iqmp: int, + public_numbers: RSAPublicNumbers, + ) -> None: ... + @property + def p(self) -> int: ... + @property + def q(self) -> int: ... + @property + def d(self) -> int: ... + @property + def dmp1(self) -> int: ... + @property + def dmq1(self) -> int: ... + @property + def iqmp(self) -> int: ... + @property + def public_numbers(self) -> RSAPublicNumbers: ... + def private_key( + self, + backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, + ) -> rsa.RSAPrivateKey: ... + +class RSAPublicNumbers: + def __init__(self, e: int, n: int) -> None: ... + @property + def n(self) -> int: ... + @property + def e(self) -> int: ... + def public_key(self, backend: typing.Any = None) -> rsa.RSAPublicKey: ... + +def generate_private_key( + public_exponent: int, + key_size: int, +) -> rsa.RSAPrivateKey: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi new file mode 100644 index 0000000..38d2add --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/x25519.pyi @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.hazmat.primitives.asymmetric import x25519 +from cryptography.utils import Buffer + +class X25519PrivateKey: ... +class X25519PublicKey: ... + +def generate_key() -> x25519.X25519PrivateKey: ... +def from_private_bytes(data: Buffer) -> x25519.X25519PrivateKey: ... +def from_public_bytes(data: bytes) -> x25519.X25519PublicKey: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/x448.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/x448.pyi new file mode 100644 index 0000000..3ac0980 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/openssl/x448.pyi @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.hazmat.primitives.asymmetric import x448 +from cryptography.utils import Buffer + +class X448PrivateKey: ... +class X448PublicKey: ... + +def generate_key() -> x448.X448PrivateKey: ... +def from_private_bytes(data: Buffer) -> x448.X448PrivateKey: ... +def from_public_bytes(data: bytes) -> x448.X448PublicKey: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/pkcs12.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/pkcs12.pyi new file mode 100644 index 0000000..b25becb --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/pkcs12.pyi @@ -0,0 +1,52 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import typing +from collections.abc import Iterable + +from cryptography import x509 +from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes +from cryptography.hazmat.primitives.serialization import ( + KeySerializationEncryption, +) +from cryptography.hazmat.primitives.serialization.pkcs12 import ( + PKCS12KeyAndCertificates, + PKCS12PrivateKeyTypes, +) +from cryptography.utils import Buffer + +class PKCS12Certificate: + def __init__( + self, cert: x509.Certificate, friendly_name: bytes | None + ) -> None: ... + @property + def friendly_name(self) -> bytes | None: ... + @property + def certificate(self) -> x509.Certificate: ... + +def load_key_and_certificates( + data: Buffer, + password: Buffer | None, + backend: typing.Any = None, +) -> tuple[ + PrivateKeyTypes | None, + x509.Certificate | None, + list[x509.Certificate], +]: ... +def load_pkcs12( + data: bytes, + password: bytes | None, + backend: typing.Any = None, +) -> PKCS12KeyAndCertificates: ... +def serialize_java_truststore( + certs: Iterable[PKCS12Certificate], + encryption_algorithm: KeySerializationEncryption, +) -> bytes: ... +def serialize_key_and_certificates( + name: bytes | None, + key: PKCS12PrivateKeyTypes | None, + cert: x509.Certificate | None, + cas: Iterable[x509.Certificate | PKCS12Certificate] | None, + encryption_algorithm: KeySerializationEncryption, +) -> bytes: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/pkcs7.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/pkcs7.pyi new file mode 100644 index 0000000..358b135 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/pkcs7.pyi @@ -0,0 +1,50 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from collections.abc import Iterable + +from cryptography import x509 +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.serialization import pkcs7 + +def serialize_certificates( + certs: list[x509.Certificate], + encoding: serialization.Encoding, +) -> bytes: ... +def encrypt_and_serialize( + builder: pkcs7.PKCS7EnvelopeBuilder, + content_encryption_algorithm: pkcs7.ContentEncryptionAlgorithm, + encoding: serialization.Encoding, + options: Iterable[pkcs7.PKCS7Options], +) -> bytes: ... +def sign_and_serialize( + builder: pkcs7.PKCS7SignatureBuilder, + encoding: serialization.Encoding, + options: Iterable[pkcs7.PKCS7Options], +) -> bytes: ... +def decrypt_der( + data: bytes, + certificate: x509.Certificate, + private_key: rsa.RSAPrivateKey, + options: Iterable[pkcs7.PKCS7Options], +) -> bytes: ... +def decrypt_pem( + data: bytes, + certificate: x509.Certificate, + private_key: rsa.RSAPrivateKey, + options: Iterable[pkcs7.PKCS7Options], +) -> bytes: ... +def decrypt_smime( + data: bytes, + certificate: x509.Certificate, + private_key: rsa.RSAPrivateKey, + options: Iterable[pkcs7.PKCS7Options], +) -> bytes: ... +def load_pem_pkcs7_certificates( + data: bytes, +) -> list[x509.Certificate]: ... +def load_der_pkcs7_certificates( + data: bytes, +) -> list[x509.Certificate]: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/test_support.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/test_support.pyi new file mode 100644 index 0000000..c6c6d0b --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/test_support.pyi @@ -0,0 +1,23 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography import x509 +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.serialization import pkcs7 +from cryptography.utils import Buffer + +class TestCertificate: + not_after_tag: int + not_before_tag: int + issuer_value_tags: list[int] + subject_value_tags: list[int] + +def test_parse_certificate(data: bytes) -> TestCertificate: ... +def pkcs7_verify( + encoding: serialization.Encoding, + sig: bytes, + msg: Buffer | None, + certs: list[x509.Certificate], + options: list[pkcs7.PKCS7Options], +) -> None: ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/_rust/x509.pyi b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/x509.pyi new file mode 100644 index 0000000..196a3c6 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/_rust/x509.pyi @@ -0,0 +1,312 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import datetime +import typing +from collections.abc import Iterator + +from cryptography import x509 +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric.ec import ECDSA +from cryptography.hazmat.primitives.asymmetric.padding import PSS, PKCS1v15 +from cryptography.hazmat.primitives.asymmetric.types import ( + CertificateIssuerPublicKeyTypes, + CertificatePublicKeyTypes, + PrivateKeyTypes, +) +from cryptography.x509 import certificate_transparency + +def load_pem_x509_certificate( + data: bytes, backend: typing.Any = None +) -> x509.Certificate: ... +def load_der_x509_certificate( + data: bytes, backend: typing.Any = None +) -> x509.Certificate: ... +def load_pem_x509_certificates( + data: bytes, +) -> list[x509.Certificate]: ... +def load_pem_x509_crl( + data: bytes, backend: typing.Any = None +) -> x509.CertificateRevocationList: ... +def load_der_x509_crl( + data: bytes, backend: typing.Any = None +) -> x509.CertificateRevocationList: ... +def load_pem_x509_csr( + data: bytes, backend: typing.Any = None +) -> x509.CertificateSigningRequest: ... +def load_der_x509_csr( + data: bytes, backend: typing.Any = None +) -> x509.CertificateSigningRequest: ... +def encode_name_bytes(name: x509.Name) -> bytes: ... +def encode_extension_value(extension: x509.ExtensionType) -> bytes: ... +def create_x509_certificate( + builder: x509.CertificateBuilder, + private_key: PrivateKeyTypes, + hash_algorithm: hashes.HashAlgorithm | None, + rsa_padding: PKCS1v15 | PSS | None, + ecdsa_deterministic: bool | None, +) -> x509.Certificate: ... +def create_x509_csr( + builder: x509.CertificateSigningRequestBuilder, + private_key: PrivateKeyTypes, + hash_algorithm: hashes.HashAlgorithm | None, + rsa_padding: PKCS1v15 | PSS | None, + ecdsa_deterministic: bool | None, +) -> x509.CertificateSigningRequest: ... +def create_revoked_certificate( + builder: x509.RevokedCertificateBuilder, +) -> x509.RevokedCertificate: ... +def create_x509_crl( + builder: x509.CertificateRevocationListBuilder, + private_key: PrivateKeyTypes, + hash_algorithm: hashes.HashAlgorithm | None, + rsa_padding: PKCS1v15 | PSS | None, + ecdsa_deterministic: bool | None, +) -> x509.CertificateRevocationList: ... + +class Sct: + @property + def version(self) -> certificate_transparency.Version: ... + @property + def log_id(self) -> bytes: ... + @property + def timestamp(self) -> datetime.datetime: ... + @property + def entry_type(self) -> certificate_transparency.LogEntryType: ... + @property + def signature_hash_algorithm(self) -> hashes.HashAlgorithm: ... + @property + def signature_algorithm( + self, + ) -> certificate_transparency.SignatureAlgorithm: ... + @property + def signature(self) -> bytes: ... + @property + def extension_bytes(self) -> bytes: ... + +class Certificate: + def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: ... + @property + def serial_number(self) -> int: ... + @property + def version(self) -> x509.Version: ... + def public_key(self) -> CertificatePublicKeyTypes: ... + @property + def public_key_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def not_valid_before(self) -> datetime.datetime: ... + @property + def not_valid_before_utc(self) -> datetime.datetime: ... + @property + def not_valid_after(self) -> datetime.datetime: ... + @property + def not_valid_after_utc(self) -> datetime.datetime: ... + @property + def issuer(self) -> x509.Name: ... + @property + def subject(self) -> x509.Name: ... + @property + def signature_hash_algorithm( + self, + ) -> hashes.HashAlgorithm | None: ... + @property + def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def signature_algorithm_parameters( + self, + ) -> PSS | PKCS1v15 | ECDSA | None: ... + @property + def extensions(self) -> x509.Extensions: ... + @property + def signature(self) -> bytes: ... + @property + def tbs_certificate_bytes(self) -> bytes: ... + @property + def tbs_precertificate_bytes(self) -> bytes: ... + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + def verify_directly_issued_by(self, issuer: Certificate) -> None: ... + +class RevokedCertificate: + @property + def serial_number(self) -> int: ... + @property + def revocation_date(self) -> datetime.datetime: ... + @property + def revocation_date_utc(self) -> datetime.datetime: ... + @property + def extensions(self) -> x509.Extensions: ... + +class CertificateRevocationList: + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + def fingerprint(self, algorithm: hashes.HashAlgorithm) -> bytes: ... + def get_revoked_certificate_by_serial_number( + self, serial_number: int + ) -> x509.RevokedCertificate | None: ... + @property + def signature_hash_algorithm( + self, + ) -> hashes.HashAlgorithm | None: ... + @property + def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def signature_algorithm_parameters( + self, + ) -> PSS | PKCS1v15 | ECDSA | None: ... + @property + def issuer(self) -> x509.Name: ... + @property + def next_update(self) -> datetime.datetime | None: ... + @property + def next_update_utc(self) -> datetime.datetime | None: ... + @property + def last_update(self) -> datetime.datetime: ... + @property + def last_update_utc(self) -> datetime.datetime: ... + @property + def extensions(self) -> x509.Extensions: ... + @property + def signature(self) -> bytes: ... + @property + def tbs_certlist_bytes(self) -> bytes: ... + def __eq__(self, other: object) -> bool: ... + def __len__(self) -> int: ... + @typing.overload + def __getitem__(self, idx: int) -> x509.RevokedCertificate: ... + @typing.overload + def __getitem__(self, idx: slice) -> list[x509.RevokedCertificate]: ... + def __iter__(self) -> Iterator[x509.RevokedCertificate]: ... + def is_signature_valid( + self, public_key: CertificateIssuerPublicKeyTypes + ) -> bool: ... + +class CertificateSigningRequest: + def __eq__(self, other: object) -> bool: ... + def __hash__(self) -> int: ... + def public_key(self) -> CertificatePublicKeyTypes: ... + @property + def subject(self) -> x509.Name: ... + @property + def signature_hash_algorithm( + self, + ) -> hashes.HashAlgorithm | None: ... + @property + def signature_algorithm_oid(self) -> x509.ObjectIdentifier: ... + @property + def signature_algorithm_parameters( + self, + ) -> PSS | PKCS1v15 | ECDSA | None: ... + @property + def extensions(self) -> x509.Extensions: ... + @property + def attributes(self) -> x509.Attributes: ... + def public_bytes(self, encoding: serialization.Encoding) -> bytes: ... + @property + def signature(self) -> bytes: ... + @property + def tbs_certrequest_bytes(self) -> bytes: ... + @property + def is_signature_valid(self) -> bool: ... + +class PolicyBuilder: + def time(self, time: datetime.datetime) -> PolicyBuilder: ... + def store(self, store: Store) -> PolicyBuilder: ... + def max_chain_depth(self, max_chain_depth: int) -> PolicyBuilder: ... + def extension_policies( + self, *, ca_policy: ExtensionPolicy, ee_policy: ExtensionPolicy + ) -> PolicyBuilder: ... + def build_client_verifier(self) -> ClientVerifier: ... + def build_server_verifier( + self, subject: x509.verification.Subject + ) -> ServerVerifier: ... + +class Policy: + @property + def max_chain_depth(self) -> int: ... + @property + def subject(self) -> x509.verification.Subject | None: ... + @property + def validation_time(self) -> datetime.datetime: ... + @property + def extended_key_usage(self) -> x509.ObjectIdentifier: ... + @property + def minimum_rsa_modulus(self) -> int: ... + +class Criticality: + CRITICAL: Criticality + AGNOSTIC: Criticality + NON_CRITICAL: Criticality + +T = typing.TypeVar("T", contravariant=True, bound=x509.ExtensionType) + +MaybeExtensionValidatorCallback = typing.Callable[ + [ + Policy, + x509.Certificate, + T | None, + ], + None, +] + +PresentExtensionValidatorCallback = typing.Callable[ + [Policy, x509.Certificate, T], + None, +] + +class ExtensionPolicy: + @staticmethod + def permit_all() -> ExtensionPolicy: ... + @staticmethod + def webpki_defaults_ca() -> ExtensionPolicy: ... + @staticmethod + def webpki_defaults_ee() -> ExtensionPolicy: ... + def require_not_present( + self, extension_type: type[x509.ExtensionType] + ) -> ExtensionPolicy: ... + def may_be_present( + self, + extension_type: type[T], + criticality: Criticality, + validator: MaybeExtensionValidatorCallback[T] | None, + ) -> ExtensionPolicy: ... + def require_present( + self, + extension_type: type[T], + criticality: Criticality, + validator: PresentExtensionValidatorCallback[T] | None, + ) -> ExtensionPolicy: ... + +class VerifiedClient: + @property + def subjects(self) -> list[x509.GeneralName] | None: ... + @property + def chain(self) -> list[x509.Certificate]: ... + +class ClientVerifier: + @property + def policy(self) -> Policy: ... + @property + def store(self) -> Store: ... + def verify( + self, + leaf: x509.Certificate, + intermediates: list[x509.Certificate], + ) -> VerifiedClient: ... + +class ServerVerifier: + @property + def policy(self) -> Policy: ... + @property + def store(self) -> Store: ... + def verify( + self, + leaf: x509.Certificate, + intermediates: list[x509.Certificate], + ) -> list[x509.Certificate]: ... + +class Store: + def __init__(self, certs: list[x509.Certificate]) -> None: ... + +class VerificationError(Exception): ... diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/openssl/__init__.py b/intelliaide/vendor/cryptography/hazmat/bindings/openssl/__init__.py new file mode 100644 index 0000000..b509336 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/openssl/__init__.py @@ -0,0 +1,3 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/openssl/_conditional.py b/intelliaide/vendor/cryptography/hazmat/bindings/openssl/_conditional.py new file mode 100644 index 0000000..1e447a5 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/openssl/_conditional.py @@ -0,0 +1,199 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + + +def cryptography_has_set_cert_cb() -> list[str]: + return [ + "SSL_CTX_set_cert_cb", + "SSL_set_cert_cb", + ] + + +def cryptography_has_ssl_st() -> list[str]: + return [ + "SSL_ST_BEFORE", + "SSL_ST_OK", + "SSL_ST_INIT", + "SSL_ST_RENEGOTIATE", + ] + + +def cryptography_has_tls_st() -> list[str]: + return [ + "TLS_ST_BEFORE", + "TLS_ST_OK", + ] + + +def cryptography_has_ssl_sigalgs() -> list[str]: + return [ + "SSL_CTX_set1_sigalgs_list", + ] + + +def cryptography_has_psk() -> list[str]: + return [ + "SSL_CTX_use_psk_identity_hint", + "SSL_CTX_set_psk_server_callback", + "SSL_CTX_set_psk_client_callback", + ] + + +def cryptography_has_psk_tlsv13() -> list[str]: + return [ + "SSL_CTX_set_psk_find_session_callback", + "SSL_CTX_set_psk_use_session_callback", + "Cryptography_SSL_SESSION_new", + "SSL_CIPHER_find", + "SSL_SESSION_set1_master_key", + "SSL_SESSION_set_cipher", + "SSL_SESSION_set_protocol_version", + ] + + +def cryptography_has_custom_ext() -> list[str]: + return [ + "SSL_CTX_add_client_custom_ext", + "SSL_CTX_add_server_custom_ext", + "SSL_extension_supported", + ] + + +def cryptography_has_tlsv13_functions() -> list[str]: + return [ + "SSL_CTX_set_ciphersuites", + ] + + +def cryptography_has_tlsv13_hs_functions() -> list[str]: + return [ + "SSL_VERIFY_POST_HANDSHAKE", + "SSL_verify_client_post_handshake", + "SSL_CTX_set_post_handshake_auth", + "SSL_set_post_handshake_auth", + "SSL_SESSION_get_max_early_data", + "SSL_write_early_data", + "SSL_read_early_data", + "SSL_CTX_set_max_early_data", + ] + + +def cryptography_has_ssl_verify_client_post_handshake() -> list[str]: + return [ + "SSL_verify_client_post_handshake", + ] + + +def cryptography_has_engine() -> list[str]: + return [ + "ENGINE_by_id", + "ENGINE_init", + "ENGINE_finish", + "ENGINE_get_default_RAND", + "ENGINE_set_default_RAND", + "ENGINE_unregister_RAND", + "ENGINE_ctrl_cmd", + "ENGINE_free", + "ENGINE_get_name", + "ENGINE_ctrl_cmd_string", + "ENGINE_load_builtin_engines", + "ENGINE_load_private_key", + "ENGINE_load_public_key", + "SSL_CTX_set_client_cert_engine", + ] + + +def cryptography_has_verified_chain() -> list[str]: + return [ + "SSL_get0_verified_chain", + ] + + +def cryptography_has_srtp() -> list[str]: + return [ + "SSL_CTX_set_tlsext_use_srtp", + "SSL_set_tlsext_use_srtp", + "SSL_get_selected_srtp_profile", + ] + + +def cryptography_has_dtls_get_data_mtu() -> list[str]: + return [ + "DTLS_get_data_mtu", + ] + + +def cryptography_has_ssl_cookie() -> list[str]: + return [ + "SSL_OP_COOKIE_EXCHANGE", + "DTLS1_COOKIE_LENGTH", + "DTLSv1_listen", + "SSL_CTX_set_cookie_generate_cb", + "SSL_CTX_set_cookie_verify_cb", + ] + + +def cryptography_has_prime_checks() -> list[str]: + return [ + "BN_prime_checks_for_size", + ] + + +def cryptography_has_unexpected_eof_while_reading() -> list[str]: + return ["SSL_R_UNEXPECTED_EOF_WHILE_READING"] + + +def cryptography_has_ssl_op_ignore_unexpected_eof() -> list[str]: + return [ + "SSL_OP_IGNORE_UNEXPECTED_EOF", + ] + + +def cryptography_has_get_extms_support() -> list[str]: + return ["SSL_get_extms_support"] + + +def cryptography_has_ssl_get0_group_name() -> list[str]: + return ["SSL_get0_group_name"] + + +# This is a mapping of +# {condition: function-returning-names-dependent-on-that-condition} so we can +# loop over them and delete unsupported names at runtime. It will be removed +# when cffi supports #if in cdef. We use functions instead of just a dict of +# lists so we can use coverage to measure which are used. +CONDITIONAL_NAMES = { + "Cryptography_HAS_SET_CERT_CB": cryptography_has_set_cert_cb, + "Cryptography_HAS_SSL_ST": cryptography_has_ssl_st, + "Cryptography_HAS_TLS_ST": cryptography_has_tls_st, + "Cryptography_HAS_SIGALGS": cryptography_has_ssl_sigalgs, + "Cryptography_HAS_PSK": cryptography_has_psk, + "Cryptography_HAS_PSK_TLSv1_3": cryptography_has_psk_tlsv13, + "Cryptography_HAS_CUSTOM_EXT": cryptography_has_custom_ext, + "Cryptography_HAS_TLSv1_3_FUNCTIONS": cryptography_has_tlsv13_functions, + "Cryptography_HAS_TLSv1_3_HS_FUNCTIONS": ( + cryptography_has_tlsv13_hs_functions + ), + "Cryptography_HAS_SSL_VERIFY_CLIENT_POST_HANDSHAKE": ( + cryptography_has_ssl_verify_client_post_handshake + ), + "Cryptography_HAS_ENGINE": cryptography_has_engine, + "Cryptography_HAS_VERIFIED_CHAIN": cryptography_has_verified_chain, + "Cryptography_HAS_SRTP": cryptography_has_srtp, + "Cryptography_HAS_DTLS_GET_DATA_MTU": cryptography_has_dtls_get_data_mtu, + "Cryptography_HAS_SSL_COOKIE": cryptography_has_ssl_cookie, + "Cryptography_HAS_PRIME_CHECKS": cryptography_has_prime_checks, + "Cryptography_HAS_UNEXPECTED_EOF_WHILE_READING": ( + cryptography_has_unexpected_eof_while_reading + ), + "Cryptography_HAS_SSL_OP_IGNORE_UNEXPECTED_EOF": ( + cryptography_has_ssl_op_ignore_unexpected_eof + ), + "Cryptography_HAS_GET_EXTMS_SUPPORT": cryptography_has_get_extms_support, + "Cryptography_HAS_SSL_GET0_GROUP_NAME": ( + cryptography_has_ssl_get0_group_name + ), +} diff --git a/intelliaide/vendor/cryptography/hazmat/bindings/openssl/binding.py b/intelliaide/vendor/cryptography/hazmat/bindings/openssl/binding.py new file mode 100644 index 0000000..6a0cafd --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/bindings/openssl/binding.py @@ -0,0 +1,122 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import os +import sys +import threading +import types +import typing +import warnings +from collections.abc import Callable + +import cryptography +from cryptography.exceptions import InternalError +from cryptography.hazmat.bindings._rust import _openssl, openssl +from cryptography.hazmat.bindings.openssl._conditional import CONDITIONAL_NAMES + + +def _openssl_assert(ok: bool) -> None: + if not ok: + errors = openssl.capture_error_stack() + + raise InternalError( + "Unknown OpenSSL error. This error is commonly encountered when " + "another library is not cleaning up the OpenSSL error stack. If " + "you are using cryptography with another library that uses " + "OpenSSL try disabling it before reporting a bug. Otherwise " + "please file an issue at https://github.com/pyca/cryptography/" + "issues with information on how to reproduce " + f"this. ({errors!r})", + errors, + ) + + +def build_conditional_library( + lib: typing.Any, + conditional_names: dict[str, Callable[[], list[str]]], +) -> typing.Any: + conditional_lib = types.ModuleType("lib") + conditional_lib._original_lib = lib # type: ignore[attr-defined] + excluded_names = set() + for condition, names_cb in conditional_names.items(): + if not getattr(lib, condition): + excluded_names.update(names_cb()) + + for attr in dir(lib): + if attr not in excluded_names: + setattr(conditional_lib, attr, getattr(lib, attr)) + + return conditional_lib + + +class Binding: + """ + OpenSSL API wrapper. + """ + + lib: typing.ClassVar[typing.Any] = None + ffi: typing.Any = _openssl.ffi + _lib_loaded = False + _init_lock = threading.Lock() + + def __init__(self) -> None: + self._ensure_ffi_initialized() + + @classmethod + def _ensure_ffi_initialized(cls) -> None: + with cls._init_lock: + if not cls._lib_loaded: + cls.lib = build_conditional_library( + _openssl.lib, CONDITIONAL_NAMES + ) + cls._lib_loaded = True + + @classmethod + def init_static_locks(cls) -> None: + cls._ensure_ffi_initialized() + + +def _verify_package_version(version: str) -> None: + # Occasionally we run into situations where the version of the Python + # package does not match the version of the shared object that is loaded. + # This may occur in environments where multiple versions of cryptography + # are installed and available in the python path. To avoid errors cropping + # up later this code checks that the currently imported package and the + # shared object that were loaded have the same version and raise an + # ImportError if they do not + so_package_version = _openssl.ffi.string( + _openssl.lib.CRYPTOGRAPHY_PACKAGE_VERSION + ) + if version.encode("ascii") != so_package_version: + raise ImportError( + "The version of cryptography does not match the loaded " + "shared object. This can happen if you have multiple copies of " + "cryptography installed in your Python path. Please try creating " + "a new virtual environment to resolve this issue. " + f"Loaded python version: {version}, " + f"shared object version: {so_package_version}" + ) + + _openssl_assert( + _openssl.lib.OpenSSL_version_num() == openssl.openssl_version(), + ) + + +_verify_package_version(cryptography.__version__) + +Binding.init_static_locks() + +if ( + sys.platform == "win32" + and os.environ.get("PROCESSOR_ARCHITEW6432") is not None +): + warnings.warn( + "You are using cryptography on a 32-bit Python on a 64-bit Windows " + "Operating System. Cryptography will be significantly faster if you " + "switch to using a 64-bit Python.", + UserWarning, + stacklevel=2, + ) diff --git a/intelliaide/vendor/cryptography/hazmat/decrepit/__init__.py b/intelliaide/vendor/cryptography/hazmat/decrepit/__init__.py new file mode 100644 index 0000000..41d7318 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/decrepit/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations diff --git a/intelliaide/vendor/cryptography/hazmat/decrepit/ciphers/__init__.py b/intelliaide/vendor/cryptography/hazmat/decrepit/ciphers/__init__.py new file mode 100644 index 0000000..41d7318 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/decrepit/ciphers/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations diff --git a/intelliaide/vendor/cryptography/hazmat/decrepit/ciphers/algorithms.py b/intelliaide/vendor/cryptography/hazmat/decrepit/ciphers/algorithms.py new file mode 100644 index 0000000..703c8e4 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/decrepit/ciphers/algorithms.py @@ -0,0 +1,142 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import warnings + +from cryptography import utils +from cryptography.hazmat.primitives._cipheralgorithm import ( + BlockCipherAlgorithm, + CipherAlgorithm, + _verify_key_size, +) + + +class ARC4(CipherAlgorithm): + name = "RC4" + key_sizes = frozenset([40, 56, 64, 80, 128, 160, 192, 256]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class TripleDES(BlockCipherAlgorithm): + name = "3DES" + block_size = 64 + key_sizes = frozenset([64, 128, 192]) + + def __init__(self, key: bytes): + if len(key) == 8: + warnings.warn( + "Single-key TripleDES (8-byte keys) is deprecated and " + "support will be removed in a future release. Use 24-byte " + "keys instead (e.g., key + key + key).", + utils.DeprecatedIn47, + stacklevel=2, + ) + key = key + key + key + elif len(key) == 16: + warnings.warn( + "Two-key TripleDES (16-byte keys) is deprecated and " + "support will be removed in a future release. Use 24-byte " + "keys instead (e.g., key + key[:8]).", + utils.DeprecatedIn47, + stacklevel=2, + ) + key = key + key[:8] + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +# Not actually supported, marker for tests +class _DES: + key_size = 64 + + +class Blowfish(BlockCipherAlgorithm): + name = "Blowfish" + block_size = 64 + key_sizes = frozenset(range(32, 449, 8)) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class CAST5(BlockCipherAlgorithm): + name = "CAST5" + block_size = 64 + key_sizes = frozenset(range(40, 129, 8)) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class SEED(BlockCipherAlgorithm): + name = "SEED" + block_size = 128 + key_sizes = frozenset([128]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class IDEA(BlockCipherAlgorithm): + name = "IDEA" + block_size = 64 + key_sizes = frozenset([128]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class Camellia(BlockCipherAlgorithm): + name = "camellia" + block_size = 128 + key_sizes = frozenset([128, 192, 256]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +# This class only allows RC2 with a 128-bit key. No support for +# effective key bits or other key sizes is provided. +class RC2(BlockCipherAlgorithm): + name = "RC2" + block_size = 64 + key_sizes = frozenset([128]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 diff --git a/intelliaide/vendor/cryptography/hazmat/decrepit/ciphers/modes.py b/intelliaide/vendor/cryptography/hazmat/decrepit/ciphers/modes.py new file mode 100644 index 0000000..1786bb0 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/decrepit/ciphers/modes.py @@ -0,0 +1,53 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography import utils +from cryptography.hazmat.primitives._modes import ( + ModeWithInitializationVector, + _check_iv_and_key_length, +) + + +class OFB(ModeWithInitializationVector): + name = "OFB" + + def __init__(self, initialization_vector: utils.Buffer): + utils._check_byteslike("initialization_vector", initialization_vector) + self._initialization_vector = initialization_vector + + @property + def initialization_vector(self) -> utils.Buffer: + return self._initialization_vector + + validate_for_algorithm = _check_iv_and_key_length + + +class CFB(ModeWithInitializationVector): + name = "CFB" + + def __init__(self, initialization_vector: utils.Buffer): + utils._check_byteslike("initialization_vector", initialization_vector) + self._initialization_vector = initialization_vector + + @property + def initialization_vector(self) -> utils.Buffer: + return self._initialization_vector + + validate_for_algorithm = _check_iv_and_key_length + + +class CFB8(ModeWithInitializationVector): + name = "CFB8" + + def __init__(self, initialization_vector: utils.Buffer): + utils._check_byteslike("initialization_vector", initialization_vector) + self._initialization_vector = initialization_vector + + @property + def initialization_vector(self) -> utils.Buffer: + return self._initialization_vector + + validate_for_algorithm = _check_iv_and_key_length diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/__init__.py b/intelliaide/vendor/cryptography/hazmat/primitives/__init__.py new file mode 100644 index 0000000..b509336 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/__init__.py @@ -0,0 +1,3 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/_asymmetric.py b/intelliaide/vendor/cryptography/hazmat/primitives/_asymmetric.py new file mode 100644 index 0000000..ea55ffd --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/_asymmetric.py @@ -0,0 +1,19 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +# This exists to break an import cycle. It is normally accessible from the +# asymmetric padding module. + + +class AsymmetricPadding(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def name(self) -> str: + """ + A string naming this padding (e.g. "PSS", "PKCS1"). + """ diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/_cipheralgorithm.py b/intelliaide/vendor/cryptography/hazmat/primitives/_cipheralgorithm.py new file mode 100644 index 0000000..305a9fd --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/_cipheralgorithm.py @@ -0,0 +1,60 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography import utils + +# This exists to break an import cycle. It is normally accessible from the +# ciphers module. + + +class CipherAlgorithm(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def name(self) -> str: + """ + A string naming this mode (e.g. "AES", "Camellia"). + """ + + @property + @abc.abstractmethod + def key_sizes(self) -> frozenset[int]: + """ + Valid key sizes for this algorithm in bits + """ + + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + The size of the key being used as an integer in bits (e.g. 128, 256). + """ + + +class BlockCipherAlgorithm(CipherAlgorithm): + key: utils.Buffer + + @property + @abc.abstractmethod + def block_size(self) -> int: + """ + The size of a block as an integer in bits (e.g. 64, 128). + """ + + +def _verify_key_size( + algorithm: CipherAlgorithm, key: utils.Buffer +) -> utils.Buffer: + # Verify that the key is instance of bytes + utils._check_byteslike("key", key) + + # Verify that the key size matches the expected key size + if len(key) * 8 not in algorithm.key_sizes: + raise ValueError( + f"Invalid key size ({len(key) * 8}) for {algorithm.name}." + ) + return key diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/_modes.py b/intelliaide/vendor/cryptography/hazmat/primitives/_modes.py new file mode 100644 index 0000000..deae8bc --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/_modes.py @@ -0,0 +1,105 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography import utils +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.primitives._cipheralgorithm import ( + BlockCipherAlgorithm, + CipherAlgorithm, +) + + +class Mode(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def name(self) -> str: + """ + A string naming this mode (e.g. "ECB", "CBC"). + """ + + @abc.abstractmethod + def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: + """ + Checks that all the necessary invariants of this (mode, algorithm) + combination are met. + """ + + +class ModeWithInitializationVector(Mode, metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def initialization_vector(self) -> utils.Buffer: + """ + The value of the initialization vector for this mode as bytes. + """ + + +class ModeWithTweak(Mode, metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def tweak(self) -> utils.Buffer: + """ + The value of the tweak for this mode as bytes. + """ + + +class ModeWithNonce(Mode, metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def nonce(self) -> utils.Buffer: + """ + The value of the nonce for this mode as bytes. + """ + + +class ModeWithAuthenticationTag(Mode, metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def tag(self) -> bytes | None: + """ + The value of the tag supplied to the constructor of this mode. + """ + + +def _check_aes_key_length(self: Mode, algorithm: CipherAlgorithm) -> None: + if algorithm.key_size > 256 and algorithm.name == "AES": + raise ValueError( + "Only 128, 192, and 256 bit keys are allowed for this AES mode" + ) + + +def _check_iv_length( + self: ModeWithInitializationVector, algorithm: BlockCipherAlgorithm +) -> None: + iv_len = len(self.initialization_vector) + if iv_len * 8 != algorithm.block_size: + raise ValueError(f"Invalid IV size ({iv_len}) for {self.name}.") + + +def _check_nonce_length( + nonce: utils.Buffer, name: str, algorithm: CipherAlgorithm +) -> None: + if not isinstance(algorithm, BlockCipherAlgorithm): + raise UnsupportedAlgorithm( + f"{name} requires a block cipher algorithm", + _Reasons.UNSUPPORTED_CIPHER, + ) + if len(nonce) * 8 != algorithm.block_size: + raise ValueError(f"Invalid nonce size ({len(nonce)}) for {name}.") + + +def _check_iv_and_key_length( + self: ModeWithInitializationVector, algorithm: CipherAlgorithm +) -> None: + if not isinstance(algorithm, BlockCipherAlgorithm): + raise UnsupportedAlgorithm( + f"{self} requires a block cipher algorithm", + _Reasons.UNSUPPORTED_CIPHER, + ) + _check_aes_key_length(self, algorithm) + _check_iv_length(self, algorithm) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/_serialization.py b/intelliaide/vendor/cryptography/hazmat/primitives/_serialization.py new file mode 100644 index 0000000..9c3c474 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/_serialization.py @@ -0,0 +1,136 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography import utils +from cryptography.hazmat.bindings._rust import Encoding as Encoding +from cryptography.hazmat.bindings._rust import ( + ParameterFormat as ParameterFormat, +) +from cryptography.hazmat.bindings._rust import PrivateFormat as PrivateFormat +from cryptography.hazmat.bindings._rust import PublicFormat as PublicFormat +from cryptography.hazmat.primitives.hashes import HashAlgorithm + +# This exists to break an import cycle. These classes are normally accessible +# from the serialization module. + + +class PBES(utils.Enum): + PBESv1SHA1And3KeyTripleDESCBC = "PBESv1 using SHA1 and 3-Key TripleDES" + PBESv2SHA256AndAES256CBC = "PBESv2 using SHA256 PBKDF2 and AES256 CBC" + + +class KeySerializationEncryption(metaclass=abc.ABCMeta): + pass + + +class BestAvailableEncryption(KeySerializationEncryption): + def __init__(self, password: bytes): + if not isinstance(password, bytes) or len(password) == 0: + raise ValueError("Password must be 1 or more bytes.") + + self.password = password + + +class NoEncryption(KeySerializationEncryption): + pass + + +class KeySerializationEncryptionBuilder: + def __init__( + self, + format: PrivateFormat, + *, + _kdf_rounds: int | None = None, + _hmac_hash: HashAlgorithm | None = None, + _key_cert_algorithm: PBES | None = None, + ) -> None: + self._format = format + + self._kdf_rounds = _kdf_rounds + self._hmac_hash = _hmac_hash + self._key_cert_algorithm = _key_cert_algorithm + + def kdf_rounds(self, rounds: int) -> KeySerializationEncryptionBuilder: + if self._kdf_rounds is not None: + raise ValueError("kdf_rounds already set") + + if not isinstance(rounds, int): + raise TypeError("kdf_rounds must be an integer") + + if rounds < 1: + raise ValueError("kdf_rounds must be a positive integer") + + return KeySerializationEncryptionBuilder( + self._format, + _kdf_rounds=rounds, + _hmac_hash=self._hmac_hash, + _key_cert_algorithm=self._key_cert_algorithm, + ) + + def hmac_hash( + self, algorithm: HashAlgorithm + ) -> KeySerializationEncryptionBuilder: + if self._format is not PrivateFormat.PKCS12: + raise TypeError( + "hmac_hash only supported with PrivateFormat.PKCS12" + ) + + if self._hmac_hash is not None: + raise ValueError("hmac_hash already set") + return KeySerializationEncryptionBuilder( + self._format, + _kdf_rounds=self._kdf_rounds, + _hmac_hash=algorithm, + _key_cert_algorithm=self._key_cert_algorithm, + ) + + def key_cert_algorithm( + self, algorithm: PBES + ) -> KeySerializationEncryptionBuilder: + if self._format is not PrivateFormat.PKCS12: + raise TypeError( + "key_cert_algorithm only supported with PrivateFormat.PKCS12" + ) + if self._key_cert_algorithm is not None: + raise ValueError("key_cert_algorithm already set") + return KeySerializationEncryptionBuilder( + self._format, + _kdf_rounds=self._kdf_rounds, + _hmac_hash=self._hmac_hash, + _key_cert_algorithm=algorithm, + ) + + def build(self, password: bytes) -> KeySerializationEncryption: + if not isinstance(password, bytes) or len(password) == 0: + raise ValueError("Password must be 1 or more bytes.") + + return _KeySerializationEncryption( + self._format, + password, + kdf_rounds=self._kdf_rounds, + hmac_hash=self._hmac_hash, + key_cert_algorithm=self._key_cert_algorithm, + ) + + +class _KeySerializationEncryption(KeySerializationEncryption): + def __init__( + self, + format: PrivateFormat, + password: bytes, + *, + kdf_rounds: int | None, + hmac_hash: HashAlgorithm | None, + key_cert_algorithm: PBES | None, + ): + self._format = format + self.password = password + + self._kdf_rounds = kdf_rounds + self._hmac_hash = hmac_hash + self._key_cert_algorithm = key_cert_algorithm diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/__init__.py b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/__init__.py new file mode 100644 index 0000000..b509336 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/__init__.py @@ -0,0 +1,3 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/dh.py b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/dh.py new file mode 100644 index 0000000..2f6b834 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/dh.py @@ -0,0 +1,159 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization + +generate_parameters = rust_openssl.dh.generate_parameters + + +DHPrivateNumbers = rust_openssl.dh.DHPrivateNumbers +DHPublicNumbers = rust_openssl.dh.DHPublicNumbers +DHParameterNumbers = rust_openssl.dh.DHParameterNumbers + + +class DHParameters(metaclass=abc.ABCMeta): + @abc.abstractmethod + def generate_private_key(self) -> DHPrivateKey: + """ + Generates and returns a DHPrivateKey. + """ + + @abc.abstractmethod + def parameter_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.ParameterFormat, + ) -> bytes: + """ + Returns the parameters serialized as bytes. + """ + + @abc.abstractmethod + def parameter_numbers(self) -> DHParameterNumbers: + """ + Returns a DHParameterNumbers. + """ + + +DHParametersWithSerialization = DHParameters +DHParameters.register(rust_openssl.dh.DHParameters) + + +class DHPublicKey(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + The bit length of the prime modulus. + """ + + @abc.abstractmethod + def parameters(self) -> DHParameters: + """ + The DHParameters object associated with this public key. + """ + + @abc.abstractmethod + def public_numbers(self) -> DHPublicNumbers: + """ + Returns a DHPublicNumbers. + """ + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + Returns the key serialized as bytes. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> DHPublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> DHPublicKey: + """ + Returns a deep copy. + """ + + +DHPublicKeyWithSerialization = DHPublicKey +DHPublicKey.register(rust_openssl.dh.DHPublicKey) + + +class DHPrivateKey(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + The bit length of the prime modulus. + """ + + @abc.abstractmethod + def public_key(self) -> DHPublicKey: + """ + The DHPublicKey associated with this private key. + """ + + @abc.abstractmethod + def parameters(self) -> DHParameters: + """ + The DHParameters object associated with this private key. + """ + + @abc.abstractmethod + def exchange(self, peer_public_key: DHPublicKey) -> bytes: + """ + Given peer's DHPublicKey, carry out the key exchange and + return shared key as bytes. + """ + + @abc.abstractmethod + def private_numbers(self) -> DHPrivateNumbers: + """ + Returns a DHPrivateNumbers. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + Returns the key serialized as bytes. + """ + + @abc.abstractmethod + def __copy__(self) -> DHPrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> DHPrivateKey: + """ + Returns a deep copy. + """ + + +DHPrivateKeyWithSerialization = DHPrivateKey +DHPrivateKey.register(rust_openssl.dh.DHPrivateKey) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/dsa.py b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/dsa.py new file mode 100644 index 0000000..f245557 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/dsa.py @@ -0,0 +1,179 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc +import typing + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization, hashes +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils +from cryptography.utils import Buffer + + +class DSAParameters(metaclass=abc.ABCMeta): + @abc.abstractmethod + def generate_private_key(self) -> DSAPrivateKey: + """ + Generates and returns a DSAPrivateKey. + """ + + @abc.abstractmethod + def parameter_numbers(self) -> DSAParameterNumbers: + """ + Returns a DSAParameterNumbers. + """ + + +DSAParametersWithNumbers = DSAParameters +DSAParameters.register(rust_openssl.dsa.DSAParameters) + + +class DSAPrivateKey(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + The bit length of the prime modulus. + """ + + @abc.abstractmethod + def public_key(self) -> DSAPublicKey: + """ + The DSAPublicKey associated with this private key. + """ + + @abc.abstractmethod + def parameters(self) -> DSAParameters: + """ + The DSAParameters object associated with this private key. + """ + + @abc.abstractmethod + def sign( + self, + data: Buffer, + algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, + ) -> bytes: + """ + Signs the data + """ + + @abc.abstractmethod + def private_numbers(self) -> DSAPrivateNumbers: + """ + Returns a DSAPrivateNumbers. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + Returns the key serialized as bytes. + """ + + @abc.abstractmethod + def __copy__(self) -> DSAPrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> DSAPrivateKey: + """ + Returns a deep copy. + """ + + +DSAPrivateKeyWithSerialization = DSAPrivateKey +DSAPrivateKey.register(rust_openssl.dsa.DSAPrivateKey) + + +class DSAPublicKey(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + The bit length of the prime modulus. + """ + + @abc.abstractmethod + def parameters(self) -> DSAParameters: + """ + The DSAParameters object associated with this public key. + """ + + @abc.abstractmethod + def public_numbers(self) -> DSAPublicNumbers: + """ + Returns a DSAPublicNumbers. + """ + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + Returns the key serialized as bytes. + """ + + @abc.abstractmethod + def verify( + self, + signature: Buffer, + data: Buffer, + algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, + ) -> None: + """ + Verifies the signature of the data. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> DSAPublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> DSAPublicKey: + """ + Returns a deep copy. + """ + + +DSAPublicKeyWithSerialization = DSAPublicKey +DSAPublicKey.register(rust_openssl.dsa.DSAPublicKey) + +DSAPrivateNumbers = rust_openssl.dsa.DSAPrivateNumbers +DSAPublicNumbers = rust_openssl.dsa.DSAPublicNumbers +DSAParameterNumbers = rust_openssl.dsa.DSAParameterNumbers + + +def generate_parameters( + key_size: int, backend: typing.Any = None +) -> DSAParameters: + if key_size not in (1024, 2048, 3072, 4096): + raise ValueError("Key size must be 1024, 2048, 3072, or 4096 bits.") + + return rust_openssl.dsa.generate_parameters(key_size) + + +def generate_private_key( + key_size: int, backend: typing.Any = None +) -> DSAPrivateKey: + parameters = generate_parameters(key_size) + return parameters.generate_private_key() diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/ec.py b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/ec.py new file mode 100644 index 0000000..39e6751 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/ec.py @@ -0,0 +1,369 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc +import typing + +from cryptography import utils +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat._oid import ObjectIdentifier +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization, hashes +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils + + +class EllipticCurveOID: + SECP192R1 = ObjectIdentifier("1.2.840.10045.3.1.1") + SECP224R1 = ObjectIdentifier("1.3.132.0.33") + SECP256K1 = ObjectIdentifier("1.3.132.0.10") + SECP256R1 = ObjectIdentifier("1.2.840.10045.3.1.7") + SECP384R1 = ObjectIdentifier("1.3.132.0.34") + SECP521R1 = ObjectIdentifier("1.3.132.0.35") + BRAINPOOLP256R1 = ObjectIdentifier("1.3.36.3.3.2.8.1.1.7") + BRAINPOOLP384R1 = ObjectIdentifier("1.3.36.3.3.2.8.1.1.11") + BRAINPOOLP512R1 = ObjectIdentifier("1.3.36.3.3.2.8.1.1.13") + + +class EllipticCurve(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def name(self) -> str: + """ + The name of the curve. e.g. secp256r1. + """ + + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + Bit size of a secret scalar for the curve. + """ + + @property + @abc.abstractmethod + def group_order(self) -> int: + """ + The order of the curve's group. + """ + + +class EllipticCurveSignatureAlgorithm(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def algorithm( + self, + ) -> asym_utils.Prehashed | hashes.HashAlgorithm: + """ + The digest algorithm used with this signature. + """ + + +class EllipticCurvePrivateKey(metaclass=abc.ABCMeta): + @abc.abstractmethod + def exchange( + self, algorithm: ECDH, peer_public_key: EllipticCurvePublicKey + ) -> bytes: + """ + Performs a key exchange operation using the provided algorithm with the + provided peer's public key. + """ + + @abc.abstractmethod + def public_key(self) -> EllipticCurvePublicKey: + """ + The EllipticCurvePublicKey for this private key. + """ + + @property + @abc.abstractmethod + def curve(self) -> EllipticCurve: + """ + The EllipticCurve that this key is on. + """ + + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + Bit size of a secret scalar for the curve. + """ + + @abc.abstractmethod + def sign( + self, + data: utils.Buffer, + signature_algorithm: EllipticCurveSignatureAlgorithm, + ) -> bytes: + """ + Signs the data + """ + + @abc.abstractmethod + def private_numbers(self) -> EllipticCurvePrivateNumbers: + """ + Returns an EllipticCurvePrivateNumbers. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + Returns the key serialized as bytes. + """ + + @abc.abstractmethod + def __copy__(self) -> EllipticCurvePrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> EllipticCurvePrivateKey: + """ + Returns a deep copy. + """ + + +EllipticCurvePrivateKeyWithSerialization = EllipticCurvePrivateKey +EllipticCurvePrivateKey.register(rust_openssl.ec.ECPrivateKey) + + +class EllipticCurvePublicKey(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def curve(self) -> EllipticCurve: + """ + The EllipticCurve that this key is on. + """ + + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + Bit size of a secret scalar for the curve. + """ + + @abc.abstractmethod + def public_numbers(self) -> EllipticCurvePublicNumbers: + """ + Returns an EllipticCurvePublicNumbers. + """ + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + Returns the key serialized as bytes. + """ + + @abc.abstractmethod + def verify( + self, + signature: utils.Buffer, + data: utils.Buffer, + signature_algorithm: EllipticCurveSignatureAlgorithm, + ) -> None: + """ + Verifies the signature of the data. + """ + + @classmethod + def from_encoded_point( + cls, curve: EllipticCurve, data: bytes + ) -> EllipticCurvePublicKey: + utils._check_bytes("data", data) + + if len(data) == 0: + raise ValueError("data must not be an empty byte string") + + if data[0] not in [0x02, 0x03, 0x04]: + raise ValueError("Unsupported elliptic curve point type") + + return rust_openssl.ec.from_public_bytes(curve, data) + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> EllipticCurvePublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> EllipticCurvePublicKey: + """ + Returns a deep copy. + """ + + +EllipticCurvePublicKeyWithSerialization = EllipticCurvePublicKey +EllipticCurvePublicKey.register(rust_openssl.ec.ECPublicKey) + +EllipticCurvePrivateNumbers = rust_openssl.ec.EllipticCurvePrivateNumbers +EllipticCurvePublicNumbers = rust_openssl.ec.EllipticCurvePublicNumbers + + +class SECP521R1(EllipticCurve): + name = "secp521r1" + key_size = 521 + group_order = 0x1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409 # noqa: E501 + + +class SECP384R1(EllipticCurve): + name = "secp384r1" + key_size = 384 + group_order = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973 # noqa: E501 + + +class SECP256R1(EllipticCurve): + name = "secp256r1" + key_size = 256 + group_order = ( + 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 + ) + + +class SECP256K1(EllipticCurve): + name = "secp256k1" + key_size = 256 + group_order = ( + 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 + ) + + +class SECP224R1(EllipticCurve): + name = "secp224r1" + key_size = 224 + group_order = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D + + +class SECP192R1(EllipticCurve): + name = "secp192r1" + key_size = 192 + group_order = 0xFFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831 + + +class BrainpoolP256R1(EllipticCurve): + name = "brainpoolP256r1" + key_size = 256 + group_order = ( + 0xA9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7 + ) + + +class BrainpoolP384R1(EllipticCurve): + name = "brainpoolP384r1" + key_size = 384 + group_order = 0x8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC3103B883202E9046565 # noqa: E501 + + +class BrainpoolP512R1(EllipticCurve): + name = "brainpoolP512r1" + key_size = 512 + group_order = 0xAADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA92619418661197FAC10471DB1D381085DDADDB58796829CA90069 # noqa: E501 + + +_CURVE_TYPES: dict[str, EllipticCurve] = { + "prime192v1": SECP192R1(), + "prime256v1": SECP256R1(), + "secp192r1": SECP192R1(), + "secp224r1": SECP224R1(), + "secp256r1": SECP256R1(), + "secp384r1": SECP384R1(), + "secp521r1": SECP521R1(), + "secp256k1": SECP256K1(), + "brainpoolP256r1": BrainpoolP256R1(), + "brainpoolP384r1": BrainpoolP384R1(), + "brainpoolP512r1": BrainpoolP512R1(), +} + + +class ECDSA(EllipticCurveSignatureAlgorithm): + def __init__( + self, + algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, + deterministic_signing: bool = False, + ): + from cryptography.hazmat.backends.openssl.backend import backend + + if ( + deterministic_signing + and not backend.ecdsa_deterministic_supported() + ): + raise UnsupportedAlgorithm( + "ECDSA with deterministic signature (RFC 6979) is not " + "supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + self._algorithm = algorithm + self._deterministic_signing = deterministic_signing + + @property + def algorithm( + self, + ) -> asym_utils.Prehashed | hashes.HashAlgorithm: + return self._algorithm + + @property + def deterministic_signing( + self, + ) -> bool: + return self._deterministic_signing + + +generate_private_key = rust_openssl.ec.generate_private_key + + +def derive_private_key( + private_value: int, + curve: EllipticCurve, + backend: typing.Any = None, +) -> EllipticCurvePrivateKey: + if not isinstance(private_value, int): + raise TypeError("private_value must be an integer type.") + + if private_value <= 0: + raise ValueError("private_value must be a positive integer.") + + return rust_openssl.ec.derive_private_key(private_value, curve) + + +class ECDH: + pass + + +_OID_TO_CURVE = { + EllipticCurveOID.SECP192R1: SECP192R1, + EllipticCurveOID.SECP224R1: SECP224R1, + EllipticCurveOID.SECP256K1: SECP256K1, + EllipticCurveOID.SECP256R1: SECP256R1, + EllipticCurveOID.SECP384R1: SECP384R1, + EllipticCurveOID.SECP521R1: SECP521R1, + EllipticCurveOID.BRAINPOOLP256R1: BrainpoolP256R1, + EllipticCurveOID.BRAINPOOLP384R1: BrainpoolP384R1, + EllipticCurveOID.BRAINPOOLP512R1: BrainpoolP512R1, +} + + +def get_curve_for_oid(oid: ObjectIdentifier) -> type[EllipticCurve]: + try: + return _OID_TO_CURVE[oid] + except KeyError: + raise LookupError( + "The provided object identifier has no matching elliptic " + "curve class" + ) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/ed25519.py b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/ed25519.py new file mode 100644 index 0000000..70aec5b --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/ed25519.py @@ -0,0 +1,116 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer + + +class Ed25519PublicKey(metaclass=abc.ABCMeta): + @classmethod + def from_public_bytes(cls, data: bytes) -> Ed25519PublicKey: + return rust_openssl.ed25519.from_public_bytes(data) + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + """ + + @abc.abstractmethod + def verify(self, signature: Buffer, data: Buffer) -> None: + """ + Verify the signature. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> Ed25519PublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> Ed25519PublicKey: + """ + Returns a deep copy. + """ + + +Ed25519PublicKey.register(rust_openssl.ed25519.Ed25519PublicKey) + + +class Ed25519PrivateKey(metaclass=abc.ABCMeta): + @classmethod + def generate(cls) -> Ed25519PrivateKey: + return rust_openssl.ed25519.generate_key() + + @classmethod + def from_private_bytes(cls, data: Buffer) -> Ed25519PrivateKey: + return rust_openssl.ed25519.from_private_bytes(data) + + @abc.abstractmethod + def public_key(self) -> Ed25519PublicKey: + """ + The Ed25519PublicKey derived from the private key. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + The serialized bytes of the private key. + """ + + @abc.abstractmethod + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key. + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + """ + + @abc.abstractmethod + def sign(self, data: Buffer) -> bytes: + """ + Signs the data. + """ + + @abc.abstractmethod + def __copy__(self) -> Ed25519PrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> Ed25519PrivateKey: + """ + Returns a deep copy. + """ + + +Ed25519PrivateKey.register(rust_openssl.ed25519.Ed25519PrivateKey) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/ed448.py b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/ed448.py new file mode 100644 index 0000000..9ecb478 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/ed448.py @@ -0,0 +1,143 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer + + +class Ed448PublicKey(metaclass=abc.ABCMeta): + @classmethod + def from_public_bytes(cls, data: bytes) -> Ed448PublicKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.ed448_supported(): + raise UnsupportedAlgorithm( + "ed448 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.ed448.from_public_bytes(data) + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + """ + + @abc.abstractmethod + def verify(self, signature: Buffer, data: Buffer) -> None: + """ + Verify the signature. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> Ed448PublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> Ed448PublicKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "ed448"): + Ed448PublicKey.register(rust_openssl.ed448.Ed448PublicKey) + + +class Ed448PrivateKey(metaclass=abc.ABCMeta): + @classmethod + def generate(cls) -> Ed448PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.ed448_supported(): + raise UnsupportedAlgorithm( + "ed448 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.ed448.generate_key() + + @classmethod + def from_private_bytes(cls, data: Buffer) -> Ed448PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.ed448_supported(): + raise UnsupportedAlgorithm( + "ed448 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.ed448.from_private_bytes(data) + + @abc.abstractmethod + def public_key(self) -> Ed448PublicKey: + """ + The Ed448PublicKey derived from the private key. + """ + + @abc.abstractmethod + def sign(self, data: Buffer) -> bytes: + """ + Signs the data. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + The serialized bytes of the private key. + """ + + @abc.abstractmethod + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key. + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + """ + + @abc.abstractmethod + def __copy__(self) -> Ed448PrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> Ed448PrivateKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "x448"): + Ed448PrivateKey.register(rust_openssl.ed448.Ed448PrivateKey) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/mldsa.py b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/mldsa.py new file mode 100644 index 0000000..0bd9684 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/mldsa.py @@ -0,0 +1,441 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer + + +class MLDSA44PublicKey(metaclass=abc.ABCMeta): + @classmethod + def from_public_bytes(cls, data: bytes) -> MLDSA44PublicKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mldsa_supported(): + raise UnsupportedAlgorithm( + "ML-DSA-44 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mldsa.from_mldsa44_public_bytes(data) + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + + The public key is 1,312 bytes for MLDSA-44. + """ + + @abc.abstractmethod + def verify( + self, + signature: Buffer, + data: Buffer, + context: Buffer | None = None, + ) -> None: + """ + Verify the signature. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> MLDSA44PublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLDSA44PublicKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mldsa"): + MLDSA44PublicKey.register(rust_openssl.mldsa.MLDSA44PublicKey) + + +class MLDSA44PrivateKey(metaclass=abc.ABCMeta): + @classmethod + def generate(cls) -> MLDSA44PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mldsa_supported(): + raise UnsupportedAlgorithm( + "ML-DSA-44 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mldsa.generate_mldsa44_key() + + @classmethod + def from_seed_bytes(cls, data: Buffer) -> MLDSA44PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mldsa_supported(): + raise UnsupportedAlgorithm( + "ML-DSA-44 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mldsa.from_mldsa44_seed_bytes(data) + + @abc.abstractmethod + def public_key(self) -> MLDSA44PublicKey: + """ + The MLDSA44PublicKey derived from the private key. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + The serialized bytes of the private key. + + This method only returns the serialization of the seed form of the + private key, never the expanded one. + """ + + @abc.abstractmethod + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key. + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + + This method only returns the seed form of the private key (32 bytes). + """ + + @abc.abstractmethod + def sign(self, data: Buffer, context: Buffer | None = None) -> bytes: + """ + Signs the data. + """ + + @abc.abstractmethod + def __copy__(self) -> MLDSA44PrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLDSA44PrivateKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mldsa"): + MLDSA44PrivateKey.register(rust_openssl.mldsa.MLDSA44PrivateKey) + + +class MLDSA65PublicKey(metaclass=abc.ABCMeta): + @classmethod + def from_public_bytes(cls, data: bytes) -> MLDSA65PublicKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mldsa_supported(): + raise UnsupportedAlgorithm( + "ML-DSA-65 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mldsa.from_mldsa65_public_bytes(data) + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + + The public key is 1,952 bytes for MLDSA-65. + """ + + @abc.abstractmethod + def verify( + self, + signature: Buffer, + data: Buffer, + context: Buffer | None = None, + ) -> None: + """ + Verify the signature. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> MLDSA65PublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLDSA65PublicKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mldsa"): + MLDSA65PublicKey.register(rust_openssl.mldsa.MLDSA65PublicKey) + + +class MLDSA65PrivateKey(metaclass=abc.ABCMeta): + @classmethod + def generate(cls) -> MLDSA65PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mldsa_supported(): + raise UnsupportedAlgorithm( + "ML-DSA-65 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mldsa.generate_mldsa65_key() + + @classmethod + def from_seed_bytes(cls, data: Buffer) -> MLDSA65PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mldsa_supported(): + raise UnsupportedAlgorithm( + "ML-DSA-65 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mldsa.from_mldsa65_seed_bytes(data) + + @abc.abstractmethod + def public_key(self) -> MLDSA65PublicKey: + """ + The MLDSA65PublicKey derived from the private key. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + The serialized bytes of the private key. + + This method only returns the serialization of the seed form of the + private key, never the expanded one. + """ + + @abc.abstractmethod + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key. + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + + This method only returns the seed form of the private key (32 bytes). + """ + + @abc.abstractmethod + def sign(self, data: Buffer, context: Buffer | None = None) -> bytes: + """ + Signs the data. + """ + + @abc.abstractmethod + def __copy__(self) -> MLDSA65PrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLDSA65PrivateKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mldsa"): + MLDSA65PrivateKey.register(rust_openssl.mldsa.MLDSA65PrivateKey) + + +class MLDSA87PublicKey(metaclass=abc.ABCMeta): + @classmethod + def from_public_bytes(cls, data: bytes) -> MLDSA87PublicKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mldsa_supported(): + raise UnsupportedAlgorithm( + "ML-DSA-87 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mldsa.from_mldsa87_public_bytes(data) + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + + The public key is 2,592 bytes for MLDSA-87. + """ + + @abc.abstractmethod + def verify( + self, + signature: Buffer, + data: Buffer, + context: Buffer | None = None, + ) -> None: + """ + Verify the signature. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> MLDSA87PublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLDSA87PublicKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mldsa"): + MLDSA87PublicKey.register(rust_openssl.mldsa.MLDSA87PublicKey) + + +class MLDSA87PrivateKey(metaclass=abc.ABCMeta): + @classmethod + def generate(cls) -> MLDSA87PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mldsa_supported(): + raise UnsupportedAlgorithm( + "ML-DSA-87 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mldsa.generate_mldsa87_key() + + @classmethod + def from_seed_bytes(cls, data: Buffer) -> MLDSA87PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mldsa_supported(): + raise UnsupportedAlgorithm( + "ML-DSA-87 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mldsa.from_mldsa87_seed_bytes(data) + + @abc.abstractmethod + def public_key(self) -> MLDSA87PublicKey: + """ + The MLDSA87PublicKey derived from the private key. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + The serialized bytes of the private key. + + This method only returns the serialization of the seed form of the + private key, never the expanded one. + """ + + @abc.abstractmethod + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key. + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + + This method only returns the seed form of the private key (32 bytes). + """ + + @abc.abstractmethod + def sign(self, data: Buffer, context: Buffer | None = None) -> bytes: + """ + Signs the data. + """ + + @abc.abstractmethod + def __copy__(self) -> MLDSA87PrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLDSA87PrivateKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mldsa"): + MLDSA87PrivateKey.register(rust_openssl.mldsa.MLDSA87PrivateKey) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/mlkem.py b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/mlkem.py new file mode 100644 index 0000000..64bca11 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/mlkem.py @@ -0,0 +1,278 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer + + +class MLKEM768PublicKey(metaclass=abc.ABCMeta): + @classmethod + def from_public_bytes(cls, data: Buffer) -> MLKEM768PublicKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mlkem_supported(): + raise UnsupportedAlgorithm( + "ML-KEM-768 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mlkem.from_mlkem768_public_bytes(data) + + @abc.abstractmethod + def encapsulate(self) -> tuple[bytes, bytes]: + """ + Encapsulate: returns (shared_secret, ciphertext). + """ + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + + The public key is 1,184 bytes for ML-KEM-768. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> MLKEM768PublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLKEM768PublicKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mlkem"): + MLKEM768PublicKey.register(rust_openssl.mlkem.MLKEM768PublicKey) + + +class MLKEM768PrivateKey(metaclass=abc.ABCMeta): + @classmethod + def generate(cls) -> MLKEM768PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mlkem_supported(): + raise UnsupportedAlgorithm( + "ML-KEM-768 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mlkem.generate_mlkem768_key() + + @classmethod + def from_seed_bytes(cls, data: Buffer) -> MLKEM768PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mlkem_supported(): + raise UnsupportedAlgorithm( + "ML-KEM-768 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mlkem.from_mlkem768_seed_bytes(data) + + @abc.abstractmethod + def decapsulate(self, ciphertext: Buffer) -> bytes: + """ + Decapsulate: returns shared_secret. + """ + + @abc.abstractmethod + def public_key(self) -> MLKEM768PublicKey: + """ + The MLKEM768PublicKey derived from this private key. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + The serialized bytes of the private key. + """ + + @abc.abstractmethod + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key (64-byte seed). + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + """ + + @abc.abstractmethod + def __copy__(self) -> MLKEM768PrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLKEM768PrivateKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mlkem"): + MLKEM768PrivateKey.register(rust_openssl.mlkem.MLKEM768PrivateKey) + + +class MLKEM1024PublicKey(metaclass=abc.ABCMeta): + @classmethod + def from_public_bytes(cls, data: Buffer) -> MLKEM1024PublicKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mlkem_supported(): + raise UnsupportedAlgorithm( + "ML-KEM-1024 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mlkem.from_mlkem1024_public_bytes(data) + + @abc.abstractmethod + def encapsulate(self) -> tuple[bytes, bytes]: + """ + Encapsulate: returns (shared_secret, ciphertext). + """ + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + + The public key is 1,568 bytes for ML-KEM-1024. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> MLKEM1024PublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLKEM1024PublicKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mlkem"): + MLKEM1024PublicKey.register(rust_openssl.mlkem.MLKEM1024PublicKey) + + +class MLKEM1024PrivateKey(metaclass=abc.ABCMeta): + @classmethod + def generate(cls) -> MLKEM1024PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mlkem_supported(): + raise UnsupportedAlgorithm( + "ML-KEM-1024 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mlkem.generate_mlkem1024_key() + + @classmethod + def from_seed_bytes(cls, data: Buffer) -> MLKEM1024PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.mlkem_supported(): + raise UnsupportedAlgorithm( + "ML-KEM-1024 is not supported by this backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM, + ) + + return rust_openssl.mlkem.from_mlkem1024_seed_bytes(data) + + @abc.abstractmethod + def decapsulate(self, ciphertext: Buffer) -> bytes: + """ + Decapsulate: returns shared_secret. + """ + + @abc.abstractmethod + def public_key(self) -> MLKEM1024PublicKey: + """ + The MLKEM1024PublicKey derived from this private key. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + The serialized bytes of the private key. + """ + + @abc.abstractmethod + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key (64-byte seed). + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + """ + + @abc.abstractmethod + def __copy__(self) -> MLKEM1024PrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> MLKEM1024PrivateKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "mlkem"): + MLKEM1024PrivateKey.register(rust_openssl.mlkem.MLKEM1024PrivateKey) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/padding.py b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/padding.py new file mode 100644 index 0000000..5121a28 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/padding.py @@ -0,0 +1,111 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives._asymmetric import ( + AsymmetricPadding as AsymmetricPadding, +) +from cryptography.hazmat.primitives.asymmetric import rsa + + +class PKCS1v15(AsymmetricPadding): + name = "EMSA-PKCS1-v1_5" + + +class _MaxLength: + "Sentinel value for `MAX_LENGTH`." + + +class _Auto: + "Sentinel value for `AUTO`." + + +class _DigestLength: + "Sentinel value for `DIGEST_LENGTH`." + + +class PSS(AsymmetricPadding): + MAX_LENGTH = _MaxLength() + AUTO = _Auto() + DIGEST_LENGTH = _DigestLength() + name = "EMSA-PSS" + _salt_length: int | _MaxLength | _Auto | _DigestLength + + def __init__( + self, + mgf: MGF, + salt_length: int | _MaxLength | _Auto | _DigestLength, + ) -> None: + self._mgf = mgf + + if not isinstance( + salt_length, (int, _MaxLength, _Auto, _DigestLength) + ): + raise TypeError( + "salt_length must be an integer, MAX_LENGTH, " + "DIGEST_LENGTH, or AUTO" + ) + + if isinstance(salt_length, int) and salt_length < 0: + raise ValueError("salt_length must be zero or greater.") + + self._salt_length = salt_length + + @property + def mgf(self) -> MGF: + return self._mgf + + +class OAEP(AsymmetricPadding): + name = "EME-OAEP" + + def __init__( + self, + mgf: MGF, + algorithm: hashes.HashAlgorithm, + label: bytes | None, + ): + if not isinstance(algorithm, hashes.HashAlgorithm): + raise TypeError("Expected instance of hashes.HashAlgorithm.") + + self._mgf = mgf + self._algorithm = algorithm + self._label = label + + @property + def algorithm(self) -> hashes.HashAlgorithm: + return self._algorithm + + @property + def mgf(self) -> MGF: + return self._mgf + + +class MGF(metaclass=abc.ABCMeta): + _algorithm: hashes.HashAlgorithm + + +class MGF1(MGF): + def __init__(self, algorithm: hashes.HashAlgorithm): + if not isinstance(algorithm, hashes.HashAlgorithm): + raise TypeError("Expected instance of hashes.HashAlgorithm.") + + self._algorithm = algorithm + + +def calculate_max_pss_salt_length( + key: rsa.RSAPrivateKey | rsa.RSAPublicKey, + hash_algorithm: hashes.HashAlgorithm, +) -> int: + if not isinstance(key, (rsa.RSAPrivateKey, rsa.RSAPublicKey)): + raise TypeError("key must be an RSA public or private key") + # bit length - 1 per RFC 3447 + emlen = (key.key_size + 6) // 8 + salt_length = emlen - hash_algorithm.digest_size - 2 + assert salt_length >= 0 + return salt_length diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/rsa.py b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/rsa.py new file mode 100644 index 0000000..d730ceb --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/rsa.py @@ -0,0 +1,295 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc +import random +import typing +from math import gcd, lcm + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization, hashes +from cryptography.hazmat.primitives._asymmetric import AsymmetricPadding +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils + + +class RSAPrivateKey(metaclass=abc.ABCMeta): + @abc.abstractmethod + def decrypt(self, ciphertext: bytes, padding: AsymmetricPadding) -> bytes: + """ + Decrypts the provided ciphertext. + """ + + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + The bit length of the public modulus. + """ + + @abc.abstractmethod + def public_key(self) -> RSAPublicKey: + """ + The RSAPublicKey associated with this private key. + """ + + @abc.abstractmethod + def sign( + self, + data: bytes, + padding: AsymmetricPadding, + algorithm: asym_utils.Prehashed + | hashes.HashAlgorithm + | asym_utils.NoDigestInfo, + ) -> bytes: + """ + Signs the data. + """ + + @abc.abstractmethod + def private_numbers(self) -> RSAPrivateNumbers: + """ + Returns an RSAPrivateNumbers. + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + Returns the key serialized as bytes. + """ + + @abc.abstractmethod + def __copy__(self) -> RSAPrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> RSAPrivateKey: + """ + Returns a deep copy. + """ + + +RSAPrivateKeyWithSerialization = RSAPrivateKey +RSAPrivateKey.register(rust_openssl.rsa.RSAPrivateKey) + + +class RSAPublicKey(metaclass=abc.ABCMeta): + @abc.abstractmethod + def encrypt(self, plaintext: bytes, padding: AsymmetricPadding) -> bytes: + """ + Encrypts the given plaintext. + """ + + @property + @abc.abstractmethod + def key_size(self) -> int: + """ + The bit length of the public modulus. + """ + + @abc.abstractmethod + def public_numbers(self) -> RSAPublicNumbers: + """ + Returns an RSAPublicNumbers + """ + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + Returns the key serialized as bytes. + """ + + @abc.abstractmethod + def verify( + self, + signature: bytes, + data: bytes, + padding: AsymmetricPadding, + algorithm: asym_utils.Prehashed | hashes.HashAlgorithm, + ) -> None: + """ + Verifies the signature of the data. + """ + + @abc.abstractmethod + def recover_data_from_signature( + self, + signature: bytes, + padding: AsymmetricPadding, + algorithm: hashes.HashAlgorithm | asym_utils.NoDigestInfo | None, + ) -> bytes: + """ + Recovers the original data from the signature. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> RSAPublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> RSAPublicKey: + """ + Returns a deep copy. + """ + + +RSAPublicKeyWithSerialization = RSAPublicKey +RSAPublicKey.register(rust_openssl.rsa.RSAPublicKey) + +RSAPrivateNumbers = rust_openssl.rsa.RSAPrivateNumbers +RSAPublicNumbers = rust_openssl.rsa.RSAPublicNumbers + + +def generate_private_key( + public_exponent: int, + key_size: int, + backend: typing.Any = None, +) -> RSAPrivateKey: + _verify_rsa_parameters(public_exponent, key_size) + return rust_openssl.rsa.generate_private_key(public_exponent, key_size) + + +def _verify_rsa_parameters(public_exponent: int, key_size: int) -> None: + if public_exponent not in (3, 65537): + raise ValueError( + "public_exponent must be either 3 (for legacy compatibility) or " + "65537. Almost everyone should choose 65537 here!" + ) + + if key_size < 1024: + raise ValueError("key_size must be at least 1024-bits.") + + +def _modinv(e: int, m: int) -> int: + """ + Modular Multiplicative Inverse. Returns x such that: (x*e) mod m == 1 + """ + x1, x2 = 1, 0 + a, b = e, m + while b > 0: + q, r = divmod(a, b) + xn = x1 - q * x2 + a, b, x1, x2 = b, r, x2, xn + return x1 % m + + +def rsa_crt_iqmp(p: int, q: int) -> int: + """ + Compute the CRT (q ** -1) % p value from RSA primes p and q. + """ + if p <= 1 or q <= 1: + raise ValueError("Values can't be <= 1") + return _modinv(q, p) + + +def rsa_crt_dmp1(private_exponent: int, p: int) -> int: + """ + Compute the CRT private_exponent % (p - 1) value from the RSA + private_exponent (d) and p. + """ + if private_exponent <= 1 or p <= 1: + raise ValueError("Values can't be <= 1") + return private_exponent % (p - 1) + + +def rsa_crt_dmq1(private_exponent: int, q: int) -> int: + """ + Compute the CRT private_exponent % (q - 1) value from the RSA + private_exponent (d) and q. + """ + if private_exponent <= 1 or q <= 1: + raise ValueError("Values can't be <= 1") + return private_exponent % (q - 1) + + +def rsa_recover_private_exponent(e: int, p: int, q: int) -> int: + """ + Compute the RSA private_exponent (d) given the public exponent (e) + and the RSA primes p and q. + + This uses the Carmichael totient function to generate the + smallest possible working value of the private exponent. + """ + # This lambda_n is the Carmichael totient function. + # The original RSA paper uses the Euler totient function + # here: phi_n = (p - 1) * (q - 1) + # Either version of the private exponent will work, but the + # one generated by the older formulation may be larger + # than necessary. (lambda_n always divides phi_n) + if e <= 1 or p <= 1 or q <= 1: + raise ValueError("Values can't be <= 1") + return _modinv(e, lcm(p - 1, q - 1)) + + +# Controls the number of iterations rsa_recover_prime_factors will perform +# to obtain the prime factors. +_MAX_RECOVERY_ATTEMPTS = 500 + + +def rsa_recover_prime_factors(n: int, e: int, d: int) -> tuple[int, int]: + """ + Compute factors p and q from the private exponent d. We assume that n has + no more than two factors. This function is adapted from code in PyCrypto. + """ + # reject invalid values early + if d <= 1 or e <= 1: + raise ValueError("d, e can't be <= 1") + if 17 != pow(17, e * d, n): + raise ValueError("n, d, e don't match") + # See 8.2.2(i) in Handbook of Applied Cryptography. + ktot = d * e - 1 + # The quantity d*e-1 is a multiple of phi(n), even, + # and can be represented as t*2^s. + t = ktot + while t % 2 == 0: + t = t // 2 + # Cycle through all multiplicative inverses in Zn. + # The algorithm is non-deterministic, but there is a 50% chance + # any candidate a leads to successful factoring. + # See "Digitalized Signatures and Public Key Functions as Intractable + # as Factorization", M. Rabin, 1979 + spotted = False + tries = 0 + while not spotted and tries < _MAX_RECOVERY_ATTEMPTS: + a = random.randint(2, n - 1) + tries += 1 + k = t + # Cycle through all values a^{t*2^i}=a^k + while k < ktot: + cand = pow(a, k, n) + # Check if a^k is a non-trivial root of unity (mod n) + if cand != 1 and cand != (n - 1) and pow(cand, 2, n) == 1: + # We have found a number such that (cand-1)(cand+1)=0 (mod n). + # Either of the terms divides n. + p = gcd(cand + 1, n) + spotted = True + break + k *= 2 + if not spotted: + raise ValueError("Unable to compute factors p and q from exponent d.") + # Found ! + q, r = divmod(n, p) + assert r == 0 + p, q = sorted((p, q), reverse=True) + return (p, q) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/types.py b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/types.py new file mode 100644 index 0000000..dfd12ce --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/types.py @@ -0,0 +1,123 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import typing + +from cryptography import utils +from cryptography.hazmat.primitives.asymmetric import ( + dh, + dsa, + ec, + ed448, + ed25519, + mldsa, + mlkem, + rsa, + x448, + x25519, +) + +# Every asymmetric key type +PublicKeyTypes = typing.Union[ + dh.DHPublicKey, + dsa.DSAPublicKey, + rsa.RSAPublicKey, + ec.EllipticCurvePublicKey, + ed25519.Ed25519PublicKey, + ed448.Ed448PublicKey, + mldsa.MLDSA44PublicKey, + mldsa.MLDSA65PublicKey, + mldsa.MLDSA87PublicKey, + mlkem.MLKEM768PublicKey, + mlkem.MLKEM1024PublicKey, + x25519.X25519PublicKey, + x448.X448PublicKey, +] +PUBLIC_KEY_TYPES = PublicKeyTypes +utils.deprecated( + PUBLIC_KEY_TYPES, + __name__, + "Use PublicKeyTypes instead", + utils.DeprecatedIn40, + name="PUBLIC_KEY_TYPES", +) +# Every asymmetric key type +PrivateKeyTypes = typing.Union[ + dh.DHPrivateKey, + ed25519.Ed25519PrivateKey, + ed448.Ed448PrivateKey, + mldsa.MLDSA44PrivateKey, + mldsa.MLDSA65PrivateKey, + mldsa.MLDSA87PrivateKey, + mlkem.MLKEM768PrivateKey, + mlkem.MLKEM1024PrivateKey, + rsa.RSAPrivateKey, + dsa.DSAPrivateKey, + ec.EllipticCurvePrivateKey, + x25519.X25519PrivateKey, + x448.X448PrivateKey, +] +PRIVATE_KEY_TYPES = PrivateKeyTypes +utils.deprecated( + PRIVATE_KEY_TYPES, + __name__, + "Use PrivateKeyTypes instead", + utils.DeprecatedIn40, + name="PRIVATE_KEY_TYPES", +) +# Just the key types we allow to be used for x509 signing. This mirrors +# the certificate public key types +CertificateIssuerPrivateKeyTypes = typing.Union[ + ed25519.Ed25519PrivateKey, + ed448.Ed448PrivateKey, + rsa.RSAPrivateKey, + dsa.DSAPrivateKey, + ec.EllipticCurvePrivateKey, +] +CERTIFICATE_PRIVATE_KEY_TYPES = CertificateIssuerPrivateKeyTypes +utils.deprecated( + CERTIFICATE_PRIVATE_KEY_TYPES, + __name__, + "Use CertificateIssuerPrivateKeyTypes instead", + utils.DeprecatedIn40, + name="CERTIFICATE_PRIVATE_KEY_TYPES", +) +# Just the key types we allow to be used for x509 signing. This mirrors +# the certificate private key types +CertificateIssuerPublicKeyTypes = typing.Union[ + dsa.DSAPublicKey, + rsa.RSAPublicKey, + ec.EllipticCurvePublicKey, + ed25519.Ed25519PublicKey, + ed448.Ed448PublicKey, +] +CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES = CertificateIssuerPublicKeyTypes +utils.deprecated( + CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES, + __name__, + "Use CertificateIssuerPublicKeyTypes instead", + utils.DeprecatedIn40, + name="CERTIFICATE_ISSUER_PUBLIC_KEY_TYPES", +) +# This type removes DHPublicKey. x448/x25519 can be a public key +# but cannot be used in signing so they are allowed here. +CertificatePublicKeyTypes = typing.Union[ + dsa.DSAPublicKey, + rsa.RSAPublicKey, + ec.EllipticCurvePublicKey, + ed25519.Ed25519PublicKey, + ed448.Ed448PublicKey, + x25519.X25519PublicKey, + x448.X448PublicKey, +] +CERTIFICATE_PUBLIC_KEY_TYPES = CertificatePublicKeyTypes +utils.deprecated( + CERTIFICATE_PUBLIC_KEY_TYPES, + __name__, + "Use CertificatePublicKeyTypes instead", + utils.DeprecatedIn40, + name="CERTIFICATE_PUBLIC_KEY_TYPES", +) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/utils.py b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/utils.py new file mode 100644 index 0000000..c01c342 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/utils.py @@ -0,0 +1,28 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import asn1 +from cryptography.hazmat.primitives import hashes + +decode_dss_signature = asn1.decode_dss_signature +encode_dss_signature = asn1.encode_dss_signature + + +class NoDigestInfo: + pass + + +class Prehashed: + def __init__(self, algorithm: hashes.HashAlgorithm): + if not isinstance(algorithm, hashes.HashAlgorithm): + raise TypeError("Expected instance of HashAlgorithm.") + + self._algorithm = algorithm + self._digest_size = algorithm.digest_size + + @property + def digest_size(self) -> int: + return self._digest_size diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/x25519.py b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/x25519.py new file mode 100644 index 0000000..7498998 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/x25519.py @@ -0,0 +1,134 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer + + +class X25519PublicKey(metaclass=abc.ABCMeta): + @classmethod + def from_public_bytes(cls, data: bytes) -> X25519PublicKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.x25519_supported(): + raise UnsupportedAlgorithm( + "X25519 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, + ) + + return rust_openssl.x25519.from_public_bytes(data) + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> X25519PublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> X25519PublicKey: + """ + Returns a deep copy. + """ + + +X25519PublicKey.register(rust_openssl.x25519.X25519PublicKey) + + +class X25519PrivateKey(metaclass=abc.ABCMeta): + @classmethod + def generate(cls) -> X25519PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.x25519_supported(): + raise UnsupportedAlgorithm( + "X25519 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, + ) + return rust_openssl.x25519.generate_key() + + @classmethod + def from_private_bytes(cls, data: Buffer) -> X25519PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.x25519_supported(): + raise UnsupportedAlgorithm( + "X25519 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, + ) + + return rust_openssl.x25519.from_private_bytes(data) + + @abc.abstractmethod + def public_key(self) -> X25519PublicKey: + """ + Returns the public key associated with this private key + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + The serialized bytes of the private key. + """ + + @abc.abstractmethod + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key. + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + """ + + @abc.abstractmethod + def exchange(self, peer_public_key: X25519PublicKey) -> bytes: + """ + Performs a key exchange operation using the provided peer's public key. + """ + + @abc.abstractmethod + def __copy__(self) -> X25519PrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> X25519PrivateKey: + """ + Returns a deep copy. + """ + + +X25519PrivateKey.register(rust_openssl.x25519.X25519PrivateKey) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/x448.py b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/x448.py new file mode 100644 index 0000000..b9dc826 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/asymmetric/x448.py @@ -0,0 +1,137 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import _serialization +from cryptography.utils import Buffer + + +class X448PublicKey(metaclass=abc.ABCMeta): + @classmethod + def from_public_bytes(cls, data: bytes) -> X448PublicKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.x448_supported(): + raise UnsupportedAlgorithm( + "X448 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, + ) + + return rust_openssl.x448.from_public_bytes(data) + + @abc.abstractmethod + def public_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PublicFormat, + ) -> bytes: + """ + The serialized bytes of the public key. + """ + + @abc.abstractmethod + def public_bytes_raw(self) -> bytes: + """ + The raw bytes of the public key. + Equivalent to public_bytes(Raw, Raw). + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Checks equality. + """ + + @abc.abstractmethod + def __copy__(self) -> X448PublicKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> X448PublicKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "x448"): + X448PublicKey.register(rust_openssl.x448.X448PublicKey) + + +class X448PrivateKey(metaclass=abc.ABCMeta): + @classmethod + def generate(cls) -> X448PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.x448_supported(): + raise UnsupportedAlgorithm( + "X448 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, + ) + + return rust_openssl.x448.generate_key() + + @classmethod + def from_private_bytes(cls, data: Buffer) -> X448PrivateKey: + from cryptography.hazmat.backends.openssl.backend import backend + + if not backend.x448_supported(): + raise UnsupportedAlgorithm( + "X448 is not supported by this version of OpenSSL.", + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM, + ) + + return rust_openssl.x448.from_private_bytes(data) + + @abc.abstractmethod + def public_key(self) -> X448PublicKey: + """ + Returns the public key associated with this private key + """ + + @abc.abstractmethod + def private_bytes( + self, + encoding: _serialization.Encoding, + format: _serialization.PrivateFormat, + encryption_algorithm: _serialization.KeySerializationEncryption, + ) -> bytes: + """ + The serialized bytes of the private key. + """ + + @abc.abstractmethod + def private_bytes_raw(self) -> bytes: + """ + The raw bytes of the private key. + Equivalent to private_bytes(Raw, Raw, NoEncryption()). + """ + + @abc.abstractmethod + def exchange(self, peer_public_key: X448PublicKey) -> bytes: + """ + Performs a key exchange operation using the provided peer's public key. + """ + + @abc.abstractmethod + def __copy__(self) -> X448PrivateKey: + """ + Returns a copy. + """ + + @abc.abstractmethod + def __deepcopy__(self, memo: dict) -> X448PrivateKey: + """ + Returns a deep copy. + """ + + +if hasattr(rust_openssl, "x448"): + X448PrivateKey.register(rust_openssl.x448.X448PrivateKey) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/ciphers/__init__.py b/intelliaide/vendor/cryptography/hazmat/primitives/ciphers/__init__.py new file mode 100644 index 0000000..10c15d0 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/ciphers/__init__.py @@ -0,0 +1,27 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.primitives._cipheralgorithm import ( + BlockCipherAlgorithm, + CipherAlgorithm, +) +from cryptography.hazmat.primitives.ciphers.base import ( + AEADCipherContext, + AEADDecryptionContext, + AEADEncryptionContext, + Cipher, + CipherContext, +) + +__all__ = [ + "AEADCipherContext", + "AEADDecryptionContext", + "AEADEncryptionContext", + "BlockCipherAlgorithm", + "Cipher", + "CipherAlgorithm", + "CipherContext", +] diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/ciphers/aead.py b/intelliaide/vendor/cryptography/hazmat/primitives/ciphers/aead.py new file mode 100644 index 0000000..c8a582d --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/ciphers/aead.py @@ -0,0 +1,23 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl + +__all__ = [ + "AESCCM", + "AESGCM", + "AESGCMSIV", + "AESOCB3", + "AESSIV", + "ChaCha20Poly1305", +] + +AESGCM = rust_openssl.aead.AESGCM +ChaCha20Poly1305 = rust_openssl.aead.ChaCha20Poly1305 +AESCCM = rust_openssl.aead.AESCCM +AESSIV = rust_openssl.aead.AESSIV +AESOCB3 = rust_openssl.aead.AESOCB3 +AESGCMSIV = rust_openssl.aead.AESGCMSIV diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/ciphers/algorithms.py b/intelliaide/vendor/cryptography/hazmat/primitives/ciphers/algorithms.py new file mode 100644 index 0000000..6b20dd5 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/ciphers/algorithms.py @@ -0,0 +1,138 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography import utils +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + ARC4 as ARC4, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + CAST5 as CAST5, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + IDEA as IDEA, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + SEED as SEED, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + Blowfish as Blowfish, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + Camellia as Camellia, +) +from cryptography.hazmat.decrepit.ciphers.algorithms import ( + TripleDES as TripleDES, +) +from cryptography.hazmat.primitives._cipheralgorithm import _verify_key_size +from cryptography.hazmat.primitives.ciphers import ( + BlockCipherAlgorithm, + CipherAlgorithm, +) + + +class AES(BlockCipherAlgorithm): + name = "AES" + block_size = 128 + # 512 added to support AES-256-XTS, which uses 512-bit keys + key_sizes = frozenset([128, 192, 256, 512]) + + def __init__(self, key: utils.Buffer): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class AES128(BlockCipherAlgorithm): + name = "AES" + block_size = 128 + key_sizes = frozenset([128]) + key_size = 128 + + def __init__(self, key: utils.Buffer): + self.key = _verify_key_size(self, key) + + +class AES256(BlockCipherAlgorithm): + name = "AES" + block_size = 128 + key_sizes = frozenset([256]) + key_size = 256 + + def __init__(self, key: utils.Buffer): + self.key = _verify_key_size(self, key) + + +utils.deprecated( + Camellia, + __name__, + "Camellia has been moved to " + "cryptography.hazmat.decrepit.ciphers.algorithms.Camellia and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.algorithms in 49.0.0.", + utils.DeprecatedIn43, + name="Camellia", +) + + +utils.deprecated( + ARC4, + __name__, + "ARC4 has been moved to " + "cryptography.hazmat.decrepit.ciphers.algorithms.ARC4 and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.algorithms in 48.0.0.", + utils.DeprecatedIn43, + name="ARC4", +) + + +utils.deprecated( + TripleDES, + __name__, + "TripleDES has been moved to " + "cryptography.hazmat.decrepit.ciphers.algorithms.TripleDES and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.algorithms in 48.0.0.", + utils.DeprecatedIn43, + name="TripleDES", +) + + +class ChaCha20(CipherAlgorithm): + name = "ChaCha20" + key_sizes = frozenset([256]) + + def __init__(self, key: utils.Buffer, nonce: utils.Buffer): + self.key = _verify_key_size(self, key) + utils._check_byteslike("nonce", nonce) + + if len(nonce) != 16: + raise ValueError("nonce must be 128-bits (16 bytes)") + + self._nonce = nonce + + @property + def nonce(self) -> utils.Buffer: + return self._nonce + + @property + def key_size(self) -> int: + return len(self.key) * 8 + + +class SM4(BlockCipherAlgorithm): + name = "SM4" + block_size = 128 + key_sizes = frozenset([128]) + + def __init__(self, key: bytes): + self.key = _verify_key_size(self, key) + + @property + def key_size(self) -> int: + return len(self.key) * 8 diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/ciphers/base.py b/intelliaide/vendor/cryptography/hazmat/primitives/ciphers/base.py new file mode 100644 index 0000000..24fceea --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/ciphers/base.py @@ -0,0 +1,146 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc +import typing + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives._cipheralgorithm import CipherAlgorithm +from cryptography.hazmat.primitives.ciphers import modes +from cryptography.utils import Buffer + + +class CipherContext(metaclass=abc.ABCMeta): + @abc.abstractmethod + def update(self, data: Buffer) -> bytes: + """ + Processes the provided bytes through the cipher and returns the results + as bytes. + """ + + @abc.abstractmethod + def update_into(self, data: Buffer, buf: Buffer) -> int: + """ + Processes the provided bytes and writes the resulting data into the + provided buffer. Returns the number of bytes written. + """ + + @abc.abstractmethod + def finalize(self) -> bytes: + """ + Returns the results of processing the final block as bytes. + """ + + @abc.abstractmethod + def reset_nonce(self, nonce: bytes) -> None: + """ + Resets the nonce for the cipher context to the provided value. + Raises an exception if it does not support reset or if the + provided nonce does not have a valid length. + """ + + +class AEADCipherContext(CipherContext, metaclass=abc.ABCMeta): + @abc.abstractmethod + def authenticate_additional_data(self, data: Buffer) -> None: + """ + Authenticates the provided bytes. + """ + + +class AEADDecryptionContext(AEADCipherContext, metaclass=abc.ABCMeta): + @abc.abstractmethod + def finalize_with_tag(self, tag: bytes) -> bytes: + """ + Returns the results of processing the final block as bytes and allows + delayed passing of the authentication tag. + """ + + +class AEADEncryptionContext(AEADCipherContext, metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def tag(self) -> bytes: + """ + Returns tag bytes. This is only available after encryption is + finalized. + """ + + +Mode = typing.TypeVar( + "Mode", bound=typing.Optional[modes.Mode], covariant=True +) + + +class Cipher(typing.Generic[Mode]): + def __init__( + self, + algorithm: CipherAlgorithm, + mode: Mode, + backend: typing.Any = None, + ) -> None: + if not isinstance(algorithm, CipherAlgorithm): + raise TypeError("Expected interface of CipherAlgorithm.") + + if mode is not None: + # mypy needs this assert to narrow the type from our generic + # type. Maybe it won't some time in the future. + assert isinstance(mode, modes.Mode) + mode.validate_for_algorithm(algorithm) + + self.algorithm = algorithm + self.mode = mode + + @typing.overload + def encryptor( + self: Cipher[modes.ModeWithAuthenticationTag], + ) -> AEADEncryptionContext: ... + + @typing.overload + def encryptor( + self: _CIPHER_TYPE, + ) -> CipherContext: ... + + def encryptor(self): + if isinstance(self.mode, modes.ModeWithAuthenticationTag): + if self.mode.tag is not None: + raise ValueError( + "Authentication tag must be None when encrypting." + ) + + return rust_openssl.ciphers.create_encryption_ctx( + self.algorithm, self.mode + ) + + @typing.overload + def decryptor( + self: Cipher[modes.ModeWithAuthenticationTag], + ) -> AEADDecryptionContext: ... + + @typing.overload + def decryptor( + self: _CIPHER_TYPE, + ) -> CipherContext: ... + + def decryptor(self): + return rust_openssl.ciphers.create_decryption_ctx( + self.algorithm, self.mode + ) + + +_CIPHER_TYPE = Cipher[ + typing.Union[ + modes.ModeWithNonce, + modes.ModeWithTweak, + modes.ECB, + modes.ModeWithInitializationVector, + None, + ] +] + +CipherContext.register(rust_openssl.ciphers.CipherContext) +AEADEncryptionContext.register(rust_openssl.ciphers.AEADEncryptionContext) +AEADDecryptionContext.register(rust_openssl.ciphers.AEADDecryptionContext) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/ciphers/modes.py b/intelliaide/vendor/cryptography/hazmat/primitives/ciphers/modes.py new file mode 100644 index 0000000..0f1c217 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/ciphers/modes.py @@ -0,0 +1,192 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography import utils +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.decrepit.ciphers.modes import CFB as CFB +from cryptography.hazmat.decrepit.ciphers.modes import CFB8 as CFB8 +from cryptography.hazmat.decrepit.ciphers.modes import OFB as OFB +from cryptography.hazmat.primitives._cipheralgorithm import ( + BlockCipherAlgorithm, + CipherAlgorithm, +) +from cryptography.hazmat.primitives._modes import ( + Mode as Mode, +) +from cryptography.hazmat.primitives._modes import ( + ModeWithAuthenticationTag as ModeWithAuthenticationTag, +) +from cryptography.hazmat.primitives._modes import ( + ModeWithInitializationVector as ModeWithInitializationVector, +) +from cryptography.hazmat.primitives._modes import ( + ModeWithNonce as ModeWithNonce, +) +from cryptography.hazmat.primitives._modes import ( + ModeWithTweak as ModeWithTweak, +) +from cryptography.hazmat.primitives._modes import ( + _check_aes_key_length, + _check_iv_and_key_length, + _check_nonce_length, +) +from cryptography.hazmat.primitives.ciphers import algorithms + + +class CBC(ModeWithInitializationVector): + name = "CBC" + + def __init__(self, initialization_vector: utils.Buffer): + utils._check_byteslike("initialization_vector", initialization_vector) + self._initialization_vector = initialization_vector + + @property + def initialization_vector(self) -> utils.Buffer: + return self._initialization_vector + + validate_for_algorithm = _check_iv_and_key_length + + +class XTS(ModeWithTweak): + name = "XTS" + + def __init__(self, tweak: utils.Buffer): + utils._check_byteslike("tweak", tweak) + + if len(tweak) != 16: + raise ValueError("tweak must be 128-bits (16 bytes)") + + self._tweak = tweak + + @property + def tweak(self) -> utils.Buffer: + return self._tweak + + def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: + if isinstance(algorithm, (algorithms.AES128, algorithms.AES256)): + raise TypeError( + "The AES128 and AES256 classes do not support XTS, please use " + "the standard AES class instead." + ) + + if algorithm.key_size not in (256, 512): + raise ValueError( + "The XTS specification requires a 256-bit key for AES-128-XTS" + " and 512-bit key for AES-256-XTS" + ) + + +class ECB(Mode): + name = "ECB" + + validate_for_algorithm = _check_aes_key_length + + +class CTR(ModeWithNonce): + name = "CTR" + + def __init__(self, nonce: utils.Buffer): + utils._check_byteslike("nonce", nonce) + self._nonce = nonce + + @property + def nonce(self) -> utils.Buffer: + return self._nonce + + def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: + _check_aes_key_length(self, algorithm) + _check_nonce_length(self.nonce, self.name, algorithm) + + +class GCM(ModeWithInitializationVector, ModeWithAuthenticationTag): + name = "GCM" + _MAX_ENCRYPTED_BYTES = (2**39 - 256) // 8 + _MAX_AAD_BYTES = (2**64) // 8 + + def __init__( + self, + initialization_vector: utils.Buffer, + tag: bytes | None = None, + min_tag_length: int = 16, + ): + # OpenSSL 3.0.0 constrains GCM IVs to [64, 1024] bits inclusive + # This is a sane limit anyway so we'll enforce it here. + utils._check_byteslike("initialization_vector", initialization_vector) + if len(initialization_vector) < 8 or len(initialization_vector) > 128: + raise ValueError( + "initialization_vector must be between 8 and 128 bytes (64 " + "and 1024 bits)." + ) + self._initialization_vector = initialization_vector + if tag is not None: + utils._check_bytes("tag", tag) + if min_tag_length < 4: + raise ValueError("min_tag_length must be >= 4") + if len(tag) < min_tag_length: + raise ValueError( + f"Authentication tag must be {min_tag_length} bytes or " + "longer." + ) + self._tag = tag + self._min_tag_length = min_tag_length + + @property + def tag(self) -> bytes | None: + return self._tag + + @property + def initialization_vector(self) -> utils.Buffer: + return self._initialization_vector + + def validate_for_algorithm(self, algorithm: CipherAlgorithm) -> None: + _check_aes_key_length(self, algorithm) + if not isinstance(algorithm, BlockCipherAlgorithm): + raise UnsupportedAlgorithm( + "GCM requires a block cipher algorithm", + _Reasons.UNSUPPORTED_CIPHER, + ) + block_size_bytes = algorithm.block_size // 8 + if self._tag is not None and len(self._tag) > block_size_bytes: + raise ValueError( + f"Authentication tag cannot be more than {block_size_bytes} " + "bytes." + ) + + +utils.deprecated( + OFB, + __name__, + "OFB has been moved to " + "cryptography.hazmat.decrepit.ciphers.modes.OFB and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.modes in 49.0.0.", + utils.DeprecatedIn47, + name="OFB", +) + + +utils.deprecated( + CFB, + __name__, + "CFB has been moved to " + "cryptography.hazmat.decrepit.ciphers.modes.CFB and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.modes in 49.0.0.", + utils.DeprecatedIn47, + name="CFB", +) + + +utils.deprecated( + CFB8, + __name__, + "CFB8 has been moved to " + "cryptography.hazmat.decrepit.ciphers.modes.CFB8 and " + "will be removed from " + "cryptography.hazmat.primitives.ciphers.modes in 49.0.0.", + utils.DeprecatedIn47, + name="CFB8", +) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/cmac.py b/intelliaide/vendor/cryptography/hazmat/primitives/cmac.py new file mode 100644 index 0000000..2c67ce2 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/cmac.py @@ -0,0 +1,10 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl + +__all__ = ["CMAC"] +CMAC = rust_openssl.cmac.CMAC diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/constant_time.py b/intelliaide/vendor/cryptography/hazmat/primitives/constant_time.py new file mode 100644 index 0000000..3975c71 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/constant_time.py @@ -0,0 +1,14 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import hmac + + +def bytes_eq(a: bytes, b: bytes) -> bool: + if not isinstance(a, bytes) or not isinstance(b, bytes): + raise TypeError("a and b must be bytes.") + + return hmac.compare_digest(a, b) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/hashes.py b/intelliaide/vendor/cryptography/hazmat/primitives/hashes.py new file mode 100644 index 0000000..4b55ec3 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/hashes.py @@ -0,0 +1,246 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.utils import Buffer + +__all__ = [ + "MD5", + "SHA1", + "SHA3_224", + "SHA3_256", + "SHA3_384", + "SHA3_512", + "SHA224", + "SHA256", + "SHA384", + "SHA512", + "SHA512_224", + "SHA512_256", + "SHAKE128", + "SHAKE256", + "SM3", + "BLAKE2b", + "BLAKE2s", + "ExtendableOutputFunction", + "Hash", + "HashAlgorithm", + "HashContext", + "XOFHash", +] + + +class HashAlgorithm(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def name(self) -> str: + """ + A string naming this algorithm (e.g. "sha256", "md5"). + """ + + @property + @abc.abstractmethod + def digest_size(self) -> int: + """ + The size of the resulting digest in bytes. + """ + + @property + @abc.abstractmethod + def block_size(self) -> int | None: + """ + The internal block size of the hash function, or None if the hash + function does not use blocks internally (e.g. SHA3). + """ + + +class HashContext(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def algorithm(self) -> HashAlgorithm: + """ + A HashAlgorithm that will be used by this context. + """ + + @abc.abstractmethod + def update(self, data: Buffer) -> None: + """ + Processes the provided bytes through the hash. + """ + + @abc.abstractmethod + def finalize(self) -> bytes: + """ + Finalizes the hash context and returns the hash digest as bytes. + """ + + @abc.abstractmethod + def copy(self) -> HashContext: + """ + Return a HashContext that is a copy of the current context. + """ + + +Hash = rust_openssl.hashes.Hash +HashContext.register(Hash) + +XOFHash = rust_openssl.hashes.XOFHash + + +class ExtendableOutputFunction(metaclass=abc.ABCMeta): + """ + An interface for extendable output functions. + """ + + +class SHA1(HashAlgorithm): + name = "sha1" + digest_size = 20 + block_size = 64 + + +class SHA512_224(HashAlgorithm): # noqa: N801 + name = "sha512-224" + digest_size = 28 + block_size = 128 + + +class SHA512_256(HashAlgorithm): # noqa: N801 + name = "sha512-256" + digest_size = 32 + block_size = 128 + + +class SHA224(HashAlgorithm): + name = "sha224" + digest_size = 28 + block_size = 64 + + +class SHA256(HashAlgorithm): + name = "sha256" + digest_size = 32 + block_size = 64 + + +class SHA384(HashAlgorithm): + name = "sha384" + digest_size = 48 + block_size = 128 + + +class SHA512(HashAlgorithm): + name = "sha512" + digest_size = 64 + block_size = 128 + + +class SHA3_224(HashAlgorithm): # noqa: N801 + name = "sha3-224" + digest_size = 28 + block_size = None + + +class SHA3_256(HashAlgorithm): # noqa: N801 + name = "sha3-256" + digest_size = 32 + block_size = None + + +class SHA3_384(HashAlgorithm): # noqa: N801 + name = "sha3-384" + digest_size = 48 + block_size = None + + +class SHA3_512(HashAlgorithm): # noqa: N801 + name = "sha3-512" + digest_size = 64 + block_size = None + + +class SHAKE128(HashAlgorithm, ExtendableOutputFunction): + name = "shake128" + block_size = None + + def __init__(self, digest_size: int): + if not isinstance(digest_size, int): + raise TypeError("digest_size must be an integer") + + if digest_size < 1: + raise ValueError("digest_size must be a positive integer") + + self._digest_size = digest_size + + @property + def digest_size(self) -> int: + return self._digest_size + + +class SHAKE256(HashAlgorithm, ExtendableOutputFunction): + name = "shake256" + block_size = None + + def __init__(self, digest_size: int): + if not isinstance(digest_size, int): + raise TypeError("digest_size must be an integer") + + if digest_size < 1: + raise ValueError("digest_size must be a positive integer") + + self._digest_size = digest_size + + @property + def digest_size(self) -> int: + return self._digest_size + + +class MD5(HashAlgorithm): + name = "md5" + digest_size = 16 + block_size = 64 + + +class BLAKE2b(HashAlgorithm): + name = "blake2b" + _max_digest_size = 64 + _min_digest_size = 1 + block_size = 128 + + def __init__(self, digest_size: int): + if digest_size != 64: + raise ValueError("Digest size must be 64") + + self._digest_size = digest_size + + @property + def digest_size(self) -> int: + return self._digest_size + + +class BLAKE2s(HashAlgorithm): + name = "blake2s" + block_size = 64 + _max_digest_size = 32 + _min_digest_size = 1 + + def __init__(self, digest_size: int): + if digest_size != 32: + raise ValueError("Digest size must be 32") + + self._digest_size = digest_size + + @property + def digest_size(self) -> int: + return self._digest_size + + +class SM3(HashAlgorithm): + name = "sm3" + digest_size = 32 + block_size = 64 diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/hmac.py b/intelliaide/vendor/cryptography/hazmat/primitives/hmac.py new file mode 100644 index 0000000..a9442d5 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/hmac.py @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives import hashes + +__all__ = ["HMAC"] + +HMAC = rust_openssl.hmac.HMAC +hashes.HashContext.register(HMAC) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/hpke.py b/intelliaide/vendor/cryptography/hazmat/primitives/hpke.py new file mode 100644 index 0000000..c802808 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/hpke.py @@ -0,0 +1,27 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl + +AEAD = rust_openssl.hpke.AEAD +KDF = rust_openssl.hpke.KDF +KEM = rust_openssl.hpke.KEM +MLKEM768X25519PrivateKey = rust_openssl.hpke.MLKEM768X25519PrivateKey +MLKEM768X25519PublicKey = rust_openssl.hpke.MLKEM768X25519PublicKey +MLKEM1024P384PrivateKey = rust_openssl.hpke.MLKEM1024P384PrivateKey +MLKEM1024P384PublicKey = rust_openssl.hpke.MLKEM1024P384PublicKey +Suite = rust_openssl.hpke.Suite + +__all__ = [ + "AEAD", + "KDF", + "KEM", + "MLKEM768X25519PrivateKey", + "MLKEM768X25519PublicKey", + "MLKEM1024P384PrivateKey", + "MLKEM1024P384PublicKey", + "Suite", +] diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/kdf/__init__.py b/intelliaide/vendor/cryptography/hazmat/primitives/kdf/__init__.py new file mode 100644 index 0000000..26c45bd --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/kdf/__init__.py @@ -0,0 +1,32 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography.utils import Buffer + + +class KeyDerivationFunction(metaclass=abc.ABCMeta): + @abc.abstractmethod + def derive(self, key_material: bytes) -> bytes: + """ + Deterministically generates and returns a new key based on the existing + key material. + """ + + @abc.abstractmethod + def derive_into(self, key_material: bytes, buffer: Buffer) -> None: + """ + Deterministically generates a new key based on the existing key + material and stores it in the provided buffer. + """ + + @abc.abstractmethod + def verify(self, key_material: bytes, expected_key: bytes) -> None: + """ + Checks whether the key generated by the key material matches the + expected derived key. Raises an exception if they do not match. + """ diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/kdf/argon2.py b/intelliaide/vendor/cryptography/hazmat/primitives/kdf/argon2.py new file mode 100644 index 0000000..03e84d4 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/kdf/argon2.py @@ -0,0 +1,17 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + +Argon2d = rust_openssl.kdf.Argon2d +Argon2i = rust_openssl.kdf.Argon2i +Argon2id = rust_openssl.kdf.Argon2id +KeyDerivationFunction.register(Argon2d) +KeyDerivationFunction.register(Argon2i) +KeyDerivationFunction.register(Argon2id) + +__all__ = ["Argon2d", "Argon2i", "Argon2id"] diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/kdf/concatkdf.py b/intelliaide/vendor/cryptography/hazmat/primitives/kdf/concatkdf.py new file mode 100644 index 0000000..398dc5d --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/kdf/concatkdf.py @@ -0,0 +1,16 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + +ConcatKDFHash = rust_openssl.kdf.ConcatKDFHash +ConcatKDFHMAC = rust_openssl.kdf.ConcatKDFHMAC + +KeyDerivationFunction.register(ConcatKDFHash) +KeyDerivationFunction.register(ConcatKDFHMAC) + +__all__ = ["ConcatKDFHMAC", "ConcatKDFHash"] diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/kdf/hkdf.py b/intelliaide/vendor/cryptography/hazmat/primitives/kdf/hkdf.py new file mode 100644 index 0000000..1e162d9 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/kdf/hkdf.py @@ -0,0 +1,16 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + +HKDF = rust_openssl.kdf.HKDF +HKDFExpand = rust_openssl.kdf.HKDFExpand + +KeyDerivationFunction.register(HKDF) +KeyDerivationFunction.register(HKDFExpand) + +__all__ = ["HKDF", "HKDFExpand"] diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/kdf/kbkdf.py b/intelliaide/vendor/cryptography/hazmat/primitives/kdf/kbkdf.py new file mode 100644 index 0000000..e559df8 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/kdf/kbkdf.py @@ -0,0 +1,26 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography import utils +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + + +class Mode(utils.Enum): + CounterMode = "ctr" + + +class CounterLocation(utils.Enum): + BeforeFixed = "before_fixed" + AfterFixed = "after_fixed" + MiddleFixed = "middle_fixed" + + +KBKDFHMAC = rust_openssl.kdf.KBKDFHMAC +KeyDerivationFunction.register(KBKDFHMAC) + +KBKDFCMAC = rust_openssl.kdf.KBKDFCMAC +KeyDerivationFunction.register(KBKDFCMAC) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/kdf/pbkdf2.py b/intelliaide/vendor/cryptography/hazmat/primitives/kdf/pbkdf2.py new file mode 100644 index 0000000..771d80d --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + +PBKDF2HMAC = rust_openssl.kdf.PBKDF2HMAC +KeyDerivationFunction.register(PBKDF2HMAC) + +__all__ = ["PBKDF2HMAC"] diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/kdf/scrypt.py b/intelliaide/vendor/cryptography/hazmat/primitives/kdf/scrypt.py new file mode 100644 index 0000000..f791cee --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/kdf/scrypt.py @@ -0,0 +1,19 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import sys + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + +# This is used by the scrypt tests to skip tests that require more memory +# than the MEM_LIMIT +_MEM_LIMIT = sys.maxsize // 2 + +Scrypt = rust_openssl.kdf.Scrypt +KeyDerivationFunction.register(Scrypt) + +__all__ = ["Scrypt"] diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/kdf/x963kdf.py b/intelliaide/vendor/cryptography/hazmat/primitives/kdf/x963kdf.py new file mode 100644 index 0000000..8c4e2d3 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/kdf/x963kdf.py @@ -0,0 +1,13 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + +X963KDF = rust_openssl.kdf.X963KDF +KeyDerivationFunction.register(X963KDF) + +__all__ = ["X963KDF"] diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/keywrap.py b/intelliaide/vendor/cryptography/hazmat/primitives/keywrap.py new file mode 100644 index 0000000..a3e56b9 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/keywrap.py @@ -0,0 +1,180 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import typing + +from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.hazmat.primitives.ciphers.algorithms import AES +from cryptography.hazmat.primitives.ciphers.modes import ECB +from cryptography.hazmat.primitives.constant_time import bytes_eq + + +def _wrap_core( + wrapping_key: bytes, + a: bytes, + r: list[bytes], +) -> bytes: + # RFC 3394 Key Wrap - 2.2.1 (index method) + encryptor = Cipher(AES(wrapping_key), ECB()).encryptor() + n = len(r) + for j in range(6): + for i in range(n): + # every encryption operation is a discrete 16 byte chunk (because + # AES has a 128-bit block size) and since we're using ECB it is + # safe to reuse the encryptor for the entire operation + b = encryptor.update(a + r[i]) + a = ( + int.from_bytes(b[:8], byteorder="big") ^ ((n * j) + i + 1) + ).to_bytes(length=8, byteorder="big") + r[i] = b[-8:] + + assert encryptor.finalize() == b"" + + return a + b"".join(r) + + +def aes_key_wrap( + wrapping_key: bytes, + key_to_wrap: bytes, + backend: typing.Any = None, +) -> bytes: + if len(wrapping_key) not in [16, 24, 32]: + raise ValueError("The wrapping key must be a valid AES key length") + + if len(key_to_wrap) < 16: + raise ValueError("The key to wrap must be at least 16 bytes") + + if len(key_to_wrap) % 8 != 0: + raise ValueError("The key to wrap must be a multiple of 8 bytes") + + a = b"\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6" + r = [key_to_wrap[i : i + 8] for i in range(0, len(key_to_wrap), 8)] + return _wrap_core(wrapping_key, a, r) + + +def _unwrap_core( + wrapping_key: bytes, + a: bytes, + r: list[bytes], +) -> tuple[bytes, list[bytes]]: + # Implement RFC 3394 Key Unwrap - 2.2.2 (index method) + decryptor = Cipher(AES(wrapping_key), ECB()).decryptor() + n = len(r) + for j in reversed(range(6)): + for i in reversed(range(n)): + atr = ( + int.from_bytes(a, byteorder="big") ^ ((n * j) + i + 1) + ).to_bytes(length=8, byteorder="big") + r[i] + # every decryption operation is a discrete 16 byte chunk so + # it is safe to reuse the decryptor for the entire operation + b = decryptor.update(atr) + a = b[:8] + r[i] = b[-8:] + + assert decryptor.finalize() == b"" + return a, r + + +def aes_key_wrap_with_padding( + wrapping_key: bytes, + key_to_wrap: bytes, + backend: typing.Any = None, +) -> bytes: + if len(wrapping_key) not in [16, 24, 32]: + raise ValueError("The wrapping key must be a valid AES key length") + + if not key_to_wrap or len(key_to_wrap) > 2**32: + raise ValueError("key_to_wrap must be between 1 and 2^32 bytes") + + aiv = b"\xa6\x59\x59\xa6" + len(key_to_wrap).to_bytes( + length=4, byteorder="big" + ) + # pad the key to wrap if necessary + pad = (8 - (len(key_to_wrap) % 8)) % 8 + key_to_wrap = key_to_wrap + b"\x00" * pad + if len(key_to_wrap) == 8: + # RFC 5649 - 4.1 - exactly 8 octets after padding + encryptor = Cipher(AES(wrapping_key), ECB()).encryptor() + b = encryptor.update(aiv + key_to_wrap) + assert encryptor.finalize() == b"" + return b + else: + r = [key_to_wrap[i : i + 8] for i in range(0, len(key_to_wrap), 8)] + return _wrap_core(wrapping_key, aiv, r) + + +def aes_key_unwrap_with_padding( + wrapping_key: bytes, + wrapped_key: bytes, + backend: typing.Any = None, +) -> bytes: + if len(wrapped_key) < 16: + raise InvalidUnwrap("Must be at least 16 bytes") + + if len(wrapping_key) not in [16, 24, 32]: + raise ValueError("The wrapping key must be a valid AES key length") + + if len(wrapped_key) == 16: + # RFC 5649 - 4.2 - exactly two 64-bit blocks + decryptor = Cipher(AES(wrapping_key), ECB()).decryptor() + out = decryptor.update(wrapped_key) + assert decryptor.finalize() == b"" + a = out[:8] + data = out[8:] + n = 1 + else: + r = [wrapped_key[i : i + 8] for i in range(0, len(wrapped_key), 8)] + encrypted_aiv = r.pop(0) + n = len(r) + a, r = _unwrap_core(wrapping_key, encrypted_aiv, r) + data = b"".join(r) + + # 1) Check that MSB(32,A) = A65959A6. + # 2) Check that 8*(n-1) < LSB(32,A) <= 8*n. If so, let + # MLI = LSB(32,A). + # 3) Let b = (8*n)-MLI, and then check that the rightmost b octets of + # the output data are zero. + mli = int.from_bytes(a[4:], byteorder="big") + b = (8 * n) - mli + if ( + not bytes_eq(a[:4], b"\xa6\x59\x59\xa6") + or not 8 * (n - 1) < mli <= 8 * n + or (b != 0 and not bytes_eq(data[-b:], b"\x00" * b)) + ): + raise InvalidUnwrap() + + if b == 0: + return data + else: + return data[:-b] + + +def aes_key_unwrap( + wrapping_key: bytes, + wrapped_key: bytes, + backend: typing.Any = None, +) -> bytes: + if len(wrapped_key) < 24: + raise InvalidUnwrap("Must be at least 24 bytes") + + if len(wrapped_key) % 8 != 0: + raise InvalidUnwrap("The wrapped key must be a multiple of 8 bytes") + + if len(wrapping_key) not in [16, 24, 32]: + raise ValueError("The wrapping key must be a valid AES key length") + + aiv = b"\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6" + r = [wrapped_key[i : i + 8] for i in range(0, len(wrapped_key), 8)] + a = r.pop(0) + a, r = _unwrap_core(wrapping_key, a, r) + if not bytes_eq(a, aiv): + raise InvalidUnwrap() + + return b"".join(r) + + +class InvalidUnwrap(Exception): + pass diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/padding.py b/intelliaide/vendor/cryptography/hazmat/primitives/padding.py new file mode 100644 index 0000000..f9cd1f1 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/padding.py @@ -0,0 +1,69 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc + +from cryptography import utils +from cryptography.hazmat.bindings._rust import ( + ANSIX923PaddingContext, + ANSIX923UnpaddingContext, + PKCS7PaddingContext, + PKCS7UnpaddingContext, +) + + +class PaddingContext(metaclass=abc.ABCMeta): + @abc.abstractmethod + def update(self, data: utils.Buffer) -> bytes: + """ + Pads the provided bytes and returns any available data as bytes. + """ + + @abc.abstractmethod + def finalize(self) -> bytes: + """ + Finalize the padding, returns bytes. + """ + + +def _byte_padding_check(block_size: int) -> None: + if not (0 <= block_size <= 2040): + raise ValueError("block_size must be in range(0, 2041).") + + if block_size % 8 != 0: + raise ValueError("block_size must be a multiple of 8.") + + +class PKCS7: + def __init__(self, block_size: int): + _byte_padding_check(block_size) + self.block_size = block_size + + def padder(self) -> PaddingContext: + return PKCS7PaddingContext(self.block_size) + + def unpadder(self) -> PaddingContext: + return PKCS7UnpaddingContext(self.block_size) + + +PaddingContext.register(PKCS7PaddingContext) +PaddingContext.register(PKCS7UnpaddingContext) + + +class ANSIX923: + def __init__(self, block_size: int): + _byte_padding_check(block_size) + self.block_size = block_size + + def padder(self) -> PaddingContext: + return ANSIX923PaddingContext(self.block_size) + + def unpadder(self) -> PaddingContext: + return ANSIX923UnpaddingContext(self.block_size) + + +PaddingContext.register(ANSIX923PaddingContext) +PaddingContext.register(ANSIX923UnpaddingContext) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/poly1305.py b/intelliaide/vendor/cryptography/hazmat/primitives/poly1305.py new file mode 100644 index 0000000..7f5a77a --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/poly1305.py @@ -0,0 +1,11 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl + +__all__ = ["Poly1305"] + +Poly1305 = rust_openssl.poly1305.Poly1305 diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/serialization/__init__.py b/intelliaide/vendor/cryptography/hazmat/primitives/serialization/__init__.py new file mode 100644 index 0000000..62283cc --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/serialization/__init__.py @@ -0,0 +1,65 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat.primitives._serialization import ( + BestAvailableEncryption, + Encoding, + KeySerializationEncryption, + NoEncryption, + ParameterFormat, + PrivateFormat, + PublicFormat, + _KeySerializationEncryption, +) +from cryptography.hazmat.primitives.serialization.base import ( + load_der_parameters, + load_der_private_key, + load_der_public_key, + load_pem_parameters, + load_pem_private_key, + load_pem_public_key, +) +from cryptography.hazmat.primitives.serialization.ssh import ( + SSHCertificate, + SSHCertificateBuilder, + SSHCertificateType, + SSHCertPrivateKeyTypes, + SSHCertPublicKeyTypes, + SSHPrivateKeyTypes, + SSHPublicKeyTypes, + load_ssh_private_key, + load_ssh_public_identity, + load_ssh_public_key, + ssh_key_fingerprint, +) + +__all__ = [ + "BestAvailableEncryption", + "Encoding", + "KeySerializationEncryption", + "NoEncryption", + "ParameterFormat", + "PrivateFormat", + "PublicFormat", + "SSHCertPrivateKeyTypes", + "SSHCertPublicKeyTypes", + "SSHCertificate", + "SSHCertificateBuilder", + "SSHCertificateType", + "SSHPrivateKeyTypes", + "SSHPublicKeyTypes", + "_KeySerializationEncryption", + "load_der_parameters", + "load_der_private_key", + "load_der_public_key", + "load_pem_parameters", + "load_pem_private_key", + "load_pem_public_key", + "load_ssh_private_key", + "load_ssh_public_identity", + "load_ssh_public_key", + "ssh_key_fingerprint", +] diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/serialization/base.py b/intelliaide/vendor/cryptography/hazmat/primitives/serialization/base.py new file mode 100644 index 0000000..e7c998b --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/serialization/base.py @@ -0,0 +1,14 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from cryptography.hazmat.bindings._rust import openssl as rust_openssl + +load_pem_private_key = rust_openssl.keys.load_pem_private_key +load_der_private_key = rust_openssl.keys.load_der_private_key + +load_pem_public_key = rust_openssl.keys.load_pem_public_key +load_der_public_key = rust_openssl.keys.load_der_public_key + +load_pem_parameters = rust_openssl.dh.from_pem_parameters +load_der_parameters = rust_openssl.dh.from_der_parameters diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/serialization/pkcs12.py b/intelliaide/vendor/cryptography/hazmat/primitives/serialization/pkcs12.py new file mode 100644 index 0000000..58884ff --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/serialization/pkcs12.py @@ -0,0 +1,176 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import typing +from collections.abc import Iterable + +from cryptography import x509 +from cryptography.hazmat.bindings._rust import pkcs12 as rust_pkcs12 +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives._serialization import PBES as PBES +from cryptography.hazmat.primitives.asymmetric import ( + dsa, + ec, + ed448, + ed25519, + rsa, +) +from cryptography.hazmat.primitives.asymmetric.types import PrivateKeyTypes + +__all__ = [ + "PBES", + "PKCS12Certificate", + "PKCS12KeyAndCertificates", + "PKCS12PrivateKeyTypes", + "load_key_and_certificates", + "load_pkcs12", + "serialize_java_truststore", + "serialize_key_and_certificates", +] + +PKCS12PrivateKeyTypes = typing.Union[ + rsa.RSAPrivateKey, + dsa.DSAPrivateKey, + ec.EllipticCurvePrivateKey, + ed25519.Ed25519PrivateKey, + ed448.Ed448PrivateKey, +] + + +PKCS12Certificate = rust_pkcs12.PKCS12Certificate + + +class PKCS12KeyAndCertificates: + def __init__( + self, + key: PrivateKeyTypes | None, + cert: PKCS12Certificate | None, + additional_certs: list[PKCS12Certificate], + ): + if key is not None and not isinstance( + key, + ( + rsa.RSAPrivateKey, + dsa.DSAPrivateKey, + ec.EllipticCurvePrivateKey, + ed25519.Ed25519PrivateKey, + ed448.Ed448PrivateKey, + ), + ): + raise TypeError( + "Key must be RSA, DSA, EllipticCurve, ED25519, or ED448" + " private key, or None." + ) + if cert is not None and not isinstance(cert, PKCS12Certificate): + raise TypeError("cert must be a PKCS12Certificate object or None") + if not all( + isinstance(add_cert, PKCS12Certificate) + for add_cert in additional_certs + ): + raise TypeError( + "all values in additional_certs must be PKCS12Certificate" + " objects" + ) + self._key = key + self._cert = cert + self._additional_certs = additional_certs + + @property + def key(self) -> PrivateKeyTypes | None: + return self._key + + @property + def cert(self) -> PKCS12Certificate | None: + return self._cert + + @property + def additional_certs(self) -> list[PKCS12Certificate]: + return self._additional_certs + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PKCS12KeyAndCertificates): + return NotImplemented + + return ( + self.key == other.key + and self.cert == other.cert + and self.additional_certs == other.additional_certs + ) + + def __hash__(self) -> int: + return hash((self.key, self.cert, tuple(self.additional_certs))) + + def __repr__(self) -> str: + fmt = ( + "" + ) + return fmt.format(self.key, self.cert, self.additional_certs) + + +load_key_and_certificates = rust_pkcs12.load_key_and_certificates +load_pkcs12 = rust_pkcs12.load_pkcs12 + + +_PKCS12CATypes = typing.Union[ + x509.Certificate, + PKCS12Certificate, +] + + +def serialize_java_truststore( + certs: Iterable[PKCS12Certificate], + encryption_algorithm: serialization.KeySerializationEncryption, +) -> bytes: + if not certs: + raise ValueError("You must supply at least one cert") + + if not isinstance( + encryption_algorithm, serialization.KeySerializationEncryption + ): + raise TypeError( + "Key encryption algorithm must be a " + "KeySerializationEncryption instance" + ) + + return rust_pkcs12.serialize_java_truststore(certs, encryption_algorithm) + + +def serialize_key_and_certificates( + name: bytes | None, + key: PKCS12PrivateKeyTypes | None, + cert: x509.Certificate | None, + cas: Iterable[_PKCS12CATypes] | None, + encryption_algorithm: serialization.KeySerializationEncryption, +) -> bytes: + if key is not None and not isinstance( + key, + ( + rsa.RSAPrivateKey, + dsa.DSAPrivateKey, + ec.EllipticCurvePrivateKey, + ed25519.Ed25519PrivateKey, + ed448.Ed448PrivateKey, + ), + ): + raise TypeError( + "Key must be RSA, DSA, EllipticCurve, ED25519, or ED448" + " private key, or None." + ) + + if not isinstance( + encryption_algorithm, serialization.KeySerializationEncryption + ): + raise TypeError( + "Key encryption algorithm must be a " + "KeySerializationEncryption instance" + ) + + if key is None and cert is None and not cas: + raise ValueError("You must supply at least one of key, cert, or cas") + + return rust_pkcs12.serialize_key_and_certificates( + name, key, cert, cas, encryption_algorithm + ) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/serialization/pkcs7.py b/intelliaide/vendor/cryptography/hazmat/primitives/serialization/pkcs7.py new file mode 100644 index 0000000..76b667a --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/serialization/pkcs7.py @@ -0,0 +1,412 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import email.base64mime +import email.generator +import email.message +import email.policy +import io +import typing +from collections.abc import Iterable + +from cryptography import utils, x509 +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.bindings._rust import pkcs7 as rust_pkcs7 +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa +from cryptography.hazmat.primitives.ciphers import ( + algorithms, +) +from cryptography.utils import _check_byteslike + +load_pem_pkcs7_certificates = rust_pkcs7.load_pem_pkcs7_certificates + +load_der_pkcs7_certificates = rust_pkcs7.load_der_pkcs7_certificates + +serialize_certificates = rust_pkcs7.serialize_certificates + +PKCS7HashTypes = typing.Union[ + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, +] + +PKCS7PrivateKeyTypes = typing.Union[ + rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey +] + +ContentEncryptionAlgorithm = typing.Union[ + type[algorithms.AES128], type[algorithms.AES256] +] + + +class PKCS7Options(utils.Enum): + Text = "Add text/plain MIME type" + Binary = "Don't translate input data into canonical MIME format" + DetachedSignature = "Don't embed data in the PKCS7 structure" + NoCapabilities = "Don't embed SMIME capabilities" + NoAttributes = "Don't embed authenticatedAttributes" + NoCerts = "Don't embed signer certificate" + + +class PKCS7SignatureBuilder: + def __init__( + self, + data: utils.Buffer | None = None, + signers: list[ + tuple[ + x509.Certificate, + PKCS7PrivateKeyTypes, + PKCS7HashTypes, + padding.PSS | padding.PKCS1v15 | None, + ] + ] = [], + additional_certs: list[x509.Certificate] = [], + ): + self._data = data + self._signers = signers + self._additional_certs = additional_certs + + def set_data(self, data: utils.Buffer) -> PKCS7SignatureBuilder: + _check_byteslike("data", data) + if self._data is not None: + raise ValueError("data may only be set once") + + return PKCS7SignatureBuilder(data, self._signers) + + def add_signer( + self, + certificate: x509.Certificate, + private_key: PKCS7PrivateKeyTypes, + hash_algorithm: PKCS7HashTypes, + *, + rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, + ) -> PKCS7SignatureBuilder: + if not isinstance( + hash_algorithm, + ( + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, + ), + ): + raise TypeError( + "hash_algorithm must be one of hashes.SHA224, " + "SHA256, SHA384, or SHA512" + ) + if not isinstance(certificate, x509.Certificate): + raise TypeError("certificate must be a x509.Certificate") + + if not isinstance( + private_key, (rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey) + ): + raise TypeError("Only RSA & EC keys are supported at this time.") + + if rsa_padding is not None: + if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)): + raise TypeError("Padding must be PSS or PKCS1v15") + if not isinstance(private_key, rsa.RSAPrivateKey): + raise TypeError("Padding is only supported for RSA keys") + + return PKCS7SignatureBuilder( + self._data, + [ + *self._signers, + (certificate, private_key, hash_algorithm, rsa_padding), + ], + ) + + def add_certificate( + self, certificate: x509.Certificate + ) -> PKCS7SignatureBuilder: + if not isinstance(certificate, x509.Certificate): + raise TypeError("certificate must be a x509.Certificate") + + return PKCS7SignatureBuilder( + self._data, self._signers, [*self._additional_certs, certificate] + ) + + def sign( + self, + encoding: serialization.Encoding, + options: Iterable[PKCS7Options], + backend: typing.Any = None, + ) -> bytes: + if len(self._signers) == 0: + raise ValueError("Must have at least one signer") + if self._data is None: + raise ValueError("You must add data to sign") + options = list(options) + if not all(isinstance(x, PKCS7Options) for x in options): + raise ValueError("options must be from the PKCS7Options enum") + if encoding not in ( + serialization.Encoding.PEM, + serialization.Encoding.DER, + serialization.Encoding.SMIME, + ): + raise ValueError( + "Must be PEM, DER, or SMIME from the Encoding enum" + ) + + # Text is a meaningless option unless it is accompanied by + # DetachedSignature + if ( + PKCS7Options.Text in options + and PKCS7Options.DetachedSignature not in options + ): + raise ValueError( + "When passing the Text option you must also pass " + "DetachedSignature" + ) + + if PKCS7Options.Text in options and encoding in ( + serialization.Encoding.DER, + serialization.Encoding.PEM, + ): + raise ValueError( + "The Text option is only available for SMIME serialization" + ) + + # No attributes implies no capabilities so we'll error if you try to + # pass both. + if ( + PKCS7Options.NoAttributes in options + and PKCS7Options.NoCapabilities in options + ): + raise ValueError( + "NoAttributes is a superset of NoCapabilities. Do not pass " + "both values." + ) + + return rust_pkcs7.sign_and_serialize(self, encoding, options) + + +class PKCS7EnvelopeBuilder: + def __init__( + self, + *, + _data: bytes | None = None, + _recipients: list[x509.Certificate] | None = None, + _content_encryption_algorithm: ContentEncryptionAlgorithm + | None = None, + ): + from cryptography.hazmat.backends.openssl.backend import ( + backend as ossl, + ) + + if not ossl.rsa_encryption_supported(padding=padding.PKCS1v15()): + raise UnsupportedAlgorithm( + "RSA with PKCS1 v1.5 padding is not supported by this version" + " of OpenSSL.", + _Reasons.UNSUPPORTED_PADDING, + ) + self._data = _data + self._recipients = _recipients if _recipients is not None else [] + self._content_encryption_algorithm = _content_encryption_algorithm + + def set_data(self, data: bytes) -> PKCS7EnvelopeBuilder: + _check_byteslike("data", data) + if self._data is not None: + raise ValueError("data may only be set once") + + return PKCS7EnvelopeBuilder( + _data=data, + _recipients=self._recipients, + _content_encryption_algorithm=self._content_encryption_algorithm, + ) + + def add_recipient( + self, + certificate: x509.Certificate, + ) -> PKCS7EnvelopeBuilder: + if not isinstance(certificate, x509.Certificate): + raise TypeError("certificate must be a x509.Certificate") + + if not isinstance(certificate.public_key(), rsa.RSAPublicKey): + raise TypeError("Only RSA keys are supported at this time.") + + return PKCS7EnvelopeBuilder( + _data=self._data, + _recipients=[ + *self._recipients, + certificate, + ], + _content_encryption_algorithm=self._content_encryption_algorithm, + ) + + def set_content_encryption_algorithm( + self, content_encryption_algorithm: ContentEncryptionAlgorithm + ) -> PKCS7EnvelopeBuilder: + if self._content_encryption_algorithm is not None: + raise ValueError("Content encryption algo may only be set once") + if content_encryption_algorithm not in { + algorithms.AES128, + algorithms.AES256, + }: + raise TypeError("Only AES128 and AES256 are supported") + + return PKCS7EnvelopeBuilder( + _data=self._data, + _recipients=self._recipients, + _content_encryption_algorithm=content_encryption_algorithm, + ) + + def encrypt( + self, + encoding: serialization.Encoding, + options: Iterable[PKCS7Options], + ) -> bytes: + if len(self._recipients) == 0: + raise ValueError("Must have at least one recipient") + if self._data is None: + raise ValueError("You must add data to encrypt") + + # The default content encryption algorithm is AES-128-CBC, which the + # S/MIME v3.2 RFC specifies as MUST support (https://datatracker.ietf.org/doc/html/rfc5751#section-2.7) + # however rest of S/MIME v3.2 is not currently supported + content_encryption_algorithm = ( + self._content_encryption_algorithm or algorithms.AES128 + ) + + options = list(options) + if not all(isinstance(x, PKCS7Options) for x in options): + raise ValueError("options must be from the PKCS7Options enum") + if encoding not in ( + serialization.Encoding.PEM, + serialization.Encoding.DER, + serialization.Encoding.SMIME, + ): + raise ValueError( + "Must be PEM, DER, or SMIME from the Encoding enum" + ) + + # Only allow options that make sense for encryption + if any( + opt not in [PKCS7Options.Text, PKCS7Options.Binary] + for opt in options + ): + raise ValueError( + "Only the following options are supported for encryption: " + "Text, Binary" + ) + elif PKCS7Options.Text in options and PKCS7Options.Binary in options: + # OpenSSL accepts both options at the same time, but ignores Text. + # We fail defensively to avoid unexpected outputs. + raise ValueError( + "Cannot use Binary and Text options at the same time" + ) + + return rust_pkcs7.encrypt_and_serialize( + self, content_encryption_algorithm, encoding, options + ) + + +pkcs7_decrypt_der = rust_pkcs7.decrypt_der +pkcs7_decrypt_pem = rust_pkcs7.decrypt_pem +pkcs7_decrypt_smime = rust_pkcs7.decrypt_smime + + +def _smime_signed_encode( + data: bytes, signature: bytes, micalg: str, text_mode: bool +) -> bytes: + # This function works pretty hard to replicate what OpenSSL does + # precisely. For good and for ill. + + m = email.message.Message() + m.add_header("MIME-Version", "1.0") + m.add_header( + "Content-Type", + "multipart/signed", + protocol="application/x-pkcs7-signature", + micalg=micalg, + ) + + m.preamble = "This is an S/MIME signed message\n" + + msg_part = OpenSSLMimePart() + msg_part.set_payload(data) + if text_mode: + msg_part.add_header("Content-Type", "text/plain") + m.attach(msg_part) + + sig_part = email.message.MIMEPart() + sig_part.add_header( + "Content-Type", "application/x-pkcs7-signature", name="smime.p7s" + ) + sig_part.add_header("Content-Transfer-Encoding", "base64") + sig_part.add_header( + "Content-Disposition", "attachment", filename="smime.p7s" + ) + sig_part.set_payload( + email.base64mime.body_encode(signature, maxlinelen=65) + ) + del sig_part["MIME-Version"] + m.attach(sig_part) + + fp = io.BytesIO() + g = email.generator.BytesGenerator( + fp, + maxheaderlen=0, + mangle_from_=False, + policy=m.policy.clone(linesep="\r\n"), + ) + g.flatten(m) + return fp.getvalue() + + +def _smime_enveloped_encode(data: bytes) -> bytes: + m = email.message.Message() + m.add_header("MIME-Version", "1.0") + m.add_header("Content-Disposition", "attachment", filename="smime.p7m") + m.add_header( + "Content-Type", + "application/pkcs7-mime", + smime_type="enveloped-data", + name="smime.p7m", + ) + m.add_header("Content-Transfer-Encoding", "base64") + + m.set_payload(email.base64mime.body_encode(data, maxlinelen=65)) + + return m.as_bytes(policy=m.policy.clone(linesep="\n", max_line_length=0)) + + +def _smime_enveloped_decode(data: bytes) -> bytes: + m = email.message_from_bytes(data) + if m.get_content_type() not in { + "application/x-pkcs7-mime", + "application/pkcs7-mime", + }: + raise ValueError("Not an S/MIME enveloped message") + return bytes(m.get_payload(decode=True)) + + +def _smime_remove_text_headers(data: bytes) -> bytes: + m = email.message_from_bytes(data) + # Using get() instead of get_content_type() since it has None as default, + # where the latter has "text/plain". Both methods are case-insensitive. + content_type = m.get("content-type") + if content_type is None: + raise ValueError( + "Decrypted MIME data has no 'Content-Type' header. " + "Please remove the 'Text' option to parse it manually." + ) + if "text/plain" not in content_type: + raise ValueError( + f"Decrypted MIME data content type is '{content_type}', not " + "'text/plain'. Remove the 'Text' option to parse it manually." + ) + return bytes(m.get_payload(decode=True)) + + +class OpenSSLMimePart(email.message.MIMEPart): + # A MIMEPart subclass that replicates OpenSSL's behavior of not including + # a newline if there are no headers. + def _write_headers(self, generator) -> None: + if list(self.raw_items()): + generator._write_headers(self) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/serialization/ssh.py b/intelliaide/vendor/cryptography/hazmat/primitives/serialization/ssh.py new file mode 100644 index 0000000..411113b --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/serialization/ssh.py @@ -0,0 +1,1621 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import binascii +import enum +import os +import re +import typing +import warnings +from base64 import encodebytes as _base64_encode +from dataclasses import dataclass + +from cryptography import utils +from cryptography.exceptions import UnsupportedAlgorithm +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ( + dsa, + ec, + ed25519, + padding, + rsa, +) +from cryptography.hazmat.primitives.asymmetric import utils as asym_utils +from cryptography.hazmat.primitives.ciphers import ( + AEADDecryptionContext, + Cipher, + algorithms, + modes, +) +from cryptography.hazmat.primitives.serialization import ( + Encoding, + KeySerializationEncryption, + NoEncryption, + PrivateFormat, + PublicFormat, + _KeySerializationEncryption, +) + +try: + from bcrypt import kdf as _bcrypt_kdf + + _bcrypt_supported = True +except ImportError: + _bcrypt_supported = False + + def _bcrypt_kdf( + password: bytes, + salt: bytes, + desired_key_bytes: int, + rounds: int, + ignore_few_rounds: bool = False, + ) -> bytes: + raise UnsupportedAlgorithm("Need bcrypt module") + + +_SSH_ED25519 = b"ssh-ed25519" +_SSH_RSA = b"ssh-rsa" +_SSH_DSA = b"ssh-dss" +_ECDSA_NISTP256 = b"ecdsa-sha2-nistp256" +_ECDSA_NISTP384 = b"ecdsa-sha2-nistp384" +_ECDSA_NISTP521 = b"ecdsa-sha2-nistp521" +_CERT_SUFFIX = b"-cert-v01@openssh.com" + +# U2F application string suffixed pubkey +_SK_SSH_ED25519 = b"sk-ssh-ed25519@openssh.com" +_SK_SSH_ECDSA_NISTP256 = b"sk-ecdsa-sha2-nistp256@openssh.com" + +# These are not key types, only algorithms, so they cannot appear +# as a public key type +_SSH_RSA_SHA256 = b"rsa-sha2-256" +_SSH_RSA_SHA512 = b"rsa-sha2-512" + +_SSH_PUBKEY_RC = re.compile(rb"\A(\S+)[ \t]+(\S+)") +_SK_MAGIC = b"openssh-key-v1\0" +_SK_START = b"-----BEGIN OPENSSH PRIVATE KEY-----" +_SK_END = b"-----END OPENSSH PRIVATE KEY-----" +_BCRYPT = b"bcrypt" +_NONE = b"none" +_DEFAULT_CIPHER = b"aes256-ctr" +_DEFAULT_ROUNDS = 16 + +# re is only way to work on bytes-like data +_PEM_RC = re.compile(_SK_START + b"(.*?)" + _SK_END, re.DOTALL) + +# padding for max blocksize +_PADDING = memoryview(bytearray(range(1, 1 + 16))) + + +@dataclass +class _SSHCipher: + alg: type[algorithms.AES] + key_len: int + mode: type[modes.CTR] | type[modes.CBC] | type[modes.GCM] + block_len: int + iv_len: int + tag_len: int | None + is_aead: bool + + +# ciphers that are actually used in key wrapping +_SSH_CIPHERS: dict[bytes, _SSHCipher] = { + b"aes256-ctr": _SSHCipher( + alg=algorithms.AES, + key_len=32, + mode=modes.CTR, + block_len=16, + iv_len=16, + tag_len=None, + is_aead=False, + ), + b"aes256-cbc": _SSHCipher( + alg=algorithms.AES, + key_len=32, + mode=modes.CBC, + block_len=16, + iv_len=16, + tag_len=None, + is_aead=False, + ), + b"aes256-gcm@openssh.com": _SSHCipher( + alg=algorithms.AES, + key_len=32, + mode=modes.GCM, + block_len=16, + iv_len=12, + tag_len=16, + is_aead=True, + ), +} + +# map local curve name to key type +_ECDSA_KEY_TYPE = { + "secp256r1": _ECDSA_NISTP256, + "secp384r1": _ECDSA_NISTP384, + "secp521r1": _ECDSA_NISTP521, +} + + +def _get_ssh_key_type(key: SSHPrivateKeyTypes | SSHPublicKeyTypes) -> bytes: + if isinstance(key, ec.EllipticCurvePrivateKey): + key_type = _ecdsa_key_type(key.public_key()) + elif isinstance(key, ec.EllipticCurvePublicKey): + key_type = _ecdsa_key_type(key) + elif isinstance(key, (rsa.RSAPrivateKey, rsa.RSAPublicKey)): + key_type = _SSH_RSA + elif isinstance(key, (dsa.DSAPrivateKey, dsa.DSAPublicKey)): + key_type = _SSH_DSA + elif isinstance( + key, (ed25519.Ed25519PrivateKey, ed25519.Ed25519PublicKey) + ): + key_type = _SSH_ED25519 + else: + raise ValueError("Unsupported key type") + + return key_type + + +def _ecdsa_key_type(public_key: ec.EllipticCurvePublicKey) -> bytes: + """Return SSH key_type and curve_name for private key.""" + curve = public_key.curve + if curve.name not in _ECDSA_KEY_TYPE: + raise ValueError( + f"Unsupported curve for ssh private key: {curve.name!r}" + ) + return _ECDSA_KEY_TYPE[curve.name] + + +def _ssh_pem_encode( + data: utils.Buffer, + prefix: bytes = _SK_START + b"\n", + suffix: bytes = _SK_END + b"\n", +) -> bytes: + return b"".join([prefix, _base64_encode(data), suffix]) + + +def _check_block_size(data: utils.Buffer, block_len: int) -> None: + """Require data to be full blocks""" + if not data or len(data) % block_len != 0: + raise ValueError("Corrupt data: missing padding") + + +def _check_empty(data: utils.Buffer) -> None: + """All data should have been parsed.""" + if data: + raise ValueError("Corrupt data: unparsed data") + + +def _init_cipher( + ciphername: bytes, + password: bytes | None, + salt: bytes, + rounds: int, +) -> Cipher[modes.CBC | modes.CTR | modes.GCM]: + """Generate key + iv and return cipher.""" + if not password: + raise TypeError( + "Key is password-protected, but password was not provided." + ) + + ciph = _SSH_CIPHERS[ciphername] + seed = _bcrypt_kdf( + password, salt, ciph.key_len + ciph.iv_len, rounds, True + ) + return Cipher( + ciph.alg(seed[: ciph.key_len]), + ciph.mode(seed[ciph.key_len :]), + ) + + +def _get_u32(data: memoryview) -> tuple[int, memoryview]: + """Uint32""" + if len(data) < 4: + raise ValueError("Invalid data") + return int.from_bytes(data[:4], byteorder="big"), data[4:] + + +def _get_u64(data: memoryview) -> tuple[int, memoryview]: + """Uint64""" + if len(data) < 8: + raise ValueError("Invalid data") + return int.from_bytes(data[:8], byteorder="big"), data[8:] + + +def _get_sshstr(data: memoryview) -> tuple[memoryview, memoryview]: + """Bytes with u32 length prefix""" + n, data = _get_u32(data) + if n > len(data): + raise ValueError("Invalid data") + return data[:n], data[n:] + + +def _get_mpint(data: memoryview) -> tuple[int, memoryview]: + """Big integer.""" + val, data = _get_sshstr(data) + if val and val[0] > 0x7F: + raise ValueError("Invalid data") + return int.from_bytes(val, "big"), data + + +def _to_mpint(val: int) -> bytes: + """Storage format for signed bigint.""" + if val < 0: + raise ValueError("negative mpint not allowed") + if not val: + return b"" + nbytes = (val.bit_length() + 8) // 8 + return utils.int_to_bytes(val, nbytes) + + +class _FragList: + """Build recursive structure without data copy.""" + + flist: list[utils.Buffer] + + def __init__(self, init: list[utils.Buffer] | None = None) -> None: + self.flist = [] + if init: + self.flist.extend(init) + + def put_raw(self, val: utils.Buffer) -> None: + """Add plain bytes""" + self.flist.append(val) + + def put_u32(self, val: int) -> None: + """Big-endian uint32""" + self.flist.append(val.to_bytes(length=4, byteorder="big")) + + def put_u64(self, val: int) -> None: + """Big-endian uint64""" + self.flist.append(val.to_bytes(length=8, byteorder="big")) + + def put_sshstr(self, val: bytes | _FragList) -> None: + """Bytes prefixed with u32 length""" + if isinstance(val, (bytes, memoryview, bytearray)): + self.put_u32(len(val)) + self.flist.append(val) + else: + self.put_u32(val.size()) + self.flist.extend(val.flist) + + def put_mpint(self, val: int) -> None: + """Big-endian bigint prefixed with u32 length""" + self.put_sshstr(_to_mpint(val)) + + def size(self) -> int: + """Current number of bytes""" + return sum(map(len, self.flist)) + + def render(self, dstbuf: memoryview, pos: int = 0) -> int: + """Write into bytearray""" + for frag in self.flist: + flen = len(frag) + start, pos = pos, pos + flen + dstbuf[start:pos] = frag + return pos + + def tobytes(self) -> bytes: + """Return as bytes""" + buf = memoryview(bytearray(self.size())) + self.render(buf) + return buf.tobytes() + + +class _SSHFormatRSA: + """Format for RSA keys. + + Public: + mpint e, n + Private: + mpint n, e, d, iqmp, p, q + """ + + def get_public( + self, data: memoryview + ) -> tuple[tuple[int, int], memoryview]: + """RSA public fields""" + e, data = _get_mpint(data) + n, data = _get_mpint(data) + return (e, n), data + + def load_public( + self, data: memoryview + ) -> tuple[rsa.RSAPublicKey, memoryview]: + """Make RSA public key from data.""" + (e, n), data = self.get_public(data) + public_numbers = rsa.RSAPublicNumbers(e, n) + public_key = public_numbers.public_key() + return public_key, data + + def load_private( + self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool + ) -> tuple[rsa.RSAPrivateKey, memoryview]: + """Make RSA private key from data.""" + n, data = _get_mpint(data) + e, data = _get_mpint(data) + d, data = _get_mpint(data) + iqmp, data = _get_mpint(data) + p, data = _get_mpint(data) + q, data = _get_mpint(data) + + if (e, n) != pubfields: + raise ValueError("Corrupt data: rsa field mismatch") + dmp1 = rsa.rsa_crt_dmp1(d, p) + dmq1 = rsa.rsa_crt_dmq1(d, q) + public_numbers = rsa.RSAPublicNumbers(e, n) + private_numbers = rsa.RSAPrivateNumbers( + p, q, d, dmp1, dmq1, iqmp, public_numbers + ) + private_key = private_numbers.private_key( + unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation + ) + return private_key, data + + def encode_public( + self, public_key: rsa.RSAPublicKey, f_pub: _FragList + ) -> None: + """Write RSA public key""" + pubn = public_key.public_numbers() + f_pub.put_mpint(pubn.e) + f_pub.put_mpint(pubn.n) + + def encode_private( + self, private_key: rsa.RSAPrivateKey, f_priv: _FragList + ) -> None: + """Write RSA private key""" + private_numbers = private_key.private_numbers() + public_numbers = private_numbers.public_numbers + + f_priv.put_mpint(public_numbers.n) + f_priv.put_mpint(public_numbers.e) + + f_priv.put_mpint(private_numbers.d) + f_priv.put_mpint(private_numbers.iqmp) + f_priv.put_mpint(private_numbers.p) + f_priv.put_mpint(private_numbers.q) + + +class _SSHFormatDSA: + """Format for DSA keys. + + Public: + mpint p, q, g, y + Private: + mpint p, q, g, y, x + """ + + def get_public(self, data: memoryview) -> tuple[tuple, memoryview]: + """DSA public fields""" + p, data = _get_mpint(data) + q, data = _get_mpint(data) + g, data = _get_mpint(data) + y, data = _get_mpint(data) + return (p, q, g, y), data + + def load_public( + self, data: memoryview + ) -> tuple[dsa.DSAPublicKey, memoryview]: + """Make DSA public key from data.""" + (p, q, g, y), data = self.get_public(data) + parameter_numbers = dsa.DSAParameterNumbers(p, q, g) + public_numbers = dsa.DSAPublicNumbers(y, parameter_numbers) + self._validate(public_numbers) + public_key = public_numbers.public_key() + return public_key, data + + def load_private( + self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool + ) -> tuple[dsa.DSAPrivateKey, memoryview]: + """Make DSA private key from data.""" + (p, q, g, y), data = self.get_public(data) + x, data = _get_mpint(data) + + if (p, q, g, y) != pubfields: + raise ValueError("Corrupt data: dsa field mismatch") + parameter_numbers = dsa.DSAParameterNumbers(p, q, g) + public_numbers = dsa.DSAPublicNumbers(y, parameter_numbers) + self._validate(public_numbers) + private_numbers = dsa.DSAPrivateNumbers(x, public_numbers) + private_key = private_numbers.private_key() + return private_key, data + + def encode_public( + self, public_key: dsa.DSAPublicKey, f_pub: _FragList + ) -> None: + """Write DSA public key""" + public_numbers = public_key.public_numbers() + parameter_numbers = public_numbers.parameter_numbers + self._validate(public_numbers) + + f_pub.put_mpint(parameter_numbers.p) + f_pub.put_mpint(parameter_numbers.q) + f_pub.put_mpint(parameter_numbers.g) + f_pub.put_mpint(public_numbers.y) + + def encode_private( + self, private_key: dsa.DSAPrivateKey, f_priv: _FragList + ) -> None: + """Write DSA private key""" + self.encode_public(private_key.public_key(), f_priv) + f_priv.put_mpint(private_key.private_numbers().x) + + def _validate(self, public_numbers: dsa.DSAPublicNumbers) -> None: + parameter_numbers = public_numbers.parameter_numbers + if parameter_numbers.p.bit_length() != 1024: + raise ValueError("SSH supports only 1024 bit DSA keys") + + +class _SSHFormatECDSA: + """Format for ECDSA keys. + + Public: + str curve + bytes point + Private: + str curve + bytes point + mpint secret + """ + + def __init__(self, ssh_curve_name: bytes, curve: ec.EllipticCurve): + self.ssh_curve_name = ssh_curve_name + self.curve = curve + + def get_public( + self, data: memoryview + ) -> tuple[tuple[memoryview, memoryview], memoryview]: + """ECDSA public fields""" + curve, data = _get_sshstr(data) + point, data = _get_sshstr(data) + if curve != self.ssh_curve_name: + raise ValueError("Curve name mismatch") + if len(point) == 0: + raise ValueError("Invalid EC point: empty data") + if point[0] != 4: + raise NotImplementedError("Need uncompressed point") + return (curve, point), data + + def load_public( + self, data: memoryview + ) -> tuple[ec.EllipticCurvePublicKey, memoryview]: + """Make ECDSA public key from data.""" + (_, point), data = self.get_public(data) + public_key = ec.EllipticCurvePublicKey.from_encoded_point( + self.curve, point.tobytes() + ) + return public_key, data + + def load_private( + self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool + ) -> tuple[ec.EllipticCurvePrivateKey, memoryview]: + """Make ECDSA private key from data.""" + (curve_name, point), data = self.get_public(data) + secret, data = _get_mpint(data) + + if (curve_name, point) != pubfields: + raise ValueError("Corrupt data: ecdsa field mismatch") + private_key = ec.derive_private_key(secret, self.curve) + return private_key, data + + def encode_public( + self, public_key: ec.EllipticCurvePublicKey, f_pub: _FragList + ) -> None: + """Write ECDSA public key""" + point = public_key.public_bytes( + Encoding.X962, PublicFormat.UncompressedPoint + ) + f_pub.put_sshstr(self.ssh_curve_name) + f_pub.put_sshstr(point) + + def encode_private( + self, private_key: ec.EllipticCurvePrivateKey, f_priv: _FragList + ) -> None: + """Write ECDSA private key""" + public_key = private_key.public_key() + private_numbers = private_key.private_numbers() + + self.encode_public(public_key, f_priv) + f_priv.put_mpint(private_numbers.private_value) + + +class _SSHFormatEd25519: + """Format for Ed25519 keys. + + Public: + bytes point + Private: + bytes point + bytes secret_and_point + """ + + def get_public( + self, data: memoryview + ) -> tuple[tuple[memoryview], memoryview]: + """Ed25519 public fields""" + point, data = _get_sshstr(data) + return (point,), data + + def load_public( + self, data: memoryview + ) -> tuple[ed25519.Ed25519PublicKey, memoryview]: + """Make Ed25519 public key from data.""" + (point,), data = self.get_public(data) + public_key = ed25519.Ed25519PublicKey.from_public_bytes( + point.tobytes() + ) + return public_key, data + + def load_private( + self, data: memoryview, pubfields, unsafe_skip_rsa_key_validation: bool + ) -> tuple[ed25519.Ed25519PrivateKey, memoryview]: + """Make Ed25519 private key from data.""" + (point,), data = self.get_public(data) + keypair, data = _get_sshstr(data) + + secret = keypair[:32] + point2 = keypair[32:] + if point != point2 or (point,) != pubfields: + raise ValueError("Corrupt data: ed25519 field mismatch") + private_key = ed25519.Ed25519PrivateKey.from_private_bytes(secret) + return private_key, data + + def encode_public( + self, public_key: ed25519.Ed25519PublicKey, f_pub: _FragList + ) -> None: + """Write Ed25519 public key""" + raw_public_key = public_key.public_bytes( + Encoding.Raw, PublicFormat.Raw + ) + f_pub.put_sshstr(raw_public_key) + + def encode_private( + self, private_key: ed25519.Ed25519PrivateKey, f_priv: _FragList + ) -> None: + """Write Ed25519 private key""" + public_key = private_key.public_key() + raw_private_key = private_key.private_bytes( + Encoding.Raw, PrivateFormat.Raw, NoEncryption() + ) + raw_public_key = public_key.public_bytes( + Encoding.Raw, PublicFormat.Raw + ) + f_keypair = _FragList([raw_private_key, raw_public_key]) + + self.encode_public(public_key, f_priv) + f_priv.put_sshstr(f_keypair) + + +def load_application(data) -> tuple[memoryview, memoryview]: + """ + U2F application strings + """ + application, data = _get_sshstr(data) + if not application.tobytes().startswith(b"ssh:"): + raise ValueError( + "U2F application string does not start with b'ssh:' " + f"({application})" + ) + return application, data + + +class _SSHFormatSKEd25519: + """ + The format of a sk-ssh-ed25519@openssh.com public key is: + + string "sk-ssh-ed25519@openssh.com" + string public key + string application (user-specified, but typically "ssh:") + """ + + def load_public( + self, data: memoryview + ) -> tuple[ed25519.Ed25519PublicKey, memoryview]: + """Make Ed25519 public key from data.""" + public_key, data = _lookup_kformat(_SSH_ED25519).load_public(data) + _, data = load_application(data) + return public_key, data + + def get_public(self, data: memoryview) -> typing.NoReturn: + # Confusingly `get_public` is an entry point used by private key + # loading. + raise UnsupportedAlgorithm( + "sk-ssh-ed25519 private keys cannot be loaded" + ) + + +class _SSHFormatSKECDSA: + """ + The format of a sk-ecdsa-sha2-nistp256@openssh.com public key is: + + string "sk-ecdsa-sha2-nistp256@openssh.com" + string curve name + ec_point Q + string application (user-specified, but typically "ssh:") + """ + + def load_public( + self, data: memoryview + ) -> tuple[ec.EllipticCurvePublicKey, memoryview]: + """Make ECDSA public key from data.""" + public_key, data = _lookup_kformat(_ECDSA_NISTP256).load_public(data) + _, data = load_application(data) + return public_key, data + + def get_public(self, data: memoryview) -> typing.NoReturn: + # Confusingly `get_public` is an entry point used by private key + # loading. + raise UnsupportedAlgorithm( + "sk-ecdsa-sha2-nistp256 private keys cannot be loaded" + ) + + +_KEY_FORMATS = { + _SSH_RSA: _SSHFormatRSA(), + _SSH_DSA: _SSHFormatDSA(), + _SSH_ED25519: _SSHFormatEd25519(), + _ECDSA_NISTP256: _SSHFormatECDSA(b"nistp256", ec.SECP256R1()), + _ECDSA_NISTP384: _SSHFormatECDSA(b"nistp384", ec.SECP384R1()), + _ECDSA_NISTP521: _SSHFormatECDSA(b"nistp521", ec.SECP521R1()), + _SK_SSH_ED25519: _SSHFormatSKEd25519(), + _SK_SSH_ECDSA_NISTP256: _SSHFormatSKECDSA(), +} + + +def _lookup_kformat(key_type: utils.Buffer): + """Return valid format or throw error""" + if not isinstance(key_type, bytes): + key_type = memoryview(key_type).tobytes() + if key_type in _KEY_FORMATS: + return _KEY_FORMATS[key_type] + raise UnsupportedAlgorithm(f"Unsupported key type: {key_type!r}") + + +SSHPrivateKeyTypes = typing.Union[ + ec.EllipticCurvePrivateKey, + rsa.RSAPrivateKey, + dsa.DSAPrivateKey, + ed25519.Ed25519PrivateKey, +] + + +def load_ssh_private_key( + data: utils.Buffer, + password: bytes | None, + backend: typing.Any = None, + *, + unsafe_skip_rsa_key_validation: bool = False, +) -> SSHPrivateKeyTypes: + """Load private key from OpenSSH custom encoding.""" + utils._check_byteslike("data", data) + if password is not None: + utils._check_bytes("password", password) + + m = _PEM_RC.search(data) + if not m: + raise ValueError("Not OpenSSH private key format") + p1 = m.start(1) + p2 = m.end(1) + data = binascii.a2b_base64(memoryview(data)[p1:p2]) + if not data.startswith(_SK_MAGIC): + raise ValueError("Not OpenSSH private key format") + data = memoryview(data)[len(_SK_MAGIC) :] + + # parse header + ciphername, data = _get_sshstr(data) + kdfname, data = _get_sshstr(data) + kdfoptions, data = _get_sshstr(data) + nkeys, data = _get_u32(data) + if nkeys != 1: + raise ValueError("Only one key supported") + + # load public key data + pubdata, data = _get_sshstr(data) + pub_key_type, pubdata = _get_sshstr(pubdata) + kformat = _lookup_kformat(pub_key_type) + pubfields, pubdata = kformat.get_public(pubdata) + _check_empty(pubdata) + + if ciphername != _NONE or kdfname != _NONE: + ciphername_bytes = ciphername.tobytes() + if ciphername_bytes not in _SSH_CIPHERS: + raise UnsupportedAlgorithm( + f"Unsupported cipher: {ciphername_bytes!r}" + ) + if kdfname != _BCRYPT: + raise UnsupportedAlgorithm(f"Unsupported KDF: {kdfname!r}") + blklen = _SSH_CIPHERS[ciphername_bytes].block_len + tag_len = _SSH_CIPHERS[ciphername_bytes].tag_len + # load secret data + edata, data = _get_sshstr(data) + # see https://bugzilla.mindrot.org/show_bug.cgi?id=3553 for + # information about how OpenSSH handles AEAD tags + if _SSH_CIPHERS[ciphername_bytes].is_aead: + tag = bytes(data) + if len(tag) != tag_len: + raise ValueError("Corrupt data: invalid tag length for cipher") + else: + _check_empty(data) + _check_block_size(edata, blklen) + salt, kbuf = _get_sshstr(kdfoptions) + rounds, kbuf = _get_u32(kbuf) + _check_empty(kbuf) + ciph = _init_cipher(ciphername_bytes, password, salt.tobytes(), rounds) + dec = ciph.decryptor() + edata = memoryview(dec.update(edata)) + if _SSH_CIPHERS[ciphername_bytes].is_aead: + assert isinstance(dec, AEADDecryptionContext) + _check_empty(dec.finalize_with_tag(tag)) + else: + # _check_block_size requires data to be a full block so there + # should be no output from finalize + _check_empty(dec.finalize()) + else: + if password: + raise TypeError( + "Password was given but private key is not encrypted." + ) + # load secret data + edata, data = _get_sshstr(data) + _check_empty(data) + blklen = 8 + _check_block_size(edata, blklen) + ck1, edata = _get_u32(edata) + ck2, edata = _get_u32(edata) + if ck1 != ck2: + raise ValueError("Corrupt data: broken checksum") + + # load per-key struct + key_type, edata = _get_sshstr(edata) + if key_type != pub_key_type: + raise ValueError("Corrupt data: key type mismatch") + private_key, edata = kformat.load_private( + edata, + pubfields, + unsafe_skip_rsa_key_validation=unsafe_skip_rsa_key_validation, + ) + # We don't use the comment + _, edata = _get_sshstr(edata) + + # yes, SSH does padding check *after* all other parsing is done. + # need to follow as it writes zero-byte padding too. + if edata != _PADDING[: len(edata)]: + raise ValueError("Corrupt data: invalid padding") + + if isinstance(private_key, dsa.DSAPrivateKey): + warnings.warn( + "SSH DSA keys are deprecated and will be removed in a future " + "release.", + utils.DeprecatedIn40, + stacklevel=2, + ) + + return private_key + + +def _serialize_ssh_private_key( + private_key: SSHPrivateKeyTypes, + password: bytes, + encryption_algorithm: KeySerializationEncryption, +) -> bytes: + """Serialize private key with OpenSSH custom encoding.""" + utils._check_bytes("password", password) + if isinstance(private_key, dsa.DSAPrivateKey): + warnings.warn( + "SSH DSA key support is deprecated and will be " + "removed in a future release", + utils.DeprecatedIn40, + stacklevel=4, + ) + + key_type = _get_ssh_key_type(private_key) + kformat = _lookup_kformat(key_type) + + # setup parameters + f_kdfoptions = _FragList() + if password: + ciphername = _DEFAULT_CIPHER + blklen = _SSH_CIPHERS[ciphername].block_len + kdfname = _BCRYPT + rounds = _DEFAULT_ROUNDS + if ( + isinstance(encryption_algorithm, _KeySerializationEncryption) + and encryption_algorithm._kdf_rounds is not None + ): + rounds = encryption_algorithm._kdf_rounds + salt = os.urandom(16) + f_kdfoptions.put_sshstr(salt) + f_kdfoptions.put_u32(rounds) + ciph = _init_cipher(ciphername, password, salt, rounds) + else: + ciphername = kdfname = _NONE + blklen = 8 + ciph = None + nkeys = 1 + checkval = os.urandom(4) + comment = b"" + + # encode public and private parts together + f_public_key = _FragList() + f_public_key.put_sshstr(key_type) + kformat.encode_public(private_key.public_key(), f_public_key) + + f_secrets = _FragList([checkval, checkval]) + f_secrets.put_sshstr(key_type) + kformat.encode_private(private_key, f_secrets) + f_secrets.put_sshstr(comment) + f_secrets.put_raw(_PADDING[: blklen - (f_secrets.size() % blklen)]) + + # top-level structure + f_main = _FragList() + f_main.put_raw(_SK_MAGIC) + f_main.put_sshstr(ciphername) + f_main.put_sshstr(kdfname) + f_main.put_sshstr(f_kdfoptions) + f_main.put_u32(nkeys) + f_main.put_sshstr(f_public_key) + f_main.put_sshstr(f_secrets) + + # copy result info bytearray + slen = f_secrets.size() + mlen = f_main.size() + buf = memoryview(bytearray(mlen + blklen)) + f_main.render(buf) + ofs = mlen - slen + + # encrypt in-place + if ciph is not None: + ciph.encryptor().update_into(buf[ofs:mlen], buf[ofs:]) + + return _ssh_pem_encode(buf[:mlen]) + + +SSHPublicKeyTypes = typing.Union[ + ec.EllipticCurvePublicKey, + rsa.RSAPublicKey, + dsa.DSAPublicKey, + ed25519.Ed25519PublicKey, +] + +SSHCertPublicKeyTypes = typing.Union[ + ec.EllipticCurvePublicKey, + rsa.RSAPublicKey, + ed25519.Ed25519PublicKey, +] + + +class SSHCertificateType(enum.Enum): + USER = 1 + HOST = 2 + + +class SSHCertificate: + def __init__( + self, + _nonce: memoryview, + _public_key: SSHPublicKeyTypes, + _serial: int, + _cctype: int, + _key_id: memoryview, + _valid_principals: list[bytes], + _valid_after: int, + _valid_before: int, + _critical_options: dict[bytes, bytes], + _extensions: dict[bytes, bytes], + _sig_type: memoryview, + _sig_key: memoryview, + _inner_sig_type: memoryview, + _signature: memoryview, + _tbs_cert_body: memoryview, + _cert_key_type: bytes, + _cert_body: memoryview, + ): + self._nonce = _nonce + self._public_key = _public_key + self._serial = _serial + try: + self._type = SSHCertificateType(_cctype) + except ValueError: + raise ValueError("Invalid certificate type") + self._key_id = _key_id + self._valid_principals = _valid_principals + self._valid_after = _valid_after + self._valid_before = _valid_before + self._critical_options = _critical_options + self._extensions = _extensions + self._sig_type = _sig_type + self._sig_key = _sig_key + self._inner_sig_type = _inner_sig_type + self._signature = _signature + self._cert_key_type = _cert_key_type + self._cert_body = _cert_body + self._tbs_cert_body = _tbs_cert_body + + @property + def nonce(self) -> bytes: + return bytes(self._nonce) + + def public_key(self) -> SSHCertPublicKeyTypes: + # make mypy happy until we remove DSA support entirely and + # the underlying union won't have a disallowed type + return typing.cast(SSHCertPublicKeyTypes, self._public_key) + + @property + def serial(self) -> int: + return self._serial + + @property + def type(self) -> SSHCertificateType: + return self._type + + @property + def key_id(self) -> bytes: + return bytes(self._key_id) + + @property + def valid_principals(self) -> list[bytes]: + return self._valid_principals + + @property + def valid_before(self) -> int: + return self._valid_before + + @property + def valid_after(self) -> int: + return self._valid_after + + @property + def critical_options(self) -> dict[bytes, bytes]: + return self._critical_options + + @property + def extensions(self) -> dict[bytes, bytes]: + return self._extensions + + def signature_key(self) -> SSHCertPublicKeyTypes: + sigformat = _lookup_kformat(self._sig_type) + signature_key, sigkey_rest = sigformat.load_public(self._sig_key) + _check_empty(sigkey_rest) + return signature_key + + def public_bytes(self) -> bytes: + return ( + bytes(self._cert_key_type) + + b" " + + binascii.b2a_base64(bytes(self._cert_body), newline=False) + ) + + def verify_cert_signature(self) -> None: + signature_key = self.signature_key() + if isinstance(signature_key, ed25519.Ed25519PublicKey): + signature_key.verify( + bytes(self._signature), bytes(self._tbs_cert_body) + ) + elif isinstance(signature_key, ec.EllipticCurvePublicKey): + # The signature is encoded as a pair of big-endian integers + r, data = _get_mpint(self._signature) + s, data = _get_mpint(data) + _check_empty(data) + computed_sig = asym_utils.encode_dss_signature(r, s) + hash_alg = _get_ec_hash_alg(signature_key.curve) + signature_key.verify( + computed_sig, bytes(self._tbs_cert_body), ec.ECDSA(hash_alg) + ) + else: + assert isinstance(signature_key, rsa.RSAPublicKey) + if self._inner_sig_type == _SSH_RSA: + hash_alg = hashes.SHA1() + elif self._inner_sig_type == _SSH_RSA_SHA256: + hash_alg = hashes.SHA256() + else: + assert self._inner_sig_type == _SSH_RSA_SHA512 + hash_alg = hashes.SHA512() + signature_key.verify( + bytes(self._signature), + bytes(self._tbs_cert_body), + padding.PKCS1v15(), + hash_alg, + ) + + +def _get_ec_hash_alg(curve: ec.EllipticCurve) -> hashes.HashAlgorithm: + if isinstance(curve, ec.SECP256R1): + return hashes.SHA256() + elif isinstance(curve, ec.SECP384R1): + return hashes.SHA384() + else: + assert isinstance(curve, ec.SECP521R1) + return hashes.SHA512() + + +def _load_ssh_public_identity( + data: utils.Buffer, + _legacy_dsa_allowed=False, +) -> SSHCertificate | SSHPublicKeyTypes: + utils._check_byteslike("data", data) + + m = _SSH_PUBKEY_RC.match(data) + if not m: + raise ValueError("Invalid line format") + key_type = orig_key_type = m.group(1) + key_body = m.group(2) + with_cert = False + if key_type.endswith(_CERT_SUFFIX): + with_cert = True + key_type = key_type[: -len(_CERT_SUFFIX)] + if key_type == _SSH_DSA and not _legacy_dsa_allowed: + raise UnsupportedAlgorithm( + "DSA keys aren't supported in SSH certificates" + ) + kformat = _lookup_kformat(key_type) + + try: + rest = memoryview(binascii.a2b_base64(key_body)) + except (TypeError, binascii.Error): + raise ValueError("Invalid format") + + if with_cert: + cert_body = rest + inner_key_type, rest = _get_sshstr(rest) + if inner_key_type != orig_key_type: + raise ValueError("Invalid key format") + if with_cert: + nonce, rest = _get_sshstr(rest) + public_key, rest = kformat.load_public(rest) + if with_cert: + serial, rest = _get_u64(rest) + cctype, rest = _get_u32(rest) + key_id, rest = _get_sshstr(rest) + principals, rest = _get_sshstr(rest) + valid_principals = [] + while principals: + principal, principals = _get_sshstr(principals) + valid_principals.append(bytes(principal)) + valid_after, rest = _get_u64(rest) + valid_before, rest = _get_u64(rest) + crit_options, rest = _get_sshstr(rest) + critical_options = _parse_exts_opts(crit_options) + exts, rest = _get_sshstr(rest) + extensions = _parse_exts_opts(exts) + # Get the reserved field, which is unused. + _, rest = _get_sshstr(rest) + sig_key_raw, rest = _get_sshstr(rest) + sig_type, sig_key = _get_sshstr(sig_key_raw) + if sig_type == _SSH_DSA and not _legacy_dsa_allowed: + raise UnsupportedAlgorithm( + "DSA signatures aren't supported in SSH certificates" + ) + # Get the entire cert body and subtract the signature + tbs_cert_body = cert_body[: -len(rest)] + signature_raw, rest = _get_sshstr(rest) + _check_empty(rest) + inner_sig_type, sig_rest = _get_sshstr(signature_raw) + # RSA certs can have multiple algorithm types + if ( + sig_type == _SSH_RSA + and inner_sig_type + not in [_SSH_RSA_SHA256, _SSH_RSA_SHA512, _SSH_RSA] + ) or (sig_type != _SSH_RSA and inner_sig_type != sig_type): + raise ValueError("Signature key type does not match") + signature, sig_rest = _get_sshstr(sig_rest) + _check_empty(sig_rest) + return SSHCertificate( + nonce, + public_key, + serial, + cctype, + key_id, + valid_principals, + valid_after, + valid_before, + critical_options, + extensions, + sig_type, + sig_key, + inner_sig_type, + signature, + tbs_cert_body, + orig_key_type, + cert_body, + ) + else: + _check_empty(rest) + return public_key + + +def load_ssh_public_identity( + data: utils.Buffer, +) -> SSHCertificate | SSHPublicKeyTypes: + return _load_ssh_public_identity(data) + + +def _parse_exts_opts(exts_opts: memoryview) -> dict[bytes, bytes]: + result: dict[bytes, bytes] = {} + last_name = None + while exts_opts: + name, exts_opts = _get_sshstr(exts_opts) + bname: bytes = bytes(name) + if bname in result: + raise ValueError("Duplicate name") + if last_name is not None and bname < last_name: + raise ValueError("Fields not lexically sorted") + value, exts_opts = _get_sshstr(exts_opts) + if len(value) > 0: + value, extra = _get_sshstr(value) + if len(extra) > 0: + raise ValueError("Unexpected extra data after value") + result[bname] = bytes(value) + last_name = bname + return result + + +def ssh_key_fingerprint( + key: SSHPublicKeyTypes, + hash_algorithm: hashes.MD5 | hashes.SHA256, +) -> bytes: + if not isinstance(hash_algorithm, (hashes.MD5, hashes.SHA256)): + raise TypeError("hash_algorithm must be either MD5 or SHA256") + + key_type = _get_ssh_key_type(key) + kformat = _lookup_kformat(key_type) + + f_pub = _FragList() + f_pub.put_sshstr(key_type) + kformat.encode_public(key, f_pub) + + ssh_binary_data = f_pub.tobytes() + + # Hash the binary data + hash_obj = hashes.Hash(hash_algorithm) + hash_obj.update(ssh_binary_data) + return hash_obj.finalize() + + +def load_ssh_public_key( + data: utils.Buffer, backend: typing.Any = None +) -> SSHPublicKeyTypes: + cert_or_key = _load_ssh_public_identity(data, _legacy_dsa_allowed=True) + public_key: SSHPublicKeyTypes + if isinstance(cert_or_key, SSHCertificate): + public_key = cert_or_key.public_key() + else: + public_key = cert_or_key + + if isinstance(public_key, dsa.DSAPublicKey): + warnings.warn( + "SSH DSA keys are deprecated and will be removed in a future " + "release.", + utils.DeprecatedIn40, + stacklevel=2, + ) + return public_key + + +def serialize_ssh_public_key(public_key: SSHPublicKeyTypes) -> bytes: + """One-line public key format for OpenSSH""" + if isinstance(public_key, dsa.DSAPublicKey): + warnings.warn( + "SSH DSA key support is deprecated and will be " + "removed in a future release", + utils.DeprecatedIn40, + stacklevel=4, + ) + key_type = _get_ssh_key_type(public_key) + kformat = _lookup_kformat(key_type) + + f_pub = _FragList() + f_pub.put_sshstr(key_type) + kformat.encode_public(public_key, f_pub) + + pub = binascii.b2a_base64(f_pub.tobytes()).strip() + return b"".join([key_type, b" ", pub]) + + +SSHCertPrivateKeyTypes = typing.Union[ + ec.EllipticCurvePrivateKey, + rsa.RSAPrivateKey, + ed25519.Ed25519PrivateKey, +] + + +# This is an undocumented limit enforced in the openssh codebase for sshd and +# ssh-keygen, but it is undefined in the ssh certificates spec. +_SSHKEY_CERT_MAX_PRINCIPALS = 256 + + +class SSHCertificateBuilder: + def __init__( + self, + _public_key: SSHCertPublicKeyTypes | None = None, + _serial: int | None = None, + _type: SSHCertificateType | None = None, + _key_id: bytes | None = None, + _valid_principals: list[bytes] = [], + _valid_for_all_principals: bool = False, + _valid_before: int | None = None, + _valid_after: int | None = None, + _critical_options: list[tuple[bytes, bytes]] = [], + _extensions: list[tuple[bytes, bytes]] = [], + ): + self._public_key = _public_key + self._serial = _serial + self._type = _type + self._key_id = _key_id + self._valid_principals = _valid_principals + self._valid_for_all_principals = _valid_for_all_principals + self._valid_before = _valid_before + self._valid_after = _valid_after + self._critical_options = _critical_options + self._extensions = _extensions + + def public_key( + self, public_key: SSHCertPublicKeyTypes + ) -> SSHCertificateBuilder: + if not isinstance( + public_key, + ( + ec.EllipticCurvePublicKey, + rsa.RSAPublicKey, + ed25519.Ed25519PublicKey, + ), + ): + raise TypeError("Unsupported key type") + if self._public_key is not None: + raise ValueError("public_key already set") + + return SSHCertificateBuilder( + _public_key=public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def serial(self, serial: int) -> SSHCertificateBuilder: + if not isinstance(serial, int): + raise TypeError("serial must be an integer") + if not 0 <= serial < 2**64: + raise ValueError("serial must be between 0 and 2**64") + if self._serial is not None: + raise ValueError("serial already set") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def type(self, type: SSHCertificateType) -> SSHCertificateBuilder: + if not isinstance(type, SSHCertificateType): + raise TypeError("type must be an SSHCertificateType") + if self._type is not None: + raise ValueError("type already set") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def key_id(self, key_id: bytes) -> SSHCertificateBuilder: + if not isinstance(key_id, bytes): + raise TypeError("key_id must be bytes") + if self._key_id is not None: + raise ValueError("key_id already set") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def valid_principals( + self, valid_principals: list[bytes] + ) -> SSHCertificateBuilder: + if self._valid_for_all_principals: + raise ValueError( + "Principals can't be set because the cert is valid " + "for all principals" + ) + if ( + not all(isinstance(x, bytes) for x in valid_principals) + or not valid_principals + ): + raise TypeError( + "principals must be a list of bytes and can't be empty" + ) + if self._valid_principals: + raise ValueError("valid_principals already set") + + if len(valid_principals) > _SSHKEY_CERT_MAX_PRINCIPALS: + raise ValueError( + "Reached or exceeded the maximum number of valid_principals" + ) + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def valid_for_all_principals(self): + if self._valid_principals: + raise ValueError( + "valid_principals already set, can't set " + "valid_for_all_principals" + ) + if self._valid_for_all_principals: + raise ValueError("valid_for_all_principals already set") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=True, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def valid_before(self, valid_before: int | float) -> SSHCertificateBuilder: + if not isinstance(valid_before, (int, float)): + raise TypeError("valid_before must be an int or float") + valid_before = int(valid_before) + if valid_before < 0 or valid_before >= 2**64: + raise ValueError("valid_before must [0, 2**64)") + if self._valid_before is not None: + raise ValueError("valid_before already set") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def valid_after(self, valid_after: int | float) -> SSHCertificateBuilder: + if not isinstance(valid_after, (int, float)): + raise TypeError("valid_after must be an int or float") + valid_after = int(valid_after) + if valid_after < 0 or valid_after >= 2**64: + raise ValueError("valid_after must [0, 2**64)") + if self._valid_after is not None: + raise ValueError("valid_after already set") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=valid_after, + _critical_options=self._critical_options, + _extensions=self._extensions, + ) + + def add_critical_option( + self, name: bytes, value: bytes + ) -> SSHCertificateBuilder: + if not isinstance(name, bytes) or not isinstance(value, bytes): + raise TypeError("name and value must be bytes") + # This is O(n**2) + if name in [name for name, _ in self._critical_options]: + raise ValueError("Duplicate critical option name") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=[*self._critical_options, (name, value)], + _extensions=self._extensions, + ) + + def add_extension( + self, name: bytes, value: bytes + ) -> SSHCertificateBuilder: + if not isinstance(name, bytes) or not isinstance(value, bytes): + raise TypeError("name and value must be bytes") + # This is O(n**2) + if name in [name for name, _ in self._extensions]: + raise ValueError("Duplicate extension name") + + return SSHCertificateBuilder( + _public_key=self._public_key, + _serial=self._serial, + _type=self._type, + _key_id=self._key_id, + _valid_principals=self._valid_principals, + _valid_for_all_principals=self._valid_for_all_principals, + _valid_before=self._valid_before, + _valid_after=self._valid_after, + _critical_options=self._critical_options, + _extensions=[*self._extensions, (name, value)], + ) + + def sign(self, private_key: SSHCertPrivateKeyTypes) -> SSHCertificate: + if not isinstance( + private_key, + ( + ec.EllipticCurvePrivateKey, + rsa.RSAPrivateKey, + ed25519.Ed25519PrivateKey, + ), + ): + raise TypeError("Unsupported private key type") + + if self._public_key is None: + raise ValueError("public_key must be set") + + # Not required + serial = 0 if self._serial is None else self._serial + + if self._type is None: + raise ValueError("type must be set") + + # Not required + key_id = b"" if self._key_id is None else self._key_id + + # A zero length list is valid, but means the certificate + # is valid for any principal of the specified type. We require + # the user to explicitly set valid_for_all_principals to get + # that behavior. + if not self._valid_principals and not self._valid_for_all_principals: + raise ValueError( + "valid_principals must be set if valid_for_all_principals " + "is False" + ) + + if self._valid_before is None: + raise ValueError("valid_before must be set") + + if self._valid_after is None: + raise ValueError("valid_after must be set") + + if self._valid_after > self._valid_before: + raise ValueError("valid_after must be earlier than valid_before") + + # lexically sort our byte strings + self._critical_options.sort(key=lambda x: x[0]) + self._extensions.sort(key=lambda x: x[0]) + + key_type = _get_ssh_key_type(self._public_key) + cert_prefix = key_type + _CERT_SUFFIX + + # Marshal the bytes to be signed + nonce = os.urandom(32) + kformat = _lookup_kformat(key_type) + f = _FragList() + f.put_sshstr(cert_prefix) + f.put_sshstr(nonce) + kformat.encode_public(self._public_key, f) + f.put_u64(serial) + f.put_u32(self._type.value) + f.put_sshstr(key_id) + fprincipals = _FragList() + for p in self._valid_principals: + fprincipals.put_sshstr(p) + f.put_sshstr(fprincipals.tobytes()) + f.put_u64(self._valid_after) + f.put_u64(self._valid_before) + fcrit = _FragList() + for name, value in self._critical_options: + fcrit.put_sshstr(name) + if len(value) > 0: + foptval = _FragList() + foptval.put_sshstr(value) + fcrit.put_sshstr(foptval.tobytes()) + else: + fcrit.put_sshstr(value) + f.put_sshstr(fcrit.tobytes()) + fext = _FragList() + for name, value in self._extensions: + fext.put_sshstr(name) + if len(value) > 0: + fextval = _FragList() + fextval.put_sshstr(value) + fext.put_sshstr(fextval.tobytes()) + else: + fext.put_sshstr(value) + f.put_sshstr(fext.tobytes()) + f.put_sshstr(b"") # RESERVED FIELD + # encode CA public key + ca_type = _get_ssh_key_type(private_key) + caformat = _lookup_kformat(ca_type) + caf = _FragList() + caf.put_sshstr(ca_type) + caformat.encode_public(private_key.public_key(), caf) + f.put_sshstr(caf.tobytes()) + # Sigs according to the rules defined for the CA's public key + # (RFC4253 section 6.6 for ssh-rsa, RFC5656 for ECDSA, + # and RFC8032 for Ed25519). + if isinstance(private_key, ed25519.Ed25519PrivateKey): + signature = private_key.sign(f.tobytes()) + fsig = _FragList() + fsig.put_sshstr(ca_type) + fsig.put_sshstr(signature) + f.put_sshstr(fsig.tobytes()) + elif isinstance(private_key, ec.EllipticCurvePrivateKey): + hash_alg = _get_ec_hash_alg(private_key.curve) + signature = private_key.sign(f.tobytes(), ec.ECDSA(hash_alg)) + r, s = asym_utils.decode_dss_signature(signature) + fsig = _FragList() + fsig.put_sshstr(ca_type) + fsigblob = _FragList() + fsigblob.put_mpint(r) + fsigblob.put_mpint(s) + fsig.put_sshstr(fsigblob.tobytes()) + f.put_sshstr(fsig.tobytes()) + + else: + assert isinstance(private_key, rsa.RSAPrivateKey) + # Just like Golang, we're going to use SHA512 for RSA + # https://cs.opensource.google/go/x/crypto/+/refs/tags/ + # v0.4.0:ssh/certs.go;l=445 + # RFC 8332 defines SHA256 and 512 as options + fsig = _FragList() + fsig.put_sshstr(_SSH_RSA_SHA512) + signature = private_key.sign( + f.tobytes(), padding.PKCS1v15(), hashes.SHA512() + ) + fsig.put_sshstr(signature) + f.put_sshstr(fsig.tobytes()) + + cert_data = binascii.b2a_base64(f.tobytes()).strip() + # load_ssh_public_identity returns a union, but this is + # guaranteed to be an SSHCertificate, so we cast to make + # mypy happy. + return typing.cast( + SSHCertificate, + load_ssh_public_identity(b"".join([cert_prefix, b" ", cert_data])), + ) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/twofactor/__init__.py b/intelliaide/vendor/cryptography/hazmat/primitives/twofactor/__init__.py new file mode 100644 index 0000000..c1af423 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/twofactor/__init__.py @@ -0,0 +1,9 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + + +class InvalidToken(Exception): + pass diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/twofactor/hotp.py b/intelliaide/vendor/cryptography/hazmat/primitives/twofactor/hotp.py new file mode 100644 index 0000000..21fb000 --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/twofactor/hotp.py @@ -0,0 +1,101 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import base64 +import typing +from urllib.parse import quote, urlencode + +from cryptography.hazmat.primitives import constant_time, hmac +from cryptography.hazmat.primitives.hashes import SHA1, SHA256, SHA512 +from cryptography.hazmat.primitives.twofactor import InvalidToken +from cryptography.utils import Buffer + +HOTPHashTypes = typing.Union[SHA1, SHA256, SHA512] + + +def _generate_uri( + hotp: HOTP, + type_name: str, + account_name: str, + issuer: str | None, + extra_parameters: list[tuple[str, int]], +) -> str: + parameters = [ + ("digits", hotp._length), + ("secret", base64.b32encode(hotp._key)), + ("algorithm", hotp._algorithm.name.upper()), + ] + + if issuer is not None: + parameters.append(("issuer", issuer)) + + parameters.extend(extra_parameters) + + label = ( + f"{quote(issuer)}:{quote(account_name)}" + if issuer + else quote(account_name) + ) + return f"otpauth://{type_name}/{label}?{urlencode(parameters)}" + + +class HOTP: + def __init__( + self, + key: Buffer, + length: int, + algorithm: HOTPHashTypes, + backend: typing.Any = None, + enforce_key_length: bool = True, + ) -> None: + if len(key) < 16 and enforce_key_length is True: + raise ValueError("Key length has to be at least 128 bits.") + + if not isinstance(length, int): + raise TypeError("Length parameter must be an integer type.") + + if length < 6 or length > 8: + raise ValueError("Length of HOTP has to be between 6 and 8.") + + if not isinstance(algorithm, (SHA1, SHA256, SHA512)): + raise TypeError("Algorithm must be SHA1, SHA256 or SHA512.") + + self._key = key + self._length = length + self._algorithm = algorithm + + def generate(self, counter: int) -> bytes: + if not isinstance(counter, int): + raise TypeError("Counter parameter must be an integer type.") + + truncated_value = self._dynamic_truncate(counter) + hotp = truncated_value % (10**self._length) + return "{0:0{1}}".format(hotp, self._length).encode() + + def verify(self, hotp: bytes, counter: int) -> None: + if not constant_time.bytes_eq(self.generate(counter), hotp): + raise InvalidToken("Supplied HOTP value does not match.") + + def _dynamic_truncate(self, counter: int) -> int: + ctx = hmac.HMAC(self._key, self._algorithm) + + try: + ctx.update(counter.to_bytes(length=8, byteorder="big")) + except OverflowError: + raise ValueError(f"Counter must be between 0 and {2**64 - 1}.") + + hmac_value = ctx.finalize() + + offset = hmac_value[len(hmac_value) - 1] & 0b1111 + p = hmac_value[offset : offset + 4] + return int.from_bytes(p, byteorder="big") & 0x7FFFFFFF + + def get_provisioning_uri( + self, account_name: str, counter: int, issuer: str | None + ) -> str: + return _generate_uri( + self, "hotp", account_name, issuer, [("counter", int(counter))] + ) diff --git a/intelliaide/vendor/cryptography/hazmat/primitives/twofactor/totp.py b/intelliaide/vendor/cryptography/hazmat/primitives/twofactor/totp.py new file mode 100644 index 0000000..10c725c --- /dev/null +++ b/intelliaide/vendor/cryptography/hazmat/primitives/twofactor/totp.py @@ -0,0 +1,56 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import typing + +from cryptography.hazmat.primitives import constant_time +from cryptography.hazmat.primitives.twofactor import InvalidToken +from cryptography.hazmat.primitives.twofactor.hotp import ( + HOTP, + HOTPHashTypes, + _generate_uri, +) +from cryptography.utils import Buffer + + +class TOTP: + def __init__( + self, + key: Buffer, + length: int, + algorithm: HOTPHashTypes, + time_step: int, + backend: typing.Any = None, + enforce_key_length: bool = True, + ): + self._time_step = time_step + self._hotp = HOTP( + key, length, algorithm, enforce_key_length=enforce_key_length + ) + + def generate(self, time: int | float) -> bytes: + if not isinstance(time, (int, float)): + raise TypeError( + "Time parameter must be an integer type or float type." + ) + + counter = int(time / self._time_step) + return self._hotp.generate(counter) + + def verify(self, totp: bytes, time: int) -> None: + if not constant_time.bytes_eq(self.generate(time), totp): + raise InvalidToken("Supplied TOTP value does not match.") + + def get_provisioning_uri( + self, account_name: str, issuer: str | None + ) -> str: + return _generate_uri( + self._hotp, + "totp", + account_name, + issuer, + [("period", int(self._time_step))], + ) diff --git a/intelliaide/vendor/cryptography/py.typed b/intelliaide/vendor/cryptography/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/intelliaide/vendor/cryptography/utils.py b/intelliaide/vendor/cryptography/utils.py new file mode 100644 index 0000000..9cfc992 --- /dev/null +++ b/intelliaide/vendor/cryptography/utils.py @@ -0,0 +1,135 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import enum +import sys +import types +import typing +import warnings +from collections.abc import Callable, Sequence + + +# We use a UserWarning subclass, instead of DeprecationWarning, because CPython +# decided deprecation warnings should be invisible by default. +class CryptographyDeprecationWarning(UserWarning): + pass + + +# Several APIs were deprecated with no specific end-of-life date because of the +# ubiquity of their use. They should not be removed until we agree on when that +# cycle ends. +DeprecatedIn36 = CryptographyDeprecationWarning +DeprecatedIn40 = CryptographyDeprecationWarning +DeprecatedIn41 = CryptographyDeprecationWarning +DeprecatedIn42 = CryptographyDeprecationWarning +DeprecatedIn43 = CryptographyDeprecationWarning +DeprecatedIn47 = CryptographyDeprecationWarning + + +# If you're wondering why we don't use `Buffer`, it's because `Buffer` would +# be more accurately named: Bufferable. It means something which has an +# `__buffer__`. Which means you can't actually treat the result as a buffer +# (and do things like take a `len()`). +Buffer = typing.Union[bytes, bytearray, memoryview] + + +def _check_bytes(name: str, value: bytes) -> None: + if not isinstance(value, bytes): + raise TypeError(f"{name} must be bytes") + + +def _check_byteslike(name: str, value: Buffer) -> None: + try: + memoryview(value) + except TypeError: + raise TypeError(f"{name} must be bytes-like") + + +def int_to_bytes(integer: int, length: int | None = None) -> bytes: + if length == 0: + raise ValueError("length argument can't be 0") + return integer.to_bytes( + length or (integer.bit_length() + 7) // 8 or 1, "big" + ) + + +class InterfaceNotImplemented(Exception): + pass + + +class _DeprecatedValue: + def __init__(self, value: object, message: str, warning_class): + self.value = value + self.message = message + self.warning_class = warning_class + + +class _ModuleWithDeprecations(types.ModuleType): + def __init__(self, module: types.ModuleType): + super().__init__(module.__name__) + self.__dict__["_module"] = module + + def __getattr__(self, name: str) -> typing.Any: + obj = getattr(self._module, name) + if isinstance(obj, _DeprecatedValue): + warnings.warn(obj.message, obj.warning_class, stacklevel=2) + obj = obj.value + return obj + + def __setattr__(self, attr: str, value: object) -> None: + setattr(self._module, attr, value) + + def __delattr__(self, attr: str) -> None: + obj = getattr(self._module, attr) + if isinstance(obj, _DeprecatedValue): + warnings.warn(obj.message, obj.warning_class, stacklevel=2) + + delattr(self._module, attr) + + def __dir__(self) -> Sequence[str]: + return ["_module", *dir(self._module)] + + +def deprecated( + value: object, + module_name: str, + message: str, + warning_class: type[Warning], + name: str | None = None, +) -> _DeprecatedValue: + module = sys.modules[module_name] + if not isinstance(module, _ModuleWithDeprecations): + sys.modules[module_name] = module = _ModuleWithDeprecations(module) + dv = _DeprecatedValue(value, message, warning_class) + # Maintain backwards compatibility with `name is None` for pyOpenSSL. + if name is not None: + setattr(module, name, dv) + return dv + + +def cached_property(func: Callable) -> property: + cached_name = f"_cached_{func}" + sentinel = object() + + def inner(instance: object): + cache = getattr(instance, cached_name, sentinel) + if cache is not sentinel: + return cache + result = func(instance) + setattr(instance, cached_name, result) + return result + + return property(inner) + + +# Python 3.10 changed representation of enums. We use well-defined object +# representation and string representation from Python 3.9. +class Enum(enum.Enum): + def __repr__(self) -> str: + return f"<{self.__class__.__name__}.{self._name_}: {self._value_!r}>" + + def __str__(self) -> str: + return f"{self.__class__.__name__}.{self._name_}" diff --git a/intelliaide/vendor/cryptography/x509/__init__.py b/intelliaide/vendor/cryptography/x509/__init__.py new file mode 100644 index 0000000..cb34833 --- /dev/null +++ b/intelliaide/vendor/cryptography/x509/__init__.py @@ -0,0 +1,271 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.x509 import certificate_transparency, oid, verification +from cryptography.x509.base import ( + Attribute, + AttributeNotFound, + Attributes, + Certificate, + CertificateBuilder, + CertificateRevocationList, + CertificateRevocationListBuilder, + CertificateSigningRequest, + CertificateSigningRequestBuilder, + InvalidVersion, + RevokedCertificate, + RevokedCertificateBuilder, + Version, + load_der_x509_certificate, + load_der_x509_crl, + load_der_x509_csr, + load_pem_x509_certificate, + load_pem_x509_certificates, + load_pem_x509_crl, + load_pem_x509_csr, + random_serial_number, +) +from cryptography.x509.extensions import ( + AccessDescription, + Admission, + Admissions, + AuthorityInformationAccess, + AuthorityKeyIdentifier, + BasicConstraints, + CertificateIssuer, + CertificatePolicies, + CRLDistributionPoints, + CRLNumber, + CRLReason, + DeltaCRLIndicator, + DistributionPoint, + DuplicateExtension, + ExtendedKeyUsage, + Extension, + ExtensionNotFound, + Extensions, + ExtensionType, + FreshestCRL, + GeneralNames, + InhibitAnyPolicy, + InvalidityDate, + IssuerAlternativeName, + IssuingDistributionPoint, + KeyUsage, + MSCertificateTemplate, + NameConstraints, + NamingAuthority, + NoticeReference, + OCSPAcceptableResponses, + OCSPNoCheck, + OCSPNonce, + PolicyConstraints, + PolicyInformation, + PrecertificateSignedCertificateTimestamps, + PrecertPoison, + PrivateKeyUsagePeriod, + ProfessionInfo, + ReasonFlags, + SignedCertificateTimestamps, + SubjectAlternativeName, + SubjectInformationAccess, + SubjectKeyIdentifier, + TLSFeature, + TLSFeatureType, + UnrecognizedExtension, + UserNotice, +) +from cryptography.x509.general_name import ( + DirectoryName, + DNSName, + GeneralName, + IPAddress, + OtherName, + RegisteredID, + RFC822Name, + UniformResourceIdentifier, + UnsupportedGeneralNameType, +) +from cryptography.x509.name import ( + Name, + NameAttribute, + RelativeDistinguishedName, +) +from cryptography.x509.oid import ( + AuthorityInformationAccessOID, + CertificatePoliciesOID, + CRLEntryExtensionOID, + ExtendedKeyUsageOID, + ExtensionOID, + NameOID, + ObjectIdentifier, + PublicKeyAlgorithmOID, + SignatureAlgorithmOID, +) + +OID_AUTHORITY_INFORMATION_ACCESS = ExtensionOID.AUTHORITY_INFORMATION_ACCESS +OID_AUTHORITY_KEY_IDENTIFIER = ExtensionOID.AUTHORITY_KEY_IDENTIFIER +OID_BASIC_CONSTRAINTS = ExtensionOID.BASIC_CONSTRAINTS +OID_CERTIFICATE_POLICIES = ExtensionOID.CERTIFICATE_POLICIES +OID_CRL_DISTRIBUTION_POINTS = ExtensionOID.CRL_DISTRIBUTION_POINTS +OID_EXTENDED_KEY_USAGE = ExtensionOID.EXTENDED_KEY_USAGE +OID_FRESHEST_CRL = ExtensionOID.FRESHEST_CRL +OID_INHIBIT_ANY_POLICY = ExtensionOID.INHIBIT_ANY_POLICY +OID_ISSUER_ALTERNATIVE_NAME = ExtensionOID.ISSUER_ALTERNATIVE_NAME +OID_KEY_USAGE = ExtensionOID.KEY_USAGE +OID_PRIVATE_KEY_USAGE_PERIOD = ExtensionOID.PRIVATE_KEY_USAGE_PERIOD +OID_NAME_CONSTRAINTS = ExtensionOID.NAME_CONSTRAINTS +OID_OCSP_NO_CHECK = ExtensionOID.OCSP_NO_CHECK +OID_POLICY_CONSTRAINTS = ExtensionOID.POLICY_CONSTRAINTS +OID_POLICY_MAPPINGS = ExtensionOID.POLICY_MAPPINGS +OID_SUBJECT_ALTERNATIVE_NAME = ExtensionOID.SUBJECT_ALTERNATIVE_NAME +OID_SUBJECT_DIRECTORY_ATTRIBUTES = ExtensionOID.SUBJECT_DIRECTORY_ATTRIBUTES +OID_SUBJECT_INFORMATION_ACCESS = ExtensionOID.SUBJECT_INFORMATION_ACCESS +OID_SUBJECT_KEY_IDENTIFIER = ExtensionOID.SUBJECT_KEY_IDENTIFIER + +OID_DSA_WITH_SHA1 = SignatureAlgorithmOID.DSA_WITH_SHA1 +OID_DSA_WITH_SHA224 = SignatureAlgorithmOID.DSA_WITH_SHA224 +OID_DSA_WITH_SHA256 = SignatureAlgorithmOID.DSA_WITH_SHA256 +OID_ECDSA_WITH_SHA1 = SignatureAlgorithmOID.ECDSA_WITH_SHA1 +OID_ECDSA_WITH_SHA224 = SignatureAlgorithmOID.ECDSA_WITH_SHA224 +OID_ECDSA_WITH_SHA256 = SignatureAlgorithmOID.ECDSA_WITH_SHA256 +OID_ECDSA_WITH_SHA384 = SignatureAlgorithmOID.ECDSA_WITH_SHA384 +OID_ECDSA_WITH_SHA512 = SignatureAlgorithmOID.ECDSA_WITH_SHA512 +OID_RSA_WITH_MD5 = SignatureAlgorithmOID.RSA_WITH_MD5 +OID_RSA_WITH_SHA1 = SignatureAlgorithmOID.RSA_WITH_SHA1 +OID_RSA_WITH_SHA224 = SignatureAlgorithmOID.RSA_WITH_SHA224 +OID_RSA_WITH_SHA256 = SignatureAlgorithmOID.RSA_WITH_SHA256 +OID_RSA_WITH_SHA384 = SignatureAlgorithmOID.RSA_WITH_SHA384 +OID_RSA_WITH_SHA512 = SignatureAlgorithmOID.RSA_WITH_SHA512 +OID_RSASSA_PSS = SignatureAlgorithmOID.RSASSA_PSS + +OID_COMMON_NAME = NameOID.COMMON_NAME +OID_COUNTRY_NAME = NameOID.COUNTRY_NAME +OID_DOMAIN_COMPONENT = NameOID.DOMAIN_COMPONENT +OID_DN_QUALIFIER = NameOID.DN_QUALIFIER +OID_EMAIL_ADDRESS = NameOID.EMAIL_ADDRESS +OID_GENERATION_QUALIFIER = NameOID.GENERATION_QUALIFIER +OID_GIVEN_NAME = NameOID.GIVEN_NAME +OID_LOCALITY_NAME = NameOID.LOCALITY_NAME +OID_ORGANIZATIONAL_UNIT_NAME = NameOID.ORGANIZATIONAL_UNIT_NAME +OID_ORGANIZATION_NAME = NameOID.ORGANIZATION_NAME +OID_PSEUDONYM = NameOID.PSEUDONYM +OID_SERIAL_NUMBER = NameOID.SERIAL_NUMBER +OID_STATE_OR_PROVINCE_NAME = NameOID.STATE_OR_PROVINCE_NAME +OID_SURNAME = NameOID.SURNAME +OID_TITLE = NameOID.TITLE + +OID_CLIENT_AUTH = ExtendedKeyUsageOID.CLIENT_AUTH +OID_CODE_SIGNING = ExtendedKeyUsageOID.CODE_SIGNING +OID_EMAIL_PROTECTION = ExtendedKeyUsageOID.EMAIL_PROTECTION +OID_OCSP_SIGNING = ExtendedKeyUsageOID.OCSP_SIGNING +OID_SERVER_AUTH = ExtendedKeyUsageOID.SERVER_AUTH +OID_TIME_STAMPING = ExtendedKeyUsageOID.TIME_STAMPING + +OID_ANY_POLICY = CertificatePoliciesOID.ANY_POLICY +OID_CPS_QUALIFIER = CertificatePoliciesOID.CPS_QUALIFIER +OID_CPS_USER_NOTICE = CertificatePoliciesOID.CPS_USER_NOTICE + +OID_CERTIFICATE_ISSUER = CRLEntryExtensionOID.CERTIFICATE_ISSUER +OID_CRL_REASON = CRLEntryExtensionOID.CRL_REASON +OID_INVALIDITY_DATE = CRLEntryExtensionOID.INVALIDITY_DATE + +OID_CA_ISSUERS = AuthorityInformationAccessOID.CA_ISSUERS +OID_OCSP = AuthorityInformationAccessOID.OCSP + +__all__ = [ + "OID_CA_ISSUERS", + "OID_OCSP", + "AccessDescription", + "Admission", + "Admissions", + "Attribute", + "AttributeNotFound", + "Attributes", + "AuthorityInformationAccess", + "AuthorityKeyIdentifier", + "BasicConstraints", + "CRLDistributionPoints", + "CRLNumber", + "CRLReason", + "Certificate", + "CertificateBuilder", + "CertificateIssuer", + "CertificatePolicies", + "CertificateRevocationList", + "CertificateRevocationListBuilder", + "CertificateSigningRequest", + "CertificateSigningRequestBuilder", + "DNSName", + "DeltaCRLIndicator", + "DirectoryName", + "DistributionPoint", + "DuplicateExtension", + "ExtendedKeyUsage", + "Extension", + "ExtensionNotFound", + "ExtensionType", + "Extensions", + "FreshestCRL", + "GeneralName", + "GeneralNames", + "IPAddress", + "InhibitAnyPolicy", + "InvalidVersion", + "InvalidityDate", + "IssuerAlternativeName", + "IssuingDistributionPoint", + "KeyUsage", + "MSCertificateTemplate", + "Name", + "NameAttribute", + "NameConstraints", + "NameOID", + "NamingAuthority", + "NoticeReference", + "OCSPAcceptableResponses", + "OCSPNoCheck", + "OCSPNonce", + "ObjectIdentifier", + "OtherName", + "PolicyConstraints", + "PolicyInformation", + "PrecertPoison", + "PrecertificateSignedCertificateTimestamps", + "PrivateKeyUsagePeriod", + "ProfessionInfo", + "PublicKeyAlgorithmOID", + "RFC822Name", + "ReasonFlags", + "RegisteredID", + "RelativeDistinguishedName", + "RevokedCertificate", + "RevokedCertificateBuilder", + "SignatureAlgorithmOID", + "SignedCertificateTimestamps", + "SubjectAlternativeName", + "SubjectInformationAccess", + "SubjectKeyIdentifier", + "TLSFeature", + "TLSFeatureType", + "UniformResourceIdentifier", + "UnrecognizedExtension", + "UnsupportedGeneralNameType", + "UserNotice", + "Version", + "certificate_transparency", + "load_der_x509_certificate", + "load_der_x509_crl", + "load_der_x509_csr", + "load_pem_x509_certificate", + "load_pem_x509_certificates", + "load_pem_x509_crl", + "load_pem_x509_csr", + "oid", + "random_serial_number", + "verification", + "verification", +] diff --git a/intelliaide/vendor/cryptography/x509/base.py b/intelliaide/vendor/cryptography/x509/base.py new file mode 100644 index 0000000..a11b8fe --- /dev/null +++ b/intelliaide/vendor/cryptography/x509/base.py @@ -0,0 +1,773 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import datetime +import os +import typing +from collections.abc import Iterable + +from cryptography import utils +from cryptography.hazmat.bindings._rust import x509 as rust_x509 +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ( + dsa, + ec, + ed448, + ed25519, + padding, + rsa, + x448, + x25519, +) +from cryptography.hazmat.primitives.asymmetric.types import ( + CertificateIssuerPrivateKeyTypes, + CertificatePublicKeyTypes, +) +from cryptography.x509.extensions import ( + Extension, + ExtensionType, + _make_sequence_methods, +) +from cryptography.x509.name import Name, _ASN1Type +from cryptography.x509.oid import ObjectIdentifier + +_EARLIEST_UTC_TIME = datetime.datetime(1950, 1, 1) + +# This must be kept in sync with sign.rs's list of allowable types in +# identify_hash_type +_AllowedHashTypes = typing.Union[ + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, + hashes.SHA3_224, + hashes.SHA3_256, + hashes.SHA3_384, + hashes.SHA3_512, +] + + +class AttributeNotFound(Exception): + def __init__(self, msg: str, oid: ObjectIdentifier) -> None: + super().__init__(msg) + self.oid = oid + + +def _reject_duplicate_extension( + extension: Extension[ExtensionType], + extensions: list[Extension[ExtensionType]], +) -> None: + # This is quadratic in the number of extensions + for e in extensions: + if e.oid == extension.oid: + raise ValueError("This extension has already been set.") + + +def _reject_duplicate_attribute( + oid: ObjectIdentifier, + attributes: list[tuple[ObjectIdentifier, bytes, int | None]], +) -> None: + # This is quadratic in the number of attributes + for attr_oid, _, _ in attributes: + if attr_oid == oid: + raise ValueError("This attribute has already been set.") + + +def _convert_to_naive_utc_time(time: datetime.datetime) -> datetime.datetime: + """Normalizes a datetime to a naive datetime in UTC. + + time -- datetime to normalize. Assumed to be in UTC if not timezone + aware. + """ + if time.tzinfo is not None: + offset = time.utcoffset() + offset = offset if offset else datetime.timedelta() + return time.replace(tzinfo=None) - offset + else: + return time + + +class Attribute: + def __init__( + self, + oid: ObjectIdentifier, + value: bytes, + _type: int = _ASN1Type.UTF8String.value, + ) -> None: + self._oid = oid + self._value = value + self._type = _type + + @property + def oid(self) -> ObjectIdentifier: + return self._oid + + @property + def value(self) -> bytes: + return self._value + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Attribute): + return NotImplemented + + return ( + self.oid == other.oid + and self.value == other.value + and self._type == other._type + ) + + def __hash__(self) -> int: + return hash((self.oid, self.value, self._type)) + + +class Attributes: + def __init__( + self, + attributes: Iterable[Attribute], + ) -> None: + self._attributes = list(attributes) + + __len__, __iter__, __getitem__ = _make_sequence_methods("_attributes") + + def __repr__(self) -> str: + return f"" + + def get_attribute_for_oid(self, oid: ObjectIdentifier) -> Attribute: + for attr in self: + if attr.oid == oid: + return attr + + raise AttributeNotFound(f"No {oid} attribute was found", oid) + + +class Version(utils.Enum): + v1 = 0 + v3 = 2 + + +class InvalidVersion(Exception): + def __init__(self, msg: str, parsed_version: int) -> None: + super().__init__(msg) + self.parsed_version = parsed_version + + +Certificate = rust_x509.Certificate +RevokedCertificate = rust_x509.RevokedCertificate + + +CertificateRevocationList = rust_x509.CertificateRevocationList +CertificateSigningRequest = rust_x509.CertificateSigningRequest + + +load_pem_x509_certificate = rust_x509.load_pem_x509_certificate +load_der_x509_certificate = rust_x509.load_der_x509_certificate + +load_pem_x509_certificates = rust_x509.load_pem_x509_certificates + +load_pem_x509_csr = rust_x509.load_pem_x509_csr +load_der_x509_csr = rust_x509.load_der_x509_csr + +load_pem_x509_crl = rust_x509.load_pem_x509_crl +load_der_x509_crl = rust_x509.load_der_x509_crl + + +class CertificateSigningRequestBuilder: + def __init__( + self, + subject_name: Name | None = None, + extensions: list[Extension[ExtensionType]] = [], + attributes: list[tuple[ObjectIdentifier, bytes, int | None]] = [], + ): + """ + Creates an empty X.509 certificate request (v1). + """ + self._subject_name = subject_name + self._extensions = extensions + self._attributes = attributes + + def subject_name(self, name: Name) -> CertificateSigningRequestBuilder: + """ + Sets the certificate requestor's distinguished name. + """ + if not isinstance(name, Name): + raise TypeError("Expecting x509.Name object.") + if self._subject_name is not None: + raise ValueError("The subject name may only be set once.") + return CertificateSigningRequestBuilder( + name, self._extensions, self._attributes + ) + + def add_extension( + self, extval: ExtensionType, critical: bool + ) -> CertificateSigningRequestBuilder: + """ + Adds an X.509 extension to the certificate request. + """ + if not isinstance(extval, ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = Extension(extval.oid, critical, extval) + _reject_duplicate_extension(extension, self._extensions) + + return CertificateSigningRequestBuilder( + self._subject_name, + [*self._extensions, extension], + self._attributes, + ) + + def add_attribute( + self, + oid: ObjectIdentifier, + value: bytes, + *, + _tag: _ASN1Type | None = None, + ) -> CertificateSigningRequestBuilder: + """ + Adds an X.509 attribute with an OID and associated value. + """ + if not isinstance(oid, ObjectIdentifier): + raise TypeError("oid must be an ObjectIdentifier") + + if not isinstance(value, bytes): + raise TypeError("value must be bytes") + + if _tag is not None and not isinstance(_tag, _ASN1Type): + raise TypeError("tag must be _ASN1Type") + + _reject_duplicate_attribute(oid, self._attributes) + + if _tag is not None: + tag = _tag.value + else: + tag = None + + return CertificateSigningRequestBuilder( + self._subject_name, + self._extensions, + [*self._attributes, (oid, value, tag)], + ) + + def sign( + self, + private_key: CertificateIssuerPrivateKeyTypes, + algorithm: _AllowedHashTypes | None, + backend: typing.Any = None, + *, + rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, + ecdsa_deterministic: bool | None = None, + ) -> CertificateSigningRequest: + """ + Signs the request using the requestor's private key. + """ + if self._subject_name is None: + raise ValueError("A CertificateSigningRequest must have a subject") + + if rsa_padding is not None: + if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)): + raise TypeError("Padding must be PSS or PKCS1v15") + if not isinstance(private_key, rsa.RSAPrivateKey): + raise TypeError("Padding is only supported for RSA keys") + + if ecdsa_deterministic is not None: + if not isinstance(private_key, ec.EllipticCurvePrivateKey): + raise TypeError( + "Deterministic ECDSA is only supported for EC keys" + ) + + return rust_x509.create_x509_csr( + self, + private_key, + algorithm, + rsa_padding, + ecdsa_deterministic, + ) + + +class CertificateBuilder: + _extensions: list[Extension[ExtensionType]] + + def __init__( + self, + issuer_name: Name | None = None, + subject_name: Name | None = None, + public_key: CertificatePublicKeyTypes | None = None, + serial_number: int | None = None, + not_valid_before: datetime.datetime | None = None, + not_valid_after: datetime.datetime | None = None, + extensions: list[Extension[ExtensionType]] = [], + ) -> None: + self._version = Version.v3 + self._issuer_name = issuer_name + self._subject_name = subject_name + self._public_key = public_key + self._serial_number = serial_number + self._not_valid_before = not_valid_before + self._not_valid_after = not_valid_after + self._extensions = extensions + + def issuer_name(self, name: Name) -> CertificateBuilder: + """ + Sets the CA's distinguished name. + """ + if not isinstance(name, Name): + raise TypeError("Expecting x509.Name object.") + if self._issuer_name is not None: + raise ValueError("The issuer name may only be set once.") + return CertificateBuilder( + name, + self._subject_name, + self._public_key, + self._serial_number, + self._not_valid_before, + self._not_valid_after, + self._extensions, + ) + + def subject_name(self, name: Name) -> CertificateBuilder: + """ + Sets the requestor's distinguished name. + """ + if not isinstance(name, Name): + raise TypeError("Expecting x509.Name object.") + if self._subject_name is not None: + raise ValueError("The subject name may only be set once.") + return CertificateBuilder( + self._issuer_name, + name, + self._public_key, + self._serial_number, + self._not_valid_before, + self._not_valid_after, + self._extensions, + ) + + def public_key( + self, + key: CertificatePublicKeyTypes, + ) -> CertificateBuilder: + """ + Sets the requestor's public key (as found in the signing request). + """ + if not isinstance( + key, + ( + dsa.DSAPublicKey, + rsa.RSAPublicKey, + ec.EllipticCurvePublicKey, + ed25519.Ed25519PublicKey, + ed448.Ed448PublicKey, + x25519.X25519PublicKey, + x448.X448PublicKey, + ), + ): + raise TypeError( + "Expecting one of DSAPublicKey, RSAPublicKey," + " EllipticCurvePublicKey, Ed25519PublicKey," + " Ed448PublicKey, X25519PublicKey, or " + "X448PublicKey." + ) + if self._public_key is not None: + raise ValueError("The public key may only be set once.") + return CertificateBuilder( + self._issuer_name, + self._subject_name, + key, + self._serial_number, + self._not_valid_before, + self._not_valid_after, + self._extensions, + ) + + def serial_number(self, number: int) -> CertificateBuilder: + """ + Sets the certificate serial number. + """ + if not isinstance(number, int): + raise TypeError("Serial number must be of integral type.") + if self._serial_number is not None: + raise ValueError("The serial number may only be set once.") + if number <= 0: + raise ValueError("The serial number should be positive.") + + # ASN.1 integers are always signed, so most significant bit must be + # zero. + if number.bit_length() >= 160: # As defined in RFC 5280 + raise ValueError( + "The serial number should not be more than 159 bits." + ) + return CertificateBuilder( + self._issuer_name, + self._subject_name, + self._public_key, + number, + self._not_valid_before, + self._not_valid_after, + self._extensions, + ) + + def not_valid_before(self, time: datetime.datetime) -> CertificateBuilder: + """ + Sets the certificate activation time. + """ + if not isinstance(time, datetime.datetime): + raise TypeError("Expecting datetime object.") + if self._not_valid_before is not None: + raise ValueError("The not valid before may only be set once.") + time = _convert_to_naive_utc_time(time) + if time < _EARLIEST_UTC_TIME: + raise ValueError( + "The not valid before date must be on or after" + " 1950 January 1)." + ) + if self._not_valid_after is not None and time > self._not_valid_after: + raise ValueError( + "The not valid before date must be before the not valid after " + "date." + ) + return CertificateBuilder( + self._issuer_name, + self._subject_name, + self._public_key, + self._serial_number, + time, + self._not_valid_after, + self._extensions, + ) + + def not_valid_after(self, time: datetime.datetime) -> CertificateBuilder: + """ + Sets the certificate expiration time. + """ + if not isinstance(time, datetime.datetime): + raise TypeError("Expecting datetime object.") + if self._not_valid_after is not None: + raise ValueError("The not valid after may only be set once.") + time = _convert_to_naive_utc_time(time) + if time < _EARLIEST_UTC_TIME: + raise ValueError( + "The not valid after date must be on or after 1950 January 1." + ) + if ( + self._not_valid_before is not None + and time < self._not_valid_before + ): + raise ValueError( + "The not valid after date must be after the not valid before " + "date." + ) + return CertificateBuilder( + self._issuer_name, + self._subject_name, + self._public_key, + self._serial_number, + self._not_valid_before, + time, + self._extensions, + ) + + def add_extension( + self, extval: ExtensionType, critical: bool + ) -> CertificateBuilder: + """ + Adds an X.509 extension to the certificate. + """ + if not isinstance(extval, ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = Extension(extval.oid, critical, extval) + _reject_duplicate_extension(extension, self._extensions) + + return CertificateBuilder( + self._issuer_name, + self._subject_name, + self._public_key, + self._serial_number, + self._not_valid_before, + self._not_valid_after, + [*self._extensions, extension], + ) + + def sign( + self, + private_key: CertificateIssuerPrivateKeyTypes, + algorithm: _AllowedHashTypes | None, + backend: typing.Any = None, + *, + rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, + ecdsa_deterministic: bool | None = None, + ) -> Certificate: + """ + Signs the certificate using the CA's private key. + """ + if self._subject_name is None: + raise ValueError("A certificate must have a subject name") + + if self._issuer_name is None: + raise ValueError("A certificate must have an issuer name") + + if self._serial_number is None: + raise ValueError("A certificate must have a serial number") + + if self._not_valid_before is None: + raise ValueError("A certificate must have a not valid before time") + + if self._not_valid_after is None: + raise ValueError("A certificate must have a not valid after time") + + if self._public_key is None: + raise ValueError("A certificate must have a public key") + + if rsa_padding is not None: + if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)): + raise TypeError("Padding must be PSS or PKCS1v15") + if not isinstance(private_key, rsa.RSAPrivateKey): + raise TypeError("Padding is only supported for RSA keys") + + if ecdsa_deterministic is not None: + if not isinstance(private_key, ec.EllipticCurvePrivateKey): + raise TypeError( + "Deterministic ECDSA is only supported for EC keys" + ) + + return rust_x509.create_x509_certificate( + self, + private_key, + algorithm, + rsa_padding, + ecdsa_deterministic, + ) + + +class CertificateRevocationListBuilder: + _extensions: list[Extension[ExtensionType]] + _revoked_certificates: list[RevokedCertificate] + + def __init__( + self, + issuer_name: Name | None = None, + last_update: datetime.datetime | None = None, + next_update: datetime.datetime | None = None, + extensions: list[Extension[ExtensionType]] = [], + revoked_certificates: list[RevokedCertificate] = [], + ): + self._issuer_name = issuer_name + self._last_update = last_update + self._next_update = next_update + self._extensions = extensions + self._revoked_certificates = revoked_certificates + + def issuer_name( + self, issuer_name: Name + ) -> CertificateRevocationListBuilder: + if not isinstance(issuer_name, Name): + raise TypeError("Expecting x509.Name object.") + if self._issuer_name is not None: + raise ValueError("The issuer name may only be set once.") + return CertificateRevocationListBuilder( + issuer_name, + self._last_update, + self._next_update, + self._extensions, + self._revoked_certificates, + ) + + def last_update( + self, last_update: datetime.datetime + ) -> CertificateRevocationListBuilder: + if not isinstance(last_update, datetime.datetime): + raise TypeError("Expecting datetime object.") + if self._last_update is not None: + raise ValueError("Last update may only be set once.") + last_update = _convert_to_naive_utc_time(last_update) + if last_update < _EARLIEST_UTC_TIME: + raise ValueError( + "The last update date must be on or after 1950 January 1." + ) + if self._next_update is not None and last_update > self._next_update: + raise ValueError( + "The last update date must be before the next update date." + ) + return CertificateRevocationListBuilder( + self._issuer_name, + last_update, + self._next_update, + self._extensions, + self._revoked_certificates, + ) + + def next_update( + self, next_update: datetime.datetime + ) -> CertificateRevocationListBuilder: + if not isinstance(next_update, datetime.datetime): + raise TypeError("Expecting datetime object.") + if self._next_update is not None: + raise ValueError("Last update may only be set once.") + next_update = _convert_to_naive_utc_time(next_update) + if next_update < _EARLIEST_UTC_TIME: + raise ValueError( + "The last update date must be on or after 1950 January 1." + ) + if self._last_update is not None and next_update < self._last_update: + raise ValueError( + "The next update date must be after the last update date." + ) + return CertificateRevocationListBuilder( + self._issuer_name, + self._last_update, + next_update, + self._extensions, + self._revoked_certificates, + ) + + def add_extension( + self, extval: ExtensionType, critical: bool + ) -> CertificateRevocationListBuilder: + """ + Adds an X.509 extension to the certificate revocation list. + """ + if not isinstance(extval, ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = Extension(extval.oid, critical, extval) + _reject_duplicate_extension(extension, self._extensions) + return CertificateRevocationListBuilder( + self._issuer_name, + self._last_update, + self._next_update, + [*self._extensions, extension], + self._revoked_certificates, + ) + + def add_revoked_certificate( + self, revoked_certificate: RevokedCertificate + ) -> CertificateRevocationListBuilder: + """ + Adds a revoked certificate to the CRL. + """ + if not isinstance(revoked_certificate, RevokedCertificate): + raise TypeError("Must be an instance of RevokedCertificate") + + return CertificateRevocationListBuilder( + self._issuer_name, + self._last_update, + self._next_update, + self._extensions, + [*self._revoked_certificates, revoked_certificate], + ) + + def sign( + self, + private_key: CertificateIssuerPrivateKeyTypes, + algorithm: _AllowedHashTypes | None, + backend: typing.Any = None, + *, + rsa_padding: padding.PSS | padding.PKCS1v15 | None = None, + ecdsa_deterministic: bool | None = None, + ) -> CertificateRevocationList: + if self._issuer_name is None: + raise ValueError("A CRL must have an issuer name") + + if self._last_update is None: + raise ValueError("A CRL must have a last update time") + + if self._next_update is None: + raise ValueError("A CRL must have a next update time") + + if rsa_padding is not None: + if not isinstance(rsa_padding, (padding.PSS, padding.PKCS1v15)): + raise TypeError("Padding must be PSS or PKCS1v15") + if not isinstance(private_key, rsa.RSAPrivateKey): + raise TypeError("Padding is only supported for RSA keys") + + if ecdsa_deterministic is not None: + if not isinstance(private_key, ec.EllipticCurvePrivateKey): + raise TypeError( + "Deterministic ECDSA is only supported for EC keys" + ) + + return rust_x509.create_x509_crl( + self, + private_key, + algorithm, + rsa_padding, + ecdsa_deterministic, + ) + + +class RevokedCertificateBuilder: + def __init__( + self, + serial_number: int | None = None, + revocation_date: datetime.datetime | None = None, + extensions: list[Extension[ExtensionType]] = [], + ): + self._serial_number = serial_number + self._revocation_date = revocation_date + self._extensions = extensions + + def serial_number(self, number: int) -> RevokedCertificateBuilder: + if not isinstance(number, int): + raise TypeError("Serial number must be of integral type.") + if self._serial_number is not None: + raise ValueError("The serial number may only be set once.") + if number <= 0: + raise ValueError("The serial number should be positive") + + # ASN.1 integers are always signed, so most significant bit must be + # zero. + if number.bit_length() >= 160: # As defined in RFC 5280 + raise ValueError( + "The serial number should not be more than 159 bits." + ) + return RevokedCertificateBuilder( + number, self._revocation_date, self._extensions + ) + + def revocation_date( + self, time: datetime.datetime + ) -> RevokedCertificateBuilder: + if not isinstance(time, datetime.datetime): + raise TypeError("Expecting datetime object.") + if self._revocation_date is not None: + raise ValueError("The revocation date may only be set once.") + time = _convert_to_naive_utc_time(time) + if time < _EARLIEST_UTC_TIME: + raise ValueError( + "The revocation date must be on or after 1950 January 1." + ) + return RevokedCertificateBuilder( + self._serial_number, time, self._extensions + ) + + def add_extension( + self, extval: ExtensionType, critical: bool + ) -> RevokedCertificateBuilder: + if not isinstance(extval, ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = Extension(extval.oid, critical, extval) + _reject_duplicate_extension(extension, self._extensions) + return RevokedCertificateBuilder( + self._serial_number, + self._revocation_date, + [*self._extensions, extension], + ) + + def build(self, backend: typing.Any = None) -> RevokedCertificate: + if self._serial_number is None: + raise ValueError("A revoked certificate must have a serial number") + if self._revocation_date is None: + raise ValueError( + "A revoked certificate must have a revocation date" + ) + return rust_x509.create_revoked_certificate(self) + + +def random_serial_number() -> int: + return int.from_bytes(os.urandom(20), "big") >> 1 diff --git a/intelliaide/vendor/cryptography/x509/certificate_transparency.py b/intelliaide/vendor/cryptography/x509/certificate_transparency.py new file mode 100644 index 0000000..fb66cc6 --- /dev/null +++ b/intelliaide/vendor/cryptography/x509/certificate_transparency.py @@ -0,0 +1,35 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography import utils +from cryptography.hazmat.bindings._rust import x509 as rust_x509 + + +class LogEntryType(utils.Enum): + X509_CERTIFICATE = 0 + PRE_CERTIFICATE = 1 + + +class Version(utils.Enum): + v1 = 0 + + +class SignatureAlgorithm(utils.Enum): + """ + Signature algorithms that are valid for SCTs. + + These are exactly the same as SignatureAlgorithm in RFC 5246 (TLS 1.2). + + See: + """ + + ANONYMOUS = 0 + RSA = 1 + DSA = 2 + ECDSA = 3 + + +SignedCertificateTimestamp = rust_x509.Sct diff --git a/intelliaide/vendor/cryptography/x509/extensions.py b/intelliaide/vendor/cryptography/x509/extensions.py new file mode 100644 index 0000000..7b78e9e --- /dev/null +++ b/intelliaide/vendor/cryptography/x509/extensions.py @@ -0,0 +1,2533 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc +import datetime +import hashlib +import ipaddress +import typing +from collections.abc import Iterable, Iterator + +from cryptography import utils +from cryptography.hazmat.bindings._rust import asn1 +from cryptography.hazmat.bindings._rust import x509 as rust_x509 +from cryptography.hazmat.primitives import constant_time, serialization +from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey +from cryptography.hazmat.primitives.asymmetric.types import ( + CertificateIssuerPublicKeyTypes, + CertificatePublicKeyTypes, +) +from cryptography.x509.certificate_transparency import ( + SignedCertificateTimestamp, +) +from cryptography.x509.general_name import ( + DirectoryName, + DNSName, + GeneralName, + IPAddress, + OtherName, + RegisteredID, + RFC822Name, + UniformResourceIdentifier, + _IPAddressTypes, +) +from cryptography.x509.name import Name, RelativeDistinguishedName +from cryptography.x509.oid import ( + CRLEntryExtensionOID, + ExtensionOID, + ObjectIdentifier, + OCSPExtensionOID, +) + +ExtensionTypeVar = typing.TypeVar( + "ExtensionTypeVar", bound="ExtensionType", covariant=True +) + + +def _key_identifier_from_public_key( + public_key: CertificatePublicKeyTypes, +) -> bytes: + if isinstance(public_key, RSAPublicKey): + data = public_key.public_bytes( + serialization.Encoding.DER, + serialization.PublicFormat.PKCS1, + ) + elif isinstance(public_key, EllipticCurvePublicKey): + data = public_key.public_bytes( + serialization.Encoding.X962, + serialization.PublicFormat.UncompressedPoint, + ) + else: + # This is a very slow way to do this. + serialized = public_key.public_bytes( + serialization.Encoding.DER, + serialization.PublicFormat.SubjectPublicKeyInfo, + ) + data = asn1.parse_spki_for_data(serialized) + + return hashlib.sha1(data).digest() + + +def _make_sequence_methods(field_name: str): + def len_method(self) -> int: + return len(getattr(self, field_name)) + + def iter_method(self): + return iter(getattr(self, field_name)) + + def getitem_method(self, idx): + return getattr(self, field_name)[idx] + + return len_method, iter_method, getitem_method + + +class DuplicateExtension(Exception): + def __init__(self, msg: str, oid: ObjectIdentifier) -> None: + super().__init__(msg) + self.oid = oid + + +class ExtensionNotFound(Exception): + def __init__(self, msg: str, oid: ObjectIdentifier) -> None: + super().__init__(msg) + self.oid = oid + + +class ExtensionType(metaclass=abc.ABCMeta): + oid: typing.ClassVar[ObjectIdentifier] + + def public_bytes(self) -> bytes: + """ + Serializes the extension type to DER. + """ + raise NotImplementedError( + f"public_bytes is not implemented for extension type {self!r}" + ) + + +class Extensions: + def __init__(self, extensions: Iterable[Extension[ExtensionType]]) -> None: + self._extensions = list(extensions) + + def get_extension_for_oid( + self, oid: ObjectIdentifier + ) -> Extension[ExtensionType]: + for ext in self: + if ext.oid == oid: + return ext + + raise ExtensionNotFound(f"No {oid} extension was found", oid) + + def get_extension_for_class( + self, extclass: type[ExtensionTypeVar] + ) -> Extension[ExtensionTypeVar]: + if extclass is UnrecognizedExtension: + raise TypeError( + "UnrecognizedExtension can't be used with " + "get_extension_for_class because more than one instance of the" + " class may be present." + ) + + for ext in self: + if isinstance(ext.value, extclass): + return ext + + raise ExtensionNotFound( + f"No {extclass} extension was found", extclass.oid + ) + + __len__, __iter__, __getitem__ = _make_sequence_methods("_extensions") + + def __repr__(self) -> str: + return f"" + + +class CRLNumber(ExtensionType): + oid = ExtensionOID.CRL_NUMBER + + def __init__(self, crl_number: int) -> None: + if not isinstance(crl_number, int): + raise TypeError("crl_number must be an integer") + + self._crl_number = crl_number + + def __eq__(self, other: object) -> bool: + if not isinstance(other, CRLNumber): + return NotImplemented + + return self.crl_number == other.crl_number + + def __hash__(self) -> int: + return hash(self.crl_number) + + def __repr__(self) -> str: + return f"" + + @property + def crl_number(self) -> int: + return self._crl_number + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class AuthorityKeyIdentifier(ExtensionType): + oid = ExtensionOID.AUTHORITY_KEY_IDENTIFIER + + def __init__( + self, + key_identifier: bytes | None, + authority_cert_issuer: Iterable[GeneralName] | None, + authority_cert_serial_number: int | None, + ) -> None: + if (authority_cert_issuer is None) != ( + authority_cert_serial_number is None + ): + raise ValueError( + "authority_cert_issuer and authority_cert_serial_number " + "must both be present or both None" + ) + + if authority_cert_issuer is not None: + authority_cert_issuer = list(authority_cert_issuer) + if not all( + isinstance(x, GeneralName) for x in authority_cert_issuer + ): + raise TypeError( + "authority_cert_issuer must be a list of GeneralName " + "objects" + ) + + if authority_cert_serial_number is not None and not isinstance( + authority_cert_serial_number, int + ): + raise TypeError("authority_cert_serial_number must be an integer") + + self._key_identifier = key_identifier + self._authority_cert_issuer = authority_cert_issuer + self._authority_cert_serial_number = authority_cert_serial_number + + # This takes a subset of CertificatePublicKeyTypes because an issuer + # cannot have an X25519/X448 key. This introduces some unfortunate + # asymmetry that requires typing users to explicitly + # narrow their type, but we should make this accurate and not just + # convenient. + @classmethod + def from_issuer_public_key( + cls, public_key: CertificateIssuerPublicKeyTypes + ) -> AuthorityKeyIdentifier: + digest = _key_identifier_from_public_key(public_key) + return cls( + key_identifier=digest, + authority_cert_issuer=None, + authority_cert_serial_number=None, + ) + + @classmethod + def from_issuer_subject_key_identifier( + cls, ski: SubjectKeyIdentifier + ) -> AuthorityKeyIdentifier: + return cls( + key_identifier=ski.digest, + authority_cert_issuer=None, + authority_cert_serial_number=None, + ) + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, AuthorityKeyIdentifier): + return NotImplemented + + return ( + self.key_identifier == other.key_identifier + and self.authority_cert_issuer == other.authority_cert_issuer + and self.authority_cert_serial_number + == other.authority_cert_serial_number + ) + + def __hash__(self) -> int: + if self.authority_cert_issuer is None: + aci = None + else: + aci = tuple(self.authority_cert_issuer) + return hash( + (self.key_identifier, aci, self.authority_cert_serial_number) + ) + + @property + def key_identifier(self) -> bytes | None: + return self._key_identifier + + @property + def authority_cert_issuer( + self, + ) -> list[GeneralName] | None: + return self._authority_cert_issuer + + @property + def authority_cert_serial_number(self) -> int | None: + return self._authority_cert_serial_number + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class SubjectKeyIdentifier(ExtensionType): + oid = ExtensionOID.SUBJECT_KEY_IDENTIFIER + + def __init__(self, digest: bytes) -> None: + self._digest = digest + + @classmethod + def from_public_key( + cls, public_key: CertificatePublicKeyTypes + ) -> SubjectKeyIdentifier: + return cls(_key_identifier_from_public_key(public_key)) + + @property + def digest(self) -> bytes: + return self._digest + + @property + def key_identifier(self) -> bytes: + return self._digest + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SubjectKeyIdentifier): + return NotImplemented + + return constant_time.bytes_eq(self.digest, other.digest) + + def __hash__(self) -> int: + return hash(self.digest) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class AuthorityInformationAccess(ExtensionType): + oid = ExtensionOID.AUTHORITY_INFORMATION_ACCESS + + def __init__(self, descriptions: Iterable[AccessDescription]) -> None: + descriptions = list(descriptions) + if not all(isinstance(x, AccessDescription) for x in descriptions): + raise TypeError( + "Every item in the descriptions list must be an " + "AccessDescription" + ) + + self._descriptions = descriptions + + __len__, __iter__, __getitem__ = _make_sequence_methods("_descriptions") + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, AuthorityInformationAccess): + return NotImplemented + + return self._descriptions == other._descriptions + + def __hash__(self) -> int: + return hash(tuple(self._descriptions)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class SubjectInformationAccess(ExtensionType): + oid = ExtensionOID.SUBJECT_INFORMATION_ACCESS + + def __init__(self, descriptions: Iterable[AccessDescription]) -> None: + descriptions = list(descriptions) + if not all(isinstance(x, AccessDescription) for x in descriptions): + raise TypeError( + "Every item in the descriptions list must be an " + "AccessDescription" + ) + + self._descriptions = descriptions + + __len__, __iter__, __getitem__ = _make_sequence_methods("_descriptions") + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SubjectInformationAccess): + return NotImplemented + + return self._descriptions == other._descriptions + + def __hash__(self) -> int: + return hash(tuple(self._descriptions)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class AccessDescription: + def __init__( + self, access_method: ObjectIdentifier, access_location: GeneralName + ) -> None: + if not isinstance(access_method, ObjectIdentifier): + raise TypeError("access_method must be an ObjectIdentifier") + + if not isinstance(access_location, GeneralName): + raise TypeError("access_location must be a GeneralName") + + self._access_method = access_method + self._access_location = access_location + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, AccessDescription): + return NotImplemented + + return ( + self.access_method == other.access_method + and self.access_location == other.access_location + ) + + def __hash__(self) -> int: + return hash((self.access_method, self.access_location)) + + @property + def access_method(self) -> ObjectIdentifier: + return self._access_method + + @property + def access_location(self) -> GeneralName: + return self._access_location + + +class BasicConstraints(ExtensionType): + oid = ExtensionOID.BASIC_CONSTRAINTS + + def __init__(self, ca: bool, path_length: int | None) -> None: + if not isinstance(ca, bool): + raise TypeError("ca must be a boolean value") + + if path_length is not None and not ca: + raise ValueError("path_length must be None when ca is False") + + if path_length is not None and ( + not isinstance(path_length, int) or path_length < 0 + ): + raise TypeError( + "path_length must be a non-negative integer or None" + ) + + self._ca = ca + self._path_length = path_length + + @property + def ca(self) -> bool: + return self._ca + + @property + def path_length(self) -> int | None: + return self._path_length + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, BasicConstraints): + return NotImplemented + + return self.ca == other.ca and self.path_length == other.path_length + + def __hash__(self) -> int: + return hash((self.ca, self.path_length)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class DeltaCRLIndicator(ExtensionType): + oid = ExtensionOID.DELTA_CRL_INDICATOR + + def __init__(self, crl_number: int) -> None: + if not isinstance(crl_number, int): + raise TypeError("crl_number must be an integer") + + self._crl_number = crl_number + + @property + def crl_number(self) -> int: + return self._crl_number + + def __eq__(self, other: object) -> bool: + if not isinstance(other, DeltaCRLIndicator): + return NotImplemented + + return self.crl_number == other.crl_number + + def __hash__(self) -> int: + return hash(self.crl_number) + + def __repr__(self) -> str: + return f"" + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class CRLDistributionPoints(ExtensionType): + oid = ExtensionOID.CRL_DISTRIBUTION_POINTS + + def __init__( + self, distribution_points: Iterable[DistributionPoint] + ) -> None: + distribution_points = list(distribution_points) + if not all( + isinstance(x, DistributionPoint) for x in distribution_points + ): + raise TypeError( + "distribution_points must be a list of DistributionPoint " + "objects" + ) + + self._distribution_points = distribution_points + + __len__, __iter__, __getitem__ = _make_sequence_methods( + "_distribution_points" + ) + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, CRLDistributionPoints): + return NotImplemented + + return self._distribution_points == other._distribution_points + + def __hash__(self) -> int: + return hash(tuple(self._distribution_points)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class FreshestCRL(ExtensionType): + oid = ExtensionOID.FRESHEST_CRL + + def __init__( + self, distribution_points: Iterable[DistributionPoint] + ) -> None: + distribution_points = list(distribution_points) + if not all( + isinstance(x, DistributionPoint) for x in distribution_points + ): + raise TypeError( + "distribution_points must be a list of DistributionPoint " + "objects" + ) + + self._distribution_points = distribution_points + + __len__, __iter__, __getitem__ = _make_sequence_methods( + "_distribution_points" + ) + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, FreshestCRL): + return NotImplemented + + return self._distribution_points == other._distribution_points + + def __hash__(self) -> int: + return hash(tuple(self._distribution_points)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class DistributionPoint: + def __init__( + self, + full_name: Iterable[GeneralName] | None, + relative_name: RelativeDistinguishedName | None, + reasons: frozenset[ReasonFlags] | None, + crl_issuer: Iterable[GeneralName] | None, + ) -> None: + if full_name and relative_name: + raise ValueError( + "You cannot provide both full_name and relative_name, at " + "least one must be None." + ) + if not full_name and not relative_name and not crl_issuer: + raise ValueError( + "Either full_name, relative_name or crl_issuer must be " + "provided." + ) + + if full_name is not None: + full_name = list(full_name) + if not all(isinstance(x, GeneralName) for x in full_name): + raise TypeError( + "full_name must be a list of GeneralName objects" + ) + + if relative_name: + if not isinstance(relative_name, RelativeDistinguishedName): + raise TypeError( + "relative_name must be a RelativeDistinguishedName" + ) + + if crl_issuer is not None: + crl_issuer = list(crl_issuer) + if not all(isinstance(x, GeneralName) for x in crl_issuer): + raise TypeError( + "crl_issuer must be None or a list of general names" + ) + + if reasons and ( + not isinstance(reasons, frozenset) + or not all(isinstance(x, ReasonFlags) for x in reasons) + ): + raise TypeError("reasons must be None or frozenset of ReasonFlags") + + if reasons and ( + ReasonFlags.unspecified in reasons + or ReasonFlags.remove_from_crl in reasons + ): + raise ValueError( + "unspecified and remove_from_crl are not valid reasons in a " + "DistributionPoint" + ) + + self._full_name = full_name + self._relative_name = relative_name + self._reasons = reasons + self._crl_issuer = crl_issuer + + def __repr__(self) -> str: + return ( + "".format(self) + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, DistributionPoint): + return NotImplemented + + return ( + self.full_name == other.full_name + and self.relative_name == other.relative_name + and self.reasons == other.reasons + and self.crl_issuer == other.crl_issuer + ) + + def __hash__(self) -> int: + if self.full_name is not None: + fn: tuple[GeneralName, ...] | None = tuple(self.full_name) + else: + fn = None + + if self.crl_issuer is not None: + crl_issuer: tuple[GeneralName, ...] | None = tuple(self.crl_issuer) + else: + crl_issuer = None + + return hash((fn, self.relative_name, self.reasons, crl_issuer)) + + @property + def full_name(self) -> list[GeneralName] | None: + return self._full_name + + @property + def relative_name(self) -> RelativeDistinguishedName | None: + return self._relative_name + + @property + def reasons(self) -> frozenset[ReasonFlags] | None: + return self._reasons + + @property + def crl_issuer(self) -> list[GeneralName] | None: + return self._crl_issuer + + +class ReasonFlags(utils.Enum): + unspecified = "unspecified" + key_compromise = "keyCompromise" + ca_compromise = "cACompromise" + affiliation_changed = "affiliationChanged" + superseded = "superseded" + cessation_of_operation = "cessationOfOperation" + certificate_hold = "certificateHold" + privilege_withdrawn = "privilegeWithdrawn" + aa_compromise = "aACompromise" + remove_from_crl = "removeFromCRL" + + +# These are distribution point bit string mappings. Not to be confused with +# CRLReason reason flags bit string mappings. +# ReasonFlags ::= BIT STRING { +# unused (0), +# keyCompromise (1), +# cACompromise (2), +# affiliationChanged (3), +# superseded (4), +# cessationOfOperation (5), +# certificateHold (6), +# privilegeWithdrawn (7), +# aACompromise (8) } +_REASON_BIT_MAPPING = { + 1: ReasonFlags.key_compromise, + 2: ReasonFlags.ca_compromise, + 3: ReasonFlags.affiliation_changed, + 4: ReasonFlags.superseded, + 5: ReasonFlags.cessation_of_operation, + 6: ReasonFlags.certificate_hold, + 7: ReasonFlags.privilege_withdrawn, + 8: ReasonFlags.aa_compromise, +} + +_CRLREASONFLAGS = { + ReasonFlags.key_compromise: 1, + ReasonFlags.ca_compromise: 2, + ReasonFlags.affiliation_changed: 3, + ReasonFlags.superseded: 4, + ReasonFlags.cessation_of_operation: 5, + ReasonFlags.certificate_hold: 6, + ReasonFlags.privilege_withdrawn: 7, + ReasonFlags.aa_compromise: 8, +} + +# CRLReason ::= ENUMERATED { +# unspecified (0), +# keyCompromise (1), +# cACompromise (2), +# affiliationChanged (3), +# superseded (4), +# cessationOfOperation (5), +# certificateHold (6), +# -- value 7 is not used +# removeFromCRL (8), +# privilegeWithdrawn (9), +# aACompromise (10) } +_CRL_ENTRY_REASON_ENUM_TO_CODE = { + ReasonFlags.unspecified: 0, + ReasonFlags.key_compromise: 1, + ReasonFlags.ca_compromise: 2, + ReasonFlags.affiliation_changed: 3, + ReasonFlags.superseded: 4, + ReasonFlags.cessation_of_operation: 5, + ReasonFlags.certificate_hold: 6, + ReasonFlags.remove_from_crl: 8, + ReasonFlags.privilege_withdrawn: 9, + ReasonFlags.aa_compromise: 10, +} + + +class PolicyConstraints(ExtensionType): + oid = ExtensionOID.POLICY_CONSTRAINTS + + def __init__( + self, + require_explicit_policy: int | None, + inhibit_policy_mapping: int | None, + ) -> None: + if require_explicit_policy is not None and not isinstance( + require_explicit_policy, int + ): + raise TypeError( + "require_explicit_policy must be a non-negative integer or " + "None" + ) + + if inhibit_policy_mapping is not None and not isinstance( + inhibit_policy_mapping, int + ): + raise TypeError( + "inhibit_policy_mapping must be a non-negative integer or None" + ) + + if inhibit_policy_mapping is None and require_explicit_policy is None: + raise ValueError( + "At least one of require_explicit_policy and " + "inhibit_policy_mapping must not be None" + ) + + self._require_explicit_policy = require_explicit_policy + self._inhibit_policy_mapping = inhibit_policy_mapping + + def __repr__(self) -> str: + return ( + "".format(self) + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PolicyConstraints): + return NotImplemented + + return ( + self.require_explicit_policy == other.require_explicit_policy + and self.inhibit_policy_mapping == other.inhibit_policy_mapping + ) + + def __hash__(self) -> int: + return hash( + (self.require_explicit_policy, self.inhibit_policy_mapping) + ) + + @property + def require_explicit_policy(self) -> int | None: + return self._require_explicit_policy + + @property + def inhibit_policy_mapping(self) -> int | None: + return self._inhibit_policy_mapping + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class CertificatePolicies(ExtensionType): + oid = ExtensionOID.CERTIFICATE_POLICIES + + def __init__(self, policies: Iterable[PolicyInformation]) -> None: + policies = list(policies) + if not all(isinstance(x, PolicyInformation) for x in policies): + raise TypeError( + "Every item in the policies list must be a PolicyInformation" + ) + + self._policies = policies + + __len__, __iter__, __getitem__ = _make_sequence_methods("_policies") + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, CertificatePolicies): + return NotImplemented + + return self._policies == other._policies + + def __hash__(self) -> int: + return hash(tuple(self._policies)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class PolicyInformation: + def __init__( + self, + policy_identifier: ObjectIdentifier, + policy_qualifiers: Iterable[str | UserNotice] | None, + ) -> None: + if not isinstance(policy_identifier, ObjectIdentifier): + raise TypeError("policy_identifier must be an ObjectIdentifier") + + self._policy_identifier = policy_identifier + + if policy_qualifiers is not None: + policy_qualifiers = list(policy_qualifiers) + if not all( + isinstance(x, (str, UserNotice)) for x in policy_qualifiers + ): + raise TypeError( + "policy_qualifiers must be a list of strings and/or " + "UserNotice objects or None" + ) + + self._policy_qualifiers = policy_qualifiers + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PolicyInformation): + return NotImplemented + + return ( + self.policy_identifier == other.policy_identifier + and self.policy_qualifiers == other.policy_qualifiers + ) + + def __hash__(self) -> int: + if self.policy_qualifiers is not None: + pq = tuple(self.policy_qualifiers) + else: + pq = None + + return hash((self.policy_identifier, pq)) + + @property + def policy_identifier(self) -> ObjectIdentifier: + return self._policy_identifier + + @property + def policy_qualifiers( + self, + ) -> list[str | UserNotice] | None: + return self._policy_qualifiers + + +class UserNotice: + def __init__( + self, + notice_reference: NoticeReference | None, + explicit_text: str | None, + ) -> None: + if notice_reference and not isinstance( + notice_reference, NoticeReference + ): + raise TypeError( + "notice_reference must be None or a NoticeReference" + ) + + self._notice_reference = notice_reference + self._explicit_text = explicit_text + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, UserNotice): + return NotImplemented + + return ( + self.notice_reference == other.notice_reference + and self.explicit_text == other.explicit_text + ) + + def __hash__(self) -> int: + return hash((self.notice_reference, self.explicit_text)) + + @property + def notice_reference(self) -> NoticeReference | None: + return self._notice_reference + + @property + def explicit_text(self) -> str | None: + return self._explicit_text + + +class NoticeReference: + def __init__( + self, + organization: str | None, + notice_numbers: Iterable[int], + ) -> None: + self._organization = organization + notice_numbers = list(notice_numbers) + if not all(isinstance(x, int) for x in notice_numbers): + raise TypeError("notice_numbers must be a list of integers") + + self._notice_numbers = notice_numbers + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, NoticeReference): + return NotImplemented + + return ( + self.organization == other.organization + and self.notice_numbers == other.notice_numbers + ) + + def __hash__(self) -> int: + return hash((self.organization, tuple(self.notice_numbers))) + + @property + def organization(self) -> str | None: + return self._organization + + @property + def notice_numbers(self) -> list[int]: + return self._notice_numbers + + +class ExtendedKeyUsage(ExtensionType): + oid = ExtensionOID.EXTENDED_KEY_USAGE + + def __init__(self, usages: Iterable[ObjectIdentifier]) -> None: + usages = list(usages) + if not all(isinstance(x, ObjectIdentifier) for x in usages): + raise TypeError( + "Every item in the usages list must be an ObjectIdentifier" + ) + + self._usages = usages + + __len__, __iter__, __getitem__ = _make_sequence_methods("_usages") + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, ExtendedKeyUsage): + return NotImplemented + + return self._usages == other._usages + + def __hash__(self) -> int: + return hash(tuple(self._usages)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class OCSPNoCheck(ExtensionType): + oid = ExtensionOID.OCSP_NO_CHECK + + def __eq__(self, other: object) -> bool: + if not isinstance(other, OCSPNoCheck): + return NotImplemented + + return True + + def __hash__(self) -> int: + return hash(OCSPNoCheck) + + def __repr__(self) -> str: + return "" + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class PrecertPoison(ExtensionType): + oid = ExtensionOID.PRECERT_POISON + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PrecertPoison): + return NotImplemented + + return True + + def __hash__(self) -> int: + return hash(PrecertPoison) + + def __repr__(self) -> str: + return "" + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class TLSFeature(ExtensionType): + oid = ExtensionOID.TLS_FEATURE + + def __init__(self, features: Iterable[TLSFeatureType]) -> None: + features = list(features) + if ( + not all(isinstance(x, TLSFeatureType) for x in features) + or len(features) == 0 + ): + raise TypeError( + "features must be a list of elements from the TLSFeatureType " + "enum" + ) + + self._features = features + + __len__, __iter__, __getitem__ = _make_sequence_methods("_features") + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, TLSFeature): + return NotImplemented + + return self._features == other._features + + def __hash__(self) -> int: + return hash(tuple(self._features)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class TLSFeatureType(utils.Enum): + # status_request is defined in RFC 6066 and is used for what is commonly + # called OCSP Must-Staple when present in the TLS Feature extension in an + # X.509 certificate. + status_request = 5 + # status_request_v2 is defined in RFC 6961 and allows multiple OCSP + # responses to be provided. It is not currently in use by clients or + # servers. + status_request_v2 = 17 + + +_TLS_FEATURE_TYPE_TO_ENUM = {x.value: x for x in TLSFeatureType} + + +class InhibitAnyPolicy(ExtensionType): + oid = ExtensionOID.INHIBIT_ANY_POLICY + + def __init__(self, skip_certs: int) -> None: + if not isinstance(skip_certs, int): + raise TypeError("skip_certs must be an integer") + + if skip_certs < 0: + raise ValueError("skip_certs must be a non-negative integer") + + self._skip_certs = skip_certs + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, InhibitAnyPolicy): + return NotImplemented + + return self.skip_certs == other.skip_certs + + def __hash__(self) -> int: + return hash(self.skip_certs) + + @property + def skip_certs(self) -> int: + return self._skip_certs + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class KeyUsage(ExtensionType): + oid = ExtensionOID.KEY_USAGE + + def __init__( + self, + digital_signature: bool, + content_commitment: bool, + key_encipherment: bool, + data_encipherment: bool, + key_agreement: bool, + key_cert_sign: bool, + crl_sign: bool, + encipher_only: bool, + decipher_only: bool, + ) -> None: + if not key_agreement and (encipher_only or decipher_only): + raise ValueError( + "encipher_only and decipher_only can only be true when " + "key_agreement is true" + ) + + self._digital_signature = digital_signature + self._content_commitment = content_commitment + self._key_encipherment = key_encipherment + self._data_encipherment = data_encipherment + self._key_agreement = key_agreement + self._key_cert_sign = key_cert_sign + self._crl_sign = crl_sign + self._encipher_only = encipher_only + self._decipher_only = decipher_only + + @property + def digital_signature(self) -> bool: + return self._digital_signature + + @property + def content_commitment(self) -> bool: + return self._content_commitment + + @property + def key_encipherment(self) -> bool: + return self._key_encipherment + + @property + def data_encipherment(self) -> bool: + return self._data_encipherment + + @property + def key_agreement(self) -> bool: + return self._key_agreement + + @property + def key_cert_sign(self) -> bool: + return self._key_cert_sign + + @property + def crl_sign(self) -> bool: + return self._crl_sign + + @property + def encipher_only(self) -> bool: + if not self.key_agreement: + raise ValueError( + "encipher_only is undefined unless key_agreement is true" + ) + else: + return self._encipher_only + + @property + def decipher_only(self) -> bool: + if not self.key_agreement: + raise ValueError( + "decipher_only is undefined unless key_agreement is true" + ) + else: + return self._decipher_only + + def __repr__(self) -> str: + try: + encipher_only = self.encipher_only + decipher_only = self.decipher_only + except ValueError: + # Users found None confusing because even though encipher/decipher + # have no meaning unless key_agreement is true, to construct an + # instance of the class you still need to pass False. + encipher_only = False + decipher_only = False + + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, KeyUsage): + return NotImplemented + + return ( + self.digital_signature == other.digital_signature + and self.content_commitment == other.content_commitment + and self.key_encipherment == other.key_encipherment + and self.data_encipherment == other.data_encipherment + and self.key_agreement == other.key_agreement + and self.key_cert_sign == other.key_cert_sign + and self.crl_sign == other.crl_sign + and self._encipher_only == other._encipher_only + and self._decipher_only == other._decipher_only + ) + + def __hash__(self) -> int: + return hash( + ( + self.digital_signature, + self.content_commitment, + self.key_encipherment, + self.data_encipherment, + self.key_agreement, + self.key_cert_sign, + self.crl_sign, + self._encipher_only, + self._decipher_only, + ) + ) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class PrivateKeyUsagePeriod(ExtensionType): + oid = ExtensionOID.PRIVATE_KEY_USAGE_PERIOD + + def __init__( + self, + not_before: datetime.datetime | None, + not_after: datetime.datetime | None, + ) -> None: + if ( + not isinstance(not_before, datetime.datetime) + and not_before is not None + ): + raise TypeError("not_before must be a datetime.datetime or None") + + if ( + not isinstance(not_after, datetime.datetime) + and not_after is not None + ): + raise TypeError("not_after must be a datetime.datetime or None") + + if not_before is None and not_after is None: + raise ValueError( + "At least one of not_before and not_after must not be None" + ) + + if ( + not_before is not None + and not_after is not None + and not_before > not_after + ): + raise ValueError("not_before must be before not_after") + + self._not_before = not_before + self._not_after = not_after + + @property + def not_before(self) -> datetime.datetime | None: + return self._not_before + + @property + def not_after(self) -> datetime.datetime | None: + return self._not_after + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PrivateKeyUsagePeriod): + return NotImplemented + + return ( + self.not_before == other.not_before + and self.not_after == other.not_after + ) + + def __hash__(self) -> int: + return hash((self.not_before, self.not_after)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class NameConstraints(ExtensionType): + oid = ExtensionOID.NAME_CONSTRAINTS + + def __init__( + self, + permitted_subtrees: Iterable[GeneralName] | None, + excluded_subtrees: Iterable[GeneralName] | None, + ) -> None: + if permitted_subtrees is not None: + permitted_subtrees = list(permitted_subtrees) + if not permitted_subtrees: + raise ValueError( + "permitted_subtrees must be a non-empty list or None" + ) + if not all(isinstance(x, GeneralName) for x in permitted_subtrees): + raise TypeError( + "permitted_subtrees must be a list of GeneralName objects " + "or None" + ) + + self._validate_tree(permitted_subtrees) + + if excluded_subtrees is not None: + excluded_subtrees = list(excluded_subtrees) + if not excluded_subtrees: + raise ValueError( + "excluded_subtrees must be a non-empty list or None" + ) + if not all(isinstance(x, GeneralName) for x in excluded_subtrees): + raise TypeError( + "excluded_subtrees must be a list of GeneralName objects " + "or None" + ) + + self._validate_tree(excluded_subtrees) + + if permitted_subtrees is None and excluded_subtrees is None: + raise ValueError( + "At least one of permitted_subtrees and excluded_subtrees " + "must not be None" + ) + + self._permitted_subtrees = permitted_subtrees + self._excluded_subtrees = excluded_subtrees + + def __eq__(self, other: object) -> bool: + if not isinstance(other, NameConstraints): + return NotImplemented + + return ( + self.excluded_subtrees == other.excluded_subtrees + and self.permitted_subtrees == other.permitted_subtrees + ) + + def _validate_tree(self, tree: Iterable[GeneralName]) -> None: + self._validate_ip_name(tree) + self._validate_dns_name(tree) + + def _validate_ip_name(self, tree: Iterable[GeneralName]) -> None: + if any( + isinstance(name, IPAddress) + and not isinstance( + name.value, (ipaddress.IPv4Network, ipaddress.IPv6Network) + ) + for name in tree + ): + raise TypeError( + "IPAddress name constraints must be an IPv4Network or" + " IPv6Network object" + ) + + def _validate_dns_name(self, tree: Iterable[GeneralName]) -> None: + if any( + isinstance(name, DNSName) and "*" in name.value for name in tree + ): + raise ValueError( + "DNSName name constraints must not contain the '*' wildcard" + " character" + ) + + def __repr__(self) -> str: + return ( + f"" + ) + + def __hash__(self) -> int: + if self.permitted_subtrees is not None: + ps: tuple[GeneralName, ...] | None = tuple(self.permitted_subtrees) + else: + ps = None + + if self.excluded_subtrees is not None: + es: tuple[GeneralName, ...] | None = tuple(self.excluded_subtrees) + else: + es = None + + return hash((ps, es)) + + @property + def permitted_subtrees( + self, + ) -> list[GeneralName] | None: + return self._permitted_subtrees + + @property + def excluded_subtrees( + self, + ) -> list[GeneralName] | None: + return self._excluded_subtrees + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class Extension(typing.Generic[ExtensionTypeVar]): + def __init__( + self, oid: ObjectIdentifier, critical: bool, value: ExtensionTypeVar + ) -> None: + if not isinstance(oid, ObjectIdentifier): + raise TypeError( + "oid argument must be an ObjectIdentifier instance." + ) + + if not isinstance(critical, bool): + raise TypeError("critical must be a boolean value") + + self._oid = oid + self._critical = critical + self._value = value + + @property + def oid(self) -> ObjectIdentifier: + return self._oid + + @property + def critical(self) -> bool: + return self._critical + + @property + def value(self) -> ExtensionTypeVar: + return self._value + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Extension): + return NotImplemented + + return ( + self.oid == other.oid + and self.critical == other.critical + and self.value == other.value + ) + + def __hash__(self) -> int: + return hash((self.oid, self.critical, self.value)) + + +class GeneralNames: + def __init__(self, general_names: Iterable[GeneralName]) -> None: + general_names = list(general_names) + if not all(isinstance(x, GeneralName) for x in general_names): + raise TypeError( + "Every item in the general_names list must be an " + "object conforming to the GeneralName interface" + ) + + self._general_names = general_names + + __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") + + @typing.overload + def get_values_for_type( + self, + type: type[DNSName] + | type[UniformResourceIdentifier] + | type[RFC822Name], + ) -> list[str]: ... + + @typing.overload + def get_values_for_type( + self, + type: type[DirectoryName], + ) -> list[Name]: ... + + @typing.overload + def get_values_for_type( + self, + type: type[RegisteredID], + ) -> list[ObjectIdentifier]: ... + + @typing.overload + def get_values_for_type( + self, type: type[IPAddress] + ) -> list[_IPAddressTypes]: ... + + @typing.overload + def get_values_for_type( + self, type: type[OtherName] + ) -> list[OtherName]: ... + + def get_values_for_type( + self, + type: type[DNSName] + | type[DirectoryName] + | type[IPAddress] + | type[OtherName] + | type[RFC822Name] + | type[RegisteredID] + | type[UniformResourceIdentifier], + ) -> ( + list[_IPAddressTypes] + | list[str] + | list[OtherName] + | list[Name] + | list[ObjectIdentifier] + ): + # Return the value of each GeneralName, except for OtherName instances + # which we return directly because it has two important properties not + # just one value. + objs = (i for i in self if isinstance(i, type)) + if type != OtherName: + return [i.value for i in objs] # type: ignore[return-value,unused-ignore] + return list(objs) # type: ignore[return-value,unused-ignore] + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, GeneralNames): + return NotImplemented + + return self._general_names == other._general_names + + def __hash__(self) -> int: + return hash(tuple(self._general_names)) + + +class SubjectAlternativeName(ExtensionType): + oid = ExtensionOID.SUBJECT_ALTERNATIVE_NAME + + def __init__(self, general_names: Iterable[GeneralName]) -> None: + self._general_names = GeneralNames(general_names) + + __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") + + @typing.overload + def get_values_for_type( + self, + type: type[DNSName] + | type[UniformResourceIdentifier] + | type[RFC822Name], + ) -> list[str]: ... + + @typing.overload + def get_values_for_type( + self, + type: type[DirectoryName], + ) -> list[Name]: ... + + @typing.overload + def get_values_for_type( + self, + type: type[RegisteredID], + ) -> list[ObjectIdentifier]: ... + + @typing.overload + def get_values_for_type( + self, type: type[IPAddress] + ) -> list[_IPAddressTypes]: ... + + @typing.overload + def get_values_for_type( + self, type: type[OtherName] + ) -> list[OtherName]: ... + + def get_values_for_type( + self, + type: type[DNSName] + | type[DirectoryName] + | type[IPAddress] + | type[OtherName] + | type[RFC822Name] + | type[RegisteredID] + | type[UniformResourceIdentifier], + ) -> ( + list[_IPAddressTypes] + | list[str] + | list[OtherName] + | list[Name] + | list[ObjectIdentifier] + ): + return self._general_names.get_values_for_type(type) + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SubjectAlternativeName): + return NotImplemented + + return self._general_names == other._general_names + + def __hash__(self) -> int: + return hash(self._general_names) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class IssuerAlternativeName(ExtensionType): + oid = ExtensionOID.ISSUER_ALTERNATIVE_NAME + + def __init__(self, general_names: Iterable[GeneralName]) -> None: + self._general_names = GeneralNames(general_names) + + __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") + + @typing.overload + def get_values_for_type( + self, + type: type[DNSName] + | type[UniformResourceIdentifier] + | type[RFC822Name], + ) -> list[str]: ... + + @typing.overload + def get_values_for_type( + self, + type: type[DirectoryName], + ) -> list[Name]: ... + + @typing.overload + def get_values_for_type( + self, + type: type[RegisteredID], + ) -> list[ObjectIdentifier]: ... + + @typing.overload + def get_values_for_type( + self, type: type[IPAddress] + ) -> list[_IPAddressTypes]: ... + + @typing.overload + def get_values_for_type( + self, type: type[OtherName] + ) -> list[OtherName]: ... + + def get_values_for_type( + self, + type: type[DNSName] + | type[DirectoryName] + | type[IPAddress] + | type[OtherName] + | type[RFC822Name] + | type[RegisteredID] + | type[UniformResourceIdentifier], + ) -> ( + list[_IPAddressTypes] + | list[str] + | list[OtherName] + | list[Name] + | list[ObjectIdentifier] + ): + return self._general_names.get_values_for_type(type) + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, IssuerAlternativeName): + return NotImplemented + + return self._general_names == other._general_names + + def __hash__(self) -> int: + return hash(self._general_names) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class CertificateIssuer(ExtensionType): + oid = CRLEntryExtensionOID.CERTIFICATE_ISSUER + + def __init__(self, general_names: Iterable[GeneralName]) -> None: + self._general_names = GeneralNames(general_names) + + __len__, __iter__, __getitem__ = _make_sequence_methods("_general_names") + + @typing.overload + def get_values_for_type( + self, + type: type[DNSName] + | type[UniformResourceIdentifier] + | type[RFC822Name], + ) -> list[str]: ... + + @typing.overload + def get_values_for_type( + self, + type: type[DirectoryName], + ) -> list[Name]: ... + + @typing.overload + def get_values_for_type( + self, + type: type[RegisteredID], + ) -> list[ObjectIdentifier]: ... + + @typing.overload + def get_values_for_type( + self, type: type[IPAddress] + ) -> list[_IPAddressTypes]: ... + + @typing.overload + def get_values_for_type( + self, type: type[OtherName] + ) -> list[OtherName]: ... + + def get_values_for_type( + self, + type: type[DNSName] + | type[DirectoryName] + | type[IPAddress] + | type[OtherName] + | type[RFC822Name] + | type[RegisteredID] + | type[UniformResourceIdentifier], + ) -> ( + list[_IPAddressTypes] + | list[str] + | list[OtherName] + | list[Name] + | list[ObjectIdentifier] + ): + return self._general_names.get_values_for_type(type) + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, CertificateIssuer): + return NotImplemented + + return self._general_names == other._general_names + + def __hash__(self) -> int: + return hash(self._general_names) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class CRLReason(ExtensionType): + oid = CRLEntryExtensionOID.CRL_REASON + + def __init__(self, reason: ReasonFlags) -> None: + if not isinstance(reason, ReasonFlags): + raise TypeError("reason must be an element from ReasonFlags") + + self._reason = reason + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, CRLReason): + return NotImplemented + + return self.reason == other.reason + + def __hash__(self) -> int: + return hash(self.reason) + + @property + def reason(self) -> ReasonFlags: + return self._reason + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class InvalidityDate(ExtensionType): + oid = CRLEntryExtensionOID.INVALIDITY_DATE + + def __init__(self, invalidity_date: datetime.datetime) -> None: + if not isinstance(invalidity_date, datetime.datetime): + raise TypeError("invalidity_date must be a datetime.datetime") + + self._invalidity_date = invalidity_date + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, InvalidityDate): + return NotImplemented + + return self.invalidity_date == other.invalidity_date + + def __hash__(self) -> int: + return hash(self.invalidity_date) + + @property + def invalidity_date(self) -> datetime.datetime: + return self._invalidity_date + + @property + def invalidity_date_utc(self) -> datetime.datetime: + if self._invalidity_date.tzinfo is None: + return self._invalidity_date.replace(tzinfo=datetime.timezone.utc) + else: + return self._invalidity_date.astimezone(tz=datetime.timezone.utc) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class PrecertificateSignedCertificateTimestamps(ExtensionType): + oid = ExtensionOID.PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS + + def __init__( + self, + signed_certificate_timestamps: Iterable[SignedCertificateTimestamp], + ) -> None: + signed_certificate_timestamps = list(signed_certificate_timestamps) + if not all( + isinstance(sct, SignedCertificateTimestamp) + for sct in signed_certificate_timestamps + ): + raise TypeError( + "Every item in the signed_certificate_timestamps list must be " + "a SignedCertificateTimestamp" + ) + self._signed_certificate_timestamps = signed_certificate_timestamps + + __len__, __iter__, __getitem__ = _make_sequence_methods( + "_signed_certificate_timestamps" + ) + + def __repr__(self) -> str: + return f"" + + def __hash__(self) -> int: + return hash(tuple(self._signed_certificate_timestamps)) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PrecertificateSignedCertificateTimestamps): + return NotImplemented + + return ( + self._signed_certificate_timestamps + == other._signed_certificate_timestamps + ) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class SignedCertificateTimestamps(ExtensionType): + oid = ExtensionOID.SIGNED_CERTIFICATE_TIMESTAMPS + + def __init__( + self, + signed_certificate_timestamps: Iterable[SignedCertificateTimestamp], + ) -> None: + signed_certificate_timestamps = list(signed_certificate_timestamps) + if not all( + isinstance(sct, SignedCertificateTimestamp) + for sct in signed_certificate_timestamps + ): + raise TypeError( + "Every item in the signed_certificate_timestamps list must be " + "a SignedCertificateTimestamp" + ) + self._signed_certificate_timestamps = signed_certificate_timestamps + + __len__, __iter__, __getitem__ = _make_sequence_methods( + "_signed_certificate_timestamps" + ) + + def __repr__(self) -> str: + return f"" + + def __hash__(self) -> int: + return hash(tuple(self._signed_certificate_timestamps)) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, SignedCertificateTimestamps): + return NotImplemented + + return ( + self._signed_certificate_timestamps + == other._signed_certificate_timestamps + ) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class OCSPNonce(ExtensionType): + oid = OCSPExtensionOID.NONCE + + def __init__(self, nonce: bytes) -> None: + if not isinstance(nonce, bytes): + raise TypeError("nonce must be bytes") + + self._nonce = nonce + + def __eq__(self, other: object) -> bool: + if not isinstance(other, OCSPNonce): + return NotImplemented + + return self.nonce == other.nonce + + def __hash__(self) -> int: + return hash(self.nonce) + + def __repr__(self) -> str: + return f"" + + @property + def nonce(self) -> bytes: + return self._nonce + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class OCSPAcceptableResponses(ExtensionType): + oid = OCSPExtensionOID.ACCEPTABLE_RESPONSES + + def __init__(self, responses: Iterable[ObjectIdentifier]) -> None: + responses = list(responses) + if any(not isinstance(r, ObjectIdentifier) for r in responses): + raise TypeError("All responses must be ObjectIdentifiers") + + self._responses = responses + + def __eq__(self, other: object) -> bool: + if not isinstance(other, OCSPAcceptableResponses): + return NotImplemented + + return self._responses == other._responses + + def __hash__(self) -> int: + return hash(tuple(self._responses)) + + def __repr__(self) -> str: + return f"" + + def __iter__(self) -> Iterator[ObjectIdentifier]: + return iter(self._responses) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class IssuingDistributionPoint(ExtensionType): + oid = ExtensionOID.ISSUING_DISTRIBUTION_POINT + + def __init__( + self, + full_name: Iterable[GeneralName] | None, + relative_name: RelativeDistinguishedName | None, + only_contains_user_certs: bool, + only_contains_ca_certs: bool, + only_some_reasons: frozenset[ReasonFlags] | None, + indirect_crl: bool, + only_contains_attribute_certs: bool, + ) -> None: + if full_name is not None: + full_name = list(full_name) + + if only_some_reasons and ( + not isinstance(only_some_reasons, frozenset) + or not all(isinstance(x, ReasonFlags) for x in only_some_reasons) + ): + raise TypeError( + "only_some_reasons must be None or frozenset of ReasonFlags" + ) + + if only_some_reasons and ( + ReasonFlags.unspecified in only_some_reasons + or ReasonFlags.remove_from_crl in only_some_reasons + ): + raise ValueError( + "unspecified and remove_from_crl are not valid reasons in an " + "IssuingDistributionPoint" + ) + + if not ( + isinstance(only_contains_user_certs, bool) + and isinstance(only_contains_ca_certs, bool) + and isinstance(indirect_crl, bool) + and isinstance(only_contains_attribute_certs, bool) + ): + raise TypeError( + "only_contains_user_certs, only_contains_ca_certs, " + "indirect_crl and only_contains_attribute_certs " + "must all be boolean." + ) + + # Per RFC5280 Section 5.2.5, the Issuing Distribution Point extension + # in a CRL can have only one of onlyContainsUserCerts, + # onlyContainsCACerts, onlyContainsAttributeCerts set to TRUE. + crl_constraints = [ + only_contains_user_certs, + only_contains_ca_certs, + only_contains_attribute_certs, + ] + + if len([x for x in crl_constraints if x]) > 1: + raise ValueError( + "Only one of the following can be set to True: " + "only_contains_user_certs, only_contains_ca_certs, " + "only_contains_attribute_certs" + ) + + if not any( + [ + only_contains_user_certs, + only_contains_ca_certs, + indirect_crl, + only_contains_attribute_certs, + full_name, + relative_name, + only_some_reasons, + ] + ): + raise ValueError( + "Cannot create empty extension: " + "if only_contains_user_certs, only_contains_ca_certs, " + "indirect_crl, and only_contains_attribute_certs are all False" + ", then either full_name, relative_name, or only_some_reasons " + "must have a value." + ) + + self._only_contains_user_certs = only_contains_user_certs + self._only_contains_ca_certs = only_contains_ca_certs + self._indirect_crl = indirect_crl + self._only_contains_attribute_certs = only_contains_attribute_certs + self._only_some_reasons = only_some_reasons + self._full_name = full_name + self._relative_name = relative_name + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, IssuingDistributionPoint): + return NotImplemented + + return ( + self.full_name == other.full_name + and self.relative_name == other.relative_name + and self.only_contains_user_certs == other.only_contains_user_certs + and self.only_contains_ca_certs == other.only_contains_ca_certs + and self.only_some_reasons == other.only_some_reasons + and self.indirect_crl == other.indirect_crl + and self.only_contains_attribute_certs + == other.only_contains_attribute_certs + ) + + def __hash__(self) -> int: + if self.full_name is not None: + full_name: tuple[GeneralName, ...] | None = tuple(self.full_name) + else: + full_name = None + + return hash( + ( + full_name, + self.relative_name, + self.only_contains_user_certs, + self.only_contains_ca_certs, + self.only_some_reasons, + self.indirect_crl, + self.only_contains_attribute_certs, + ) + ) + + @property + def full_name(self) -> list[GeneralName] | None: + return self._full_name + + @property + def relative_name(self) -> RelativeDistinguishedName | None: + return self._relative_name + + @property + def only_contains_user_certs(self) -> bool: + return self._only_contains_user_certs + + @property + def only_contains_ca_certs(self) -> bool: + return self._only_contains_ca_certs + + @property + def only_some_reasons( + self, + ) -> frozenset[ReasonFlags] | None: + return self._only_some_reasons + + @property + def indirect_crl(self) -> bool: + return self._indirect_crl + + @property + def only_contains_attribute_certs(self) -> bool: + return self._only_contains_attribute_certs + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class MSCertificateTemplate(ExtensionType): + oid = ExtensionOID.MS_CERTIFICATE_TEMPLATE + + def __init__( + self, + template_id: ObjectIdentifier, + major_version: int | None, + minor_version: int | None, + ) -> None: + if not isinstance(template_id, ObjectIdentifier): + raise TypeError("oid must be an ObjectIdentifier") + self._template_id = template_id + if ( + major_version is not None and not isinstance(major_version, int) + ) or ( + minor_version is not None and not isinstance(minor_version, int) + ): + raise TypeError( + "major_version and minor_version must be integers or None" + ) + self._major_version = major_version + self._minor_version = minor_version + + @property + def template_id(self) -> ObjectIdentifier: + return self._template_id + + @property + def major_version(self) -> int | None: + return self._major_version + + @property + def minor_version(self) -> int | None: + return self._minor_version + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, MSCertificateTemplate): + return NotImplemented + + return ( + self.template_id == other.template_id + and self.major_version == other.major_version + and self.minor_version == other.minor_version + ) + + def __hash__(self) -> int: + return hash((self.template_id, self.major_version, self.minor_version)) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class NamingAuthority: + def __init__( + self, + id: ObjectIdentifier | None, + url: str | None, + text: str | None, + ) -> None: + if id is not None and not isinstance(id, ObjectIdentifier): + raise TypeError("id must be an ObjectIdentifier") + + if url is not None and not isinstance(url, str): + raise TypeError("url must be a str") + + if text is not None and not isinstance(text, str): + raise TypeError("text must be a str") + + self._id = id + self._url = url + self._text = text + + @property + def id(self) -> ObjectIdentifier | None: + return self._id + + @property + def url(self) -> str | None: + return self._url + + @property + def text(self) -> str | None: + return self._text + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, NamingAuthority): + return NotImplemented + + return ( + self.id == other.id + and self.url == other.url + and self.text == other.text + ) + + def __hash__(self) -> int: + return hash( + ( + self.id, + self.url, + self.text, + ) + ) + + +class ProfessionInfo: + def __init__( + self, + naming_authority: NamingAuthority | None, + profession_items: Iterable[str], + profession_oids: Iterable[ObjectIdentifier] | None, + registration_number: str | None, + add_profession_info: bytes | None, + ) -> None: + if naming_authority is not None and not isinstance( + naming_authority, NamingAuthority + ): + raise TypeError("naming_authority must be a NamingAuthority") + + profession_items = list(profession_items) + if not all(isinstance(item, str) for item in profession_items): + raise TypeError( + "Every item in the profession_items list must be a str" + ) + + if profession_oids is not None: + profession_oids = list(profession_oids) + if not all( + isinstance(oid, ObjectIdentifier) for oid in profession_oids + ): + raise TypeError( + "Every item in the profession_oids list must be an " + "ObjectIdentifier" + ) + + if registration_number is not None and not isinstance( + registration_number, str + ): + raise TypeError("registration_number must be a str") + + if add_profession_info is not None and not isinstance( + add_profession_info, bytes + ): + raise TypeError("add_profession_info must be bytes") + + self._naming_authority = naming_authority + self._profession_items = profession_items + self._profession_oids = profession_oids + self._registration_number = registration_number + self._add_profession_info = add_profession_info + + @property + def naming_authority(self) -> NamingAuthority | None: + return self._naming_authority + + @property + def profession_items(self) -> list[str]: + return self._profession_items + + @property + def profession_oids(self) -> list[ObjectIdentifier] | None: + return self._profession_oids + + @property + def registration_number(self) -> str | None: + return self._registration_number + + @property + def add_profession_info(self) -> bytes | None: + return self._add_profession_info + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, ProfessionInfo): + return NotImplemented + + return ( + self.naming_authority == other.naming_authority + and self.profession_items == other.profession_items + and self.profession_oids == other.profession_oids + and self.registration_number == other.registration_number + and self.add_profession_info == other.add_profession_info + ) + + def __hash__(self) -> int: + if self.profession_oids is not None: + profession_oids = tuple(self.profession_oids) + else: + profession_oids = None + return hash( + ( + self.naming_authority, + tuple(self.profession_items), + profession_oids, + self.registration_number, + self.add_profession_info, + ) + ) + + +class Admission: + def __init__( + self, + admission_authority: GeneralName | None, + naming_authority: NamingAuthority | None, + profession_infos: Iterable[ProfessionInfo], + ) -> None: + if admission_authority is not None and not isinstance( + admission_authority, GeneralName + ): + raise TypeError("admission_authority must be a GeneralName") + + if naming_authority is not None and not isinstance( + naming_authority, NamingAuthority + ): + raise TypeError("naming_authority must be a NamingAuthority") + + profession_infos = list(profession_infos) + if not all( + isinstance(info, ProfessionInfo) for info in profession_infos + ): + raise TypeError( + "Every item in the profession_infos list must be a " + "ProfessionInfo" + ) + + self._admission_authority = admission_authority + self._naming_authority = naming_authority + self._profession_infos = profession_infos + + @property + def admission_authority(self) -> GeneralName | None: + return self._admission_authority + + @property + def naming_authority(self) -> NamingAuthority | None: + return self._naming_authority + + @property + def profession_infos(self) -> list[ProfessionInfo]: + return self._profession_infos + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Admission): + return NotImplemented + + return ( + self.admission_authority == other.admission_authority + and self.naming_authority == other.naming_authority + and self.profession_infos == other.profession_infos + ) + + def __hash__(self) -> int: + return hash( + ( + self.admission_authority, + self.naming_authority, + tuple(self.profession_infos), + ) + ) + + +class Admissions(ExtensionType): + oid = ExtensionOID.ADMISSIONS + + def __init__( + self, + authority: GeneralName | None, + admissions: Iterable[Admission], + ) -> None: + if authority is not None and not isinstance(authority, GeneralName): + raise TypeError("authority must be a GeneralName") + + admissions = list(admissions) + if not all( + isinstance(admission, Admission) for admission in admissions + ): + raise TypeError( + "Every item in the contents_of_admissions list must be an " + "Admission" + ) + + self._authority = authority + self._admissions = admissions + + __len__, __iter__, __getitem__ = _make_sequence_methods("_admissions") + + @property + def authority(self) -> GeneralName | None: + return self._authority + + def __repr__(self) -> str: + return ( + f"" + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Admissions): + return NotImplemented + + return ( + self.authority == other.authority + and self._admissions == other._admissions + ) + + def __hash__(self) -> int: + return hash((self.authority, tuple(self._admissions))) + + def public_bytes(self) -> bytes: + return rust_x509.encode_extension_value(self) + + +class UnrecognizedExtension(ExtensionType): + def __init__(self, oid: ObjectIdentifier, value: bytes) -> None: + if not isinstance(oid, ObjectIdentifier): + raise TypeError("oid must be an ObjectIdentifier") + self._oid = oid + self._value = value + + @property + def oid(self) -> ObjectIdentifier: # type: ignore[override] + return self._oid + + @property + def value(self) -> bytes: + return self._value + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, UnrecognizedExtension): + return NotImplemented + + return self.oid == other.oid and self.value == other.value + + def __hash__(self) -> int: + return hash((self.oid, self.value)) + + def public_bytes(self) -> bytes: + return self.value diff --git a/intelliaide/vendor/cryptography/x509/general_name.py b/intelliaide/vendor/cryptography/x509/general_name.py new file mode 100644 index 0000000..672f287 --- /dev/null +++ b/intelliaide/vendor/cryptography/x509/general_name.py @@ -0,0 +1,281 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import abc +import ipaddress +import typing +from email.utils import parseaddr + +from cryptography.x509.name import Name +from cryptography.x509.oid import ObjectIdentifier + +_IPAddressTypes = typing.Union[ + ipaddress.IPv4Address, + ipaddress.IPv6Address, + ipaddress.IPv4Network, + ipaddress.IPv6Network, +] + + +class UnsupportedGeneralNameType(Exception): + pass + + +class GeneralName(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def value(self) -> typing.Any: + """ + Return the value of the object + """ + + +class RFC822Name(GeneralName): + def __init__(self, value: str) -> None: + if isinstance(value, str): + try: + value.encode("ascii") + except UnicodeEncodeError: + raise ValueError( + "RFC822Name values should be passed as an A-label string. " + "This means unicode characters should be encoded via " + "a library like idna." + ) + else: + raise TypeError("value must be string") + + name, address = parseaddr(value) + if name or not address: + # parseaddr has found a name (e.g. Name ) or the entire + # value is an empty string. + raise ValueError("Invalid rfc822name value") + + self._value = value + + @property + def value(self) -> str: + return self._value + + @classmethod + def _init_without_validation(cls, value: str) -> RFC822Name: + instance = cls.__new__(cls) + instance._value = value + return instance + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, RFC822Name): + return NotImplemented + + return self.value == other.value + + def __hash__(self) -> int: + return hash(self.value) + + +class DNSName(GeneralName): + def __init__(self, value: str) -> None: + if isinstance(value, str): + try: + value.encode("ascii") + except UnicodeEncodeError: + raise ValueError( + "DNSName values should be passed as an A-label string. " + "This means unicode characters should be encoded via " + "a library like idna." + ) + else: + raise TypeError("value must be string") + + self._value = value + + @property + def value(self) -> str: + return self._value + + @classmethod + def _init_without_validation(cls, value: str) -> DNSName: + instance = cls.__new__(cls) + instance._value = value + return instance + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, DNSName): + return NotImplemented + + return self.value == other.value + + def __hash__(self) -> int: + return hash(self.value) + + +class UniformResourceIdentifier(GeneralName): + def __init__(self, value: str) -> None: + if isinstance(value, str): + try: + value.encode("ascii") + except UnicodeEncodeError: + raise ValueError( + "URI values should be passed as an A-label string. " + "This means unicode characters should be encoded via " + "a library like idna." + ) + else: + raise TypeError("value must be string") + + self._value = value + + @property + def value(self) -> str: + return self._value + + @classmethod + def _init_without_validation(cls, value: str) -> UniformResourceIdentifier: + instance = cls.__new__(cls) + instance._value = value + return instance + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, UniformResourceIdentifier): + return NotImplemented + + return self.value == other.value + + def __hash__(self) -> int: + return hash(self.value) + + +class DirectoryName(GeneralName): + def __init__(self, value: Name) -> None: + if not isinstance(value, Name): + raise TypeError("value must be a Name") + + self._value = value + + @property + def value(self) -> Name: + return self._value + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, DirectoryName): + return NotImplemented + + return self.value == other.value + + def __hash__(self) -> int: + return hash(self.value) + + +class RegisteredID(GeneralName): + def __init__(self, value: ObjectIdentifier) -> None: + if not isinstance(value, ObjectIdentifier): + raise TypeError("value must be an ObjectIdentifier") + + self._value = value + + @property + def value(self) -> ObjectIdentifier: + return self._value + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, RegisteredID): + return NotImplemented + + return self.value == other.value + + def __hash__(self) -> int: + return hash(self.value) + + +class IPAddress(GeneralName): + def __init__(self, value: _IPAddressTypes) -> None: + if not isinstance( + value, + ( + ipaddress.IPv4Address, + ipaddress.IPv6Address, + ipaddress.IPv4Network, + ipaddress.IPv6Network, + ), + ): + raise TypeError( + "value must be an instance of ipaddress.IPv4Address, " + "ipaddress.IPv6Address, ipaddress.IPv4Network, or " + "ipaddress.IPv6Network" + ) + + self._value = value + + @property + def value(self) -> _IPAddressTypes: + return self._value + + def _packed(self) -> bytes: + if isinstance( + self.value, (ipaddress.IPv4Address, ipaddress.IPv6Address) + ): + return self.value.packed + else: + return ( + self.value.network_address.packed + self.value.netmask.packed + ) + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, IPAddress): + return NotImplemented + + return self.value == other.value + + def __hash__(self) -> int: + return hash(self.value) + + +class OtherName(GeneralName): + def __init__(self, type_id: ObjectIdentifier, value: bytes) -> None: + if not isinstance(type_id, ObjectIdentifier): + raise TypeError("type_id must be an ObjectIdentifier") + if not isinstance(value, bytes): + raise TypeError("value must be a binary string") + + self._type_id = type_id + self._value = value + + @property + def type_id(self) -> ObjectIdentifier: + return self._type_id + + @property + def value(self) -> bytes: + return self._value + + def __repr__(self) -> str: + return f"" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, OtherName): + return NotImplemented + + return self.type_id == other.type_id and self.value == other.value + + def __hash__(self) -> int: + return hash((self.type_id, self.value)) diff --git a/intelliaide/vendor/cryptography/x509/name.py b/intelliaide/vendor/cryptography/x509/name.py new file mode 100644 index 0000000..57f3d56 --- /dev/null +++ b/intelliaide/vendor/cryptography/x509/name.py @@ -0,0 +1,482 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import binascii +import re +import sys +import typing +import warnings +from collections.abc import Iterable, Iterator + +from cryptography import utils +from cryptography.hazmat.bindings._rust import x509 as rust_x509 +from cryptography.x509.oid import NameOID, ObjectIdentifier + + +class _ASN1Type(utils.Enum): + BitString = 3 + OctetString = 4 + UTF8String = 12 + NumericString = 18 + PrintableString = 19 + T61String = 20 + IA5String = 22 + UTCTime = 23 + GeneralizedTime = 24 + VisibleString = 26 + UniversalString = 28 + BMPString = 30 + + +_ASN1_TYPE_TO_ENUM = {i.value: i for i in _ASN1Type} +_NAMEOID_DEFAULT_TYPE: dict[ObjectIdentifier, _ASN1Type] = { + NameOID.COUNTRY_NAME: _ASN1Type.PrintableString, + NameOID.JURISDICTION_COUNTRY_NAME: _ASN1Type.PrintableString, + NameOID.SERIAL_NUMBER: _ASN1Type.PrintableString, + NameOID.DN_QUALIFIER: _ASN1Type.PrintableString, + NameOID.EMAIL_ADDRESS: _ASN1Type.IA5String, + NameOID.DOMAIN_COMPONENT: _ASN1Type.IA5String, +} + +# Type alias +_OidNameMap = typing.Mapping[ObjectIdentifier, str] +_NameOidMap = typing.Mapping[str, ObjectIdentifier] + +#: Short attribute names from RFC 4514: +#: https://tools.ietf.org/html/rfc4514#page-7 +_NAMEOID_TO_NAME: _OidNameMap = { + NameOID.COMMON_NAME: "CN", + NameOID.LOCALITY_NAME: "L", + NameOID.STATE_OR_PROVINCE_NAME: "ST", + NameOID.ORGANIZATION_NAME: "O", + NameOID.ORGANIZATIONAL_UNIT_NAME: "OU", + NameOID.COUNTRY_NAME: "C", + NameOID.STREET_ADDRESS: "STREET", + NameOID.DOMAIN_COMPONENT: "DC", + NameOID.USER_ID: "UID", +} +_NAME_TO_NAMEOID = {v: k for k, v in _NAMEOID_TO_NAME.items()} + +_NAMEOID_LENGTH_LIMIT = { + NameOID.COUNTRY_NAME: (2, 2), + NameOID.JURISDICTION_COUNTRY_NAME: (2, 2), + NameOID.COMMON_NAME: (1, 64), +} + + +def _escape_dn_value(val: str | bytes) -> str: + """Escape special characters in RFC4514 Distinguished Name value.""" + + if not val: + return "" + + # RFC 4514 Section 2.4 defines the value as being the # (U+0023) character + # followed by the hexadecimal encoding of the octets. + if isinstance(val, bytes): + return "#" + binascii.hexlify(val).decode("utf8") + + # See https://tools.ietf.org/html/rfc4514#section-2.4 + val = val.replace("\\", "\\\\") + val = val.replace('"', '\\"') + val = val.replace("+", "\\+") + val = val.replace(",", "\\,") + val = val.replace(";", "\\;") + val = val.replace("<", "\\<") + val = val.replace(">", "\\>") + val = val.replace("\0", "\\00") + + if val[0] == "#" or (val[0] == " " and len(val) > 1): + val = "\\" + val + if val[-1] == " ": + val = val[:-1] + "\\ " + + return val + + +def _unescape_dn_value(val: str) -> str: + if not val: + return "" + + # See https://tools.ietf.org/html/rfc4514#section-3 + + # special = escaped / SPACE / SHARP / EQUALS + # escaped = DQUOTE / PLUS / COMMA / SEMI / LANGLE / RANGLE + def sub(m): + val = m.group(0) + # Special character escape + if len(val) == 2: + return val[1:] + + # Unicode string of hex + return binascii.unhexlify(val.replace("\\", "")).decode() + + return _RFC4514NameParser._PAIR_MULTI_RE.sub(sub, val) + + +NameAttributeValueType = typing.TypeVar( + "NameAttributeValueType", + typing.Union[str, bytes], + str, + bytes, + covariant=True, +) + + +class NameAttribute(typing.Generic[NameAttributeValueType]): + def __init__( + self, + oid: ObjectIdentifier, + value: NameAttributeValueType, + _type: _ASN1Type | None = None, + *, + _validate: bool = True, + ) -> None: + if not isinstance(oid, ObjectIdentifier): + raise TypeError( + "oid argument must be an ObjectIdentifier instance." + ) + if _type == _ASN1Type.BitString: + if oid != NameOID.X500_UNIQUE_IDENTIFIER: + raise TypeError( + "oid must be X500_UNIQUE_IDENTIFIER for BitString type." + ) + if not isinstance(value, bytes): + raise TypeError("value must be bytes for BitString") + elif not isinstance(value, str): + raise TypeError("value argument must be a str") + + length_limits = _NAMEOID_LENGTH_LIMIT.get(oid) + if length_limits is not None: + min_length, max_length = length_limits + assert isinstance(value, str) + c_len = len(value.encode("utf8")) + if c_len < min_length or c_len > max_length: + msg = ( + f"Attribute's length must be >= {min_length} and " + f"<= {max_length}, but it was {c_len}" + ) + if _validate is True: + raise ValueError(msg) + else: + warnings.warn(msg, stacklevel=2) + + # The appropriate ASN1 string type varies by OID and is defined across + # multiple RFCs including 2459, 3280, and 5280. In general UTF8String + # is preferred (2459), but 3280 and 5280 specify several OIDs with + # alternate types. This means when we see the sentinel value we need + # to look up whether the OID has a non-UTF8 type. If it does, set it + # to that. Otherwise, UTF8! + if _type is None: + _type = _NAMEOID_DEFAULT_TYPE.get(oid, _ASN1Type.UTF8String) + + if not isinstance(_type, _ASN1Type): + raise TypeError("_type must be from the _ASN1Type enum") + + self._oid = oid + self._value: NameAttributeValueType = value + self._type: _ASN1Type = _type + + @property + def oid(self) -> ObjectIdentifier: + return self._oid + + @property + def value(self) -> NameAttributeValueType: + return self._value + + @property + def rfc4514_attribute_name(self) -> str: + """ + The short attribute name (for example "CN") if available, + otherwise the OID dotted string. + """ + return _NAMEOID_TO_NAME.get(self.oid, self.oid.dotted_string) + + def rfc4514_string( + self, attr_name_overrides: _OidNameMap | None = None + ) -> str: + """ + Format as RFC4514 Distinguished Name string. + + Use short attribute name if available, otherwise fall back to OID + dotted string. + """ + attr_name = ( + attr_name_overrides.get(self.oid) if attr_name_overrides else None + ) + if attr_name is None: + attr_name = self.rfc4514_attribute_name + + return f"{attr_name}={_escape_dn_value(self.value)}" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, NameAttribute): + return NotImplemented + + return self.oid == other.oid and self.value == other.value + + def __hash__(self) -> int: + return hash((self.oid, self.value)) + + def __repr__(self) -> str: + return f"" + + +class RelativeDistinguishedName: + def __init__(self, attributes: Iterable[NameAttribute[str | bytes]]): + attributes = list(attributes) + if not attributes: + raise ValueError("a relative distinguished name cannot be empty") + if not all(isinstance(x, NameAttribute) for x in attributes): + raise TypeError("attributes must be an iterable of NameAttribute") + + # Keep list and frozenset to preserve attribute order where it matters + self._attributes = attributes + self._attribute_set = frozenset(attributes) + + if len(self._attribute_set) != len(attributes): + raise ValueError("duplicate attributes are not allowed") + + def get_attributes_for_oid( + self, + oid: ObjectIdentifier, + ) -> list[NameAttribute[str | bytes]]: + return [i for i in self if i.oid == oid] + + def rfc4514_string( + self, attr_name_overrides: _OidNameMap | None = None + ) -> str: + """ + Format as RFC4514 Distinguished Name string. + + Within each RDN, attributes are joined by '+', although that is rarely + used in certificates. + """ + return "+".join( + attr.rfc4514_string(attr_name_overrides) + for attr in self._attributes + ) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, RelativeDistinguishedName): + return NotImplemented + + return self._attribute_set == other._attribute_set + + def __hash__(self) -> int: + return hash(self._attribute_set) + + def __iter__(self) -> Iterator[NameAttribute[str | bytes]]: + return iter(self._attributes) + + def __len__(self) -> int: + return len(self._attributes) + + def __repr__(self) -> str: + return f"" + + +class Name: + @typing.overload + def __init__( + self, attributes: Iterable[NameAttribute[str | bytes]] + ) -> None: ... + + @typing.overload + def __init__( + self, attributes: Iterable[RelativeDistinguishedName] + ) -> None: ... + + def __init__( + self, + attributes: Iterable[ + NameAttribute[str | bytes] | RelativeDistinguishedName + ], + ) -> None: + attributes = list(attributes) + if all(isinstance(x, NameAttribute) for x in attributes): + self._attributes = [ + RelativeDistinguishedName([typing.cast(NameAttribute, x)]) + for x in attributes + ] + elif all(isinstance(x, RelativeDistinguishedName) for x in attributes): + self._attributes = typing.cast( + list[RelativeDistinguishedName], attributes + ) + else: + raise TypeError( + "attributes must be a list of NameAttribute" + " or a list RelativeDistinguishedName" + ) + + @classmethod + def from_rfc4514_string( + cls, + data: str, + attr_name_overrides: _NameOidMap | None = None, + ) -> Name: + return _RFC4514NameParser(data, attr_name_overrides or {}).parse() + + def rfc4514_string( + self, attr_name_overrides: _OidNameMap | None = None + ) -> str: + """ + Format as RFC4514 Distinguished Name string. + For example 'CN=foobar.com,O=Foo Corp,C=US' + + An X.509 name is a two-level structure: a list of sets of attributes. + Each list element is separated by ',' and within each list element, set + elements are separated by '+'. The latter is almost never used in + real world certificates. According to RFC4514 section 2.1 the + RDNSequence must be reversed when converting to string representation. + """ + return ",".join( + attr.rfc4514_string(attr_name_overrides) + for attr in reversed(self._attributes) + ) + + def get_attributes_for_oid( + self, + oid: ObjectIdentifier, + ) -> list[NameAttribute[str | bytes]]: + return [i for i in self if i.oid == oid] + + @property + def rdns(self) -> list[RelativeDistinguishedName]: + return self._attributes + + def public_bytes(self, backend: typing.Any = None) -> bytes: + return rust_x509.encode_name_bytes(self) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Name): + return NotImplemented + + return self._attributes == other._attributes + + def __hash__(self) -> int: + # TODO: this is relatively expensive, if this looks like a bottleneck + # for you, consider optimizing! + return hash(tuple(self._attributes)) + + def __iter__(self) -> Iterator[NameAttribute[str | bytes]]: + for rdn in self._attributes: + yield from rdn + + def __len__(self) -> int: + return sum(len(rdn) for rdn in self._attributes) + + def __repr__(self) -> str: + return f"" + + +class _RFC4514NameParser: + _OID_RE = re.compile(r"(0|([1-9]\d*))(\.(0|([1-9]\d*)))+") + _DESCR_RE = re.compile(r"[a-zA-Z][a-zA-Z\d-]*") + + _ESCAPE_SPECIAL = r"[\\ #=\"\+,;<>]" + _ESCAPE_HEX = r"[\da-zA-Z]{2}" + _PAIR = rf"\\({_ESCAPE_SPECIAL}|{_ESCAPE_HEX})" + _PAIR_MULTI_RE = re.compile(rf"(\\{_ESCAPE_SPECIAL})|((\\{_ESCAPE_HEX})+)") + _LUTF1 = r"[\x01-\x1f\x21\x24-\x2A\x2D-\x3A\x3D\x3F-\x5B\x5D-\x7F]" + _SUTF1 = r"[\x01-\x21\x23-\x2A\x2D-\x3A\x3D\x3F-\x5B\x5D-\x7F]" + _TUTF1 = r"[\x01-\x1F\x21\x23-\x2A\x2D-\x3A\x3D\x3F-\x5B\x5D-\x7F]" + _UTFMB = rf"[\x80-{chr(sys.maxunicode)}]" + _LEADCHAR = rf"{_LUTF1}|{_UTFMB}" + _STRINGCHAR = rf"{_SUTF1}|{_UTFMB}" + _TRAILCHAR = rf"{_TUTF1}|{_UTFMB}" + _STRING_RE = re.compile( + rf""" + ( + ({_LEADCHAR}|{_PAIR}) + ( + ({_STRINGCHAR}|{_PAIR})* + ({_TRAILCHAR}|{_PAIR}) + )? + )? + """, + re.VERBOSE, + ) + _HEXSTRING_RE = re.compile(r"#([\da-zA-Z]{2})+") + + def __init__(self, data: str, attr_name_overrides: _NameOidMap) -> None: + self._data = data + self._idx = 0 + + self._attr_name_overrides = attr_name_overrides + + def _has_data(self) -> bool: + return self._idx < len(self._data) + + def _peek(self) -> str | None: + if self._has_data(): + return self._data[self._idx] + return None + + def _read_char(self, ch: str) -> None: + if self._peek() != ch: + raise ValueError + self._idx += 1 + + def _read_re(self, pat) -> str: + match = pat.match(self._data, pos=self._idx) + if match is None: + raise ValueError + val = match.group() + self._idx += len(val) + return val + + def parse(self) -> Name: + """ + Parses the `data` string and converts it to a Name. + + According to RFC4514 section 2.1 the RDNSequence must be + reversed when converting to string representation. So, when + we parse it, we need to reverse again to get the RDNs on the + correct order. + """ + + if not self._has_data(): + return Name([]) + + rdns = [self._parse_rdn()] + + while self._has_data(): + self._read_char(",") + rdns.append(self._parse_rdn()) + + return Name(reversed(rdns)) + + def _parse_rdn(self) -> RelativeDistinguishedName: + nas = [self._parse_na()] + while self._peek() == "+": + self._read_char("+") + nas.append(self._parse_na()) + + return RelativeDistinguishedName(nas) + + def _parse_na(self) -> NameAttribute[str]: + try: + oid_value = self._read_re(self._OID_RE) + except ValueError: + name = self._read_re(self._DESCR_RE) + oid = self._attr_name_overrides.get( + name, _NAME_TO_NAMEOID.get(name) + ) + if oid is None: + raise ValueError + else: + oid = ObjectIdentifier(oid_value) + + self._read_char("=") + if self._peek() == "#": + value = self._read_re(self._HEXSTRING_RE) + value = binascii.unhexlify(value[1:]).decode() + else: + raw_value = self._read_re(self._STRING_RE) + value = _unescape_dn_value(raw_value) + + return NameAttribute(oid, value) diff --git a/intelliaide/vendor/cryptography/x509/ocsp.py b/intelliaide/vendor/cryptography/x509/ocsp.py new file mode 100644 index 0000000..f61ed80 --- /dev/null +++ b/intelliaide/vendor/cryptography/x509/ocsp.py @@ -0,0 +1,379 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import datetime +from collections.abc import Iterable + +from cryptography import utils, x509 +from cryptography.hazmat.bindings._rust import ocsp +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric.types import ( + CertificateIssuerPrivateKeyTypes, +) +from cryptography.x509.base import _reject_duplicate_extension + + +class OCSPResponderEncoding(utils.Enum): + HASH = "By Hash" + NAME = "By Name" + + +class OCSPResponseStatus(utils.Enum): + SUCCESSFUL = 0 + MALFORMED_REQUEST = 1 + INTERNAL_ERROR = 2 + TRY_LATER = 3 + SIG_REQUIRED = 5 + UNAUTHORIZED = 6 + + +_ALLOWED_HASHES = ( + hashes.SHA1, + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, +) + + +def _verify_algorithm(algorithm: hashes.HashAlgorithm) -> None: + if not isinstance(algorithm, _ALLOWED_HASHES): + raise ValueError( + "Algorithm must be SHA1, SHA224, SHA256, SHA384, or SHA512" + ) + + +class OCSPCertStatus(utils.Enum): + GOOD = 0 + REVOKED = 1 + UNKNOWN = 2 + + +class _SingleResponse: + def __init__( + self, + resp: tuple[x509.Certificate, x509.Certificate] | None, + resp_hash: tuple[bytes, bytes, int] | None, + algorithm: hashes.HashAlgorithm, + cert_status: OCSPCertStatus, + this_update: datetime.datetime, + next_update: datetime.datetime | None, + revocation_time: datetime.datetime | None, + revocation_reason: x509.ReasonFlags | None, + ): + _verify_algorithm(algorithm) + if not isinstance(this_update, datetime.datetime): + raise TypeError("this_update must be a datetime object") + if next_update is not None and not isinstance( + next_update, datetime.datetime + ): + raise TypeError("next_update must be a datetime object or None") + + self._resp = resp + self._resp_hash = resp_hash + self._algorithm = algorithm + self._this_update = this_update + self._next_update = next_update + + if not isinstance(cert_status, OCSPCertStatus): + raise TypeError( + "cert_status must be an item from the OCSPCertStatus enum" + ) + if cert_status is not OCSPCertStatus.REVOKED: + if revocation_time is not None: + raise ValueError( + "revocation_time can only be provided if the certificate " + "is revoked" + ) + if revocation_reason is not None: + raise ValueError( + "revocation_reason can only be provided if the certificate" + " is revoked" + ) + else: + if not isinstance(revocation_time, datetime.datetime): + raise TypeError("revocation_time must be a datetime object") + + if revocation_reason is not None and not isinstance( + revocation_reason, x509.ReasonFlags + ): + raise TypeError( + "revocation_reason must be an item from the ReasonFlags " + "enum or None" + ) + + self._cert_status = cert_status + self._revocation_time = revocation_time + self._revocation_reason = revocation_reason + + +OCSPRequest = ocsp.OCSPRequest +OCSPResponse = ocsp.OCSPResponse +OCSPSingleResponse = ocsp.OCSPSingleResponse + + +class OCSPRequestBuilder: + def __init__( + self, + request: tuple[ + x509.Certificate, x509.Certificate, hashes.HashAlgorithm + ] + | None = None, + request_hash: tuple[bytes, bytes, int, hashes.HashAlgorithm] + | None = None, + extensions: list[x509.Extension[x509.ExtensionType]] = [], + ) -> None: + self._request = request + self._request_hash = request_hash + self._extensions = extensions + + def add_certificate( + self, + cert: x509.Certificate, + issuer: x509.Certificate, + algorithm: hashes.HashAlgorithm, + ) -> OCSPRequestBuilder: + if self._request is not None or self._request_hash is not None: + raise ValueError("Only one certificate can be added to a request") + + _verify_algorithm(algorithm) + if not isinstance(cert, x509.Certificate) or not isinstance( + issuer, x509.Certificate + ): + raise TypeError("cert and issuer must be a Certificate") + + return OCSPRequestBuilder( + (cert, issuer, algorithm), self._request_hash, self._extensions + ) + + def add_certificate_by_hash( + self, + issuer_name_hash: bytes, + issuer_key_hash: bytes, + serial_number: int, + algorithm: hashes.HashAlgorithm, + ) -> OCSPRequestBuilder: + if self._request is not None or self._request_hash is not None: + raise ValueError("Only one certificate can be added to a request") + + if not isinstance(serial_number, int): + raise TypeError("serial_number must be an integer") + + _verify_algorithm(algorithm) + utils._check_bytes("issuer_name_hash", issuer_name_hash) + utils._check_bytes("issuer_key_hash", issuer_key_hash) + if algorithm.digest_size != len( + issuer_name_hash + ) or algorithm.digest_size != len(issuer_key_hash): + raise ValueError( + "issuer_name_hash and issuer_key_hash must be the same length " + "as the digest size of the algorithm" + ) + + return OCSPRequestBuilder( + self._request, + (issuer_name_hash, issuer_key_hash, serial_number, algorithm), + self._extensions, + ) + + def add_extension( + self, extval: x509.ExtensionType, critical: bool + ) -> OCSPRequestBuilder: + if not isinstance(extval, x509.ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = x509.Extension(extval.oid, critical, extval) + _reject_duplicate_extension(extension, self._extensions) + + return OCSPRequestBuilder( + self._request, self._request_hash, [*self._extensions, extension] + ) + + def build(self) -> OCSPRequest: + if self._request is None and self._request_hash is None: + raise ValueError("You must add a certificate before building") + + return ocsp.create_ocsp_request(self) + + +class OCSPResponseBuilder: + def __init__( + self, + response: _SingleResponse | None = None, + responder_id: tuple[x509.Certificate, OCSPResponderEncoding] + | None = None, + certs: list[x509.Certificate] | None = None, + extensions: list[x509.Extension[x509.ExtensionType]] = [], + ): + self._response = response + self._responder_id = responder_id + self._certs = certs + self._extensions = extensions + + def add_response( + self, + cert: x509.Certificate, + issuer: x509.Certificate, + algorithm: hashes.HashAlgorithm, + cert_status: OCSPCertStatus, + this_update: datetime.datetime, + next_update: datetime.datetime | None, + revocation_time: datetime.datetime | None, + revocation_reason: x509.ReasonFlags | None, + ) -> OCSPResponseBuilder: + if self._response is not None: + raise ValueError("Only one response per OCSPResponse.") + + if not isinstance(cert, x509.Certificate) or not isinstance( + issuer, x509.Certificate + ): + raise TypeError("cert and issuer must be a Certificate") + + singleresp = _SingleResponse( + (cert, issuer), + None, + algorithm, + cert_status, + this_update, + next_update, + revocation_time, + revocation_reason, + ) + return OCSPResponseBuilder( + singleresp, + self._responder_id, + self._certs, + self._extensions, + ) + + def add_response_by_hash( + self, + issuer_name_hash: bytes, + issuer_key_hash: bytes, + serial_number: int, + algorithm: hashes.HashAlgorithm, + cert_status: OCSPCertStatus, + this_update: datetime.datetime, + next_update: datetime.datetime | None, + revocation_time: datetime.datetime | None, + revocation_reason: x509.ReasonFlags | None, + ) -> OCSPResponseBuilder: + if self._response is not None: + raise ValueError("Only one response per OCSPResponse.") + + if not isinstance(serial_number, int): + raise TypeError("serial_number must be an integer") + + utils._check_bytes("issuer_name_hash", issuer_name_hash) + utils._check_bytes("issuer_key_hash", issuer_key_hash) + _verify_algorithm(algorithm) + if algorithm.digest_size != len( + issuer_name_hash + ) or algorithm.digest_size != len(issuer_key_hash): + raise ValueError( + "issuer_name_hash and issuer_key_hash must be the same length " + "as the digest size of the algorithm" + ) + + singleresp = _SingleResponse( + None, + (issuer_name_hash, issuer_key_hash, serial_number), + algorithm, + cert_status, + this_update, + next_update, + revocation_time, + revocation_reason, + ) + return OCSPResponseBuilder( + singleresp, + self._responder_id, + self._certs, + self._extensions, + ) + + def responder_id( + self, encoding: OCSPResponderEncoding, responder_cert: x509.Certificate + ) -> OCSPResponseBuilder: + if self._responder_id is not None: + raise ValueError("responder_id can only be set once") + if not isinstance(responder_cert, x509.Certificate): + raise TypeError("responder_cert must be a Certificate") + if not isinstance(encoding, OCSPResponderEncoding): + raise TypeError( + "encoding must be an element from OCSPResponderEncoding" + ) + + return OCSPResponseBuilder( + self._response, + (responder_cert, encoding), + self._certs, + self._extensions, + ) + + def certificates( + self, certs: Iterable[x509.Certificate] + ) -> OCSPResponseBuilder: + if self._certs is not None: + raise ValueError("certificates may only be set once") + certs = list(certs) + if len(certs) == 0: + raise ValueError("certs must not be an empty list") + if not all(isinstance(x, x509.Certificate) for x in certs): + raise TypeError("certs must be a list of Certificates") + return OCSPResponseBuilder( + self._response, + self._responder_id, + certs, + self._extensions, + ) + + def add_extension( + self, extval: x509.ExtensionType, critical: bool + ) -> OCSPResponseBuilder: + if not isinstance(extval, x509.ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = x509.Extension(extval.oid, critical, extval) + _reject_duplicate_extension(extension, self._extensions) + + return OCSPResponseBuilder( + self._response, + self._responder_id, + self._certs, + [*self._extensions, extension], + ) + + def sign( + self, + private_key: CertificateIssuerPrivateKeyTypes, + algorithm: hashes.HashAlgorithm | None, + ) -> OCSPResponse: + if self._response is None: + raise ValueError("You must add a response before signing") + if self._responder_id is None: + raise ValueError("You must add a responder_id before signing") + + return ocsp.create_ocsp_response( + OCSPResponseStatus.SUCCESSFUL, self, private_key, algorithm + ) + + @classmethod + def build_unsuccessful( + cls, response_status: OCSPResponseStatus + ) -> OCSPResponse: + if not isinstance(response_status, OCSPResponseStatus): + raise TypeError( + "response_status must be an item from OCSPResponseStatus" + ) + if response_status is OCSPResponseStatus.SUCCESSFUL: + raise ValueError("response_status cannot be SUCCESSFUL") + + return ocsp.create_ocsp_response(response_status, None, None, None) + + +load_der_ocsp_request = ocsp.load_der_ocsp_request +load_der_ocsp_response = ocsp.load_der_ocsp_response diff --git a/intelliaide/vendor/cryptography/x509/oid.py b/intelliaide/vendor/cryptography/x509/oid.py new file mode 100644 index 0000000..520fc7a --- /dev/null +++ b/intelliaide/vendor/cryptography/x509/oid.py @@ -0,0 +1,37 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +from cryptography.hazmat._oid import ( + AttributeOID, + AuthorityInformationAccessOID, + CertificatePoliciesOID, + CRLEntryExtensionOID, + ExtendedKeyUsageOID, + ExtensionOID, + NameOID, + ObjectIdentifier, + OCSPExtensionOID, + OtherNameFormOID, + PublicKeyAlgorithmOID, + SignatureAlgorithmOID, + SubjectInformationAccessOID, +) + +__all__ = [ + "AttributeOID", + "AuthorityInformationAccessOID", + "CRLEntryExtensionOID", + "CertificatePoliciesOID", + "ExtendedKeyUsageOID", + "ExtensionOID", + "NameOID", + "OCSPExtensionOID", + "ObjectIdentifier", + "OtherNameFormOID", + "PublicKeyAlgorithmOID", + "SignatureAlgorithmOID", + "SubjectInformationAccessOID", +] diff --git a/intelliaide/vendor/cryptography/x509/verification.py b/intelliaide/vendor/cryptography/x509/verification.py new file mode 100644 index 0000000..2db4324 --- /dev/null +++ b/intelliaide/vendor/cryptography/x509/verification.py @@ -0,0 +1,34 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import typing + +from cryptography.hazmat.bindings._rust import x509 as rust_x509 +from cryptography.x509.general_name import DNSName, IPAddress + +__all__ = [ + "ClientVerifier", + "Criticality", + "ExtensionPolicy", + "Policy", + "PolicyBuilder", + "ServerVerifier", + "Store", + "Subject", + "VerificationError", + "VerifiedClient", +] + +Store = rust_x509.Store +Subject = typing.Union[DNSName, IPAddress] +VerifiedClient = rust_x509.VerifiedClient +ClientVerifier = rust_x509.ClientVerifier +ServerVerifier = rust_x509.ServerVerifier +PolicyBuilder = rust_x509.PolicyBuilder +Policy = rust_x509.Policy +ExtensionPolicy = rust_x509.ExtensionPolicy +Criticality = rust_x509.Criticality +VerificationError = rust_x509.VerificationError diff --git a/intelliaide/vendor/defusedxml-0.7.1.dist-info/INSTALLER b/intelliaide/vendor/defusedxml-0.7.1.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/intelliaide/vendor/defusedxml-0.7.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/intelliaide/vendor/defusedxml-0.7.1.dist-info/LICENSE b/intelliaide/vendor/defusedxml-0.7.1.dist-info/LICENSE new file mode 100644 index 0000000..311690c --- /dev/null +++ b/intelliaide/vendor/defusedxml-0.7.1.dist-info/LICENSE @@ -0,0 +1,49 @@ +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python +alone or in any derivative version, provided, however, that PSF's +License Agreement and PSF's notice of copyright, i.e., "Copyright (c) +2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative +version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + diff --git a/intelliaide/vendor/defusedxml-0.7.1.dist-info/METADATA b/intelliaide/vendor/defusedxml-0.7.1.dist-info/METADATA new file mode 100644 index 0000000..f916e89 --- /dev/null +++ b/intelliaide/vendor/defusedxml-0.7.1.dist-info/METADATA @@ -0,0 +1,978 @@ +Metadata-Version: 2.1 +Name: defusedxml +Version: 0.7.1 +Summary: XML bomb protection for Python stdlib modules +Home-page: https://github.com/tiran/defusedxml +Author: Christian Heimes +Author-email: christian@python.org +Maintainer: Christian Heimes +Maintainer-email: christian@python.org +License: PSFL +Download-URL: https://pypi.python.org/pypi/defusedxml +Keywords: xml bomb DoS +Platform: all +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Python Software Foundation License +Classifier: Natural Language :: English +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Topic :: Text Processing :: Markup :: XML +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* + +=================================================== +defusedxml -- defusing XML bombs and other exploits +=================================================== + +.. image:: https://img.shields.io/pypi/v/defusedxml.svg + :target: https://pypi.org/project/defusedxml/ + :alt: Latest Version + +.. image:: https://img.shields.io/pypi/pyversions/defusedxml.svg + :target: https://pypi.org/project/defusedxml/ + :alt: Supported Python versions + +.. image:: https://travis-ci.org/tiran/defusedxml.svg?branch=master + :target: https://travis-ci.org/tiran/defusedxml + :alt: Travis CI + +.. image:: https://codecov.io/github/tiran/defusedxml/coverage.svg?branch=master + :target: https://codecov.io/github/tiran/defusedxml?branch=master + :alt: codecov + +.. image:: https://img.shields.io/pypi/dm/defusedxml.svg + :target: https://pypistats.org/packages/defusedxml + :alt: PyPI downloads + +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + :alt: Code style: black + +.. + + "It's just XML, what could probably go wrong?" + +Christian Heimes + +Synopsis +======== + +The results of an attack on a vulnerable XML library can be fairly dramatic. +With just a few hundred **Bytes** of XML data an attacker can occupy several +**Gigabytes** of memory within **seconds**. An attacker can also keep +CPUs busy for a long time with a small to medium size request. Under some +circumstances it is even possible to access local files on your +server, to circumvent a firewall, or to abuse services to rebound attacks to +third parties. + +The attacks use and abuse less common features of XML and its parsers. The +majority of developers are unacquainted with features such as processing +instructions and entity expansions that XML inherited from SGML. At best +they know about ```` from experience with HTML but they are not +aware that a document type definition (DTD) can generate an HTTP request +or load a file from the file system. + +None of the issues is new. They have been known for a long time. Billion +laughs was first reported in 2003. Nevertheless some XML libraries and +applications are still vulnerable and even heavy users of XML are +surprised by these features. It's hard to say whom to blame for the +situation. It's too short sighted to shift all blame on XML parsers and +XML libraries for using insecure default settings. After all they +properly implement XML specifications. Application developers must not rely +that a library is always configured for security and potential harmful data +by default. + + +.. contents:: Table of Contents + :depth: 2 + + +Attack vectors +============== + +billion laughs / exponential entity expansion +--------------------------------------------- + +The `Billion Laughs`_ attack -- also known as exponential entity expansion -- +uses multiple levels of nested entities. The original example uses 9 levels +of 10 expansions in each level to expand the string ``lol`` to a string of +3 * 10 :sup:`9` bytes, hence the name "billion laughs". The resulting string +occupies 3 GB (2.79 GiB) of memory; intermediate strings require additional +memory. Because most parsers don't cache the intermediate step for every +expansion it is repeated over and over again. It increases the CPU load even +more. + +An XML document of just a few hundred bytes can disrupt all services on a +machine within seconds. + +Example XML:: + + + + + + ]> + &d; + + +quadratic blowup entity expansion +--------------------------------- + +A quadratic blowup attack is similar to a `Billion Laughs`_ attack; it abuses +entity expansion, too. Instead of nested entities it repeats one large entity +with a couple of thousand chars over and over again. The attack isn't as +efficient as the exponential case but it avoids triggering countermeasures of +parsers against heavily nested entities. Some parsers limit the depth and +breadth of a single entity but not the total amount of expanded text +throughout an entire XML document. + +A medium-sized XML document with a couple of hundred kilobytes can require a +couple of hundred MB to several GB of memory. When the attack is combined +with some level of nested expansion an attacker is able to achieve a higher +ratio of success. + +:: + + + ]> + &a;&a;&a;... repeat + + +external entity expansion (remote) +---------------------------------- + +Entity declarations can contain more than just text for replacement. They can +also point to external resources by public identifiers or system identifiers. +System identifiers are standard URIs. When the URI is a URL (e.g. a +``http://`` locator) some parsers download the resource from the remote +location and embed them into the XML document verbatim. + +Simple example of a parsed external entity:: + + + ]> + + +The case of parsed external entities works only for valid XML content. The +XML standard also supports unparsed external entities with a +``NData declaration``. + +External entity expansion opens the door to plenty of exploits. An attacker +can abuse a vulnerable XML library and application to rebound and forward +network requests with the IP address of the server. It highly depends +on the parser and the application what kind of exploit is possible. For +example: + +* An attacker can circumvent firewalls and gain access to restricted + resources as all the requests are made from an internal and trustworthy + IP address, not from the outside. +* An attacker can abuse a service to attack, spy on or DoS your servers but + also third party services. The attack is disguised with the IP address of + the server and the attacker is able to utilize the high bandwidth of a big + machine. +* An attacker can exhaust additional resources on the machine, e.g. with + requests to a service that doesn't respond or responds with very large + files. +* An attacker may gain knowledge, when, how often and from which IP address + an XML document is accessed. +* An attacker could send mail from inside your network if the URL handler + supports ``smtp://`` URIs. + + +external entity expansion (local file) +-------------------------------------- + +External entities with references to local files are a sub-case of external +entity expansion. It's listed as an extra attack because it deserves extra +attention. Some XML libraries such as lxml disable network access by default +but still allow entity expansion with local file access by default. Local +files are either referenced with a ``file://`` URL or by a file path (either +relative or absolute). + +An attacker may be able to access and download all files that can be read by +the application process. This may include critical configuration files, too. + +:: + + + ]> + + + +DTD retrieval +------------- + +This case is similar to external entity expansion, too. Some XML libraries +like Python's xml.dom.pulldom retrieve document type definitions from remote +or local locations. Several attack scenarios from the external entity case +apply to this issue as well. + +:: + + + + + + text + + + +Python XML Libraries +==================== + +.. csv-table:: vulnerabilities and features + :header: "kind", "sax", "etree", "minidom", "pulldom", "xmlrpc", "lxml", "genshi" + :widths: 24, 7, 8, 8, 7, 8, 8, 8 + :stub-columns: 0 + + "billion laughs", "**True**", "**True**", "**True**", "**True**", "**True**", "False (1)", "False (5)" + "quadratic blowup", "**True**", "**True**", "**True**", "**True**", "**True**", "**True**", "False (5)" + "external entity expansion (remote)", "**True**", "False (3)", "False (4)", "**True**", "false", "False (1)", "False (5)" + "external entity expansion (local file)", "**True**", "False (3)", "False (4)", "**True**", "false", "**True**", "False (5)" + "DTD retrieval", "**True**", "False", "False", "**True**", "false", "False (1)", "False" + "gzip bomb", "False", "False", "False", "False", "**True**", "**partly** (2)", "False" + "xpath support (7)", "False", "False", "False", "False", "False", "**True**", "False" + "xsl(t) support (7)", "False", "False", "False", "False", "False", "**True**", "False" + "xinclude support (7)", "False", "**True** (6)", "False", "False", "False", "**True** (6)", "**True**" + "C library", "expat", "expat", "expat", "expat", "expat", "libxml2", "expat" + +1. Lxml is protected against billion laughs attacks and doesn't do network + lookups by default. +2. libxml2 and lxml are not directly vulnerable to gzip decompression bombs + but they don't protect you against them either. +3. xml.etree doesn't expand entities and raises a ParserError when an entity + occurs. +4. minidom doesn't expand entities and simply returns the unexpanded entity + verbatim. +5. genshi.input of genshi 0.6 doesn't support entity expansion and raises a + ParserError when an entity occurs. +6. Library has (limited) XInclude support but requires an additional step to + process inclusion. +7. These are features but they may introduce exploitable holes, see + `Other things to consider`_ + + +Settings in standard library +---------------------------- + + +xml.sax.handler Features +........................ + +feature_external_ges (http://xml.org/sax/features/external-general-entities) + disables external entity expansion + +feature_external_pes (http://xml.org/sax/features/external-parameter-entities) + the option is ignored and doesn't modify any functionality + +DOM xml.dom.xmlbuilder.Options +.............................. + +external_parameter_entities + ignored + +external_general_entities + ignored + +external_dtd_subset + ignored + +entities + unsure + + +defusedxml +========== + +The `defusedxml package`_ (`defusedxml on PyPI`_) +contains several Python-only workarounds and fixes +for denial of service and other vulnerabilities in Python's XML libraries. +In order to benefit from the protection you just have to import and use the +listed functions / classes from the right defusedxml module instead of the +original module. Merely `defusedxml.xmlrpc`_ is implemented as monkey patch. + +Instead of:: + + >>> from xml.etree.ElementTree import parse + >>> et = parse(xmlfile) + +alter code to:: + + >>> from defusedxml.ElementTree import parse + >>> et = parse(xmlfile) + +Additionally the package has an **untested** function to monkey patch +all stdlib modules with ``defusedxml.defuse_stdlib()``. + +All functions and parser classes accept three additional keyword arguments. +They return either the same objects as the original functions or compatible +subclasses. + +forbid_dtd (default: False) + disallow XML with a ```` processing instruction and raise a + *DTDForbidden* exception when a DTD processing instruction is found. + +forbid_entities (default: True) + disallow XML with ```` declarations inside the DTD and raise an + *EntitiesForbidden* exception when an entity is declared. + +forbid_external (default: True) + disallow any access to remote or local resources in external entities + or DTD and raising an *ExternalReferenceForbidden* exception when a DTD + or entity references an external resource. + + +defusedxml (package) +-------------------- + +DefusedXmlException, DTDForbidden, EntitiesForbidden, +ExternalReferenceForbidden, NotSupportedError + +defuse_stdlib() (*experimental*) + + +defusedxml.cElementTree +----------------------- + +**NOTE** ``defusedxml.cElementTree`` is deprecated and will be removed in a +future release. Import from ``defusedxml.ElementTree`` instead. + +parse(), iterparse(), fromstring(), XMLParser + + +defusedxml.ElementTree +----------------------- + +parse(), iterparse(), fromstring(), XMLParser + + +defusedxml.expatreader +---------------------- + +create_parser(), DefusedExpatParser + + +defusedxml.sax +-------------- + +parse(), parseString(), make_parser() + + +defusedxml.expatbuilder +----------------------- + +parse(), parseString(), DefusedExpatBuilder, DefusedExpatBuilderNS + + +defusedxml.minidom +------------------ + +parse(), parseString() + + +defusedxml.pulldom +------------------ + +parse(), parseString() + + +defusedxml.xmlrpc +----------------- + +The fix is implemented as monkey patch for the stdlib's xmlrpc package (3.x) +or xmlrpclib module (2.x). The function `monkey_patch()` enables the fixes, +`unmonkey_patch()` removes the patch and puts the code in its former state. + +The monkey patch protects against XML related attacks as well as +decompression bombs and excessively large requests or responses. The default +setting is 30 MB for requests, responses and gzip decompression. You can +modify the default by changing the module variable `MAX_DATA`. A value of +`-1` disables the limit. + + +defusedxml.lxml +--------------- + +**DEPRECATED** The module is deprecated and will be removed in a future +release. + +The module acts as an *example* how you could protect code that uses +lxml.etree. It implements a custom Element class that filters out +Entity instances, a custom parser factory and a thread local storage for +parser instances. It also has a check_docinfo() function which inspects +a tree for internal or external DTDs and entity declarations. In order to +check for entities lxml > 3.0 is required. + +parse(), fromstring() +RestrictedElement, GlobalParserTLS, getDefaultParser(), check_docinfo() + + +defusedexpat +============ + +The `defusedexpat package`_ (`defusedexpat on PyPI`_) +comes with binary extensions and a +`modified expat`_ library instead of the standard `expat parser`_. It's +basically a stand-alone version of the patches for Python's standard +library C extensions. + +Modifications in expat +---------------------- + +new definitions:: + + XML_BOMB_PROTECTION + XML_DEFAULT_MAX_ENTITY_INDIRECTIONS + XML_DEFAULT_MAX_ENTITY_EXPANSIONS + XML_DEFAULT_RESET_DTD + +new XML_FeatureEnum members:: + + XML_FEATURE_MAX_ENTITY_INDIRECTIONS + XML_FEATURE_MAX_ENTITY_EXPANSIONS + XML_FEATURE_IGNORE_DTD + +new XML_Error members:: + + XML_ERROR_ENTITY_INDIRECTIONS + XML_ERROR_ENTITY_EXPANSION + +new API functions:: + + int XML_GetFeature(XML_Parser parser, + enum XML_FeatureEnum feature, + long *value); + int XML_SetFeature(XML_Parser parser, + enum XML_FeatureEnum feature, + long value); + int XML_GetFeatureDefault(enum XML_FeatureEnum feature, + long *value); + int XML_SetFeatureDefault(enum XML_FeatureEnum feature, + long value); + +XML_FEATURE_MAX_ENTITY_INDIRECTIONS + Limit the amount of indirections that are allowed to occur during the + expansion of a nested entity. A counter starts when an entity reference + is encountered. It resets after the entity is fully expanded. The limit + protects the parser against exponential entity expansion attacks (aka + billion laughs attack). When the limit is exceeded the parser stops and + fails with `XML_ERROR_ENTITY_INDIRECTIONS`. + A value of 0 disables the protection. + + Supported range + 0 .. UINT_MAX + Default + 40 + +XML_FEATURE_MAX_ENTITY_EXPANSIONS + Limit the total length of all entity expansions throughout the entire + document. The lengths of all entities are accumulated in a parser variable. + The setting protects against quadratic blowup attacks (lots of expansions + of a large entity declaration). When the sum of all entities exceeds + the limit, the parser stops and fails with `XML_ERROR_ENTITY_EXPANSION`. + A value of 0 disables the protection. + + Supported range + 0 .. UINT_MAX + Default + 8 MiB + +XML_FEATURE_RESET_DTD + Reset all DTD information after the block has been parsed. When + the flag is set (default: false) all DTD information after the + endDoctypeDeclHandler has been called. The flag can be set inside the + endDoctypeDeclHandler. Without DTD information any entity reference in + the document body leads to `XML_ERROR_UNDEFINED_ENTITY`. + + Supported range + 0, 1 + Default + 0 + + +How to avoid XML vulnerabilities +================================ + +Best practices +-------------- + +* Don't allow DTDs +* Don't expand entities +* Don't resolve externals +* Limit parse depth +* Limit total input size +* Limit parse time +* Favor a SAX or iterparse-like parser for potential large data +* Validate and properly quote arguments to XSL transformations and + XPath queries +* Don't use XPath expression from untrusted sources +* Don't apply XSL transformations that come untrusted sources + +(based on Brad Hill's `Attacking XML Security`_) + + +Other things to consider +======================== + +XML, XML parsers and processing libraries have more features and possible +issue that could lead to DoS vulnerabilities or security exploits in +applications. I have compiled an incomplete list of theoretical issues that +need further research and more attention. The list is deliberately pessimistic +and a bit paranoid, too. It contains things that might go wrong under daffy +circumstances. + + +attribute blowup / hash collision attack +---------------------------------------- + +XML parsers may use an algorithm with quadratic runtime O(n :sup:`2`) to +handle attributes and namespaces. If it uses hash tables (dictionaries) to +store attributes and namespaces the implementation may be vulnerable to +hash collision attacks, thus reducing the performance to O(n :sup:`2`) again. +In either case an attacker is able to forge a denial of service attack with +an XML document that contains thousands upon thousands of attributes in +a single node. + +I haven't researched yet if expat, pyexpat or libxml2 are vulnerable. + + +decompression bomb +------------------ + +The issue of decompression bombs (aka `ZIP bomb`_) apply to all XML libraries +that can parse compressed XML stream like gzipped HTTP streams or LZMA-ed +files. For an attacker it can reduce the amount of transmitted data by three +magnitudes or more. Gzip is able to compress 1 GiB zeros to roughly 1 MB, +lzma is even better:: + + $ dd if=/dev/zero bs=1M count=1024 | gzip > zeros.gz + $ dd if=/dev/zero bs=1M count=1024 | lzma -z > zeros.xy + $ ls -sh zeros.* + 1020K zeros.gz + 148K zeros.xy + +None of Python's standard XML libraries decompress streams except for +``xmlrpclib``. The module is vulnerable +to decompression bombs. + +lxml can load and process compressed data through libxml2 transparently. +libxml2 can handle even very large blobs of compressed data efficiently +without using too much memory. But it doesn't protect applications from +decompression bombs. A carefully written SAX or iterparse-like approach can +be safe. + + +Processing Instruction +---------------------- + +`PI`_'s like:: + + + +may impose more threats for XML processing. It depends if and how a +processor handles processing instructions. The issue of URL retrieval with +network or local file access apply to processing instructions, too. + + +Other DTD features +------------------ + +`DTD`_ has more features like ````. I haven't researched how +these features may be a security threat. + + +XPath +----- + +XPath statements may introduce DoS vulnerabilities. Code should never execute +queries from untrusted sources. An attacker may also be able to create an XML +document that makes certain XPath queries costly or resource hungry. + + +XPath injection attacks +----------------------- + +XPath injeciton attacks pretty much work like SQL injection attacks. +Arguments to XPath queries must be quoted and validated properly, especially +when they are taken from the user. The page `Avoid the dangers of XPath injection`_ +list some ramifications of XPath injections. + +Python's standard library doesn't have XPath support. Lxml supports +parameterized XPath queries which does proper quoting. You just have to use +its xpath() method correctly:: + + # DON'T + >>> tree.xpath("/tag[@id='%s']" % value) + + # instead do + >>> tree.xpath("/tag[@id=$tagid]", tagid=name) + + +XInclude +-------- + +`XML Inclusion`_ is another way to load and include external files:: + + + + + +This feature should be disabled when XML files from an untrusted source are +processed. Some Python XML libraries and libxml2 support XInclude but don't +have an option to sandbox inclusion and limit it to allowed directories. + + +XMLSchema location +------------------ + +A validating XML parser may download schema files from the information in a +``xsi:schemaLocation`` attribute. + +:: + + + + + +XSL Transformation +------------------ + +You should keep in mind that XSLT is a Turing complete language. Never +process XSLT code from unknown or untrusted source! XSLT processors may +allow you to interact with external resources in ways you can't even imagine. +Some processors even support extensions that allow read/write access to file +system, access to JRE objects or scripting with Jython. + +Example from `Attacking XML Security`_ for Xalan-J:: + + + + + + + + + + + +Related CVEs +============ + +CVE-2013-1664 + Unrestricted entity expansion induces DoS vulnerabilities in Python XML + libraries (XML bomb) + +CVE-2013-1665 + External entity expansion in Python XML libraries inflicts potential + security flaws and DoS vulnerabilities + + +Other languages / frameworks +============================= + +Several other programming languages and frameworks are vulnerable as well. A +couple of them are affected by the fact that libxml2 up to 2.9.0 has no +protection against quadratic blowup attacks. Most of them have potential +dangerous default settings for entity expansion and external entities, too. + +Perl +---- + +Perl's XML::Simple is vulnerable to quadratic entity expansion and external +entity expansion (both local and remote). + + +Ruby +---- + +Ruby's REXML document parser is vulnerable to entity expansion attacks +(both quadratic and exponential) but it doesn't do external entity +expansion by default. In order to counteract entity expansion you have to +disable the feature:: + + REXML::Document.entity_expansion_limit = 0 + +libxml-ruby and hpricot don't expand entities in their default configuration. + + +PHP +--- + +PHP's SimpleXML API is vulnerable to quadratic entity expansion and loads +entities from local and remote resources. The option ``LIBXML_NONET`` disables +network access but still allows local file access. ``LIBXML_NOENT`` seems to +have no effect on entity expansion in PHP 5.4.6. + + +C# / .NET / Mono +---------------- + +Information in `XML DoS and Defenses (MSDN)`_ suggest that .NET is +vulnerable with its default settings. The article contains code snippets +how to create a secure XML reader:: + + XmlReaderSettings settings = new XmlReaderSettings(); + settings.ProhibitDtd = false; + settings.MaxCharactersFromEntities = 1024; + settings.XmlResolver = null; + XmlReader reader = XmlReader.Create(stream, settings); + + +Java +---- + +Untested. The documentation of Xerces and its `Xerces SecurityMananger`_ +sounds like Xerces is also vulnerable to billion laugh attacks with its +default settings. It also does entity resolving when an +``org.xml.sax.EntityResolver`` is configured. I'm not yet sure about the +default setting here. + +Java specialists suggest to have a custom builder factory:: + + DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); + builderFactory.setXIncludeAware(False); + builderFactory.setExpandEntityReferences(False); + builderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, True); + # either + builderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", True); + # or if you need DTDs + builderFactory.setFeature("http://xml.org/sax/features/external-general-entities", False); + builderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", False); + builderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", False); + builderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", False); + + +TODO +==== + +* DOM: Use xml.dom.xmlbuilder options for entity handling +* SAX: take feature_external_ges and feature_external_pes (?) into account +* test experimental monkey patching of stdlib modules +* improve documentation + + +License +======= + +Copyright (c) 2013-2017 by Christian Heimes + +Licensed to PSF under a Contributor Agreement. + +See https://www.python.org/psf/license for licensing details. + + +Acknowledgements +================ + +Brett Cannon (Python Core developer) + review and code cleanup + +Antoine Pitrou (Python Core developer) + code review + +Aaron Patterson, Ben Murphy and Michael Koziarski (Ruby community) + Many thanks to Aaron, Ben and Michael from the Ruby community for their + report and assistance. + +Thierry Carrez (OpenStack) + Many thanks to Thierry for his report to the Python Security Response + Team on behalf of the OpenStack security team. + +Carl Meyer (Django) + Many thanks to Carl for his report to PSRT on behalf of the Django security + team. + +Daniel Veillard (libxml2) + Many thanks to Daniel for his insight and assistance with libxml2. + +semantics GmbH (https://www.semantics.de/) + Many thanks to my employer semantics for letting me work on the issue + during working hours as part of semantics's open source initiative. + + +References +========== + +* `XML DoS and Defenses (MSDN)`_ +* `Billion Laughs`_ on Wikipedia +* `ZIP bomb`_ on Wikipedia +* `Configure SAX parsers for secure processing`_ +* `Testing for XML Injection`_ + +.. _defusedxml package: https://github.com/tiran/defusedxml +.. _defusedxml on PyPI: https://pypi.python.org/pypi/defusedxml +.. _defusedexpat package: https://github.com/tiran/defusedexpat +.. _defusedexpat on PyPI: https://pypi.python.org/pypi/defusedexpat +.. _modified expat: https://github.com/tiran/expat +.. _expat parser: http://expat.sourceforge.net/ +.. _Attacking XML Security: https://www.isecpartners.com/media/12976/iSEC-HILL-Attacking-XML-Security-bh07.pdf +.. _Billion Laughs: https://en.wikipedia.org/wiki/Billion_laughs +.. _XML DoS and Defenses (MSDN): https://msdn.microsoft.com/en-us/magazine/ee335713.aspx +.. _ZIP bomb: https://en.wikipedia.org/wiki/Zip_bomb +.. _DTD: https://en.wikipedia.org/wiki/Document_Type_Definition +.. _PI: https://en.wikipedia.org/wiki/Processing_Instruction +.. _Avoid the dangers of XPath injection: http://www.ibm.com/developerworks/xml/library/x-xpathinjection/index.html +.. _Configure SAX parsers for secure processing: http://www.ibm.com/developerworks/xml/library/x-tipcfsx/index.html +.. _Testing for XML Injection: https://www.owasp.org/index.php/Testing_for_XML_Injection_(OWASP-DV-008) +.. _Xerces SecurityMananger: https://xerces.apache.org/xerces2-j/javadocs/xerces2/org/apache/xerces/util/SecurityManager.html +.. _XML Inclusion: https://www.w3.org/TR/xinclude/#include_element + +Changelog +========= + +defusedxml 0.7.1 +--------------------- + +*Release date: 08-Mar-2021* + +- Fix regression ``defusedxml.ElementTree.ParseError`` (#63) + The ``ParseError`` exception is now the same class object as + ``xml.etree.ElementTree.ParseError`` again. + + +defusedxml 0.7.0 +---------------- + +*Release date: 4-Mar-2021* + +- No changes + + +defusedxml 0.7.0rc2 +------------------- + +*Release date: 12-Jan-2021* + +- Re-add and deprecate ``defusedxml.cElementTree`` +- Use GitHub Actions instead of TravisCI +- Restore ``ElementTree`` attribute of ``xml.etree`` module after patching + +defusedxml 0.7.0rc1 +------------------- + +*Release date: 04-May-2020* + +- Add support for Python 3.9 +- ``defusedxml.cElementTree`` is not available with Python 3.9. +- Python 2 is deprecate. Support for Python 2 will be removed in 0.8.0. + + +defusedxml 0.6.0 +---------------- + +*Release date: 17-Apr-2019* + +- Increase test coverage. +- Add badges to README. + + +defusedxml 0.6.0rc1 +------------------- + +*Release date: 14-Apr-2019* + +- Test on Python 3.7 stable and 3.8-dev +- Drop support for Python 3.4 +- No longer pass *html* argument to XMLParse. It has been deprecated and + ignored for a long time. The DefusedXMLParser still takes a html argument. + A deprecation warning is issued when the argument is False and a TypeError + when it's True. +- defusedxml now fails early when pyexpat stdlib module is not available or + broken. +- defusedxml.ElementTree.__all__ now lists ParseError as public attribute. +- The defusedxml.ElementTree and defusedxml.cElementTree modules had a typo + and used XMLParse instead of XMLParser as an alias for DefusedXMLParser. + Both the old and fixed name are now available. + + +defusedxml 0.5.0 +---------------- + +*Release date: 07-Feb-2017* + +- No changes + + +defusedxml 0.5.0.rc1 +-------------------- + +*Release date: 28-Jan-2017* + +- Add compatibility with Python 3.6 +- Drop support for Python 2.6, 3.1, 3.2, 3.3 +- Fix lxml tests (XMLSyntaxError: Detected an entity reference loop) + + +defusedxml 0.4.1 +---------------- + +*Release date: 28-Mar-2013* + +- Add more demo exploits, e.g. python_external.py and Xalan XSLT demos. +- Improved documentation. + + +defusedxml 0.4 +-------------- + +*Release date: 25-Feb-2013* + +- As per http://seclists.org/oss-sec/2013/q1/340 please REJECT + CVE-2013-0278, CVE-2013-0279 and CVE-2013-0280 and use CVE-2013-1664, + CVE-2013-1665 for OpenStack/etc. +- Add missing parser_list argument to sax.make_parser(). The argument is + ignored, though. (thanks to Florian Apolloner) +- Add demo exploit for external entity attack on Python's SAX parser, XML-RPC + and WebDAV. + + +defusedxml 0.3 +-------------- + +*Release date: 19-Feb-2013* + +- Improve documentation + + +defusedxml 0.2 +-------------- + +*Release date: 15-Feb-2013* + +- Rename ExternalEntitiesForbidden to ExternalReferenceForbidden +- Rename defusedxml.lxml.check_dtd() to check_docinfo() +- Unify argument names in callbacks +- Add arguments and formatted representation to exceptions +- Add forbid_external argument to all functions and classes +- More tests +- LOTS of documentation +- Add example code for other languages (Ruby, Perl, PHP) and parsers (Genshi) +- Add protection against XML and gzip attacks to xmlrpclib + +defusedxml 0.1 +-------------- + +*Release date: 08-Feb-2013* + +- Initial and internal release for PSRT review + + diff --git a/intelliaide/vendor/defusedxml-0.7.1.dist-info/RECORD b/intelliaide/vendor/defusedxml-0.7.1.dist-info/RECORD new file mode 100644 index 0000000..6d32da1 --- /dev/null +++ b/intelliaide/vendor/defusedxml-0.7.1.dist-info/RECORD @@ -0,0 +1,28 @@ +defusedxml-0.7.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +defusedxml-0.7.1.dist-info/LICENSE,sha256=uAzp2oxCofkQeWJ_u-K_JyEK4Qig_-Xwd9WwjgdsJMg,2409 +defusedxml-0.7.1.dist-info/METADATA,sha256=Np0872SHDa-En7pxHLjQWn7-PI2asPdjrcNAef43i7E,32518 +defusedxml-0.7.1.dist-info/RECORD,, +defusedxml-0.7.1.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110 +defusedxml-0.7.1.dist-info/top_level.txt,sha256=QGHa90F50pVKhWSFlERI0jtSKtqDiGyfeZX7dQNZAAw,11 +defusedxml/ElementTree.py,sha256=GLSqpCz58oXGPGyzf_HylsPS9_dcGVP5SN4dK7yvyPw,4640 +defusedxml/__init__.py,sha256=RczeaVJG64p2Fgy1jlCzbuRdchEPnEaCBrxgk8JJ_pM,1444 +defusedxml/__pycache__/ElementTree.cpython-312.pyc,, +defusedxml/__pycache__/__init__.cpython-312.pyc,, +defusedxml/__pycache__/cElementTree.cpython-312.pyc,, +defusedxml/__pycache__/common.cpython-312.pyc,, +defusedxml/__pycache__/expatbuilder.cpython-312.pyc,, +defusedxml/__pycache__/expatreader.cpython-312.pyc,, +defusedxml/__pycache__/lxml.cpython-312.pyc,, +defusedxml/__pycache__/minidom.cpython-312.pyc,, +defusedxml/__pycache__/pulldom.cpython-312.pyc,, +defusedxml/__pycache__/sax.cpython-312.pyc,, +defusedxml/__pycache__/xmlrpc.cpython-312.pyc,, +defusedxml/cElementTree.py,sha256=PpaKMh3rU29sY8amAK4fzHQKl8gcAYD0h1LCoW62Rtk,1449 +defusedxml/common.py,sha256=3d26jNW4fNXzgjWhvUfs83Afiz5EVxFDupQbugkSMZc,4036 +defusedxml/expatbuilder.py,sha256=b4Q05vsBMJ5StkiTFf4my2rGGo1gZyEl_hC5MeFTOAA,3732 +defusedxml/expatreader.py,sha256=KOpSrwkSvj5SGOY9pTXOM26Dnz00rsJt33WueVvzpvc,2196 +defusedxml/lxml.py,sha256=HW-LFKdrfMRzHdi0Vcucq4-n8yz7v_OQwEQWFg1JQYA,4940 +defusedxml/minidom.py,sha256=3QcgygVwJqcWDQ3IZ2iol8zsH4cx3BRX70SPcd0bG2g,1884 +defusedxml/pulldom.py,sha256=DYj2D2lc7xoxZ38gfzujXmdznd8ovzDqGFXqyXbtxjk,1170 +defusedxml/sax.py,sha256=-SF08Msc2mWEYAMw62pJ5FMwWccOctFSnQwDLYLLlVE,1477 +defusedxml/xmlrpc.py,sha256=7rZQey3tqXcc1hrrM3RprOICU6fiFny9B9l4nmTioxA,5364 diff --git a/intelliaide/vendor/defusedxml-0.7.1.dist-info/WHEEL b/intelliaide/vendor/defusedxml-0.7.1.dist-info/WHEEL new file mode 100644 index 0000000..ef99c6c --- /dev/null +++ b/intelliaide/vendor/defusedxml-0.7.1.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.34.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/intelliaide/vendor/defusedxml-0.7.1.dist-info/top_level.txt b/intelliaide/vendor/defusedxml-0.7.1.dist-info/top_level.txt new file mode 100644 index 0000000..36969f2 --- /dev/null +++ b/intelliaide/vendor/defusedxml-0.7.1.dist-info/top_level.txt @@ -0,0 +1 @@ +defusedxml diff --git a/intelliaide/vendor/defusedxml/ElementTree.py b/intelliaide/vendor/defusedxml/ElementTree.py new file mode 100644 index 0000000..5ba765f --- /dev/null +++ b/intelliaide/vendor/defusedxml/ElementTree.py @@ -0,0 +1,154 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See https://www.python.org/psf/license for licensing details. +"""Defused xml.etree.ElementTree facade +""" +from __future__ import print_function, absolute_import + +import sys +import warnings +from xml.etree.ElementTree import ParseError +from xml.etree.ElementTree import TreeBuilder as _TreeBuilder +from xml.etree.ElementTree import parse as _parse +from xml.etree.ElementTree import tostring + +from .common import PY3 + +if PY3: + import importlib +else: + from xml.etree.ElementTree import XMLParser as _XMLParser + from xml.etree.ElementTree import iterparse as _iterparse + + +from .common import ( + DTDForbidden, + EntitiesForbidden, + ExternalReferenceForbidden, + _generate_etree_functions, +) + +__origin__ = "xml.etree.ElementTree" + + +def _get_py3_cls(): + """Python 3.3 hides the pure Python code but defusedxml requires it. + + The code is based on test.support.import_fresh_module(). + """ + pymodname = "xml.etree.ElementTree" + cmodname = "_elementtree" + + pymod = sys.modules.pop(pymodname, None) + cmod = sys.modules.pop(cmodname, None) + + sys.modules[cmodname] = None + try: + pure_pymod = importlib.import_module(pymodname) + finally: + # restore module + sys.modules[pymodname] = pymod + if cmod is not None: + sys.modules[cmodname] = cmod + else: + sys.modules.pop(cmodname, None) + # restore attribute on original package + etree_pkg = sys.modules["xml.etree"] + if pymod is not None: + etree_pkg.ElementTree = pymod + elif hasattr(etree_pkg, "ElementTree"): + del etree_pkg.ElementTree + + _XMLParser = pure_pymod.XMLParser + _iterparse = pure_pymod.iterparse + # patch pure module to use ParseError from C extension + pure_pymod.ParseError = ParseError + + return _XMLParser, _iterparse + + +if PY3: + _XMLParser, _iterparse = _get_py3_cls() + + +_sentinel = object() + + +class DefusedXMLParser(_XMLParser): + def __init__( + self, + html=_sentinel, + target=None, + encoding=None, + forbid_dtd=False, + forbid_entities=True, + forbid_external=True, + ): + # Python 2.x old style class + _XMLParser.__init__(self, target=target, encoding=encoding) + if html is not _sentinel: + # the 'html' argument has been deprecated and ignored in all + # supported versions of Python. Python 3.8 finally removed it. + if html: + raise TypeError("'html=True' is no longer supported.") + else: + warnings.warn( + "'html' keyword argument is no longer supported. Pass " + "in arguments as keyword arguments.", + category=DeprecationWarning, + ) + + self.forbid_dtd = forbid_dtd + self.forbid_entities = forbid_entities + self.forbid_external = forbid_external + if PY3: + parser = self.parser + else: + parser = self._parser + if self.forbid_dtd: + parser.StartDoctypeDeclHandler = self.defused_start_doctype_decl + if self.forbid_entities: + parser.EntityDeclHandler = self.defused_entity_decl + parser.UnparsedEntityDeclHandler = self.defused_unparsed_entity_decl + if self.forbid_external: + parser.ExternalEntityRefHandler = self.defused_external_entity_ref_handler + + def defused_start_doctype_decl(self, name, sysid, pubid, has_internal_subset): + raise DTDForbidden(name, sysid, pubid) + + def defused_entity_decl( + self, name, is_parameter_entity, value, base, sysid, pubid, notation_name + ): + raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name) + + def defused_unparsed_entity_decl(self, name, base, sysid, pubid, notation_name): + # expat 1.2 + raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name) # pragma: no cover + + def defused_external_entity_ref_handler(self, context, base, sysid, pubid): + raise ExternalReferenceForbidden(context, base, sysid, pubid) + + +# aliases +# XMLParse is a typo, keep it for backwards compatibility +XMLTreeBuilder = XMLParse = XMLParser = DefusedXMLParser + +parse, iterparse, fromstring = _generate_etree_functions( + DefusedXMLParser, _TreeBuilder, _parse, _iterparse +) +XML = fromstring + + +__all__ = [ + "ParseError", + "XML", + "XMLParse", + "XMLParser", + "XMLTreeBuilder", + "fromstring", + "iterparse", + "parse", + "tostring", +] diff --git a/intelliaide/vendor/defusedxml/__init__.py b/intelliaide/vendor/defusedxml/__init__.py new file mode 100644 index 0000000..4b5a230 --- /dev/null +++ b/intelliaide/vendor/defusedxml/__init__.py @@ -0,0 +1,67 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See https://www.python.org/psf/license for licensing details. +"""Defuse XML bomb denial of service vulnerabilities +""" +from __future__ import print_function, absolute_import + +import warnings + +from .common import ( + DefusedXmlException, + DTDForbidden, + EntitiesForbidden, + ExternalReferenceForbidden, + NotSupportedError, + _apply_defusing, +) + + +def defuse_stdlib(): + """Monkey patch and defuse all stdlib packages + + :warning: The monkey patch is an EXPERIMETNAL feature. + """ + defused = {} + + with warnings.catch_warnings(): + from . import cElementTree + from . import ElementTree + from . import minidom + from . import pulldom + from . import sax + from . import expatbuilder + from . import expatreader + from . import xmlrpc + + xmlrpc.monkey_patch() + defused[xmlrpc] = None + + defused_mods = [ + cElementTree, + ElementTree, + minidom, + pulldom, + sax, + expatbuilder, + expatreader, + ] + + for defused_mod in defused_mods: + stdlib_mod = _apply_defusing(defused_mod) + defused[defused_mod] = stdlib_mod + + return defused + + +__version__ = "0.7.1" + +__all__ = [ + "DefusedXmlException", + "DTDForbidden", + "EntitiesForbidden", + "ExternalReferenceForbidden", + "NotSupportedError", +] diff --git a/intelliaide/vendor/defusedxml/cElementTree.py b/intelliaide/vendor/defusedxml/cElementTree.py new file mode 100644 index 0000000..84670c6 --- /dev/null +++ b/intelliaide/vendor/defusedxml/cElementTree.py @@ -0,0 +1,62 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See https://www.python.org/psf/license for licensing details. +"""Defused xml.etree.cElementTree +""" +from __future__ import absolute_import + +import warnings + +from .common import _generate_etree_functions + +from xml.etree.cElementTree import TreeBuilder as _TreeBuilder +from xml.etree.cElementTree import parse as _parse +from xml.etree.cElementTree import tostring + +# iterparse from ElementTree! +from xml.etree.ElementTree import iterparse as _iterparse + +# This module is an alias for ElementTree just like xml.etree.cElementTree +from .ElementTree import ( + XML, + XMLParse, + XMLParser, + XMLTreeBuilder, + fromstring, + iterparse, + parse, + tostring, + DefusedXMLParser, + ParseError, +) + +__origin__ = "xml.etree.cElementTree" + + +warnings.warn( + "defusedxml.cElementTree is deprecated, import from defusedxml.ElementTree instead.", + category=DeprecationWarning, + stacklevel=2, +) + +# XMLParse is a typo, keep it for backwards compatibility +XMLTreeBuilder = XMLParse = XMLParser = DefusedXMLParser + +parse, iterparse, fromstring = _generate_etree_functions( + DefusedXMLParser, _TreeBuilder, _parse, _iterparse +) +XML = fromstring + +__all__ = [ + "ParseError", + "XML", + "XMLParse", + "XMLParser", + "XMLTreeBuilder", + "fromstring", + "iterparse", + "parse", + "tostring", +] diff --git a/intelliaide/vendor/defusedxml/common.py b/intelliaide/vendor/defusedxml/common.py new file mode 100644 index 0000000..5ceda1f --- /dev/null +++ b/intelliaide/vendor/defusedxml/common.py @@ -0,0 +1,129 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See https://www.python.org/psf/license for licensing details. +"""Common constants, exceptions and helpe functions +""" +import sys +import xml.parsers.expat + +PY3 = sys.version_info[0] == 3 + +# Fail early when pyexpat is not installed correctly +if not hasattr(xml.parsers.expat, "ParserCreate"): + raise ImportError("pyexpat") # pragma: no cover + + +class DefusedXmlException(ValueError): + """Base exception""" + + def __repr__(self): + return str(self) + + +class DTDForbidden(DefusedXmlException): + """Document type definition is forbidden""" + + def __init__(self, name, sysid, pubid): + super(DTDForbidden, self).__init__() + self.name = name + self.sysid = sysid + self.pubid = pubid + + def __str__(self): + tpl = "DTDForbidden(name='{}', system_id={!r}, public_id={!r})" + return tpl.format(self.name, self.sysid, self.pubid) + + +class EntitiesForbidden(DefusedXmlException): + """Entity definition is forbidden""" + + def __init__(self, name, value, base, sysid, pubid, notation_name): + super(EntitiesForbidden, self).__init__() + self.name = name + self.value = value + self.base = base + self.sysid = sysid + self.pubid = pubid + self.notation_name = notation_name + + def __str__(self): + tpl = "EntitiesForbidden(name='{}', system_id={!r}, public_id={!r})" + return tpl.format(self.name, self.sysid, self.pubid) + + +class ExternalReferenceForbidden(DefusedXmlException): + """Resolving an external reference is forbidden""" + + def __init__(self, context, base, sysid, pubid): + super(ExternalReferenceForbidden, self).__init__() + self.context = context + self.base = base + self.sysid = sysid + self.pubid = pubid + + def __str__(self): + tpl = "ExternalReferenceForbidden(system_id='{}', public_id={})" + return tpl.format(self.sysid, self.pubid) + + +class NotSupportedError(DefusedXmlException): + """The operation is not supported""" + + +def _apply_defusing(defused_mod): + assert defused_mod is sys.modules[defused_mod.__name__] + stdlib_name = defused_mod.__origin__ + __import__(stdlib_name, {}, {}, ["*"]) + stdlib_mod = sys.modules[stdlib_name] + stdlib_names = set(dir(stdlib_mod)) + for name, obj in vars(defused_mod).items(): + if name.startswith("_") or name not in stdlib_names: + continue + setattr(stdlib_mod, name, obj) + return stdlib_mod + + +def _generate_etree_functions(DefusedXMLParser, _TreeBuilder, _parse, _iterparse): + """Factory for functions needed by etree, dependent on whether + cElementTree or ElementTree is used.""" + + def parse(source, parser=None, forbid_dtd=False, forbid_entities=True, forbid_external=True): + if parser is None: + parser = DefusedXMLParser( + target=_TreeBuilder(), + forbid_dtd=forbid_dtd, + forbid_entities=forbid_entities, + forbid_external=forbid_external, + ) + return _parse(source, parser) + + def iterparse( + source, + events=None, + parser=None, + forbid_dtd=False, + forbid_entities=True, + forbid_external=True, + ): + if parser is None: + parser = DefusedXMLParser( + target=_TreeBuilder(), + forbid_dtd=forbid_dtd, + forbid_entities=forbid_entities, + forbid_external=forbid_external, + ) + return _iterparse(source, events, parser) + + def fromstring(text, forbid_dtd=False, forbid_entities=True, forbid_external=True): + parser = DefusedXMLParser( + target=_TreeBuilder(), + forbid_dtd=forbid_dtd, + forbid_entities=forbid_entities, + forbid_external=forbid_external, + ) + parser.feed(text) + return parser.close() + + return parse, iterparse, fromstring diff --git a/intelliaide/vendor/defusedxml/expatbuilder.py b/intelliaide/vendor/defusedxml/expatbuilder.py new file mode 100644 index 0000000..7bfc57e --- /dev/null +++ b/intelliaide/vendor/defusedxml/expatbuilder.py @@ -0,0 +1,107 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See https://www.python.org/psf/license for licensing details. +"""Defused xml.dom.expatbuilder +""" +from __future__ import print_function, absolute_import + +from xml.dom.expatbuilder import ExpatBuilder as _ExpatBuilder +from xml.dom.expatbuilder import Namespaces as _Namespaces + +from .common import DTDForbidden, EntitiesForbidden, ExternalReferenceForbidden + +__origin__ = "xml.dom.expatbuilder" + + +class DefusedExpatBuilder(_ExpatBuilder): + """Defused document builder""" + + def __init__( + self, options=None, forbid_dtd=False, forbid_entities=True, forbid_external=True + ): + _ExpatBuilder.__init__(self, options) + self.forbid_dtd = forbid_dtd + self.forbid_entities = forbid_entities + self.forbid_external = forbid_external + + def defused_start_doctype_decl(self, name, sysid, pubid, has_internal_subset): + raise DTDForbidden(name, sysid, pubid) + + def defused_entity_decl( + self, name, is_parameter_entity, value, base, sysid, pubid, notation_name + ): + raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name) + + def defused_unparsed_entity_decl(self, name, base, sysid, pubid, notation_name): + # expat 1.2 + raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name) # pragma: no cover + + def defused_external_entity_ref_handler(self, context, base, sysid, pubid): + raise ExternalReferenceForbidden(context, base, sysid, pubid) + + def install(self, parser): + _ExpatBuilder.install(self, parser) + + if self.forbid_dtd: + parser.StartDoctypeDeclHandler = self.defused_start_doctype_decl + if self.forbid_entities: + # if self._options.entities: + parser.EntityDeclHandler = self.defused_entity_decl + parser.UnparsedEntityDeclHandler = self.defused_unparsed_entity_decl + if self.forbid_external: + parser.ExternalEntityRefHandler = self.defused_external_entity_ref_handler + + +class DefusedExpatBuilderNS(_Namespaces, DefusedExpatBuilder): + """Defused document builder that supports namespaces.""" + + def install(self, parser): + DefusedExpatBuilder.install(self, parser) + if self._options.namespace_declarations: + parser.StartNamespaceDeclHandler = self.start_namespace_decl_handler + + def reset(self): + DefusedExpatBuilder.reset(self) + self._initNamespaces() + + +def parse(file, namespaces=True, forbid_dtd=False, forbid_entities=True, forbid_external=True): + """Parse a document, returning the resulting Document node. + + 'file' may be either a file name or an open file object. + """ + if namespaces: + build_builder = DefusedExpatBuilderNS + else: + build_builder = DefusedExpatBuilder + builder = build_builder( + forbid_dtd=forbid_dtd, forbid_entities=forbid_entities, forbid_external=forbid_external + ) + + if isinstance(file, str): + fp = open(file, "rb") + try: + result = builder.parseFile(fp) + finally: + fp.close() + else: + result = builder.parseFile(file) + return result + + +def parseString( + string, namespaces=True, forbid_dtd=False, forbid_entities=True, forbid_external=True +): + """Parse a document from a string, returning the resulting + Document node. + """ + if namespaces: + build_builder = DefusedExpatBuilderNS + else: + build_builder = DefusedExpatBuilder + builder = build_builder( + forbid_dtd=forbid_dtd, forbid_entities=forbid_entities, forbid_external=forbid_external + ) + return builder.parseString(string) diff --git a/intelliaide/vendor/defusedxml/expatreader.py b/intelliaide/vendor/defusedxml/expatreader.py new file mode 100644 index 0000000..890e1d1 --- /dev/null +++ b/intelliaide/vendor/defusedxml/expatreader.py @@ -0,0 +1,61 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See https://www.python.org/psf/license for licensing details. +"""Defused xml.sax.expatreader +""" +from __future__ import print_function, absolute_import + +from xml.sax.expatreader import ExpatParser as _ExpatParser + +from .common import DTDForbidden, EntitiesForbidden, ExternalReferenceForbidden + +__origin__ = "xml.sax.expatreader" + + +class DefusedExpatParser(_ExpatParser): + """Defused SAX driver for the pyexpat C module.""" + + def __init__( + self, + namespaceHandling=0, + bufsize=2 ** 16 - 20, + forbid_dtd=False, + forbid_entities=True, + forbid_external=True, + ): + _ExpatParser.__init__(self, namespaceHandling, bufsize) + self.forbid_dtd = forbid_dtd + self.forbid_entities = forbid_entities + self.forbid_external = forbid_external + + def defused_start_doctype_decl(self, name, sysid, pubid, has_internal_subset): + raise DTDForbidden(name, sysid, pubid) + + def defused_entity_decl( + self, name, is_parameter_entity, value, base, sysid, pubid, notation_name + ): + raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name) + + def defused_unparsed_entity_decl(self, name, base, sysid, pubid, notation_name): + # expat 1.2 + raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name) # pragma: no cover + + def defused_external_entity_ref_handler(self, context, base, sysid, pubid): + raise ExternalReferenceForbidden(context, base, sysid, pubid) + + def reset(self): + _ExpatParser.reset(self) + parser = self._parser + if self.forbid_dtd: + parser.StartDoctypeDeclHandler = self.defused_start_doctype_decl + if self.forbid_entities: + parser.EntityDeclHandler = self.defused_entity_decl + parser.UnparsedEntityDeclHandler = self.defused_unparsed_entity_decl + if self.forbid_external: + parser.ExternalEntityRefHandler = self.defused_external_entity_ref_handler + + +def create_parser(*args, **kwargs): + return DefusedExpatParser(*args, **kwargs) diff --git a/intelliaide/vendor/defusedxml/lxml.py b/intelliaide/vendor/defusedxml/lxml.py new file mode 100644 index 0000000..99d5be9 --- /dev/null +++ b/intelliaide/vendor/defusedxml/lxml.py @@ -0,0 +1,153 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See https://www.python.org/psf/license for licensing details. +"""DEPRECATED Example code for lxml.etree protection + +The code has NO protection against decompression bombs. +""" +from __future__ import print_function, absolute_import + +import threading +import warnings + +from lxml import etree as _etree + +from .common import DTDForbidden, EntitiesForbidden, NotSupportedError + +LXML3 = _etree.LXML_VERSION[0] >= 3 + +__origin__ = "lxml.etree" + +tostring = _etree.tostring + + +warnings.warn( + "defusedxml.lxml is no longer supported and will be removed in a future release.", + category=DeprecationWarning, + stacklevel=2, +) + + +class RestrictedElement(_etree.ElementBase): + """A restricted Element class that filters out instances of some classes""" + + __slots__ = () + # blacklist = (etree._Entity, etree._ProcessingInstruction, etree._Comment) + blacklist = _etree._Entity + + def _filter(self, iterator): + blacklist = self.blacklist + for child in iterator: + if isinstance(child, blacklist): + continue + yield child + + def __iter__(self): + iterator = super(RestrictedElement, self).__iter__() + return self._filter(iterator) + + def iterchildren(self, tag=None, reversed=False): + iterator = super(RestrictedElement, self).iterchildren(tag=tag, reversed=reversed) + return self._filter(iterator) + + def iter(self, tag=None, *tags): + iterator = super(RestrictedElement, self).iter(tag=tag, *tags) + return self._filter(iterator) + + def iterdescendants(self, tag=None, *tags): + iterator = super(RestrictedElement, self).iterdescendants(tag=tag, *tags) + return self._filter(iterator) + + def itersiblings(self, tag=None, preceding=False): + iterator = super(RestrictedElement, self).itersiblings(tag=tag, preceding=preceding) + return self._filter(iterator) + + def getchildren(self): + iterator = super(RestrictedElement, self).__iter__() + return list(self._filter(iterator)) + + def getiterator(self, tag=None): + iterator = super(RestrictedElement, self).getiterator(tag) + return self._filter(iterator) + + +class GlobalParserTLS(threading.local): + """Thread local context for custom parser instances""" + + parser_config = { + "resolve_entities": False, + # 'remove_comments': True, + # 'remove_pis': True, + } + + element_class = RestrictedElement + + def createDefaultParser(self): + parser = _etree.XMLParser(**self.parser_config) + element_class = self.element_class + if self.element_class is not None: + lookup = _etree.ElementDefaultClassLookup(element=element_class) + parser.set_element_class_lookup(lookup) + return parser + + def setDefaultParser(self, parser): + self._default_parser = parser + + def getDefaultParser(self): + parser = getattr(self, "_default_parser", None) + if parser is None: + parser = self.createDefaultParser() + self.setDefaultParser(parser) + return parser + + +_parser_tls = GlobalParserTLS() +getDefaultParser = _parser_tls.getDefaultParser + + +def check_docinfo(elementtree, forbid_dtd=False, forbid_entities=True): + """Check docinfo of an element tree for DTD and entity declarations + + The check for entity declarations needs lxml 3 or newer. lxml 2.x does + not support dtd.iterentities(). + """ + docinfo = elementtree.docinfo + if docinfo.doctype: + if forbid_dtd: + raise DTDForbidden(docinfo.doctype, docinfo.system_url, docinfo.public_id) + if forbid_entities and not LXML3: + # lxml < 3 has no iterentities() + raise NotSupportedError("Unable to check for entity declarations " "in lxml 2.x") + + if forbid_entities: + for dtd in docinfo.internalDTD, docinfo.externalDTD: + if dtd is None: + continue + for entity in dtd.iterentities(): + raise EntitiesForbidden(entity.name, entity.content, None, None, None, None) + + +def parse(source, parser=None, base_url=None, forbid_dtd=False, forbid_entities=True): + if parser is None: + parser = getDefaultParser() + elementtree = _etree.parse(source, parser, base_url=base_url) + check_docinfo(elementtree, forbid_dtd, forbid_entities) + return elementtree + + +def fromstring(text, parser=None, base_url=None, forbid_dtd=False, forbid_entities=True): + if parser is None: + parser = getDefaultParser() + rootelement = _etree.fromstring(text, parser, base_url=base_url) + elementtree = rootelement.getroottree() + check_docinfo(elementtree, forbid_dtd, forbid_entities) + return rootelement + + +XML = fromstring + + +def iterparse(*args, **kwargs): + raise NotSupportedError("defused lxml.etree.iterparse not available") diff --git a/intelliaide/vendor/defusedxml/minidom.py b/intelliaide/vendor/defusedxml/minidom.py new file mode 100644 index 0000000..78033b6 --- /dev/null +++ b/intelliaide/vendor/defusedxml/minidom.py @@ -0,0 +1,63 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See https://www.python.org/psf/license for licensing details. +"""Defused xml.dom.minidom +""" +from __future__ import print_function, absolute_import + +from xml.dom.minidom import _do_pulldom_parse +from . import expatbuilder as _expatbuilder +from . import pulldom as _pulldom + +__origin__ = "xml.dom.minidom" + + +def parse( + file, parser=None, bufsize=None, forbid_dtd=False, forbid_entities=True, forbid_external=True +): + """Parse a file into a DOM by filename or file object.""" + if parser is None and not bufsize: + return _expatbuilder.parse( + file, + forbid_dtd=forbid_dtd, + forbid_entities=forbid_entities, + forbid_external=forbid_external, + ) + else: + return _do_pulldom_parse( + _pulldom.parse, + (file,), + { + "parser": parser, + "bufsize": bufsize, + "forbid_dtd": forbid_dtd, + "forbid_entities": forbid_entities, + "forbid_external": forbid_external, + }, + ) + + +def parseString( + string, parser=None, forbid_dtd=False, forbid_entities=True, forbid_external=True +): + """Parse a file into a DOM from a string.""" + if parser is None: + return _expatbuilder.parseString( + string, + forbid_dtd=forbid_dtd, + forbid_entities=forbid_entities, + forbid_external=forbid_external, + ) + else: + return _do_pulldom_parse( + _pulldom.parseString, + (string,), + { + "parser": parser, + "forbid_dtd": forbid_dtd, + "forbid_entities": forbid_entities, + "forbid_external": forbid_external, + }, + ) diff --git a/intelliaide/vendor/defusedxml/pulldom.py b/intelliaide/vendor/defusedxml/pulldom.py new file mode 100644 index 0000000..e3b10a4 --- /dev/null +++ b/intelliaide/vendor/defusedxml/pulldom.py @@ -0,0 +1,41 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See https://www.python.org/psf/license for licensing details. +"""Defused xml.dom.pulldom +""" +from __future__ import print_function, absolute_import + +from xml.dom.pulldom import parse as _parse +from xml.dom.pulldom import parseString as _parseString +from .sax import make_parser + +__origin__ = "xml.dom.pulldom" + + +def parse( + stream_or_string, + parser=None, + bufsize=None, + forbid_dtd=False, + forbid_entities=True, + forbid_external=True, +): + if parser is None: + parser = make_parser() + parser.forbid_dtd = forbid_dtd + parser.forbid_entities = forbid_entities + parser.forbid_external = forbid_external + return _parse(stream_or_string, parser, bufsize) + + +def parseString( + string, parser=None, forbid_dtd=False, forbid_entities=True, forbid_external=True +): + if parser is None: + parser = make_parser() + parser.forbid_dtd = forbid_dtd + parser.forbid_entities = forbid_entities + parser.forbid_external = forbid_external + return _parseString(string, parser) diff --git a/intelliaide/vendor/defusedxml/sax.py b/intelliaide/vendor/defusedxml/sax.py new file mode 100644 index 0000000..b2786f7 --- /dev/null +++ b/intelliaide/vendor/defusedxml/sax.py @@ -0,0 +1,60 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See https://www.python.org/psf/license for licensing details. +"""Defused xml.sax +""" +from __future__ import print_function, absolute_import + +from xml.sax import InputSource as _InputSource +from xml.sax import ErrorHandler as _ErrorHandler + +from . import expatreader + +__origin__ = "xml.sax" + + +def parse( + source, + handler, + errorHandler=_ErrorHandler(), + forbid_dtd=False, + forbid_entities=True, + forbid_external=True, +): + parser = make_parser() + parser.setContentHandler(handler) + parser.setErrorHandler(errorHandler) + parser.forbid_dtd = forbid_dtd + parser.forbid_entities = forbid_entities + parser.forbid_external = forbid_external + parser.parse(source) + + +def parseString( + string, + handler, + errorHandler=_ErrorHandler(), + forbid_dtd=False, + forbid_entities=True, + forbid_external=True, +): + from io import BytesIO + + if errorHandler is None: + errorHandler = _ErrorHandler() + parser = make_parser() + parser.setContentHandler(handler) + parser.setErrorHandler(errorHandler) + parser.forbid_dtd = forbid_dtd + parser.forbid_entities = forbid_entities + parser.forbid_external = forbid_external + + inpsrc = _InputSource() + inpsrc.setByteStream(BytesIO(string)) + parser.parse(inpsrc) + + +def make_parser(parser_list=[]): + return expatreader.create_parser() diff --git a/intelliaide/vendor/defusedxml/xmlrpc.py b/intelliaide/vendor/defusedxml/xmlrpc.py new file mode 100644 index 0000000..fbc674d --- /dev/null +++ b/intelliaide/vendor/defusedxml/xmlrpc.py @@ -0,0 +1,153 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See https://www.python.org/psf/license for licensing details. +"""Defused xmlrpclib + +Also defuses gzip bomb +""" +from __future__ import print_function, absolute_import + +import io + +from .common import DTDForbidden, EntitiesForbidden, ExternalReferenceForbidden, PY3 + +if PY3: + __origin__ = "xmlrpc.client" + from xmlrpc.client import ExpatParser + from xmlrpc import client as xmlrpc_client + from xmlrpc import server as xmlrpc_server + from xmlrpc.client import gzip_decode as _orig_gzip_decode + from xmlrpc.client import GzipDecodedResponse as _OrigGzipDecodedResponse +else: + __origin__ = "xmlrpclib" + from xmlrpclib import ExpatParser + import xmlrpclib as xmlrpc_client + + xmlrpc_server = None + from xmlrpclib import gzip_decode as _orig_gzip_decode + from xmlrpclib import GzipDecodedResponse as _OrigGzipDecodedResponse + +try: + import gzip +except ImportError: # pragma: no cover + gzip = None + + +# Limit maximum request size to prevent resource exhaustion DoS +# Also used to limit maximum amount of gzip decoded data in order to prevent +# decompression bombs +# A value of -1 or smaller disables the limit +MAX_DATA = 30 * 1024 * 1024 # 30 MB + + +def defused_gzip_decode(data, limit=None): + """gzip encoded data -> unencoded data + + Decode data using the gzip content encoding as described in RFC 1952 + """ + if not gzip: # pragma: no cover + raise NotImplementedError + if limit is None: + limit = MAX_DATA + f = io.BytesIO(data) + gzf = gzip.GzipFile(mode="rb", fileobj=f) + try: + if limit < 0: # no limit + decoded = gzf.read() + else: + decoded = gzf.read(limit + 1) + except IOError: # pragma: no cover + raise ValueError("invalid data") + f.close() + gzf.close() + if limit >= 0 and len(decoded) > limit: + raise ValueError("max gzipped payload length exceeded") + return decoded + + +class DefusedGzipDecodedResponse(gzip.GzipFile if gzip else object): + """a file-like object to decode a response encoded with the gzip + method, as described in RFC 1952. + """ + + def __init__(self, response, limit=None): + # response doesn't support tell() and read(), required by + # GzipFile + if not gzip: # pragma: no cover + raise NotImplementedError + self.limit = limit = limit if limit is not None else MAX_DATA + if limit < 0: # no limit + data = response.read() + self.readlength = None + else: + data = response.read(limit + 1) + self.readlength = 0 + if limit >= 0 and len(data) > limit: + raise ValueError("max payload length exceeded") + self.stringio = io.BytesIO(data) + gzip.GzipFile.__init__(self, mode="rb", fileobj=self.stringio) + + def read(self, n): + if self.limit >= 0: + left = self.limit - self.readlength + n = min(n, left + 1) + data = gzip.GzipFile.read(self, n) + self.readlength += len(data) + if self.readlength > self.limit: + raise ValueError("max payload length exceeded") + return data + else: + return gzip.GzipFile.read(self, n) + + def close(self): + gzip.GzipFile.close(self) + self.stringio.close() + + +class DefusedExpatParser(ExpatParser): + def __init__(self, target, forbid_dtd=False, forbid_entities=True, forbid_external=True): + ExpatParser.__init__(self, target) + self.forbid_dtd = forbid_dtd + self.forbid_entities = forbid_entities + self.forbid_external = forbid_external + parser = self._parser + if self.forbid_dtd: + parser.StartDoctypeDeclHandler = self.defused_start_doctype_decl + if self.forbid_entities: + parser.EntityDeclHandler = self.defused_entity_decl + parser.UnparsedEntityDeclHandler = self.defused_unparsed_entity_decl + if self.forbid_external: + parser.ExternalEntityRefHandler = self.defused_external_entity_ref_handler + + def defused_start_doctype_decl(self, name, sysid, pubid, has_internal_subset): + raise DTDForbidden(name, sysid, pubid) + + def defused_entity_decl( + self, name, is_parameter_entity, value, base, sysid, pubid, notation_name + ): + raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name) + + def defused_unparsed_entity_decl(self, name, base, sysid, pubid, notation_name): + # expat 1.2 + raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name) # pragma: no cover + + def defused_external_entity_ref_handler(self, context, base, sysid, pubid): + raise ExternalReferenceForbidden(context, base, sysid, pubid) + + +def monkey_patch(): + xmlrpc_client.FastParser = DefusedExpatParser + xmlrpc_client.GzipDecodedResponse = DefusedGzipDecodedResponse + xmlrpc_client.gzip_decode = defused_gzip_decode + if xmlrpc_server: + xmlrpc_server.gzip_decode = defused_gzip_decode + + +def unmonkey_patch(): + xmlrpc_client.FastParser = None + xmlrpc_client.GzipDecodedResponse = _OrigGzipDecodedResponse + xmlrpc_client.gzip_decode = _orig_gzip_decode + if xmlrpc_server: + xmlrpc_server.gzip_decode = _orig_gzip_decode diff --git a/intelliaide/vendor/docx/__init__.py b/intelliaide/vendor/docx/__init__.py new file mode 100644 index 0000000..fd06c84 --- /dev/null +++ b/intelliaide/vendor/docx/__init__.py @@ -0,0 +1,65 @@ +"""Initialize `docx` package. + +Export the `Document` constructor function and establish the mapping of part-type to +the part-classe that implements that type. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Type + +from docx.api import Document + +if TYPE_CHECKING: + from docx.opc.part import Part + +__version__ = "1.2.0" + + +__all__ = ["Document"] + + +# -- register custom Part classes with opc package reader -- + +from docx.opc.constants import CONTENT_TYPE as CT +from docx.opc.constants import RELATIONSHIP_TYPE as RT +from docx.opc.part import PartFactory +from docx.opc.parts.coreprops import CorePropertiesPart +from docx.parts.comments import CommentsPart +from docx.parts.document import DocumentPart +from docx.parts.hdrftr import FooterPart, HeaderPart +from docx.parts.image import ImagePart +from docx.parts.numbering import NumberingPart +from docx.parts.settings import SettingsPart +from docx.parts.styles import StylesPart + + +def part_class_selector(content_type: str, reltype: str) -> Type[Part] | None: + if reltype == RT.IMAGE: + return ImagePart + return None + + +PartFactory.part_class_selector = part_class_selector +PartFactory.part_type_for[CT.OPC_CORE_PROPERTIES] = CorePropertiesPart +PartFactory.part_type_for[CT.WML_COMMENTS] = CommentsPart +PartFactory.part_type_for[CT.WML_DOCUMENT_MAIN] = DocumentPart +PartFactory.part_type_for[CT.WML_FOOTER] = FooterPart +PartFactory.part_type_for[CT.WML_HEADER] = HeaderPart +PartFactory.part_type_for[CT.WML_NUMBERING] = NumberingPart +PartFactory.part_type_for[CT.WML_SETTINGS] = SettingsPart +PartFactory.part_type_for[CT.WML_STYLES] = StylesPart + +del ( + CT, + CorePropertiesPart, + CommentsPart, + DocumentPart, + FooterPart, + HeaderPart, + NumberingPart, + PartFactory, + SettingsPart, + StylesPart, + part_class_selector, +) diff --git a/intelliaide/vendor/docx/api.py b/intelliaide/vendor/docx/api.py new file mode 100644 index 0000000..aea8764 --- /dev/null +++ b/intelliaide/vendor/docx/api.py @@ -0,0 +1,37 @@ +"""Directly exposed API functions and classes, :func:`Document` for now. + +Provides a syntactically more convenient API for interacting with the OpcPackage graph. +""" + +from __future__ import annotations + +import os +from typing import IO, TYPE_CHECKING, cast + +from docx.opc.constants import CONTENT_TYPE as CT +from docx.package import Package + +if TYPE_CHECKING: + from docx.document import Document as DocumentObject + from docx.parts.document import DocumentPart + + +def Document(docx: str | IO[bytes] | None = None) -> DocumentObject: + """Return a |Document| object loaded from `docx`, where `docx` can be either a path + to a ``.docx`` file (a string) or a file-like object. + + If `docx` is missing or ``None``, the built-in default document "template" is + loaded. + """ + docx = _default_docx_path() if docx is None else docx + document_part = cast("DocumentPart", Package.open(docx).main_document_part) + if document_part.content_type != CT.WML_DOCUMENT_MAIN: + tmpl = "file '%s' is not a Word file, content type is '%s'" + raise ValueError(tmpl % (docx, document_part.content_type)) + return document_part.document + + +def _default_docx_path(): + """Return the path to the built-in default .docx package.""" + _thisdir = os.path.split(__file__)[0] + return os.path.join(_thisdir, "templates", "default.docx") diff --git a/intelliaide/vendor/docx/blkcntnr.py b/intelliaide/vendor/docx/blkcntnr.py new file mode 100644 index 0000000..82c7ef7 --- /dev/null +++ b/intelliaide/vendor/docx/blkcntnr.py @@ -0,0 +1,101 @@ +# pyright: reportImportCycles=false + +"""Block item container, used by body, cell, header, etc. + +Block level items are things like paragraph and table, although there are a few other +specialized ones like structured document tags. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Iterator + +from typing_extensions import TypeAlias + +from docx.oxml.table import CT_Tbl +from docx.oxml.text.paragraph import CT_P +from docx.shared import StoryChild +from docx.text.paragraph import Paragraph + +if TYPE_CHECKING: + import docx.types as t + from docx.oxml.comments import CT_Comment + from docx.oxml.document import CT_Body + from docx.oxml.section import CT_HdrFtr + from docx.oxml.table import CT_Tc + from docx.shared import Length + from docx.styles.style import ParagraphStyle + from docx.table import Table + +BlockItemElement: TypeAlias = "CT_Body | CT_Comment | CT_HdrFtr | CT_Tc" + + +class BlockItemContainer(StoryChild): + """Base class for proxy objects that can contain block items. + + These containers include _Body, _Cell, header, footer, footnote, endnote, comment, + and text box objects. Provides the shared functionality to add a block item like a + paragraph or table. + """ + + def __init__(self, element: BlockItemElement, parent: t.ProvidesStoryPart): + super(BlockItemContainer, self).__init__(parent) + self._element = element + + def add_paragraph(self, text: str = "", style: str | ParagraphStyle | None = None) -> Paragraph: + """Return paragraph newly added to the end of the content in this container. + + The paragraph has `text` in a single run if present, and is given paragraph + style `style`. + + If `style` is |None|, no paragraph style is applied, which has the same effect + as applying the 'Normal' style. + """ + paragraph = self._add_paragraph() + if text: + paragraph.add_run(text) + if style is not None: + paragraph.style = style + return paragraph + + def add_table(self, rows: int, cols: int, width: Length) -> Table: + """Return table of `width` having `rows` rows and `cols` columns. + + The table is appended appended at the end of the content in this container. + + `width` is evenly distributed between the table columns. + """ + from docx.table import Table + + tbl = CT_Tbl.new_tbl(rows, cols, width) + self._element._insert_tbl(tbl) # pyright: ignore[reportPrivateUsage] + return Table(tbl, self) + + def iter_inner_content(self) -> Iterator[Paragraph | Table]: + """Generate each `Paragraph` or `Table` in this container in document order.""" + from docx.table import Table + + for element in self._element.inner_content_elements: + yield (Paragraph(element, self) if isinstance(element, CT_P) else Table(element, self)) + + @property + def paragraphs(self): + """A list containing the paragraphs in this container, in document order. + + Read-only. + """ + return [Paragraph(p, self) for p in self._element.p_lst] + + @property + def tables(self): + """A list containing the tables in this container, in document order. + + Read-only. + """ + from docx.table import Table + + return [Table(tbl, self) for tbl in self._element.tbl_lst] + + def _add_paragraph(self): + """Return paragraph newly added to the end of the content in this container.""" + return Paragraph(self._element.add_p(), self) diff --git a/intelliaide/vendor/docx/comments.py b/intelliaide/vendor/docx/comments.py new file mode 100644 index 0000000..8ea1952 --- /dev/null +++ b/intelliaide/vendor/docx/comments.py @@ -0,0 +1,163 @@ +"""Collection providing access to comments added to this document.""" + +from __future__ import annotations + +import datetime as dt +from typing import TYPE_CHECKING, Iterator + +from docx.blkcntnr import BlockItemContainer + +if TYPE_CHECKING: + from docx.oxml.comments import CT_Comment, CT_Comments + from docx.parts.comments import CommentsPart + from docx.styles.style import ParagraphStyle + from docx.text.paragraph import Paragraph + + +class Comments: + """Collection containing the comments added to this document.""" + + def __init__(self, comments_elm: CT_Comments, comments_part: CommentsPart): + self._comments_elm = comments_elm + self._comments_part = comments_part + + def __iter__(self) -> Iterator[Comment]: + """Iterator over the comments in this collection.""" + return ( + Comment(comment_elm, self._comments_part) + for comment_elm in self._comments_elm.comment_lst + ) + + def __len__(self) -> int: + """The number of comments in this collection.""" + return len(self._comments_elm.comment_lst) + + def add_comment(self, text: str = "", author: str = "", initials: str | None = "") -> Comment: + """Add a new comment to the document and return it. + + The comment is added to the end of the comments collection and is assigned a unique + comment-id. + + If `text` is provided, it is added to the comment. This option provides for the common + case where a comment contains a modest passage of plain text. Multiple paragraphs can be + added using the `text` argument by separating their text with newlines (`"\\\\n"`). + Between newlines, text is interpreted as it is in `Document.add_paragraph(text=...)`. + + The default is to place a single empty paragraph in the comment, which is the same + behavior as the Word UI when you add a comment. New runs can be added to the first + paragraph in the empty comment with `comments.paragraphs[0].add_run()` to adding more + complex text with emphasis or images. Additional paragraphs can be added using + `.add_paragraph()`. + + `author` is a required attribute, set to the empty string by default. + + `initials` is an optional attribute, set to the empty string by default. Passing |None| + for the `initials` parameter causes that attribute to be omitted from the XML. + """ + comment_elm = self._comments_elm.add_comment() + comment_elm.author = author + comment_elm.initials = initials + comment_elm.date = dt.datetime.now(dt.timezone.utc) + comment = Comment(comment_elm, self._comments_part) + + if text == "": + return comment + + para_text_iter = iter(text.split("\n")) + + first_para_text = next(para_text_iter) + first_para = comment.paragraphs[0] + first_para.add_run(first_para_text) + + for s in para_text_iter: + comment.add_paragraph(text=s) + + return comment + + def get(self, comment_id: int) -> Comment | None: + """Return the comment identified by `comment_id`, or |None| if not found.""" + comment_elm = self._comments_elm.get_comment_by_id(comment_id) + return Comment(comment_elm, self._comments_part) if comment_elm is not None else None + + +class Comment(BlockItemContainer): + """Proxy for a single comment in the document. + + Provides methods to access comment metadata such as author, initials, and date. + + A comment is also a block-item container, similar to a table cell, so it can contain both + paragraphs and tables and its paragraphs can contain rich text, hyperlinks and images, + although the common case is that a comment contains a single paragraph of plain text like a + sentence or phrase. + + Note that certain content like tables may not be displayed in the Word comment sidebar due to + space limitations. Such "over-sized" content can still be viewed in the review pane. + """ + + def __init__(self, comment_elm: CT_Comment, comments_part: CommentsPart): + super().__init__(comment_elm, comments_part) + self._comment_elm = comment_elm + + def add_paragraph(self, text: str = "", style: str | ParagraphStyle | None = None) -> Paragraph: + """Return paragraph newly added to the end of the content in this container. + + The paragraph has `text` in a single run if present, and is given paragraph style `style`. + When `style` is |None| or ommitted, the "CommentText" paragraph style is applied, which is + the default style for comments. + """ + paragraph = super().add_paragraph(text, style) + + # -- have to assign style directly to element because `paragraph.style` raises when + # -- a style is not present in the styles part + if style is None: + paragraph._p.style = "CommentText" # pyright: ignore[reportPrivateUsage] + + return paragraph + + @property + def author(self) -> str: + """Read/write. The recorded author of this comment. + + This field is required but can be set to the empty string. + """ + return self._comment_elm.author + + @author.setter + def author(self, value: str): + self._comment_elm.author = value + + @property + def comment_id(self) -> int: + """The unique identifier of this comment.""" + return self._comment_elm.id + + @property + def initials(self) -> str | None: + """Read/write. The recorded initials of the comment author. + + This attribute is optional in the XML, returns |None| if not set. Assigning |None| removes + any existing initials from the XML. + """ + return self._comment_elm.initials + + @initials.setter + def initials(self, value: str | None): + self._comment_elm.initials = value + + @property + def text(self) -> str: + """The text content of this comment as a string. + + Only content in paragraphs is included and of course all emphasis and styling is stripped. + + Paragraph boundaries are indicated with a newline (`"\\\\n"`) + """ + return "\n".join(p.text for p in self.paragraphs) + + @property + def timestamp(self) -> dt.datetime | None: + """The date and time this comment was authored. + + This attribute is optional in the XML, returns |None| if not set. + """ + return self._comment_elm.date diff --git a/intelliaide/vendor/docx/dml/__init__.py b/intelliaide/vendor/docx/dml/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/intelliaide/vendor/docx/dml/color.py b/intelliaide/vendor/docx/dml/color.py new file mode 100644 index 0000000..a8322d2 --- /dev/null +++ b/intelliaide/vendor/docx/dml/color.py @@ -0,0 +1,112 @@ +"""DrawingML objects related to color, ColorFormat being the most prominent.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, cast + +from typing_extensions import TypeAlias + +from docx.enum.dml import MSO_COLOR_TYPE +from docx.oxml.simpletypes import ST_HexColorAuto +from docx.shared import ElementProxy, RGBColor + +if TYPE_CHECKING: + from docx.enum.dml import MSO_THEME_COLOR + from docx.oxml.text.font import CT_Color + from docx.oxml.text.run import CT_R + +# -- other element types can be a parent of an `w:rPr` element, but for now only `w:r` is -- +RPrParent: TypeAlias = "CT_R" + + +class ColorFormat(ElementProxy): + """Provides access to color settings like RGB color, theme color, and luminance adjustments.""" + + def __init__(self, rPr_parent: RPrParent): + super(ColorFormat, self).__init__(rPr_parent) + self._element = rPr_parent + + @property + def rgb(self) -> RGBColor | None: + """An |RGBColor| value or |None| if no RGB color is specified. + + When :attr:`type` is `MSO_COLOR_TYPE.RGB`, the value of this property will always be an + |RGBColor| value. It may also be an |RGBColor| value if :attr:`type` is + `MSO_COLOR_TYPE.THEME`, as Word writes the current value of a theme color when one is + assigned. In that case, the RGB value should be interpreted as no more than a good guess + however, as the theme color takes precedence at rendering time. Its value is |None| + whenever :attr:`type` is either |None| or `MSO_COLOR_TYPE.AUTO`. + + Assigning an |RGBColor| value causes :attr:`type` to become `MSO_COLOR_TYPE.RGB` and any + theme color is removed. Assigning |None| causes any color to be removed such that the + effective color is inherited from the style hierarchy. + """ + color = self._color + if color is None: + return None + if color.val == ST_HexColorAuto.AUTO: + return None + return cast(RGBColor, color.val) + + @rgb.setter + def rgb(self, value: RGBColor | None): + if value is None and self._color is None: + return + rPr = self._element.get_or_add_rPr() + rPr._remove_color() # pyright: ignore[reportPrivateUsage] + if value is not None: + rPr.get_or_add_color().val = value + + @property + def theme_color(self) -> MSO_THEME_COLOR | None: + """Member of :ref:`MsoThemeColorIndex` or |None| if no theme color is specified. + + When :attr:`type` is `MSO_COLOR_TYPE.THEME`, the value of this property will always be a + member of :ref:`MsoThemeColorIndex`. When :attr:`type` has any other value, the value of + this property is |None|. + + Assigning a member of :ref:`MsoThemeColorIndex` causes :attr:`type` to become + `MSO_COLOR_TYPE.THEME`. Any existing RGB value is retained but ignored by Word. Assigning + |None| causes any color specification to be removed such that the effective color is + inherited from the style hierarchy. + """ + color = self._color + if color is None: + return None + return color.themeColor + + @theme_color.setter + def theme_color(self, value: MSO_THEME_COLOR | None): + if value is None: + if self._color is not None and self._element.rPr is not None: + self._element.rPr._remove_color() # pyright: ignore[reportPrivateUsage] + return + self._element.get_or_add_rPr().get_or_add_color().themeColor = value + + @property + def type(self) -> MSO_COLOR_TYPE | None: + """Read-only. + + A member of :ref:`MsoColorType`, one of RGB, THEME, or AUTO, corresponding to the way this + color is defined. Its value is |None| if no color is applied at this level, which causes + the effective color to be inherited from the style hierarchy. + """ + color = self._color + if color is None: + return None + if color.themeColor is not None: + return MSO_COLOR_TYPE.THEME + if color.val == ST_HexColorAuto.AUTO: + return MSO_COLOR_TYPE.AUTO + return MSO_COLOR_TYPE.RGB + + @property + def _color(self) -> CT_Color | None: + """Return `w:rPr/w:color` or |None| if not present. + + Helper to factor out repetitive element access. + """ + rPr = self._element.rPr + if rPr is None: + return None + return rPr.color diff --git a/intelliaide/vendor/docx/document.py b/intelliaide/vendor/docx/document.py new file mode 100644 index 0000000..73757b4 --- /dev/null +++ b/intelliaide/vendor/docx/document.py @@ -0,0 +1,265 @@ +# pyright: reportImportCycles=false +# pyright: reportPrivateUsage=false + +"""|Document| and closely related objects.""" + +from __future__ import annotations + +from typing import IO, TYPE_CHECKING, Iterator, List, Sequence + +from docx.blkcntnr import BlockItemContainer +from docx.enum.section import WD_SECTION +from docx.enum.text import WD_BREAK +from docx.section import Section, Sections +from docx.shared import ElementProxy, Emu, Inches, Length +from docx.text.run import Run + +if TYPE_CHECKING: + import docx.types as t + from docx.comments import Comment, Comments + from docx.oxml.document import CT_Body, CT_Document + from docx.parts.document import DocumentPart + from docx.settings import Settings + from docx.styles.style import ParagraphStyle, _TableStyle + from docx.table import Table + from docx.text.paragraph import Paragraph + + +class Document(ElementProxy): + """WordprocessingML (WML) document. + + Not intended to be constructed directly. Use :func:`docx.Document` to open or create + a document. + """ + + def __init__(self, element: CT_Document, part: DocumentPart): + super(Document, self).__init__(element) + self._element = element + self._part = part + self.__body = None + + def add_comment( + self, + runs: Run | Sequence[Run], + text: str | None = "", + author: str = "", + initials: str | None = "", + ) -> Comment: + """Add a comment to the document, anchored to the specified runs. + + `runs` can be a single `Run` object or a non-empty sequence of `Run` objects. Only the + first and last run of a sequence are used, it's just more convenient to pass a whole + sequence when that's what you have handy, like `paragraph.runs` for example. When `runs` + contains a single `Run` object, that run serves as both the first and last run. + + A comment can be anchored only on an even run boundary, meaning the text the comment + "references" must be a non-zero integer number of consecutive runs. The runs need not be + _contiguous_ per se, like the first can be in one paragraph and the last in the next + paragraph, but all runs between the first and the last will be included in the reference. + + The comment reference range is delimited by placing a `w:commentRangeStart` element before + the first run and a `w:commentRangeEnd` element after the last run. This is why only the + first and last run are required and why a single run can serve as both first and last. + Word works out which text to highlight in the UI based on these range markers. + + `text` allows the contents of a simple comment to be provided in the call, providing for + the common case where a comment is a single phrase or sentence without special formatting + such as bold or italics. More complex comments can be added using the returned `Comment` + object in much the same way as a `Document` or (table) `Cell` object, using methods like + `.add_paragraph()`, .add_run()`, etc. + + The `author` and `initials` parameters allow that metadata to be set for the comment. + `author` is a required attribute on a comment and is the empty string by default. + `initials` is optional on a comment and may be omitted by passing |None|, but Word adds an + `initials` attribute by default and we follow that convention by using the empty string + when no `initials` argument is provided. + """ + # -- normalize `runs` to a sequence of runs -- + runs = [runs] if isinstance(runs, Run) else runs + first_run = runs[0] + last_run = runs[-1] + + # -- Note that comments can only appear in the document part -- + comment = self.comments.add_comment(text=text, author=author, initials=initials) + + # -- let the first run orchestrate placement of the comment range start and end -- + first_run.mark_comment_range(last_run, comment.comment_id) + + return comment + + def add_heading(self, text: str = "", level: int = 1): + """Return a heading paragraph newly added to the end of the document. + + The heading paragraph will contain `text` and have its paragraph style + determined by `level`. If `level` is 0, the style is set to `Title`. If `level` + is 1 (or omitted), `Heading 1` is used. Otherwise the style is set to `Heading + {level}`. Raises |ValueError| if `level` is outside the range 0-9. + """ + if not 0 <= level <= 9: + raise ValueError("level must be in range 0-9, got %d" % level) + style = "Title" if level == 0 else "Heading %d" % level + return self.add_paragraph(text, style) + + def add_page_break(self): + """Return newly |Paragraph| object containing only a page break.""" + paragraph = self.add_paragraph() + paragraph.add_run().add_break(WD_BREAK.PAGE) + return paragraph + + def add_paragraph(self, text: str = "", style: str | ParagraphStyle | None = None) -> Paragraph: + """Return paragraph newly added to the end of the document. + + The paragraph is populated with `text` and having paragraph style `style`. + + `text` can contain tab (``\\t``) characters, which are converted to the + appropriate XML form for a tab. `text` can also include newline (``\\n``) or + carriage return (``\\r``) characters, each of which is converted to a line + break. + """ + return self._body.add_paragraph(text, style) + + def add_picture( + self, + image_path_or_stream: str | IO[bytes], + width: int | Length | None = None, + height: int | Length | None = None, + ): + """Return new picture shape added in its own paragraph at end of the document. + + The picture contains the image at `image_path_or_stream`, scaled based on + `width` and `height`. If neither width nor height is specified, the picture + appears at its native size. If only one is specified, it is used to compute a + scaling factor that is then applied to the unspecified dimension, preserving the + aspect ratio of the image. The native size of the picture is calculated using + the dots-per-inch (dpi) value specified in the image file, defaulting to 72 dpi + if no value is specified, as is often the case. + """ + run = self.add_paragraph().add_run() + return run.add_picture(image_path_or_stream, width, height) + + def add_section(self, start_type: WD_SECTION = WD_SECTION.NEW_PAGE): + """Return a |Section| object newly added at the end of the document. + + The optional `start_type` argument must be a member of the :ref:`WdSectionStart` + enumeration, and defaults to ``WD_SECTION.NEW_PAGE`` if not provided. + """ + new_sectPr = self._element.body.add_section_break() + new_sectPr.start_type = start_type + return Section(new_sectPr, self._part) + + def add_table(self, rows: int, cols: int, style: str | _TableStyle | None = None): + """Add a table having row and column counts of `rows` and `cols` respectively. + + `style` may be a table style object or a table style name. If `style` is |None|, + the table inherits the default table style of the document. + """ + table = self._body.add_table(rows, cols, self._block_width) + table.style = style + return table + + @property + def comments(self) -> Comments: + """A |Comments| object providing access to comments added to the document.""" + return self._part.comments + + @property + def core_properties(self): + """A |CoreProperties| object providing Dublin Core properties of document.""" + return self._part.core_properties + + @property + def inline_shapes(self): + """The |InlineShapes| collection for this document. + + An inline shape is a graphical object, such as a picture, contained in a run of + text and behaving like a character glyph, being flowed like other text in a + paragraph. + """ + return self._part.inline_shapes + + def iter_inner_content(self) -> Iterator[Paragraph | Table]: + """Generate each `Paragraph` or `Table` in this document in document order.""" + return self._body.iter_inner_content() + + @property + def paragraphs(self) -> List[Paragraph]: + """The |Paragraph| instances in the document, in document order. + + Note that paragraphs within revision marks such as ```` or ```` do + not appear in this list. + """ + return self._body.paragraphs + + @property + def part(self) -> DocumentPart: + """The |DocumentPart| object of this document.""" + return self._part + + def save(self, path_or_stream: str | IO[bytes]): + """Save this document to `path_or_stream`. + + `path_or_stream` can be either a path to a filesystem location (a string) or a + file-like object. + """ + self._part.save(path_or_stream) + + @property + def sections(self) -> Sections: + """|Sections| object providing access to each section in this document.""" + return Sections(self._element, self._part) + + @property + def settings(self) -> Settings: + """A |Settings| object providing access to the document-level settings.""" + return self._part.settings + + @property + def styles(self): + """A |Styles| object providing access to the styles in this document.""" + return self._part.styles + + @property + def tables(self) -> List[Table]: + """All |Table| instances in the document, in document order. + + Note that only tables appearing at the top level of the document appear in this + list; a table nested inside a table cell does not appear. A table within + revision marks such as ```` or ```` will also not appear in the + list. + """ + return self._body.tables + + @property + def _block_width(self) -> Length: + """A |Length| object specifying the space between margins in last section.""" + section = self.sections[-1] + page_width = section.page_width or Inches(8.5) + left_margin = section.left_margin or Inches(1) + right_margin = section.right_margin or Inches(1) + return Emu(page_width - left_margin - right_margin) + + @property + def _body(self) -> _Body: + """The |_Body| instance containing the content for this document.""" + if self.__body is None: + self.__body = _Body(self._element.body, self) + return self.__body + + +class _Body(BlockItemContainer): + """Proxy for `` element in this document. + + It's primary role is a container for document content. + """ + + def __init__(self, body_elm: CT_Body, parent: t.ProvidesStoryPart): + super(_Body, self).__init__(body_elm, parent) + self._body = body_elm + + def clear_content(self) -> _Body: + """Return this |_Body| instance after clearing it of all content. + + Section properties for the main document story, if present, are preserved. + """ + self._body.clear_content() + return self diff --git a/intelliaide/vendor/docx/drawing/__init__.py b/intelliaide/vendor/docx/drawing/__init__.py new file mode 100644 index 0000000..00d1f51 --- /dev/null +++ b/intelliaide/vendor/docx/drawing/__init__.py @@ -0,0 +1,59 @@ +"""DrawingML-related objects are in this subpackage.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from docx.oxml.drawing import CT_Drawing +from docx.shared import Parented + +if TYPE_CHECKING: + import docx.types as t + from docx.image.image import Image + + +class Drawing(Parented): + """Container for a DrawingML object.""" + + def __init__(self, drawing: CT_Drawing, parent: t.ProvidesStoryPart): + super().__init__(parent) + self._parent = parent + self._drawing = self._element = drawing + + @property + def has_picture(self) -> bool: + """True when `drawing` contains an embedded picture. + + A drawing can contain a picture, but it can also contain a chart, SmartArt, or a + drawing canvas. Methods related to a picture, like `.image`, will raise when the drawing + does not contain a picture. Use this value to determine whether image methods will succeed. + + This value is `False` when a linked picture is present. This should be relatively rare and + the image would only be retrievable from the filesystem. + + Note this does not distinguish between inline and floating images. The presence of either + one will cause this value to be `True`. + """ + xpath_expr = ( + # -- an inline picture -- + "./wp:inline/a:graphic/a:graphicData/pic:pic" + # -- a floating picture -- + " | ./wp:anchor/a:graphic/a:graphicData/pic:pic" + ) + # -- xpath() will return a list, empty if there are no matches -- + return bool(self._drawing.xpath(xpath_expr)) + + @property + def image(self) -> Image: + """An `Image` proxy object for the image in this (picture) drawing. + + Raises `ValueError` when this drawing does contains something other than a picture. Use + `.has_picture` to qualify drawing objects before using this property. + """ + picture_rIds = self._drawing.xpath(".//pic:blipFill/a:blip/@r:embed") + if not picture_rIds: + raise ValueError("drawing does not contain a picture") + rId = picture_rIds[0] + doc_part = self.part + image_part = doc_part.related_parts[rId] + return image_part.image diff --git a/intelliaide/vendor/docx/enum/__init__.py b/intelliaide/vendor/docx/enum/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/intelliaide/vendor/docx/enum/base.py b/intelliaide/vendor/docx/enum/base.py new file mode 100644 index 0000000..66e9897 --- /dev/null +++ b/intelliaide/vendor/docx/enum/base.py @@ -0,0 +1,150 @@ +"""Base classes and other objects used by enumerations.""" + +from __future__ import annotations + +import enum +import textwrap +from typing import TYPE_CHECKING, Any, Dict, Type, TypeVar + +if TYPE_CHECKING: + from typing_extensions import Self + +_T = TypeVar("_T", bound="BaseXmlEnum") + + +class BaseEnum(int, enum.Enum): + """Base class for Enums that do not map XML attr values. + + The enum's value will be an integer, corresponding to the integer assigned the + corresponding member in the MS API enum of the same name. + """ + + def __new__(cls, ms_api_value: int, docstr: str): + self = int.__new__(cls, ms_api_value) + self._value_ = ms_api_value + self.__doc__ = docstr.strip() + return self + + def __str__(self): + """The symbolic name and string value of this member, e.g. 'MIDDLE (3)'.""" + return f"{self.name} ({self.value})" + + +class BaseXmlEnum(int, enum.Enum): + """Base class for Enums that also map XML attr values. + + The enum's value will be an integer, corresponding to the integer assigned the + corresponding member in the MS API enum of the same name. + """ + + xml_value: str | None + + def __new__(cls, ms_api_value: int, xml_value: str | None, docstr: str): + self = int.__new__(cls, ms_api_value) + self._value_ = ms_api_value + self.xml_value = xml_value + self.__doc__ = docstr.strip() + return self + + def __str__(self): + """The symbolic name and string value of this member, e.g. 'MIDDLE (3)'.""" + return f"{self.name} ({self.value})" + + @classmethod + def from_xml(cls, xml_value: str | None) -> Self: + """Enumeration member corresponding to XML attribute value `xml_value`. + + Example:: + + >>> WD_PARAGRAPH_ALIGNMENT.from_xml("center") + WD_PARAGRAPH_ALIGNMENT.CENTER + + """ + member = next((member for member in cls if member.xml_value == xml_value), None) + if member is None: + raise ValueError(f"{cls.__name__} has no XML mapping for '{xml_value}'") + return member + + @classmethod + def to_xml(cls: Type[_T], value: int | _T | None) -> str | None: + """XML value of this enum member, generally an XML attribute value.""" + # -- presence of multi-arg `__new__()` method fools type-checker, but getting a + # -- member by its value using EnumCls(val) works as usual. + member = cls(value) + xml_value = member.xml_value + if not xml_value: + raise ValueError(f"{cls.__name__}.{member.name} has no XML representation") + return xml_value + + +class DocsPageFormatter: + """Generate an .rst doc page for an enumeration. + + Formats a RestructuredText documention page (string) for the enumeration class parts + passed to the constructor. An immutable one-shot service object. + """ + + def __init__(self, clsname: str, clsdict: Dict[str, Any]): + self._clsname = clsname + self._clsdict = clsdict + + @property + def page_str(self): + """The RestructuredText documentation page for the enumeration. + + This is the only API member for the class. + """ + tmpl = ".. _%s:\n\n%s\n\n%s\n\n----\n\n%s" + components = ( + self._ms_name, + self._page_title, + self._intro_text, + self._member_defs, + ) + return tmpl % components + + @property + def _intro_text(self): + """Docstring of the enumeration, formatted for documentation page.""" + try: + cls_docstring = self._clsdict["__doc__"] + except KeyError: + cls_docstring = "" + + if cls_docstring is None: + return "" + + return textwrap.dedent(cls_docstring).strip() + + def _member_def(self, member: BaseEnum | BaseXmlEnum): + """Return an individual member definition formatted as an RST glossary entry, + wrapped to fit within 78 columns.""" + assert member.__doc__ is not None + member_docstring = textwrap.dedent(member.__doc__).strip() + member_docstring = textwrap.fill( + member_docstring, + width=78, + initial_indent=" " * 4, + subsequent_indent=" " * 4, + ) + return "%s\n%s\n" % (member.name, member_docstring) + + @property + def _member_defs(self): + """A single string containing the aggregated member definitions section of the + documentation page.""" + members = self._clsdict["__members__"] + member_defs = [self._member_def(member) for member in members if member.name is not None] + return "\n".join(member_defs) + + @property + def _ms_name(self): + """The Microsoft API name for this enumeration.""" + return self._clsdict["__ms_name__"] + + @property + def _page_title(self): + """The title for the documentation page, formatted as code (surrounded in + double-backtics) and underlined with '=' characters.""" + title_underscore = "=" * (len(self._clsname) + 4) + return "``%s``\n%s" % (self._clsname, title_underscore) diff --git a/intelliaide/vendor/docx/enum/dml.py b/intelliaide/vendor/docx/enum/dml.py new file mode 100644 index 0000000..27c63a2 --- /dev/null +++ b/intelliaide/vendor/docx/enum/dml.py @@ -0,0 +1,103 @@ +"""Enumerations used by DrawingML objects.""" + +from .base import BaseEnum, BaseXmlEnum + + +class MSO_COLOR_TYPE(BaseEnum): + """Specifies the color specification scheme. + + Example:: + + from docx.enum.dml import MSO_COLOR_TYPE + + assert font.color.type == MSO_COLOR_TYPE.SCHEME + + MS API name: `MsoColorType` + + http://msdn.microsoft.com/en-us/library/office/ff864912(v=office.15).aspx + """ + + RGB = (1, "Color is specified by an |RGBColor| value.") + """Color is specified by an |RGBColor| value.""" + + THEME = (2, "Color is one of the preset theme colors.") + """Color is one of the preset theme colors.""" + + AUTO = (101, "Color is determined automatically by the application.") + """Color is determined automatically by the application.""" + + +class MSO_THEME_COLOR_INDEX(BaseXmlEnum): + """Indicates the Office theme color, one of those shown in the color gallery on the + formatting ribbon. + + Alias: ``MSO_THEME_COLOR`` + + Example:: + + from docx.enum.dml import MSO_THEME_COLOR + + font.color.theme_color = MSO_THEME_COLOR.ACCENT_1 + + MS API name: `MsoThemeColorIndex` + + http://msdn.microsoft.com/en-us/library/office/ff860782(v=office.15).aspx + """ + + NOT_THEME_COLOR = (0, "UNMAPPED", "Indicates the color is not a theme color.") + """Indicates the color is not a theme color.""" + + ACCENT_1 = (5, "accent1", "Specifies the Accent 1 theme color.") + """Specifies the Accent 1 theme color.""" + + ACCENT_2 = (6, "accent2", "Specifies the Accent 2 theme color.") + """Specifies the Accent 2 theme color.""" + + ACCENT_3 = (7, "accent3", "Specifies the Accent 3 theme color.") + """Specifies the Accent 3 theme color.""" + + ACCENT_4 = (8, "accent4", "Specifies the Accent 4 theme color.") + """Specifies the Accent 4 theme color.""" + + ACCENT_5 = (9, "accent5", "Specifies the Accent 5 theme color.") + """Specifies the Accent 5 theme color.""" + + ACCENT_6 = (10, "accent6", "Specifies the Accent 6 theme color.") + """Specifies the Accent 6 theme color.""" + + BACKGROUND_1 = (14, "background1", "Specifies the Background 1 theme color.") + """Specifies the Background 1 theme color.""" + + BACKGROUND_2 = (16, "background2", "Specifies the Background 2 theme color.") + """Specifies the Background 2 theme color.""" + + DARK_1 = (1, "dark1", "Specifies the Dark 1 theme color.") + """Specifies the Dark 1 theme color.""" + + DARK_2 = (3, "dark2", "Specifies the Dark 2 theme color.") + """Specifies the Dark 2 theme color.""" + + FOLLOWED_HYPERLINK = ( + 12, + "followedHyperlink", + "Specifies the theme color for a clicked hyperlink.", + ) + """Specifies the theme color for a clicked hyperlink.""" + + HYPERLINK = (11, "hyperlink", "Specifies the theme color for a hyperlink.") + """Specifies the theme color for a hyperlink.""" + + LIGHT_1 = (2, "light1", "Specifies the Light 1 theme color.") + """Specifies the Light 1 theme color.""" + + LIGHT_2 = (4, "light2", "Specifies the Light 2 theme color.") + """Specifies the Light 2 theme color.""" + + TEXT_1 = (13, "text1", "Specifies the Text 1 theme color.") + """Specifies the Text 1 theme color.""" + + TEXT_2 = (15, "text2", "Specifies the Text 2 theme color.") + """Specifies the Text 2 theme color.""" + + +MSO_THEME_COLOR = MSO_THEME_COLOR_INDEX diff --git a/intelliaide/vendor/docx/enum/section.py b/intelliaide/vendor/docx/enum/section.py new file mode 100644 index 0000000..982e191 --- /dev/null +++ b/intelliaide/vendor/docx/enum/section.py @@ -0,0 +1,86 @@ +"""Enumerations related to the main document in WordprocessingML files.""" + +from .base import BaseXmlEnum + + +class WD_HEADER_FOOTER_INDEX(BaseXmlEnum): + """Alias: **WD_HEADER_FOOTER** + + Specifies one of the three possible header/footer definitions for a section. + + For internal use only; not part of the python-docx API. + + MS API name: `WdHeaderFooterIndex` + URL: https://docs.microsoft.com/en-us/office/vba/api/word.wdheaderfooterindex + """ + + PRIMARY = (1, "default", "Header for odd pages or all if no even header.") + """Header for odd pages or all if no even header.""" + + FIRST_PAGE = (2, "first", "Header for first page of section.") + """Header for first page of section.""" + + EVEN_PAGE = (3, "even", "Header for even pages of recto/verso section.") + """Header for even pages of recto/verso section.""" + + +WD_HEADER_FOOTER = WD_HEADER_FOOTER_INDEX + + +class WD_ORIENTATION(BaseXmlEnum): + """Alias: **WD_ORIENT** + + Specifies the page layout orientation. + + Example:: + + from docx.enum.section import WD_ORIENT + + section = document.sections[-1] section.orientation = WD_ORIENT.LANDSCAPE + + MS API name: `WdOrientation` + MS API URL: http://msdn.microsoft.com/en-us/library/office/ff837902.aspx + """ + + PORTRAIT = (0, "portrait", "Portrait orientation.") + """Portrait orientation.""" + + LANDSCAPE = (1, "landscape", "Landscape orientation.") + """Landscape orientation.""" + + +WD_ORIENT = WD_ORIENTATION + + +class WD_SECTION_START(BaseXmlEnum): + """Alias: **WD_SECTION** + + Specifies the start type of a section break. + + Example:: + + from docx.enum.section import WD_SECTION + + section = document.sections[0] section.start_type = WD_SECTION.NEW_PAGE + + MS API name: `WdSectionStart` + MS API URL: http://msdn.microsoft.com/en-us/library/office/ff840975.aspx + """ + + CONTINUOUS = (0, "continuous", "Continuous section break.") + """Continuous section break.""" + + NEW_COLUMN = (1, "nextColumn", "New column section break.") + """New column section break.""" + + NEW_PAGE = (2, "nextPage", "New page section break.") + """New page section break.""" + + EVEN_PAGE = (3, "evenPage", "Even pages section break.") + """Even pages section break.""" + + ODD_PAGE = (4, "oddPage", "Section begins on next odd page.") + """Section begins on next odd page.""" + + +WD_SECTION = WD_SECTION_START diff --git a/intelliaide/vendor/docx/enum/shape.py b/intelliaide/vendor/docx/enum/shape.py new file mode 100644 index 0000000..ed086c3 --- /dev/null +++ b/intelliaide/vendor/docx/enum/shape.py @@ -0,0 +1,19 @@ +"""Enumerations related to DrawingML shapes in WordprocessingML files.""" + +import enum + + +class WD_INLINE_SHAPE_TYPE(enum.Enum): + """Corresponds to WdInlineShapeType enumeration. + + http://msdn.microsoft.com/en-us/library/office/ff192587.aspx. + """ + + CHART = 12 + LINKED_PICTURE = 4 + PICTURE = 3 + SMART_ART = 15 + NOT_IMPLEMENTED = -6 + + +WD_INLINE_SHAPE = WD_INLINE_SHAPE_TYPE diff --git a/intelliaide/vendor/docx/enum/style.py b/intelliaide/vendor/docx/enum/style.py new file mode 100644 index 0000000..d247461 --- /dev/null +++ b/intelliaide/vendor/docx/enum/style.py @@ -0,0 +1,452 @@ +"""Enumerations related to styles.""" + +from .base import BaseEnum, BaseXmlEnum + + +class WD_BUILTIN_STYLE(BaseEnum): + """Alias: **WD_STYLE** + + Specifies a built-in Microsoft Word style. + + Example:: + + from docx import Document + from docx.enum.style import WD_STYLE + + document = Document() + styles = document.styles + style = styles[WD_STYLE.BODY_TEXT] + + + MS API name: `WdBuiltinStyle` + + http://msdn.microsoft.com/en-us/library/office/ff835210.aspx + """ + + BLOCK_QUOTATION = (-85, "Block Text.") + """Block Text.""" + + BODY_TEXT = (-67, "Body Text.") + """Body Text.""" + + BODY_TEXT_2 = (-81, "Body Text 2.") + """Body Text 2.""" + + BODY_TEXT_3 = (-82, "Body Text 3.") + """Body Text 3.""" + + BODY_TEXT_FIRST_INDENT = (-78, "Body Text First Indent.") + """Body Text First Indent.""" + + BODY_TEXT_FIRST_INDENT_2 = (-79, "Body Text First Indent 2.") + """Body Text First Indent 2.""" + + BODY_TEXT_INDENT = (-68, "Body Text Indent.") + """Body Text Indent.""" + + BODY_TEXT_INDENT_2 = (-83, "Body Text Indent 2.") + """Body Text Indent 2.""" + + BODY_TEXT_INDENT_3 = (-84, "Body Text Indent 3.") + """Body Text Indent 3.""" + + BOOK_TITLE = (-265, "Book Title.") + """Book Title.""" + + CAPTION = (-35, "Caption.") + """Caption.""" + + CLOSING = (-64, "Closing.") + """Closing.""" + + COMMENT_REFERENCE = (-40, "Comment Reference.") + """Comment Reference.""" + + COMMENT_TEXT = (-31, "Comment Text.") + """Comment Text.""" + + DATE = (-77, "Date.") + """Date.""" + + DEFAULT_PARAGRAPH_FONT = (-66, "Default Paragraph Font.") + """Default Paragraph Font.""" + + EMPHASIS = (-89, "Emphasis.") + """Emphasis.""" + + ENDNOTE_REFERENCE = (-43, "Endnote Reference.") + """Endnote Reference.""" + + ENDNOTE_TEXT = (-44, "Endnote Text.") + """Endnote Text.""" + + ENVELOPE_ADDRESS = (-37, "Envelope Address.") + """Envelope Address.""" + + ENVELOPE_RETURN = (-38, "Envelope Return.") + """Envelope Return.""" + + FOOTER = (-33, "Footer.") + """Footer.""" + + FOOTNOTE_REFERENCE = (-39, "Footnote Reference.") + """Footnote Reference.""" + + FOOTNOTE_TEXT = (-30, "Footnote Text.") + """Footnote Text.""" + + HEADER = (-32, "Header.") + """Header.""" + + HEADING_1 = (-2, "Heading 1.") + """Heading 1.""" + + HEADING_2 = (-3, "Heading 2.") + """Heading 2.""" + + HEADING_3 = (-4, "Heading 3.") + """Heading 3.""" + + HEADING_4 = (-5, "Heading 4.") + """Heading 4.""" + + HEADING_5 = (-6, "Heading 5.") + """Heading 5.""" + + HEADING_6 = (-7, "Heading 6.") + """Heading 6.""" + + HEADING_7 = (-8, "Heading 7.") + """Heading 7.""" + + HEADING_8 = (-9, "Heading 8.") + """Heading 8.""" + + HEADING_9 = (-10, "Heading 9.") + """Heading 9.""" + + HTML_ACRONYM = (-96, "HTML Acronym.") + """HTML Acronym.""" + + HTML_ADDRESS = (-97, "HTML Address.") + """HTML Address.""" + + HTML_CITE = (-98, "HTML Cite.") + """HTML Cite.""" + + HTML_CODE = (-99, "HTML Code.") + """HTML Code.""" + + HTML_DFN = (-100, "HTML Definition.") + """HTML Definition.""" + + HTML_KBD = (-101, "HTML Keyboard.") + """HTML Keyboard.""" + + HTML_NORMAL = (-95, "Normal (Web).") + """Normal (Web).""" + + HTML_PRE = (-102, "HTML Preformatted.") + """HTML Preformatted.""" + + HTML_SAMP = (-103, "HTML Sample.") + """HTML Sample.""" + + HTML_TT = (-104, "HTML Typewriter.") + """HTML Typewriter.""" + + HTML_VAR = (-105, "HTML Variable.") + """HTML Variable.""" + + HYPERLINK = (-86, "Hyperlink.") + """Hyperlink.""" + + HYPERLINK_FOLLOWED = (-87, "Followed Hyperlink.") + """Followed Hyperlink.""" + + INDEX_1 = (-11, "Index 1.") + """Index 1.""" + + INDEX_2 = (-12, "Index 2.") + """Index 2.""" + + INDEX_3 = (-13, "Index 3.") + """Index 3.""" + + INDEX_4 = (-14, "Index 4.") + """Index 4.""" + + INDEX_5 = (-15, "Index 5.") + """Index 5.""" + + INDEX_6 = (-16, "Index 6.") + """Index 6.""" + + INDEX_7 = (-17, "Index 7.") + """Index 7.""" + + INDEX_8 = (-18, "Index 8.") + """Index 8.""" + + INDEX_9 = (-19, "Index 9.") + """Index 9.""" + + INDEX_HEADING = (-34, "Index Heading") + """Index Heading""" + + INTENSE_EMPHASIS = (-262, "Intense Emphasis.") + """Intense Emphasis.""" + + INTENSE_QUOTE = (-182, "Intense Quote.") + """Intense Quote.""" + + INTENSE_REFERENCE = (-264, "Intense Reference.") + """Intense Reference.""" + + LINE_NUMBER = (-41, "Line Number.") + """Line Number.""" + + LIST = (-48, "List.") + """List.""" + + LIST_2 = (-51, "List 2.") + """List 2.""" + + LIST_3 = (-52, "List 3.") + """List 3.""" + + LIST_4 = (-53, "List 4.") + """List 4.""" + + LIST_5 = (-54, "List 5.") + """List 5.""" + + LIST_BULLET = (-49, "List Bullet.") + """List Bullet.""" + + LIST_BULLET_2 = (-55, "List Bullet 2.") + """List Bullet 2.""" + + LIST_BULLET_3 = (-56, "List Bullet 3.") + """List Bullet 3.""" + + LIST_BULLET_4 = (-57, "List Bullet 4.") + """List Bullet 4.""" + + LIST_BULLET_5 = (-58, "List Bullet 5.") + """List Bullet 5.""" + + LIST_CONTINUE = (-69, "List Continue.") + """List Continue.""" + + LIST_CONTINUE_2 = (-70, "List Continue 2.") + """List Continue 2.""" + + LIST_CONTINUE_3 = (-71, "List Continue 3.") + """List Continue 3.""" + + LIST_CONTINUE_4 = (-72, "List Continue 4.") + """List Continue 4.""" + + LIST_CONTINUE_5 = (-73, "List Continue 5.") + """List Continue 5.""" + + LIST_NUMBER = (-50, "List Number.") + """List Number.""" + + LIST_NUMBER_2 = (-59, "List Number 2.") + """List Number 2.""" + + LIST_NUMBER_3 = (-60, "List Number 3.") + """List Number 3.""" + + LIST_NUMBER_4 = (-61, "List Number 4.") + """List Number 4.""" + + LIST_NUMBER_5 = (-62, "List Number 5.") + """List Number 5.""" + + LIST_PARAGRAPH = (-180, "List Paragraph.") + """List Paragraph.""" + + MACRO_TEXT = (-46, "Macro Text.") + """Macro Text.""" + + MESSAGE_HEADER = (-74, "Message Header.") + """Message Header.""" + + NAV_PANE = (-90, "Document Map.") + """Document Map.""" + + NORMAL = (-1, "Normal.") + """Normal.""" + + NORMAL_INDENT = (-29, "Normal Indent.") + """Normal Indent.""" + + NORMAL_OBJECT = (-158, "Normal (applied to an object).") + """Normal (applied to an object).""" + + NORMAL_TABLE = (-106, "Normal (applied within a table).") + """Normal (applied within a table).""" + + NOTE_HEADING = (-80, "Note Heading.") + """Note Heading.""" + + PAGE_NUMBER = (-42, "Page Number.") + """Page Number.""" + + PLAIN_TEXT = (-91, "Plain Text.") + """Plain Text.""" + + QUOTE = (-181, "Quote.") + """Quote.""" + + SALUTATION = (-76, "Salutation.") + """Salutation.""" + + SIGNATURE = (-65, "Signature.") + """Signature.""" + + STRONG = (-88, "Strong.") + """Strong.""" + + SUBTITLE = (-75, "Subtitle.") + """Subtitle.""" + + SUBTLE_EMPHASIS = (-261, "Subtle Emphasis.") + """Subtle Emphasis.""" + + SUBTLE_REFERENCE = (-263, "Subtle Reference.") + """Subtle Reference.""" + + TABLE_COLORFUL_GRID = (-172, "Colorful Grid.") + """Colorful Grid.""" + + TABLE_COLORFUL_LIST = (-171, "Colorful List.") + """Colorful List.""" + + TABLE_COLORFUL_SHADING = (-170, "Colorful Shading.") + """Colorful Shading.""" + + TABLE_DARK_LIST = (-169, "Dark List.") + """Dark List.""" + + TABLE_LIGHT_GRID = (-161, "Light Grid.") + """Light Grid.""" + + TABLE_LIGHT_GRID_ACCENT_1 = (-175, "Light Grid Accent 1.") + """Light Grid Accent 1.""" + + TABLE_LIGHT_LIST = (-160, "Light List.") + """Light List.""" + + TABLE_LIGHT_LIST_ACCENT_1 = (-174, "Light List Accent 1.") + """Light List Accent 1.""" + + TABLE_LIGHT_SHADING = (-159, "Light Shading.") + """Light Shading.""" + + TABLE_LIGHT_SHADING_ACCENT_1 = (-173, "Light Shading Accent 1.") + """Light Shading Accent 1.""" + + TABLE_MEDIUM_GRID_1 = (-166, "Medium Grid 1.") + """Medium Grid 1.""" + + TABLE_MEDIUM_GRID_2 = (-167, "Medium Grid 2.") + """Medium Grid 2.""" + + TABLE_MEDIUM_GRID_3 = (-168, "Medium Grid 3.") + """Medium Grid 3.""" + + TABLE_MEDIUM_LIST_1 = (-164, "Medium List 1.") + """Medium List 1.""" + + TABLE_MEDIUM_LIST_1_ACCENT_1 = (-178, "Medium List 1 Accent 1.") + """Medium List 1 Accent 1.""" + + TABLE_MEDIUM_LIST_2 = (-165, "Medium List 2.") + """Medium List 2.""" + + TABLE_MEDIUM_SHADING_1 = (-162, "Medium Shading 1.") + """Medium Shading 1.""" + + TABLE_MEDIUM_SHADING_1_ACCENT_1 = (-176, "Medium Shading 1 Accent 1.") + """Medium Shading 1 Accent 1.""" + + TABLE_MEDIUM_SHADING_2 = (-163, "Medium Shading 2.") + """Medium Shading 2.""" + + TABLE_MEDIUM_SHADING_2_ACCENT_1 = (-177, "Medium Shading 2 Accent 1.") + """Medium Shading 2 Accent 1.""" + + TABLE_OF_AUTHORITIES = (-45, "Table of Authorities.") + """Table of Authorities.""" + + TABLE_OF_FIGURES = (-36, "Table of Figures.") + """Table of Figures.""" + + TITLE = (-63, "Title.") + """Title.""" + + TOAHEADING = (-47, "TOA Heading.") + """TOA Heading.""" + + TOC_1 = (-20, "TOC 1.") + """TOC 1.""" + + TOC_2 = (-21, "TOC 2.") + """TOC 2.""" + + TOC_3 = (-22, "TOC 3.") + """TOC 3.""" + + TOC_4 = (-23, "TOC 4.") + """TOC 4.""" + + TOC_5 = (-24, "TOC 5.") + """TOC 5.""" + + TOC_6 = (-25, "TOC 6.") + """TOC 6.""" + + TOC_7 = (-26, "TOC 7.") + """TOC 7.""" + + TOC_8 = (-27, "TOC 8.") + """TOC 8.""" + + TOC_9 = (-28, "TOC 9.") + """TOC 9.""" + + +WD_STYLE = WD_BUILTIN_STYLE + + +class WD_STYLE_TYPE(BaseXmlEnum): + """Specifies one of the four style types: paragraph, character, list, or table. + + Example:: + + from docx import Document + from docx.enum.style import WD_STYLE_TYPE + + styles = Document().styles + assert styles[0].type == WD_STYLE_TYPE.PARAGRAPH + + MS API name: `WdStyleType` + + http://msdn.microsoft.com/en-us/library/office/ff196870.aspx + """ + + CHARACTER = (2, "character", "Character style.") + """Character style.""" + + LIST = (4, "numbering", "List style.") + """List style.""" + + PARAGRAPH = (1, "paragraph", "Paragraph style.") + """Paragraph style.""" + + TABLE = (3, "table", "Table style.") + """Table style.""" diff --git a/intelliaide/vendor/docx/enum/table.py b/intelliaide/vendor/docx/enum/table.py new file mode 100644 index 0000000..eb1eb9d --- /dev/null +++ b/intelliaide/vendor/docx/enum/table.py @@ -0,0 +1,136 @@ +"""Enumerations related to tables in WordprocessingML files.""" + +from docx.enum.base import BaseEnum, BaseXmlEnum + + +class WD_CELL_VERTICAL_ALIGNMENT(BaseXmlEnum): + """Alias: **WD_ALIGN_VERTICAL** + + Specifies the vertical alignment of text in one or more cells of a table. + + Example:: + + from docx.enum.table import WD_ALIGN_VERTICAL + + table = document.add_table(3, 3) + table.cell(0, 0).vertical_alignment = WD_ALIGN_VERTICAL.BOTTOM + + MS API name: `WdCellVerticalAlignment` + + https://msdn.microsoft.com/en-us/library/office/ff193345.aspx + """ + + TOP = (0, "top", "Text is aligned to the top border of the cell.") + """Text is aligned to the top border of the cell.""" + + CENTER = (1, "center", "Text is aligned to the center of the cell.") + """Text is aligned to the center of the cell.""" + + BOTTOM = (3, "bottom", "Text is aligned to the bottom border of the cell.") + """Text is aligned to the bottom border of the cell.""" + + BOTH = ( + 101, + "both", + "This is an option in the OpenXml spec, but not in Word itself. It's not" + " clear what Word behavior this setting produces. If you find out please" + " let us know and we'll update this documentation. Otherwise, probably best" + " to avoid this option.", + ) + """This is an option in the OpenXml spec, but not in Word itself. + + It's not clear what Word behavior this setting produces. If you find out please let + us know and we'll update this documentation. Otherwise, probably best to avoid this + option. + """ + + +WD_ALIGN_VERTICAL = WD_CELL_VERTICAL_ALIGNMENT + + +class WD_ROW_HEIGHT_RULE(BaseXmlEnum): + """Alias: **WD_ROW_HEIGHT** + + Specifies the rule for determining the height of a table row + + Example:: + + from docx.enum.table import WD_ROW_HEIGHT_RULE + + table = document.add_table(3, 3) + table.rows[0].height_rule = WD_ROW_HEIGHT_RULE.EXACTLY + + MS API name: `WdRowHeightRule` + + https://msdn.microsoft.com/en-us/library/office/ff193620.aspx + """ + + AUTO = ( + 0, + "auto", + "The row height is adjusted to accommodate the tallest value in the row.", + ) + """The row height is adjusted to accommodate the tallest value in the row.""" + + AT_LEAST = (1, "atLeast", "The row height is at least a minimum specified value.") + """The row height is at least a minimum specified value.""" + + EXACTLY = (2, "exact", "The row height is an exact value.") + """The row height is an exact value.""" + + +WD_ROW_HEIGHT = WD_ROW_HEIGHT_RULE + + +class WD_TABLE_ALIGNMENT(BaseXmlEnum): + """Specifies table justification type. + + Example:: + + from docx.enum.table import WD_TABLE_ALIGNMENT + + table = document.add_table(3, 3) + table.alignment = WD_TABLE_ALIGNMENT.CENTER + + MS API name: `WdRowAlignment` + + http://office.microsoft.com/en-us/word-help/HV080607259.aspx + """ + + LEFT = (0, "left", "Left-aligned") + """Left-aligned""" + + CENTER = (1, "center", "Center-aligned.") + """Center-aligned.""" + + RIGHT = (2, "right", "Right-aligned.") + """Right-aligned.""" + + +class WD_TABLE_DIRECTION(BaseEnum): + """Specifies the direction in which an application orders cells in the specified + table or row. + + Example:: + + from docx.enum.table import WD_TABLE_DIRECTION + + table = document.add_table(3, 3) + table.direction = WD_TABLE_DIRECTION.RTL + + MS API name: `WdTableDirection` + + http://msdn.microsoft.com/en-us/library/ff835141.aspx + """ + + LTR = ( + 0, + "The table or row is arranged with the first column in the leftmost position.", + ) + """The table or row is arranged with the first column in the leftmost position.""" + + RTL = ( + 1, + "The table or row is arranged with the first column in the rightmost position.", + ) + """The table or row is arranged with the first column in the rightmost position.""" diff --git a/intelliaide/vendor/docx/enum/text.py b/intelliaide/vendor/docx/enum/text.py new file mode 100644 index 0000000..99e776f --- /dev/null +++ b/intelliaide/vendor/docx/enum/text.py @@ -0,0 +1,367 @@ +"""Enumerations related to text in WordprocessingML files.""" + +from __future__ import annotations + +import enum + +from docx.enum.base import BaseXmlEnum + + +class WD_PARAGRAPH_ALIGNMENT(BaseXmlEnum): + """Alias: **WD_ALIGN_PARAGRAPH** + + Specifies paragraph justification type. + + Example:: + + from docx.enum.text import WD_ALIGN_PARAGRAPH + + paragraph = document.add_paragraph() + paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER + """ + + LEFT = (0, "left", "Left-aligned") + """Left-aligned""" + + CENTER = (1, "center", "Center-aligned.") + """Center-aligned.""" + + RIGHT = (2, "right", "Right-aligned.") + """Right-aligned.""" + + JUSTIFY = (3, "both", "Fully justified.") + """Fully justified.""" + + DISTRIBUTE = ( + 4, + "distribute", + "Paragraph characters are distributed to fill entire width of paragraph.", + ) + """Paragraph characters are distributed to fill entire width of paragraph.""" + + JUSTIFY_MED = ( + 5, + "mediumKashida", + "Justified with a medium character compression ratio.", + ) + """Justified with a medium character compression ratio.""" + + JUSTIFY_HI = ( + 7, + "highKashida", + "Justified with a high character compression ratio.", + ) + """Justified with a high character compression ratio.""" + + JUSTIFY_LOW = (8, "lowKashida", "Justified with a low character compression ratio.") + """Justified with a low character compression ratio.""" + + THAI_JUSTIFY = ( + 9, + "thaiDistribute", + "Justified according to Thai formatting layout.", + ) + """Justified according to Thai formatting layout.""" + + +WD_ALIGN_PARAGRAPH = WD_PARAGRAPH_ALIGNMENT + + +class WD_BREAK_TYPE(enum.Enum): + """Corresponds to WdBreakType enumeration. + + http://msdn.microsoft.com/en-us/library/office/ff195905.aspx. + """ + + COLUMN = 8 + LINE = 6 + LINE_CLEAR_LEFT = 9 + LINE_CLEAR_RIGHT = 10 + LINE_CLEAR_ALL = 11 # -- added for consistency, not in MS version -- + PAGE = 7 + SECTION_CONTINUOUS = 3 + SECTION_EVEN_PAGE = 4 + SECTION_NEXT_PAGE = 2 + SECTION_ODD_PAGE = 5 + TEXT_WRAPPING = 11 + + +WD_BREAK = WD_BREAK_TYPE + + +class WD_COLOR_INDEX(BaseXmlEnum): + """Specifies a standard preset color to apply. + + Used for font highlighting and perhaps other applications. + + * MS API name: `WdColorIndex` + * URL: https://msdn.microsoft.com/EN-US/library/office/ff195343.aspx + """ + + INHERITED = (-1, None, "Color is inherited from the style hierarchy.") + """Color is inherited from the style hierarchy.""" + + AUTO = (0, "default", "Automatic color. Default; usually black.") + """Automatic color. Default; usually black.""" + + BLACK = (1, "black", "Black color.") + """Black color.""" + + BLUE = (2, "blue", "Blue color") + """Blue color""" + + BRIGHT_GREEN = (4, "green", "Bright green color.") + """Bright green color.""" + + DARK_BLUE = (9, "darkBlue", "Dark blue color.") + """Dark blue color.""" + + DARK_RED = (13, "darkRed", "Dark red color.") + """Dark red color.""" + + DARK_YELLOW = (14, "darkYellow", "Dark yellow color.") + """Dark yellow color.""" + + GRAY_25 = (16, "lightGray", "25% shade of gray color.") + """25% shade of gray color.""" + + GRAY_50 = (15, "darkGray", "50% shade of gray color.") + """50% shade of gray color.""" + + GREEN = (11, "darkGreen", "Green color.") + """Green color.""" + + PINK = (5, "magenta", "Pink color.") + """Pink color.""" + + RED = (6, "red", "Red color.") + """Red color.""" + + TEAL = (10, "darkCyan", "Teal color.") + """Teal color.""" + + TURQUOISE = (3, "cyan", "Turquoise color.") + """Turquoise color.""" + + VIOLET = (12, "darkMagenta", "Violet color.") + """Violet color.""" + + WHITE = (8, "white", "White color.") + """White color.""" + + YELLOW = (7, "yellow", "Yellow color.") + """Yellow color.""" + + +WD_COLOR = WD_COLOR_INDEX + + +class WD_LINE_SPACING(BaseXmlEnum): + """Specifies a line spacing format to be applied to a paragraph. + + Example:: + + from docx.enum.text import WD_LINE_SPACING + + paragraph = document.add_paragraph() + paragraph.line_spacing_rule = WD_LINE_SPACING.EXACTLY + + + MS API name: `WdLineSpacing` + + URL: http://msdn.microsoft.com/en-us/library/office/ff844910.aspx + """ + + SINGLE = (0, "UNMAPPED", "Single spaced (default).") + """Single spaced (default).""" + + ONE_POINT_FIVE = (1, "UNMAPPED", "Space-and-a-half line spacing.") + """Space-and-a-half line spacing.""" + + DOUBLE = (2, "UNMAPPED", "Double spaced.") + """Double spaced.""" + + AT_LEAST = ( + 3, + "atLeast", + "Minimum line spacing is specified amount. Amount is specified separately.", + ) + """Minimum line spacing is specified amount. Amount is specified separately.""" + + EXACTLY = ( + 4, + "exact", + "Line spacing is exactly specified amount. Amount is specified separately.", + ) + """Line spacing is exactly specified amount. Amount is specified separately.""" + + MULTIPLE = ( + 5, + "auto", + "Line spacing is specified as multiple of line heights. Changing font size" + " will change line spacing proportionately.", + ) + """Line spacing is specified as multiple of line heights. Changing font size will + change the line spacing proportionately.""" + + +class WD_TAB_ALIGNMENT(BaseXmlEnum): + """Specifies the tab stop alignment to apply. + + MS API name: `WdTabAlignment` + + URL: https://msdn.microsoft.com/EN-US/library/office/ff195609.aspx + """ + + LEFT = (0, "left", "Left-aligned.") + """Left-aligned.""" + + CENTER = (1, "center", "Center-aligned.") + """Center-aligned.""" + + RIGHT = (2, "right", "Right-aligned.") + """Right-aligned.""" + + DECIMAL = (3, "decimal", "Decimal-aligned.") + """Decimal-aligned.""" + + BAR = (4, "bar", "Bar-aligned.") + """Bar-aligned.""" + + LIST = (6, "list", "List-aligned. (deprecated)") + """List-aligned. (deprecated)""" + + CLEAR = (101, "clear", "Clear an inherited tab stop.") + """Clear an inherited tab stop.""" + + END = (102, "end", "Right-aligned. (deprecated)") + """Right-aligned. (deprecated)""" + + NUM = (103, "num", "Left-aligned. (deprecated)") + """Left-aligned. (deprecated)""" + + START = (104, "start", "Left-aligned. (deprecated)") + """Left-aligned. (deprecated)""" + + +class WD_TAB_LEADER(BaseXmlEnum): + """Specifies the character to use as the leader with formatted tabs. + + MS API name: `WdTabLeader` + + URL: https://msdn.microsoft.com/en-us/library/office/ff845050.aspx + """ + + SPACES = (0, "none", "Spaces. Default.") + """Spaces. Default.""" + + DOTS = (1, "dot", "Dots.") + """Dots.""" + + DASHES = (2, "hyphen", "Dashes.") + """Dashes.""" + + LINES = (3, "underscore", "Double lines.") + """Double lines.""" + + HEAVY = (4, "heavy", "A heavy line.") + """A heavy line.""" + + MIDDLE_DOT = (5, "middleDot", "A vertically-centered dot.") + """A vertically-centered dot.""" + + +class WD_UNDERLINE(BaseXmlEnum): + """Specifies the style of underline applied to a run of characters. + + MS API name: `WdUnderline` + + URL: http://msdn.microsoft.com/en-us/library/office/ff822388.aspx + """ + + INHERITED = (-1, None, "Inherit underline setting from containing paragraph.") + """Inherit underline setting from containing paragraph.""" + + NONE = ( + 0, + "none", + "No underline.\n\nThis setting overrides any inherited underline value, so can" + " be used to remove underline from a run that inherits underlining from its" + " containing paragraph. Note this is not the same as assigning |None| to" + " Run.underline. |None| is a valid assignment value, but causes the run to" + " inherit its underline value. Assigning `WD_UNDERLINE.NONE` causes" + " underlining to be unconditionally turned off.", + ) + """No underline. + + This setting overrides any inherited underline value, so can be used to remove + underline from a run that inherits underlining from its containing paragraph. Note + this is not the same as assigning |None| to Run.underline. |None| is a valid + assignment value, but causes the run to inherit its underline value. Assigning + ``WD_UNDERLINE.NONE`` causes underlining to be unconditionally turned off. + """ + + SINGLE = ( + 1, + "single", + "A single line.\n\nNote that this setting is write-only in the sense that" + " |True| (rather than `WD_UNDERLINE.SINGLE`) is returned for a run having" + " this setting.", + ) + """A single line. + + Note that this setting is write-only in the sense that |True| + (rather than ``WD_UNDERLINE.SINGLE``) is returned for a run having this setting. + """ + + WORDS = (2, "words", "Underline individual words only.") + """Underline individual words only.""" + + DOUBLE = (3, "double", "A double line.") + """A double line.""" + + DOTTED = (4, "dotted", "Dots.") + """Dots.""" + + THICK = (6, "thick", "A single thick line.") + """A single thick line.""" + + DASH = (7, "dash", "Dashes.") + """Dashes.""" + + DOT_DASH = (9, "dotDash", "Alternating dots and dashes.") + """Alternating dots and dashes.""" + + DOT_DOT_DASH = (10, "dotDotDash", "An alternating dot-dot-dash pattern.") + """An alternating dot-dot-dash pattern.""" + + WAVY = (11, "wave", "A single wavy line.") + """A single wavy line.""" + + DOTTED_HEAVY = (20, "dottedHeavy", "Heavy dots.") + """Heavy dots.""" + + DASH_HEAVY = (23, "dashedHeavy", "Heavy dashes.") + """Heavy dashes.""" + + DOT_DASH_HEAVY = (25, "dashDotHeavy", "Alternating heavy dots and heavy dashes.") + """Alternating heavy dots and heavy dashes.""" + + DOT_DOT_DASH_HEAVY = ( + 26, + "dashDotDotHeavy", + "An alternating heavy dot-dot-dash pattern.", + ) + """An alternating heavy dot-dot-dash pattern.""" + + WAVY_HEAVY = (27, "wavyHeavy", "A heavy wavy line.") + """A heavy wavy line.""" + + DASH_LONG = (39, "dashLong", "Long dashes.") + """Long dashes.""" + + WAVY_DOUBLE = (43, "wavyDouble", "A double wavy line.") + """A double wavy line.""" + + DASH_LONG_HEAVY = (55, "dashLongHeavy", "Long heavy dashes.") + """Long heavy dashes.""" diff --git a/intelliaide/vendor/docx/exceptions.py b/intelliaide/vendor/docx/exceptions.py new file mode 100644 index 0000000..e26f4c3 --- /dev/null +++ b/intelliaide/vendor/docx/exceptions.py @@ -0,0 +1,18 @@ +"""Exceptions used with python-docx. + +The base exception class is PythonDocxError. +""" + + +class PythonDocxError(Exception): + """Generic error class.""" + + +class InvalidSpanError(PythonDocxError): + """Raised when an invalid merge region is specified in a request to merge table + cells.""" + + +class InvalidXmlError(PythonDocxError): + """Raised when invalid XML is encountered, such as on attempt to access a missing + required child element.""" diff --git a/intelliaide/vendor/docx/image/__init__.py b/intelliaide/vendor/docx/image/__init__.py new file mode 100644 index 0000000..9d5e4b0 --- /dev/null +++ b/intelliaide/vendor/docx/image/__init__.py @@ -0,0 +1,23 @@ +"""Provides objects that can characterize image streams. + +That characterization is as to content type and size, as a required step in including +them in a document. +""" + +from docx.image.bmp import Bmp +from docx.image.gif import Gif +from docx.image.jpeg import Exif, Jfif +from docx.image.png import Png +from docx.image.tiff import Tiff + +SIGNATURES = ( + # class, offset, signature_bytes + (Png, 0, b"\x89PNG\x0d\x0a\x1a\x0a"), + (Jfif, 6, b"JFIF"), + (Exif, 6, b"Exif"), + (Gif, 0, b"GIF87a"), + (Gif, 0, b"GIF89a"), + (Tiff, 0, b"MM\x00*"), # big-endian (Motorola) TIFF + (Tiff, 0, b"II*\x00"), # little-endian (Intel) TIFF + (Bmp, 0, b"BM"), +) diff --git a/intelliaide/vendor/docx/image/bmp.py b/intelliaide/vendor/docx/image/bmp.py new file mode 100644 index 0000000..115b01d --- /dev/null +++ b/intelliaide/vendor/docx/image/bmp.py @@ -0,0 +1,43 @@ +from .constants import MIME_TYPE +from .helpers import LITTLE_ENDIAN, StreamReader +from .image import BaseImageHeader + + +class Bmp(BaseImageHeader): + """Image header parser for BMP images.""" + + @classmethod + def from_stream(cls, stream): + """Return |Bmp| instance having header properties parsed from the BMP image in + `stream`.""" + stream_rdr = StreamReader(stream, LITTLE_ENDIAN) + + px_width = stream_rdr.read_long(0x12) + px_height = stream_rdr.read_long(0x16) + + horz_px_per_meter = stream_rdr.read_long(0x26) + vert_px_per_meter = stream_rdr.read_long(0x2A) + + horz_dpi = cls._dpi(horz_px_per_meter) + vert_dpi = cls._dpi(vert_px_per_meter) + + return cls(px_width, px_height, horz_dpi, vert_dpi) + + @property + def content_type(self): + """MIME content type for this image, unconditionally `image/bmp` for BMP + images.""" + return MIME_TYPE.BMP + + @property + def default_ext(self): + """Default filename extension, always 'bmp' for BMP images.""" + return "bmp" + + @staticmethod + def _dpi(px_per_meter): + """Return the integer pixels per inch from `px_per_meter`, defaulting to 96 if + `px_per_meter` is zero.""" + if px_per_meter == 0: + return 96 + return int(round(px_per_meter * 0.0254)) diff --git a/intelliaide/vendor/docx/image/constants.py b/intelliaide/vendor/docx/image/constants.py new file mode 100644 index 0000000..03fae58 --- /dev/null +++ b/intelliaide/vendor/docx/image/constants.py @@ -0,0 +1,172 @@ +"""Constants specific the the image sub-package.""" + + +class JPEG_MARKER_CODE: + """JPEG marker codes.""" + + TEM = b"\x01" + DHT = b"\xc4" + DAC = b"\xcc" + JPG = b"\xc8" + + SOF0 = b"\xc0" + SOF1 = b"\xc1" + SOF2 = b"\xc2" + SOF3 = b"\xc3" + SOF5 = b"\xc5" + SOF6 = b"\xc6" + SOF7 = b"\xc7" + SOF9 = b"\xc9" + SOFA = b"\xca" + SOFB = b"\xcb" + SOFD = b"\xcd" + SOFE = b"\xce" + SOFF = b"\xcf" + + RST0 = b"\xd0" + RST1 = b"\xd1" + RST2 = b"\xd2" + RST3 = b"\xd3" + RST4 = b"\xd4" + RST5 = b"\xd5" + RST6 = b"\xd6" + RST7 = b"\xd7" + + SOI = b"\xd8" + EOI = b"\xd9" + SOS = b"\xda" + DQT = b"\xdb" # Define Quantization Table(s) + DNL = b"\xdc" + DRI = b"\xdd" + DHP = b"\xde" + EXP = b"\xdf" + + APP0 = b"\xe0" + APP1 = b"\xe1" + APP2 = b"\xe2" + APP3 = b"\xe3" + APP4 = b"\xe4" + APP5 = b"\xe5" + APP6 = b"\xe6" + APP7 = b"\xe7" + APP8 = b"\xe8" + APP9 = b"\xe9" + APPA = b"\xea" + APPB = b"\xeb" + APPC = b"\xec" + APPD = b"\xed" + APPE = b"\xee" + APPF = b"\xef" + + STANDALONE_MARKERS = (TEM, SOI, EOI, RST0, RST1, RST2, RST3, RST4, RST5, RST6, RST7) + + SOF_MARKER_CODES = ( + SOF0, + SOF1, + SOF2, + SOF3, + SOF5, + SOF6, + SOF7, + SOF9, + SOFA, + SOFB, + SOFD, + SOFE, + SOFF, + ) + + marker_names = { + b"\x00": "UNKNOWN", + b"\xc0": "SOF0", + b"\xc2": "SOF2", + b"\xc4": "DHT", + b"\xda": "SOS", # start of scan + b"\xd8": "SOI", # start of image + b"\xd9": "EOI", # end of image + b"\xdb": "DQT", + b"\xe0": "APP0", + b"\xe1": "APP1", + b"\xe2": "APP2", + b"\xed": "APP13", + b"\xee": "APP14", + } + + @classmethod + def is_standalone(cls, marker_code): + return marker_code in cls.STANDALONE_MARKERS + + +class MIME_TYPE: + """Image content types.""" + + BMP = "image/bmp" + GIF = "image/gif" + JPEG = "image/jpeg" + PNG = "image/png" + TIFF = "image/tiff" + + +class PNG_CHUNK_TYPE: + """PNG chunk type names.""" + + IHDR = "IHDR" + pHYs = "pHYs" + IEND = "IEND" + + +class TIFF_FLD_TYPE: + """Tag codes for TIFF Image File Directory (IFD) entries.""" + + BYTE = 1 + ASCII = 2 + SHORT = 3 + LONG = 4 + RATIONAL = 5 + + field_type_names = { + 1: "BYTE", + 2: "ASCII char", + 3: "SHORT", + 4: "LONG", + 5: "RATIONAL", + } + + +TIFF_FLD = TIFF_FLD_TYPE + + +class TIFF_TAG: + """Tag codes for TIFF Image File Directory (IFD) entries.""" + + IMAGE_WIDTH = 0x0100 + IMAGE_LENGTH = 0x0101 + X_RESOLUTION = 0x011A + Y_RESOLUTION = 0x011B + RESOLUTION_UNIT = 0x0128 + + tag_names = { + 0x00FE: "NewSubfileType", + 0x0100: "ImageWidth", + 0x0101: "ImageLength", + 0x0102: "BitsPerSample", + 0x0103: "Compression", + 0x0106: "PhotometricInterpretation", + 0x010E: "ImageDescription", + 0x010F: "Make", + 0x0110: "Model", + 0x0111: "StripOffsets", + 0x0112: "Orientation", + 0x0115: "SamplesPerPixel", + 0x0117: "StripByteCounts", + 0x011A: "XResolution", + 0x011B: "YResolution", + 0x011C: "PlanarConfiguration", + 0x0128: "ResolutionUnit", + 0x0131: "Software", + 0x0132: "DateTime", + 0x0213: "YCbCrPositioning", + 0x8769: "ExifTag", + 0x8825: "GPS IFD", + 0xC4A5: "PrintImageMatching", + } diff --git a/intelliaide/vendor/docx/image/exceptions.py b/intelliaide/vendor/docx/image/exceptions.py new file mode 100644 index 0000000..2b35187 --- /dev/null +++ b/intelliaide/vendor/docx/image/exceptions.py @@ -0,0 +1,13 @@ +"""Exceptions specific the the image sub-package.""" + + +class InvalidImageStreamError(Exception): + """The recognized image stream appears to be corrupted.""" + + +class UnexpectedEndOfFileError(Exception): + """EOF was unexpectedly encountered while reading an image stream.""" + + +class UnrecognizedImageError(Exception): + """The provided image stream could not be recognized.""" diff --git a/intelliaide/vendor/docx/image/gif.py b/intelliaide/vendor/docx/image/gif.py new file mode 100644 index 0000000..e164872 --- /dev/null +++ b/intelliaide/vendor/docx/image/gif.py @@ -0,0 +1,38 @@ +from struct import Struct + +from .constants import MIME_TYPE +from .image import BaseImageHeader + + +class Gif(BaseImageHeader): + """Image header parser for GIF images. + + Note that the GIF format does not support resolution (DPI) information. Both + horizontal and vertical DPI default to 72. + """ + + @classmethod + def from_stream(cls, stream): + """Return |Gif| instance having header properties parsed from GIF image in + `stream`.""" + px_width, px_height = cls._dimensions_from_stream(stream) + return cls(px_width, px_height, 72, 72) + + @property + def content_type(self): + """MIME content type for this image, unconditionally `image/gif` for GIF + images.""" + return MIME_TYPE.GIF + + @property + def default_ext(self): + """Default filename extension, always 'gif' for GIF images.""" + return "gif" + + @classmethod + def _dimensions_from_stream(cls, stream): + stream.seek(6) + bytes_ = stream.read(4) + struct = Struct("L" + return self._read_int(fmt, base, offset) + + def read_short(self, base, offset=0): + """Return the int value of the two bytes at the file position determined by + `base` and `offset`, similarly to ``read_long()`` above.""" + fmt = b"H" + return self._read_int(fmt, base, offset) + + def read_str(self, char_count, base, offset=0): + """Return a string containing the `char_count` bytes at the file position + determined by self._base_offset + `base` + `offset`.""" + + def str_struct(char_count): + format_ = "%ds" % char_count + return Struct(format_) + + struct = str_struct(char_count) + chars = self._unpack_item(struct, base, offset) + unicode_str = chars.decode("UTF-8") + return unicode_str + + def seek(self, base, offset=0): + location = self._base_offset + base + offset + self._stream.seek(location) + + def tell(self): + """Allow pass-through tell() call.""" + return self._stream.tell() + + def _read_bytes(self, byte_count, base, offset): + self.seek(base, offset) + bytes_ = self._stream.read(byte_count) + if len(bytes_) < byte_count: + raise UnexpectedEndOfFileError + return bytes_ + + def _read_int(self, fmt, base, offset): + struct = Struct(fmt) + return self._unpack_item(struct, base, offset) + + def _unpack_item(self, struct, base, offset): + bytes_ = self._read_bytes(struct.size, base, offset) + return struct.unpack(bytes_)[0] diff --git a/intelliaide/vendor/docx/image/image.py b/intelliaide/vendor/docx/image/image.py new file mode 100644 index 0000000..e5e7f8a --- /dev/null +++ b/intelliaide/vendor/docx/image/image.py @@ -0,0 +1,234 @@ +"""Provides objects that can characterize image streams. + +That characterization is as to content type and size, as a required step in including +them in a document. +""" + +from __future__ import annotations + +import hashlib +import io +import os +from typing import IO, Tuple + +from docx.image.exceptions import UnrecognizedImageError +from docx.shared import Emu, Inches, Length, lazyproperty + + +class Image: + """Graphical image stream such as JPEG, PNG, or GIF with properties and methods + required by ImagePart.""" + + def __init__(self, blob: bytes, filename: str, image_header: BaseImageHeader): + super(Image, self).__init__() + self._blob = blob + self._filename = filename + self._image_header = image_header + + @classmethod + def from_blob(cls, blob: bytes) -> Image: + """Return a new |Image| subclass instance parsed from the image binary contained + in `blob`.""" + stream = io.BytesIO(blob) + return cls._from_stream(stream, blob) + + @classmethod + def from_file(cls, image_descriptor: str | IO[bytes]): + """Return a new |Image| subclass instance loaded from the image file identified + by `image_descriptor`, a path or file-like object.""" + if isinstance(image_descriptor, str): + path = image_descriptor + with open(path, "rb") as f: + blob = f.read() + stream = io.BytesIO(blob) + filename = os.path.basename(path) + else: + stream = image_descriptor + stream.seek(0) + blob = stream.read() + filename = None + return cls._from_stream(stream, blob, filename) + + @property + def blob(self): + """The bytes of the image 'file'.""" + return self._blob + + @property + def content_type(self) -> str: + """MIME content type for this image, e.g. ``'image/jpeg'`` for a JPEG image.""" + return self._image_header.content_type + + @lazyproperty + def ext(self): + """The file extension for the image. + + If an actual one is available from a load filename it is used. Otherwise a + canonical extension is assigned based on the content type. Does not contain the + leading period, e.g. 'jpg', not '.jpg'. + """ + return os.path.splitext(self._filename)[1][1:] + + @property + def filename(self): + """Original image file name, if loaded from disk, or a generic filename if + loaded from an anonymous stream.""" + return self._filename + + @property + def px_width(self) -> int: + """The horizontal pixel dimension of the image.""" + return self._image_header.px_width + + @property + def px_height(self) -> int: + """The vertical pixel dimension of the image.""" + return self._image_header.px_height + + @property + def horz_dpi(self) -> int: + """Integer dots per inch for the width of this image. + + Defaults to 72 when not present in the file, as is often the case. + """ + return self._image_header.horz_dpi + + @property + def vert_dpi(self) -> int: + """Integer dots per inch for the height of this image. + + Defaults to 72 when not present in the file, as is often the case. + """ + return self._image_header.vert_dpi + + @property + def width(self) -> Inches: + """A |Length| value representing the native width of the image, calculated from + the values of `px_width` and `horz_dpi`.""" + return Inches(self.px_width / self.horz_dpi) + + @property + def height(self) -> Inches: + """A |Length| value representing the native height of the image, calculated from + the values of `px_height` and `vert_dpi`.""" + return Inches(self.px_height / self.vert_dpi) + + def scaled_dimensions( + self, width: int | Length | None = None, height: int | Length | None = None + ) -> Tuple[Length, Length]: + """(cx, cy) pair representing scaled dimensions of this image. + + The native dimensions of the image are scaled by applying the following rules to + the `width` and `height` arguments. + + * If both `width` and `height` are specified, the return value is (`width`, + `height`); no scaling is performed. + * If only one is specified, it is used to compute a scaling factor that is then + applied to the unspecified dimension, preserving the aspect ratio of the image. + * If both `width` and `height` are |None|, the native dimensions are returned. + + The native dimensions are calculated using the dots-per-inch (dpi) value + embedded in the image, defaulting to 72 dpi if no value is specified, as is + often the case. The returned values are both |Length| objects. + """ + if width is None and height is None: + return self.width, self.height + + if width is None: + assert height is not None + scaling_factor = float(height) / float(self.height) + width = round(self.width * scaling_factor) + + if height is None: + scaling_factor = float(width) / float(self.width) + height = round(self.height * scaling_factor) + + return Emu(width), Emu(height) + + @lazyproperty + def sha1(self): + """SHA1 hash digest of the image blob.""" + return hashlib.sha1(self._blob).hexdigest() + + @classmethod + def _from_stream( + cls, + stream: IO[bytes], + blob: bytes, + filename: str | None = None, + ) -> Image: + """Return an instance of the |Image| subclass corresponding to the format of the + image in `stream`.""" + image_header = _ImageHeaderFactory(stream) + if filename is None: + filename = "image.%s" % image_header.default_ext + return cls(blob, filename, image_header) + + +def _ImageHeaderFactory(stream: IO[bytes]): + """A |BaseImageHeader| subclass instance that can parse headers of image in `stream`.""" + from docx.image import SIGNATURES + + def read_32(stream: IO[bytes]): + stream.seek(0) + return stream.read(32) + + header = read_32(stream) + for cls, offset, signature_bytes in SIGNATURES: + end = offset + len(signature_bytes) + found_bytes = header[offset:end] + if found_bytes == signature_bytes: + return cls.from_stream(stream) + raise UnrecognizedImageError + + +class BaseImageHeader: + """Base class for image header subclasses like |Jpeg| and |Tiff|.""" + + def __init__(self, px_width: int, px_height: int, horz_dpi: int, vert_dpi: int): + self._px_width = px_width + self._px_height = px_height + self._horz_dpi = horz_dpi + self._vert_dpi = vert_dpi + + @property + def content_type(self) -> str: + """Abstract property definition, must be implemented by all subclasses.""" + msg = "content_type property must be implemented by all subclasses of BaseImageHeader" + raise NotImplementedError(msg) + + @property + def default_ext(self) -> str: + """Default filename extension for images of this type. + + An abstract property definition, must be implemented by all subclasses. + """ + raise NotImplementedError( + "default_ext property must be implemented by all subclasses of BaseImageHeader" + ) + + @property + def px_width(self): + """The horizontal pixel dimension of the image.""" + return self._px_width + + @property + def px_height(self): + """The vertical pixel dimension of the image.""" + return self._px_height + + @property + def horz_dpi(self): + """Integer dots per inch for the width of this image. + + Defaults to 72 when not present in the file, as is often the case. + """ + return self._horz_dpi + + @property + def vert_dpi(self): + """Integer dots per inch for the height of this image. + + Defaults to 72 when not present in the file, as is often the case. + """ + return self._vert_dpi diff --git a/intelliaide/vendor/docx/image/jpeg.py b/intelliaide/vendor/docx/image/jpeg.py new file mode 100644 index 0000000..74da518 --- /dev/null +++ b/intelliaide/vendor/docx/image/jpeg.py @@ -0,0 +1,425 @@ +"""Objects related to parsing headers of JPEG image streams. + +Includes both JFIF and Exif sub-formats. +""" + +import io + +from docx.image.constants import JPEG_MARKER_CODE, MIME_TYPE +from docx.image.helpers import BIG_ENDIAN, StreamReader +from docx.image.image import BaseImageHeader +from docx.image.tiff import Tiff + + +class Jpeg(BaseImageHeader): + """Base class for JFIF and EXIF subclasses.""" + + @property + def content_type(self): + """MIME content type for this image, unconditionally `image/jpeg` for JPEG + images.""" + return MIME_TYPE.JPEG + + @property + def default_ext(self): + """Default filename extension, always 'jpg' for JPG images.""" + return "jpg" + + +class Exif(Jpeg): + """Image header parser for Exif image format.""" + + @classmethod + def from_stream(cls, stream): + """Return |Exif| instance having header properties parsed from Exif image in + `stream`.""" + markers = _JfifMarkers.from_stream(stream) + # print('\n%s' % markers) + + px_width = markers.sof.px_width + px_height = markers.sof.px_height + horz_dpi = markers.app1.horz_dpi + vert_dpi = markers.app1.vert_dpi + + return cls(px_width, px_height, horz_dpi, vert_dpi) + + +class Jfif(Jpeg): + """Image header parser for JFIF image format.""" + + @classmethod + def from_stream(cls, stream): + """Return a |Jfif| instance having header properties parsed from image in + `stream`.""" + markers = _JfifMarkers.from_stream(stream) + + px_width = markers.sof.px_width + px_height = markers.sof.px_height + horz_dpi = markers.app0.horz_dpi + vert_dpi = markers.app0.vert_dpi + + return cls(px_width, px_height, horz_dpi, vert_dpi) + + +class _JfifMarkers: + """Sequence of markers in a JPEG file, perhaps truncated at first SOS marker for + performance reasons.""" + + def __init__(self, markers): + super(_JfifMarkers, self).__init__() + self._markers = list(markers) + + def __str__(self): # pragma: no cover + """Returns a tabular listing of the markers in this instance, which can be handy + for debugging and perhaps other uses.""" + header = " offset seglen mc name\n======= ====== == =====" + tmpl = "%7d %6d %02X %s" + rows = [] + for marker in self._markers: + rows.append( + tmpl + % ( + marker.offset, + marker.segment_length, + ord(marker.marker_code), + marker.name, + ) + ) + lines = [header] + rows + return "\n".join(lines) + + @classmethod + def from_stream(cls, stream): + """Return a |_JfifMarkers| instance containing a |_JfifMarker| subclass instance + for each marker in `stream`.""" + marker_parser = _MarkerParser.from_stream(stream) + markers = [] + for marker in marker_parser.iter_markers(): + markers.append(marker) + if marker.marker_code == JPEG_MARKER_CODE.SOS: + break + return cls(markers) + + @property + def app0(self): + """First APP0 marker in image markers.""" + for m in self._markers: + if m.marker_code == JPEG_MARKER_CODE.APP0: + return m + raise KeyError("no APP0 marker in image") + + @property + def app1(self): + """First APP1 marker in image markers.""" + for m in self._markers: + if m.marker_code == JPEG_MARKER_CODE.APP1: + return m + raise KeyError("no APP1 marker in image") + + @property + def sof(self): + """First start of frame (SOFn) marker in this sequence.""" + for m in self._markers: + if m.marker_code in JPEG_MARKER_CODE.SOF_MARKER_CODES: + return m + raise KeyError("no start of frame (SOFn) marker in image") + + +class _MarkerParser: + """Service class that knows how to parse a JFIF stream and iterate over its + markers.""" + + def __init__(self, stream_reader): + super(_MarkerParser, self).__init__() + self._stream = stream_reader + + @classmethod + def from_stream(cls, stream): + """Return a |_MarkerParser| instance to parse JFIF markers from `stream`.""" + stream_reader = StreamReader(stream, BIG_ENDIAN) + return cls(stream_reader) + + def iter_markers(self): + """Generate a (marker_code, segment_offset) 2-tuple for each marker in the JPEG + `stream`, in the order they occur in the stream.""" + marker_finder = _MarkerFinder.from_stream(self._stream) + start = 0 + marker_code = None + while marker_code != JPEG_MARKER_CODE.EOI: + marker_code, segment_offset = marker_finder.next(start) + marker = _MarkerFactory(marker_code, self._stream, segment_offset) + yield marker + start = segment_offset + marker.segment_length + + +class _MarkerFinder: + """Service class that knows how to find the next JFIF marker in a stream.""" + + def __init__(self, stream): + super(_MarkerFinder, self).__init__() + self._stream = stream + + @classmethod + def from_stream(cls, stream): + """Return a |_MarkerFinder| instance to find JFIF markers in `stream`.""" + return cls(stream) + + def next(self, start): + """Return a (marker_code, segment_offset) 2-tuple identifying and locating the + first marker in `stream` occuring after offset `start`. + + The returned `segment_offset` points to the position immediately following the + 2-byte marker code, the start of the marker segment, for those markers that have + a segment. + """ + position = start + while True: + # skip over any non-\xFF bytes + position = self._offset_of_next_ff_byte(start=position) + # skip over any \xFF padding bytes + position, byte_ = self._next_non_ff_byte(start=position + 1) + # 'FF 00' sequence is not a marker, start over if found + if byte_ == b"\x00": + continue + # this is a marker, gather return values and break out of scan + marker_code, segment_offset = byte_, position + 1 + break + return marker_code, segment_offset + + def _next_non_ff_byte(self, start): + """Return an offset, byte 2-tuple for the next byte in `stream` that is not + '\xff', starting with the byte at offset `start`. + + If the byte at offset `start` is not '\xff', `start` and the returned `offset` + will be the same. + """ + self._stream.seek(start) + byte_ = self._read_byte() + while byte_ == b"\xff": + byte_ = self._read_byte() + offset_of_non_ff_byte = self._stream.tell() - 1 + return offset_of_non_ff_byte, byte_ + + def _offset_of_next_ff_byte(self, start): + """Return the offset of the next '\xff' byte in `stream` starting with the byte + at offset `start`. + + Returns `start` if the byte at that offset is a hex 255; it does not necessarily + advance in the stream. + """ + self._stream.seek(start) + byte_ = self._read_byte() + while byte_ != b"\xff": + byte_ = self._read_byte() + offset_of_ff_byte = self._stream.tell() - 1 + return offset_of_ff_byte + + def _read_byte(self): + """Return the next byte read from stream. + + Raise Exception if stream is at end of file. + """ + byte_ = self._stream.read(1) + if not byte_: # pragma: no cover + raise Exception("unexpected end of file") + return byte_ + + +def _MarkerFactory(marker_code, stream, offset): + """Return |_Marker| or subclass instance appropriate for marker at `offset` in + `stream` having `marker_code`.""" + if marker_code == JPEG_MARKER_CODE.APP0: + marker_cls = _App0Marker + elif marker_code == JPEG_MARKER_CODE.APP1: + marker_cls = _App1Marker + elif marker_code in JPEG_MARKER_CODE.SOF_MARKER_CODES: + marker_cls = _SofMarker + else: + marker_cls = _Marker + return marker_cls.from_stream(stream, marker_code, offset) + + +class _Marker: + """Base class for JFIF marker classes. + + Represents a marker and its segment occuring in a JPEG byte stream. + """ + + def __init__(self, marker_code, offset, segment_length): + super(_Marker, self).__init__() + self._marker_code = marker_code + self._offset = offset + self._segment_length = segment_length + + @classmethod + def from_stream(cls, stream, marker_code, offset): + """Return a generic |_Marker| instance for the marker at `offset` in `stream` + having `marker_code`.""" + if JPEG_MARKER_CODE.is_standalone(marker_code): + segment_length = 0 + else: + segment_length = stream.read_short(offset) + return cls(marker_code, offset, segment_length) + + @property + def marker_code(self): + """The single-byte code that identifies the type of this marker, e.g. ``'\xe0'`` + for start of image (SOI).""" + return self._marker_code + + @property + def name(self): # pragma: no cover + return JPEG_MARKER_CODE.marker_names[self._marker_code] + + @property + def offset(self): # pragma: no cover + return self._offset + + @property + def segment_length(self): + """The length in bytes of this marker's segment.""" + return self._segment_length + + +class _App0Marker(_Marker): + """Represents a JFIF APP0 marker segment.""" + + def __init__(self, marker_code, offset, length, density_units, x_density, y_density): + super(_App0Marker, self).__init__(marker_code, offset, length) + self._density_units = density_units + self._x_density = x_density + self._y_density = y_density + + @property + def horz_dpi(self): + """Horizontal dots per inch specified in this marker, defaults to 72 if not + specified.""" + return self._dpi(self._x_density) + + @property + def vert_dpi(self): + """Vertical dots per inch specified in this marker, defaults to 72 if not + specified.""" + return self._dpi(self._y_density) + + def _dpi(self, density): + """Return dots per inch corresponding to `density` value.""" + if self._density_units == 1: + dpi = density + elif self._density_units == 2: + dpi = int(round(density * 2.54)) + else: + dpi = 72 + return dpi + + @classmethod + def from_stream(cls, stream, marker_code, offset): + """Return an |_App0Marker| instance for the APP0 marker at `offset` in + `stream`.""" + # field off type notes + # ------------------ --- ----- ------------------- + # segment length 0 short + # JFIF identifier 2 5 chr 'JFIF\x00' + # major JPEG version 7 byte typically 1 + # minor JPEG version 8 byte typically 1 or 2 + # density units 9 byte 1=inches, 2=cm + # horz dots per unit 10 short + # vert dots per unit 12 short + # ------------------ --- ----- ------------------- + segment_length = stream.read_short(offset) + density_units = stream.read_byte(offset, 9) + x_density = stream.read_short(offset, 10) + y_density = stream.read_short(offset, 12) + return cls(marker_code, offset, segment_length, density_units, x_density, y_density) + + +class _App1Marker(_Marker): + """Represents a JFIF APP1 (Exif) marker segment.""" + + def __init__(self, marker_code, offset, length, horz_dpi, vert_dpi): + super(_App1Marker, self).__init__(marker_code, offset, length) + self._horz_dpi = horz_dpi + self._vert_dpi = vert_dpi + + @classmethod + def from_stream(cls, stream, marker_code, offset): + """Extract the horizontal and vertical dots-per-inch value from the APP1 header + at `offset` in `stream`.""" + # field off len type notes + # -------------------- --- --- ----- ---------------------------- + # segment length 0 2 short + # Exif identifier 2 6 6 chr 'Exif\x00\x00' + # TIFF byte order 8 2 2 chr 'II'=little 'MM'=big endian + # meaning of universe 10 2 2 chr '*\x00' or '\x00*' depending + # IFD0 off fr/II or MM 10 16 long relative to ...? + # -------------------- --- --- ----- ---------------------------- + segment_length = stream.read_short(offset) + if cls._is_non_Exif_APP1_segment(stream, offset): + return cls(marker_code, offset, segment_length, 72, 72) + tiff = cls._tiff_from_exif_segment(stream, offset, segment_length) + return cls(marker_code, offset, segment_length, tiff.horz_dpi, tiff.vert_dpi) + + @property + def horz_dpi(self): + """Horizontal dots per inch specified in this marker, defaults to 72 if not + specified.""" + return self._horz_dpi + + @property + def vert_dpi(self): + """Vertical dots per inch specified in this marker, defaults to 72 if not + specified.""" + return self._vert_dpi + + @classmethod + def _is_non_Exif_APP1_segment(cls, stream, offset): + """Return True if the APP1 segment at `offset` in `stream` is NOT an Exif + segment, as determined by the ``'Exif\x00\x00'`` signature at offset 2 in the + segment.""" + stream.seek(offset + 2) + exif_signature = stream.read(6) + return exif_signature != b"Exif\x00\x00" + + @classmethod + def _tiff_from_exif_segment(cls, stream, offset, segment_length): + """Return a |Tiff| instance parsed from the Exif APP1 segment of + `segment_length` at `offset` in `stream`.""" + # wrap full segment in its own stream and feed to Tiff() + stream.seek(offset + 8) + segment_bytes = stream.read(segment_length - 8) + substream = io.BytesIO(segment_bytes) + return Tiff.from_stream(substream) + + +class _SofMarker(_Marker): + """Represents a JFIF start of frame (SOFx) marker segment.""" + + def __init__(self, marker_code, offset, segment_length, px_width, px_height): + super(_SofMarker, self).__init__(marker_code, offset, segment_length) + self._px_width = px_width + self._px_height = px_height + + @classmethod + def from_stream(cls, stream, marker_code, offset): + """Return an |_SofMarker| instance for the SOFn marker at `offset` in stream.""" + # field off type notes + # ------------------ --- ----- ---------------------------- + # segment length 0 short + # Data precision 2 byte + # Vertical lines 3 short px_height + # Horizontal lines 5 short px_width + # ------------------ --- ----- ---------------------------- + segment_length = stream.read_short(offset) + px_height = stream.read_short(offset, 3) + px_width = stream.read_short(offset, 5) + return cls(marker_code, offset, segment_length, px_width, px_height) + + @property + def px_height(self): + """Image height in pixels.""" + return self._px_height + + @property + def px_width(self): + """Image width in pixels.""" + return self._px_width diff --git a/intelliaide/vendor/docx/image/png.py b/intelliaide/vendor/docx/image/png.py new file mode 100644 index 0000000..dd3cf81 --- /dev/null +++ b/intelliaide/vendor/docx/image/png.py @@ -0,0 +1,253 @@ +from .constants import MIME_TYPE, PNG_CHUNK_TYPE +from .exceptions import InvalidImageStreamError +from .helpers import BIG_ENDIAN, StreamReader +from .image import BaseImageHeader + + +class Png(BaseImageHeader): + """Image header parser for PNG images.""" + + @property + def content_type(self): + """MIME content type for this image, unconditionally `image/png` for PNG + images.""" + return MIME_TYPE.PNG + + @property + def default_ext(self): + """Default filename extension, always 'png' for PNG images.""" + return "png" + + @classmethod + def from_stream(cls, stream): + """Return a |Png| instance having header properties parsed from image in + `stream`.""" + parser = _PngParser.parse(stream) + + px_width = parser.px_width + px_height = parser.px_height + horz_dpi = parser.horz_dpi + vert_dpi = parser.vert_dpi + + return cls(px_width, px_height, horz_dpi, vert_dpi) + + +class _PngParser: + """Parses a PNG image stream to extract the image properties found in its chunks.""" + + def __init__(self, chunks): + super(_PngParser, self).__init__() + self._chunks = chunks + + @classmethod + def parse(cls, stream): + """Return a |_PngParser| instance containing the header properties parsed from + the PNG image in `stream`.""" + chunks = _Chunks.from_stream(stream) + return cls(chunks) + + @property + def px_width(self): + """The number of pixels in each row of the image.""" + IHDR = self._chunks.IHDR + return IHDR.px_width + + @property + def px_height(self): + """The number of stacked rows of pixels in the image.""" + IHDR = self._chunks.IHDR + return IHDR.px_height + + @property + def horz_dpi(self): + """Integer dots per inch for the width of this image. + + Defaults to 72 when not present in the file, as is often the case. + """ + pHYs = self._chunks.pHYs + if pHYs is None: + return 72 + return self._dpi(pHYs.units_specifier, pHYs.horz_px_per_unit) + + @property + def vert_dpi(self): + """Integer dots per inch for the height of this image. + + Defaults to 72 when not present in the file, as is often the case. + """ + pHYs = self._chunks.pHYs + if pHYs is None: + return 72 + return self._dpi(pHYs.units_specifier, pHYs.vert_px_per_unit) + + @staticmethod + def _dpi(units_specifier, px_per_unit): + """Return dots per inch value calculated from `units_specifier` and + `px_per_unit`.""" + if units_specifier == 1 and px_per_unit: + return int(round(px_per_unit * 0.0254)) + return 72 + + +class _Chunks: + """Collection of the chunks parsed from a PNG image stream.""" + + def __init__(self, chunk_iterable): + super(_Chunks, self).__init__() + self._chunks = list(chunk_iterable) + + @classmethod + def from_stream(cls, stream): + """Return a |_Chunks| instance containing the PNG chunks in `stream`.""" + chunk_parser = _ChunkParser.from_stream(stream) + chunks = list(chunk_parser.iter_chunks()) + return cls(chunks) + + @property + def IHDR(self): + """IHDR chunk in PNG image.""" + match = lambda chunk: chunk.type_name == PNG_CHUNK_TYPE.IHDR # noqa + IHDR = self._find_first(match) + if IHDR is None: + raise InvalidImageStreamError("no IHDR chunk in PNG image") + return IHDR + + @property + def pHYs(self): + """PHYs chunk in PNG image, or |None| if not present.""" + match = lambda chunk: chunk.type_name == PNG_CHUNK_TYPE.pHYs # noqa + return self._find_first(match) + + def _find_first(self, match): + """Return first chunk in stream order returning True for function `match`.""" + for chunk in self._chunks: + if match(chunk): + return chunk + return None + + +class _ChunkParser: + """Extracts chunks from a PNG image stream.""" + + def __init__(self, stream_rdr): + super(_ChunkParser, self).__init__() + self._stream_rdr = stream_rdr + + @classmethod + def from_stream(cls, stream): + """Return a |_ChunkParser| instance that can extract the chunks from the PNG + image in `stream`.""" + stream_rdr = StreamReader(stream, BIG_ENDIAN) + return cls(stream_rdr) + + def iter_chunks(self): + """Generate a |_Chunk| subclass instance for each chunk in this parser's PNG + stream, in the order encountered in the stream.""" + for chunk_type, offset in self._iter_chunk_offsets(): + chunk = _ChunkFactory(chunk_type, self._stream_rdr, offset) + yield chunk + + def _iter_chunk_offsets(self): + """Generate a (chunk_type, chunk_offset) 2-tuple for each of the chunks in the + PNG image stream. + + Iteration stops after the IEND chunk is returned. + """ + chunk_offset = 8 + while True: + chunk_data_len = self._stream_rdr.read_long(chunk_offset) + chunk_type = self._stream_rdr.read_str(4, chunk_offset, 4) + data_offset = chunk_offset + 8 + yield chunk_type, data_offset + if chunk_type == "IEND": + break + # incr offset for chunk len long, chunk type, chunk data, and CRC + chunk_offset += 4 + 4 + chunk_data_len + 4 + + +def _ChunkFactory(chunk_type, stream_rdr, offset): + """Return a |_Chunk| subclass instance appropriate to `chunk_type` parsed from + `stream_rdr` at `offset`.""" + chunk_cls_map = { + PNG_CHUNK_TYPE.IHDR: _IHDRChunk, + PNG_CHUNK_TYPE.pHYs: _pHYsChunk, + } + chunk_cls = chunk_cls_map.get(chunk_type, _Chunk) + return chunk_cls.from_offset(chunk_type, stream_rdr, offset) + + +class _Chunk: + """Base class for specific chunk types. + + Also serves as the default chunk type. + """ + + def __init__(self, chunk_type): + super(_Chunk, self).__init__() + self._chunk_type = chunk_type + + @classmethod + def from_offset(cls, chunk_type, stream_rdr, offset): + """Return a default _Chunk instance that only knows its chunk type.""" + return cls(chunk_type) + + @property + def type_name(self): + """The chunk type name, e.g. 'IHDR', 'pHYs', etc.""" + return self._chunk_type + + +class _IHDRChunk(_Chunk): + """IHDR chunk, contains the image dimensions.""" + + def __init__(self, chunk_type, px_width, px_height): + super(_IHDRChunk, self).__init__(chunk_type) + self._px_width = px_width + self._px_height = px_height + + @classmethod + def from_offset(cls, chunk_type, stream_rdr, offset): + """Return an _IHDRChunk instance containing the image dimensions extracted from + the IHDR chunk in `stream` at `offset`.""" + px_width = stream_rdr.read_long(offset) + px_height = stream_rdr.read_long(offset, 4) + return cls(chunk_type, px_width, px_height) + + @property + def px_width(self): + return self._px_width + + @property + def px_height(self): + return self._px_height + + +class _pHYsChunk(_Chunk): + """PYHs chunk, contains the image dpi information.""" + + def __init__(self, chunk_type, horz_px_per_unit, vert_px_per_unit, units_specifier): + super(_pHYsChunk, self).__init__(chunk_type) + self._horz_px_per_unit = horz_px_per_unit + self._vert_px_per_unit = vert_px_per_unit + self._units_specifier = units_specifier + + @classmethod + def from_offset(cls, chunk_type, stream_rdr, offset): + """Return a _pHYsChunk instance containing the image resolution extracted from + the pHYs chunk in `stream` at `offset`.""" + horz_px_per_unit = stream_rdr.read_long(offset) + vert_px_per_unit = stream_rdr.read_long(offset, 4) + units_specifier = stream_rdr.read_byte(offset, 8) + return cls(chunk_type, horz_px_per_unit, vert_px_per_unit, units_specifier) + + @property + def horz_px_per_unit(self): + return self._horz_px_per_unit + + @property + def vert_px_per_unit(self): + return self._vert_px_per_unit + + @property + def units_specifier(self): + return self._units_specifier diff --git a/intelliaide/vendor/docx/image/tiff.py b/intelliaide/vendor/docx/image/tiff.py new file mode 100644 index 0000000..1194929 --- /dev/null +++ b/intelliaide/vendor/docx/image/tiff.py @@ -0,0 +1,289 @@ +from .constants import MIME_TYPE, TIFF_FLD, TIFF_TAG +from .helpers import BIG_ENDIAN, LITTLE_ENDIAN, StreamReader +from .image import BaseImageHeader + + +class Tiff(BaseImageHeader): + """Image header parser for TIFF images. + + Handles both big and little endian byte ordering. + """ + + @property + def content_type(self): + """Return the MIME type of this TIFF image, unconditionally the string + ``image/tiff``.""" + return MIME_TYPE.TIFF + + @property + def default_ext(self): + """Default filename extension, always 'tiff' for TIFF images.""" + return "tiff" + + @classmethod + def from_stream(cls, stream): + """Return a |Tiff| instance containing the properties of the TIFF image in + `stream`.""" + parser = _TiffParser.parse(stream) + + px_width = parser.px_width + px_height = parser.px_height + horz_dpi = parser.horz_dpi + vert_dpi = parser.vert_dpi + + return cls(px_width, px_height, horz_dpi, vert_dpi) + + +class _TiffParser: + """Parses a TIFF image stream to extract the image properties found in its main + image file directory (IFD)""" + + def __init__(self, ifd_entries): + super(_TiffParser, self).__init__() + self._ifd_entries = ifd_entries + + @classmethod + def parse(cls, stream): + """Return an instance of |_TiffParser| containing the properties parsed from the + TIFF image in `stream`.""" + stream_rdr = cls._make_stream_reader(stream) + ifd0_offset = stream_rdr.read_long(4) + ifd_entries = _IfdEntries.from_stream(stream_rdr, ifd0_offset) + return cls(ifd_entries) + + @property + def horz_dpi(self): + """The horizontal dots per inch value calculated from the XResolution and + ResolutionUnit tags of the IFD; defaults to 72 if those tags are not present.""" + return self._dpi(TIFF_TAG.X_RESOLUTION) + + @property + def vert_dpi(self): + """The vertical dots per inch value calculated from the XResolution and + ResolutionUnit tags of the IFD; defaults to 72 if those tags are not present.""" + return self._dpi(TIFF_TAG.Y_RESOLUTION) + + @property + def px_height(self): + """The number of stacked rows of pixels in the image, |None| if the IFD contains + no ``ImageLength`` tag, the expected case when the TIFF is embeded in an Exif + image.""" + return self._ifd_entries.get(TIFF_TAG.IMAGE_LENGTH) + + @property + def px_width(self): + """The number of pixels in each row in the image, |None| if the IFD contains no + ``ImageWidth`` tag, the expected case when the TIFF is embeded in an Exif + image.""" + return self._ifd_entries.get(TIFF_TAG.IMAGE_WIDTH) + + @classmethod + def _detect_endian(cls, stream): + """Return either BIG_ENDIAN or LITTLE_ENDIAN depending on the endian indicator + found in the TIFF `stream` header, either 'MM' or 'II'.""" + stream.seek(0) + endian_str = stream.read(2) + return BIG_ENDIAN if endian_str == b"MM" else LITTLE_ENDIAN + + def _dpi(self, resolution_tag): + """Return the dpi value calculated for `resolution_tag`, which can be either + TIFF_TAG.X_RESOLUTION or TIFF_TAG.Y_RESOLUTION. + + The calculation is based on the values of both that tag and the + TIFF_TAG.RESOLUTION_UNIT tag in this parser's |_IfdEntries| instance. + """ + ifd_entries = self._ifd_entries + + if resolution_tag not in ifd_entries: + return 72 + + # resolution unit defaults to inches (2) + resolution_unit = ifd_entries.get(TIFF_TAG.RESOLUTION_UNIT, 2) + + if resolution_unit == 1: # aspect ratio only + return 72 + # resolution_unit == 2 for inches, 3 for centimeters + units_per_inch = 1 if resolution_unit == 2 else 2.54 + dots_per_unit = ifd_entries[resolution_tag] + return int(round(dots_per_unit * units_per_inch)) + + @classmethod + def _make_stream_reader(cls, stream): + """Return a |StreamReader| instance with wrapping `stream` and having "endian- + ness" determined by the 'MM' or 'II' indicator in the TIFF stream header.""" + endian = cls._detect_endian(stream) + return StreamReader(stream, endian) + + +class _IfdEntries: + """Image File Directory for a TIFF image, having mapping (dict) semantics allowing + "tag" values to be retrieved by tag code.""" + + def __init__(self, entries): + super(_IfdEntries, self).__init__() + self._entries = entries + + def __contains__(self, key): + """Provides ``in`` operator, e.g. ``tag in ifd_entries``""" + return self._entries.__contains__(key) + + def __getitem__(self, key): + """Provides indexed access, e.g. ``tag_value = ifd_entries[tag_code]``""" + return self._entries.__getitem__(key) + + @classmethod + def from_stream(cls, stream, offset): + """Return a new |_IfdEntries| instance parsed from `stream` starting at + `offset`.""" + ifd_parser = _IfdParser(stream, offset) + entries = {e.tag: e.value for e in ifd_parser.iter_entries()} + return cls(entries) + + def get(self, tag_code, default=None): + """Return value of IFD entry having tag matching `tag_code`, or `default` if no + matching tag found.""" + return self._entries.get(tag_code, default) + + +class _IfdParser: + """Service object that knows how to extract directory entries from an Image File + Directory (IFD)""" + + def __init__(self, stream_rdr, offset): + super(_IfdParser, self).__init__() + self._stream_rdr = stream_rdr + self._offset = offset + + def iter_entries(self): + """Generate an |_IfdEntry| instance corresponding to each entry in the + directory.""" + for idx in range(self._entry_count): + dir_entry_offset = self._offset + 2 + (idx * 12) + ifd_entry = _IfdEntryFactory(self._stream_rdr, dir_entry_offset) + yield ifd_entry + + @property + def _entry_count(self): + """The count of directory entries, read from the top of the IFD header.""" + return self._stream_rdr.read_short(self._offset) + + +def _IfdEntryFactory(stream_rdr, offset): + """Return an |_IfdEntry| subclass instance containing the value of the directory + entry at `offset` in `stream_rdr`.""" + ifd_entry_classes = { + TIFF_FLD.ASCII: _AsciiIfdEntry, + TIFF_FLD.SHORT: _ShortIfdEntry, + TIFF_FLD.LONG: _LongIfdEntry, + TIFF_FLD.RATIONAL: _RationalIfdEntry, + } + field_type = stream_rdr.read_short(offset, 2) + EntryCls = ifd_entry_classes.get(field_type, _IfdEntry) + return EntryCls.from_stream(stream_rdr, offset) + + +class _IfdEntry: + """Base class for IFD entry classes. + + Subclasses are differentiated by value type, e.g. ASCII, long int, etc. + """ + + def __init__(self, tag_code, value): + super(_IfdEntry, self).__init__() + self._tag_code = tag_code + self._value = value + + @classmethod + def from_stream(cls, stream_rdr, offset): + """Return an |_IfdEntry| subclass instance containing the tag and value of the + tag parsed from `stream_rdr` at `offset`. + + Note this method is common to all subclasses. Override the ``_parse_value()`` + method to provide distinctive behavior based on field type. + """ + tag_code = stream_rdr.read_short(offset, 0) + value_count = stream_rdr.read_long(offset, 4) + value_offset = stream_rdr.read_long(offset, 8) + value = cls._parse_value(stream_rdr, offset, value_count, value_offset) + return cls(tag_code, value) + + @classmethod + def _parse_value(cls, stream_rdr, offset, value_count, value_offset): + """Return the value of this field parsed from `stream_rdr` at `offset`. + + Intended to be overridden by subclasses. + """ + return "UNIMPLEMENTED FIELD TYPE" # pragma: no cover + + @property + def tag(self): + """Short int code that identifies this IFD entry.""" + return self._tag_code + + @property + def value(self): + """Value of this tag, its type being dependent on the tag.""" + return self._value + + +class _AsciiIfdEntry(_IfdEntry): + """IFD entry having the form of a NULL-terminated ASCII string.""" + + @classmethod + def _parse_value(cls, stream_rdr, offset, value_count, value_offset): + """Return the ASCII string parsed from `stream_rdr` at `value_offset`. + + The length of the string, including a terminating '\x00' (NUL) character, is in + `value_count`. + """ + return stream_rdr.read_str(value_count - 1, value_offset) + + +class _ShortIfdEntry(_IfdEntry): + """IFD entry expressed as a short (2-byte) integer.""" + + @classmethod + def _parse_value(cls, stream_rdr, offset, value_count, value_offset): + """Return the short int value contained in the `value_offset` field of this + entry. + + Only supports single values at present. + """ + if value_count == 1: + return stream_rdr.read_short(offset, 8) + else: # pragma: no cover + return "Multi-value short integer NOT IMPLEMENTED" + + +class _LongIfdEntry(_IfdEntry): + """IFD entry expressed as a long (4-byte) integer.""" + + @classmethod + def _parse_value(cls, stream_rdr, offset, value_count, value_offset): + """Return the long int value contained in the `value_offset` field of this + entry. + + Only supports single values at present. + """ + if value_count == 1: + return stream_rdr.read_long(offset, 8) + else: # pragma: no cover + return "Multi-value long integer NOT IMPLEMENTED" + + +class _RationalIfdEntry(_IfdEntry): + """IFD entry expressed as a numerator, denominator pair.""" + + @classmethod + def _parse_value(cls, stream_rdr, offset, value_count, value_offset): + """Return the rational (numerator / denominator) value at `value_offset` in + `stream_rdr` as a floating-point number. + + Only supports single values at present. + """ + if value_count == 1: + numerator = stream_rdr.read_long(value_offset) + denominator = stream_rdr.read_long(value_offset, 4) + return numerator / denominator + else: # pragma: no cover + return "Multi-value Rational NOT IMPLEMENTED" diff --git a/intelliaide/vendor/docx/opc/__init__.py b/intelliaide/vendor/docx/opc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/intelliaide/vendor/docx/opc/constants.py b/intelliaide/vendor/docx/opc/constants.py new file mode 100644 index 0000000..a3d0e08 --- /dev/null +++ b/intelliaide/vendor/docx/opc/constants.py @@ -0,0 +1,306 @@ +"""Constant values related to the Open Packaging Convention. + +In particular it includes content types and relationship types. +""" + + +class CONTENT_TYPE: + """Content type URIs (like MIME-types) that specify a part's format.""" + + BMP = "image/bmp" + DML_CHART = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml" + DML_CHARTSHAPES = "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml" + DML_DIAGRAM_COLORS = "application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml" + DML_DIAGRAM_DATA = "application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml" + DML_DIAGRAM_LAYOUT = "application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml" + DML_DIAGRAM_STYLE = "application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml" + GIF = "image/gif" + JPEG = "image/jpeg" + MS_PHOTO = "image/vnd.ms-photo" + OFC_CUSTOM_PROPERTIES = "application/vnd.openxmlformats-officedocument.custom-properties+xml" + OFC_CUSTOM_XML_PROPERTIES = ( + "application/vnd.openxmlformats-officedocument.customXmlProperties+xml" + ) + OFC_DRAWING = "application/vnd.openxmlformats-officedocument.drawing+xml" + OFC_EXTENDED_PROPERTIES = ( + "application/vnd.openxmlformats-officedocument.extended-properties+xml" + ) + OFC_OLE_OBJECT = "application/vnd.openxmlformats-officedocument.oleObject" + OFC_PACKAGE = "application/vnd.openxmlformats-officedocument.package" + OFC_THEME = "application/vnd.openxmlformats-officedocument.theme+xml" + OFC_THEME_OVERRIDE = "application/vnd.openxmlformats-officedocument.themeOverride+xml" + OFC_VML_DRAWING = "application/vnd.openxmlformats-officedocument.vmlDrawing" + OPC_CORE_PROPERTIES = "application/vnd.openxmlformats-package.core-properties+xml" + OPC_DIGITAL_SIGNATURE_CERTIFICATE = ( + "application/vnd.openxmlformats-package.digital-signature-certificate" + ) + OPC_DIGITAL_SIGNATURE_ORIGIN = "application/vnd.openxmlformats-package.digital-signature-origin" + OPC_DIGITAL_SIGNATURE_XMLSIGNATURE = ( + "application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml" + ) + OPC_RELATIONSHIPS = "application/vnd.openxmlformats-package.relationships+xml" + PML_COMMENTS = "application/vnd.openxmlformats-officedocument.presentationml.comments+xml" + PML_COMMENT_AUTHORS = ( + "application/vnd.openxmlformats-officedocument.presentationml.commentAuthors+xml" + ) + PML_HANDOUT_MASTER = ( + "application/vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml" + ) + PML_NOTES_MASTER = ( + "application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml" + ) + PML_NOTES_SLIDE = "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml" + PML_PRESENTATION_MAIN = ( + "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml" + ) + PML_PRES_PROPS = "application/vnd.openxmlformats-officedocument.presentationml.presProps+xml" + PML_PRINTER_SETTINGS = ( + "application/vnd.openxmlformats-officedocument.presentationml.printerSettings" + ) + PML_SLIDE = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml" + PML_SLIDESHOW_MAIN = ( + "application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml" + ) + PML_SLIDE_LAYOUT = ( + "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml" + ) + PML_SLIDE_MASTER = ( + "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml" + ) + PML_SLIDE_UPDATE_INFO = ( + "application/vnd.openxmlformats-officedocument.presentationml.slideUpdateInfo+xml" + ) + PML_TABLE_STYLES = ( + "application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml" + ) + PML_TAGS = "application/vnd.openxmlformats-officedocument.presentationml.tags+xml" + PML_TEMPLATE_MAIN = ( + "application/vnd.openxmlformats-officedocument.presentationml.template.main+xml" + ) + PML_VIEW_PROPS = "application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml" + PNG = "image/png" + SML_CALC_CHAIN = "application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml" + SML_CHARTSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml" + SML_COMMENTS = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml" + SML_CONNECTIONS = "application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml" + SML_CUSTOM_PROPERTY = ( + "application/vnd.openxmlformats-officedocument.spreadsheetml.customProperty" + ) + SML_DIALOGSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml" + SML_EXTERNAL_LINK = ( + "application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml" + ) + SML_PIVOT_CACHE_DEFINITION = ( + "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml" + ) + SML_PIVOT_CACHE_RECORDS = ( + "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml" + ) + SML_PIVOT_TABLE = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml" + SML_PRINTER_SETTINGS = ( + "application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings" + ) + SML_QUERY_TABLE = "application/vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml" + SML_REVISION_HEADERS = ( + "application/vnd.openxmlformats-officedocument.spreadsheetml.revisionHeaders+xml" + ) + SML_REVISION_LOG = "application/vnd.openxmlformats-officedocument.spreadsheetml.revisionLog+xml" + SML_SHARED_STRINGS = ( + "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml" + ) + SML_SHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + SML_SHEET_MAIN = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" + SML_SHEET_METADATA = ( + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml" + ) + SML_STYLES = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" + SML_TABLE = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml" + SML_TABLE_SINGLE_CELLS = ( + "application/vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells+xml" + ) + SML_TEMPLATE_MAIN = ( + "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml" + ) + SML_USER_NAMES = "application/vnd.openxmlformats-officedocument.spreadsheetml.userNames+xml" + SML_VOLATILE_DEPENDENCIES = ( + "application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml" + ) + SML_WORKSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" + TIFF = "image/tiff" + WML_COMMENTS = "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml" + WML_DOCUMENT = "application/vnd.openxmlformats-officedocument.wordprocessingml.document" + WML_DOCUMENT_GLOSSARY = ( + "application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml" + ) + WML_DOCUMENT_MAIN = ( + "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" + ) + WML_ENDNOTES = "application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml" + WML_FONT_TABLE = "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml" + WML_FOOTER = "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml" + WML_FOOTNOTES = "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml" + WML_HEADER = "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml" + WML_NUMBERING = "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml" + WML_PRINTER_SETTINGS = ( + "application/vnd.openxmlformats-officedocument.wordprocessingml.printerSettings" + ) + WML_SETTINGS = "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml" + WML_STYLES = "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml" + WML_WEB_SETTINGS = ( + "application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml" + ) + XML = "application/xml" + X_EMF = "image/x-emf" + X_FONTDATA = "application/x-fontdata" + X_FONT_TTF = "application/x-font-ttf" + X_WMF = "image/x-wmf" + + +class NAMESPACE: + """Constant values for OPC XML namespaces.""" + + DML_WORDPROCESSING_DRAWING = ( + "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" + ) + OFC_RELATIONSHIPS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships" + OPC_RELATIONSHIPS = "http://schemas.openxmlformats.org/package/2006/relationships" + OPC_CONTENT_TYPES = "http://schemas.openxmlformats.org/package/2006/content-types" + WML_MAIN = "http://schemas.openxmlformats.org/wordprocessingml/2006/main" + + +class RELATIONSHIP_TARGET_MODE: + """Open XML relationship target modes.""" + + EXTERNAL = "External" + INTERNAL = "Internal" + + +class RELATIONSHIP_TYPE: + AUDIO = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/audio" + A_F_CHUNK = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/aFChunk" + CALC_CHAIN = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/calcChain" + CERTIFICATE = ( + "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/certificate" + ) + CHART = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart" + CHARTSHEET = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet" + CHART_USER_SHAPES = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartUserShapes" + ) + COMMENTS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments" + COMMENT_AUTHORS = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/commentAuthors" + ) + CONNECTIONS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/connections" + CONTROL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/control" + CORE_PROPERTIES = ( + "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" + ) + CUSTOM_PROPERTIES = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties" + ) + CUSTOM_PROPERTY = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/customProperty" + ) + CUSTOM_XML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXml" + CUSTOM_XML_PROPS = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXmlProps" + ) + DIAGRAM_COLORS = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramColors" + ) + DIAGRAM_DATA = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramData" + DIAGRAM_LAYOUT = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramLayout" + ) + DIAGRAM_QUICK_STYLE = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/diagramQuickStyle" + ) + DIALOGSHEET = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/dialogsheet" + DRAWING = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing" + ENDNOTES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes" + EXTENDED_PROPERTIES = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" + ) + EXTERNAL_LINK = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/externalLink" + ) + FONT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/font" + FONT_TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" + FOOTER = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" + FOOTNOTES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes" + GLOSSARY_DOCUMENT = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/glossaryDocument" + ) + HANDOUT_MASTER = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/handoutMaster" + ) + HEADER = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" + HYPERLINK = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" + IMAGE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" + NOTES_MASTER = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster" + NOTES_SLIDE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide" + NUMBERING = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering" + OFFICE_DOCUMENT = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" + ) + OLE_OBJECT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject" + ORIGIN = "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin" + PACKAGE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/package" + PIVOT_CACHE_DEFINITION = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition" + ) + PIVOT_CACHE_RECORDS = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships" + "/spreadsheetml/pivotCacheRecords" + ) + PIVOT_TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable" + PRES_PROPS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/presProps" + PRINTER_SETTINGS = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings" + ) + QUERY_TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/queryTable" + REVISION_HEADERS = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/revisionHeaders" + ) + REVISION_LOG = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/revisionLog" + SETTINGS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings" + SHARED_STRINGS = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" + ) + SHEET_METADATA = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMetadata" + ) + SIGNATURE = ( + "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/signature" + ) + SLIDE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" + SLIDE_LAYOUT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" + SLIDE_MASTER = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" + SLIDE_UPDATE_INFO = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideUpdateInfo" + ) + STYLES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" + TABLE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table" + TABLE_SINGLE_CELLS = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableSingleCells" + ) + TABLE_STYLES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableStyles" + TAGS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tags" + THEME = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" + THEME_OVERRIDE = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/themeOverride" + ) + THUMBNAIL = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail" + USERNAMES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/usernames" + VIDEO = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/video" + VIEW_PROPS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/viewProps" + VML_DRAWING = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing" + VOLATILE_DEPENDENCIES = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/volatileDependencies" + ) + WEB_SETTINGS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings" + WORKSHEET_SOURCE = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheetSource" + ) + XML_MAPS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/xmlMaps" diff --git a/intelliaide/vendor/docx/opc/coreprops.py b/intelliaide/vendor/docx/opc/coreprops.py new file mode 100644 index 0000000..62f0c5a --- /dev/null +++ b/intelliaide/vendor/docx/opc/coreprops.py @@ -0,0 +1,142 @@ +"""Provides CoreProperties, Dublin-Core attributes of the document. + +These are broadly-standardized attributes like author, last-modified, etc. +""" + +from __future__ import annotations + +import datetime as dt +from typing import TYPE_CHECKING + +from docx.oxml.coreprops import CT_CoreProperties + +if TYPE_CHECKING: + from docx.oxml.coreprops import CT_CoreProperties + + +class CoreProperties: + """Corresponds to part named ``/docProps/core.xml``, containing the core document + properties for this document package.""" + + def __init__(self, element: CT_CoreProperties): + self._element = element + + @property + def author(self): + return self._element.author_text + + @author.setter + def author(self, value: str): + self._element.author_text = value + + @property + def category(self): + return self._element.category_text + + @category.setter + def category(self, value: str): + self._element.category_text = value + + @property + def comments(self): + return self._element.comments_text + + @comments.setter + def comments(self, value: str): + self._element.comments_text = value + + @property + def content_status(self): + return self._element.contentStatus_text + + @content_status.setter + def content_status(self, value: str): + self._element.contentStatus_text = value + + @property + def created(self): + return self._element.created_datetime + + @created.setter + def created(self, value: dt.datetime): + self._element.created_datetime = value + + @property + def identifier(self): + return self._element.identifier_text + + @identifier.setter + def identifier(self, value: str): + self._element.identifier_text = value + + @property + def keywords(self): + return self._element.keywords_text + + @keywords.setter + def keywords(self, value: str): + self._element.keywords_text = value + + @property + def language(self): + return self._element.language_text + + @language.setter + def language(self, value: str): + self._element.language_text = value + + @property + def last_modified_by(self): + return self._element.lastModifiedBy_text + + @last_modified_by.setter + def last_modified_by(self, value: str): + self._element.lastModifiedBy_text = value + + @property + def last_printed(self): + return self._element.lastPrinted_datetime + + @last_printed.setter + def last_printed(self, value: dt.datetime): + self._element.lastPrinted_datetime = value + + @property + def modified(self): + return self._element.modified_datetime + + @modified.setter + def modified(self, value: dt.datetime): + self._element.modified_datetime = value + + @property + def revision(self): + return self._element.revision_number + + @revision.setter + def revision(self, value: int): + self._element.revision_number = value + + @property + def subject(self): + return self._element.subject_text + + @subject.setter + def subject(self, value: str): + self._element.subject_text = value + + @property + def title(self): + return self._element.title_text + + @title.setter + def title(self, value: str): + self._element.title_text = value + + @property + def version(self): + return self._element.version_text + + @version.setter + def version(self, value: str): + self._element.version_text = value diff --git a/intelliaide/vendor/docx/opc/exceptions.py b/intelliaide/vendor/docx/opc/exceptions.py new file mode 100644 index 0000000..c5583d3 --- /dev/null +++ b/intelliaide/vendor/docx/opc/exceptions.py @@ -0,0 +1,12 @@ +"""Exceptions specific to python-opc. + +The base exception class is OpcError. +""" + + +class OpcError(Exception): + """Base error class for python-opc.""" + + +class PackageNotFoundError(OpcError): + """Raised when a package cannot be found at the specified path.""" diff --git a/intelliaide/vendor/docx/opc/oxml.py b/intelliaide/vendor/docx/opc/oxml.py new file mode 100644 index 0000000..7d3c489 --- /dev/null +++ b/intelliaide/vendor/docx/opc/oxml.py @@ -0,0 +1,247 @@ +# pyright: reportPrivateUsage=false + +"""Temporary stand-in for main oxml module. + +This module came across with the PackageReader transplant. Probably much will get +replaced with objects from the pptx.oxml.core and then this module will either get +deleted or only hold the package related custom element classes. +""" + +from __future__ import annotations + +from typing import cast + +from lxml import etree + +from docx.opc.constants import NAMESPACE as NS +from docx.opc.constants import RELATIONSHIP_TARGET_MODE as RTM + +# configure XML parser +element_class_lookup = etree.ElementNamespaceClassLookup() +oxml_parser = etree.XMLParser(remove_blank_text=True, resolve_entities=False) +oxml_parser.set_element_class_lookup(element_class_lookup) + +nsmap = { + "ct": NS.OPC_CONTENT_TYPES, + "pr": NS.OPC_RELATIONSHIPS, + "r": NS.OFC_RELATIONSHIPS, +} + + +# =========================================================================== +# functions +# =========================================================================== + + +def parse_xml(text: str) -> etree._Element: + """`etree.fromstring()` replacement that uses oxml parser.""" + return etree.fromstring(text, oxml_parser) + + +def qn(tag: str) -> str: + """Stands for "qualified name", a utility function to turn a namespace prefixed tag + name into a Clark-notation qualified tag name for lxml. + + For + example, ``qn('p:cSld')`` returns ``'{http://schemas.../main}cSld'``. + """ + prefix, tagroot = tag.split(":") + uri = nsmap[prefix] + return "{%s}%s" % (uri, tagroot) + + +def serialize_part_xml(part_elm: etree._Element) -> bytes: + """Serialize `part_elm` etree element to XML suitable for storage as an XML part. + + That is to say, no insignificant whitespace added for readability, and an + appropriate XML declaration added with UTF-8 encoding specified. + """ + return etree.tostring(part_elm, encoding="UTF-8", standalone=True) + + +def serialize_for_reading(element: etree._Element) -> str: + """Serialize `element` to human-readable XML suitable for tests. + + No XML declaration. + """ + return etree.tostring(element, encoding="unicode", pretty_print=True) + + +# =========================================================================== +# Custom element classes +# =========================================================================== + + +class BaseOxmlElement(etree.ElementBase): + """Base class for all custom element classes, to add standardized behavior to all + classes in one place.""" + + @property + def xml(self) -> str: + """Return XML string for this element, suitable for testing purposes. + + Pretty printed for readability and without an XML declaration at the top. + """ + return serialize_for_reading(self) + + +class CT_Default(BaseOxmlElement): + """`` element that appears in `[Content_Types].xml` part. + + Used to specify a default content type to be applied to any part with the specified extension. + """ + + @property + def content_type(self): + """String held in the ``ContentType`` attribute of this ```` + element.""" + return self.get("ContentType") + + @property + def extension(self): + """String held in the ``Extension`` attribute of this ```` element.""" + return self.get("Extension") + + @staticmethod + def new(ext: str, content_type: str): + """Return a new ```` element with attributes set to parameter values.""" + xml = '' % nsmap["ct"] + default = parse_xml(xml) + default.set("Extension", ext) + default.set("ContentType", content_type) + return default + + +class CT_Override(BaseOxmlElement): + """```` element, specifying the content type to be applied for a part with + the specified partname.""" + + @property + def content_type(self): + """String held in the ``ContentType`` attribute of this ```` + element.""" + return self.get("ContentType") + + @staticmethod + def new(partname, content_type): + """Return a new ```` element with attributes set to parameter values.""" + xml = '' % nsmap["ct"] + override = parse_xml(xml) + override.set("PartName", partname) + override.set("ContentType", content_type) + return override + + @property + def partname(self): + """String held in the ``PartName`` attribute of this ```` element.""" + return self.get("PartName") + + +class CT_Relationship(BaseOxmlElement): + """`` element, representing a single relationship from source to target part.""" + + @staticmethod + def new(rId: str, reltype: str, target: str, target_mode: str = RTM.INTERNAL): + """Return a new ```` element.""" + xml = '' % nsmap["pr"] + relationship = parse_xml(xml) + relationship.set("Id", rId) + relationship.set("Type", reltype) + relationship.set("Target", target) + if target_mode == RTM.EXTERNAL: + relationship.set("TargetMode", RTM.EXTERNAL) + return relationship + + @property + def rId(self): + """String held in the ``Id`` attribute of this ```` element.""" + return self.get("Id") + + @property + def reltype(self): + """String held in the ``Type`` attribute of this ```` element.""" + return self.get("Type") + + @property + def target_ref(self): + """String held in the ``Target`` attribute of this ```` + element.""" + return self.get("Target") + + @property + def target_mode(self): + """String held in the ``TargetMode`` attribute of this ```` + element, either ``Internal`` or ``External``. + + Defaults to ``Internal``. + """ + return self.get("TargetMode", RTM.INTERNAL) + + +class CT_Relationships(BaseOxmlElement): + """```` element, the root element in a .rels file.""" + + def add_rel(self, rId: str, reltype: str, target: str, is_external: bool = False): + """Add a child ```` element with attributes set according to + parameter values.""" + target_mode = RTM.EXTERNAL if is_external else RTM.INTERNAL + relationship = CT_Relationship.new(rId, reltype, target, target_mode) + self.append(relationship) + + @staticmethod + def new() -> CT_Relationships: + """Return a new ```` element.""" + xml = '' % nsmap["pr"] + return cast(CT_Relationships, parse_xml(xml)) + + @property + def Relationship_lst(self): + """Return a list containing all the ```` child elements.""" + return self.findall(qn("pr:Relationship")) + + @property + def xml(self): + """Return XML string for this element, suitable for saving in a .rels stream, + not pretty printed and with an XML declaration at the top.""" + return serialize_part_xml(self) + + +class CT_Types(BaseOxmlElement): + """```` element, the container element for Default and Override elements in + [Content_Types].xml.""" + + def add_default(self, ext, content_type): + """Add a child ```` element with attributes set to parameter values.""" + default = CT_Default.new(ext, content_type) + self.append(default) + + def add_override(self, partname, content_type): + """Add a child ```` element with attributes set to parameter + values.""" + override = CT_Override.new(partname, content_type) + self.append(override) + + @property + def defaults(self): + return self.findall(qn("ct:Default")) + + @staticmethod + def new(): + """Return a new ```` element.""" + xml = '' % nsmap["ct"] + types = parse_xml(xml) + return types + + @property + def overrides(self): + return self.findall(qn("ct:Override")) + + +ct_namespace = element_class_lookup.get_namespace(nsmap["ct"]) +ct_namespace["Default"] = CT_Default +ct_namespace["Override"] = CT_Override +ct_namespace["Types"] = CT_Types + +pr_namespace = element_class_lookup.get_namespace(nsmap["pr"]) +pr_namespace["Relationship"] = CT_Relationship +pr_namespace["Relationships"] = CT_Relationships diff --git a/intelliaide/vendor/docx/opc/package.py b/intelliaide/vendor/docx/opc/package.py new file mode 100644 index 0000000..3c1cdca --- /dev/null +++ b/intelliaide/vendor/docx/opc/package.py @@ -0,0 +1,219 @@ +"""Objects that implement reading and writing OPC packages.""" + +from __future__ import annotations + +from typing import IO, TYPE_CHECKING, Iterator, cast + +from docx.opc.constants import RELATIONSHIP_TYPE as RT +from docx.opc.packuri import PACKAGE_URI, PackURI +from docx.opc.part import PartFactory +from docx.opc.parts.coreprops import CorePropertiesPart +from docx.opc.pkgreader import PackageReader +from docx.opc.pkgwriter import PackageWriter +from docx.opc.rel import Relationships +from docx.shared import lazyproperty + +if TYPE_CHECKING: + from typing_extensions import Self + + from docx.opc.coreprops import CoreProperties + from docx.opc.part import Part + from docx.opc.rel import _Relationship # pyright: ignore[reportPrivateUsage] + + +class OpcPackage: + """Main API class for |python-opc|. + + A new instance is constructed by calling the :meth:`open` class method with a path + to a package file or file-like object containing one. + """ + + def after_unmarshal(self): + """Entry point for any post-unmarshaling processing. + + May be overridden by subclasses without forwarding call to super. + """ + # don't place any code here, just catch call if not overridden by + # subclass + pass + + @property + def core_properties(self) -> CoreProperties: + """|CoreProperties| object providing read/write access to the Dublin Core + properties for this document.""" + return self._core_properties_part.core_properties + + def iter_rels(self) -> Iterator[_Relationship]: + """Generate exactly one reference to each relationship in the package by + performing a depth-first traversal of the rels graph.""" + + def walk_rels( + source: OpcPackage | Part, visited: list[Part] | None = None + ) -> Iterator[_Relationship]: + visited = [] if visited is None else visited + for rel in source.rels.values(): + yield rel + if rel.is_external: + continue + part = rel.target_part + if part in visited: + continue + visited.append(part) + new_source = part + for rel in walk_rels(new_source, visited): + yield rel + + for rel in walk_rels(self): + yield rel + + def iter_parts(self) -> Iterator[Part]: + """Generate exactly one reference to each of the parts in the package by + performing a depth-first traversal of the rels graph.""" + + def walk_parts(source, visited=[]): + for rel in source.rels.values(): + if rel.is_external: + continue + part = rel.target_part + if part in visited: + continue + visited.append(part) + yield part + new_source = part + for part in walk_parts(new_source, visited): + yield part + + for part in walk_parts(self): + yield part + + def load_rel(self, reltype: str, target: Part | str, rId: str, is_external: bool = False): + """Return newly added |_Relationship| instance of `reltype` between this part + and `target` with key `rId`. + + Target mode is set to ``RTM.EXTERNAL`` if `is_external` is |True|. Intended for + use during load from a serialized package, where the rId is well known. Other + methods exist for adding a new relationship to the package during processing. + """ + return self.rels.add_relationship(reltype, target, rId, is_external) + + @property + def main_document_part(self): + """Return a reference to the main document part for this package. + + Examples include a document part for a WordprocessingML package, a presentation + part for a PresentationML package, or a workbook part for a SpreadsheetML + package. + """ + return self.part_related_by(RT.OFFICE_DOCUMENT) + + def next_partname(self, template: str) -> PackURI: + """Return a |PackURI| instance representing partname matching `template`. + + The returned part-name has the next available numeric suffix to distinguish it + from other parts of its type. `template` is a printf (%)-style template string + containing a single replacement item, a '%d' to be used to insert the integer + portion of the partname. Example: "/word/header%d.xml" + """ + partnames = {part.partname for part in self.iter_parts()} + for n in range(1, len(partnames) + 2): + candidate_partname = template % n + if candidate_partname not in partnames: + return PackURI(candidate_partname) + + @classmethod + def open(cls, pkg_file: str | IO[bytes]) -> Self: + """Return an |OpcPackage| instance loaded with the contents of `pkg_file`.""" + pkg_reader = PackageReader.from_file(pkg_file) + package = cls() + Unmarshaller.unmarshal(pkg_reader, package, PartFactory) + return package + + def part_related_by(self, reltype: str) -> Part: + """Return part to which this package has a relationship of `reltype`. + + Raises |KeyError| if no such relationship is found and |ValueError| if more than + one such relationship is found. + """ + return self.rels.part_with_reltype(reltype) + + @property + def parts(self) -> list[Part]: + """Return a list containing a reference to each of the parts in this package.""" + return list(self.iter_parts()) + + def relate_to(self, part: Part, reltype: str): + """Return rId key of new or existing relationship to `part`. + + If a relationship of `reltype` to `part` already exists, its rId is returned. Otherwise a + new relationship is created and that rId is returned. + """ + rel = self.rels.get_or_add(reltype, part) + return rel.rId + + @lazyproperty + def rels(self): + """Return a reference to the |Relationships| instance holding the collection of + relationships for this package.""" + return Relationships(PACKAGE_URI.baseURI) + + def save(self, pkg_file: str | IO[bytes]): + """Save this package to `pkg_file`. + + `pkg_file` can be either a file-path or a file-like object. + """ + for part in self.parts: + part.before_marshal() + PackageWriter.write(pkg_file, self.rels, self.parts) + + @property + def _core_properties_part(self) -> CorePropertiesPart: + """|CorePropertiesPart| object related to this package. + + Creates a default core properties part if one is not present (not common). + """ + try: + return cast(CorePropertiesPart, self.part_related_by(RT.CORE_PROPERTIES)) + except KeyError: + core_properties_part = CorePropertiesPart.default(self) + self.relate_to(core_properties_part, RT.CORE_PROPERTIES) + return core_properties_part + + +class Unmarshaller: + """Hosts static methods for unmarshalling a package from a |PackageReader|.""" + + @staticmethod + def unmarshal(pkg_reader, package, part_factory): + """Construct graph of parts and realized relationships based on the contents of + `pkg_reader`, delegating construction of each part to `part_factory`. + + Package relationships are added to `pkg`. + """ + parts = Unmarshaller._unmarshal_parts(pkg_reader, package, part_factory) + Unmarshaller._unmarshal_relationships(pkg_reader, package, parts) + for part in parts.values(): + part.after_unmarshal() + package.after_unmarshal() + + @staticmethod + def _unmarshal_parts(pkg_reader, package, part_factory): + """Return a dictionary of |Part| instances unmarshalled from `pkg_reader`, keyed + by partname. + + Side-effect is that each part in `pkg_reader` is constructed using + `part_factory`. + """ + parts = {} + for partname, content_type, reltype, blob in pkg_reader.iter_sparts(): + parts[partname] = part_factory(partname, content_type, reltype, blob, package) + return parts + + @staticmethod + def _unmarshal_relationships(pkg_reader, package, parts): + """Add a relationship to the source object corresponding to each of the + relationships in `pkg_reader` with its target_part set to the actual target part + in `parts`.""" + for source_uri, srel in pkg_reader.iter_srels(): + source = package if source_uri == "/" else parts[source_uri] + target = srel.target_ref if srel.is_external else parts[srel.target_partname] + source.load_rel(srel.reltype, target, srel.rId, srel.is_external) diff --git a/intelliaide/vendor/docx/opc/packuri.py b/intelliaide/vendor/docx/opc/packuri.py new file mode 100644 index 0000000..89437b1 --- /dev/null +++ b/intelliaide/vendor/docx/opc/packuri.py @@ -0,0 +1,109 @@ +"""Provides the PackURI value type. + +Also some useful known pack URI strings such as PACKAGE_URI. +""" + +from __future__ import annotations + +import posixpath +import re + + +class PackURI(str): + """Provides access to pack URI components such as the baseURI and the filename slice. + + Behaves as |str| otherwise. + """ + + _filename_re = re.compile("([a-zA-Z]+)([1-9][0-9]*)?") + + def __new__(cls, pack_uri_str: str): + if pack_uri_str[0] != "/": + tmpl = "PackURI must begin with slash, got '%s'" + raise ValueError(tmpl % pack_uri_str) + return str.__new__(cls, pack_uri_str) + + @staticmethod + def from_rel_ref(baseURI: str, relative_ref: str) -> PackURI: + """The absolute PackURI formed by translating `relative_ref` onto `baseURI`.""" + joined_uri = posixpath.join(baseURI, relative_ref) + abs_uri = posixpath.abspath(joined_uri) + return PackURI(abs_uri) + + @property + def baseURI(self) -> str: + """The base URI of this pack URI, the directory portion, roughly speaking. + + E.g. ``'/ppt/slides'`` for ``'/ppt/slides/slide1.xml'``. For the package pseudo- + partname '/', baseURI is '/'. + """ + return posixpath.split(self)[0] + + @property + def ext(self) -> str: + """The extension portion of this pack URI, e.g. ``'xml'`` for ``'/word/document.xml'``. + + Note the period is not included. + """ + # raw_ext is either empty string or starts with period, e.g. '.xml' + raw_ext = posixpath.splitext(self)[1] + return raw_ext[1:] if raw_ext.startswith(".") else raw_ext + + @property + def filename(self): + """The "filename" portion of this pack URI, e.g. ``'slide1.xml'`` for + ``'/ppt/slides/slide1.xml'``. + + For the package pseudo-partname '/', filename is ''. + """ + return posixpath.split(self)[1] + + @property + def idx(self): + """Return partname index as integer for tuple partname or None for singleton + partname, e.g. ``21`` for ``'/ppt/slides/slide21.xml'`` and |None| for + ``'/ppt/presentation.xml'``.""" + filename = self.filename + if not filename: + return None + name_part = posixpath.splitext(filename)[0] # filename w/ext removed + match = self._filename_re.match(name_part) + if match is None: + return None + if match.group(2): + return int(match.group(2)) + return None + + @property + def membername(self): + """The pack URI with the leading slash stripped off, the form used as the Zip + file membername for the package item. + + Returns '' for the package pseudo-partname '/'. + """ + return self[1:] + + def relative_ref(self, baseURI: str): + """Return string containing relative reference to package item from `baseURI`. + + E.g. PackURI('/ppt/slideLayouts/slideLayout1.xml') would return + '../slideLayouts/slideLayout1.xml' for baseURI '/ppt/slides'. + """ + # workaround for posixpath bug in 2.6, doesn't generate correct + # relative path when `start` (second) parameter is root ('/') + return self[1:] if baseURI == "/" else posixpath.relpath(self, baseURI) + + @property + def rels_uri(self): + """The pack URI of the .rels part corresponding to the current pack URI. + + Only produces sensible output if the pack URI is a partname or the package + pseudo-partname '/'. + """ + rels_filename = "%s.rels" % self.filename + rels_uri_str = posixpath.join(self.baseURI, "_rels", rels_filename) + return PackURI(rels_uri_str) + + +PACKAGE_URI = PackURI("/") +CONTENT_TYPES_URI = PackURI("/[Content_Types].xml") diff --git a/intelliaide/vendor/docx/opc/part.py b/intelliaide/vendor/docx/opc/part.py new file mode 100644 index 0000000..cbb4ab5 --- /dev/null +++ b/intelliaide/vendor/docx/opc/part.py @@ -0,0 +1,247 @@ +# pyright: reportImportCycles=false + +"""Open Packaging Convention (OPC) objects related to package parts.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable, Type, cast + +from docx.opc.oxml import serialize_part_xml +from docx.opc.packuri import PackURI +from docx.opc.rel import Relationships +from docx.opc.shared import cls_method_fn +from docx.oxml.parser import parse_xml +from docx.shared import lazyproperty + +if TYPE_CHECKING: + from docx.oxml.xmlchemy import BaseOxmlElement + from docx.package import Package + + +class Part: + """Base class for package parts. + + Provides common properties and methods, but intended to be subclassed in client code + to implement specific part behaviors. + """ + + def __init__( + self, + partname: PackURI, + content_type: str, + blob: bytes | None = None, + package: Package | None = None, + ): + super(Part, self).__init__() + self._partname = partname + self._content_type = content_type + self._blob = blob + self._package = package + + def after_unmarshal(self): + """Entry point for post-unmarshaling processing, for example to parse the part + XML. + + May be overridden by subclasses without forwarding call to super. + """ + # don't place any code here, just catch call if not overridden by + # subclass + pass + + def before_marshal(self): + """Entry point for pre-serialization processing, for example to finalize part + naming if necessary. + + May be overridden by subclasses without forwarding call to super. + """ + # don't place any code here, just catch call if not overridden by + # subclass + pass + + @property + def blob(self) -> bytes: + """Contents of this package part as a sequence of bytes. + + May be text or binary. Intended to be overridden by subclasses. Default behavior + is to return load blob. + """ + return self._blob or b"" + + @property + def content_type(self): + """Content type of this part.""" + return self._content_type + + def drop_rel(self, rId: str): + """Remove the relationship identified by `rId` if its reference count is less + than 2. + + Relationships with a reference count of 0 are implicit relationships. + """ + if self._rel_ref_count(rId) < 2: + del self.rels[rId] + + @classmethod + def load(cls, partname: PackURI, content_type: str, blob: bytes, package: Package): + return cls(partname, content_type, blob, package) + + def load_rel(self, reltype: str, target: Part | str, rId: str, is_external: bool = False): + """Return newly added |_Relationship| instance of `reltype`. + + The new relationship relates the `target` part to this part with key `rId`. + + Target mode is set to ``RTM.EXTERNAL`` if `is_external` is |True|. Intended for + use during load from a serialized package, where the rId is well-known. Other + methods exist for adding a new relationship to a part when manipulating a part. + """ + return self.rels.add_relationship(reltype, target, rId, is_external) + + @property + def package(self): + """|OpcPackage| instance this part belongs to.""" + return self._package + + @property + def partname(self): + """|PackURI| instance holding partname of this part, e.g. + '/ppt/slides/slide1.xml'.""" + return self._partname + + @partname.setter + def partname(self, partname: str): + if not isinstance(partname, PackURI): + tmpl = "partname must be instance of PackURI, got '%s'" + raise TypeError(tmpl % type(partname).__name__) + self._partname = partname + + def part_related_by(self, reltype: str) -> Part: + """Return part to which this part has a relationship of `reltype`. + + Raises |KeyError| if no such relationship is found and |ValueError| if more than + one such relationship is found. Provides ability to resolve implicitly related + part, such as Slide -> SlideLayout. + """ + return self.rels.part_with_reltype(reltype) + + def relate_to(self, target: Part | str, reltype: str, is_external: bool = False) -> str: + """Return rId key of relationship of `reltype` to `target`. + + The returned `rId` is from an existing relationship if there is one, otherwise a + new relationship is created. + """ + if is_external: + return self.rels.get_or_add_ext_rel(reltype, cast(str, target)) + else: + rel = self.rels.get_or_add(reltype, cast(Part, target)) + return rel.rId + + @property + def related_parts(self): + """Dictionary mapping related parts by rId, so child objects can resolve + explicit relationships present in the part XML, e.g. sldIdLst to a specific + |Slide| instance.""" + return self.rels.related_parts + + @lazyproperty + def rels(self): + """|Relationships| instance holding the relationships for this part.""" + # -- prevent breakage in `python-docx-template` by retaining legacy `._rels` attribute -- + self._rels = Relationships(self._partname.baseURI) + return self._rels + + def target_ref(self, rId: str) -> str: + """Return URL contained in target ref of relationship identified by `rId`.""" + rel = self.rels[rId] + return rel.target_ref + + def _rel_ref_count(self, rId: str) -> int: + """Return the count of references in this part to the relationship identified by `rId`. + + Only an XML part can contain references, so this is 0 for `Part`. + """ + return 0 + + +class PartFactory: + """Provides a way for client code to specify a subclass of |Part| to be constructed + by |Unmarshaller| based on its content type and/or a custom callable. + + Setting ``PartFactory.part_class_selector`` to a callable object will cause that + object to be called with the parameters ``content_type, reltype``, once for each + part in the package. If the callable returns an object, it is used as the class for + that part. If it returns |None|, part class selection falls back to the content type + map defined in ``PartFactory.part_type_for``. If no class is returned from either of + these, the class contained in ``PartFactory.default_part_type`` is used to construct + the part, which is by default ``opc.package.Part``. + """ + + part_class_selector: Callable[[str, str], Type[Part] | None] | None + part_type_for: dict[str, Type[Part]] = {} + default_part_type = Part + + def __new__( + cls, + partname: PackURI, + content_type: str, + reltype: str, + blob: bytes, + package: Package, + ): + PartClass: Type[Part] | None = None + if cls.part_class_selector is not None: + part_class_selector = cls_method_fn(cls, "part_class_selector") + PartClass = part_class_selector(content_type, reltype) + if PartClass is None: + PartClass = cls._part_cls_for(content_type) + return PartClass.load(partname, content_type, blob, package) + + @classmethod + def _part_cls_for(cls, content_type: str): + """Return the custom part class registered for `content_type`, or the default + part class if no custom class is registered for `content_type`.""" + if content_type in cls.part_type_for: + return cls.part_type_for[content_type] + return cls.default_part_type + + +class XmlPart(Part): + """Base class for package parts containing an XML payload, which is most of them. + + Provides additional methods to the |Part| base class that take care of parsing and + reserializing the XML payload and managing relationships to other parts. + """ + + def __init__( + self, partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package + ): + super(XmlPart, self).__init__(partname, content_type, package=package) + self._element = element + + @property + def blob(self): + return serialize_part_xml(self._element) + + @property + def element(self): + """The root XML element of this XML part.""" + return self._element + + @classmethod + def load(cls, partname: PackURI, content_type: str, blob: bytes, package: Package): + element = parse_xml(blob) + return cls(partname, content_type, element, package) + + @property + def part(self): + """Part of the parent protocol, "children" of the document will not know the + part that contains them so must ask their parent object. + + That chain of delegation ends here for child objects. + """ + return self + + def _rel_ref_count(self, rId: str) -> int: + """Return the count of references in this part's XML to the relationship + identified by `rId`.""" + rIds = cast("list[str]", self._element.xpath("//@r:id")) + return len([_rId for _rId in rIds if _rId == rId]) diff --git a/intelliaide/vendor/docx/opc/parts/__init__.py b/intelliaide/vendor/docx/opc/parts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/intelliaide/vendor/docx/opc/parts/coreprops.py b/intelliaide/vendor/docx/opc/parts/coreprops.py new file mode 100644 index 0000000..fda0112 --- /dev/null +++ b/intelliaide/vendor/docx/opc/parts/coreprops.py @@ -0,0 +1,48 @@ +"""Core properties part, corresponds to ``/docProps/core.xml`` part in package.""" + +from __future__ import annotations + +import datetime as dt +from typing import TYPE_CHECKING + +from docx.opc.constants import CONTENT_TYPE as CT +from docx.opc.coreprops import CoreProperties +from docx.opc.packuri import PackURI +from docx.opc.part import XmlPart +from docx.oxml.coreprops import CT_CoreProperties + +if TYPE_CHECKING: + from docx.opc.package import OpcPackage + + +class CorePropertiesPart(XmlPart): + """Corresponds to part named ``/docProps/core.xml``. + + The "core" is short for "Dublin Core" and contains document metadata relatively common across + documents of all types, not just DOCX. + """ + + @classmethod + def default(cls, package: OpcPackage): + """Return a new |CorePropertiesPart| object initialized with default values for + its base properties.""" + core_properties_part = cls._new(package) + core_properties = core_properties_part.core_properties + core_properties.title = "Word Document" + core_properties.last_modified_by = "python-docx" + core_properties.revision = 1 + core_properties.modified = dt.datetime.now(dt.timezone.utc) + return core_properties_part + + @property + def core_properties(self): + """A |CoreProperties| object providing read/write access to the core properties + contained in this core properties part.""" + return CoreProperties(self.element) + + @classmethod + def _new(cls, package: OpcPackage) -> CorePropertiesPart: + partname = PackURI("/docProps/core.xml") + content_type = CT.OPC_CORE_PROPERTIES + coreProperties = CT_CoreProperties.new() + return CorePropertiesPart(partname, content_type, coreProperties, package) diff --git a/intelliaide/vendor/docx/opc/phys_pkg.py b/intelliaide/vendor/docx/opc/phys_pkg.py new file mode 100644 index 0000000..5ec3223 --- /dev/null +++ b/intelliaide/vendor/docx/opc/phys_pkg.py @@ -0,0 +1,119 @@ +"""Provides a general interface to a `physical` OPC package, such as a zip file.""" + +import os +from zipfile import ZIP_DEFLATED, ZipFile, is_zipfile + +from docx.opc.exceptions import PackageNotFoundError +from docx.opc.packuri import CONTENT_TYPES_URI + + +class PhysPkgReader: + """Factory for physical package reader objects.""" + + def __new__(cls, pkg_file): + # if `pkg_file` is a string, treat it as a path + if isinstance(pkg_file, str): + if os.path.isdir(pkg_file): + reader_cls = _DirPkgReader + elif is_zipfile(pkg_file): + reader_cls = _ZipPkgReader + else: + raise PackageNotFoundError("Package not found at '%s'" % pkg_file) + else: # assume it's a stream and pass it to Zip reader to sort out + reader_cls = _ZipPkgReader + + return super(PhysPkgReader, cls).__new__(reader_cls) + + +class PhysPkgWriter: + """Factory for physical package writer objects.""" + + def __new__(cls, pkg_file): + return super(PhysPkgWriter, cls).__new__(_ZipPkgWriter) + + +class _DirPkgReader(PhysPkgReader): + """Implements |PhysPkgReader| interface for an OPC package extracted into a + directory.""" + + def __init__(self, path): + """`path` is the path to a directory containing an expanded package.""" + super(_DirPkgReader, self).__init__() + self._path = os.path.abspath(path) + + def blob_for(self, pack_uri): + """Return contents of file corresponding to `pack_uri` in package directory.""" + path = os.path.join(self._path, pack_uri.membername) + with open(path, "rb") as f: + blob = f.read() + return blob + + def close(self): + """Provides interface consistency with |ZipFileSystem|, but does nothing, a + directory file system doesn't need closing.""" + pass + + @property + def content_types_xml(self): + """Return the `[Content_Types].xml` blob from the package.""" + return self.blob_for(CONTENT_TYPES_URI) + + def rels_xml_for(self, source_uri): + """Return rels item XML for source with `source_uri`, or None if the item has no + rels item.""" + try: + rels_xml = self.blob_for(source_uri.rels_uri) + except IOError: + rels_xml = None + return rels_xml + + +class _ZipPkgReader(PhysPkgReader): + """Implements |PhysPkgReader| interface for a zip file OPC package.""" + + def __init__(self, pkg_file): + super(_ZipPkgReader, self).__init__() + self._zipf = ZipFile(pkg_file, "r") + + def blob_for(self, pack_uri): + """Return blob corresponding to `pack_uri`. + + Raises |ValueError| if no matching member is present in zip archive. + """ + return self._zipf.read(pack_uri.membername) + + def close(self): + """Close the zip archive, releasing any resources it is using.""" + self._zipf.close() + + @property + def content_types_xml(self): + """Return the `[Content_Types].xml` blob from the zip package.""" + return self.blob_for(CONTENT_TYPES_URI) + + def rels_xml_for(self, source_uri): + """Return rels item XML for source with `source_uri` or None if no rels item is + present.""" + try: + rels_xml = self.blob_for(source_uri.rels_uri) + except KeyError: + rels_xml = None + return rels_xml + + +class _ZipPkgWriter(PhysPkgWriter): + """Implements |PhysPkgWriter| interface for a zip file OPC package.""" + + def __init__(self, pkg_file): + super(_ZipPkgWriter, self).__init__() + self._zipf = ZipFile(pkg_file, "w", compression=ZIP_DEFLATED) + + def close(self): + """Close the zip archive, flushing any pending physical writes and releasing any + resources it's using.""" + self._zipf.close() + + def write(self, pack_uri, blob): + """Write `blob` to this zip package with the membername corresponding to + `pack_uri`.""" + self._zipf.writestr(pack_uri.membername, blob) diff --git a/intelliaide/vendor/docx/opc/pkgreader.py b/intelliaide/vendor/docx/opc/pkgreader.py new file mode 100644 index 0000000..15207e5 --- /dev/null +++ b/intelliaide/vendor/docx/opc/pkgreader.py @@ -0,0 +1,254 @@ +"""Low-level, read-only API to a serialized Open Packaging Convention (OPC) package.""" + +from docx.opc.constants import RELATIONSHIP_TARGET_MODE as RTM +from docx.opc.oxml import parse_xml +from docx.opc.packuri import PACKAGE_URI, PackURI +from docx.opc.phys_pkg import PhysPkgReader +from docx.opc.shared import CaseInsensitiveDict + + +class PackageReader: + """Provides access to the contents of a zip-format OPC package via its + :attr:`serialized_parts` and :attr:`pkg_srels` attributes.""" + + def __init__(self, content_types, pkg_srels, sparts): + super(PackageReader, self).__init__() + self._pkg_srels = pkg_srels + self._sparts = sparts + + @staticmethod + def from_file(pkg_file): + """Return a |PackageReader| instance loaded with contents of `pkg_file`.""" + phys_reader = PhysPkgReader(pkg_file) + content_types = _ContentTypeMap.from_xml(phys_reader.content_types_xml) + pkg_srels = PackageReader._srels_for(phys_reader, PACKAGE_URI) + sparts = PackageReader._load_serialized_parts(phys_reader, pkg_srels, content_types) + phys_reader.close() + return PackageReader(content_types, pkg_srels, sparts) + + def iter_sparts(self): + """Generate a 4-tuple `(partname, content_type, reltype, blob)` for each of the + serialized parts in the package.""" + for s in self._sparts: + yield (s.partname, s.content_type, s.reltype, s.blob) + + def iter_srels(self): + """Generate a 2-tuple `(source_uri, srel)` for each of the relationships in the + package.""" + for srel in self._pkg_srels: + yield (PACKAGE_URI, srel) + for spart in self._sparts: + for srel in spart.srels: + yield (spart.partname, srel) + + @staticmethod + def _load_serialized_parts(phys_reader, pkg_srels, content_types): + """Return a list of |_SerializedPart| instances corresponding to the parts in + `phys_reader` accessible by walking the relationship graph starting with + `pkg_srels`.""" + sparts = [] + part_walker = PackageReader._walk_phys_parts(phys_reader, pkg_srels) + for partname, blob, reltype, srels in part_walker: + content_type = content_types[partname] + spart = _SerializedPart(partname, content_type, reltype, blob, srels) + sparts.append(spart) + return tuple(sparts) + + @staticmethod + def _srels_for(phys_reader, source_uri): + """Return |_SerializedRelationships| instance populated with relationships for + source identified by `source_uri`.""" + rels_xml = phys_reader.rels_xml_for(source_uri) + return _SerializedRelationships.load_from_xml(source_uri.baseURI, rels_xml) + + @staticmethod + def _walk_phys_parts(phys_reader, srels, visited_partnames=None): + """Generate a 4-tuple `(partname, blob, reltype, srels)` for each of the parts + in `phys_reader` by walking the relationship graph rooted at srels.""" + if visited_partnames is None: + visited_partnames = [] + for srel in srels: + if srel.is_external: + continue + partname = srel.target_partname + if partname in visited_partnames: + continue + visited_partnames.append(partname) + reltype = srel.reltype + part_srels = PackageReader._srels_for(phys_reader, partname) + blob = phys_reader.blob_for(partname) + yield (partname, blob, reltype, part_srels) + next_walker = PackageReader._walk_phys_parts(phys_reader, part_srels, visited_partnames) + for partname, blob, reltype, srels in next_walker: + yield (partname, blob, reltype, srels) + + +class _ContentTypeMap: + """Value type providing dictionary semantics for looking up content type by part + name, e.g. ``content_type = cti['/ppt/presentation.xml']``.""" + + def __init__(self): + super(_ContentTypeMap, self).__init__() + self._overrides = CaseInsensitiveDict() + self._defaults = CaseInsensitiveDict() + + def __getitem__(self, partname): + """Return content type for part identified by `partname`.""" + if not isinstance(partname, PackURI): + tmpl = "_ContentTypeMap key must be , got %s" + raise KeyError(tmpl % type(partname)) + if partname in self._overrides: + return self._overrides[partname] + if partname.ext in self._defaults: + return self._defaults[partname.ext] + tmpl = "no content type for partname '%s' in [Content_Types].xml" + raise KeyError(tmpl % partname) + + @staticmethod + def from_xml(content_types_xml): + """Return a new |_ContentTypeMap| instance populated with the contents of + `content_types_xml`.""" + types_elm = parse_xml(content_types_xml) + ct_map = _ContentTypeMap() + for o in types_elm.overrides: + ct_map._add_override(o.partname, o.content_type) + for d in types_elm.defaults: + ct_map._add_default(d.extension, d.content_type) + return ct_map + + def _add_default(self, extension, content_type): + """Add the default mapping of `extension` to `content_type` to this content type + mapping.""" + self._defaults[extension] = content_type + + def _add_override(self, partname, content_type): + """Add the default mapping of `partname` to `content_type` to this content type + mapping.""" + self._overrides[partname] = content_type + + +class _SerializedPart: + """Value object for an OPC package part. + + Provides access to the partname, content type, blob, and serialized relationships + for the part. + """ + + def __init__(self, partname, content_type, reltype, blob, srels): + super(_SerializedPart, self).__init__() + self._partname = partname + self._content_type = content_type + self._reltype = reltype + self._blob = blob + self._srels = srels + + @property + def partname(self): + return self._partname + + @property + def content_type(self): + return self._content_type + + @property + def blob(self): + return self._blob + + @property + def reltype(self): + """The referring relationship type of this part.""" + return self._reltype + + @property + def srels(self): + return self._srels + + +class _SerializedRelationship: + """Value object representing a serialized relationship in an OPC package. + + Serialized, in this case, means any target part is referred to via its partname + rather than a direct link to an in-memory |Part| object. + """ + + def __init__(self, baseURI, rel_elm): + super(_SerializedRelationship, self).__init__() + self._baseURI = baseURI + self._rId = rel_elm.rId + self._reltype = rel_elm.reltype + self._target_mode = rel_elm.target_mode + self._target_ref = rel_elm.target_ref + + @property + def is_external(self): + """True if target_mode is ``RTM.EXTERNAL``""" + return self._target_mode == RTM.EXTERNAL + + @property + def reltype(self): + """Relationship type, like ``RT.OFFICE_DOCUMENT``""" + return self._reltype + + @property + def rId(self): + """Relationship id, like 'rId9', corresponds to the ``Id`` attribute on the + ``CT_Relationship`` element.""" + return self._rId + + @property + def target_mode(self): + """String in ``TargetMode`` attribute of ``CT_Relationship`` element, one of + ``RTM.INTERNAL`` or ``RTM.EXTERNAL``.""" + return self._target_mode + + @property + def target_ref(self): + """String in ``Target`` attribute of ``CT_Relationship`` element, a relative + part reference for internal target mode or an arbitrary URI, e.g. an HTTP URL, + for external target mode.""" + return self._target_ref + + @property + def target_partname(self): + """|PackURI| instance containing partname targeted by this relationship. + + Raises ``ValueError`` on reference if target_mode is ``'External'``. Use + :attr:`target_mode` to check before referencing. + """ + if self.is_external: + msg = ( + "target_partname attribute on Relationship is undefined w" + 'here TargetMode == "External"' + ) + raise ValueError(msg) + # lazy-load _target_partname attribute + if not hasattr(self, "_target_partname"): + self._target_partname = PackURI.from_rel_ref(self._baseURI, self.target_ref) + return self._target_partname + + +class _SerializedRelationships: + """Read-only sequence of |_SerializedRelationship| instances corresponding to the + relationships item XML passed to constructor.""" + + def __init__(self): + super(_SerializedRelationships, self).__init__() + self._srels = [] + + def __iter__(self): + """Support iteration, e.g. 'for x in srels:'.""" + return self._srels.__iter__() + + @staticmethod + def load_from_xml(baseURI, rels_item_xml): + """Return |_SerializedRelationships| instance loaded with the relationships + contained in `rels_item_xml`. + + Returns an empty collection if `rels_item_xml` is |None|. + """ + srels = _SerializedRelationships() + if rels_item_xml is not None: + rels_elm = parse_xml(rels_item_xml) + for rel_elm in rels_elm.Relationship_lst: + srels._srels.append(_SerializedRelationship(baseURI, rel_elm)) + return srels diff --git a/intelliaide/vendor/docx/opc/pkgwriter.py b/intelliaide/vendor/docx/opc/pkgwriter.py new file mode 100644 index 0000000..e635169 --- /dev/null +++ b/intelliaide/vendor/docx/opc/pkgwriter.py @@ -0,0 +1,115 @@ +"""Provides low-level, write-only API to serialized (OPC) package. + +OPC stands for Open Packaging Convention. This is e, essentially an implementation of +OpcPackage.save(). +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Iterable + +from docx.opc.constants import CONTENT_TYPE as CT +from docx.opc.oxml import CT_Types, serialize_part_xml +from docx.opc.packuri import CONTENT_TYPES_URI, PACKAGE_URI +from docx.opc.phys_pkg import PhysPkgWriter +from docx.opc.shared import CaseInsensitiveDict +from docx.opc.spec import default_content_types + +if TYPE_CHECKING: + from docx.opc.part import Part + + +class PackageWriter: + """Writes a zip-format OPC package to `pkg_file`, where `pkg_file` can be either a + path to a zip file (a string) or a file-like object. + + Its single API method, :meth:`write`, is static, so this class is not intended to be + instantiated. + """ + + @staticmethod + def write(pkg_file, pkg_rels, parts): + """Write a physical package (.pptx file) to `pkg_file` containing `pkg_rels` and + `parts` and a content types stream based on the content types of the parts.""" + phys_writer = PhysPkgWriter(pkg_file) + PackageWriter._write_content_types_stream(phys_writer, parts) + PackageWriter._write_pkg_rels(phys_writer, pkg_rels) + PackageWriter._write_parts(phys_writer, parts) + phys_writer.close() + + @staticmethod + def _write_content_types_stream(phys_writer, parts): + """Write ``[Content_Types].xml`` part to the physical package with an + appropriate content type lookup target for each part in `parts`.""" + cti = _ContentTypesItem.from_parts(parts) + phys_writer.write(CONTENT_TYPES_URI, cti.blob) + + @staticmethod + def _write_parts(phys_writer: PhysPkgWriter, parts: Iterable[Part]): + """Write the blob of each part in `parts` to the package, along with a rels item + for its relationships if and only if it has any.""" + for part in parts: + phys_writer.write(part.partname, part.blob) + if len(part.rels): + phys_writer.write(part.partname.rels_uri, part.rels.xml) + + @staticmethod + def _write_pkg_rels(phys_writer, pkg_rels): + """Write the XML rels item for `pkg_rels` ('/_rels/.rels') to the package.""" + phys_writer.write(PACKAGE_URI.rels_uri, pkg_rels.xml) + + +class _ContentTypesItem: + """Service class that composes a content types item ([Content_Types].xml) based on a + list of parts. + + Not meant to be instantiated directly, its single interface method is xml_for(), + e.g. ``_ContentTypesItem.xml_for(parts)``. + """ + + def __init__(self): + self._defaults = CaseInsensitiveDict() + self._overrides = {} + + @property + def blob(self): + """Return XML form of this content types item, suitable for storage as + ``[Content_Types].xml`` in an OPC package.""" + return serialize_part_xml(self._element) + + @classmethod + def from_parts(cls, parts): + """Return content types XML mapping each part in `parts` to the appropriate + content type and suitable for storage as ``[Content_Types].xml`` in an OPC + package.""" + cti = cls() + cti._defaults["rels"] = CT.OPC_RELATIONSHIPS + cti._defaults["xml"] = CT.XML + for part in parts: + cti._add_content_type(part.partname, part.content_type) + return cti + + def _add_content_type(self, partname, content_type): + """Add a content type for the part with `partname` and `content_type`, using a + default or override as appropriate.""" + ext = partname.ext + if (ext.lower(), content_type) in default_content_types: + self._defaults[ext] = content_type + else: + self._overrides[partname] = content_type + + @property + def _element(self): + """Return XML form of this content types item, suitable for storage as + ``[Content_Types].xml`` in an OPC package. + + Although the sequence of elements is not strictly significant, as an aid to + testing and readability Default elements are sorted by extension and Override + elements are sorted by partname. + """ + _types_elm = CT_Types.new() + for ext in sorted(self._defaults.keys()): + _types_elm.add_default(ext, self._defaults[ext]) + for partname in sorted(self._overrides.keys()): + _types_elm.add_override(partname, self._overrides[partname]) + return _types_elm diff --git a/intelliaide/vendor/docx/opc/rel.py b/intelliaide/vendor/docx/opc/rel.py new file mode 100644 index 0000000..153b308 --- /dev/null +++ b/intelliaide/vendor/docx/opc/rel.py @@ -0,0 +1,153 @@ +"""Relationship-related objects.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, cast + +from docx.opc.oxml import CT_Relationships + +if TYPE_CHECKING: + from docx.opc.part import Part + + +class Relationships(Dict[str, "_Relationship"]): + """Collection object for |_Relationship| instances, having list semantics.""" + + def __init__(self, baseURI: str): + super(Relationships, self).__init__() + self._baseURI = baseURI + self._target_parts_by_rId: dict[str, Any] = {} + + def add_relationship( + self, reltype: str, target: Part | str, rId: str, is_external: bool = False + ) -> "_Relationship": + """Return a newly added |_Relationship| instance.""" + rel = _Relationship(rId, reltype, target, self._baseURI, is_external) + self[rId] = rel + if not is_external: + self._target_parts_by_rId[rId] = target + return rel + + def get_or_add(self, reltype: str, target_part: Part) -> _Relationship: + """Return relationship of `reltype` to `target_part`, newly added if not already + present in collection.""" + rel = self._get_matching(reltype, target_part) + if rel is None: + rId = self._next_rId + rel = self.add_relationship(reltype, target_part, rId) + return rel + + def get_or_add_ext_rel(self, reltype: str, target_ref: str) -> str: + """Return rId of external relationship of `reltype` to `target_ref`, newly added + if not already present in collection.""" + rel = self._get_matching(reltype, target_ref, is_external=True) + if rel is None: + rId = self._next_rId + rel = self.add_relationship(reltype, target_ref, rId, is_external=True) + return rel.rId + + def part_with_reltype(self, reltype: str) -> Part: + """Return target part of rel with matching `reltype`, raising |KeyError| if not + found and |ValueError| if more than one matching relationship is found.""" + rel = self._get_rel_of_type(reltype) + return rel.target_part + + @property + def related_parts(self): + """Dict mapping rIds to target parts for all the internal relationships in the + collection.""" + return self._target_parts_by_rId + + @property + def xml(self) -> str: + """Serialize this relationship collection into XML suitable for storage as a + .rels file in an OPC package.""" + rels_elm = CT_Relationships.new() + for rel in self.values(): + rels_elm.add_rel(rel.rId, rel.reltype, rel.target_ref, rel.is_external) + return rels_elm.xml + + def _get_matching( + self, reltype: str, target: Part | str, is_external: bool = False + ) -> _Relationship | None: + """Return relationship of matching `reltype`, `target`, and `is_external` from + collection, or None if not found.""" + + def matches(rel: _Relationship, reltype: str, target: Part | str, is_external: bool): + if rel.reltype != reltype: + return False + if rel.is_external != is_external: + return False + rel_target = rel.target_ref if rel.is_external else rel.target_part + return rel_target == target + + for rel in self.values(): + if matches(rel, reltype, target, is_external): + return rel + return None + + def _get_rel_of_type(self, reltype: str): + """Return single relationship of type `reltype` from the collection. + + Raises |KeyError| if no matching relationship is found. Raises |ValueError| if + more than one matching relationship is found. + """ + matching = [rel for rel in self.values() if rel.reltype == reltype] + if len(matching) == 0: + tmpl = "no relationship of type '%s' in collection" + raise KeyError(tmpl % reltype) + if len(matching) > 1: + tmpl = "multiple relationships of type '%s' in collection" + raise ValueError(tmpl % reltype) + return matching[0] + + @property + def _next_rId(self) -> str: # pyright: ignore[reportReturnType] + """Next available rId in collection, starting from 'rId1' and making use of any + gaps in numbering, e.g. 'rId2' for rIds ['rId1', 'rId3'].""" + for n in range(1, len(self) + 2): + rId_candidate = "rId%d" % n # like 'rId19' + if rId_candidate not in self: + return rId_candidate + + +class _Relationship: + """Value object for relationship to part.""" + + def __init__( + self, rId: str, reltype: str, target: Part | str, baseURI: str, external: bool = False + ): + super(_Relationship, self).__init__() + self._rId = rId + self._reltype = reltype + self._target = target + self._baseURI = baseURI + self._is_external = bool(external) + + @property + def is_external(self) -> bool: + return self._is_external + + @property + def reltype(self) -> str: + return self._reltype + + @property + def rId(self) -> str: + return self._rId + + @property + def target_part(self) -> Part: + if self._is_external: + raise ValueError( + "target_part property on _Relationship is undefined when target mode is External" + ) + return cast("Part", self._target) + + @property + def target_ref(self) -> str: + if self._is_external: + return cast(str, self._target) + else: + target = cast("Part", self._target) + return target.partname.relative_ref(self._baseURI) diff --git a/intelliaide/vendor/docx/opc/shared.py b/intelliaide/vendor/docx/opc/shared.py new file mode 100644 index 0000000..9d4c0a6 --- /dev/null +++ b/intelliaide/vendor/docx/opc/shared.py @@ -0,0 +1,31 @@ +"""Objects shared by opc modules.""" + +from __future__ import annotations + +from typing import Any, Dict, TypeVar + +_T = TypeVar("_T") + + +class CaseInsensitiveDict(Dict[str, Any]): + """Mapping type that behaves like dict except that it matches without respect to the + case of the key. + + E.g. cid['A'] == cid['a']. Note this is not general-purpose, just complete enough to + satisfy opc package needs. It assumes str keys, and that it is created empty; keys + passed in constructor are not accounted for + """ + + def __contains__(self, key): + return super(CaseInsensitiveDict, self).__contains__(key.lower()) + + def __getitem__(self, key): + return super(CaseInsensitiveDict, self).__getitem__(key.lower()) + + def __setitem__(self, key, value): + return super(CaseInsensitiveDict, self).__setitem__(key.lower(), value) + + +def cls_method_fn(cls: type, method_name: str): + """Return method of `cls` having `method_name`.""" + return getattr(cls, method_name) diff --git a/intelliaide/vendor/docx/opc/spec.py b/intelliaide/vendor/docx/opc/spec.py new file mode 100644 index 0000000..011a482 --- /dev/null +++ b/intelliaide/vendor/docx/opc/spec.py @@ -0,0 +1,24 @@ +"""Provides mappings that embody aspects of the Open XML spec ISO/IEC 29500.""" + +from docx.opc.constants import CONTENT_TYPE as CT + +default_content_types = ( + ("bin", CT.PML_PRINTER_SETTINGS), + ("bin", CT.SML_PRINTER_SETTINGS), + ("bin", CT.WML_PRINTER_SETTINGS), + ("bmp", CT.BMP), + ("emf", CT.X_EMF), + ("fntdata", CT.X_FONTDATA), + ("gif", CT.GIF), + ("jpe", CT.JPEG), + ("jpeg", CT.JPEG), + ("jpg", CT.JPEG), + ("png", CT.PNG), + ("rels", CT.OPC_RELATIONSHIPS), + ("tif", CT.TIFF), + ("tiff", CT.TIFF), + ("wdp", CT.MS_PHOTO), + ("wmf", CT.X_WMF), + ("xlsx", CT.SML_SHEET), + ("xml", CT.XML), +) diff --git a/intelliaide/vendor/docx/oxml/__init__.py b/intelliaide/vendor/docx/oxml/__init__.py new file mode 100644 index 0000000..37f608c --- /dev/null +++ b/intelliaide/vendor/docx/oxml/__init__.py @@ -0,0 +1,251 @@ +# ruff: noqa: E402, I001 + +"""Initializes oxml sub-package. + +This including registering custom element classes corresponding to Open XML elements. +""" + +from __future__ import annotations + +from docx.oxml.drawing import CT_Drawing +from docx.oxml.parser import OxmlElement, parse_xml, register_element_cls +from docx.oxml.shape import ( + CT_Anchor, + CT_Blip, + CT_BlipFillProperties, + CT_GraphicalObject, + CT_GraphicalObjectData, + CT_Inline, + CT_NonVisualDrawingProps, + CT_Picture, + CT_PictureNonVisual, + CT_Point2D, + CT_PositiveSize2D, + CT_ShapeProperties, + CT_Transform2D, +) +from docx.oxml.shared import CT_DecimalNumber, CT_OnOff, CT_String +from docx.oxml.text.hyperlink import CT_Hyperlink +from docx.oxml.text.pagebreak import CT_LastRenderedPageBreak +from docx.oxml.text.run import ( + CT_R, + CT_Br, + CT_Cr, + CT_NoBreakHyphen, + CT_PTab, + CT_Text, +) + +# -- `OxmlElement` and `parse_xml()` are not used in this module but several downstream +# -- "extension" packages expect to find them here and there's no compelling reason +# -- not to republish them here so those keep working. +__all__ = ["OxmlElement", "parse_xml"] + +# --------------------------------------------------------------------------- +# DrawingML-related elements + +register_element_cls("a:blip", CT_Blip) +register_element_cls("a:ext", CT_PositiveSize2D) +register_element_cls("a:graphic", CT_GraphicalObject) +register_element_cls("a:graphicData", CT_GraphicalObjectData) +register_element_cls("a:off", CT_Point2D) +register_element_cls("a:xfrm", CT_Transform2D) +register_element_cls("pic:blipFill", CT_BlipFillProperties) +register_element_cls("pic:cNvPr", CT_NonVisualDrawingProps) +register_element_cls("pic:nvPicPr", CT_PictureNonVisual) +register_element_cls("pic:pic", CT_Picture) +register_element_cls("pic:spPr", CT_ShapeProperties) +register_element_cls("w:drawing", CT_Drawing) +register_element_cls("wp:anchor", CT_Anchor) +register_element_cls("wp:docPr", CT_NonVisualDrawingProps) +register_element_cls("wp:extent", CT_PositiveSize2D) +register_element_cls("wp:inline", CT_Inline) + +# --------------------------------------------------------------------------- +# hyperlink-related elements + +register_element_cls("w:hyperlink", CT_Hyperlink) + +# --------------------------------------------------------------------------- +# text-related elements + +register_element_cls("w:br", CT_Br) +register_element_cls("w:cr", CT_Cr) +register_element_cls("w:lastRenderedPageBreak", CT_LastRenderedPageBreak) +register_element_cls("w:noBreakHyphen", CT_NoBreakHyphen) +register_element_cls("w:ptab", CT_PTab) +register_element_cls("w:r", CT_R) +register_element_cls("w:t", CT_Text) + +# --------------------------------------------------------------------------- +# header/footer-related mappings + +register_element_cls("w:evenAndOddHeaders", CT_OnOff) +register_element_cls("w:titlePg", CT_OnOff) + +# --------------------------------------------------------------------------- +# other custom element class mappings + +from .comments import CT_Comments, CT_Comment + +register_element_cls("w:comments", CT_Comments) +register_element_cls("w:comment", CT_Comment) + +from .coreprops import CT_CoreProperties + +register_element_cls("cp:coreProperties", CT_CoreProperties) + +from .document import CT_Body, CT_Document + +register_element_cls("w:body", CT_Body) +register_element_cls("w:document", CT_Document) + +from .numbering import CT_Num, CT_Numbering, CT_NumLvl, CT_NumPr + +register_element_cls("w:abstractNumId", CT_DecimalNumber) +register_element_cls("w:ilvl", CT_DecimalNumber) +register_element_cls("w:lvlOverride", CT_NumLvl) +register_element_cls("w:num", CT_Num) +register_element_cls("w:numId", CT_DecimalNumber) +register_element_cls("w:numPr", CT_NumPr) +register_element_cls("w:numbering", CT_Numbering) +register_element_cls("w:startOverride", CT_DecimalNumber) + +from .section import ( + CT_HdrFtr, + CT_HdrFtrRef, + CT_PageMar, + CT_PageSz, + CT_SectPr, + CT_SectType, +) + +register_element_cls("w:footerReference", CT_HdrFtrRef) +register_element_cls("w:ftr", CT_HdrFtr) +register_element_cls("w:hdr", CT_HdrFtr) +register_element_cls("w:headerReference", CT_HdrFtrRef) +register_element_cls("w:pgMar", CT_PageMar) +register_element_cls("w:pgSz", CT_PageSz) +register_element_cls("w:sectPr", CT_SectPr) +register_element_cls("w:type", CT_SectType) + +from .settings import CT_Settings + +register_element_cls("w:settings", CT_Settings) + +from .styles import CT_LatentStyles, CT_LsdException, CT_Style, CT_Styles + +register_element_cls("w:basedOn", CT_String) +register_element_cls("w:latentStyles", CT_LatentStyles) +register_element_cls("w:locked", CT_OnOff) +register_element_cls("w:lsdException", CT_LsdException) +register_element_cls("w:name", CT_String) +register_element_cls("w:next", CT_String) +register_element_cls("w:qFormat", CT_OnOff) +register_element_cls("w:semiHidden", CT_OnOff) +register_element_cls("w:style", CT_Style) +register_element_cls("w:styles", CT_Styles) +register_element_cls("w:uiPriority", CT_DecimalNumber) +register_element_cls("w:unhideWhenUsed", CT_OnOff) + +from .table import ( + CT_Height, + CT_Row, + CT_Tbl, + CT_TblGrid, + CT_TblGridCol, + CT_TblLayoutType, + CT_TblPr, + CT_TblPrEx, + CT_TblWidth, + CT_Tc, + CT_TcPr, + CT_TrPr, + CT_VMerge, + CT_VerticalJc, +) + +register_element_cls("w:bidiVisual", CT_OnOff) +register_element_cls("w:gridAfter", CT_DecimalNumber) +register_element_cls("w:gridBefore", CT_DecimalNumber) +register_element_cls("w:gridCol", CT_TblGridCol) +register_element_cls("w:gridSpan", CT_DecimalNumber) +register_element_cls("w:tbl", CT_Tbl) +register_element_cls("w:tblGrid", CT_TblGrid) +register_element_cls("w:tblLayout", CT_TblLayoutType) +register_element_cls("w:tblPr", CT_TblPr) +register_element_cls("w:tblPrEx", CT_TblPrEx) +register_element_cls("w:tblStyle", CT_String) +register_element_cls("w:tc", CT_Tc) +register_element_cls("w:tcPr", CT_TcPr) +register_element_cls("w:tcW", CT_TblWidth) +register_element_cls("w:tr", CT_Row) +register_element_cls("w:trHeight", CT_Height) +register_element_cls("w:trPr", CT_TrPr) +register_element_cls("w:vAlign", CT_VerticalJc) +register_element_cls("w:vMerge", CT_VMerge) + +from .text.font import ( + CT_Color, + CT_Fonts, + CT_Highlight, + CT_HpsMeasure, + CT_RPr, + CT_Underline, + CT_VerticalAlignRun, +) + +register_element_cls("w:b", CT_OnOff) +register_element_cls("w:bCs", CT_OnOff) +register_element_cls("w:caps", CT_OnOff) +register_element_cls("w:color", CT_Color) +register_element_cls("w:cs", CT_OnOff) +register_element_cls("w:dstrike", CT_OnOff) +register_element_cls("w:emboss", CT_OnOff) +register_element_cls("w:highlight", CT_Highlight) +register_element_cls("w:i", CT_OnOff) +register_element_cls("w:iCs", CT_OnOff) +register_element_cls("w:imprint", CT_OnOff) +register_element_cls("w:noProof", CT_OnOff) +register_element_cls("w:oMath", CT_OnOff) +register_element_cls("w:outline", CT_OnOff) +register_element_cls("w:rFonts", CT_Fonts) +register_element_cls("w:rPr", CT_RPr) +register_element_cls("w:rStyle", CT_String) +register_element_cls("w:rtl", CT_OnOff) +register_element_cls("w:shadow", CT_OnOff) +register_element_cls("w:smallCaps", CT_OnOff) +register_element_cls("w:snapToGrid", CT_OnOff) +register_element_cls("w:specVanish", CT_OnOff) +register_element_cls("w:strike", CT_OnOff) +register_element_cls("w:sz", CT_HpsMeasure) +register_element_cls("w:u", CT_Underline) +register_element_cls("w:vanish", CT_OnOff) +register_element_cls("w:vertAlign", CT_VerticalAlignRun) +register_element_cls("w:webHidden", CT_OnOff) + +from .text.paragraph import CT_P + +register_element_cls("w:p", CT_P) + +from .text.parfmt import ( + CT_Ind, + CT_Jc, + CT_PPr, + CT_Spacing, + CT_TabStop, + CT_TabStops, +) + +register_element_cls("w:ind", CT_Ind) +register_element_cls("w:jc", CT_Jc) +register_element_cls("w:keepLines", CT_OnOff) +register_element_cls("w:keepNext", CT_OnOff) +register_element_cls("w:outlineLvl", CT_DecimalNumber) +register_element_cls("w:pageBreakBefore", CT_OnOff) +register_element_cls("w:pPr", CT_PPr) +register_element_cls("w:pStyle", CT_String) +register_element_cls("w:spacing", CT_Spacing) +register_element_cls("w:tab", CT_TabStop) +register_element_cls("w:tabs", CT_TabStops) +register_element_cls("w:widowControl", CT_OnOff) diff --git a/intelliaide/vendor/docx/oxml/comments.py b/intelliaide/vendor/docx/oxml/comments.py new file mode 100644 index 0000000..ad98217 --- /dev/null +++ b/intelliaide/vendor/docx/oxml/comments.py @@ -0,0 +1,124 @@ +"""Custom element classes related to document comments.""" + +from __future__ import annotations + +import datetime as dt +from typing import TYPE_CHECKING, Callable, cast + +from docx.oxml.ns import nsdecls +from docx.oxml.parser import parse_xml +from docx.oxml.simpletypes import ST_DateTime, ST_DecimalNumber, ST_String +from docx.oxml.xmlchemy import BaseOxmlElement, OptionalAttribute, RequiredAttribute, ZeroOrMore + +if TYPE_CHECKING: + from docx.oxml.table import CT_Tbl + from docx.oxml.text.paragraph import CT_P + + +class CT_Comments(BaseOxmlElement): + """`w:comments` element, the root element for the comments part. + + Simply contains a collection of `w:comment` elements, each representing a single comment. Each + contained comment is identified by a unique `w:id` attribute, used to reference the comment + from the document text. The offset of the comment in this collection is arbitrary; it is + essentially a _set_ implemented as a list. + """ + + # -- type-declarations to fill in the gaps for metaclass-added methods -- + comment_lst: list[CT_Comment] + + comment = ZeroOrMore("w:comment") + + def add_comment(self) -> CT_Comment: + """Return newly added `w:comment` child of this `w:comments`. + + The returned `w:comment` element is the minimum valid value, having a `w:id` value unique + within the existing comments and the required `w:author` attribute present but set to the + empty string. It's content is limited to a single run containing the necessary annotation + reference but no text. Content is added by adding runs to this first paragraph and by + adding additional paragraphs as needed. + """ + next_id = self._next_available_comment_id() + comment = cast( + CT_Comment, + parse_xml( + f'' + f" " + f" " + f' ' + f" " + f" " + f" " + f' ' + f" " + f" " + f" " + f" " + f"" + ), + ) + self.append(comment) + return comment + + def get_comment_by_id(self, comment_id: int) -> CT_Comment | None: + """Return the `w:comment` element identified by `comment_id`, or |None| if not found.""" + comment_elms = self.xpath(f"(./w:comment[@w:id='{comment_id}'])[1]") + return comment_elms[0] if comment_elms else None + + def _next_available_comment_id(self) -> int: + """The next available comment id. + + According to the schema, this can be any positive integer, as big as you like, and the + default mechanism is to use `max() + 1`. However, if that yields a value larger than will + fit in a 32-bit signed integer, we take a more deliberate approach to use the first + ununsed integer starting from 0. + """ + used_ids = [int(x) for x in self.xpath("./w:comment/@w:id")] + + next_id = max(used_ids, default=-1) + 1 + + if next_id <= 2**31 - 1: + return next_id + + # -- fall-back to enumerating all used ids to find the first unused one -- + for expected, actual in enumerate(sorted(used_ids)): + if expected != actual: + return expected + + return len(used_ids) + + +class CT_Comment(BaseOxmlElement): + """`w:comment` element, representing a single comment. + + A comment is a so-called "story" and can contain paragraphs and tables much like a table-cell. + While probably most often used for a single sentence or phrase, a comment can contain rich + content, including multiple rich-text paragraphs, hyperlinks, images, and tables. + """ + + # -- attributes on `w:comment` -- + id: int = RequiredAttribute("w:id", ST_DecimalNumber) # pyright: ignore[reportAssignmentType] + author: str = RequiredAttribute("w:author", ST_String) # pyright: ignore[reportAssignmentType] + initials: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:initials", ST_String + ) + date: dt.datetime | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:date", ST_DateTime + ) + + # -- children -- + + p = ZeroOrMore("w:p", successors=()) + tbl = ZeroOrMore("w:tbl", successors=()) + + # -- type-declarations for methods added by metaclass -- + + add_p: Callable[[], CT_P] + p_lst: list[CT_P] + tbl_lst: list[CT_Tbl] + _insert_tbl: Callable[[CT_Tbl], CT_Tbl] + + @property + def inner_content_elements(self) -> list[CT_P | CT_Tbl]: + """Generate all `w:p` and `w:tbl` elements in this comment.""" + return self.xpath("./w:p | ./w:tbl") diff --git a/intelliaide/vendor/docx/oxml/coreprops.py b/intelliaide/vendor/docx/oxml/coreprops.py new file mode 100644 index 0000000..fcff0c7 --- /dev/null +++ b/intelliaide/vendor/docx/oxml/coreprops.py @@ -0,0 +1,298 @@ +"""Custom element classes for core properties-related XML elements.""" + +from __future__ import annotations + +import datetime as dt +import re +from typing import TYPE_CHECKING, Any, Callable, cast + +from docx.oxml.ns import nsdecls, qn +from docx.oxml.parser import parse_xml +from docx.oxml.xmlchemy import BaseOxmlElement, ZeroOrOne + +if TYPE_CHECKING: + from lxml.etree import _Element as etree_Element # pyright: ignore[reportPrivateUsage] + + +class CT_CoreProperties(BaseOxmlElement): + """`` element, the root element of the Core Properties part. + + Stored as `/docProps/core.xml`. Implements many of the Dublin Core document metadata + elements. String elements resolve to an empty string ("") if the element is not + present in the XML. String elements are limited in length to 255 unicode characters. + """ + + get_or_add_revision: Callable[[], etree_Element] + + category = ZeroOrOne("cp:category", successors=()) + contentStatus = ZeroOrOne("cp:contentStatus", successors=()) + created = ZeroOrOne("dcterms:created", successors=()) + creator = ZeroOrOne("dc:creator", successors=()) + description = ZeroOrOne("dc:description", successors=()) + identifier = ZeroOrOne("dc:identifier", successors=()) + keywords = ZeroOrOne("cp:keywords", successors=()) + language = ZeroOrOne("dc:language", successors=()) + lastModifiedBy = ZeroOrOne("cp:lastModifiedBy", successors=()) + lastPrinted = ZeroOrOne("cp:lastPrinted", successors=()) + modified = ZeroOrOne("dcterms:modified", successors=()) + revision: etree_Element | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "cp:revision", successors=() + ) + subject = ZeroOrOne("dc:subject", successors=()) + title = ZeroOrOne("dc:title", successors=()) + version = ZeroOrOne("cp:version", successors=()) + + _coreProperties_tmpl = "\n" % nsdecls("cp", "dc", "dcterms") + + @classmethod + def new(cls) -> CT_CoreProperties: + """Return a new `` element.""" + xml = cls._coreProperties_tmpl + coreProperties = cast(CT_CoreProperties, parse_xml(xml)) + return coreProperties + + @property + def author_text(self) -> str: + """The text in the `dc:creator` child element.""" + return self._text_of_element("creator") + + @author_text.setter + def author_text(self, value: str): + self._set_element_text("creator", value) + + @property + def category_text(self) -> str: + return self._text_of_element("category") + + @category_text.setter + def category_text(self, value: str): + self._set_element_text("category", value) + + @property + def comments_text(self) -> str: + return self._text_of_element("description") + + @comments_text.setter + def comments_text(self, value: str): + self._set_element_text("description", value) + + @property + def contentStatus_text(self) -> str: + return self._text_of_element("contentStatus") + + @contentStatus_text.setter + def contentStatus_text(self, value: str): + self._set_element_text("contentStatus", value) + + @property + def created_datetime(self) -> dt.datetime | None: + return self._datetime_of_element("created") + + @created_datetime.setter + def created_datetime(self, value: dt.datetime): + self._set_element_datetime("created", value) + + @property + def identifier_text(self) -> str: + return self._text_of_element("identifier") + + @identifier_text.setter + def identifier_text(self, value: str): + self._set_element_text("identifier", value) + + @property + def keywords_text(self) -> str: + return self._text_of_element("keywords") + + @keywords_text.setter + def keywords_text(self, value: str): + self._set_element_text("keywords", value) + + @property + def language_text(self) -> str: + return self._text_of_element("language") + + @language_text.setter + def language_text(self, value: str): + self._set_element_text("language", value) + + @property + def lastModifiedBy_text(self) -> str: + return self._text_of_element("lastModifiedBy") + + @lastModifiedBy_text.setter + def lastModifiedBy_text(self, value: str): + self._set_element_text("lastModifiedBy", value) + + @property + def lastPrinted_datetime(self) -> dt.datetime | None: + return self._datetime_of_element("lastPrinted") + + @lastPrinted_datetime.setter + def lastPrinted_datetime(self, value: dt.datetime): + self._set_element_datetime("lastPrinted", value) + + @property + def modified_datetime(self) -> dt.datetime | None: + return self._datetime_of_element("modified") + + @modified_datetime.setter + def modified_datetime(self, value: dt.datetime): + self._set_element_datetime("modified", value) + + @property + def revision_number(self) -> int: + """Integer value of revision property.""" + revision = self.revision + if revision is None: + return 0 + revision_str = str(revision.text) + try: + revision = int(revision_str) + except ValueError: + # non-integer revision strings also resolve to 0 + revision = 0 + # as do negative integers + if revision < 0: + revision = 0 + return revision + + @revision_number.setter + def revision_number(self, value: int): + """Set revision property to string value of integer `value`.""" + if not isinstance(value, int) or value < 1: # pyright: ignore[reportUnnecessaryIsInstance] + tmpl = "revision property requires positive int, got '%s'" + raise ValueError(tmpl % value) + revision = self.get_or_add_revision() + revision.text = str(value) + + @property + def subject_text(self) -> str: + return self._text_of_element("subject") + + @subject_text.setter + def subject_text(self, value: str): + self._set_element_text("subject", value) + + @property + def title_text(self) -> str: + return self._text_of_element("title") + + @title_text.setter + def title_text(self, value: str): + self._set_element_text("title", value) + + @property + def version_text(self) -> str: + return self._text_of_element("version") + + @version_text.setter + def version_text(self, value: str): + self._set_element_text("version", value) + + def _datetime_of_element(self, property_name: str) -> dt.datetime | None: + element = getattr(self, property_name) + if element is None: + return None + datetime_str = element.text + try: + return self._parse_W3CDTF_to_datetime(datetime_str) + except ValueError: + # invalid datetime strings are ignored + return None + + def _get_or_add(self, prop_name: str) -> BaseOxmlElement: + """Return element returned by "get_or_add_" method for `prop_name`.""" + get_or_add_method_name = "get_or_add_%s" % prop_name + get_or_add_method = getattr(self, get_or_add_method_name) + element = get_or_add_method() + return element + + @classmethod + def _offset_dt(cls, dt_: dt.datetime, offset_str: str) -> dt.datetime: + """A |datetime| instance offset from `dt_` by timezone offset in `offset_str`. + + `offset_str` is like `"-07:00"`. + """ + match = cls._offset_pattern.match(offset_str) + if match is None: + raise ValueError("'%s' is not a valid offset string" % offset_str) + sign, hours_str, minutes_str = match.groups() + sign_factor = -1 if sign == "+" else 1 + hours = int(hours_str) * sign_factor + minutes = int(minutes_str) * sign_factor + td = dt.timedelta(hours=hours, minutes=minutes) + return dt_ + td + + _offset_pattern = re.compile(r"([+-])(\d\d):(\d\d)") + + @classmethod + def _parse_W3CDTF_to_datetime(cls, w3cdtf_str: str) -> dt.datetime: + # valid W3CDTF date cases: + # yyyy e.g. "2003" + # yyyy-mm e.g. "2003-12" + # yyyy-mm-dd e.g. "2003-12-31" + # UTC timezone e.g. "2003-12-31T10:14:55Z" + # numeric timezone e.g. "2003-12-31T10:14:55-08:00" + templates = ( + "%Y-%m-%dT%H:%M:%S", + "%Y-%m-%d", + "%Y-%m", + "%Y", + ) + # strptime isn't smart enough to parse literal timezone offsets like + # "-07:30", so we have to do it ourselves + parseable_part = w3cdtf_str[:19] + offset_str = w3cdtf_str[19:] + dt_ = None + for tmpl in templates: + try: + dt_ = dt.datetime.strptime(parseable_part, tmpl) + except ValueError: + continue + if dt_ is None: + tmpl = "could not parse W3CDTF datetime string '%s'" + raise ValueError(tmpl % w3cdtf_str) + if len(offset_str) == 6: + dt_ = cls._offset_dt(dt_, offset_str) + return dt_.replace(tzinfo=dt.timezone.utc) + + def _set_element_datetime(self, prop_name: str, value: dt.datetime) -> None: + """Set date/time value of child element having `prop_name` to `value`.""" + if not isinstance(value, dt.datetime): # pyright: ignore[reportUnnecessaryIsInstance] + tmpl = "property requires object, got %s" + raise ValueError(tmpl % type(value)) + element = self._get_or_add(prop_name) + dt_str = value.strftime("%Y-%m-%dT%H:%M:%SZ") + element.text = dt_str + if prop_name in ("created", "modified"): + # These two require an explicit "xsi:type="dcterms:W3CDTF"" + # attribute. The first and last line are a hack required to add + # the xsi namespace to the root element rather than each child + # element in which it is referenced + self.set(qn("xsi:foo"), "bar") + element.set(qn("xsi:type"), "dcterms:W3CDTF") + del self.attrib[qn("xsi:foo")] + + def _set_element_text(self, prop_name: str, value: Any) -> None: + """Set string value of `name` property to `value`.""" + if not isinstance(value, str): + value = str(value) + + if len(value) > 255: + tmpl = "exceeded 255 char limit for property, got:\n\n'%s'" + raise ValueError(tmpl % value) + element = self._get_or_add(prop_name) + element.text = value + + def _text_of_element(self, property_name: str) -> str: + """The text in the element matching `property_name`. + + The empty string if the element is not present or contains no text. + """ + element = getattr(self, property_name) + if element is None: + return "" + if element.text is None: + return "" + return element.text diff --git a/intelliaide/vendor/docx/oxml/document.py b/intelliaide/vendor/docx/oxml/document.py new file mode 100644 index 0000000..36819ef --- /dev/null +++ b/intelliaide/vendor/docx/oxml/document.py @@ -0,0 +1,88 @@ +"""Custom element classes that correspond to the document part, e.g. .""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable, List + +from docx.oxml.section import CT_SectPr +from docx.oxml.xmlchemy import BaseOxmlElement, ZeroOrMore, ZeroOrOne + +if TYPE_CHECKING: + from docx.oxml.table import CT_Tbl + from docx.oxml.text.paragraph import CT_P + + +class CT_Document(BaseOxmlElement): + """```` element, the root element of a document.xml file.""" + + body: CT_Body = ZeroOrOne("w:body") # pyright: ignore[reportAssignmentType] + + @property + def sectPr_lst(self) -> List[CT_SectPr]: + """All `w:sectPr` elements directly accessible from document element. + + Note this does not include a `sectPr` child in a paragraphs wrapped in + revision marks or other intervening layer, perhaps `w:sdt` or customXml + elements. + + `w:sectPr` elements appear in document order. The last one is always + `w:body/w:sectPr`, all preceding are `w:p/w:pPr/w:sectPr`. + """ + xpath = "./w:body/w:p/w:pPr/w:sectPr | ./w:body/w:sectPr" + return self.xpath(xpath) + + +class CT_Body(BaseOxmlElement): + """`w:body`, the container element for the main document story in `document.xml`.""" + + add_p: Callable[[], CT_P] + get_or_add_sectPr: Callable[[], CT_SectPr] + p_lst: List[CT_P] + tbl_lst: List[CT_Tbl] + + _insert_tbl: Callable[[CT_Tbl], CT_Tbl] + + p = ZeroOrMore("w:p", successors=("w:sectPr",)) + tbl = ZeroOrMore("w:tbl", successors=("w:sectPr",)) + sectPr: CT_SectPr | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:sectPr", successors=() + ) + + def add_section_break(self) -> CT_SectPr: + """Return `w:sectPr` element for new section added at end of document. + + The last `w:sectPr` becomes the second-to-last, with the new `w:sectPr` being an + exact clone of the previous one, except that all header and footer references + are removed (and are therefore now "inherited" from the prior section). + + A copy of the previously-last `w:sectPr` will now appear in a new `w:p` at the + end of the document. The returned `w:sectPr` is the sentinel `w:sectPr` for the + document (and as implemented, `is` the prior sentinel `w:sectPr` with headers + and footers removed). + """ + # ---get the sectPr at file-end, which controls last section (sections[-1])--- + sentinel_sectPr = self.get_or_add_sectPr() + # ---add exact copy to new `w:p` element; that is now second-to last section--- + self.add_p().set_sectPr(sentinel_sectPr.clone()) + # ---remove any header or footer references from "new" last section--- + for hdrftr_ref in sentinel_sectPr.xpath("w:headerReference|w:footerReference"): + sentinel_sectPr.remove(hdrftr_ref) + # ---the sentinel `w:sectPr` now controls the new last section--- + return sentinel_sectPr + + def clear_content(self): + """Remove all content child elements from this element. + + Leave the element if it is present. + """ + for content_elm in self.xpath("./*[not(self::w:sectPr)]"): + self.remove(content_elm) + + @property + def inner_content_elements(self) -> List[CT_P | CT_Tbl]: + """Generate all `w:p` and `w:tbl` elements in this document-body. + + Elements appear in document order. Elements shaded by nesting in a `w:ins` or + other "wrapper" element will not be included. + """ + return self.xpath("./w:p | ./w:tbl") diff --git a/intelliaide/vendor/docx/oxml/drawing.py b/intelliaide/vendor/docx/oxml/drawing.py new file mode 100644 index 0000000..5b627f9 --- /dev/null +++ b/intelliaide/vendor/docx/oxml/drawing.py @@ -0,0 +1,11 @@ +"""Custom element-classes for DrawingML-related elements like ``. + +For legacy reasons, many DrawingML-related elements are in `docx.oxml.shape`. Expect +those to move over here as we have reason to touch them. +""" + +from docx.oxml.xmlchemy import BaseOxmlElement + + +class CT_Drawing(BaseOxmlElement): + """`` element, containing a DrawingML object like a picture or chart.""" diff --git a/intelliaide/vendor/docx/oxml/exceptions.py b/intelliaide/vendor/docx/oxml/exceptions.py new file mode 100644 index 0000000..8919239 --- /dev/null +++ b/intelliaide/vendor/docx/oxml/exceptions.py @@ -0,0 +1,10 @@ +"""Exceptions for oxml sub-package.""" + + +class XmlchemyError(Exception): + """Generic error class.""" + + +class InvalidXmlError(XmlchemyError): + """Raised when invalid XML is encountered, such as on attempt to access a missing + required child element.""" diff --git a/intelliaide/vendor/docx/oxml/ns.py b/intelliaide/vendor/docx/oxml/ns.py new file mode 100644 index 0000000..ce03940 --- /dev/null +++ b/intelliaide/vendor/docx/oxml/ns.py @@ -0,0 +1,109 @@ +"""Namespace-related objects.""" + +from __future__ import annotations + +from typing import Dict + +nsmap = { + "a": "http://schemas.openxmlformats.org/drawingml/2006/main", + "c": "http://schemas.openxmlformats.org/drawingml/2006/chart", + "cp": "http://schemas.openxmlformats.org/package/2006/metadata/core-properties", + "dc": "http://purl.org/dc/elements/1.1/", + "dcmitype": "http://purl.org/dc/dcmitype/", + "dcterms": "http://purl.org/dc/terms/", + "dgm": "http://schemas.openxmlformats.org/drawingml/2006/diagram", + "m": "http://schemas.openxmlformats.org/officeDocument/2006/math", + "pic": "http://schemas.openxmlformats.org/drawingml/2006/picture", + "r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships", + "sl": "http://schemas.openxmlformats.org/schemaLibrary/2006/main", + "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main", + "w14": "http://schemas.microsoft.com/office/word/2010/wordml", + "wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", + "xml": "http://www.w3.org/XML/1998/namespace", + "xsi": "http://www.w3.org/2001/XMLSchema-instance", +} + +pfxmap = {value: key for key, value in nsmap.items()} + + +class NamespacePrefixedTag(str): + """Value object that knows the semantics of an XML tag having a namespace prefix.""" + + def __new__(cls, nstag: str): + return super(NamespacePrefixedTag, cls).__new__(cls, nstag) + + def __init__(self, nstag: str): + self._pfx, self._local_part = nstag.split(":") + self._ns_uri = nsmap[self._pfx] + + @property + def clark_name(self) -> str: + return "{%s}%s" % (self._ns_uri, self._local_part) + + @classmethod + def from_clark_name(cls, clark_name: str) -> NamespacePrefixedTag: + nsuri, local_name = clark_name[1:].split("}") + nstag = "%s:%s" % (pfxmap[nsuri], local_name) + return cls(nstag) + + @property + def local_part(self) -> str: + """The local part of this tag. + + E.g. "foobar" is returned for tag "f:foobar". + """ + return self._local_part + + @property + def nsmap(self) -> Dict[str, str]: + """Single-member dict mapping prefix of this tag to it's namespace name. + + Example: `{"f": "http://foo/bar"}`. This is handy for passing to xpath calls + and other uses. + """ + return {self._pfx: self._ns_uri} + + @property + def nspfx(self) -> str: + """The namespace-prefix for this tag. + + For example, "f" is returned for tag "f:foobar". + """ + return self._pfx + + @property + def nsuri(self) -> str: + """The namespace URI for this tag. + + For example, "http://foo/bar" would be returned for tag "f:foobar" if the "f" + prefix maps to "http://foo/bar" in nsmap. + """ + return self._ns_uri + + +def nsdecls(*prefixes: str) -> str: + """Namespace declaration including each namespace-prefix in `prefixes`. + + Handy for adding required namespace declarations to a tree root element. + """ + return " ".join(['xmlns:%s="%s"' % (pfx, nsmap[pfx]) for pfx in prefixes]) + + +def nspfxmap(*nspfxs: str) -> Dict[str, str]: + """Subset namespace-prefix mappings specified by *nspfxs*. + + Any number of namespace prefixes can be supplied, e.g. namespaces("a", "r", "p"). + """ + return {pfx: nsmap[pfx] for pfx in nspfxs} + + +def qn(tag: str) -> str: + """Stands for "qualified name". + + This utility function converts a familiar namespace-prefixed tag name like "w:p" + into a Clark-notation qualified tag name for lxml. For example, `qn("w:p")` returns + "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}p". + """ + prefix, tagroot = tag.split(":") + uri = nsmap[prefix] + return "{%s}%s" % (uri, tagroot) diff --git a/intelliaide/vendor/docx/oxml/numbering.py b/intelliaide/vendor/docx/oxml/numbering.py new file mode 100644 index 0000000..3512de6 --- /dev/null +++ b/intelliaide/vendor/docx/oxml/numbering.py @@ -0,0 +1,109 @@ +"""Custom element classes related to the numbering part.""" + +from docx.oxml.parser import OxmlElement +from docx.oxml.shared import CT_DecimalNumber +from docx.oxml.simpletypes import ST_DecimalNumber +from docx.oxml.xmlchemy import ( + BaseOxmlElement, + OneAndOnlyOne, + RequiredAttribute, + ZeroOrMore, + ZeroOrOne, +) + + +class CT_Num(BaseOxmlElement): + """```` element, which represents a concrete list definition instance, having + a required child that references an abstract numbering definition + that defines most of the formatting details.""" + + abstractNumId = OneAndOnlyOne("w:abstractNumId") + lvlOverride = ZeroOrMore("w:lvlOverride") + numId = RequiredAttribute("w:numId", ST_DecimalNumber) + + def add_lvlOverride(self, ilvl): + """Return a newly added CT_NumLvl () element having its ``ilvl`` + attribute set to `ilvl`.""" + return self._add_lvlOverride(ilvl=ilvl) + + @classmethod + def new(cls, num_id, abstractNum_id): + """Return a new ```` element having numId of `num_id` and having a + ```` child with val attribute set to `abstractNum_id`.""" + num = OxmlElement("w:num") + num.numId = num_id + abstractNumId = CT_DecimalNumber.new("w:abstractNumId", abstractNum_id) + num.append(abstractNumId) + return num + + +class CT_NumLvl(BaseOxmlElement): + """```` element, which identifies a level in a list definition to + override with settings it contains.""" + + startOverride = ZeroOrOne("w:startOverride", successors=("w:lvl",)) + ilvl = RequiredAttribute("w:ilvl", ST_DecimalNumber) + + def add_startOverride(self, val): + """Return a newly added CT_DecimalNumber element having tagname + ``w:startOverride`` and ``val`` attribute set to `val`.""" + return self._add_startOverride(val=val) + + +class CT_NumPr(BaseOxmlElement): + """A ```` element, a container for numbering properties applied to a + paragraph.""" + + ilvl = ZeroOrOne("w:ilvl", successors=("w:numId", "w:numberingChange", "w:ins")) + numId = ZeroOrOne("w:numId", successors=("w:numberingChange", "w:ins")) + + # @ilvl.setter + # def _set_ilvl(self, val): + # """ + # Get or add a child and set its ``w:val`` attribute to `val`. + # """ + # ilvl = self.get_or_add_ilvl() + # ilvl.val = val + + # @numId.setter + # def numId(self, val): + # """ + # Get or add a child and set its ``w:val`` attribute to + # `val`. + # """ + # numId = self.get_or_add_numId() + # numId.val = val + + +class CT_Numbering(BaseOxmlElement): + """```` element, the root element of a numbering part, i.e. + numbering.xml.""" + + num = ZeroOrMore("w:num", successors=("w:numIdMacAtCleanup",)) + + def add_num(self, abstractNum_id): + """Return a newly added CT_Num () element referencing the abstract + numbering definition identified by `abstractNum_id`.""" + next_num_id = self._next_numId + num = CT_Num.new(next_num_id, abstractNum_id) + return self._insert_num(num) + + def num_having_numId(self, numId): + """Return the ```` child element having ``numId`` attribute matching + `numId`.""" + xpath = './w:num[@w:numId="%d"]' % numId + try: + return self.xpath(xpath)[0] + except IndexError: + raise KeyError("no element with numId %d" % numId) + + @property + def _next_numId(self): + """The first ``numId`` unused by a ```` element, starting at 1 and + filling any gaps in numbering between existing ```` elements.""" + numId_strs = self.xpath("./w:num/@w:numId") + num_ids = [int(numId_str) for numId_str in numId_strs] + for num in range(1, len(num_ids) + 2): + if num not in num_ids: + break + return num diff --git a/intelliaide/vendor/docx/oxml/parser.py b/intelliaide/vendor/docx/oxml/parser.py new file mode 100644 index 0000000..e16ba30 --- /dev/null +++ b/intelliaide/vendor/docx/oxml/parser.py @@ -0,0 +1,62 @@ +# pyright: reportImportCycles=false + +"""XML parser for python-docx.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Dict, Type, cast + +from lxml import etree + +from docx.oxml.ns import NamespacePrefixedTag, nsmap + +if TYPE_CHECKING: + from docx.oxml.xmlchemy import BaseOxmlElement + + +# -- configure XML parser -- +element_class_lookup = etree.ElementNamespaceClassLookup() +oxml_parser = etree.XMLParser(remove_blank_text=True, resolve_entities=False) +oxml_parser.set_element_class_lookup(element_class_lookup) + + +def parse_xml(xml: str | bytes) -> "BaseOxmlElement": + """Root lxml element obtained by parsing XML character string `xml`. + + The custom parser is used, so custom element classes are produced for elements in + `xml` that have them. + """ + return cast("BaseOxmlElement", etree.fromstring(xml, oxml_parser)) + + +def register_element_cls(tag: str, cls: Type["BaseOxmlElement"]): + """Register an lxml custom element-class to use for `tag`. + + A instance of `cls` to be constructed when the oxml parser encounters an element + with matching `tag`. `tag` is a string of the form `nspfx:tagroot`, e.g. + `'w:document'`. + """ + nspfx, tagroot = tag.split(":") + namespace = element_class_lookup.get_namespace(nsmap[nspfx]) + namespace[tagroot] = cls + + +def OxmlElement( + nsptag_str: str, + attrs: Dict[str, str] | None = None, + nsdecls: Dict[str, str] | None = None, +) -> BaseOxmlElement | etree._Element: # pyright: ignore[reportPrivateUsage] + """Return a 'loose' lxml element having the tag specified by `nsptag_str`. + + The tag in `nsptag_str` must contain the standard namespace prefix, e.g. `a:tbl`. + The resulting element is an instance of the custom element class for this tag name + if one is defined. A dictionary of attribute values may be provided as `attrs`; they + are set if present. All namespaces defined in the dict `nsdecls` are declared in the + element using the key as the prefix and the value as the namespace name. If + `nsdecls` is not provided, a single namespace declaration is added based on the + prefix on `nsptag_str`. + """ + nsptag = NamespacePrefixedTag(nsptag_str) + if nsdecls is None: + nsdecls = nsptag.nsmap + return oxml_parser.makeelement(nsptag.clark_name, attrib=attrs, nsmap=nsdecls) diff --git a/intelliaide/vendor/docx/oxml/section.py b/intelliaide/vendor/docx/oxml/section.py new file mode 100644 index 0000000..71072e2 --- /dev/null +++ b/intelliaide/vendor/docx/oxml/section.py @@ -0,0 +1,537 @@ +"""Section-related custom element classes.""" + +from __future__ import annotations + +from copy import deepcopy +from typing import Callable, Iterator, List, Sequence, cast + +from lxml import etree +from typing_extensions import TypeAlias + +from docx.enum.section import WD_HEADER_FOOTER, WD_ORIENTATION, WD_SECTION_START +from docx.oxml.ns import nsmap +from docx.oxml.shared import CT_OnOff +from docx.oxml.simpletypes import ST_SignedTwipsMeasure, ST_TwipsMeasure, XsdString +from docx.oxml.table import CT_Tbl +from docx.oxml.text.paragraph import CT_P +from docx.oxml.xmlchemy import ( + BaseOxmlElement, + OptionalAttribute, + RequiredAttribute, + ZeroOrMore, + ZeroOrOne, +) +from docx.shared import Length, lazyproperty + +BlockElement: TypeAlias = "CT_P | CT_Tbl" + + +class CT_HdrFtr(BaseOxmlElement): + """`w:hdr` and `w:ftr`, the root element for header and footer part respectively.""" + + add_p: Callable[[], CT_P] + p_lst: List[CT_P] + tbl_lst: List[CT_Tbl] + + _insert_tbl: Callable[[CT_Tbl], CT_Tbl] + + p = ZeroOrMore("w:p", successors=()) + tbl = ZeroOrMore("w:tbl", successors=()) + + @property + def inner_content_elements(self) -> List[CT_P | CT_Tbl]: + """Generate all `w:p` and `w:tbl` elements in this header or footer. + + Elements appear in document order. Elements shaded by nesting in a `w:ins` or + other "wrapper" element will not be included. + """ + return self.xpath("./w:p | ./w:tbl") + + +class CT_HdrFtrRef(BaseOxmlElement): + """`w:headerReference` and `w:footerReference` elements.""" + + type_: WD_HEADER_FOOTER = RequiredAttribute( # pyright: ignore[reportAssignmentType] + "w:type", WD_HEADER_FOOTER + ) + rId: str = RequiredAttribute("r:id", XsdString) # pyright: ignore[reportAssignmentType] + + +class CT_PageMar(BaseOxmlElement): + """```` element, defining page margins.""" + + top: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:top", ST_SignedTwipsMeasure + ) + right: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:right", ST_TwipsMeasure + ) + bottom: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:bottom", ST_SignedTwipsMeasure + ) + left: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:left", ST_TwipsMeasure + ) + header: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:header", ST_TwipsMeasure + ) + footer: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:footer", ST_TwipsMeasure + ) + gutter: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:gutter", ST_TwipsMeasure + ) + + +class CT_PageSz(BaseOxmlElement): + """```` element, defining page dimensions and orientation.""" + + w: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:w", ST_TwipsMeasure + ) + h: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:h", ST_TwipsMeasure + ) + orient: WD_ORIENTATION = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:orient", WD_ORIENTATION, default=WD_ORIENTATION.PORTRAIT + ) + + +class CT_SectPr(BaseOxmlElement): + """`w:sectPr` element, the container element for section properties.""" + + get_or_add_pgMar: Callable[[], CT_PageMar] + get_or_add_pgSz: Callable[[], CT_PageSz] + get_or_add_titlePg: Callable[[], CT_OnOff] + get_or_add_type: Callable[[], CT_SectType] + _add_footerReference: Callable[[], CT_HdrFtrRef] + _add_headerReference: Callable[[], CT_HdrFtrRef] + _remove_titlePg: Callable[[], None] + _remove_type: Callable[[], None] + + _tag_seq = ( + "w:footnotePr", + "w:endnotePr", + "w:type", + "w:pgSz", + "w:pgMar", + "w:paperSrc", + "w:pgBorders", + "w:lnNumType", + "w:pgNumType", + "w:cols", + "w:formProt", + "w:vAlign", + "w:noEndnote", + "w:titlePg", + "w:textDirection", + "w:bidi", + "w:rtlGutter", + "w:docGrid", + "w:printerSettings", + "w:sectPrChange", + ) + headerReference = ZeroOrMore("w:headerReference", successors=_tag_seq) + footerReference = ZeroOrMore("w:footerReference", successors=_tag_seq) + type: CT_SectType | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:type", successors=_tag_seq[3:] + ) + pgSz: CT_PageSz | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:pgSz", successors=_tag_seq[4:] + ) + pgMar: CT_PageMar | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:pgMar", successors=_tag_seq[5:] + ) + titlePg: CT_OnOff | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:titlePg", successors=_tag_seq[14:] + ) + del _tag_seq + + def add_footerReference(self, type_: WD_HEADER_FOOTER, rId: str) -> CT_HdrFtrRef: + """Return newly added CT_HdrFtrRef element of `type_` with `rId`. + + The element tag is `w:footerReference`. + """ + footerReference = self._add_footerReference() + footerReference.type_ = type_ + footerReference.rId = rId + return footerReference + + def add_headerReference(self, type_: WD_HEADER_FOOTER, rId: str) -> CT_HdrFtrRef: + """Return newly added CT_HdrFtrRef element of `type_` with `rId`. + + The element tag is `w:headerReference`. + """ + headerReference = self._add_headerReference() + headerReference.type_ = type_ + headerReference.rId = rId + return headerReference + + @property + def bottom_margin(self) -> Length | None: + """Value of the `w:bottom` attr of `` child element, as |Length|. + + |None| when either the element or the attribute is not present. + """ + pgMar = self.pgMar + if pgMar is None: + return None + return pgMar.bottom + + @bottom_margin.setter + def bottom_margin(self, value: int | Length | None): + pgMar = self.get_or_add_pgMar() + pgMar.bottom = value if value is None or isinstance(value, Length) else Length(value) + + def clone(self) -> CT_SectPr: + """Return an exact duplicate of this ```` element tree suitable for + use in adding a section break. + + All rsid* attributes are removed from the root ```` element. + """ + cloned_sectPr = deepcopy(self) + cloned_sectPr.attrib.clear() + return cloned_sectPr + + @property + def footer(self) -> Length | None: + """Distance from bottom edge of page to bottom edge of the footer. + + This is the value of the `w:footer` attribute in the `w:pgMar` child element, + as a |Length| object, or |None| if either the element or the attribute is not + present. + """ + pgMar = self.pgMar + if pgMar is None: + return None + return pgMar.footer + + @footer.setter + def footer(self, value: int | Length | None): + pgMar = self.get_or_add_pgMar() + pgMar.footer = value if value is None or isinstance(value, Length) else Length(value) + + def get_footerReference(self, type_: WD_HEADER_FOOTER) -> CT_HdrFtrRef | None: + """Return footerReference element of `type_` or None if not present.""" + path = "./w:footerReference[@w:type='%s']" % WD_HEADER_FOOTER.to_xml(type_) + footerReferences = self.xpath(path) + if not footerReferences: + return None + return footerReferences[0] + + def get_headerReference(self, type_: WD_HEADER_FOOTER) -> CT_HdrFtrRef | None: + """Return headerReference element of `type_` or None if not present.""" + matching_headerReferences = self.xpath( + "./w:headerReference[@w:type='%s']" % WD_HEADER_FOOTER.to_xml(type_) + ) + if len(matching_headerReferences) == 0: + return None + return matching_headerReferences[0] + + @property + def gutter(self) -> Length | None: + """The value of the ``w:gutter`` attribute in the ```` child element, + as a |Length| object, or |None| if either the element or the attribute is not + present.""" + pgMar = self.pgMar + if pgMar is None: + return None + return pgMar.gutter + + @gutter.setter + def gutter(self, value: int | Length | None): + pgMar = self.get_or_add_pgMar() + pgMar.gutter = value if value is None or isinstance(value, Length) else Length(value) + + @property + def header(self) -> Length | None: + """Distance from top edge of page to top edge of header. + + This value comes from the `w:header` attribute on the `w:pgMar` child element. + |None| if either the element or the attribute is not present. + """ + pgMar = self.pgMar + if pgMar is None: + return None + return pgMar.header + + @header.setter + def header(self, value: int | Length | None): + pgMar = self.get_or_add_pgMar() + pgMar.header = value if value is None or isinstance(value, Length) else Length(value) + + def iter_inner_content(self) -> Iterator[CT_P | CT_Tbl]: + """Generate all `w:p` and `w:tbl` elements in this section. + + Elements appear in document order. Elements shaded by nesting in a `w:ins` or + other "wrapper" element will not be included. + """ + return _SectBlockElementIterator.iter_sect_block_elements(self) + + @property + def left_margin(self) -> Length | None: + """The value of the ``w:left`` attribute in the ```` child element, as + a |Length| object, or |None| if either the element or the attribute is not + present.""" + pgMar = self.pgMar + if pgMar is None: + return None + return pgMar.left + + @left_margin.setter + def left_margin(self, value: int | Length | None): + pgMar = self.get_or_add_pgMar() + pgMar.left = value if value is None or isinstance(value, Length) else Length(value) + + @property + def orientation(self) -> WD_ORIENTATION: + """`WD_ORIENTATION` member indicating page-orientation for this section. + + This is the value of the `orient` attribute on the `w:pgSz` child, or + `WD_ORIENTATION.PORTRAIT` if not present. + """ + pgSz = self.pgSz + if pgSz is None: + return WD_ORIENTATION.PORTRAIT + return pgSz.orient + + @orientation.setter + def orientation(self, value: WD_ORIENTATION | None): + pgSz = self.get_or_add_pgSz() + pgSz.orient = value if value else WD_ORIENTATION.PORTRAIT + + @property + def page_height(self) -> Length | None: + """Value in EMU of the `h` attribute of the `w:pgSz` child element. + + |None| if not present. + """ + pgSz = self.pgSz + if pgSz is None: + return None + return pgSz.h + + @page_height.setter + def page_height(self, value: Length | None): + pgSz = self.get_or_add_pgSz() + pgSz.h = value + + @property + def page_width(self) -> Length | None: + """Value in EMU of the ``w`` attribute of the ```` child element. + + |None| if not present. + """ + pgSz = self.pgSz + if pgSz is None: + return None + return pgSz.w + + @page_width.setter + def page_width(self, value: Length | None): + pgSz = self.get_or_add_pgSz() + pgSz.w = value + + @property + def preceding_sectPr(self) -> CT_SectPr | None: + """SectPr immediately preceding this one or None if this is the first.""" + # -- [1] predicate returns list of zero or one value -- + preceding_sectPrs = self.xpath("./preceding::w:sectPr[1]") + return preceding_sectPrs[0] if len(preceding_sectPrs) > 0 else None + + def remove_footerReference(self, type_: WD_HEADER_FOOTER) -> str: + """Return rId of w:footerReference child of `type_` after removing it.""" + footerReference = self.get_footerReference(type_) + if footerReference is None: + # -- should never happen, but to satisfy type-check and just in case -- + raise ValueError("CT_SectPr has no footer reference") + rId = footerReference.rId + self.remove(footerReference) + return rId + + def remove_headerReference(self, type_: WD_HEADER_FOOTER): + """Return rId of w:headerReference child of `type_` after removing it.""" + headerReference = self.get_headerReference(type_) + if headerReference is None: + # -- should never happen, but to satisfy type-check and just in case -- + raise ValueError("CT_SectPr has no header reference") + rId = headerReference.rId + self.remove(headerReference) + return rId + + @property + def right_margin(self) -> Length | None: + """The value of the ``w:right`` attribute in the ```` child element, as + a |Length| object, or |None| if either the element or the attribute is not + present.""" + pgMar = self.pgMar + if pgMar is None: + return None + return pgMar.right + + @right_margin.setter + def right_margin(self, value: Length | None): + pgMar = self.get_or_add_pgMar() + pgMar.right = value + + @property + def start_type(self) -> WD_SECTION_START: + """The member of the ``WD_SECTION_START`` enumeration corresponding to the value + of the ``val`` attribute of the ```` child element, or + ``WD_SECTION_START.NEW_PAGE`` if not present.""" + type = self.type + if type is None or type.val is None: + return WD_SECTION_START.NEW_PAGE + return type.val + + @start_type.setter + def start_type(self, value: WD_SECTION_START | None): + if value is None or value is WD_SECTION_START.NEW_PAGE: + self._remove_type() + return + type = self.get_or_add_type() + type.val = value + + @property + def titlePg_val(self) -> bool: + """Value of `w:titlePg/@val` or |False| if `./w:titlePg` is not present.""" + titlePg = self.titlePg + if titlePg is None: + return False + return titlePg.val + + @titlePg_val.setter + def titlePg_val(self, value: bool | None): + if value in [None, False]: + self._remove_titlePg() + else: + self.get_or_add_titlePg().val = True + + @property + def top_margin(self) -> Length | None: + """The value of the ``w:top`` attribute in the ```` child element, as a + |Length| object, or |None| if either the element or the attribute is not + present.""" + pgMar = self.pgMar + if pgMar is None: + return None + return pgMar.top + + @top_margin.setter + def top_margin(self, value: Length | None): + pgMar = self.get_or_add_pgMar() + pgMar.top = value + + +class CT_SectType(BaseOxmlElement): + """```` element, defining the section start type.""" + + val: WD_SECTION_START | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:val", WD_SECTION_START + ) + + +# == HELPERS ========================================================================= + + +class _SectBlockElementIterator: + """Generates the block-item XML elements in a section. + + A block-item element is a `CT_P` (paragraph) or a `CT_Tbl` (table). + """ + + _compiled_blocks_xpath: etree.XPath | None = None + _compiled_count_xpath: etree.XPath | None = None + + def __init__(self, sectPr: CT_SectPr): + self._sectPr = sectPr + + @classmethod + def iter_sect_block_elements(cls, sectPr: CT_SectPr) -> Iterator[BlockElement]: + """Generate each CT_P or CT_Tbl element within extents governed by `sectPr`.""" + return cls(sectPr)._iter_sect_block_elements() + + def _iter_sect_block_elements(self) -> Iterator[BlockElement]: + """Generate each CT_P or CT_Tbl element in section.""" + # -- General strategy is to get all block ( and ) elements from + # -- start of doc to and including this section, then compute the count of those + # -- elements that came from prior sections and skip that many to leave only the + # -- ones in this section. It's possible to express this "between here and + # -- there" (end of prior section and end of this one) concept in XPath, but it + # -- would be harder to follow because there are special cases (e.g. no prior + # -- section) and the boundary expressions are fairly hairy. I also believe it + # -- would be computationally more expensive than doing it this straighforward + # -- albeit (theoretically) slightly wasteful way. + + sectPr, sectPrs = self._sectPr, self._sectPrs + sectPr_idx = sectPrs.index(sectPr) + + # -- count block items belonging to prior sections -- + n_blks_to_skip = ( + 0 + if sectPr_idx == 0 + else self._count_of_blocks_in_and_above_section(sectPrs[sectPr_idx - 1]) + ) + + # -- and skip those in set of all blks from doc start to end of this section -- + for element in self._blocks_in_and_above_section(sectPr)[n_blks_to_skip:]: + yield element + + def _blocks_in_and_above_section(self, sectPr: CT_SectPr) -> Sequence[BlockElement]: + """All ps and tbls in section defined by `sectPr` and all prior sections.""" + if self._compiled_blocks_xpath is None: + self._compiled_blocks_xpath = etree.XPath( + self._blocks_in_and_above_section_xpath, + namespaces=nsmap, + regexp=False, + ) + xpath = self._compiled_blocks_xpath + # -- XPath callable results are Any (basically), so need a cast. -- + return cast(Sequence[BlockElement], xpath(sectPr)) + + @lazyproperty + def _blocks_in_and_above_section_xpath(self) -> str: + """XPath expr for ps and tbls in context of a sectPr and all prior sectPrs.""" + # -- "p_sect" is a section with sectPr located at w:p/w:pPr/w:sectPr. + # -- "body_sect" is a section with sectPr located at w:body/w:sectPr. The last + # -- section in the document is a "body_sect". All others are of the "p_sect" + # -- variety. "term" means "terminal", like the last p or tbl in the section. + # -- "pred" means "predecessor", like a preceding p or tbl in the section. + + # -- the terminal block in a p-based sect is the p the sectPr appears in -- + p_sect_term_block = "./parent::w:pPr/parent::w:p" + # -- the terminus of a body-based sect is the sectPr itself (not a block) -- + body_sect_term = "self::w:sectPr[parent::w:body]" + # -- all the ps and tbls preceding (but not including) the context node -- + pred_ps_and_tbls = "preceding-sibling::*[self::w:p | self::w:tbl]" + + # -- p_sect_term_block and body_sect_term(inus) are mutually exclusive. So the + # -- result is either the union of nodes found by the first two selectors or the + # -- nodes found by the last selector, never both. + return ( + # -- include the p containing a sectPr -- + f"{p_sect_term_block}" + # -- along with all the blocks that precede it -- + f" | {p_sect_term_block}/{pred_ps_and_tbls}" + # -- or all the preceding blocks if sectPr is body-based (last sectPr) -- + f" | {body_sect_term}/{pred_ps_and_tbls}" + ) + + def _count_of_blocks_in_and_above_section(self, sectPr: CT_SectPr) -> int: + """All ps and tbls in section defined by `sectPr` and all prior sections.""" + if self._compiled_count_xpath is None: + self._compiled_count_xpath = etree.XPath( + f"count({self._blocks_in_and_above_section_xpath})", + namespaces=nsmap, + regexp=False, + ) + xpath = self._compiled_count_xpath + # -- numeric XPath results are always float, so need an int() conversion -- + return int(cast(float, xpath(sectPr))) + + @lazyproperty + def _sectPrs(self) -> Sequence[CT_SectPr]: + """All w:sectPr elements in document, in document-order.""" + return self._sectPr.xpath( + "/w:document/w:body/w:p/w:pPr/w:sectPr | /w:document/w:body/w:sectPr", + ) diff --git a/intelliaide/vendor/docx/oxml/settings.py b/intelliaide/vendor/docx/oxml/settings.py new file mode 100644 index 0000000..d5bb41a --- /dev/null +++ b/intelliaide/vendor/docx/oxml/settings.py @@ -0,0 +1,138 @@ +"""Custom element classes related to document settings.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable + +from docx.oxml.xmlchemy import BaseOxmlElement, ZeroOrOne + +if TYPE_CHECKING: + from docx.oxml.shared import CT_OnOff + + +class CT_Settings(BaseOxmlElement): + """`w:settings` element, root element for the settings part.""" + + get_or_add_evenAndOddHeaders: Callable[[], CT_OnOff] + _remove_evenAndOddHeaders: Callable[[], None] + + _tag_seq = ( + "w:writeProtection", + "w:view", + "w:zoom", + "w:removePersonalInformation", + "w:removeDateAndTime", + "w:doNotDisplayPageBoundaries", + "w:displayBackgroundShape", + "w:printPostScriptOverText", + "w:printFractionalCharacterWidth", + "w:printFormsData", + "w:embedTrueTypeFonts", + "w:embedSystemFonts", + "w:saveSubsetFonts", + "w:saveFormsData", + "w:mirrorMargins", + "w:alignBordersAndEdges", + "w:bordersDoNotSurroundHeader", + "w:bordersDoNotSurroundFooter", + "w:gutterAtTop", + "w:hideSpellingErrors", + "w:hideGrammaticalErrors", + "w:activeWritingStyle", + "w:proofState", + "w:formsDesign", + "w:attachedTemplate", + "w:linkStyles", + "w:stylePaneFormatFilter", + "w:stylePaneSortMethod", + "w:documentType", + "w:mailMerge", + "w:revisionView", + "w:trackRevisions", + "w:doNotTrackMoves", + "w:doNotTrackFormatting", + "w:documentProtection", + "w:autoFormatOverride", + "w:styleLockTheme", + "w:styleLockQFSet", + "w:defaultTabStop", + "w:autoHyphenation", + "w:consecutiveHyphenLimit", + "w:hyphenationZone", + "w:doNotHyphenateCaps", + "w:showEnvelope", + "w:summaryLength", + "w:clickAndTypeStyle", + "w:defaultTableStyle", + "w:evenAndOddHeaders", + "w:bookFoldRevPrinting", + "w:bookFoldPrinting", + "w:bookFoldPrintingSheets", + "w:drawingGridHorizontalSpacing", + "w:drawingGridVerticalSpacing", + "w:displayHorizontalDrawingGridEvery", + "w:displayVerticalDrawingGridEvery", + "w:doNotUseMarginsForDrawingGridOrigin", + "w:drawingGridHorizontalOrigin", + "w:drawingGridVerticalOrigin", + "w:doNotShadeFormData", + "w:noPunctuationKerning", + "w:characterSpacingControl", + "w:printTwoOnOne", + "w:strictFirstAndLastChars", + "w:noLineBreaksAfter", + "w:noLineBreaksBefore", + "w:savePreviewPicture", + "w:doNotValidateAgainstSchema", + "w:saveInvalidXml", + "w:ignoreMixedContent", + "w:alwaysShowPlaceholderText", + "w:doNotDemarcateInvalidXml", + "w:saveXmlDataOnly", + "w:useXSLTWhenSaving", + "w:saveThroughXslt", + "w:showXMLTags", + "w:alwaysMergeEmptyNamespace", + "w:updateFields", + "w:hdrShapeDefaults", + "w:footnotePr", + "w:endnotePr", + "w:compat", + "w:docVars", + "w:rsids", + "m:mathPr", + "w:attachedSchema", + "w:themeFontLang", + "w:clrSchemeMapping", + "w:doNotIncludeSubdocsInStats", + "w:doNotAutoCompressPictures", + "w:forceUpgrade", + "w:captions", + "w:readModeInkLockDown", + "w:smartTagType", + "sl:schemaLibrary", + "w:shapeDefaults", + "w:doNotEmbedSmartTags", + "w:decimalSymbol", + "w:listSeparator", + ) + evenAndOddHeaders: CT_OnOff | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:evenAndOddHeaders", successors=_tag_seq[48:] + ) + del _tag_seq + + @property + def evenAndOddHeaders_val(self) -> bool: + """Value of `w:evenAndOddHeaders/@w:val` or |None| if not present.""" + evenAndOddHeaders = self.evenAndOddHeaders + if evenAndOddHeaders is None: + return False + return evenAndOddHeaders.val + + @evenAndOddHeaders_val.setter + def evenAndOddHeaders_val(self, value: bool | None): + if value is None or value is False: + self._remove_evenAndOddHeaders() + return + + self.get_or_add_evenAndOddHeaders().val = value diff --git a/intelliaide/vendor/docx/oxml/shape.py b/intelliaide/vendor/docx/oxml/shape.py new file mode 100644 index 0000000..c6df8e7 --- /dev/null +++ b/intelliaide/vendor/docx/oxml/shape.py @@ -0,0 +1,299 @@ +"""Custom element classes for shape-related elements like ``.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, cast + +from docx.oxml.ns import nsdecls +from docx.oxml.parser import parse_xml +from docx.oxml.simpletypes import ( + ST_Coordinate, + ST_DrawingElementId, + ST_PositiveCoordinate, + ST_RelationshipId, + XsdString, + XsdToken, +) +from docx.oxml.xmlchemy import ( + BaseOxmlElement, + OneAndOnlyOne, + OptionalAttribute, + RequiredAttribute, + ZeroOrOne, +) + +if TYPE_CHECKING: + from docx.shared import Length + + +class CT_Anchor(BaseOxmlElement): + """`` element, container for a "floating" shape.""" + + +class CT_Blip(BaseOxmlElement): + """```` element, specifies image source and adjustments such as alpha and + tint.""" + + embed: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "r:embed", ST_RelationshipId + ) + link: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "r:link", ST_RelationshipId + ) + + +class CT_BlipFillProperties(BaseOxmlElement): + """```` element, specifies picture properties.""" + + blip: CT_Blip = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "a:blip", successors=("a:srcRect", "a:tile", "a:stretch") + ) + + +class CT_GraphicalObject(BaseOxmlElement): + """```` element, container for a DrawingML object.""" + + graphicData: CT_GraphicalObjectData = OneAndOnlyOne( # pyright: ignore[reportAssignmentType] + "a:graphicData" + ) + + +class CT_GraphicalObjectData(BaseOxmlElement): + """```` element, container for the XML of a DrawingML object.""" + + pic: CT_Picture = ZeroOrOne("pic:pic") # pyright: ignore[reportAssignmentType] + uri: str = RequiredAttribute("uri", XsdToken) # pyright: ignore[reportAssignmentType] + + +class CT_Inline(BaseOxmlElement): + """`` element, container for an inline shape.""" + + extent: CT_PositiveSize2D = OneAndOnlyOne("wp:extent") # pyright: ignore[reportAssignmentType] + docPr: CT_NonVisualDrawingProps = OneAndOnlyOne( # pyright: ignore[reportAssignmentType] + "wp:docPr" + ) + graphic: CT_GraphicalObject = OneAndOnlyOne( # pyright: ignore[reportAssignmentType] + "a:graphic" + ) + + @classmethod + def new(cls, cx: Length, cy: Length, shape_id: int, pic: CT_Picture) -> CT_Inline: + """Return a new ```` element populated with the values passed as + parameters.""" + inline = cast(CT_Inline, parse_xml(cls._inline_xml())) + inline.extent.cx = cx + inline.extent.cy = cy + inline.docPr.id = shape_id + inline.docPr.name = "Picture %d" % shape_id + inline.graphic.graphicData.uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" + inline.graphic.graphicData._insert_pic(pic) + return inline + + @classmethod + def new_pic_inline( + cls, shape_id: int, rId: str, filename: str, cx: Length, cy: Length + ) -> CT_Inline: + """Create `wp:inline` element containing a `pic:pic` element. + + The contents of the `pic:pic` element is taken from the argument values. + """ + pic_id = 0 # Word doesn't seem to use this, but does not omit it + pic = CT_Picture.new(pic_id, filename, rId, cx, cy) + inline = cls.new(cx, cy, shape_id, pic) + return inline + + @classmethod + def _inline_xml(cls): + return ( + "\n" + ' \n' + ' \n' + " \n" + ' \n' + " \n" + " \n" + ' \n' + " \n" + "" % nsdecls("wp", "a", "pic", "r") + ) + + +class CT_NonVisualDrawingProps(BaseOxmlElement): + """Used for ```` element, and perhaps others. + + Specifies the id and name of a DrawingML drawing. + """ + + id = RequiredAttribute("id", ST_DrawingElementId) + name = RequiredAttribute("name", XsdString) + + +class CT_NonVisualPictureProperties(BaseOxmlElement): + """```` element, specifies picture locking and resize behaviors.""" + + +class CT_Picture(BaseOxmlElement): + """```` element, a DrawingML picture.""" + + nvPicPr: CT_PictureNonVisual = OneAndOnlyOne( # pyright: ignore[reportAssignmentType] + "pic:nvPicPr" + ) + blipFill: CT_BlipFillProperties = OneAndOnlyOne( # pyright: ignore[reportAssignmentType] + "pic:blipFill" + ) + spPr: CT_ShapeProperties = OneAndOnlyOne("pic:spPr") # pyright: ignore[reportAssignmentType] + + @classmethod + def new(cls, pic_id: int, filename: str, rId: str, cx: Length, cy: Length) -> CT_Picture: + """A new minimum viable `` (picture) element.""" + pic = parse_xml(cls._pic_xml()) + pic.nvPicPr.cNvPr.id = pic_id + pic.nvPicPr.cNvPr.name = filename + pic.blipFill.blip.embed = rId + pic.spPr.cx = cx + pic.spPr.cy = cy + return pic + + @classmethod + def _pic_xml(cls): + return ( + "\n" + " \n" + ' \n' + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ' \n' + ' \n' + " \n" + ' \n' + " \n" + "" % nsdecls("pic", "a", "r") + ) + + +class CT_PictureNonVisual(BaseOxmlElement): + """```` element, non-visual picture properties.""" + + cNvPr = OneAndOnlyOne("pic:cNvPr") + + +class CT_Point2D(BaseOxmlElement): + """Used for ```` element, and perhaps others. + + Specifies an x, y coordinate (point). + """ + + x = RequiredAttribute("x", ST_Coordinate) + y = RequiredAttribute("y", ST_Coordinate) + + +class CT_PositiveSize2D(BaseOxmlElement): + """Used for ```` element, and perhaps others later. + + Specifies the size of a DrawingML drawing. + """ + + cx: Length = RequiredAttribute( # pyright: ignore[reportAssignmentType] + "cx", ST_PositiveCoordinate + ) + cy: Length = RequiredAttribute( # pyright: ignore[reportAssignmentType] + "cy", ST_PositiveCoordinate + ) + + +class CT_PresetGeometry2D(BaseOxmlElement): + """```` element, specifies an preset autoshape geometry, such as + ``rect``.""" + + +class CT_RelativeRect(BaseOxmlElement): + """```` element, specifying picture should fill containing rectangle + shape.""" + + +class CT_ShapeProperties(BaseOxmlElement): + """```` element, specifies size and shape of picture container.""" + + xfrm = ZeroOrOne( + "a:xfrm", + successors=( + "a:custGeom", + "a:prstGeom", + "a:ln", + "a:effectLst", + "a:effectDag", + "a:scene3d", + "a:sp3d", + "a:extLst", + ), + ) + + @property + def cx(self): + """Shape width as an instance of Emu, or None if not present.""" + xfrm = self.xfrm + if xfrm is None: + return None + return xfrm.cx + + @cx.setter + def cx(self, value): + xfrm = self.get_or_add_xfrm() + xfrm.cx = value + + @property + def cy(self): + """Shape height as an instance of Emu, or None if not present.""" + xfrm = self.xfrm + if xfrm is None: + return None + return xfrm.cy + + @cy.setter + def cy(self, value): + xfrm = self.get_or_add_xfrm() + xfrm.cy = value + + +class CT_StretchInfoProperties(BaseOxmlElement): + """```` element, specifies how picture should fill its containing + shape.""" + + +class CT_Transform2D(BaseOxmlElement): + """```` element, specifies size and shape of picture container.""" + + off = ZeroOrOne("a:off", successors=("a:ext",)) + ext = ZeroOrOne("a:ext", successors=()) + + @property + def cx(self): + ext = self.ext + if ext is None: + return None + return ext.cx + + @cx.setter + def cx(self, value): + ext = self.get_or_add_ext() + ext.cx = value + + @property + def cy(self): + ext = self.ext + if ext is None: + return None + return ext.cy + + @cy.setter + def cy(self, value): + ext = self.get_or_add_ext() + ext.cy = value diff --git a/intelliaide/vendor/docx/oxml/shared.py b/intelliaide/vendor/docx/oxml/shared.py new file mode 100644 index 0000000..8cfcd2b --- /dev/null +++ b/intelliaide/vendor/docx/oxml/shared.py @@ -0,0 +1,52 @@ +"""Objects shared by modules in the docx.oxml subpackage.""" + +from __future__ import annotations + +from typing import cast + +from docx.oxml.ns import qn +from docx.oxml.parser import OxmlElement +from docx.oxml.simpletypes import ST_DecimalNumber, ST_OnOff, ST_String +from docx.oxml.xmlchemy import BaseOxmlElement, OptionalAttribute, RequiredAttribute + + +class CT_DecimalNumber(BaseOxmlElement): + """Used for ````, ````, ```` and several others, + containing a text representation of a decimal number (e.g. 42) in its ``val`` + attribute.""" + + val: int = RequiredAttribute("w:val", ST_DecimalNumber) # pyright: ignore[reportAssignmentType] + + @classmethod + def new(cls, nsptagname: str, val: int): + """Return a new ``CT_DecimalNumber`` element having tagname `nsptagname` and + ``val`` attribute set to `val`.""" + return OxmlElement(nsptagname, attrs={qn("w:val"): str(val)}) + + +class CT_OnOff(BaseOxmlElement): + """Used for `w:b`, `w:i` elements and others. + + Contains a bool-ish string in its `val` attribute, xsd:boolean plus "on" and + "off". Defaults to `True`, so `` for example means "bold is turned on". + """ + + val: bool = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:val", ST_OnOff, default=True + ) + + +class CT_String(BaseOxmlElement): + """Used for `w:pStyle` and `w:tblStyle` elements and others. + + In those cases, it containing a style name in its `val` attribute. + """ + + val: str = RequiredAttribute("w:val", ST_String) # pyright: ignore[reportAssignmentType] + + @classmethod + def new(cls, nsptagname: str, val: str): + """A new `CT_String`` element with tagname `nsptagname` and `val` attribute set to `val`.""" + elm = cast(CT_String, OxmlElement(nsptagname)) + elm.val = val + return elm diff --git a/intelliaide/vendor/docx/oxml/simpletypes.py b/intelliaide/vendor/docx/oxml/simpletypes.py new file mode 100644 index 0000000..a0fc87d --- /dev/null +++ b/intelliaide/vendor/docx/oxml/simpletypes.py @@ -0,0 +1,434 @@ +# pyright: reportImportCycles=false + +"""Simple-type classes, corresponding to ST_* schema items. + +These provide validation and format translation for values stored in XML element +attributes. Naming generally corresponds to the simple type in the associated XML +schema. +""" + +from __future__ import annotations + +import datetime as dt +from typing import TYPE_CHECKING, Any, Tuple + +from docx.exceptions import InvalidXmlError +from docx.shared import Emu, Pt, RGBColor, Twips + +if TYPE_CHECKING: + from docx.shared import Length + + +class BaseSimpleType: + """Base class for simple-types.""" + + @classmethod + def from_xml(cls, xml_value: str) -> Any: + return cls.convert_from_xml(xml_value) + + @classmethod + def to_xml(cls, value: Any) -> str: + cls.validate(value) + str_value = cls.convert_to_xml(value) + return str_value + + @classmethod + def convert_from_xml(cls, str_value: str) -> Any: + return int(str_value) + + @classmethod + def convert_to_xml(cls, value: Any) -> str: ... + + @classmethod + def validate(cls, value: Any) -> None: ... + + @classmethod + def validate_int(cls, value: object): + if not isinstance(value, int): + raise TypeError("value must be , got %s" % type(value)) + + @classmethod + def validate_int_in_range(cls, value: int, min_inclusive: int, max_inclusive: int) -> None: + cls.validate_int(value) + if value < min_inclusive or value > max_inclusive: + raise ValueError( + "value must be in range %d to %d inclusive, got %d" + % (min_inclusive, max_inclusive, value) + ) + + @classmethod + def validate_string(cls, value: Any) -> str: + if not isinstance(value, str): + raise TypeError("value must be a string, got %s" % type(value)) + return value + + +class BaseIntType(BaseSimpleType): + @classmethod + def convert_from_xml(cls, str_value: str) -> int: + return int(str_value) + + @classmethod + def convert_to_xml(cls, value: int) -> str: + return str(value) + + @classmethod + def validate(cls, value: Any) -> None: + cls.validate_int(value) + + +class BaseStringType(BaseSimpleType): + @classmethod + def convert_from_xml(cls, str_value: str) -> str: + return str_value + + @classmethod + def convert_to_xml(cls, value: str) -> str: + return value + + @classmethod + def validate(cls, value: str): + cls.validate_string(value) + + +class BaseStringEnumerationType(BaseStringType): + _members: Tuple[str, ...] + + @classmethod + def validate(cls, value: Any) -> None: + cls.validate_string(value) + if value not in cls._members: + raise ValueError("must be one of %s, got '%s'" % (cls._members, value)) + + +class XsdAnyUri(BaseStringType): + """There's a regex in the spec this is supposed to meet... + + but current assessment is that spending cycles on validating wouldn't be worth it + for the number of programming errors it would catch. + """ + + +class XsdBoolean(BaseSimpleType): + @classmethod + def convert_from_xml(cls, str_value: str) -> bool: + if str_value not in ("1", "0", "true", "false"): + raise InvalidXmlError( + "value must be one of '1', '0', 'true' or 'false', got '%s'" % str_value + ) + return str_value in ("1", "true") + + @classmethod + def convert_to_xml(cls, value: bool) -> str: + return {True: "1", False: "0"}[value] + + @classmethod + def validate(cls, value: Any) -> None: + if value not in (True, False): + raise TypeError( + "only True or False (and possibly None) may be assigned, got '%s'" % value + ) + + +class XsdId(BaseStringType): + """String that must begin with a letter or underscore and cannot contain any colons. + + Not fully validated because not used in external API. + """ + + pass + + +class XsdInt(BaseIntType): + @classmethod + def validate(cls, value: Any) -> None: + cls.validate_int_in_range(value, -2147483648, 2147483647) + + +class XsdLong(BaseIntType): + @classmethod + def validate(cls, value: Any) -> None: + cls.validate_int_in_range(value, -9223372036854775808, 9223372036854775807) + + +class XsdString(BaseStringType): + pass + + +class XsdStringEnumeration(BaseStringEnumerationType): + """Set of enumerated xsd:string values.""" + + +class XsdToken(BaseStringType): + """Xsd:string with whitespace collapsing, e.g. multiple spaces reduced to one, + leading and trailing space stripped.""" + + pass + + +class XsdUnsignedInt(BaseIntType): + @classmethod + def validate(cls, value: Any) -> None: + cls.validate_int_in_range(value, 0, 4294967295) + + +class XsdUnsignedLong(BaseIntType): + @classmethod + def validate(cls, value: Any) -> None: + cls.validate_int_in_range(value, 0, 18446744073709551615) + + +class ST_BrClear(XsdString): + @classmethod + def validate(cls, value: str) -> None: + cls.validate_string(value) + valid_values = ("none", "left", "right", "all") + if value not in valid_values: + raise ValueError("must be one of %s, got '%s'" % (valid_values, value)) + + +class ST_BrType(XsdString): + @classmethod + def validate(cls, value: Any) -> None: + cls.validate_string(value) + valid_values = ("page", "column", "textWrapping") + if value not in valid_values: + raise ValueError("must be one of %s, got '%s'" % (valid_values, value)) + + +class ST_Coordinate(BaseIntType): + @classmethod + def convert_from_xml(cls, str_value: str) -> Length: + if "i" in str_value or "m" in str_value or "p" in str_value: + return ST_UniversalMeasure.convert_from_xml(str_value) + return Emu(int(str_value)) + + @classmethod + def validate(cls, value: Any) -> None: + ST_CoordinateUnqualified.validate(value) + + +class ST_CoordinateUnqualified(XsdLong): + @classmethod + def validate(cls, value: Any) -> None: + cls.validate_int_in_range(value, -27273042329600, 27273042316900) + + +class ST_DateTime(BaseSimpleType): + @classmethod + def convert_from_xml(cls, str_value: str) -> dt.datetime: + """Convert an xsd:dateTime string to a datetime object.""" + + def parse_xsd_datetime(dt_str: str) -> dt.datetime: + # -- handle trailing 'Z' (Zulu/UTC), common in Word files -- + if dt_str.endswith("Z"): + try: + # -- optional fractional seconds case -- + return dt.datetime.strptime(dt_str, "%Y-%m-%dT%H:%M:%S.%fZ").replace( + tzinfo=dt.timezone.utc + ) + except ValueError: + return dt.datetime.strptime(dt_str, "%Y-%m-%dT%H:%M:%SZ").replace( + tzinfo=dt.timezone.utc + ) + + # -- handles explicit offsets like +00:00, -05:00, or naive datetimes -- + try: + return dt.datetime.fromisoformat(dt_str) + except ValueError: + # -- fall-back to parsing as naive datetime (with or without fractional seconds) -- + try: + return dt.datetime.strptime(dt_str, "%Y-%m-%dT%H:%M:%S.%f") + except ValueError: + return dt.datetime.strptime(dt_str, "%Y-%m-%dT%H:%M:%S") + + try: + # -- parse anything reasonable, but never raise, just use default epoch time -- + return parse_xsd_datetime(str_value) + except Exception: + return dt.datetime(1970, 1, 1, tzinfo=dt.timezone.utc) + + @classmethod + def convert_to_xml(cls, value: dt.datetime) -> str: + # -- convert naive datetime to timezon-aware assuming local timezone -- + if value.tzinfo is None: + value = value.astimezone() + + # -- convert to UTC if not already -- + value = value.astimezone(dt.timezone.utc) + + # -- format with 'Z' suffix for UTC -- + return value.strftime("%Y-%m-%dT%H:%M:%SZ") + + @classmethod + def validate(cls, value: Any) -> None: + if not isinstance(value, dt.datetime): + raise TypeError("only a datetime.datetime object may be assigned, got '%s'" % value) + + +class ST_DecimalNumber(XsdInt): + pass + + +class ST_DrawingElementId(XsdUnsignedInt): + pass + + +class ST_HexColor(BaseStringType): + @classmethod + def convert_from_xml( # pyright: ignore[reportIncompatibleMethodOverride] + cls, str_value: str + ) -> RGBColor | str: + if str_value == "auto": + return ST_HexColorAuto.AUTO + return RGBColor.from_string(str_value) + + @classmethod + def convert_to_xml( # pyright: ignore[reportIncompatibleMethodOverride] + cls, value: RGBColor + ) -> str: + """Keep alpha hex numerals all uppercase just for consistency.""" + # expecting 3-tuple of ints in range 0-255 + return "%02X%02X%02X" % value + + @classmethod + def validate(cls, value: Any) -> None: + # must be an RGBColor object --- + if not isinstance(value, RGBColor): + raise ValueError( + "rgb color value must be RGBColor object, got %s %s" % (type(value), value) + ) + + +class ST_HexColorAuto(XsdStringEnumeration): + """Value for `w:color/[@val="auto"] attribute setting.""" + + AUTO = "auto" + + _members = (AUTO,) + + +class ST_HpsMeasure(XsdUnsignedLong): + """Half-point measure, e.g. 24.0 represents 12.0 points.""" + + @classmethod + def convert_from_xml(cls, str_value: str) -> Length: + if "m" in str_value or "n" in str_value or "p" in str_value: + return ST_UniversalMeasure.convert_from_xml(str_value) + return Pt(int(str_value) / 2.0) + + @classmethod + def convert_to_xml(cls, value: int | Length) -> str: + emu = Emu(value) + half_points = int(emu.pt * 2) + return str(half_points) + + +class ST_Merge(XsdStringEnumeration): + """Valid values for attribute.""" + + CONTINUE = "continue" + RESTART = "restart" + + _members = (CONTINUE, RESTART) + + +class ST_OnOff(XsdBoolean): + @classmethod + def convert_from_xml(cls, str_value: str) -> bool: + if str_value not in ("1", "0", "true", "false", "on", "off"): + raise InvalidXmlError( + "value must be one of '1', '0', 'true', 'false', 'on', or 'o" + "ff', got '%s'" % str_value + ) + return str_value in ("1", "true", "on") + + +class ST_PositiveCoordinate(XsdLong): + @classmethod + def convert_from_xml(cls, str_value: str) -> Length: + return Emu(int(str_value)) + + @classmethod + def validate(cls, value: Any) -> None: + cls.validate_int_in_range(value, 0, 27273042316900) + + +class ST_RelationshipId(XsdString): + pass + + +class ST_SignedTwipsMeasure(XsdInt): + @classmethod + def convert_from_xml(cls, str_value: str) -> Length: + if "i" in str_value or "m" in str_value or "p" in str_value: + return ST_UniversalMeasure.convert_from_xml(str_value) + return Twips(int(round(float(str_value)))) + + @classmethod + def convert_to_xml(cls, value: int | Length) -> str: + emu = Emu(value) + twips = emu.twips + return str(twips) + + +class ST_String(XsdString): + pass + + +class ST_TblLayoutType(XsdString): + @classmethod + def validate(cls, value: Any) -> None: + cls.validate_string(value) + valid_values = ("fixed", "autofit") + if value not in valid_values: + raise ValueError("must be one of %s, got '%s'" % (valid_values, value)) + + +class ST_TblWidth(XsdString): + @classmethod + def validate(cls, value: Any) -> None: + cls.validate_string(value) + valid_values = ("auto", "dxa", "nil", "pct") + if value not in valid_values: + raise ValueError("must be one of %s, got '%s'" % (valid_values, value)) + + +class ST_TwipsMeasure(XsdUnsignedLong): + @classmethod + def convert_from_xml(cls, str_value: str) -> Length: + if "i" in str_value or "m" in str_value or "p" in str_value: + return ST_UniversalMeasure.convert_from_xml(str_value) + return Twips(int(str_value)) + + @classmethod + def convert_to_xml(cls, value: int | Length) -> str: + emu = Emu(value) + twips = emu.twips + return str(twips) + + +class ST_UniversalMeasure(BaseSimpleType): + @classmethod + def convert_from_xml(cls, str_value: str) -> Emu: + float_part, units_part = str_value[:-2], str_value[-2:] + quantity = float(float_part) + multiplier = { + "mm": 36000, + "cm": 360000, + "in": 914400, + "pt": 12700, + "pc": 152400, + "pi": 152400, + }[units_part] + return Emu(int(round(quantity * multiplier))) + + +class ST_VerticalAlignRun(XsdStringEnumeration): + """Valid values for `w:vertAlign/@val`.""" + + BASELINE = "baseline" + SUPERSCRIPT = "superscript" + SUBSCRIPT = "subscript" + + _members = (BASELINE, SUPERSCRIPT, SUBSCRIPT) diff --git a/intelliaide/vendor/docx/oxml/styles.py b/intelliaide/vendor/docx/oxml/styles.py new file mode 100644 index 0000000..fb0e5d0 --- /dev/null +++ b/intelliaide/vendor/docx/oxml/styles.py @@ -0,0 +1,320 @@ +"""Custom element classes related to the styles part.""" + +from __future__ import annotations + +from docx.enum.style import WD_STYLE_TYPE +from docx.oxml.simpletypes import ST_DecimalNumber, ST_OnOff, ST_String +from docx.oxml.xmlchemy import ( + BaseOxmlElement, + OptionalAttribute, + RequiredAttribute, + ZeroOrMore, + ZeroOrOne, +) + + +def styleId_from_name(name): + """Return the style id corresponding to `name`, taking into account special-case + names such as 'Heading 1'.""" + return { + "caption": "Caption", + "heading 1": "Heading1", + "heading 2": "Heading2", + "heading 3": "Heading3", + "heading 4": "Heading4", + "heading 5": "Heading5", + "heading 6": "Heading6", + "heading 7": "Heading7", + "heading 8": "Heading8", + "heading 9": "Heading9", + }.get(name, name.replace(" ", "")) + + +class CT_LatentStyles(BaseOxmlElement): + """`w:latentStyles` element, defining behavior defaults for latent styles and + containing `w:lsdException` child elements that each override those defaults for a + named latent style.""" + + lsdException = ZeroOrMore("w:lsdException", successors=()) + + count = OptionalAttribute("w:count", ST_DecimalNumber) + defLockedState = OptionalAttribute("w:defLockedState", ST_OnOff) + defQFormat = OptionalAttribute("w:defQFormat", ST_OnOff) + defSemiHidden = OptionalAttribute("w:defSemiHidden", ST_OnOff) + defUIPriority = OptionalAttribute("w:defUIPriority", ST_DecimalNumber) + defUnhideWhenUsed = OptionalAttribute("w:defUnhideWhenUsed", ST_OnOff) + + def bool_prop(self, attr_name): + """Return the boolean value of the attribute having `attr_name`, or |False| if + not present.""" + value = getattr(self, attr_name) + if value is None: + return False + return value + + def get_by_name(self, name): + """Return the `w:lsdException` child having `name`, or |None| if not found.""" + found = self.xpath('w:lsdException[@w:name="%s"]' % name) + if not found: + return None + return found[0] + + def set_bool_prop(self, attr_name, value): + """Set the on/off attribute having `attr_name` to `value`.""" + setattr(self, attr_name, bool(value)) + + +class CT_LsdException(BaseOxmlElement): + """```` element, defining override visibility behaviors for a named + latent style.""" + + locked = OptionalAttribute("w:locked", ST_OnOff) + name = RequiredAttribute("w:name", ST_String) + qFormat = OptionalAttribute("w:qFormat", ST_OnOff) + semiHidden = OptionalAttribute("w:semiHidden", ST_OnOff) + uiPriority = OptionalAttribute("w:uiPriority", ST_DecimalNumber) + unhideWhenUsed = OptionalAttribute("w:unhideWhenUsed", ST_OnOff) + + def delete(self): + """Remove this `w:lsdException` element from the XML document.""" + self.getparent().remove(self) + + def on_off_prop(self, attr_name): + """Return the boolean value of the attribute having `attr_name`, or |None| if + not present.""" + return getattr(self, attr_name) + + def set_on_off_prop(self, attr_name, value): + """Set the on/off attribute having `attr_name` to `value`.""" + setattr(self, attr_name, value) + + +class CT_Style(BaseOxmlElement): + """A ```` element, representing a style definition.""" + + _tag_seq = ( + "w:name", + "w:aliases", + "w:basedOn", + "w:next", + "w:link", + "w:autoRedefine", + "w:hidden", + "w:uiPriority", + "w:semiHidden", + "w:unhideWhenUsed", + "w:qFormat", + "w:locked", + "w:personal", + "w:personalCompose", + "w:personalReply", + "w:rsid", + "w:pPr", + "w:rPr", + "w:tblPr", + "w:trPr", + "w:tcPr", + "w:tblStylePr", + ) + name = ZeroOrOne("w:name", successors=_tag_seq[1:]) + basedOn = ZeroOrOne("w:basedOn", successors=_tag_seq[3:]) + next = ZeroOrOne("w:next", successors=_tag_seq[4:]) + uiPriority = ZeroOrOne("w:uiPriority", successors=_tag_seq[8:]) + semiHidden = ZeroOrOne("w:semiHidden", successors=_tag_seq[9:]) + unhideWhenUsed = ZeroOrOne("w:unhideWhenUsed", successors=_tag_seq[10:]) + qFormat = ZeroOrOne("w:qFormat", successors=_tag_seq[11:]) + locked = ZeroOrOne("w:locked", successors=_tag_seq[12:]) + pPr = ZeroOrOne("w:pPr", successors=_tag_seq[17:]) + rPr = ZeroOrOne("w:rPr", successors=_tag_seq[18:]) + del _tag_seq + + type: WD_STYLE_TYPE | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:type", WD_STYLE_TYPE + ) + styleId: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:styleId", ST_String + ) + default = OptionalAttribute("w:default", ST_OnOff) + customStyle = OptionalAttribute("w:customStyle", ST_OnOff) + + @property + def basedOn_val(self): + """Value of `w:basedOn/@w:val` or |None| if not present.""" + basedOn = self.basedOn + if basedOn is None: + return None + return basedOn.val + + @basedOn_val.setter + def basedOn_val(self, value): + if value is None: + self._remove_basedOn() + else: + self.get_or_add_basedOn().val = value + + @property + def base_style(self): + """Sibling CT_Style element this style is based on or |None| if no base style or + base style not found.""" + basedOn = self.basedOn + if basedOn is None: + return None + styles = self.getparent() + base_style = styles.get_by_id(basedOn.val) + if base_style is None: + return None + return base_style + + def delete(self): + """Remove this `w:style` element from its parent `w:styles` element.""" + self.getparent().remove(self) + + @property + def locked_val(self): + """Value of `w:locked/@w:val` or |False| if not present.""" + locked = self.locked + if locked is None: + return False + return locked.val + + @locked_val.setter + def locked_val(self, value): + self._remove_locked() + if bool(value) is True: + locked = self._add_locked() + locked.val = value + + @property + def name_val(self): + """Value of ```` child or |None| if not present.""" + name = self.name + if name is None: + return None + return name.val + + @name_val.setter + def name_val(self, value): + self._remove_name() + if value is not None: + name = self._add_name() + name.val = value + + @property + def next_style(self): + """Sibling CT_Style element identified by the value of `w:name/@w:val` or |None| + if no value is present or no style with that style id is found.""" + next = self.next + if next is None: + return None + styles = self.getparent() + return styles.get_by_id(next.val) # None if not found + + @property + def qFormat_val(self): + """Value of `w:qFormat/@w:val` or |False| if not present.""" + qFormat = self.qFormat + if qFormat is None: + return False + return qFormat.val + + @qFormat_val.setter + def qFormat_val(self, value): + self._remove_qFormat() + if bool(value): + self._add_qFormat() + + @property + def semiHidden_val(self): + """Value of ```` child or |False| if not present.""" + semiHidden = self.semiHidden + if semiHidden is None: + return False + return semiHidden.val + + @semiHidden_val.setter + def semiHidden_val(self, value): + self._remove_semiHidden() + if bool(value) is True: + semiHidden = self._add_semiHidden() + semiHidden.val = value + + @property + def uiPriority_val(self): + """Value of ```` child or |None| if not present.""" + uiPriority = self.uiPriority + if uiPriority is None: + return None + return uiPriority.val + + @uiPriority_val.setter + def uiPriority_val(self, value): + self._remove_uiPriority() + if value is not None: + uiPriority = self._add_uiPriority() + uiPriority.val = value + + @property + def unhideWhenUsed_val(self): + """Value of `w:unhideWhenUsed/@w:val` or |False| if not present.""" + unhideWhenUsed = self.unhideWhenUsed + if unhideWhenUsed is None: + return False + return unhideWhenUsed.val + + @unhideWhenUsed_val.setter + def unhideWhenUsed_val(self, value): + self._remove_unhideWhenUsed() + if bool(value) is True: + unhideWhenUsed = self._add_unhideWhenUsed() + unhideWhenUsed.val = value + + +class CT_Styles(BaseOxmlElement): + """```` element, the root element of a styles part, i.e. styles.xml.""" + + _tag_seq = ("w:docDefaults", "w:latentStyles", "w:style") + latentStyles = ZeroOrOne("w:latentStyles", successors=_tag_seq[2:]) + style = ZeroOrMore("w:style", successors=()) + del _tag_seq + + def add_style_of_type(self, name, style_type, builtin): + """Return a newly added `w:style` element having `name` and `style_type`. + + `w:style/@customStyle` is set based on the value of `builtin`. + """ + style = self.add_style() + style.type = style_type + style.customStyle = None if builtin else True + style.styleId = styleId_from_name(name) + style.name_val = name + return style + + def default_for(self, style_type): + """Return `w:style[@w:type="*{style_type}*][-1]` or |None| if not found.""" + default_styles_for_type = [ + s for s in self._iter_styles() if s.type == style_type and s.default + ] + if not default_styles_for_type: + return None + # spec calls for last default in document order + return default_styles_for_type[-1] + + def get_by_id(self, styleId: str) -> CT_Style | None: + """`w:style` child where @styleId = `styleId`. + + |None| if not found. + """ + xpath = f'w:style[@w:styleId="{styleId}"]' + return next(iter(self.xpath(xpath)), None) + + def get_by_name(self, name: str) -> CT_Style | None: + """`w:style` child with `w:name` grandchild having value `name`. + + |None| if not found. + """ + xpath = 'w:style[w:name/@w:val="%s"]' % name + return next(iter(self.xpath(xpath)), None) + + def _iter_styles(self): + """Generate each of the `w:style` child elements in document order.""" + return (style for style in self.xpath("w:style")) diff --git a/intelliaide/vendor/docx/oxml/table.py b/intelliaide/vendor/docx/oxml/table.py new file mode 100644 index 0000000..9457da2 --- /dev/null +++ b/intelliaide/vendor/docx/oxml/table.py @@ -0,0 +1,977 @@ +"""Custom element classes for tables.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable, cast + +from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_ROW_HEIGHT_RULE, WD_TABLE_DIRECTION +from docx.exceptions import InvalidSpanError +from docx.oxml.ns import nsdecls, qn +from docx.oxml.parser import parse_xml +from docx.oxml.shared import CT_DecimalNumber +from docx.oxml.simpletypes import ( + ST_Merge, + ST_TblLayoutType, + ST_TblWidth, + ST_TwipsMeasure, + XsdInt, +) +from docx.oxml.text.paragraph import CT_P +from docx.oxml.xmlchemy import ( + BaseOxmlElement, + OneAndOnlyOne, + OneOrMore, + OptionalAttribute, + RequiredAttribute, + ZeroOrMore, + ZeroOrOne, +) +from docx.shared import Emu, Length, Twips + +if TYPE_CHECKING: + from docx.enum.table import WD_TABLE_ALIGNMENT + from docx.enum.text import WD_ALIGN_PARAGRAPH + from docx.oxml.shared import CT_OnOff, CT_String + from docx.oxml.text.parfmt import CT_Jc + + +class CT_Height(BaseOxmlElement): + """Used for `w:trHeight` to specify a row height and row height rule.""" + + val: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:val", ST_TwipsMeasure + ) + hRule: WD_ROW_HEIGHT_RULE | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:hRule", WD_ROW_HEIGHT_RULE + ) + + +class CT_Row(BaseOxmlElement): + """```` element.""" + + add_tc: Callable[[], CT_Tc] + get_or_add_trPr: Callable[[], CT_TrPr] + _add_trPr: Callable[[], CT_TrPr] + + tc_lst: list[CT_Tc] + # -- custom inserter below -- + tblPrEx: CT_TblPrEx | None = ZeroOrOne("w:tblPrEx") # pyright: ignore[reportAssignmentType] + # -- custom inserter below -- + trPr: CT_TrPr | None = ZeroOrOne("w:trPr") # pyright: ignore[reportAssignmentType] + tc = ZeroOrMore("w:tc") + + @property + def grid_after(self) -> int: + """The number of unpopulated layout-grid cells at the end of this row.""" + trPr = self.trPr + if trPr is None: + return 0 + return trPr.grid_after + + @property + def grid_before(self) -> int: + """The number of unpopulated layout-grid cells at the start of this row.""" + trPr = self.trPr + if trPr is None: + return 0 + return trPr.grid_before + + def tc_at_grid_offset(self, grid_offset: int) -> CT_Tc: + """The `tc` element in this tr at exact `grid offset`. + + Raises ValueError when this `w:tr` contains no `w:tc` with exact starting `grid_offset`. + """ + # -- account for omitted cells at the start of the row -- + remaining_offset = grid_offset - self.grid_before + + for tc in self.tc_lst: + # -- We've gone past grid_offset without finding a tc, no sense searching further. -- + if remaining_offset < 0: + break + # -- We've arrived at grid_offset, this is the `w:tc` we're looking for. -- + if remaining_offset == 0: + return tc + # -- We're not there yet, skip forward the number of layout-grid cells this cell + # -- occupies. + remaining_offset -= tc.grid_span + + raise ValueError(f"no `tc` element at grid_offset={grid_offset}") + + @property + def tr_idx(self) -> int: + """Index of this `w:tr` element within its parent `w:tbl` element.""" + tbl = cast(CT_Tbl, self.getparent()) + return tbl.tr_lst.index(self) + + @property + def trHeight_hRule(self) -> WD_ROW_HEIGHT_RULE | None: + """The value of `./w:trPr/w:trHeight/@w:hRule`, or |None| if not present.""" + trPr = self.trPr + if trPr is None: + return None + return trPr.trHeight_hRule + + @trHeight_hRule.setter + def trHeight_hRule(self, value: WD_ROW_HEIGHT_RULE | None): + trPr = self.get_or_add_trPr() + trPr.trHeight_hRule = value + + @property + def trHeight_val(self): + """Return the value of `w:trPr/w:trHeight@w:val`, or |None| if not present.""" + trPr = self.trPr + if trPr is None: + return None + return trPr.trHeight_val + + @trHeight_val.setter + def trHeight_val(self, value: Length | None): + trPr = self.get_or_add_trPr() + trPr.trHeight_val = value + + def _insert_tblPrEx(self, tblPrEx: CT_TblPrEx): + self.insert(0, tblPrEx) + + def _insert_trPr(self, trPr: CT_TrPr): + tblPrEx = self.tblPrEx + if tblPrEx is not None: + tblPrEx.addnext(trPr) + else: + self.insert(0, trPr) + + def _new_tc(self): + return CT_Tc.new() + + +class CT_Tbl(BaseOxmlElement): + """```` element.""" + + add_tr: Callable[[], CT_Row] + tr_lst: list[CT_Row] + + tblPr: CT_TblPr = OneAndOnlyOne("w:tblPr") # pyright: ignore[reportAssignmentType] + tblGrid: CT_TblGrid = OneAndOnlyOne("w:tblGrid") # pyright: ignore[reportAssignmentType] + tr = ZeroOrMore("w:tr") + + @property + def bidiVisual_val(self) -> bool | None: + """Value of `./w:tblPr/w:bidiVisual/@w:val` or |None| if not present. + + Controls whether table cells are displayed right-to-left or left-to-right. + """ + bidiVisual = self.tblPr.bidiVisual + if bidiVisual is None: + return None + return bidiVisual.val + + @bidiVisual_val.setter + def bidiVisual_val(self, value: WD_TABLE_DIRECTION | None): + tblPr = self.tblPr + if value is None: + tblPr._remove_bidiVisual() # pyright: ignore[reportPrivateUsage] + else: + tblPr.get_or_add_bidiVisual().val = bool(value) + + @property + def col_count(self): + """The number of grid columns in this table.""" + return len(self.tblGrid.gridCol_lst) + + def iter_tcs(self): + """Generate each of the `w:tc` elements in this table, left to right and top to + bottom. + + Each cell in the first row is generated, followed by each cell in the second + row, etc. + """ + for tr in self.tr_lst: + for tc in tr.tc_lst: + yield tc + + @classmethod + def new_tbl(cls, rows: int, cols: int, width: Length) -> CT_Tbl: + """Return a new `w:tbl` element having `rows` rows and `cols` columns. + + `width` is distributed evenly between the columns. + """ + return cast(CT_Tbl, parse_xml(cls._tbl_xml(rows, cols, width))) + + @property + def tblStyle_val(self) -> str | None: + """`w:tblPr/w:tblStyle/@w:val` (a table style id) or |None| if not present.""" + tblStyle = self.tblPr.tblStyle + if tblStyle is None: + return None + return tblStyle.val + + @tblStyle_val.setter + def tblStyle_val(self, styleId: str | None) -> None: + """Set the value of `w:tblPr/w:tblStyle/@w:val` (a table style id) to `styleId`. + + If `styleId` is None, remove the `w:tblStyle` element. + """ + tblPr = self.tblPr + tblPr._remove_tblStyle() # pyright: ignore[reportPrivateUsage] + if styleId is None: + return + tblPr._add_tblStyle().val = styleId # pyright: ignore[reportPrivateUsage] + + @classmethod + def _tbl_xml(cls, rows: int, cols: int, width: Length) -> str: + col_width = Emu(width // cols) if cols > 0 else Emu(0) + return ( + f"\n" + f" \n" + f' \n' + f' \n' + f" \n" + f"{cls._tblGrid_xml(cols, col_width)}" + f"{cls._trs_xml(rows, cols, col_width)}" + f"\n" + ) + + @classmethod + def _tblGrid_xml(cls, col_count: int, col_width: Length) -> str: + xml = " \n" + for _ in range(col_count): + xml += ' \n' % col_width.twips + xml += " \n" + return xml + + @classmethod + def _trs_xml(cls, row_count: int, col_count: int, col_width: Length) -> str: + return f" \n{cls._tcs_xml(col_count, col_width)} \n" * row_count + + @classmethod + def _tcs_xml(cls, col_count: int, col_width: Length) -> str: + return ( + f" \n" + f" \n" + f' \n' + f" \n" + f" \n" + f" \n" + ) * col_count + + +class CT_TblGrid(BaseOxmlElement): + """`w:tblGrid` element. + + Child of `w:tbl`, holds `w:gridCol> elements that define column count, width, etc. + """ + + add_gridCol: Callable[[], CT_TblGridCol] + gridCol_lst: list[CT_TblGridCol] + + gridCol = ZeroOrMore("w:gridCol", successors=("w:tblGridChange",)) + + +class CT_TblGridCol(BaseOxmlElement): + """`w:gridCol` element, child of `w:tblGrid`, defines a table column.""" + + w: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:w", ST_TwipsMeasure + ) + + @property + def gridCol_idx(self) -> int: + """Index of this `w:gridCol` element within its parent `w:tblGrid` element.""" + tblGrid = cast(CT_TblGrid, self.getparent()) + return tblGrid.gridCol_lst.index(self) + + +class CT_TblLayoutType(BaseOxmlElement): + """`w:tblLayout` element. + + Specifies whether column widths are fixed or can be automatically adjusted based on + content. + """ + + type: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:type", ST_TblLayoutType + ) + + +class CT_TblPr(BaseOxmlElement): + """```` element, child of ````, holds child elements that define + table properties such as style and borders.""" + + get_or_add_bidiVisual: Callable[[], CT_OnOff] + get_or_add_jc: Callable[[], CT_Jc] + get_or_add_tblLayout: Callable[[], CT_TblLayoutType] + _add_tblStyle: Callable[[], CT_String] + _remove_bidiVisual: Callable[[], None] + _remove_jc: Callable[[], None] + _remove_tblStyle: Callable[[], None] + + _tag_seq = ( + "w:tblStyle", + "w:tblpPr", + "w:tblOverlap", + "w:bidiVisual", + "w:tblStyleRowBandSize", + "w:tblStyleColBandSize", + "w:tblW", + "w:jc", + "w:tblCellSpacing", + "w:tblInd", + "w:tblBorders", + "w:shd", + "w:tblLayout", + "w:tblCellMar", + "w:tblLook", + "w:tblCaption", + "w:tblDescription", + "w:tblPrChange", + ) + tblStyle: CT_String | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:tblStyle", successors=_tag_seq[1:] + ) + bidiVisual: CT_OnOff | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:bidiVisual", successors=_tag_seq[4:] + ) + jc: CT_Jc | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:jc", successors=_tag_seq[8:] + ) + tblLayout: CT_TblLayoutType | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:tblLayout", successors=_tag_seq[13:] + ) + del _tag_seq + + @property + def alignment(self) -> WD_TABLE_ALIGNMENT | None: + """Horizontal alignment of table, |None| if `./w:jc` is not present.""" + jc = self.jc + if jc is None: + return None + return cast("WD_TABLE_ALIGNMENT | None", jc.val) + + @alignment.setter + def alignment(self, value: WD_TABLE_ALIGNMENT | None): + self._remove_jc() + if value is None: + return + jc = self.get_or_add_jc() + jc.val = cast("WD_ALIGN_PARAGRAPH", value) + + @property + def autofit(self) -> bool: + """|False| when there is a `w:tblLayout` child with `@w:type="fixed"`. + + Otherwise |True|. + """ + tblLayout = self.tblLayout + return True if tblLayout is None else tblLayout.type != "fixed" + + @autofit.setter + def autofit(self, value: bool): + tblLayout = self.get_or_add_tblLayout() + tblLayout.type = "autofit" if value else "fixed" + + @property + def style(self): + """Return the value of the ``val`` attribute of the ```` child or + |None| if not present.""" + tblStyle = self.tblStyle + if tblStyle is None: + return None + return tblStyle.val + + @style.setter + def style(self, value: str | None): + self._remove_tblStyle() + if value is None: + return + self._add_tblStyle().val = value + + +class CT_TblPrEx(BaseOxmlElement): + """`w:tblPrEx` element, exceptions to table-properties. + + Applied at a lower level, like a `w:tr` to modify the appearance. Possibly used when + two tables are merged. For more see: + http://officeopenxml.com/WPtablePropertyExceptions.php + """ + + +class CT_TblWidth(BaseOxmlElement): + """Used for `w:tblW` and `w:tcW` and others, specifies a table-related width.""" + + # the type for `w` attr is actually ST_MeasurementOrPercent, but using + # XsdInt for now because only dxa (twips) values are being used. It's not + # entirely clear what the semantics are for other values like -01.4mm + w: int = RequiredAttribute("w:w", XsdInt) # pyright: ignore[reportAssignmentType] + type = RequiredAttribute("w:type", ST_TblWidth) + + @property + def width(self) -> Length | None: + """EMU length indicated by the combined `w:w` and `w:type` attrs.""" + if self.type != "dxa": + return None + return Twips(self.w) + + @width.setter + def width(self, value: Length): + self.type = "dxa" + self.w = Emu(value).twips + + +class CT_Tc(BaseOxmlElement): + """`w:tc` table cell element.""" + + add_p: Callable[[], CT_P] + get_or_add_tcPr: Callable[[], CT_TcPr] + p_lst: list[CT_P] + tbl_lst: list[CT_Tbl] + _insert_tbl: Callable[[CT_Tbl], CT_Tbl] + _new_p: Callable[[], CT_P] + + # -- tcPr has many successors, `._insert_tcPr()` is overridden below -- + tcPr: CT_TcPr | None = ZeroOrOne("w:tcPr") # pyright: ignore[reportAssignmentType] + p = OneOrMore("w:p") + tbl = OneOrMore("w:tbl") + + @property + def bottom(self) -> int: + """The row index that marks the bottom extent of the vertical span of this cell. + + This is one greater than the index of the bottom-most row of the span, similar + to how a slice of the cell's rows would be specified. + """ + if self.vMerge is not None: + tc_below = self._tc_below + if tc_below is not None and tc_below.vMerge == ST_Merge.CONTINUE: + return tc_below.bottom + return self._tr_idx + 1 + + def clear_content(self): + """Remove all content elements, preserving `w:tcPr` element if present. + + Note that this leaves the `w:tc` element in an invalid state because it doesn't + contain at least one block-level element. It's up to the caller to add a + `w:p`child element as the last content element. + """ + # -- remove all cell inner-content except a `w:tcPr` when present. -- + for e in self.xpath("./*[not(self::w:tcPr)]"): + self.remove(e) + + @property + def grid_offset(self) -> int: + """Starting offset of `tc` in the layout-grid columns of its table. + + A cell in the leftmost grid-column has offset 0. + """ + grid_before = self._tr.grid_before + preceding_tc_grid_spans = sum( + tc.grid_span for tc in self.xpath("./preceding-sibling::w:tc") + ) + return grid_before + preceding_tc_grid_spans + + @property + def grid_span(self) -> int: + """The integer number of columns this cell spans. + + Determined by ./w:tcPr/w:gridSpan/@val, it defaults to 1. + """ + tcPr = self.tcPr + return 1 if tcPr is None else tcPr.grid_span + + @grid_span.setter + def grid_span(self, value: int): + tcPr = self.get_or_add_tcPr() + tcPr.grid_span = value + + @property + def inner_content_elements(self) -> list[CT_P | CT_Tbl]: + """Generate all `w:p` and `w:tbl` elements in this document-body. + + Elements appear in document order. Elements shaded by nesting in a `w:ins` or + other "wrapper" element will not be included. + """ + return self.xpath("./w:p | ./w:tbl") + + def iter_block_items(self): + """Generate a reference to each of the block-level content elements in this + cell, in the order they appear.""" + block_item_tags = (qn("w:p"), qn("w:tbl"), qn("w:sdt")) + for child in self: + if child.tag in block_item_tags: + yield child + + @property + def left(self) -> int: + """The grid column index at which this ```` element appears.""" + return self.grid_offset + + def merge(self, other_tc: CT_Tc) -> CT_Tc: + """Return top-left `w:tc` element of a new span. + + Span is formed by merging the rectangular region defined by using this tc + element and `other_tc` as diagonal corners. + """ + top, left, height, width = self._span_dimensions(other_tc) + top_tc = self._tbl.tr_lst[top].tc_at_grid_offset(left) + top_tc._grow_to(width, height) + return top_tc + + @classmethod + def new(cls) -> CT_Tc: + """A new `w:tc` element, containing an empty paragraph as the required EG_BlockLevelElt.""" + return cast(CT_Tc, parse_xml("" % nsdecls("w"))) + + @property + def right(self) -> int: + """The grid column index that marks the right-side extent of the horizontal span + of this cell. + + This is one greater than the index of the right-most column of the span, similar + to how a slice of the cell's columns would be specified. + """ + return self.grid_offset + self.grid_span + + @property + def top(self) -> int: + """The top-most row index in the vertical span of this cell.""" + if self.vMerge is None or self.vMerge == ST_Merge.RESTART: + return self._tr_idx + return self._tc_above.top + + @property + def vMerge(self) -> str | None: + """Value of ./w:tcPr/w:vMerge/@val, |None| if w:vMerge is not present.""" + tcPr = self.tcPr + if tcPr is None: + return None + return tcPr.vMerge_val + + @vMerge.setter + def vMerge(self, value: str | None): + tcPr = self.get_or_add_tcPr() + tcPr.vMerge_val = value + + @property + def width(self) -> Length | None: + """EMU length represented in `./w:tcPr/w:tcW` or |None| if not present.""" + tcPr = self.tcPr + if tcPr is None: + return None + return tcPr.width + + @width.setter + def width(self, value: Length): + tcPr = self.get_or_add_tcPr() + tcPr.width = value + + def _add_width_of(self, other_tc: CT_Tc): + """Add the width of `other_tc` to this cell. + + Does nothing if either this tc or `other_tc` does not have a specified width. + """ + if self.width and other_tc.width: + self.width = Length(self.width + other_tc.width) + + def _grow_to(self, width: int, height: int, top_tc: CT_Tc | None = None): + """Grow this cell to `width` grid columns and `height` rows. + + This is accomplished by expanding horizontal spans and creating continuation + cells to form vertical spans. + """ + + def vMerge_val(top_tc: CT_Tc): + return ( + ST_Merge.CONTINUE + if top_tc is not self + else None + if height == 1 + else ST_Merge.RESTART + ) + + top_tc = self if top_tc is None else top_tc + self._span_to_width(width, top_tc, vMerge_val(top_tc)) + if height > 1: + tc_below = self._tc_below + assert tc_below is not None + tc_below._grow_to(width, height - 1, top_tc) + + def _insert_tcPr(self, tcPr: CT_TcPr) -> CT_TcPr: + """Override default `._insert_tcPr()`.""" + # -- `tcPr`` has a large number of successors, but always comes first if it appears, + # -- so just using insert(0, ...) rather than spelling out successors. + self.insert(0, tcPr) + return tcPr + + @property + def _is_empty(self) -> bool: + """True if this cell contains only a single empty `w:p` element.""" + block_items = list(self.iter_block_items()) + if len(block_items) > 1: + return False + # -- cell must include at least one block item but can be a `w:tbl`, `w:sdt`, + # -- `w:customXml` or a `w:p` + only_item = block_items[0] + return isinstance(only_item, CT_P) and len(only_item.r_lst) == 0 + + def _move_content_to(self, other_tc: CT_Tc): + """Append the content of this cell to `other_tc`. + + Leaves this cell with a single empty ```` element. + """ + if other_tc is self: + return + if self._is_empty: + return + other_tc._remove_trailing_empty_p() + # -- appending moves each element from self to other_tc -- + for block_element in self.iter_block_items(): + other_tc.append(block_element) + # -- add back the required minimum single empty element -- + self.append(self._new_p()) + + def _new_tbl(self) -> None: + raise NotImplementedError( + "use CT_Tbl.new_tbl() to add a new table, specifying rows and columns" + ) + + @property + def _next_tc(self) -> CT_Tc | None: + """The `w:tc` element immediately following this one in this row, or |None| if + this is the last `w:tc` element in the row.""" + following_tcs = self.xpath("./following-sibling::w:tc") + return following_tcs[0] if following_tcs else None + + def _remove(self): + """Remove this `w:tc` element from the XML tree.""" + parent_element = self.getparent() + assert parent_element is not None + parent_element.remove(self) + + def _remove_trailing_empty_p(self): + """Remove last content element from this cell if it's an empty `w:p` element.""" + block_items = list(self.iter_block_items()) + last_content_elm = block_items[-1] + if not isinstance(last_content_elm, CT_P): + return + p = last_content_elm + if len(p.r_lst) > 0: + return + self.remove(p) + + def _span_dimensions(self, other_tc: CT_Tc) -> tuple[int, int, int, int]: + """Return a (top, left, height, width) 4-tuple specifying the extents of the + merged cell formed by using this tc and `other_tc` as opposite corner + extents.""" + + def raise_on_inverted_L(a: CT_Tc, b: CT_Tc): + if a.top == b.top and a.bottom != b.bottom: + raise InvalidSpanError("requested span not rectangular") + if a.left == b.left and a.right != b.right: + raise InvalidSpanError("requested span not rectangular") + + def raise_on_tee_shaped(a: CT_Tc, b: CT_Tc): + top_most, other = (a, b) if a.top < b.top else (b, a) + if top_most.top < other.top and top_most.bottom > other.bottom: + raise InvalidSpanError("requested span not rectangular") + + left_most, other = (a, b) if a.left < b.left else (b, a) + if left_most.left < other.left and left_most.right > other.right: + raise InvalidSpanError("requested span not rectangular") + + raise_on_inverted_L(self, other_tc) + raise_on_tee_shaped(self, other_tc) + + top = min(self.top, other_tc.top) + left = min(self.left, other_tc.left) + bottom = max(self.bottom, other_tc.bottom) + right = max(self.right, other_tc.right) + + return top, left, bottom - top, right - left + + def _span_to_width(self, grid_width: int, top_tc: CT_Tc, vMerge: str | None): + """Incorporate `w:tc` elements to the right until this cell spans `grid_width`. + + Incorporated `w:tc` elements are removed (replaced by gridSpan value). + + Raises |ValueError| if `grid_width` cannot be exactly achieved, such as when a + merged cell would drive the span width greater than `grid_width` or if not + enough grid columns are available to make this cell that wide. All content from + incorporated cells is appended to `top_tc`. The val attribute of the vMerge + element on the single remaining cell is set to `vMerge`. If `vMerge` is |None|, + the vMerge element is removed if present. + """ + self._move_content_to(top_tc) + while self.grid_span < grid_width: + self._swallow_next_tc(grid_width, top_tc) + self.vMerge = vMerge + + def _swallow_next_tc(self, grid_width: int, top_tc: CT_Tc): + """Extend the horizontal span of this `w:tc` element to incorporate the + following `w:tc` element in the row and then delete that following `w:tc` + element. + + Any content in the following `w:tc` element is appended to the content of + `top_tc`. The width of the following `w:tc` element is added to this one, if + present. Raises |InvalidSpanError| if the width of the resulting cell is greater + than `grid_width` or if there is no next `` element in the row. + """ + + def raise_on_invalid_swallow(next_tc: CT_Tc | None): + if next_tc is None: + raise InvalidSpanError("not enough grid columns") + if self.grid_span + next_tc.grid_span > grid_width: + raise InvalidSpanError("span is not rectangular") + + next_tc = self._next_tc + raise_on_invalid_swallow(next_tc) + assert next_tc is not None + next_tc._move_content_to(top_tc) + self._add_width_of(next_tc) + self.grid_span += next_tc.grid_span + next_tc._remove() + + @property + def _tbl(self) -> CT_Tbl: + """The tbl element this tc element appears in.""" + return cast(CT_Tbl, self.xpath("./ancestor::w:tbl[position()=1]")[0]) + + @property + def _tc_above(self) -> CT_Tc: + """The `w:tc` element immediately above this one in its grid column.""" + return self._tr_above.tc_at_grid_offset(self.grid_offset) + + @property + def _tc_below(self) -> CT_Tc | None: + """The tc element immediately below this one in its grid column.""" + tr_below = self._tr_below + if tr_below is None: + return None + return tr_below.tc_at_grid_offset(self.grid_offset) + + @property + def _tr(self) -> CT_Row: + """The tr element this tc element appears in.""" + return cast(CT_Row, self.xpath("./ancestor::w:tr[position()=1]")[0]) + + @property + def _tr_above(self) -> CT_Row: + """The tr element prior in sequence to the tr this cell appears in. + + Raises |ValueError| if called on a cell in the top-most row. + """ + tr_aboves = self.xpath("./ancestor::w:tr[position()=1]/preceding-sibling::w:tr[1]") + if not tr_aboves: + raise ValueError("no tr above topmost tr in w:tbl") + return tr_aboves[0] + + @property + def _tr_below(self) -> CT_Row | None: + """The tr element next in sequence after the tr this cell appears in, or |None| + if this cell appears in the last row.""" + tr_lst = self._tbl.tr_lst + tr_idx = tr_lst.index(self._tr) + try: + return tr_lst[tr_idx + 1] + except IndexError: + return None + + @property + def _tr_idx(self) -> int: + """The row index of the tr element this tc element appears in.""" + return self._tbl.tr_lst.index(self._tr) + + +class CT_TcPr(BaseOxmlElement): + """```` element, defining table cell properties.""" + + get_or_add_gridSpan: Callable[[], CT_DecimalNumber] + get_or_add_tcW: Callable[[], CT_TblWidth] + get_or_add_vAlign: Callable[[], CT_VerticalJc] + _add_vMerge: Callable[[], CT_VMerge] + _remove_gridSpan: Callable[[], None] + _remove_vAlign: Callable[[], None] + _remove_vMerge: Callable[[], None] + + _tag_seq = ( + "w:cnfStyle", + "w:tcW", + "w:gridSpan", + "w:hMerge", + "w:vMerge", + "w:tcBorders", + "w:shd", + "w:noWrap", + "w:tcMar", + "w:textDirection", + "w:tcFitText", + "w:vAlign", + "w:hideMark", + "w:headers", + "w:cellIns", + "w:cellDel", + "w:cellMerge", + "w:tcPrChange", + ) + tcW: CT_TblWidth | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:tcW", successors=_tag_seq[2:] + ) + gridSpan: CT_DecimalNumber | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:gridSpan", successors=_tag_seq[3:] + ) + vMerge: CT_VMerge | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:vMerge", successors=_tag_seq[5:] + ) + vAlign: CT_VerticalJc | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:vAlign", successors=_tag_seq[12:] + ) + del _tag_seq + + @property + def grid_span(self) -> int: + """The integer number of columns this cell spans. + + Determined by ./w:gridSpan/@val, it defaults to 1. + """ + gridSpan = self.gridSpan + return 1 if gridSpan is None else gridSpan.val + + @grid_span.setter + def grid_span(self, value: int): + self._remove_gridSpan() + if value > 1: + self.get_or_add_gridSpan().val = value + + @property + def vAlign_val(self): + """Value of `w:val` attribute on `w:vAlign` child. + + Value is |None| if `w:vAlign` child is not present. The `w:val` attribute on + `w:vAlign` is required. + """ + vAlign = self.vAlign + if vAlign is None: + return None + return vAlign.val + + @vAlign_val.setter + def vAlign_val(self, value: WD_CELL_VERTICAL_ALIGNMENT | None): + if value is None: + self._remove_vAlign() + return + self.get_or_add_vAlign().val = value + + @property + def vMerge_val(self): + """The value of the ./w:vMerge/@val attribute, or |None| if the w:vMerge element + is not present.""" + vMerge = self.vMerge + if vMerge is None: + return None + return vMerge.val + + @vMerge_val.setter + def vMerge_val(self, value: str | None): + self._remove_vMerge() + if value is not None: + self._add_vMerge().val = value + + @property + def width(self) -> Length | None: + """EMU length in `./w:tcW` or |None| if not present or its type is not 'dxa'.""" + tcW = self.tcW + if tcW is None: + return None + return tcW.width + + @width.setter + def width(self, value: Length): + tcW = self.get_or_add_tcW() + tcW.width = value + + +class CT_TrPr(BaseOxmlElement): + """```` element, defining table row properties.""" + + get_or_add_trHeight: Callable[[], CT_Height] + + _tag_seq = ( + "w:cnfStyle", + "w:divId", + "w:gridBefore", + "w:gridAfter", + "w:wBefore", + "w:wAfter", + "w:cantSplit", + "w:trHeight", + "w:tblHeader", + "w:tblCellSpacing", + "w:jc", + "w:hidden", + "w:ins", + "w:del", + "w:trPrChange", + ) + gridAfter: CT_DecimalNumber | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:gridAfter", successors=_tag_seq[4:] + ) + gridBefore: CT_DecimalNumber | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:gridBefore", successors=_tag_seq[3:] + ) + trHeight: CT_Height | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:trHeight", successors=_tag_seq[8:] + ) + del _tag_seq + + @property + def grid_after(self) -> int: + """The number of unpopulated layout-grid cells at the end of this row.""" + gridAfter = self.gridAfter + return 0 if gridAfter is None else gridAfter.val + + @property + def grid_before(self) -> int: + """The number of unpopulated layout-grid cells at the start of this row.""" + gridBefore = self.gridBefore + return 0 if gridBefore is None else gridBefore.val + + @property + def trHeight_hRule(self) -> WD_ROW_HEIGHT_RULE | None: + """Return the value of `w:trHeight@w:hRule`, or |None| if not present.""" + trHeight = self.trHeight + return None if trHeight is None else trHeight.hRule + + @trHeight_hRule.setter + def trHeight_hRule(self, value: WD_ROW_HEIGHT_RULE | None): + if value is None and self.trHeight is None: + return + trHeight = self.get_or_add_trHeight() + trHeight.hRule = value + + @property + def trHeight_val(self): + """Return the value of `w:trHeight@w:val`, or |None| if not present.""" + trHeight = self.trHeight + return None if trHeight is None else trHeight.val + + @trHeight_val.setter + def trHeight_val(self, value: Length | None): + if value is None and self.trHeight is None: + return + trHeight = self.get_or_add_trHeight() + trHeight.val = value + + +class CT_VerticalJc(BaseOxmlElement): + """`w:vAlign` element, specifying vertical alignment of cell.""" + + val: WD_CELL_VERTICAL_ALIGNMENT = RequiredAttribute( # pyright: ignore[reportAssignmentType] + "w:val", WD_CELL_VERTICAL_ALIGNMENT + ) + + +class CT_VMerge(BaseOxmlElement): + """```` element, specifying vertical merging behavior of a cell.""" + + val: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:val", ST_Merge, default=ST_Merge.CONTINUE + ) diff --git a/intelliaide/vendor/docx/oxml/text/__init__.py b/intelliaide/vendor/docx/oxml/text/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/intelliaide/vendor/docx/oxml/text/font.py b/intelliaide/vendor/docx/oxml/text/font.py new file mode 100644 index 0000000..32eb567 --- /dev/null +++ b/intelliaide/vendor/docx/oxml/text/font.py @@ -0,0 +1,331 @@ +# pyright: reportAssignmentType=false + +"""Custom element classes related to run properties (font).""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable + +from docx.enum.dml import MSO_THEME_COLOR +from docx.enum.text import WD_COLOR_INDEX, WD_UNDERLINE +from docx.oxml.ns import nsdecls +from docx.oxml.parser import parse_xml +from docx.oxml.simpletypes import ( + ST_HexColor, + ST_HpsMeasure, + ST_String, + ST_VerticalAlignRun, +) +from docx.oxml.xmlchemy import ( + BaseOxmlElement, + OptionalAttribute, + RequiredAttribute, + ZeroOrOne, +) +from docx.shared import RGBColor + +if TYPE_CHECKING: + from docx.oxml.shared import CT_OnOff, CT_String + from docx.shared import Length + + +class CT_Color(BaseOxmlElement): + """`w:color` element, specifying the color of a font and perhaps other objects.""" + + val: RGBColor | str = RequiredAttribute("w:val", ST_HexColor) + themeColor: MSO_THEME_COLOR | None = OptionalAttribute("w:themeColor", MSO_THEME_COLOR) + + +class CT_Fonts(BaseOxmlElement): + """`` element. + + Specifies typeface name for the various language types. + """ + + ascii: str | None = OptionalAttribute("w:ascii", ST_String) + hAnsi: str | None = OptionalAttribute("w:hAnsi", ST_String) + + +class CT_Highlight(BaseOxmlElement): + """`w:highlight` element, specifying font highlighting/background color.""" + + val: WD_COLOR_INDEX = RequiredAttribute("w:val", WD_COLOR_INDEX) + + +class CT_HpsMeasure(BaseOxmlElement): + """Used for `` element and others, specifying font size in half-points.""" + + val: Length = RequiredAttribute("w:val", ST_HpsMeasure) + + +class CT_RPr(BaseOxmlElement): + """`` element, containing the properties for a run.""" + + get_or_add_color: Callable[[], CT_Color] + get_or_add_highlight: Callable[[], CT_Highlight] + get_or_add_rFonts: Callable[[], CT_Fonts] + get_or_add_sz: Callable[[], CT_HpsMeasure] + get_or_add_vertAlign: Callable[[], CT_VerticalAlignRun] + _add_rStyle: Callable[..., CT_String] + _add_u: Callable[[], CT_Underline] + _remove_color: Callable[[], None] + _remove_highlight: Callable[[], None] + _remove_rFonts: Callable[[], None] + _remove_rStyle: Callable[[], None] + _remove_sz: Callable[[], None] + _remove_u: Callable[[], None] + _remove_vertAlign: Callable[[], None] + + _tag_seq = ( + "w:rStyle", + "w:rFonts", + "w:b", + "w:bCs", + "w:i", + "w:iCs", + "w:caps", + "w:smallCaps", + "w:strike", + "w:dstrike", + "w:outline", + "w:shadow", + "w:emboss", + "w:imprint", + "w:noProof", + "w:snapToGrid", + "w:vanish", + "w:webHidden", + "w:color", + "w:spacing", + "w:w", + "w:kern", + "w:position", + "w:sz", + "w:szCs", + "w:highlight", + "w:u", + "w:effect", + "w:bdr", + "w:shd", + "w:fitText", + "w:vertAlign", + "w:rtl", + "w:cs", + "w:em", + "w:lang", + "w:eastAsianLayout", + "w:specVanish", + "w:oMath", + ) + rStyle: CT_String | None = ZeroOrOne("w:rStyle", successors=_tag_seq[1:]) + rFonts: CT_Fonts | None = ZeroOrOne("w:rFonts", successors=_tag_seq[2:]) + b: CT_OnOff | None = ZeroOrOne("w:b", successors=_tag_seq[3:]) + bCs = ZeroOrOne("w:bCs", successors=_tag_seq[4:]) + i = ZeroOrOne("w:i", successors=_tag_seq[5:]) + iCs = ZeroOrOne("w:iCs", successors=_tag_seq[6:]) + caps = ZeroOrOne("w:caps", successors=_tag_seq[7:]) + smallCaps = ZeroOrOne("w:smallCaps", successors=_tag_seq[8:]) + strike = ZeroOrOne("w:strike", successors=_tag_seq[9:]) + dstrike = ZeroOrOne("w:dstrike", successors=_tag_seq[10:]) + outline = ZeroOrOne("w:outline", successors=_tag_seq[11:]) + shadow = ZeroOrOne("w:shadow", successors=_tag_seq[12:]) + emboss = ZeroOrOne("w:emboss", successors=_tag_seq[13:]) + imprint = ZeroOrOne("w:imprint", successors=_tag_seq[14:]) + noProof = ZeroOrOne("w:noProof", successors=_tag_seq[15:]) + snapToGrid = ZeroOrOne("w:snapToGrid", successors=_tag_seq[16:]) + vanish = ZeroOrOne("w:vanish", successors=_tag_seq[17:]) + webHidden = ZeroOrOne("w:webHidden", successors=_tag_seq[18:]) + color: CT_Color | None = ZeroOrOne("w:color", successors=_tag_seq[19:]) + sz: CT_HpsMeasure | None = ZeroOrOne("w:sz", successors=_tag_seq[24:]) + highlight: CT_Highlight | None = ZeroOrOne("w:highlight", successors=_tag_seq[26:]) + u: CT_Underline | None = ZeroOrOne("w:u", successors=_tag_seq[27:]) + vertAlign: CT_VerticalAlignRun | None = ZeroOrOne("w:vertAlign", successors=_tag_seq[32:]) + rtl = ZeroOrOne("w:rtl", successors=_tag_seq[33:]) + cs = ZeroOrOne("w:cs", successors=_tag_seq[34:]) + specVanish = ZeroOrOne("w:specVanish", successors=_tag_seq[38:]) + oMath = ZeroOrOne("w:oMath", successors=_tag_seq[39:]) + del _tag_seq + + def _new_color(self): + """Override metaclass method to set `w:color/@val` to RGB black on create.""" + return parse_xml('' % nsdecls("w")) + + @property + def highlight_val(self) -> WD_COLOR_INDEX | None: + """Value of `./w:highlight/@val`. + + Specifies font's highlight color, or `None` if the text is not highlighted. + """ + highlight = self.highlight + if highlight is None: + return None + return highlight.val + + @highlight_val.setter + def highlight_val(self, value: WD_COLOR_INDEX | None) -> None: + if value is None: + self._remove_highlight() + return + highlight = self.get_or_add_highlight() + highlight.val = value + + @property + def rFonts_ascii(self) -> str | None: + """The value of `w:rFonts/@w:ascii` or |None| if not present. + + Represents the assigned typeface name. The rFonts element also specifies other + special-case typeface names; this method handles the case where just the common + name is required. + """ + rFonts = self.rFonts + if rFonts is None: + return None + return rFonts.ascii + + @rFonts_ascii.setter + def rFonts_ascii(self, value: str | None) -> None: + if value is None: + self._remove_rFonts() + return + rFonts = self.get_or_add_rFonts() + rFonts.ascii = value + + @property + def rFonts_hAnsi(self) -> str | None: + """The value of `w:rFonts/@w:hAnsi` or |None| if not present.""" + rFonts = self.rFonts + if rFonts is None: + return None + return rFonts.hAnsi + + @rFonts_hAnsi.setter + def rFonts_hAnsi(self, value: str | None): + if value is None and self.rFonts is None: + return + rFonts = self.get_or_add_rFonts() + rFonts.hAnsi = value + + @property + def style(self) -> str | None: + """String in `./w:rStyle/@val`, or None if `w:rStyle` is not present.""" + rStyle = self.rStyle + if rStyle is None: + return None + return rStyle.val + + @style.setter + def style(self, style: str | None) -> None: + """Set `./w:rStyle/@val` to `style`, adding the `w:rStyle` element if necessary. + + If `style` is |None|, remove `w:rStyle` element if present. + """ + if style is None: + self._remove_rStyle() + elif self.rStyle is None: + self._add_rStyle(val=style) + else: + self.rStyle.val = style + + @property + def subscript(self) -> bool | None: + """|True| if `./w:vertAlign/@w:val` is "subscript". + + |False| if `w:vertAlign/@w:val` contains any other value. |None| if + `w:vertAlign` is not present. + """ + vertAlign = self.vertAlign + if vertAlign is None: + return None + return vertAlign.val == ST_VerticalAlignRun.SUBSCRIPT + + @subscript.setter + def subscript(self, value: bool | None) -> None: + if value is None: + self._remove_vertAlign() + elif bool(value) is True: + self.get_or_add_vertAlign().val = ST_VerticalAlignRun.SUBSCRIPT + # -- assert bool(value) is False -- + elif self.vertAlign is not None and self.vertAlign.val == ST_VerticalAlignRun.SUBSCRIPT: + self._remove_vertAlign() + + @property + def superscript(self) -> bool | None: + """|True| if `w:vertAlign/@w:val` is 'superscript'. + + |False| if `w:vertAlign/@w:val` contains any other value. |None| if + `w:vertAlign` is not present. + """ + vertAlign = self.vertAlign + if vertAlign is None: + return None + return vertAlign.val == ST_VerticalAlignRun.SUPERSCRIPT + + @superscript.setter + def superscript(self, value: bool | None): + if value is None: + self._remove_vertAlign() + elif bool(value) is True: + self.get_or_add_vertAlign().val = ST_VerticalAlignRun.SUPERSCRIPT + # -- assert bool(value) is False -- + elif self.vertAlign is not None and self.vertAlign.val == ST_VerticalAlignRun.SUPERSCRIPT: + self._remove_vertAlign() + + @property + def sz_val(self) -> Length | None: + """The value of `w:sz/@w:val` or |None| if not present.""" + sz = self.sz + if sz is None: + return None + return sz.val + + @sz_val.setter + def sz_val(self, value: Length | None): + if value is None: + self._remove_sz() + return + sz = self.get_or_add_sz() + sz.val = value + + @property + def u_val(self) -> WD_UNDERLINE | None: + """Value of `w:u/@val`, or None if not present. + + Values `WD_UNDERLINE.SINGLE` and `WD_UNDERLINE.NONE` are mapped to `True` and + `False` respectively. + """ + u = self.u + if u is None: + return None + return u.val + + @u_val.setter + def u_val(self, value: WD_UNDERLINE | None): + self._remove_u() + if value is not None: + self._add_u().val = value + + def _get_bool_val(self, name: str) -> bool | None: + """Value of boolean child with `name`, e.g. "w:b", "w:i", and "w:smallCaps".""" + element = getattr(self, name) + if element is None: + return None + return element.val + + def _set_bool_val(self, name: str, value: bool | None): + if value is None: + getattr(self, "_remove_%s" % name)() + return + element = getattr(self, "get_or_add_%s" % name)() + element.val = value + + +class CT_Underline(BaseOxmlElement): + """`` element, specifying the underlining style for a run.""" + + val: WD_UNDERLINE | None = OptionalAttribute("w:val", WD_UNDERLINE) + + +class CT_VerticalAlignRun(BaseOxmlElement): + """`` element, specifying subscript or superscript.""" + + val: str = RequiredAttribute("w:val", ST_VerticalAlignRun) diff --git a/intelliaide/vendor/docx/oxml/text/hyperlink.py b/intelliaide/vendor/docx/oxml/text/hyperlink.py new file mode 100644 index 0000000..38a33ff --- /dev/null +++ b/intelliaide/vendor/docx/oxml/text/hyperlink.py @@ -0,0 +1,45 @@ +"""Custom element classes related to hyperlinks (CT_Hyperlink).""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, List + +from docx.oxml.simpletypes import ST_OnOff, ST_String, XsdString +from docx.oxml.text.run import CT_R +from docx.oxml.xmlchemy import ( + BaseOxmlElement, + OptionalAttribute, + ZeroOrMore, +) + +if TYPE_CHECKING: + from docx.oxml.text.pagebreak import CT_LastRenderedPageBreak + + +class CT_Hyperlink(BaseOxmlElement): + """`` element, containing the text and address for a hyperlink.""" + + r_lst: List[CT_R] + + rId: str | None = OptionalAttribute("r:id", XsdString) # pyright: ignore[reportAssignmentType] + anchor: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:anchor", ST_String + ) + history: bool = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:history", ST_OnOff, default=True + ) + + r = ZeroOrMore("w:r") + + @property + def lastRenderedPageBreaks(self) -> List[CT_LastRenderedPageBreak]: + """All `w:lastRenderedPageBreak` descendants of this hyperlink.""" + return self.xpath("./w:r/w:lastRenderedPageBreak") + + @property + def text(self) -> str: # pyright: ignore[reportIncompatibleMethodOverride] + """The textual content of this hyperlink. + + `CT_Hyperlink` stores the hyperlink-text as one or more `w:r` children. + """ + return "".join(r.text for r in self.xpath("w:r")) diff --git a/intelliaide/vendor/docx/oxml/text/pagebreak.py b/intelliaide/vendor/docx/oxml/text/pagebreak.py new file mode 100644 index 0000000..45a6f51 --- /dev/null +++ b/intelliaide/vendor/docx/oxml/text/pagebreak.py @@ -0,0 +1,278 @@ +"""Custom element class for rendered page-break (CT_LastRenderedPageBreak).""" + +from __future__ import annotations + +import copy +from typing import TYPE_CHECKING + +from docx.oxml.xmlchemy import BaseOxmlElement +from docx.shared import lazyproperty + +if TYPE_CHECKING: + from docx.oxml.text.hyperlink import CT_Hyperlink + from docx.oxml.text.paragraph import CT_P + + +class CT_LastRenderedPageBreak(BaseOxmlElement): + """`` element, indicating page break inserted by renderer. + + A rendered page-break is one inserted by the renderer when it runs out of room on a + page. It is an empty element (no attrs or children) and is a child of CT_R, peer to + CT_Text. + + NOTE: this complex-type name does not exist in the schema, where + `w:lastRenderedPageBreak` maps to `CT_Empty`. This name was added to give it + distinguished behavior. CT_Empty is used for many elements. + """ + + @property + def following_fragment_p(self) -> CT_P: + """A "loose" `CT_P` containing only the paragraph content before this break. + + Raises `ValueError` if this `w:lastRenderedPageBreak` is not the first rendered + page-break in its paragraph. + + The returned `CT_P` is a "clone" (deepcopy) of the `w:p` ancestor of this + page-break with this `w:lastRenderedPageBreak` element and all content preceding + it removed. + + NOTE: this `w:p` can itself contain one or more `w:renderedPageBreak` elements + (when the paragraph contained more than one). While this is rare, the caller + should treat this paragraph the same as other paragraphs and split it if + necessary in a folloing step or recursion. + """ + if not self == self._first_lrpb_in_p(self._enclosing_p): + raise ValueError("only defined on first rendered page-break in paragraph") + + # -- splitting approach is different when break is inside a hyperlink -- + return ( + self._following_frag_in_hlink if self._is_in_hyperlink else self._following_frag_in_run + ) + + @property + def follows_all_content(self) -> bool: + """True when this page-break element is the last "content" in the paragraph. + + This is very uncommon case and may only occur in contrived or cases where the + XML is edited by hand, but it is not precluded by the spec. + """ + # -- a page-break inside a hyperlink never meets these criteria (for our + # -- purposes at least) because it is considered "atomic" and always associated + # -- with the page it starts on. + if self._is_in_hyperlink: + return False + + return bool( + # -- XPath will match zero-or-one w:lastRenderedPageBreak element -- + self._enclosing_p.xpath( + # -- in first run of paragraph -- + f"(./w:r)[last()]" + # -- all page-breaks -- + f"/w:lastRenderedPageBreak" + # -- that are not preceded by any content-bearing elements -- + f"[not(following-sibling::*[{self._run_inner_content_xpath}])]" + ) + ) + + @property + def precedes_all_content(self) -> bool: + """True when a `w:lastRenderedPageBreak` precedes all paragraph content. + + This is a common case; it occurs whenever the page breaks on an even paragraph + boundary. + """ + # -- a page-break inside a hyperlink never meets these criteria because there + # -- is always part of the hyperlink text before the page-break. + if self._is_in_hyperlink: + return False + + return bool( + # -- XPath will match zero-or-one w:lastRenderedPageBreak element -- + self._enclosing_p.xpath( + # -- in first run of paragraph -- + f"./w:r[1]" + # -- all page-breaks -- + f"/w:lastRenderedPageBreak" + # -- that are not preceded by any content-bearing elements -- + f"[not(preceding-sibling::*[{self._run_inner_content_xpath}])]" + ) + ) + + @property + def preceding_fragment_p(self) -> CT_P: + """A "loose" `CT_P` containing only the paragraph content before this break. + + Raises `ValueError` if this `w:lastRenderedPageBreak` is not the first rendered + paragraph in its paragraph. + + The returned `CT_P` is a "clone" (deepcopy) of the `w:p` ancestor of this + page-break with this `w:lastRenderedPageBreak` element and all its following + siblings removed. + """ + if not self == self._first_lrpb_in_p(self._enclosing_p): + raise ValueError("only defined on first rendered page-break in paragraph") + + # -- splitting approach is different when break is inside a hyperlink -- + return ( + self._preceding_frag_in_hlink if self._is_in_hyperlink else self._preceding_frag_in_run + ) + + def _enclosing_hyperlink(self, lrpb: CT_LastRenderedPageBreak) -> CT_Hyperlink: + """The `w:hyperlink` grandparent of this `w:lastRenderedPageBreak`. + + Raises `IndexError` when this page-break has a `w:p` grandparent, so only call + when `._is_in_hyperlink` is True. + """ + return lrpb.xpath("./parent::w:r/parent::w:hyperlink")[0] + + @property + def _enclosing_p(self) -> CT_P: + """The `w:p` element parent or grandparent of this `w:lastRenderedPageBreak`.""" + return self.xpath("./ancestor::w:p[1]")[0] + + def _first_lrpb_in_p(self, p: CT_P) -> CT_LastRenderedPageBreak: + """The first `w:lastRenderedPageBreak` element in `p`. + + Raises `ValueError` if there are no rendered page-breaks in `p`. + """ + lrpbs = p.xpath("./w:r/w:lastRenderedPageBreak | ./w:hyperlink/w:r/w:lastRenderedPageBreak") + if not lrpbs: + raise ValueError("no rendered page-breaks in paragraph element") + return lrpbs[0] + + @lazyproperty + def _following_frag_in_hlink(self) -> CT_P: + """Following CT_P fragment when break occurs within a hyperlink. + + Note this is a *partial-function* and raises when `lrpb` is not inside a + hyperlink. + """ + if not self._is_in_hyperlink: + raise ValueError("only defined on a rendered page-break in a hyperlink") + + # -- work on a clone `w:p` so our mutations don't persist -- + p = copy.deepcopy(self._enclosing_p) + + # -- get this `w:lastRenderedPageBreak` in the cloned `w:p` (not self) -- + lrpb = self._first_lrpb_in_p(p) + + # -- locate `w:hyperlink` in which this `w:lastRenderedPageBreak` is found -- + hyperlink = lrpb._enclosing_hyperlink(lrpb) + + # -- delete all w:p inner-content preceding the hyperlink -- + for e in hyperlink.xpath("./preceding-sibling::*[not(self::w:pPr)]"): + p.remove(e) + + # -- remove the whole hyperlink, it belongs to the preceding-fragment-p -- + hyperlink.getparent().remove(hyperlink) + + # -- that's it, return the remaining fragment of `w:p` clone -- + return p + + @lazyproperty + def _following_frag_in_run(self) -> CT_P: + """following CT_P fragment when break does not occur in a hyperlink. + + Note this is a *partial-function* and raises when `lrpb` is inside a hyperlink. + """ + if self._is_in_hyperlink: + raise ValueError("only defined on a rendered page-break not in a hyperlink") + + # -- work on a clone `w:p` so our mutations don't persist -- + p = copy.deepcopy(self._enclosing_p) + + # -- get this `w:lastRenderedPageBreak` in the cloned `w:p` (not self) -- + lrpb = self._first_lrpb_in_p(p) + + # -- locate `w:r` in which this `w:lastRenderedPageBreak` is found -- + enclosing_r = lrpb.xpath("./parent::w:r")[0] + + # -- delete all w:p inner-content preceding that run (but not w:pPr) -- + for e in enclosing_r.xpath("./preceding-sibling::*[not(self::w:pPr)]"): + p.remove(e) + + # -- then remove all run inner-content preceding this lrpb in its run (but not + # -- the `w:rPr`) and also remove the page-break itself + for e in lrpb.xpath("./preceding-sibling::*[not(self::w:rPr)]"): + enclosing_r.remove(e) + enclosing_r.remove(lrpb) + + return p + + @lazyproperty + def _is_in_hyperlink(self) -> bool: + """True when this page-break is embedded in a hyperlink run.""" + return bool(self.xpath("./parent::w:r/parent::w:hyperlink")) + + @lazyproperty + def _preceding_frag_in_hlink(self) -> CT_P: + """Preceding CT_P fragment when break occurs within a hyperlink. + + Note this is a *partial-function* and raises when `lrpb` is not inside a + hyperlink. + """ + if not self._is_in_hyperlink: + raise ValueError("only defined on a rendered page-break in a hyperlink") + + # -- work on a clone `w:p` so our mutations don't persist -- + p = copy.deepcopy(self._enclosing_p) + + # -- get this `w:lastRenderedPageBreak` in the cloned `w:p` (not self) -- + lrpb = self._first_lrpb_in_p(p) + + # -- locate `w:hyperlink` in which this `w:lastRenderedPageBreak` is found -- + hyperlink = lrpb._enclosing_hyperlink(lrpb) + + # -- delete all w:p inner-content following the hyperlink -- + for e in hyperlink.xpath("./following-sibling::*"): + p.remove(e) + + # -- remove this page-break from inside the hyperlink -- + lrpb.getparent().remove(lrpb) + + # -- that's it, the entire hyperlink goes into the preceding fragment so + # -- the hyperlink is not "split". + return p + + @lazyproperty + def _preceding_frag_in_run(self) -> CT_P: + """Preceding CT_P fragment when break does not occur in a hyperlink. + + Note this is a *partial-function* and raises when `lrpb` is inside a hyperlink. + """ + if self._is_in_hyperlink: + raise ValueError("only defined on a rendered page-break not in a hyperlink") + + # -- work on a clone `w:p` so our mutations don't persist -- + p = copy.deepcopy(self._enclosing_p) + + # -- get this `w:lastRenderedPageBreak` in the cloned `w:p` (not self) -- + lrpb = self._first_lrpb_in_p(p) + + # -- locate `w:r` in which this `w:lastRenderedPageBreak` is found -- + enclosing_r = lrpb.xpath("./parent::w:r")[0] + + # -- delete all `w:p` inner-content following that run -- + for e in enclosing_r.xpath("./following-sibling::*"): + p.remove(e) + + # -- then delete all `w:r` inner-content following this lrpb in its run and + # -- also remove the page-break itself + for e in lrpb.xpath("./following-sibling::*"): + enclosing_r.remove(e) + enclosing_r.remove(lrpb) + + return p + + @lazyproperty + def _run_inner_content_xpath(self) -> str: + """XPath fragment matching any run inner-content elements.""" + return ( + "self::w:br" + " | self::w:cr" + " | self::w:drawing" + " | self::w:noBreakHyphen" + " | self::w:ptab" + " | self::w:t" + " | self::w:tab" + ) diff --git a/intelliaide/vendor/docx/oxml/text/paragraph.py b/intelliaide/vendor/docx/oxml/text/paragraph.py new file mode 100644 index 0000000..63e96f3 --- /dev/null +++ b/intelliaide/vendor/docx/oxml/text/paragraph.py @@ -0,0 +1,106 @@ +# pyright: reportPrivateUsage=false + +"""Custom element classes related to paragraphs (CT_P).""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable, List, cast + +from docx.oxml.parser import OxmlElement +from docx.oxml.xmlchemy import BaseOxmlElement, ZeroOrMore, ZeroOrOne + +if TYPE_CHECKING: + from docx.enum.text import WD_PARAGRAPH_ALIGNMENT + from docx.oxml.section import CT_SectPr + from docx.oxml.text.hyperlink import CT_Hyperlink + from docx.oxml.text.pagebreak import CT_LastRenderedPageBreak + from docx.oxml.text.parfmt import CT_PPr + from docx.oxml.text.run import CT_R + + +class CT_P(BaseOxmlElement): + """`` element, containing the properties and text for a paragraph.""" + + add_r: Callable[[], CT_R] + get_or_add_pPr: Callable[[], CT_PPr] + hyperlink_lst: List[CT_Hyperlink] + r_lst: List[CT_R] + + pPr: CT_PPr | None = ZeroOrOne("w:pPr") # pyright: ignore[reportAssignmentType] + hyperlink = ZeroOrMore("w:hyperlink") + r = ZeroOrMore("w:r") + + def add_p_before(self) -> CT_P: + """Return a new `` element inserted directly prior to this one.""" + new_p = cast(CT_P, OxmlElement("w:p")) + self.addprevious(new_p) + return new_p + + @property + def alignment(self) -> WD_PARAGRAPH_ALIGNMENT | None: + """The value of the `` grandchild element or |None| if not present.""" + pPr = self.pPr + if pPr is None: + return None + return pPr.jc_val + + @alignment.setter + def alignment(self, value: WD_PARAGRAPH_ALIGNMENT): + pPr = self.get_or_add_pPr() + pPr.jc_val = value + + def clear_content(self): + """Remove all child elements, except the `` element if present.""" + for child in self.xpath("./*[not(self::w:pPr)]"): + self.remove(child) + + @property + def inner_content_elements(self) -> List[CT_R | CT_Hyperlink]: + """Run and hyperlink children of the `w:p` element, in document order.""" + return self.xpath("./w:r | ./w:hyperlink") + + @property + def lastRenderedPageBreaks(self) -> List[CT_LastRenderedPageBreak]: + """All `w:lastRenderedPageBreak` descendants of this paragraph. + + Rendered page-breaks commonly occur in a run but can also occur in a run inside + a hyperlink. This returns both. + """ + return self.xpath( + "./w:r/w:lastRenderedPageBreak | ./w:hyperlink/w:r/w:lastRenderedPageBreak" + ) + + def set_sectPr(self, sectPr: CT_SectPr): + """Unconditionally replace or add `sectPr` as grandchild in correct sequence.""" + pPr = self.get_or_add_pPr() + pPr._remove_sectPr() + pPr._insert_sectPr(sectPr) + + @property + def style(self) -> str | None: + """String contained in `w:val` attribute of `./w:pPr/w:pStyle` grandchild. + + |None| if not present. + """ + pPr = self.pPr + if pPr is None: + return None + return pPr.style + + @style.setter + def style(self, style: str | None): + pPr = self.get_or_add_pPr() + pPr.style = style + + @property + def text(self): # pyright: ignore[reportIncompatibleMethodOverride] + """The textual content of this paragraph. + + Inner-content child elements like `w:r` and `w:hyperlink` are translated to + their text equivalent. + """ + return "".join(e.text for e in self.xpath("w:r | w:hyperlink")) + + def _insert_pPr(self, pPr: CT_PPr) -> CT_PPr: + self.insert(0, pPr) + return pPr diff --git a/intelliaide/vendor/docx/oxml/text/parfmt.py b/intelliaide/vendor/docx/oxml/text/parfmt.py new file mode 100644 index 0000000..2133686 --- /dev/null +++ b/intelliaide/vendor/docx/oxml/text/parfmt.py @@ -0,0 +1,392 @@ +"""Custom element classes related to paragraph properties (CT_PPr).""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable + +from docx.enum.text import ( + WD_ALIGN_PARAGRAPH, + WD_LINE_SPACING, + WD_TAB_ALIGNMENT, + WD_TAB_LEADER, +) +from docx.oxml.shared import CT_DecimalNumber +from docx.oxml.simpletypes import ST_SignedTwipsMeasure, ST_TwipsMeasure +from docx.oxml.xmlchemy import ( + BaseOxmlElement, + OneOrMore, + OptionalAttribute, + RequiredAttribute, + ZeroOrOne, +) +from docx.shared import Length + +if TYPE_CHECKING: + from docx.oxml.section import CT_SectPr + from docx.oxml.shared import CT_String + + +class CT_Ind(BaseOxmlElement): + """```` element, specifying paragraph indentation.""" + + left: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:left", ST_SignedTwipsMeasure + ) + right: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:right", ST_SignedTwipsMeasure + ) + firstLine: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:firstLine", ST_TwipsMeasure + ) + hanging: Length | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:hanging", ST_TwipsMeasure + ) + + +class CT_Jc(BaseOxmlElement): + """```` element, specifying paragraph justification.""" + + val: WD_ALIGN_PARAGRAPH = RequiredAttribute( # pyright: ignore[reportAssignmentType] + "w:val", WD_ALIGN_PARAGRAPH + ) + + +class CT_PPr(BaseOxmlElement): + """```` element, containing the properties for a paragraph.""" + + get_or_add_ind: Callable[[], CT_Ind] + get_or_add_pStyle: Callable[[], CT_String] + get_or_add_sectPr: Callable[[], CT_SectPr] + _insert_sectPr: Callable[[CT_SectPr], None] + _remove_pStyle: Callable[[], None] + _remove_sectPr: Callable[[], None] + + _tag_seq = ( + "w:pStyle", + "w:keepNext", + "w:keepLines", + "w:pageBreakBefore", + "w:framePr", + "w:widowControl", + "w:numPr", + "w:suppressLineNumbers", + "w:pBdr", + "w:shd", + "w:tabs", + "w:suppressAutoHyphens", + "w:kinsoku", + "w:wordWrap", + "w:overflowPunct", + "w:topLinePunct", + "w:autoSpaceDE", + "w:autoSpaceDN", + "w:bidi", + "w:adjustRightInd", + "w:snapToGrid", + "w:spacing", + "w:ind", + "w:contextualSpacing", + "w:mirrorIndents", + "w:suppressOverlap", + "w:jc", + "w:textDirection", + "w:textAlignment", + "w:textboxTightWrap", + "w:outlineLvl", + "w:divId", + "w:cnfStyle", + "w:rPr", + "w:sectPr", + "w:pPrChange", + ) + pStyle: CT_String | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:pStyle", successors=_tag_seq[1:] + ) + keepNext = ZeroOrOne("w:keepNext", successors=_tag_seq[2:]) + keepLines = ZeroOrOne("w:keepLines", successors=_tag_seq[3:]) + pageBreakBefore = ZeroOrOne("w:pageBreakBefore", successors=_tag_seq[4:]) + widowControl = ZeroOrOne("w:widowControl", successors=_tag_seq[6:]) + numPr = ZeroOrOne("w:numPr", successors=_tag_seq[7:]) + tabs = ZeroOrOne("w:tabs", successors=_tag_seq[11:]) + spacing = ZeroOrOne("w:spacing", successors=_tag_seq[22:]) + ind: CT_Ind | None = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:ind", successors=_tag_seq[23:] + ) + jc = ZeroOrOne("w:jc", successors=_tag_seq[27:]) + outlineLvl: CT_DecimalNumber = ZeroOrOne( # pyright: ignore[reportAssignmentType] + "w:outlineLvl", successors=_tag_seq[31:] + ) + sectPr = ZeroOrOne("w:sectPr", successors=_tag_seq[35:]) + del _tag_seq + + @property + def first_line_indent(self) -> Length | None: + """A |Length| value calculated from the values of `w:ind/@w:firstLine` and + `w:ind/@w:hanging`. + + Returns |None| if the `w:ind` child is not present. + """ + ind = self.ind + if ind is None: + return None + hanging = ind.hanging + if hanging is not None: + return Length(-hanging) + firstLine = ind.firstLine + if firstLine is None: + return None + return firstLine + + @first_line_indent.setter + def first_line_indent(self, value: Length | None): + if self.ind is None and value is None: + return + ind = self.get_or_add_ind() + ind.firstLine = ind.hanging = None + if value is None: + return + elif value < 0: + ind.hanging = -value + else: + ind.firstLine = value + + @property + def ind_left(self) -> Length | None: + """The value of `w:ind/@w:left` or |None| if not present.""" + ind = self.ind + if ind is None: + return None + return ind.left + + @ind_left.setter + def ind_left(self, value: Length | None): + if value is None and self.ind is None: + return + ind = self.get_or_add_ind() + ind.left = value + + @property + def ind_right(self) -> Length | None: + """The value of `w:ind/@w:right` or |None| if not present.""" + ind = self.ind + if ind is None: + return None + return ind.right + + @ind_right.setter + def ind_right(self, value: Length | None): + if value is None and self.ind is None: + return + ind = self.get_or_add_ind() + ind.right = value + + @property + def jc_val(self) -> WD_ALIGN_PARAGRAPH | None: + """Value of the `` child element or |None| if not present.""" + return self.jc.val if self.jc is not None else None + + @jc_val.setter + def jc_val(self, value): + if value is None: + self._remove_jc() + return + self.get_or_add_jc().val = value + + @property + def keepLines_val(self): + """The value of `keepLines/@val` or |None| if not present.""" + keepLines = self.keepLines + if keepLines is None: + return None + return keepLines.val + + @keepLines_val.setter + def keepLines_val(self, value): + if value is None: + self._remove_keepLines() + else: + self.get_or_add_keepLines().val = value + + @property + def keepNext_val(self): + """The value of `keepNext/@val` or |None| if not present.""" + keepNext = self.keepNext + if keepNext is None: + return None + return keepNext.val + + @keepNext_val.setter + def keepNext_val(self, value): + if value is None: + self._remove_keepNext() + else: + self.get_or_add_keepNext().val = value + + @property + def pageBreakBefore_val(self): + """The value of `pageBreakBefore/@val` or |None| if not present.""" + pageBreakBefore = self.pageBreakBefore + if pageBreakBefore is None: + return None + return pageBreakBefore.val + + @pageBreakBefore_val.setter + def pageBreakBefore_val(self, value): + if value is None: + self._remove_pageBreakBefore() + else: + self.get_or_add_pageBreakBefore().val = value + + @property + def spacing_after(self): + """The value of `w:spacing/@w:after` or |None| if not present.""" + spacing = self.spacing + if spacing is None: + return None + return spacing.after + + @spacing_after.setter + def spacing_after(self, value): + if value is None and self.spacing is None: + return + self.get_or_add_spacing().after = value + + @property + def spacing_before(self): + """The value of `w:spacing/@w:before` or |None| if not present.""" + spacing = self.spacing + if spacing is None: + return None + return spacing.before + + @spacing_before.setter + def spacing_before(self, value): + if value is None and self.spacing is None: + return + self.get_or_add_spacing().before = value + + @property + def spacing_line(self): + """The value of `w:spacing/@w:line` or |None| if not present.""" + spacing = self.spacing + if spacing is None: + return None + return spacing.line + + @spacing_line.setter + def spacing_line(self, value): + if value is None and self.spacing is None: + return + self.get_or_add_spacing().line = value + + @property + def spacing_lineRule(self): + """The value of `w:spacing/@w:lineRule` as a member of the :ref:`WdLineSpacing` + enumeration. + + Only the `MULTIPLE`, `EXACTLY`, and `AT_LEAST` members are used. It is the + responsibility of the client to calculate the use of `SINGLE`, `DOUBLE`, and + `MULTIPLE` based on the value of `w:spacing/@w:line` if that behavior is + desired. + """ + spacing = self.spacing + if spacing is None: + return None + lineRule = spacing.lineRule + if lineRule is None and spacing.line is not None: + return WD_LINE_SPACING.MULTIPLE + return lineRule + + @spacing_lineRule.setter + def spacing_lineRule(self, value): + if value is None and self.spacing is None: + return + self.get_or_add_spacing().lineRule = value + + @property + def style(self) -> str | None: + """String contained in `./w:pStyle/@val`, or None if child is not present.""" + pStyle = self.pStyle + if pStyle is None: + return None + return pStyle.val + + @style.setter + def style(self, style: str | None): + """Set `./w:pStyle/@val` `style`, adding a new element if necessary. + + If `style` is |None|, remove `./w:pStyle` when present. + """ + if style is None: + self._remove_pStyle() + return + pStyle = self.get_or_add_pStyle() + pStyle.val = style + + @property + def widowControl_val(self): + """The value of `widowControl/@val` or |None| if not present.""" + widowControl = self.widowControl + if widowControl is None: + return None + return widowControl.val + + @widowControl_val.setter + def widowControl_val(self, value): + if value is None: + self._remove_widowControl() + else: + self.get_or_add_widowControl().val = value + + +class CT_Spacing(BaseOxmlElement): + """```` element, specifying paragraph spacing attributes such as space + before and line spacing.""" + + after = OptionalAttribute("w:after", ST_TwipsMeasure) + before = OptionalAttribute("w:before", ST_TwipsMeasure) + line = OptionalAttribute("w:line", ST_SignedTwipsMeasure) + lineRule = OptionalAttribute("w:lineRule", WD_LINE_SPACING) + + +class CT_TabStop(BaseOxmlElement): + """`` element, representing an individual tab stop. + + Overloaded to use for a tab-character in a run, which also uses the w:tab tag but + only needs a __str__ method. + """ + + val: WD_TAB_ALIGNMENT = RequiredAttribute( # pyright: ignore[reportAssignmentType] + "w:val", WD_TAB_ALIGNMENT + ) + leader: WD_TAB_LEADER | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:leader", WD_TAB_LEADER, default=WD_TAB_LEADER.SPACES + ) + pos: Length = RequiredAttribute( # pyright: ignore[reportAssignmentType] + "w:pos", ST_SignedTwipsMeasure + ) + + def __str__(self) -> str: + """Text equivalent of a `w:tab` element appearing in a run. + + Allows text of run inner-content to be accessed consistently across all text + inner-content. + """ + return "\t" + + +class CT_TabStops(BaseOxmlElement): + """```` element, container for a sorted sequence of tab stops.""" + + tab = OneOrMore("w:tab", successors=()) + + def insert_tab_in_order(self, pos, align, leader): + """Insert a newly created `w:tab` child element in `pos` order.""" + new_tab = self._new_tab() + new_tab.pos, new_tab.val, new_tab.leader = pos, align, leader + for tab in self.tab_lst: + if new_tab.pos < tab.pos: + tab.addprevious(new_tab) + return new_tab + self.append(new_tab) + return new_tab diff --git a/intelliaide/vendor/docx/oxml/text/run.py b/intelliaide/vendor/docx/oxml/text/run.py new file mode 100644 index 0000000..7496aa6 --- /dev/null +++ b/intelliaide/vendor/docx/oxml/text/run.py @@ -0,0 +1,307 @@ +"""Custom element classes related to text runs (CT_R).""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable, Iterator, List, cast + +from docx.oxml.drawing import CT_Drawing +from docx.oxml.ns import qn +from docx.oxml.parser import OxmlElement +from docx.oxml.simpletypes import ST_BrClear, ST_BrType +from docx.oxml.text.font import CT_RPr +from docx.oxml.xmlchemy import BaseOxmlElement, OptionalAttribute, ZeroOrMore, ZeroOrOne +from docx.shared import TextAccumulator + +if TYPE_CHECKING: + from docx.oxml.shape import CT_Anchor, CT_Inline + from docx.oxml.text.pagebreak import CT_LastRenderedPageBreak + from docx.oxml.text.parfmt import CT_TabStop + +# ------------------------------------------------------------------------------------ +# Run-level elements + + +class CT_R(BaseOxmlElement): + """`` element, containing the properties and text for a run.""" + + add_br: Callable[[], CT_Br] + add_tab: Callable[[], CT_TabStop] + get_or_add_rPr: Callable[[], CT_RPr] + _add_drawing: Callable[[], CT_Drawing] + _add_t: Callable[..., CT_Text] + + rPr: CT_RPr | None = ZeroOrOne("w:rPr") # pyright: ignore[reportAssignmentType] + br = ZeroOrMore("w:br") + cr = ZeroOrMore("w:cr") + drawing = ZeroOrMore("w:drawing") + t = ZeroOrMore("w:t") + tab = ZeroOrMore("w:tab") + + def add_t(self, text: str) -> CT_Text: + """Return a newly added `` element containing `text`.""" + t = self._add_t(text=text) + if len(text.strip()) < len(text): + t.set(qn("xml:space"), "preserve") + return t + + def add_drawing(self, inline_or_anchor: CT_Inline | CT_Anchor) -> CT_Drawing: + """Return newly appended `CT_Drawing` (`w:drawing`) child element. + + The `w:drawing` element has `inline_or_anchor` as its child. + """ + drawing = self._add_drawing() + drawing.append(inline_or_anchor) + return drawing + + def clear_content(self) -> None: + """Remove all child elements except a `w:rPr` element if present.""" + # -- remove all run inner-content except a `w:rPr` when present. -- + for e in self.xpath("./*[not(self::w:rPr)]"): + self.remove(e) + + @property + def inner_content_items(self) -> List[str | CT_Drawing | CT_LastRenderedPageBreak]: + """Text of run, possibly punctuated by `w:lastRenderedPageBreak` elements.""" + from docx.oxml.text.pagebreak import CT_LastRenderedPageBreak + + accum = TextAccumulator() + + def iter_items() -> Iterator[str | CT_Drawing | CT_LastRenderedPageBreak]: + for e in self.xpath( + "w:br" + " | w:cr" + " | w:drawing" + " | w:lastRenderedPageBreak" + " | w:noBreakHyphen" + " | w:ptab" + " | w:t" + " | w:tab" + ): + if isinstance(e, (CT_Drawing, CT_LastRenderedPageBreak)): + yield from accum.pop() + yield e + else: + accum.push(str(e)) + + # -- don't forget the "tail" string -- + yield from accum.pop() + + return list(iter_items()) + + def insert_comment_range_end_and_reference_below(self, comment_id: int) -> None: + """Insert a `w:commentRangeEnd` and `w:commentReference` element after this run. + + The `w:commentRangeEnd` element is the immediate sibling of this `w:r` and is followed by + a `w:r` containing the `w:commentReference` element. + """ + self.addnext(self._new_comment_reference_run(comment_id)) + self.addnext(OxmlElement("w:commentRangeEnd", attrs={qn("w:id"): str(comment_id)})) + + def insert_comment_range_start_above(self, comment_id: int) -> None: + """Insert a `w:commentRangeStart` element with `comment_id` before this run.""" + self.addprevious(OxmlElement("w:commentRangeStart", attrs={qn("w:id"): str(comment_id)})) + + @property + def lastRenderedPageBreaks(self) -> List[CT_LastRenderedPageBreak]: + """All `w:lastRenderedPageBreaks` descendants of this run.""" + return self.xpath("./w:lastRenderedPageBreak") + + @property + def style(self) -> str | None: + """String contained in `w:val` attribute of `w:rStyle` grandchild. + + |None| if that element is not present. + """ + rPr = self.rPr + if rPr is None: + return None + return rPr.style + + @style.setter + def style(self, style: str | None): + """Set character style of this `w:r` element to `style`. + + If `style` is None, remove the style element. + """ + rPr = self.get_or_add_rPr() + rPr.style = style + + @property + def text(self) -> str: + """The textual content of this run. + + Inner-content child elements like `w:tab` are translated to their text + equivalent. + """ + return "".join( + str(e) for e in self.xpath("w:br | w:cr | w:noBreakHyphen | w:ptab | w:t | w:tab") + ) + + @text.setter + def text(self, text: str): # pyright: ignore[reportIncompatibleMethodOverride] + self.clear_content() + _RunContentAppender.append_to_run_from_text(self, text) + + def _insert_rPr(self, rPr: CT_RPr) -> CT_RPr: + self.insert(0, rPr) + return rPr + + def _new_comment_reference_run(self, comment_id: int) -> CT_R: + """Return a new `w:r` element with `w:commentReference` referencing `comment_id`. + + Should look like this: + + + + + + + """ + r = cast(CT_R, OxmlElement("w:r")) + rPr = r.get_or_add_rPr() + rPr.style = "CommentReference" + r.append(OxmlElement("w:commentReference", attrs={qn("w:id"): str(comment_id)})) + return r + + +# ------------------------------------------------------------------------------------ +# Run inner-content elements + + +class CT_Br(BaseOxmlElement): + """`` element, indicating a line, page, or column break in a run.""" + + type: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType] + "w:type", ST_BrType, default="textWrapping" + ) + clear: str | None = OptionalAttribute("w:clear", ST_BrClear) # pyright: ignore + + def __str__(self) -> str: + """Text equivalent of this element. Actual value depends on break type. + + A line break is translated as "\n". Column and page breaks produce the empty + string (""). + + This allows the text of run inner-content to be accessed in a consistent way + for all run inner-context text elements. + """ + return "\n" if self.type == "textWrapping" else "" + + +class CT_Cr(BaseOxmlElement): + """`` element, representing a carriage-return (0x0D) character within a run. + + In Word, this represents a "soft carriage-return" in the sense that it does not end + the paragraph the way pressing Enter (aka. Return) on the keyboard does. Here the + text equivalent is considered to be newline ("\n") since in plain-text that's the + closest Python equivalent. + + NOTE: this complex-type name does not exist in the schema, where `w:tab` maps to + `CT_Empty`. This name was added to give it distinguished behavior. CT_Empty is used + for many elements. + """ + + def __str__(self) -> str: + """Text equivalent of this element, a single newline ("\n").""" + return "\n" + + +class CT_NoBreakHyphen(BaseOxmlElement): + """`` element, a hyphen ineligible for a line-wrap position. + + This maps to a plain-text dash ("-"). + + NOTE: this complex-type name does not exist in the schema, where `w:noBreakHyphen` + maps to `CT_Empty`. This name was added to give it behavior distinguished from the + many other elements represented in the schema by CT_Empty. + """ + + def __str__(self) -> str: + """Text equivalent of this element, a single dash character ("-").""" + return "-" + + +class CT_PTab(BaseOxmlElement): + """`` element, representing an absolute-position tab character within a run. + + This character advances the rendering position to the specified position regardless + of any tab-stops, perhaps for layout of a table-of-contents (TOC) or similar. + """ + + def __str__(self) -> str: + """Text equivalent of this element, a single tab ("\t") character. + + This allows the text of run inner-content to be accessed in a consistent way + for all run inner-context text elements. + """ + return "\t" + + +# -- CT_Tab functionality is provided by CT_TabStop which also uses `w:tab` tag. That +# -- element class provides the __str__() method for this empty element, unconditionally +# -- returning "\t". + + +class CT_Text(BaseOxmlElement): + """`` element, containing a sequence of characters within a run.""" + + def __str__(self) -> str: + """Text contained in this element, the empty string if it has no content. + + This property allows this run inner-content element to be queried for its text + the same way as other run-content elements are. In particular, this never + returns None, as etree._Element does when there is no content. + """ + return self.text or "" + + +# ------------------------------------------------------------------------------------ +# Utility + + +class _RunContentAppender: + """Translates a Python string into run content elements appended in a `w:r` element. + + Contiguous sequences of regular characters are appended in a single `` element. + Each tab character ('\t') causes a `` element to be appended. Likewise a + newline or carriage return character ('\n', '\r') causes a `` element to be + appended. + """ + + def __init__(self, r: CT_R): + self._r = r + self._bfr: List[str] = [] + + @classmethod + def append_to_run_from_text(cls, r: CT_R, text: str): + """Append inner-content elements for `text` to `r` element.""" + appender = cls(r) + appender.add_text(text) + + def add_text(self, text: str): + """Append inner-content elements for `text` to the `w:r` element.""" + for char in text: + self.add_char(char) + self.flush() + + def add_char(self, char: str): + """Process next character of input through finite state maching (FSM). + + There are two possible states, buffer pending and not pending, but those are + hidden behind the `.flush()` method which must be called at the end of text to + ensure any pending `` element is written. + """ + if char == "\t": + self.flush() + self._r.add_tab() + elif char in "\r\n": + self.flush() + self._r.add_br() + else: + self._bfr.append(char) + + def flush(self): + text = "".join(self._bfr) + if text: + self._r.add_t(text) + self._bfr.clear() diff --git a/intelliaide/vendor/docx/oxml/xmlchemy.py b/intelliaide/vendor/docx/oxml/xmlchemy.py new file mode 100644 index 0000000..e2c54b3 --- /dev/null +++ b/intelliaide/vendor/docx/oxml/xmlchemy.py @@ -0,0 +1,696 @@ +# pyright: reportImportCycles=false + +"""Enabling declarative definition of lxml custom element classes.""" + +from __future__ import annotations + +import re +from typing import TYPE_CHECKING, Any, Callable, Sequence, Type, TypeVar + +from lxml import etree +from lxml.etree import ElementBase, _Element # pyright: ignore[reportPrivateUsage] + +from docx.oxml.exceptions import InvalidXmlError +from docx.oxml.ns import NamespacePrefixedTag, nsmap, qn +from docx.shared import lazyproperty + +if TYPE_CHECKING: + from docx.enum.base import BaseXmlEnum + from docx.oxml.simpletypes import BaseSimpleType + + +def serialize_for_reading(element: ElementBase): + """Serialize `element` to human-readable XML suitable for tests. + + No XML declaration. + """ + xml = etree.tostring(element, encoding="unicode", pretty_print=True) + return XmlString(xml) + + +class XmlString(str): + """Provides string comparison override suitable for serialized XML that is useful + for tests.""" + + # ' text' + # | | || | + # +----------+------------------------------------------++-----------+ + # front attrs | text + # close + + _xml_elm_line_patt = re.compile(r"( *)([^<]*)?$") + + def __eq__(self, other: object) -> bool: + if not isinstance(other, str): + return False + lines = self.splitlines() + lines_other = other.splitlines() + if len(lines) != len(lines_other): + return False + for line, line_other in zip(lines, lines_other): + if not self._eq_elm_strs(line, line_other): + return False + return True + + def __ne__(self, other: object) -> bool: + return not self.__eq__(other) + + def _attr_seq(self, attrs: str) -> list[str]: + """Return a sequence of attribute strings parsed from `attrs`. + + Each attribute string is stripped of whitespace on both ends. + """ + attrs = attrs.strip() + attr_lst = attrs.split() + return sorted(attr_lst) + + def _eq_elm_strs(self, line: str, line_2: str): + """Return True if the element in `line_2` is XML equivalent to the element in + `line`.""" + front, attrs, close, text = self._parse_line(line) + front_2, attrs_2, close_2, text_2 = self._parse_line(line_2) + if front != front_2: + return False + if self._attr_seq(attrs) != self._attr_seq(attrs_2): + return False + if close != close_2: + return False + return text == text_2 + + @classmethod + def _parse_line(cls, line: str) -> tuple[str, str, str, str]: + """(front, attrs, close, text) 4-tuple result of parsing XML element `line`.""" + match = cls._xml_elm_line_patt.match(line) + if match is None: + return "", "", "", "" + front, attrs, close, text = [match.group(n) for n in range(1, 5)] + return front, attrs, close, text + + +_T = TypeVar("_T") + + +class MetaOxmlElement(type): + """Metaclass for BaseOxmlElement.""" + + def __init__(cls, clsname: str, bases: tuple[type, ...], namespace: dict[str, Any]): + dispatchable = ( + OneAndOnlyOne, + OneOrMore, + OptionalAttribute, + RequiredAttribute, + ZeroOrMore, + ZeroOrOne, + ZeroOrOneChoice, + ) + for key, value in namespace.items(): + if isinstance(value, dispatchable): + value.populate_class_members(cls, key) + + +class BaseAttribute: + """Base class for OptionalAttribute and RequiredAttribute. + + Provides common methods. + """ + + def __init__(self, attr_name: str, simple_type: Type[BaseXmlEnum] | Type[BaseSimpleType]): + super(BaseAttribute, self).__init__() + self._attr_name = attr_name + self._simple_type = simple_type + + def populate_class_members(self, element_cls: MetaOxmlElement, prop_name: str) -> None: + """Add the appropriate methods to `element_cls`.""" + self._element_cls = element_cls + self._prop_name = prop_name + + self._add_attr_property() + + def _add_attr_property(self): + """Add a read/write `.{prop_name}` property to the element class. + + The property returns the interpreted value of this attribute on access and + changes the attribute value to its ST_* counterpart on assignment. + """ + property_ = property(self._getter, self._setter, None) + # -- assign unconditionally to overwrite element name definition -- + setattr(self._element_cls, self._prop_name, property_) + + @property + def _clark_name(self): + if ":" in self._attr_name: + return qn(self._attr_name) + return self._attr_name + + @property + def _getter(self) -> Callable[[BaseOxmlElement], Any | None]: ... + + @property + def _setter( + self, + ) -> Callable[[BaseOxmlElement, Any | None], None]: ... + + +class OptionalAttribute(BaseAttribute): + """Defines an optional attribute on a custom element class. + + An optional attribute returns a default value when not present for reading. When + assigned |None|, the attribute is removed, but still returns the default value when + one is specified. + """ + + def __init__( + self, + attr_name: str, + simple_type: Type[BaseXmlEnum] | Type[BaseSimpleType], + default: BaseXmlEnum | BaseSimpleType | str | bool | None = None, + ): + super(OptionalAttribute, self).__init__(attr_name, simple_type) + self._default = default + + @property + def _docstring(self): + """String to use as `__doc__` attribute of attribute property.""" + return ( + f"{self._simple_type.__name__} type-converted value of" + f" ``{self._attr_name}`` attribute, or |None| (or specified default" + f" value) if not present. Assigning the default value causes the" + f" attribute to be removed from the element." + ) + + @property + def _getter( + self, + ) -> Callable[[BaseOxmlElement], Any | None]: + """Function suitable for `__get__()` method on attribute property descriptor.""" + + def get_attr_value( + obj: BaseOxmlElement, + ) -> Any | None: + attr_str_value = obj.get(self._clark_name) + if attr_str_value is None: + return self._default + return self._simple_type.from_xml(attr_str_value) + + get_attr_value.__doc__ = self._docstring + return get_attr_value + + @property + def _setter(self) -> Callable[[BaseOxmlElement, Any], None]: + """Function suitable for `__set__()` method on attribute property descriptor.""" + + def set_attr_value(obj: BaseOxmlElement, value: Any | None): + if value is None or value == self._default: + if self._clark_name in obj.attrib: + del obj.attrib[self._clark_name] + return + str_value = self._simple_type.to_xml(value) + if str_value is None: + if self._clark_name in obj.attrib: + del obj.attrib[self._clark_name] + return + obj.set(self._clark_name, str_value) + + return set_attr_value + + +class RequiredAttribute(BaseAttribute): + """Defines a required attribute on a custom element class. + + A required attribute is assumed to be present for reading, so does not have a + default value; its actual value is always used. If missing on read, an + |InvalidXmlError| is raised. It also does not remove the attribute if |None| is + assigned. Assigning |None| raises |TypeError| or |ValueError|, depending on the + simple type of the attribute. + """ + + @property + def _docstring(self): + """Return the string to use as the ``__doc__`` attribute of the property for + this attribute.""" + return "%s type-converted value of ``%s`` attribute." % ( + self._simple_type.__name__, + self._attr_name, + ) + + @property + def _getter(self) -> Callable[[BaseOxmlElement], Any]: + """function object suitable for "get" side of attr property descriptor.""" + + def get_attr_value(obj: BaseOxmlElement) -> Any | None: + attr_str_value = obj.get(self._clark_name) + if attr_str_value is None: + raise InvalidXmlError( + "required '%s' attribute not present on element %s" % (self._attr_name, obj.tag) + ) + return self._simple_type.from_xml(attr_str_value) + + get_attr_value.__doc__ = self._docstring + return get_attr_value + + @property + def _setter(self) -> Callable[[BaseOxmlElement, Any], None]: + """function object suitable for "set" side of attribute property descriptor.""" + + def set_attr_value(obj: BaseOxmlElement, value: Any): + str_value = self._simple_type.to_xml(value) + if str_value is None: + raise ValueError(f"cannot assign {value} to this required attribute") + obj.set(self._clark_name, str_value) + + return set_attr_value + + +class _BaseChildElement: + """Base class for the child-element classes. + + The child-element sub-classes correspond to varying cardinalities, such as ZeroOrOne + and ZeroOrMore. + """ + + def __init__(self, nsptagname: str, successors: tuple[str, ...] = ()): + super(_BaseChildElement, self).__init__() + self._nsptagname = nsptagname + self._successors = successors + + def populate_class_members(self, element_cls: MetaOxmlElement, prop_name: str) -> None: + """Baseline behavior for adding the appropriate methods to `element_cls`.""" + self._element_cls = element_cls + self._prop_name = prop_name + + def _add_adder(self): + """Add an ``_add_x()`` method to the element class for this child element.""" + + def _add_child(obj: BaseOxmlElement, **attrs: Any): + new_method = getattr(obj, self._new_method_name) + child = new_method() + for key, value in attrs.items(): + setattr(child, key, value) + insert_method = getattr(obj, self._insert_method_name) + insert_method(child) + return child + + _add_child.__doc__ = ( + "Add a new ``<%s>`` child element unconditionally, inserted in t" + "he correct sequence." % self._nsptagname + ) + self._add_to_class(self._add_method_name, _add_child) + + def _add_creator(self): + """Add a ``_new_{prop_name}()`` method to the element class that creates a new, + empty element of the correct type, having no attributes.""" + creator = self._creator + creator.__doc__ = ( + 'Return a "loose", newly created ``<%s>`` element having no attri' + "butes, text, or children." % self._nsptagname + ) + self._add_to_class(self._new_method_name, creator) + + def _add_getter(self): + """Add a read-only ``{prop_name}`` property to the element class for this child + element.""" + property_ = property(self._getter, None, None) + # -- assign unconditionally to overwrite element name definition -- + setattr(self._element_cls, self._prop_name, property_) + + def _add_inserter(self): + """Add an ``_insert_x()`` method to the element class for this child element.""" + + def _insert_child(obj: BaseOxmlElement, child: BaseOxmlElement): + obj.insert_element_before(child, *self._successors) + return child + + _insert_child.__doc__ = ( + "Return the passed ``<%s>`` element after inserting it as a chil" + "d in the correct sequence." % self._nsptagname + ) + self._add_to_class(self._insert_method_name, _insert_child) + + def _add_list_getter(self): + """Add a read-only ``{prop_name}_lst`` property to the element class to retrieve + a list of child elements matching this type.""" + prop_name = "%s_lst" % self._prop_name + property_ = property(self._list_getter, None, None) + setattr(self._element_cls, prop_name, property_) + + @lazyproperty + def _add_method_name(self): + return "_add_%s" % self._prop_name + + def _add_public_adder(self): + """Add a public ``add_x()`` method to the parent element class.""" + + def add_child(obj: BaseOxmlElement): + private_add_method = getattr(obj, self._add_method_name) + child = private_add_method() + return child + + add_child.__doc__ = ( + "Add a new ``<%s>`` child element unconditionally, inserted in t" + "he correct sequence." % self._nsptagname + ) + self._add_to_class(self._public_add_method_name, add_child) + + def _add_to_class(self, name: str, method: Callable[..., Any]): + """Add `method` to the target class as `name`, unless `name` is already defined + on the class.""" + if hasattr(self._element_cls, name): + return + setattr(self._element_cls, name, method) + + @property + def _creator(self) -> Callable[[BaseOxmlElement], BaseOxmlElement]: + """Callable that creates an empty element of the right type, with no attrs.""" + from docx.oxml.parser import OxmlElement + + def new_child_element(obj: BaseOxmlElement): + return OxmlElement(self._nsptagname) + + return new_child_element + + @property + def _getter(self): + """Return a function object suitable for the "get" side of the property + descriptor. + + This default getter returns the child element with matching tag name or |None| + if not present. + """ + + def get_child_element(obj: BaseOxmlElement): + return obj.find(qn(self._nsptagname)) + + get_child_element.__doc__ = ( + "``<%s>`` child element or |None| if not present." % self._nsptagname + ) + return get_child_element + + @lazyproperty + def _insert_method_name(self): + return "_insert_%s" % self._prop_name + + @property + def _list_getter(self): + """Return a function object suitable for the "get" side of a list property + descriptor.""" + + def get_child_element_list(obj: BaseOxmlElement): + return obj.findall(qn(self._nsptagname)) + + get_child_element_list.__doc__ = ( + "A list containing each of the ``<%s>`` child elements, in the o" + "rder they appear." % self._nsptagname + ) + return get_child_element_list + + @lazyproperty + def _public_add_method_name(self): + """add_childElement() is public API for a repeating element, allowing new + elements to be added to the sequence. + + May be overridden to provide a friendlier API to clients having domain + appropriate parameter names for required attributes. + """ + return "add_%s" % self._prop_name + + @lazyproperty + def _remove_method_name(self): + return "_remove_%s" % self._prop_name + + @lazyproperty + def _new_method_name(self): + return "_new_%s" % self._prop_name + + +class Choice(_BaseChildElement): + """Defines a child element belonging to a group, only one of which may appear as a child.""" + + @property + def nsptagname(self): + return self._nsptagname + + def populate_class_members( # pyright: ignore[reportIncompatibleMethodOverride] + self, + element_cls: MetaOxmlElement, + group_prop_name: str, + successors: tuple[str, ...], + ) -> None: + """Add the appropriate methods to `element_cls`.""" + self._element_cls = element_cls + self._group_prop_name = group_prop_name + self._successors = successors + + self._add_getter() + self._add_creator() + self._add_inserter() + self._add_adder() + self._add_get_or_change_to_method() + + def _add_get_or_change_to_method(self): + """Add a ``get_or_change_to_x()`` method to the element class for this child + element.""" + + def get_or_change_to_child(obj: BaseOxmlElement): + child = getattr(obj, self._prop_name) + if child is not None: + return child + remove_group_method = getattr(obj, self._remove_group_method_name) + remove_group_method() + add_method = getattr(obj, self._add_method_name) + child = add_method() + return child + + get_or_change_to_child.__doc__ = ( + "Return the ``<%s>`` child, replacing any other group element if found." + ) % self._nsptagname + self._add_to_class(self._get_or_change_to_method_name, get_or_change_to_child) + + @property + def _prop_name(self): + """Property name computed from tag name, e.g. a:schemeClr -> schemeClr.""" + start = self._nsptagname.index(":") + 1 if ":" in self._nsptagname else 0 + return self._nsptagname[start:] + + @lazyproperty + def _get_or_change_to_method_name(self): + return "get_or_change_to_%s" % self._prop_name + + @lazyproperty + def _remove_group_method_name(self): + return "_remove_%s" % self._group_prop_name + + +class OneAndOnlyOne(_BaseChildElement): + """Defines a required child element for MetaOxmlElement.""" + + def __init__(self, nsptagname: str): + super(OneAndOnlyOne, self).__init__(nsptagname, ()) + + def populate_class_members(self, element_cls: MetaOxmlElement, prop_name: str) -> None: + """Add the appropriate methods to `element_cls`.""" + super(OneAndOnlyOne, self).populate_class_members(element_cls, prop_name) + self._add_getter() + + @property + def _getter(self): + """Return a function object suitable for the "get" side of the property + descriptor.""" + + def get_child_element(obj: BaseOxmlElement): + child = obj.find(qn(self._nsptagname)) + if child is None: + raise InvalidXmlError( + "required ``<%s>`` child element not present" % self._nsptagname + ) + return child + + get_child_element.__doc__ = "Required ``<%s>`` child element." % self._nsptagname + return get_child_element + + +class OneOrMore(_BaseChildElement): + """Defines a repeating child element for MetaOxmlElement that must appear at least + once.""" + + def populate_class_members(self, element_cls: MetaOxmlElement, prop_name: str) -> None: + """Add the appropriate methods to `element_cls`.""" + super(OneOrMore, self).populate_class_members(element_cls, prop_name) + self._add_list_getter() + self._add_creator() + self._add_inserter() + self._add_adder() + self._add_public_adder() + delattr(element_cls, prop_name) + + +class ZeroOrMore(_BaseChildElement): + """Defines an optional repeating child element for MetaOxmlElement.""" + + def populate_class_members(self, element_cls: MetaOxmlElement, prop_name: str) -> None: + """Add the appropriate methods to `element_cls`.""" + super(ZeroOrMore, self).populate_class_members(element_cls, prop_name) + self._add_list_getter() + self._add_creator() + self._add_inserter() + self._add_adder() + self._add_public_adder() + delattr(element_cls, prop_name) + + +class ZeroOrOne(_BaseChildElement): + """Defines an optional child element for MetaOxmlElement.""" + + def populate_class_members(self, element_cls: MetaOxmlElement, prop_name: str) -> None: + """Add the appropriate methods to `element_cls`.""" + super(ZeroOrOne, self).populate_class_members(element_cls, prop_name) + self._add_getter() + self._add_creator() + self._add_inserter() + self._add_adder() + self._add_get_or_adder() + self._add_remover() + + def _add_get_or_adder(self): + """Add a ``get_or_add_x()`` method to the element class for this child + element.""" + + def get_or_add_child(obj: BaseOxmlElement): + child = getattr(obj, self._prop_name) + if child is None: + add_method = getattr(obj, self._add_method_name) + child = add_method() + return child + + get_or_add_child.__doc__ = ( + "Return the ``<%s>`` child element, newly added if not present." + ) % self._nsptagname + self._add_to_class(self._get_or_add_method_name, get_or_add_child) + + def _add_remover(self): + """Add a ``_remove_x()`` method to the element class for this child element.""" + + def _remove_child(obj: BaseOxmlElement): + obj.remove_all(self._nsptagname) + + _remove_child.__doc__ = ("Remove all ``<%s>`` child elements.") % self._nsptagname + self._add_to_class(self._remove_method_name, _remove_child) + + @lazyproperty + def _get_or_add_method_name(self): + return "get_or_add_%s" % self._prop_name + + +class ZeroOrOneChoice(_BaseChildElement): + """Correspondes to an ``EG_*`` element group where at most one of its members may + appear as a child.""" + + def __init__(self, choices: Sequence[Choice], successors: tuple[str, ...] = ()): + self._choices = choices + self._successors = successors + + def populate_class_members(self, element_cls: MetaOxmlElement, prop_name: str) -> None: + """Add the appropriate methods to `element_cls`.""" + super(ZeroOrOneChoice, self).populate_class_members(element_cls, prop_name) + self._add_choice_getter() + for choice in self._choices: + choice.populate_class_members(element_cls, self._prop_name, self._successors) + self._add_group_remover() + + def _add_choice_getter(self): + """Add a read-only ``{prop_name}`` property to the element class that returns + the present member of this group, or |None| if none are present.""" + property_ = property(self._choice_getter, None, None) + # assign unconditionally to overwrite element name definition + setattr(self._element_cls, self._prop_name, property_) + + def _add_group_remover(self): + """Add a ``_remove_eg_x()`` method to the element class for this choice + group.""" + + def _remove_choice_group(obj: BaseOxmlElement): + for tagname in self._member_nsptagnames: + obj.remove_all(tagname) + + _remove_choice_group.__doc__ = "Remove the current choice group child element if present." + self._add_to_class(self._remove_choice_group_method_name, _remove_choice_group) + + @property + def _choice_getter(self): + """Return a function object suitable for the "get" side of the property + descriptor.""" + + def get_group_member_element(obj: BaseOxmlElement): + return obj.first_child_found_in(*self._member_nsptagnames) + + get_group_member_element.__doc__ = ( + "Return the child element belonging to this element group, or " + "|None| if no member child is present." + ) + return get_group_member_element + + @lazyproperty + def _member_nsptagnames(self): + """Sequence of namespace-prefixed tagnames, one for each of the member elements + of this choice group.""" + return [choice.nsptagname for choice in self._choices] + + @lazyproperty + def _remove_choice_group_method_name(self): + return "_remove_%s" % self._prop_name + + +# -- lxml typing isn't quite right here, just ignore this error on _Element -- +class BaseOxmlElement(etree.ElementBase, metaclass=MetaOxmlElement): + """Effective base class for all custom element classes. + + Adds standardized behavior to all classes in one place. + """ + + def __repr__(self): + return "<%s '<%s>' at 0x%0x>" % ( + self.__class__.__name__, + self._nsptag, + id(self), + ) + + def first_child_found_in(self, *tagnames: str) -> _Element | None: + """First child with tag in `tagnames`, or None if not found.""" + for tagname in tagnames: + child = self.find(qn(tagname)) + if child is not None: + return child + return None + + def insert_element_before(self, elm: ElementBase, *tagnames: str): + successor = self.first_child_found_in(*tagnames) + if successor is not None: + successor.addprevious(elm) + else: + self.append(elm) + return elm + + def remove_all(self, *tagnames: str) -> None: + """Remove child elements with tagname (e.g. "a:p") in `tagnames`.""" + for tagname in tagnames: + matching = self.findall(qn(tagname)) + for child in matching: + self.remove(child) + + @property + def xml(self) -> str: + """XML string for this element, suitable for testing purposes. + + Pretty printed for readability and without an XML declaration at the top. + """ + return serialize_for_reading(self) + + def xpath(self, xpath_str: str) -> Any: # pyright: ignore[reportIncompatibleMethodOverride] + """Override of `lxml` _Element.xpath() method. + + Provides standard Open XML namespace mapping (`nsmap`) in centralized location. + """ + return super().xpath(xpath_str, namespaces=nsmap) + + @property + def _nsptag(self) -> str: + return NamespacePrefixedTag.from_clark_name(self.tag) diff --git a/intelliaide/vendor/docx/package.py b/intelliaide/vendor/docx/package.py new file mode 100644 index 0000000..7ea47e6 --- /dev/null +++ b/intelliaide/vendor/docx/package.py @@ -0,0 +1,110 @@ +"""WordprocessingML Package class and related objects.""" + +from __future__ import annotations + +from typing import IO, cast + +from docx.image.image import Image +from docx.opc.constants import RELATIONSHIP_TYPE as RT +from docx.opc.package import OpcPackage +from docx.opc.packuri import PackURI +from docx.parts.image import ImagePart +from docx.shared import lazyproperty + + +class Package(OpcPackage): + """Customizations specific to a WordprocessingML package.""" + + def after_unmarshal(self): + """Called by loading code after all parts and relationships have been loaded. + + This method affords the opportunity for any required post-processing. + """ + self._gather_image_parts() + + def get_or_add_image_part(self, image_descriptor: str | IO[bytes]) -> ImagePart: + """Return |ImagePart| containing image specified by `image_descriptor`. + + The image-part is newly created if a matching one is not already present in the + collection. + """ + return self.image_parts.get_or_add_image_part(image_descriptor) + + @lazyproperty + def image_parts(self) -> ImageParts: + """|ImageParts| collection object for this package.""" + return ImageParts() + + def _gather_image_parts(self): + """Load the image part collection with all the image parts in package.""" + for rel in self.iter_rels(): + if rel.is_external: + continue + if rel.reltype != RT.IMAGE: + continue + if rel.target_part in self.image_parts: + continue + self.image_parts.append(cast("ImagePart", rel.target_part)) + + +class ImageParts: + """Collection of |ImagePart| objects corresponding to images in the package.""" + + def __init__(self): + self._image_parts: list[ImagePart] = [] + + def __contains__(self, item: object): + return self._image_parts.__contains__(item) + + def __iter__(self): + return self._image_parts.__iter__() + + def __len__(self): + return self._image_parts.__len__() + + def append(self, item: ImagePart): + self._image_parts.append(item) + + def get_or_add_image_part(self, image_descriptor: str | IO[bytes]) -> ImagePart: + """Return |ImagePart| object containing image identified by `image_descriptor`. + + The image-part is newly created if a matching one is not present in the + collection. + """ + image = Image.from_file(image_descriptor) + matching_image_part = self._get_by_sha1(image.sha1) + if matching_image_part is not None: + return matching_image_part + return self._add_image_part(image) + + def _add_image_part(self, image: Image): + """Return |ImagePart| instance newly created from `image` and appended to the collection.""" + partname = self._next_image_partname(image.ext) + image_part = ImagePart.from_image(image, partname) + self.append(image_part) + return image_part + + def _get_by_sha1(self, sha1: str) -> ImagePart | None: + """Return the image part in this collection having a SHA1 hash matching `sha1`, + or |None| if not found.""" + for image_part in self._image_parts: + if image_part.sha1 == sha1: + return image_part + return None + + def _next_image_partname(self, ext: str) -> PackURI: + """The next available image partname, starting from ``/word/media/image1.{ext}`` + where unused numbers are reused. + + The partname is unique by number, without regard to the extension. `ext` does + not include the leading period. + """ + + def image_partname(n: int) -> PackURI: + return PackURI("/word/media/image%d.%s" % (n, ext)) + + used_numbers = [image_part.partname.idx for image_part in self] + for n in range(1, len(self) + 1): + if n not in used_numbers: + return image_partname(n) + return image_partname(len(self) + 1) diff --git a/intelliaide/vendor/docx/parts/__init__.py b/intelliaide/vendor/docx/parts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/intelliaide/vendor/docx/parts/comments.py b/intelliaide/vendor/docx/parts/comments.py new file mode 100644 index 0000000..0e4cc74 --- /dev/null +++ b/intelliaide/vendor/docx/parts/comments.py @@ -0,0 +1,51 @@ +"""Contains comments added to the document.""" + +from __future__ import annotations + +import os +from typing import TYPE_CHECKING, cast + +from typing_extensions import Self + +from docx.comments import Comments +from docx.opc.constants import CONTENT_TYPE as CT +from docx.opc.packuri import PackURI +from docx.oxml.comments import CT_Comments +from docx.oxml.parser import parse_xml +from docx.package import Package +from docx.parts.story import StoryPart + +if TYPE_CHECKING: + from docx.oxml.comments import CT_Comments + from docx.package import Package + + +class CommentsPart(StoryPart): + """Container part for comments added to the document.""" + + def __init__( + self, partname: PackURI, content_type: str, element: CT_Comments, package: Package + ): + super().__init__(partname, content_type, element, package) + self._comments = element + + @property + def comments(self) -> Comments: + """A |Comments| proxy object for the `w:comments` root element of this part.""" + return Comments(self._comments, self) + + @classmethod + def default(cls, package: Package) -> Self: + """A newly created comments part, containing a default empty `w:comments` element.""" + partname = PackURI("/word/comments.xml") + content_type = CT.WML_COMMENTS + element = cast("CT_Comments", parse_xml(cls._default_comments_xml())) + return cls(partname, content_type, element, package) + + @classmethod + def _default_comments_xml(cls) -> bytes: + """A byte-string containing XML for a default comments part.""" + path = os.path.join(os.path.split(__file__)[0], "..", "templates", "default-comments.xml") + with open(path, "rb") as f: + xml_bytes = f.read() + return xml_bytes diff --git a/intelliaide/vendor/docx/parts/document.py b/intelliaide/vendor/docx/parts/document.py new file mode 100644 index 0000000..4960264 --- /dev/null +++ b/intelliaide/vendor/docx/parts/document.py @@ -0,0 +1,169 @@ +"""|DocumentPart| and closely related objects.""" + +from __future__ import annotations + +from typing import IO, TYPE_CHECKING, cast + +from docx.document import Document +from docx.opc.constants import RELATIONSHIP_TYPE as RT +from docx.parts.comments import CommentsPart +from docx.parts.hdrftr import FooterPart, HeaderPart +from docx.parts.numbering import NumberingPart +from docx.parts.settings import SettingsPart +from docx.parts.story import StoryPart +from docx.parts.styles import StylesPart +from docx.shape import InlineShapes +from docx.shared import lazyproperty + +if TYPE_CHECKING: + from docx.comments import Comments + from docx.enum.style import WD_STYLE_TYPE + from docx.opc.coreprops import CoreProperties + from docx.settings import Settings + from docx.styles.style import BaseStyle + + +class DocumentPart(StoryPart): + """Main document part of a WordprocessingML (WML) package, aka a .docx file. + + Acts as broker to other parts such as image, core properties, and style parts. It + also acts as a convenient delegate when a mid-document object needs a service + involving a remote ancestor. The `Parented.part` property inherited by many content + objects provides access to this part object for that purpose. + """ + + def add_footer_part(self): + """Return (footer_part, rId) pair for newly-created footer part.""" + footer_part = FooterPart.new(self.package) + rId = self.relate_to(footer_part, RT.FOOTER) + return footer_part, rId + + def add_header_part(self): + """Return (header_part, rId) pair for newly-created header part.""" + header_part = HeaderPart.new(self.package) + rId = self.relate_to(header_part, RT.HEADER) + return header_part, rId + + @property + def comments(self) -> Comments: + """|Comments| object providing access to the comments added to this document.""" + return self._comments_part.comments + + @property + def core_properties(self) -> CoreProperties: + """A |CoreProperties| object providing read/write access to the core properties + of this document.""" + return self.package.core_properties + + @property + def document(self): + """A |Document| object providing access to the content of this document.""" + return Document(self._element, self) + + def drop_header_part(self, rId: str) -> None: + """Remove related header part identified by `rId`.""" + self.drop_rel(rId) + + def footer_part(self, rId: str): + """Return |FooterPart| related by `rId`.""" + return self.related_parts[rId] + + def get_style(self, style_id: str | None, style_type: WD_STYLE_TYPE) -> BaseStyle: + """Return the style in this document matching `style_id`. + + Returns the default style for `style_type` if `style_id` is |None| or does not + match a defined style of `style_type`. + """ + return self.styles.get_by_id(style_id, style_type) + + def get_style_id(self, style_or_name, style_type): + """Return the style_id (|str|) of the style of `style_type` matching + `style_or_name`. + + Returns |None| if the style resolves to the default style for `style_type` or if + `style_or_name` is itself |None|. Raises if `style_or_name` is a style of the + wrong type or names a style not present in the document. + """ + return self.styles.get_style_id(style_or_name, style_type) + + def header_part(self, rId: str): + """Return |HeaderPart| related by `rId`.""" + return self.related_parts[rId] + + @lazyproperty + def inline_shapes(self): + """The |InlineShapes| instance containing the inline shapes in the document.""" + return InlineShapes(self._element.body, self) + + @lazyproperty + def numbering_part(self) -> NumberingPart: + """A |NumberingPart| object providing access to the numbering definitions for this document. + + Creates an empty numbering part if one is not present. + """ + try: + return cast(NumberingPart, self.part_related_by(RT.NUMBERING)) + except KeyError: + numbering_part = NumberingPart.new() + self.relate_to(numbering_part, RT.NUMBERING) + return numbering_part + + def save(self, path_or_stream: str | IO[bytes]): + """Save this document to `path_or_stream`, which can be either a path to a + filesystem location (a string) or a file-like object.""" + self.package.save(path_or_stream) + + @property + def settings(self) -> Settings: + """A |Settings| object providing access to the settings in the settings part of + this document.""" + return self._settings_part.settings + + @property + def styles(self): + """A |Styles| object providing access to the styles in the styles part of this + document.""" + return self._styles_part.styles + + @property + def _comments_part(self) -> CommentsPart: + """A |CommentsPart| object providing access to the comments added to this document. + + Creates a default comments part if one is not present. + """ + try: + return cast(CommentsPart, self.part_related_by(RT.COMMENTS)) + except KeyError: + assert self.package is not None + comments_part = CommentsPart.default(self.package) + self.relate_to(comments_part, RT.COMMENTS) + return comments_part + + @property + def _settings_part(self) -> SettingsPart: + """A |SettingsPart| object providing access to the document-level settings for + this document. + + Creates a default settings part if one is not present. + """ + try: + return cast(SettingsPart, self.part_related_by(RT.SETTINGS)) + except KeyError: + settings_part = SettingsPart.default(self.package) + self.relate_to(settings_part, RT.SETTINGS) + return settings_part + + @property + def _styles_part(self) -> StylesPart: + """Instance of |StylesPart| for this document. + + Creates an empty styles part if one is not present. + """ + try: + return cast(StylesPart, self.part_related_by(RT.STYLES)) + except KeyError: + package = self.package + assert package is not None + styles_part = StylesPart.default(package) + self.relate_to(styles_part, RT.STYLES) + return styles_part diff --git a/intelliaide/vendor/docx/parts/hdrftr.py b/intelliaide/vendor/docx/parts/hdrftr.py new file mode 100644 index 0000000..3511380 --- /dev/null +++ b/intelliaide/vendor/docx/parts/hdrftr.py @@ -0,0 +1,53 @@ +"""Header and footer part objects.""" + +from __future__ import annotations + +import os +from typing import TYPE_CHECKING + +from docx.opc.constants import CONTENT_TYPE as CT +from docx.oxml.parser import parse_xml +from docx.parts.story import StoryPart + +if TYPE_CHECKING: + from docx.package import Package + + +class FooterPart(StoryPart): + """Definition of a section footer.""" + + @classmethod + def new(cls, package: Package): + """Return newly created footer part.""" + partname = package.next_partname("/word/footer%d.xml") + content_type = CT.WML_FOOTER + element = parse_xml(cls._default_footer_xml()) + return cls(partname, content_type, element, package) + + @classmethod + def _default_footer_xml(cls): + """Return bytes containing XML for a default footer part.""" + path = os.path.join(os.path.split(__file__)[0], "..", "templates", "default-footer.xml") + with open(path, "rb") as f: + xml_bytes = f.read() + return xml_bytes + + +class HeaderPart(StoryPart): + """Definition of a section header.""" + + @classmethod + def new(cls, package: Package): + """Return newly created header part.""" + partname = package.next_partname("/word/header%d.xml") + content_type = CT.WML_HEADER + element = parse_xml(cls._default_header_xml()) + return cls(partname, content_type, element, package) + + @classmethod + def _default_header_xml(cls): + """Return bytes containing XML for a default header part.""" + path = os.path.join(os.path.split(__file__)[0], "..", "templates", "default-header.xml") + with open(path, "rb") as f: + xml_bytes = f.read() + return xml_bytes diff --git a/intelliaide/vendor/docx/parts/image.py b/intelliaide/vendor/docx/parts/image.py new file mode 100644 index 0000000..5aec070 --- /dev/null +++ b/intelliaide/vendor/docx/parts/image.py @@ -0,0 +1,80 @@ +"""The proxy class for an image part, and related objects.""" + +from __future__ import annotations + +import hashlib +from typing import TYPE_CHECKING + +from docx.image.image import Image +from docx.opc.part import Part +from docx.shared import Emu, Inches + +if TYPE_CHECKING: + from docx.opc.package import OpcPackage + from docx.opc.packuri import PackURI + + +class ImagePart(Part): + """An image part. + + Corresponds to the target part of a relationship with type RELATIONSHIP_TYPE.IMAGE. + """ + + def __init__( + self, partname: PackURI, content_type: str, blob: bytes, image: Image | None = None + ): + super(ImagePart, self).__init__(partname, content_type, blob) + self._image = image + + @property + def default_cx(self): + """Native width of this image, calculated from its width in pixels and + horizontal dots per inch (dpi).""" + px_width = self.image.px_width + horz_dpi = self.image.horz_dpi + width_in_inches = px_width / horz_dpi + return Inches(width_in_inches) + + @property + def default_cy(self): + """Native height of this image, calculated from its height in pixels and + vertical dots per inch (dpi).""" + px_height = self.image.px_height + horz_dpi = self.image.horz_dpi + height_in_emu = int(round(914400 * px_height / horz_dpi)) + return Emu(height_in_emu) + + @property + def filename(self): + """Filename from which this image part was originally created. + + A generic name, e.g. 'image.png', is substituted if no name is available, for + example when the image was loaded from an unnamed stream. In that case a default + extension is applied based on the detected MIME type of the image. + """ + if self._image is not None: + return self._image.filename + return "image.%s" % self.partname.ext + + @classmethod + def from_image(cls, image: Image, partname: PackURI): + """Return an |ImagePart| instance newly created from `image` and assigned + `partname`.""" + return ImagePart(partname, image.content_type, image.blob, image) + + @property + def image(self) -> Image: + if self._image is None: + self._image = Image.from_blob(self.blob) + return self._image + + @classmethod + def load(cls, partname: PackURI, content_type: str, blob: bytes, package: OpcPackage): + """Called by ``docx.opc.package.PartFactory`` to load an image part from a + package being opened by ``Document(...)`` call.""" + return cls(partname, content_type, blob) + + @property + def sha1(self): + """SHA1 hash digest of the blob of this image part.""" + return hashlib.sha1(self.blob).hexdigest() diff --git a/intelliaide/vendor/docx/parts/numbering.py b/intelliaide/vendor/docx/parts/numbering.py new file mode 100644 index 0000000..745c845 --- /dev/null +++ b/intelliaide/vendor/docx/parts/numbering.py @@ -0,0 +1,32 @@ +"""|NumberingPart| and closely related objects.""" + +from ..opc.part import XmlPart +from ..shared import lazyproperty + + +class NumberingPart(XmlPart): + """Proxy for the numbering.xml part containing numbering definitions for a document + or glossary.""" + + @classmethod + def new(cls) -> "NumberingPart": + """Newly created numbering part, containing only the root ```` element.""" + raise NotImplementedError + + @lazyproperty + def numbering_definitions(self): + """The |_NumberingDefinitions| instance containing the numbering definitions + ( element proxies) for this numbering part.""" + return _NumberingDefinitions(self._element) + + +class _NumberingDefinitions: + """Collection of |_NumberingDefinition| instances corresponding to the ```` + elements in a numbering part.""" + + def __init__(self, numbering_elm): + super(_NumberingDefinitions, self).__init__() + self._numbering = numbering_elm + + def __len__(self): + return len(self._numbering.num_lst) diff --git a/intelliaide/vendor/docx/parts/settings.py b/intelliaide/vendor/docx/parts/settings.py new file mode 100644 index 0000000..7fe371f --- /dev/null +++ b/intelliaide/vendor/docx/parts/settings.py @@ -0,0 +1,50 @@ +"""|SettingsPart| and closely related objects.""" + +from __future__ import annotations + +import os +from typing import TYPE_CHECKING, cast + +from docx.opc.constants import CONTENT_TYPE as CT +from docx.opc.packuri import PackURI +from docx.opc.part import XmlPart +from docx.oxml.parser import parse_xml +from docx.settings import Settings + +if TYPE_CHECKING: + from docx.oxml.settings import CT_Settings + from docx.package import Package + + +class SettingsPart(XmlPart): + """Document-level settings part of a WordprocessingML (WML) package.""" + + def __init__( + self, partname: PackURI, content_type: str, element: CT_Settings, package: Package + ): + super().__init__(partname, content_type, element, package) + self._settings = element + + @classmethod + def default(cls, package: Package): + """Return a newly created settings part, containing a default `w:settings` element tree.""" + partname = PackURI("/word/settings.xml") + content_type = CT.WML_SETTINGS + element = cast("CT_Settings", parse_xml(cls._default_settings_xml())) + return cls(partname, content_type, element, package) + + @property + def settings(self) -> Settings: + """A |Settings| proxy object for the `w:settings` element in this part. + + Contains the document-level settings for this document. + """ + return Settings(self._settings) + + @classmethod + def _default_settings_xml(cls): + """Return a bytestream containing XML for a default settings part.""" + path = os.path.join(os.path.split(__file__)[0], "..", "templates", "default-settings.xml") + with open(path, "rb") as f: + xml_bytes = f.read() + return xml_bytes diff --git a/intelliaide/vendor/docx/parts/story.py b/intelliaide/vendor/docx/parts/story.py new file mode 100644 index 0000000..7482c91 --- /dev/null +++ b/intelliaide/vendor/docx/parts/story.py @@ -0,0 +1,95 @@ +"""|StoryPart| and related objects.""" + +from __future__ import annotations + +from typing import IO, TYPE_CHECKING, Tuple, cast + +from docx.opc.constants import RELATIONSHIP_TYPE as RT +from docx.opc.part import XmlPart +from docx.oxml.shape import CT_Inline +from docx.shared import Length, lazyproperty + +if TYPE_CHECKING: + from docx.enum.style import WD_STYLE_TYPE + from docx.image.image import Image + from docx.parts.document import DocumentPart + from docx.styles.style import BaseStyle + + +class StoryPart(XmlPart): + """Base class for story parts. + + A story part is one that can contain textual content, such as the document-part and + header or footer parts. These all share content behaviors like `.paragraphs`, + `.add_paragraph()`, `.add_table()` etc. + """ + + def get_or_add_image(self, image_descriptor: str | IO[bytes]) -> Tuple[str, Image]: + """Return (rId, image) pair for image identified by `image_descriptor`. + + `rId` is the str key (often like "rId7") for the relationship between this story + part and the image part, reused if already present, newly created if not. + `image` is an |Image| instance providing access to the properties of the image, + such as dimensions and image type. + """ + package = self._package + assert package is not None + image_part = package.get_or_add_image_part(image_descriptor) + rId = self.relate_to(image_part, RT.IMAGE) + return rId, image_part.image + + def get_style(self, style_id: str | None, style_type: WD_STYLE_TYPE) -> BaseStyle: + """Return the style in this document matching `style_id`. + + Returns the default style for `style_type` if `style_id` is |None| or does not + match a defined style of `style_type`. + """ + return self._document_part.get_style(style_id, style_type) + + def get_style_id( + self, style_or_name: BaseStyle | str | None, style_type: WD_STYLE_TYPE + ) -> str | None: + """Return str style_id for `style_or_name` of `style_type`. + + Returns |None| if the style resolves to the default style for `style_type` or if + `style_or_name` is itself |None|. Raises if `style_or_name` is a style of the + wrong type or names a style not present in the document. + """ + return self._document_part.get_style_id(style_or_name, style_type) + + def new_pic_inline( + self, + image_descriptor: str | IO[bytes], + width: int | Length | None = None, + height: int | Length | None = None, + ) -> CT_Inline: + """Return a newly-created `w:inline` element. + + The element contains the image specified by `image_descriptor` and is scaled + based on the values of `width` and `height`. + """ + rId, image = self.get_or_add_image(image_descriptor) + cx, cy = image.scaled_dimensions(width, height) + shape_id, filename = self.next_id, image.filename + return CT_Inline.new_pic_inline(shape_id, rId, filename, cx, cy) + + @property + def next_id(self) -> int: + """Next available positive integer id value in this story XML document. + + The value is determined by incrementing the maximum existing id value. Gaps in + the existing id sequence are not filled. The id attribute value is unique in the + document, without regard to the element type it appears on. + """ + id_str_lst = self._element.xpath("//@id") + used_ids = [int(id_str) for id_str in id_str_lst if id_str.isdigit()] + if not used_ids: + return 1 + return max(used_ids) + 1 + + @lazyproperty + def _document_part(self) -> DocumentPart: + """|DocumentPart| object for this package.""" + package = self.package + assert package is not None + return cast("DocumentPart", package.main_document_part) diff --git a/intelliaide/vendor/docx/parts/styles.py b/intelliaide/vendor/docx/parts/styles.py new file mode 100644 index 0000000..6e065be --- /dev/null +++ b/intelliaide/vendor/docx/parts/styles.py @@ -0,0 +1,42 @@ +"""Provides StylesPart and related objects.""" + +from __future__ import annotations + +import os +from typing import TYPE_CHECKING + +from docx.opc.constants import CONTENT_TYPE as CT +from docx.opc.packuri import PackURI +from docx.opc.part import XmlPart +from docx.oxml.parser import parse_xml +from docx.styles.styles import Styles + +if TYPE_CHECKING: + from docx.opc.package import OpcPackage + + +class StylesPart(XmlPart): + """Proxy for the styles.xml part containing style definitions for a document or + glossary.""" + + @classmethod + def default(cls, package: OpcPackage) -> StylesPart: + """Return a newly created styles part, containing a default set of elements.""" + partname = PackURI("/word/styles.xml") + content_type = CT.WML_STYLES + element = parse_xml(cls._default_styles_xml()) + return cls(partname, content_type, element, package) + + @property + def styles(self): + """The |_Styles| instance containing the styles ( element proxies) for + this styles part.""" + return Styles(self.element) + + @classmethod + def _default_styles_xml(cls): + """Return a bytestream containing XML for a default styles part.""" + path = os.path.join(os.path.split(__file__)[0], "..", "templates", "default-styles.xml") + with open(path, "rb") as f: + xml_bytes = f.read() + return xml_bytes diff --git a/intelliaide/vendor/docx/py.typed b/intelliaide/vendor/docx/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/intelliaide/vendor/docx/section.py b/intelliaide/vendor/docx/section.py new file mode 100644 index 0000000..982a143 --- /dev/null +++ b/intelliaide/vendor/docx/section.py @@ -0,0 +1,479 @@ +"""The |Section| object and related proxy classes.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Iterator, List, Sequence, overload + +from docx.blkcntnr import BlockItemContainer +from docx.enum.section import WD_HEADER_FOOTER +from docx.oxml.text.paragraph import CT_P +from docx.parts.hdrftr import FooterPart, HeaderPart +from docx.shared import lazyproperty +from docx.table import Table +from docx.text.paragraph import Paragraph + +if TYPE_CHECKING: + from docx.enum.section import WD_ORIENTATION, WD_SECTION_START + from docx.oxml.document import CT_Document + from docx.oxml.section import CT_SectPr + from docx.parts.document import DocumentPart + from docx.parts.story import StoryPart + from docx.shared import Length + + +class Section: + """Document section, providing access to section and page setup settings. + + Also provides access to headers and footers. + """ + + def __init__(self, sectPr: CT_SectPr, document_part: DocumentPart): + super(Section, self).__init__() + self._sectPr = sectPr + self._document_part = document_part + + @property + def bottom_margin(self) -> Length | None: + """Read/write. Bottom margin for pages in this section, in EMU. + + `None` when no bottom margin has been specified. Assigning |None| removes any + bottom-margin setting. + """ + return self._sectPr.bottom_margin + + @bottom_margin.setter + def bottom_margin(self, value: int | Length | None): + self._sectPr.bottom_margin = value + + @property + def different_first_page_header_footer(self) -> bool: + """True if this section displays a distinct first-page header and footer. + + Read/write. The definition of the first-page header and footer are accessed + using :attr:`.first_page_header` and :attr:`.first_page_footer` respectively. + """ + return self._sectPr.titlePg_val + + @different_first_page_header_footer.setter + def different_first_page_header_footer(self, value: bool): + self._sectPr.titlePg_val = value + + @property + def even_page_footer(self) -> _Footer: + """|_Footer| object defining footer content for even pages. + + The content of this footer definition is ignored unless the document setting + :attr:`~.Settings.odd_and_even_pages_header_footer` is set True. + """ + return _Footer(self._sectPr, self._document_part, WD_HEADER_FOOTER.EVEN_PAGE) + + @property + def even_page_header(self) -> _Header: + """|_Header| object defining header content for even pages. + + The content of this header definition is ignored unless the document setting + :attr:`~.Settings.odd_and_even_pages_header_footer` is set True. + """ + return _Header(self._sectPr, self._document_part, WD_HEADER_FOOTER.EVEN_PAGE) + + @property + def first_page_footer(self) -> _Footer: + """|_Footer| object defining footer content for the first page of this section. + + The content of this footer definition is ignored unless the property + :attr:`.different_first_page_header_footer` is set True. + """ + return _Footer(self._sectPr, self._document_part, WD_HEADER_FOOTER.FIRST_PAGE) + + @property + def first_page_header(self) -> _Header: + """|_Header| object defining header content for the first page of this section. + + The content of this header definition is ignored unless the property + :attr:`.different_first_page_header_footer` is set True. + """ + return _Header(self._sectPr, self._document_part, WD_HEADER_FOOTER.FIRST_PAGE) + + @lazyproperty + def footer(self) -> _Footer: + """|_Footer| object representing default page footer for this section. + + The default footer is used for odd-numbered pages when separate odd/even footers + are enabled. It is used for both odd and even-numbered pages otherwise. + """ + return _Footer(self._sectPr, self._document_part, WD_HEADER_FOOTER.PRIMARY) + + @property + def footer_distance(self) -> Length | None: + """Distance from bottom edge of page to bottom edge of the footer. + + Read/write. |None| if no setting is present in the XML. + """ + return self._sectPr.footer + + @footer_distance.setter + def footer_distance(self, value: int | Length | None): + self._sectPr.footer = value + + @property + def gutter(self) -> Length | None: + """|Length| object representing page gutter size in English Metric Units. + + Read/write. The page gutter is extra spacing added to the `inner` margin to + ensure even margins after page binding. Generally only used in book-bound + documents with double-sided and facing pages. + + This setting applies to all pages in this section. + + """ + return self._sectPr.gutter + + @gutter.setter + def gutter(self, value: int | Length | None): + self._sectPr.gutter = value + + @lazyproperty + def header(self) -> _Header: + """|_Header| object representing default page header for this section. + + The default header is used for odd-numbered pages when separate odd/even headers + are enabled. It is used for both odd and even-numbered pages otherwise. + """ + return _Header(self._sectPr, self._document_part, WD_HEADER_FOOTER.PRIMARY) + + @property + def header_distance(self) -> Length | None: + """Distance from top edge of page to top edge of header. + + Read/write. |None| if no setting is present in the XML. Assigning |None| causes + default value to be used. + """ + return self._sectPr.header + + @header_distance.setter + def header_distance(self, value: int | Length | None): + self._sectPr.header = value + + def iter_inner_content(self) -> Iterator[Paragraph | Table]: + """Generate each Paragraph or Table object in this `section`. + + Items appear in document order. + """ + for element in self._sectPr.iter_inner_content(): + yield (Paragraph(element, self) if isinstance(element, CT_P) else Table(element, self)) + + @property + def left_margin(self) -> Length | None: + """|Length| object representing the left margin for all pages in this section in + English Metric Units.""" + return self._sectPr.left_margin + + @left_margin.setter + def left_margin(self, value: int | Length | None): + self._sectPr.left_margin = value + + @property + def orientation(self) -> WD_ORIENTATION: + """:ref:`WdOrientation` member specifying page orientation for this section. + + One of ``WD_ORIENT.PORTRAIT`` or ``WD_ORIENT.LANDSCAPE``. + """ + return self._sectPr.orientation + + @orientation.setter + def orientation(self, value: WD_ORIENTATION | None): + self._sectPr.orientation = value + + @property + def page_height(self) -> Length | None: + """Total page height used for this section. + + This value is inclusive of all edge spacing values such as margins. + + Page orientation is taken into account, so for example, its expected value + would be ``Inches(8.5)`` for letter-sized paper when orientation is landscape. + """ + return self._sectPr.page_height + + @page_height.setter + def page_height(self, value: Length | None): + self._sectPr.page_height = value + + @property + def page_width(self) -> Length | None: + """Total page width used for this section. + + This value is like "paper size" and includes all edge spacing values such as + margins. + + Page orientation is taken into account, so for example, its expected value + would be ``Inches(11)`` for letter-sized paper when orientation is landscape. + """ + return self._sectPr.page_width + + @page_width.setter + def page_width(self, value: Length | None): + self._sectPr.page_width = value + + @property + def part(self) -> StoryPart: + return self._document_part + + @property + def right_margin(self) -> Length | None: + """|Length| object representing the right margin for all pages in this section + in English Metric Units.""" + return self._sectPr.right_margin + + @right_margin.setter + def right_margin(self, value: Length | None): + self._sectPr.right_margin = value + + @property + def start_type(self) -> WD_SECTION_START: + """Type of page-break (if any) inserted at the start of this section. + + For exmple, ``WD_SECTION_START.ODD_PAGE`` if the section should begin on the + next odd page, possibly inserting two page-breaks instead of one. + """ + return self._sectPr.start_type + + @start_type.setter + def start_type(self, value: WD_SECTION_START | None): + self._sectPr.start_type = value + + @property + def top_margin(self) -> Length | None: + """|Length| object representing the top margin for all pages in this section in + English Metric Units.""" + return self._sectPr.top_margin + + @top_margin.setter + def top_margin(self, value: Length | None): + self._sectPr.top_margin = value + + +class Sections(Sequence[Section]): + """Sequence of |Section| objects corresponding to the sections in the document. + + Supports ``len()``, iteration, and indexed access. + """ + + def __init__(self, document_elm: CT_Document, document_part: DocumentPart): + super(Sections, self).__init__() + self._document_elm = document_elm + self._document_part = document_part + + @overload + def __getitem__(self, key: int) -> Section: ... + + @overload + def __getitem__(self, key: slice) -> List[Section]: ... + + def __getitem__(self, key: int | slice) -> Section | List[Section]: + if isinstance(key, slice): + return [ + Section(sectPr, self._document_part) + for sectPr in self._document_elm.sectPr_lst[key] + ] + return Section(self._document_elm.sectPr_lst[key], self._document_part) + + def __iter__(self) -> Iterator[Section]: + for sectPr in self._document_elm.sectPr_lst: + yield Section(sectPr, self._document_part) + + def __len__(self) -> int: + return len(self._document_elm.sectPr_lst) + + +class _BaseHeaderFooter(BlockItemContainer): + """Base class for header and footer classes.""" + + def __init__( + self, + sectPr: CT_SectPr, + document_part: DocumentPart, + header_footer_index: WD_HEADER_FOOTER, + ): + self._sectPr = sectPr + self._document_part = document_part + self._hdrftr_index = header_footer_index + + @property + def is_linked_to_previous(self) -> bool: + """``True`` if this header/footer uses the definition from the prior section. + + ``False`` if this header/footer has an explicit definition. + + Assigning ``True`` to this property removes the header/footer definition for + this section, causing it to "inherit" the corresponding definition of the prior + section. Assigning ``False`` causes a new, empty definition to be added for this + section, but only if no definition is already present. + """ + # ---absence of a header/footer part indicates "linked" behavior--- + return not self._has_definition + + @is_linked_to_previous.setter + def is_linked_to_previous(self, value: bool) -> None: + new_state = bool(value) + # ---do nothing when value is not being changed--- + if new_state == self.is_linked_to_previous: + return + if new_state is True: + self._drop_definition() + else: + self._add_definition() + + @property + def part(self) -> HeaderPart | FooterPart: + """The |HeaderPart| or |FooterPart| for this header/footer. + + This overrides `BlockItemContainer.part` and is required to support image + insertion and perhaps other content like hyperlinks. + """ + # ---should not appear in documentation; + # ---not an interface property, even though public + return self._get_or_add_definition() + + def _add_definition(self) -> HeaderPart | FooterPart: + """Return newly-added header/footer part.""" + raise NotImplementedError("must be implemented by each subclass") + + @property + def _definition(self) -> HeaderPart | FooterPart: + """|HeaderPart| or |FooterPart| object containing header/footer content.""" + raise NotImplementedError("must be implemented by each subclass") + + def _drop_definition(self) -> None: + """Remove header/footer part containing the definition of this header/footer.""" + raise NotImplementedError("must be implemented by each subclass") + + @property + def _element(self): + """`w:hdr` or `w:ftr` element, root of header/footer part.""" + return self._get_or_add_definition().element + + def _get_or_add_definition(self) -> HeaderPart | FooterPart: + """Return HeaderPart or FooterPart object for this section. + + If this header/footer inherits its content, the part for the prior header/footer + is returned; this process continue recursively until a definition is found. If + the definition cannot be inherited (because the header/footer belongs to the + first section), a new definition is added for that first section and then + returned. + """ + # ---note this method is called recursively to access inherited definitions--- + # ---case-1: definition is not inherited--- + if self._has_definition: + return self._definition + # ---case-2: definition is inherited and belongs to second-or-later section--- + prior_headerfooter = self._prior_headerfooter + if prior_headerfooter: + return prior_headerfooter._get_or_add_definition() + # ---case-3: definition is inherited, but belongs to first section--- + return self._add_definition() + + @property + def _has_definition(self) -> bool: + """True if this header/footer has a related part containing its definition.""" + raise NotImplementedError("must be implemented by each subclass") + + @property + def _prior_headerfooter(self) -> _Header | _Footer | None: + """|_Header| or |_Footer| proxy on prior sectPr element. + + Returns None if this is first section. + """ + raise NotImplementedError("must be implemented by each subclass") + + +class _Footer(_BaseHeaderFooter): + """Page footer, used for all three types (default, even-page, and first-page). + + Note that, like a document or table cell, a footer must contain a minimum of one + paragraph and a new or otherwise "empty" footer contains a single empty paragraph. + This first paragraph can be accessed as `footer.paragraphs[0]` for purposes of + adding content to it. Using :meth:`add_paragraph()` by itself to add content will + leave an empty paragraph above the newly added one. + """ + + def _add_definition(self) -> FooterPart: + """Return newly-added footer part.""" + footer_part, rId = self._document_part.add_footer_part() + self._sectPr.add_footerReference(self._hdrftr_index, rId) + return footer_part + + @property + def _definition(self): + """|FooterPart| object containing content of this footer.""" + footerReference = self._sectPr.get_footerReference(self._hdrftr_index) + # -- currently this is never called when `._has_definition` evaluates False -- + assert footerReference is not None + return self._document_part.footer_part(footerReference.rId) + + def _drop_definition(self): + """Remove footer definition (footer part) associated with this section.""" + rId = self._sectPr.remove_footerReference(self._hdrftr_index) + self._document_part.drop_rel(rId) + + @property + def _has_definition(self) -> bool: + """True if a footer is defined for this section.""" + footerReference = self._sectPr.get_footerReference(self._hdrftr_index) + return footerReference is not None + + @property + def _prior_headerfooter(self): + """|_Footer| proxy on prior sectPr element or None if this is first section.""" + preceding_sectPr = self._sectPr.preceding_sectPr + return ( + None + if preceding_sectPr is None + else _Footer(preceding_sectPr, self._document_part, self._hdrftr_index) + ) + + +class _Header(_BaseHeaderFooter): + """Page header, used for all three types (default, even-page, and first-page). + + Note that, like a document or table cell, a header must contain a minimum of one + paragraph and a new or otherwise "empty" header contains a single empty paragraph. + This first paragraph can be accessed as `header.paragraphs[0]` for purposes of + adding content to it. Using :meth:`add_paragraph()` by itself to add content will + leave an empty paragraph above the newly added one. + """ + + def _add_definition(self): + """Return newly-added header part.""" + header_part, rId = self._document_part.add_header_part() + self._sectPr.add_headerReference(self._hdrftr_index, rId) + return header_part + + @property + def _definition(self): + """|HeaderPart| object containing content of this header.""" + headerReference = self._sectPr.get_headerReference(self._hdrftr_index) + # -- currently this is never called when `._has_definition` evaluates False -- + assert headerReference is not None + return self._document_part.header_part(headerReference.rId) + + def _drop_definition(self): + """Remove header definition associated with this section.""" + rId = self._sectPr.remove_headerReference(self._hdrftr_index) + self._document_part.drop_header_part(rId) + + @property + def _has_definition(self) -> bool: + """True if a header is explicitly defined for this section.""" + headerReference = self._sectPr.get_headerReference(self._hdrftr_index) + return headerReference is not None + + @property + def _prior_headerfooter(self): + """|_Header| proxy on prior sectPr element or None if this is first section.""" + preceding_sectPr = self._sectPr.preceding_sectPr + return ( + None + if preceding_sectPr is None + else _Header(preceding_sectPr, self._document_part, self._hdrftr_index) + ) diff --git a/intelliaide/vendor/docx/settings.py b/intelliaide/vendor/docx/settings.py new file mode 100644 index 0000000..0a5aa2f --- /dev/null +++ b/intelliaide/vendor/docx/settings.py @@ -0,0 +1,35 @@ +"""Settings object, providing access to document-level settings.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, cast + +from docx.shared import ElementProxy + +if TYPE_CHECKING: + import docx.types as t + from docx.oxml.settings import CT_Settings + from docx.oxml.xmlchemy import BaseOxmlElement + + +class Settings(ElementProxy): + """Provides access to document-level settings for a document. + + Accessed using the :attr:`.Document.settings` property. + """ + + def __init__(self, element: BaseOxmlElement, parent: t.ProvidesXmlPart | None = None): + super().__init__(element, parent) + self._settings = cast("CT_Settings", element) + + @property + def odd_and_even_pages_header_footer(self) -> bool: + """True if this document has distinct odd and even page headers and footers. + + Read/write. + """ + return self._settings.evenAndOddHeaders_val + + @odd_and_even_pages_header_footer.setter + def odd_and_even_pages_header_footer(self, value: bool): + self._settings.evenAndOddHeaders_val = value diff --git a/intelliaide/vendor/docx/shape.py b/intelliaide/vendor/docx/shape.py new file mode 100644 index 0000000..cd35deb --- /dev/null +++ b/intelliaide/vendor/docx/shape.py @@ -0,0 +1,103 @@ +"""Objects related to shapes. + +A shape is a visual object that appears on the drawing layer of a document. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from docx.enum.shape import WD_INLINE_SHAPE +from docx.oxml.ns import nsmap +from docx.shared import Parented + +if TYPE_CHECKING: + from docx.oxml.document import CT_Body + from docx.oxml.shape import CT_Inline + from docx.parts.story import StoryPart + from docx.shared import Length + + +class InlineShapes(Parented): + """Sequence of |InlineShape| instances, supporting len(), iteration, and indexed access.""" + + def __init__(self, body_elm: CT_Body, parent: StoryPart): + super(InlineShapes, self).__init__(parent) + self._body = body_elm + + def __getitem__(self, idx: int): + """Provide indexed access, e.g. 'inline_shapes[idx]'.""" + try: + inline = self._inline_lst[idx] + except IndexError: + msg = "inline shape index [%d] out of range" % idx + raise IndexError(msg) + + return InlineShape(inline) + + def __iter__(self): + return (InlineShape(inline) for inline in self._inline_lst) + + def __len__(self): + return len(self._inline_lst) + + @property + def _inline_lst(self): + body = self._body + xpath = "//w:p/w:r/w:drawing/wp:inline" + return body.xpath(xpath) + + +class InlineShape: + """Proxy for an ```` element, representing the container for an inline + graphical object.""" + + def __init__(self, inline: CT_Inline): + super(InlineShape, self).__init__() + self._inline = inline + + @property + def height(self) -> Length: + """Read/write. + + The display height of this inline shape as an |Emu| instance. + """ + return self._inline.extent.cy + + @height.setter + def height(self, cy: Length): + self._inline.extent.cy = cy + self._inline.graphic.graphicData.pic.spPr.cy = cy + + @property + def type(self): + """The type of this inline shape as a member of + ``docx.enum.shape.WD_INLINE_SHAPE``, e.g. ``LINKED_PICTURE``. + + Read-only. + """ + graphicData = self._inline.graphic.graphicData + uri = graphicData.uri + if uri == nsmap["pic"]: + blip = graphicData.pic.blipFill.blip + if blip.link is not None: + return WD_INLINE_SHAPE.LINKED_PICTURE + return WD_INLINE_SHAPE.PICTURE + if uri == nsmap["c"]: + return WD_INLINE_SHAPE.CHART + if uri == nsmap["dgm"]: + return WD_INLINE_SHAPE.SMART_ART + return WD_INLINE_SHAPE.NOT_IMPLEMENTED + + @property + def width(self): + """Read/write. + + The display width of this inline shape as an |Emu| instance. + """ + return self._inline.extent.cx + + @width.setter + def width(self, cx: Length): + self._inline.extent.cx = cx + self._inline.graphic.graphicData.pic.spPr.cx = cx diff --git a/intelliaide/vendor/docx/shared.py b/intelliaide/vendor/docx/shared.py new file mode 100644 index 0000000..6c12dc9 --- /dev/null +++ b/intelliaide/vendor/docx/shared.py @@ -0,0 +1,382 @@ +"""Objects shared by docx modules.""" + +from __future__ import annotations + +import functools +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Generic, + Iterator, + List, + Tuple, + TypeVar, + cast, +) + +if TYPE_CHECKING: + import docx.types as t + from docx.opc.part import XmlPart + from docx.oxml.xmlchemy import BaseOxmlElement + from docx.parts.story import StoryPart + + +class Length(int): + """Base class for length constructor classes Inches, Cm, Mm, Px, and Emu. + + Behaves as an int count of English Metric Units, 914,400 to the inch, 36,000 to the + mm. Provides convenience unit conversion methods in the form of read-only + properties. Immutable. + """ + + _EMUS_PER_INCH = 914400 + _EMUS_PER_CM = 360000 + _EMUS_PER_MM = 36000 + _EMUS_PER_PT = 12700 + _EMUS_PER_TWIP = 635 + + def __new__(cls, emu: int): + return int.__new__(cls, emu) + + @property + def cm(self): + """The equivalent length expressed in centimeters (float).""" + return self / float(self._EMUS_PER_CM) + + @property + def emu(self): + """The equivalent length expressed in English Metric Units (int).""" + return self + + @property + def inches(self): + """The equivalent length expressed in inches (float).""" + return self / float(self._EMUS_PER_INCH) + + @property + def mm(self): + """The equivalent length expressed in millimeters (float).""" + return self / float(self._EMUS_PER_MM) + + @property + def pt(self): + """Floating point length in points.""" + return self / float(self._EMUS_PER_PT) + + @property + def twips(self): + """The equivalent length expressed in twips (int).""" + return int(round(self / float(self._EMUS_PER_TWIP))) + + +class Inches(Length): + """Convenience constructor for length in inches, e.g. ``width = Inches(0.5)``.""" + + def __new__(cls, inches: float): + emu = int(inches * Length._EMUS_PER_INCH) + return Length.__new__(cls, emu) + + +class Cm(Length): + """Convenience constructor for length in centimeters, e.g. ``height = Cm(12)``.""" + + def __new__(cls, cm: float): + emu = int(cm * Length._EMUS_PER_CM) + return Length.__new__(cls, emu) + + +class Emu(Length): + """Convenience constructor for length in English Metric Units, e.g. ``width = + Emu(457200)``.""" + + def __new__(cls, emu: int): + return Length.__new__(cls, int(emu)) + + +class Mm(Length): + """Convenience constructor for length in millimeters, e.g. ``width = Mm(240.5)``.""" + + def __new__(cls, mm: float): + emu = int(mm * Length._EMUS_PER_MM) + return Length.__new__(cls, emu) + + +class Pt(Length): + """Convenience value class for specifying a length in points.""" + + def __new__(cls, points: float): + emu = int(points * Length._EMUS_PER_PT) + return Length.__new__(cls, emu) + + +class Twips(Length): + """Convenience constructor for length in twips, e.g. ``width = Twips(42)``. + + A twip is a twentieth of a point, 635 EMU. + """ + + def __new__(cls, twips: float): + emu = int(twips * Length._EMUS_PER_TWIP) + return Length.__new__(cls, emu) + + +class RGBColor(Tuple[int, int, int]): + """Immutable value object defining a particular RGB color.""" + + def __new__(cls, r: int, g: int, b: int): + msg = "RGBColor() takes three integer values 0-255" + for val in (r, g, b): + if not isinstance(val, int): # pyright: ignore[reportUnnecessaryIsInstance] + raise TypeError(msg) + if val < 0 or val > 255: + raise ValueError(msg) + return super(RGBColor, cls).__new__(cls, (r, g, b)) + + def __repr__(self): + return "RGBColor(0x%02x, 0x%02x, 0x%02x)" % self + + def __str__(self): + """Return a hex string rgb value, like '3C2F80'.""" + return "%02X%02X%02X" % self + + @classmethod + def from_string(cls, rgb_hex_str: str) -> RGBColor: + """Return a new instance from an RGB color hex string like ``'3C2F80'``.""" + r = int(rgb_hex_str[:2], 16) + g = int(rgb_hex_str[2:4], 16) + b = int(rgb_hex_str[4:], 16) + return cls(r, g, b) + + +T = TypeVar("T") + + +class lazyproperty(Generic[T]): + """Decorator like @property, but evaluated only on first access. + + Like @property, this can only be used to decorate methods having only a `self` + parameter, and is accessed like an attribute on an instance, i.e. trailing + parentheses are not used. Unlike @property, the decorated method is only evaluated + on first access; the resulting value is cached and that same value returned on + second and later access without re-evaluation of the method. + + Like @property, this class produces a *data descriptor* object, which is stored in + the __dict__ of the *class* under the name of the decorated method ('fget' + nominally). The cached value is stored in the __dict__ of the *instance* under that + same name. + + Because it is a data descriptor (as opposed to a *non-data descriptor*), its + `__get__()` method is executed on each access of the decorated attribute; the + __dict__ item of the same name is "shadowed" by the descriptor. + + While this may represent a performance improvement over a property, its greater + benefit may be its other characteristics. One common use is to construct + collaborator objects, removing that "real work" from the constructor, while still + only executing once. It also de-couples client code from any sequencing + considerations; if it's accessed from more than one location, it's assured it will + be ready whenever needed. + + Loosely based on: https://stackoverflow.com/a/6849299/1902513. + + A lazyproperty is read-only. There is no counterpart to the optional "setter" (or + deleter) behavior of an @property. This is critically important to maintaining its + immutability and idempotence guarantees. Attempting to assign to a lazyproperty + raises AttributeError unconditionally. + + The parameter names in the methods below correspond to this usage example:: + + class Obj(object) + + @lazyproperty + def fget(self): + return 'some result' + + obj = Obj() + + Not suitable for wrapping a function (as opposed to a method) because it is not + callable.""" + + def __init__(self, fget: Callable[..., T]) -> None: + """*fget* is the decorated method (a "getter" function). + + A lazyproperty is read-only, so there is only an *fget* function (a regular + @property can also have an fset and fdel function). This name was chosen for + consistency with Python's `property` class which uses this name for the + corresponding parameter. + """ + # --- maintain a reference to the wrapped getter method + self._fget = fget + # --- and store the name of that decorated method + self._name = fget.__name__ + # --- adopt fget's __name__, __doc__, and other attributes + functools.update_wrapper(self, fget) # pyright: ignore + + def __get__(self, obj: Any, type: Any = None) -> T: + """Called on each access of 'fget' attribute on class or instance. + + *self* is this instance of a lazyproperty descriptor "wrapping" the property + method it decorates (`fget`, nominally). + + *obj* is the "host" object instance when the attribute is accessed from an + object instance, e.g. `obj = Obj(); obj.fget`. *obj* is None when accessed on + the class, e.g. `Obj.fget`. + + *type* is the class hosting the decorated getter method (`fget`) on both class + and instance attribute access. + """ + # --- when accessed on class, e.g. Obj.fget, just return this descriptor + # --- instance (patched above to look like fget). + if obj is None: + return self # type: ignore + + # --- when accessed on instance, start by checking instance __dict__ for + # --- item with key matching the wrapped function's name + value = obj.__dict__.get(self._name) + if value is None: + # --- on first access, the __dict__ item will be absent. Evaluate fget() + # --- and store that value in the (otherwise unused) host-object + # --- __dict__ value of same name ('fget' nominally) + value = self._fget(obj) + obj.__dict__[self._name] = value + return cast(T, value) + + def __set__(self, obj: Any, value: Any) -> None: + """Raises unconditionally, to preserve read-only behavior. + + This decorator is intended to implement immutable (and idempotent) object + attributes. For that reason, assignment to this property must be explicitly + prevented. + + If this __set__ method was not present, this descriptor would become a + *non-data descriptor*. That would be nice because the cached value would be + accessed directly once set (__dict__ attrs have precedence over non-data + descriptors on instance attribute lookup). The problem is, there would be + nothing to stop assignment to the cached value, which would overwrite the result + of `fget()` and break both the immutability and idempotence guarantees of this + decorator. + + The performance with this __set__() method in place was roughly 0.4 usec per + access when measured on a 2.8GHz development machine; so quite snappy and + probably not a rich target for optimization efforts. + """ + raise AttributeError("can't set attribute") + + +def write_only_property(f: Callable[[Any, Any], None]): + """@write_only_property decorator. + + Creates a property (descriptor attribute) that accepts assignment, but not getattr + (use in an expression). + """ + docstring = f.__doc__ + + return property(fset=f, doc=docstring) + + +class ElementProxy: + """Base class for lxml element proxy classes. + + An element proxy class is one whose primary responsibilities are fulfilled by + manipulating the attributes and child elements of an XML element. They are the most + common type of class in python-docx other than custom element (oxml) classes. + """ + + def __init__(self, element: BaseOxmlElement, parent: t.ProvidesXmlPart | None = None): + self._element = element + self._parent = parent + + def __eq__(self, other: object): + """Return |True| if this proxy object refers to the same oxml element as does + `other`. + + ElementProxy objects are value objects and should maintain no mutable local + state. Equality for proxy objects is defined as referring to the same XML + element, whether or not they are the same proxy object instance. + """ + if not isinstance(other, ElementProxy): + return False + return self._element is other._element + + def __ne__(self, other: object): + if not isinstance(other, ElementProxy): + return True + return self._element is not other._element + + @property + def element(self): + """The lxml element proxied by this object.""" + return self._element + + @property + def part(self) -> XmlPart: + """The package part containing this object.""" + if self._parent is None: + raise ValueError("part is not accessible from this element") + return self._parent.part + + +class Parented: + """Provides common services for document elements that occur below a part but may + occasionally require an ancestor object to provide a service, such as add or drop a + relationship. + + Provides ``self._parent`` attribute to subclasses. + """ + + def __init__(self, parent: t.ProvidesXmlPart): + self._parent = parent + + @property + def part(self) -> XmlPart: + """The package part containing this object.""" + return self._parent.part + + +class StoryChild: + """A document element within a story part. + + Story parts include DocumentPart and Header/FooterPart and can contain block items + (paragraphs and tables). Items from the block-item subtree occasionally require an + ancestor object to provide access to part-level or package-level items like styles + or images or to add or drop a relationship. + + Provides `self._parent` attribute to subclasses. + """ + + def __init__(self, parent: t.ProvidesStoryPart): + self._parent = parent + + @property + def part(self) -> StoryPart: + """The package part containing this object.""" + return self._parent.part + + +class TextAccumulator: + """Accepts `str` fragments and joins them together, in order, on `.pop(). + + Handy when text in a stream is broken up arbitrarily and you want to join it back + together within certain bounds. The optional `separator` argument determines how + the text fragments are punctuated, defaulting to the empty string. + """ + + def __init__(self, separator: str = ""): + self._separator = separator + self._texts: List[str] = [] + + def push(self, text: str) -> None: + """Add a text fragment to the accumulator.""" + self._texts.append(text) + + def pop(self) -> Iterator[str]: + """Generate sero-or-one str from those accumulated. + + Using `yield from accum.pop()` in a generator setting avoids producing an empty + string when no text is in the accumulator. + """ + if not self._texts: + return + text = self._separator.join(self._texts) + self._texts.clear() + yield text diff --git a/intelliaide/vendor/docx/styles/__init__.py b/intelliaide/vendor/docx/styles/__init__.py new file mode 100644 index 0000000..6358baf --- /dev/null +++ b/intelliaide/vendor/docx/styles/__init__.py @@ -0,0 +1,40 @@ +"""Sub-package module for docx.styles sub-package.""" + +from __future__ import annotations + +from typing import Dict + + +class BabelFish: + """Translates special-case style names from UI name (e.g. Heading 1) to + internal/styles.xml name (e.g. heading 1) and back.""" + + style_aliases = ( + ("Caption", "caption"), + ("Footer", "footer"), + ("Header", "header"), + ("Heading 1", "heading 1"), + ("Heading 2", "heading 2"), + ("Heading 3", "heading 3"), + ("Heading 4", "heading 4"), + ("Heading 5", "heading 5"), + ("Heading 6", "heading 6"), + ("Heading 7", "heading 7"), + ("Heading 8", "heading 8"), + ("Heading 9", "heading 9"), + ) + + internal_style_names: Dict[str, str] = dict(style_aliases) + ui_style_names = {item[1]: item[0] for item in style_aliases} + + @classmethod + def ui2internal(cls, ui_style_name: str) -> str: + """Return the internal style name corresponding to `ui_style_name`, such as + 'heading 1' for 'Heading 1'.""" + return cls.internal_style_names.get(ui_style_name, ui_style_name) + + @classmethod + def internal2ui(cls, internal_style_name: str) -> str: + """Return the user interface style name corresponding to `internal_style_name`, + such as 'Heading 1' for 'heading 1'.""" + return cls.ui_style_names.get(internal_style_name, internal_style_name) diff --git a/intelliaide/vendor/docx/styles/latent.py b/intelliaide/vendor/docx/styles/latent.py new file mode 100644 index 0000000..c9db62f --- /dev/null +++ b/intelliaide/vendor/docx/styles/latent.py @@ -0,0 +1,198 @@ +"""Latent style-related objects.""" + +from docx.shared import ElementProxy +from docx.styles import BabelFish + + +class LatentStyles(ElementProxy): + """Provides access to the default behaviors for latent styles in this document and + to the collection of |_LatentStyle| objects that define overrides of those defaults + for a particular named latent style.""" + + def __getitem__(self, key): + """Enables dictionary-style access to a latent style by name.""" + style_name = BabelFish.ui2internal(key) + lsdException = self._element.get_by_name(style_name) + if lsdException is None: + raise KeyError("no latent style with name '%s'" % key) + return _LatentStyle(lsdException) + + def __iter__(self): + return (_LatentStyle(ls) for ls in self._element.lsdException_lst) + + def __len__(self): + return len(self._element.lsdException_lst) + + def add_latent_style(self, name): + """Return a newly added |_LatentStyle| object to override the inherited defaults + defined in this latent styles object for the built-in style having `name`.""" + lsdException = self._element.add_lsdException() + lsdException.name = BabelFish.ui2internal(name) + return _LatentStyle(lsdException) + + @property + def default_priority(self): + """Integer between 0 and 99 inclusive specifying the default sort order for + latent styles in style lists and the style gallery. + + |None| if no value is assigned, which causes Word to use the default value 99. + """ + return self._element.defUIPriority + + @default_priority.setter + def default_priority(self, value): + self._element.defUIPriority = value + + @property + def default_to_hidden(self): + """Boolean specifying whether the default behavior for latent styles is to be + hidden. + + A hidden style does not appear in the recommended list or in the style gallery. + """ + return self._element.bool_prop("defSemiHidden") + + @default_to_hidden.setter + def default_to_hidden(self, value): + self._element.set_bool_prop("defSemiHidden", value) + + @property + def default_to_locked(self): + """Boolean specifying whether the default behavior for latent styles is to be + locked. + + A locked style does not appear in the styles panel or the style gallery and + cannot be applied to document content. This behavior is only active when + formatting protection is turned on for the document (via the Developer menu). + """ + return self._element.bool_prop("defLockedState") + + @default_to_locked.setter + def default_to_locked(self, value): + self._element.set_bool_prop("defLockedState", value) + + @property + def default_to_quick_style(self): + """Boolean specifying whether the default behavior for latent styles is to + appear in the style gallery when not hidden.""" + return self._element.bool_prop("defQFormat") + + @default_to_quick_style.setter + def default_to_quick_style(self, value): + self._element.set_bool_prop("defQFormat", value) + + @property + def default_to_unhide_when_used(self): + """Boolean specifying whether the default behavior for latent styles is to be + unhidden when first applied to content.""" + return self._element.bool_prop("defUnhideWhenUsed") + + @default_to_unhide_when_used.setter + def default_to_unhide_when_used(self, value): + self._element.set_bool_prop("defUnhideWhenUsed", value) + + @property + def load_count(self): + """Integer specifying the number of built-in styles to initialize to the + defaults specified in this |LatentStyles| object. + + |None| if there is no setting in the XML (very uncommon). The default Word 2011 + template sets this value to 276, accounting for the built-in styles in Word + 2010. + """ + return self._element.count + + @load_count.setter + def load_count(self, value): + self._element.count = value + + +class _LatentStyle(ElementProxy): + """Proxy for an `w:lsdException` element, which specifies display behaviors for a + built-in style when no definition for that style is stored yet in the `styles.xml` + part. + + The values in this element override the defaults specified in the parent + `w:latentStyles` element. + """ + + def delete(self): + """Remove this latent style definition such that the defaults defined in the + containing |LatentStyles| object provide the effective value for each of its + attributes. + + Attempting to access any attributes on this object after calling this method + will raise |AttributeError|. + """ + self._element.delete() + self._element = None + + @property + def hidden(self): + """Tri-state value specifying whether this latent style should appear in the + recommended list. + + |None| indicates the effective value is inherited from the parent + ```` element. + """ + return self._element.on_off_prop("semiHidden") + + @hidden.setter + def hidden(self, value): + self._element.set_on_off_prop("semiHidden", value) + + @property + def locked(self): + """Tri-state value specifying whether this latent styles is locked. + + A locked style does not appear in the styles panel or the style gallery and + cannot be applied to document content. This behavior is only active when + formatting protection is turned on for the document (via the Developer menu). + """ + return self._element.on_off_prop("locked") + + @locked.setter + def locked(self, value): + self._element.set_on_off_prop("locked", value) + + @property + def name(self): + """The name of the built-in style this exception applies to.""" + return BabelFish.internal2ui(self._element.name) + + @property + def priority(self): + """The integer sort key for this latent style in the Word UI.""" + return self._element.uiPriority + + @priority.setter + def priority(self, value): + self._element.uiPriority = value + + @property + def quick_style(self): + """Tri-state value specifying whether this latent style should appear in the + Word styles gallery when not hidden. + + |None| indicates the effective value should be inherited from the default values + in its parent |LatentStyles| object. + """ + return self._element.on_off_prop("qFormat") + + @quick_style.setter + def quick_style(self, value): + self._element.set_on_off_prop("qFormat", value) + + @property + def unhide_when_used(self): + """Tri-state value specifying whether this style should have its :attr:`hidden` + attribute set |False| the next time the style is applied to content. + + |None| indicates the effective value should be inherited from the default + specified by its parent |LatentStyles| object. + """ + return self._element.on_off_prop("unhideWhenUsed") + + @unhide_when_used.setter + def unhide_when_used(self, value): + self._element.set_on_off_prop("unhideWhenUsed", value) diff --git a/intelliaide/vendor/docx/styles/style.py b/intelliaide/vendor/docx/styles/style.py new file mode 100644 index 0000000..aa175ea --- /dev/null +++ b/intelliaide/vendor/docx/styles/style.py @@ -0,0 +1,254 @@ +"""Style object hierarchy.""" + +from __future__ import annotations + +from typing import Type + +from docx.enum.style import WD_STYLE_TYPE +from docx.oxml.styles import CT_Style +from docx.shared import ElementProxy +from docx.styles import BabelFish +from docx.text.font import Font +from docx.text.parfmt import ParagraphFormat + + +def StyleFactory(style_elm: CT_Style) -> BaseStyle: + """Return `Style` object of appropriate |BaseStyle| subclass for `style_elm`.""" + style_cls: Type[BaseStyle] = { + WD_STYLE_TYPE.PARAGRAPH: ParagraphStyle, + WD_STYLE_TYPE.CHARACTER: CharacterStyle, + WD_STYLE_TYPE.TABLE: _TableStyle, + WD_STYLE_TYPE.LIST: _NumberingStyle, + }[style_elm.type] + + return style_cls(style_elm) + + +class BaseStyle(ElementProxy): + """Base class for the various types of style object, paragraph, character, table, + and numbering. + + These properties and methods are inherited by all style objects. + """ + + def __init__(self, style_elm: CT_Style): + super().__init__(style_elm) + self._style_elm = style_elm + + @property + def builtin(self): + """Read-only. + + |True| if this style is a built-in style. |False| indicates it is a custom + (user-defined) style. Note this value is based on the presence of a + `customStyle` attribute in the XML, not on specific knowledge of which styles + are built into Word. + """ + return not self._element.customStyle + + def delete(self): + """Remove this style definition from the document. + + Note that calling this method does not remove or change the style applied to any + document content. Content items having the deleted style will be rendered using + the default style, as is any content with a style not defined in the document. + """ + self._element.delete() + self._element = None + + @property + def hidden(self): + """|True| if display of this style in the style gallery and list of recommended + styles is suppressed. + + |False| otherwise. In order to be shown in the style gallery, this value must be + |False| and :attr:`.quick_style` must be |True|. + """ + return self._element.semiHidden_val + + @hidden.setter + def hidden(self, value): + self._element.semiHidden_val = value + + @property + def locked(self): + """Read/write Boolean. + + |True| if this style is locked. A locked style does not appear in the styles + panel or the style gallery and cannot be applied to document content. This + behavior is only active when formatting protection is turned on for the document + (via the Developer menu). + """ + return self._element.locked_val + + @locked.setter + def locked(self, value): + self._element.locked_val = value + + @property + def name(self): + """The UI name of this style.""" + name = self._element.name_val + if name is None: + return None + return BabelFish.internal2ui(name) + + @name.setter + def name(self, value): + self._element.name_val = value + + @property + def priority(self): + """The integer sort key governing display sequence of this style in the Word UI. + + |None| indicates no setting is defined, causing Word to use the default value of + 0. Style name is used as a secondary sort key to resolve ordering of styles + having the same priority value. + """ + return self._element.uiPriority_val + + @priority.setter + def priority(self, value): + self._element.uiPriority_val = value + + @property + def quick_style(self): + """|True| if this style should be displayed in the style gallery when + :attr:`.hidden` is |False|. + + Read/write Boolean. + """ + return self._element.qFormat_val + + @quick_style.setter + def quick_style(self, value): + self._element.qFormat_val = value + + @property + def style_id(self) -> str: + """The unique key name (string) for this style. + + This value is subject to rewriting by Word and should generally not be changed + unless you are familiar with the internals involved. + """ + return self._style_elm.styleId + + @style_id.setter + def style_id(self, value): + self._element.styleId = value + + @property + def type(self): + """Member of :ref:`WdStyleType` corresponding to the type of this style, e.g. + ``WD_STYLE_TYPE.PARAGRAPH``.""" + type = self._style_elm.type + if type is None: + return WD_STYLE_TYPE.PARAGRAPH + return type + + @property + def unhide_when_used(self): + """|True| if an application should make this style visible the next time it is + applied to content. + + False otherwise. Note that |docx| does not automatically unhide a style having + |True| for this attribute when it is applied to content. + """ + return self._element.unhideWhenUsed_val + + @unhide_when_used.setter + def unhide_when_used(self, value): + self._element.unhideWhenUsed_val = value + + +class CharacterStyle(BaseStyle): + """A character style. + + A character style is applied to a |Run| object and primarily provides character- + level formatting via the |Font| object in its :attr:`.font` property. + """ + + @property + def base_style(self): + """Style object this style inherits from or |None| if this style is not based on + another style.""" + base_style = self._element.base_style + if base_style is None: + return None + return StyleFactory(base_style) + + @base_style.setter + def base_style(self, style): + style_id = style.style_id if style is not None else None + self._element.basedOn_val = style_id + + @property + def font(self): + """The |Font| object providing access to the character formatting properties for + this style, such as font name and size.""" + return Font(self._element) + + +# -- just in case someone uses the old name in an extension function -- +_CharacterStyle = CharacterStyle + + +class ParagraphStyle(CharacterStyle): + """A paragraph style. + + A paragraph style provides both character formatting and paragraph formatting such + as indentation and line-spacing. + """ + + def __repr__(self): + return "_ParagraphStyle('%s') id: %s" % (self.name, id(self)) + + @property + def next_paragraph_style(self): + """|_ParagraphStyle| object representing the style to be applied automatically + to a new paragraph inserted after a paragraph of this style. + + Returns self if no next paragraph style is defined. Assigning |None| or `self` + removes the setting such that new paragraphs are created using this same style. + """ + next_style_elm = self._element.next_style + if next_style_elm is None: + return self + if next_style_elm.type != WD_STYLE_TYPE.PARAGRAPH: + return self + return StyleFactory(next_style_elm) + + @next_paragraph_style.setter + def next_paragraph_style(self, style): + if style is None or style.style_id == self.style_id: + self._element._remove_next() + else: + self._element.get_or_add_next().val = style.style_id + + @property + def paragraph_format(self): + """The |ParagraphFormat| object providing access to the paragraph formatting + properties for this style such as indentation.""" + return ParagraphFormat(self._element) + + +# -- just in case someone uses the old name in an extension function -- +_ParagraphStyle = ParagraphStyle + + +class _TableStyle(ParagraphStyle): + """A table style. + + A table style provides character and paragraph formatting for its contents as well + as special table formatting properties. + """ + + def __repr__(self): + return "_TableStyle('%s') id: %s" % (self.name, id(self)) + + +class _NumberingStyle(BaseStyle): + """A numbering style. + + Not yet implemented. + """ diff --git a/intelliaide/vendor/docx/styles/styles.py b/intelliaide/vendor/docx/styles/styles.py new file mode 100644 index 0000000..b05b3eb --- /dev/null +++ b/intelliaide/vendor/docx/styles/styles.py @@ -0,0 +1,136 @@ +"""Styles object, container for all objects in the styles part.""" + +from __future__ import annotations + +from warnings import warn + +from docx.enum.style import WD_STYLE_TYPE +from docx.oxml.styles import CT_Styles +from docx.shared import ElementProxy +from docx.styles import BabelFish +from docx.styles.latent import LatentStyles +from docx.styles.style import BaseStyle, StyleFactory + + +class Styles(ElementProxy): + """Provides access to the styles defined in a document. + + Accessed using the :attr:`.Document.styles` property. Supports ``len()``, iteration, + and dictionary-style access by style name. + """ + + def __init__(self, styles: CT_Styles): + super().__init__(styles) + self._element = styles + + def __contains__(self, name): + """Enables `in` operator on style name.""" + internal_name = BabelFish.ui2internal(name) + return any(style.name_val == internal_name for style in self._element.style_lst) + + def __getitem__(self, key: str): + """Enables dictionary-style access by UI name. + + Lookup by style id is deprecated, triggers a warning, and will be removed in a + near-future release. + """ + style_elm = self._element.get_by_name(BabelFish.ui2internal(key)) + if style_elm is not None: + return StyleFactory(style_elm) + + style_elm = self._element.get_by_id(key) + if style_elm is not None: + msg = "style lookup by style_id is deprecated. Use style name as key instead." + warn(msg, UserWarning, stacklevel=2) + return StyleFactory(style_elm) + + raise KeyError("no style with name '%s'" % key) + + def __iter__(self): + return (StyleFactory(style) for style in self._element.style_lst) + + def __len__(self): + return len(self._element.style_lst) + + def add_style(self, name, style_type, builtin=False): + """Return a newly added style object of `style_type` and identified by `name`. + + A builtin style can be defined by passing True for the optional `builtin` + argument. + """ + style_name = BabelFish.ui2internal(name) + if style_name in self: + raise ValueError("document already contains style '%s'" % name) + style = self._element.add_style_of_type(style_name, style_type, builtin) + return StyleFactory(style) + + def default(self, style_type: WD_STYLE_TYPE): + """Return the default style for `style_type` or |None| if no default is defined + for that type (not common).""" + style = self._element.default_for(style_type) + if style is None: + return None + return StyleFactory(style) + + def get_by_id(self, style_id: str | None, style_type: WD_STYLE_TYPE): + """Return the style of `style_type` matching `style_id`. + + Returns the default for `style_type` if `style_id` is not found or is |None|, or + if the style having `style_id` is not of `style_type`. + """ + if style_id is None: + return self.default(style_type) + return self._get_by_id(style_id, style_type) + + def get_style_id(self, style_or_name, style_type): + """Return the id of the style corresponding to `style_or_name`, or |None| if + `style_or_name` is |None|. + + If `style_or_name` is not a style object, the style is looked up using + `style_or_name` as a style name, raising |ValueError| if no style with that name + is defined. Raises |ValueError| if the target style is not of `style_type`. + """ + if style_or_name is None: + return None + elif isinstance(style_or_name, BaseStyle): + return self._get_style_id_from_style(style_or_name, style_type) + else: + return self._get_style_id_from_name(style_or_name, style_type) + + @property + def latent_styles(self): + """A |LatentStyles| object providing access to the default behaviors for latent + styles and the collection of |_LatentStyle| objects that define overrides of + those defaults for a particular named latent style.""" + return LatentStyles(self._element.get_or_add_latentStyles()) + + def _get_by_id(self, style_id: str | None, style_type: WD_STYLE_TYPE): + """Return the style of `style_type` matching `style_id`. + + Returns the default for `style_type` if `style_id` is not found or if the style + having `style_id` is not of `style_type`. + """ + style = self._element.get_by_id(style_id) if style_id else None + if style is None or style.type != style_type: + return self.default(style_type) + return StyleFactory(style) + + def _get_style_id_from_name(self, style_name: str, style_type: WD_STYLE_TYPE) -> str | None: + """Return the id of the style of `style_type` corresponding to `style_name`. + + Returns |None| if that style is the default style for `style_type`. Raises + |ValueError| if the named style is not found in the document or does not match + `style_type`. + """ + return self._get_style_id_from_style(self[style_name], style_type) + + def _get_style_id_from_style(self, style: BaseStyle, style_type: WD_STYLE_TYPE) -> str | None: + """Id of `style`, or |None| if it is the default style of `style_type`. + + Raises |ValueError| if style is not of `style_type`. + """ + if style.type != style_type: + raise ValueError("assigned style is type %s, need type %s" % (style.type, style_type)) + if style == self.default(style_type): + return None + return style.style_id diff --git a/intelliaide/vendor/docx/table.py b/intelliaide/vendor/docx/table.py new file mode 100644 index 0000000..545c468 --- /dev/null +++ b/intelliaide/vendor/docx/table.py @@ -0,0 +1,537 @@ +"""The |Table| object and related proxy classes.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Iterator, cast, overload + +from typing_extensions import TypeAlias + +from docx.blkcntnr import BlockItemContainer +from docx.enum.style import WD_STYLE_TYPE +from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT +from docx.oxml.simpletypes import ST_Merge +from docx.oxml.table import CT_TblGridCol +from docx.shared import Inches, Parented, StoryChild, lazyproperty + +if TYPE_CHECKING: + import docx.types as t + from docx.enum.table import WD_ROW_HEIGHT_RULE, WD_TABLE_ALIGNMENT, WD_TABLE_DIRECTION + from docx.oxml.table import CT_Row, CT_Tbl, CT_TblPr, CT_Tc + from docx.shared import Length + from docx.styles.style import ( + ParagraphStyle, + _TableStyle, # pyright: ignore[reportPrivateUsage] + ) + +TableParent: TypeAlias = "Table | _Columns | _Rows" + + +class Table(StoryChild): + """Proxy class for a WordprocessingML ```` element.""" + + def __init__(self, tbl: CT_Tbl, parent: t.ProvidesStoryPart): + super(Table, self).__init__(parent) + self._element = tbl + self._tbl = tbl + + def add_column(self, width: Length): + """Return a |_Column| object of `width`, newly added rightmost to the table.""" + tblGrid = self._tbl.tblGrid + gridCol = tblGrid.add_gridCol() + gridCol.w = width + for tr in self._tbl.tr_lst: + tc = tr.add_tc() + tc.width = width + return _Column(gridCol, self) + + def add_row(self): + """Return a |_Row| instance, newly added bottom-most to the table.""" + tbl = self._tbl + tr = tbl.add_tr() + for gridCol in tbl.tblGrid.gridCol_lst: + tc = tr.add_tc() + if gridCol.w is not None: + tc.width = gridCol.w + return _Row(tr, self) + + @property + def alignment(self) -> WD_TABLE_ALIGNMENT | None: + """Read/write. + + A member of :ref:`WdRowAlignment` or None, specifying the positioning of this + table between the page margins. |None| if no setting is specified, causing the + effective value to be inherited from the style hierarchy. + """ + return self._tblPr.alignment + + @alignment.setter + def alignment(self, value: WD_TABLE_ALIGNMENT | None): + self._tblPr.alignment = value + + @property + def autofit(self) -> bool: + """|True| if column widths can be automatically adjusted to improve the fit of + cell contents. + + |False| if table layout is fixed. Column widths are adjusted in either case if + total column width exceeds page width. Read/write boolean. + """ + return self._tblPr.autofit + + @autofit.setter + def autofit(self, value: bool): + self._tblPr.autofit = value + + def cell(self, row_idx: int, col_idx: int) -> _Cell: + """|_Cell| at `row_idx`, `col_idx` intersection. + + (0, 0) is the top, left-most cell. + """ + cell_idx = col_idx + (row_idx * self._column_count) + return self._cells[cell_idx] + + def column_cells(self, column_idx: int) -> list[_Cell]: + """Sequence of cells in the column at `column_idx` in this table.""" + cells = self._cells + idxs = range(column_idx, len(cells), self._column_count) + return [cells[idx] for idx in idxs] + + @lazyproperty + def columns(self): + """|_Columns| instance representing the sequence of columns in this table.""" + return _Columns(self._tbl, self) + + def row_cells(self, row_idx: int) -> list[_Cell]: + """DEPRECATED: Use `table.rows[row_idx].cells` instead. + + Sequence of cells in the row at `row_idx` in this table. + """ + column_count = self._column_count + start = row_idx * column_count + end = start + column_count + return self._cells[start:end] + + @lazyproperty + def rows(self) -> _Rows: + """|_Rows| instance containing the sequence of rows in this table.""" + return _Rows(self._tbl, self) + + @property + def style(self) -> _TableStyle | None: + """|_TableStyle| object representing the style applied to this table. + + Read/write. The default table style for the document (often `Normal Table`) is + returned if the table has no directly-applied style. Assigning |None| to this + property removes any directly-applied table style causing it to inherit the + default table style of the document. + + Note that the style name of a table style differs slightly from that displayed + in the user interface; a hyphen, if it appears, must be removed. For example, + `Light Shading - Accent 1` becomes `Light Shading Accent 1`. + """ + style_id = self._tbl.tblStyle_val + return cast("_TableStyle | None", self.part.get_style(style_id, WD_STYLE_TYPE.TABLE)) + + @style.setter + def style(self, style_or_name: _TableStyle | str | None): + style_id = self.part.get_style_id(style_or_name, WD_STYLE_TYPE.TABLE) + self._tbl.tblStyle_val = style_id + + @property + def table(self): + """Provide child objects with reference to the |Table| object they belong to, + without them having to know their direct parent is a |Table| object. + + This is the terminus of a series of `parent._table` calls from an arbitrary + child through its ancestors. + """ + return self + + @property + def table_direction(self) -> WD_TABLE_DIRECTION | None: + """Member of :ref:`WdTableDirection` indicating cell-ordering direction. + + For example: `WD_TABLE_DIRECTION.LTR`. |None| indicates the value is inherited + from the style hierarchy. + """ + return cast("WD_TABLE_DIRECTION | None", self._tbl.bidiVisual_val) + + @table_direction.setter + def table_direction(self, value: WD_TABLE_DIRECTION | None): + self._element.bidiVisual_val = value + + @property + def _cells(self) -> list[_Cell]: + """A sequence of |_Cell| objects, one for each cell of the layout grid. + + If the table contains a span, one or more |_Cell| object references are + repeated. + """ + col_count = self._column_count + cells: list[_Cell] = [] + for tc in self._tbl.iter_tcs(): + for grid_span_idx in range(tc.grid_span): + if tc.vMerge == ST_Merge.CONTINUE: + cells.append(cells[-col_count]) + elif grid_span_idx > 0: + cells.append(cells[-1]) + else: + cells.append(_Cell(tc, self)) + return cells + + @property + def _column_count(self): + """The number of grid columns in this table.""" + return self._tbl.col_count + + @property + def _tblPr(self) -> CT_TblPr: + return self._tbl.tblPr + + +class _Cell(BlockItemContainer): + """Table cell.""" + + def __init__(self, tc: CT_Tc, parent: TableParent): + super(_Cell, self).__init__(tc, cast("t.ProvidesStoryPart", parent)) + self._parent = parent + self._tc = self._element = tc + + def add_paragraph(self, text: str = "", style: str | ParagraphStyle | None = None): + """Return a paragraph newly added to the end of the content in this cell. + + If present, `text` is added to the paragraph in a single run. If specified, the + paragraph style `style` is applied. If `style` is not specified or is |None|, + the result is as though the 'Normal' style was applied. Note that the formatting + of text in a cell can be influenced by the table style. `text` can contain tab + (``\\t``) characters, which are converted to the appropriate XML form for a tab. + `text` can also include newline (``\\n``) or carriage return (``\\r``) + characters, each of which is converted to a line break. + """ + return super(_Cell, self).add_paragraph(text, style) + + def add_table( # pyright: ignore[reportIncompatibleMethodOverride] + self, rows: int, cols: int + ) -> Table: + """Return a table newly added to this cell after any existing cell content. + + The new table will have `rows` rows and `cols` columns. + + An empty paragraph is added after the table because Word requires a paragraph + element as the last element in every cell. + """ + width = self.width if self.width is not None else Inches(1) + table = super(_Cell, self).add_table(rows, cols, width) + self.add_paragraph() + return table + + @property + def grid_span(self) -> int: + """Number of layout-grid cells this cell spans horizontally. + + A "normal" cell has a grid-span of 1. A horizontally merged cell has a grid-span of 2 or + more. + """ + return self._tc.grid_span + + def merge(self, other_cell: _Cell): + """Return a merged cell created by spanning the rectangular region having this + cell and `other_cell` as diagonal corners. + + Raises |InvalidSpanError| if the cells do not define a rectangular region. + """ + tc, tc_2 = self._tc, other_cell._tc + merged_tc = tc.merge(tc_2) + return _Cell(merged_tc, self._parent) + + @property + def paragraphs(self): + """List of paragraphs in the cell. + + A table cell is required to contain at least one block-level element and end + with a paragraph. By default, a new cell contains a single paragraph. Read-only + """ + return super(_Cell, self).paragraphs + + @property + def tables(self): + """List of tables in the cell, in the order they appear. + + Read-only. + """ + return super(_Cell, self).tables + + @property + def text(self) -> str: + """The entire contents of this cell as a string of text. + + Assigning a string to this property replaces all existing content with a single + paragraph containing the assigned text in a single run. + """ + return "\n".join(p.text for p in self.paragraphs) + + @text.setter + def text(self, text: str): + """Write-only. + + Set entire contents of cell to the string `text`. Any existing content or + revisions are replaced. + """ + tc = self._tc + tc.clear_content() + p = tc.add_p() + r = p.add_r() + r.text = text + + @property + def vertical_alignment(self): + """Member of :ref:`WdCellVerticalAlignment` or None. + + A value of |None| indicates vertical alignment for this cell is inherited. + Assigning |None| causes any explicitly defined vertical alignment to be removed, + restoring inheritance. + """ + tcPr = self._element.tcPr + if tcPr is None: + return None + return tcPr.vAlign_val + + @vertical_alignment.setter + def vertical_alignment(self, value: WD_CELL_VERTICAL_ALIGNMENT | None): + tcPr = self._element.get_or_add_tcPr() + tcPr.vAlign_val = value + + @property + def width(self): + """The width of this cell in EMU, or |None| if no explicit width is set.""" + return self._tc.width + + @width.setter + def width(self, value: Length): + self._tc.width = value + + +class _Column(Parented): + """Table column.""" + + def __init__(self, gridCol: CT_TblGridCol, parent: TableParent): + super(_Column, self).__init__(parent) + self._parent = parent + self._gridCol = gridCol + + @property + def cells(self) -> tuple[_Cell, ...]: + """Sequence of |_Cell| instances corresponding to cells in this column.""" + return tuple(self.table.column_cells(self._index)) + + @property + def table(self) -> Table: + """Reference to the |Table| object this column belongs to.""" + return self._parent.table + + @property + def width(self) -> Length | None: + """The width of this column in EMU, or |None| if no explicit width is set.""" + return self._gridCol.w + + @width.setter + def width(self, value: Length | None): + self._gridCol.w = value + + @property + def _index(self): + """Index of this column in its table, starting from zero.""" + return self._gridCol.gridCol_idx + + +class _Columns(Parented): + """Sequence of |_Column| instances corresponding to the columns in a table. + + Supports ``len()``, iteration and indexed access. + """ + + def __init__(self, tbl: CT_Tbl, parent: TableParent): + super(_Columns, self).__init__(parent) + self._parent = parent + self._tbl = tbl + + def __getitem__(self, idx: int): + """Provide indexed access, e.g. 'columns[0]'.""" + try: + gridCol = self._gridCol_lst[idx] + except IndexError: + msg = "column index [%d] is out of range" % idx + raise IndexError(msg) + return _Column(gridCol, self) + + def __iter__(self): + for gridCol in self._gridCol_lst: + yield _Column(gridCol, self) + + def __len__(self): + return len(self._gridCol_lst) + + @property + def table(self) -> Table: + """Reference to the |Table| object this column collection belongs to.""" + return self._parent.table + + @property + def _gridCol_lst(self): + """Sequence containing ```` elements for this table, each + representing a table column.""" + tblGrid = self._tbl.tblGrid + return tblGrid.gridCol_lst + + +class _Row(Parented): + """Table row.""" + + def __init__(self, tr: CT_Row, parent: TableParent): + super(_Row, self).__init__(parent) + self._parent = parent + self._tr = self._element = tr + + @property + def cells(self) -> tuple[_Cell, ...]: + """Sequence of |_Cell| instances corresponding to cells in this row. + + Note that Word allows table rows to start later than the first column and end before the + last column. + + - Only cells actually present are included in the return value. + - This implies the length of this cell sequence may differ between rows of the same table. + - If you are reading the cells from each row to form a rectangular "matrix" data structure + of the table cell values, you will need to account for empty leading and/or trailing + layout-grid positions using `.grid_cols_before` and `.grid_cols_after`. + + """ + + def iter_tc_cells(tc: CT_Tc) -> Iterator[_Cell]: + """Generate a cell object for each layout-grid cell in `tc`. + + In particular, a `` element with a horizontal "span" with generate the same cell + multiple times, one for each grid-cell being spanned. This approximates a row in a + "uniform" table, where each row has a cell for each column in the table. + """ + # -- a cell comprising the second or later row of a vertical span is indicated by + # -- tc.vMerge="continue" (the default value of the `w:vMerge` attribute, when it is + # -- present in the XML). The `w:tc` element at the same grid-offset in the prior row + # -- is guaranteed to be the same width (gridSpan). So we can delegate content + # -- discovery to that prior-row `w:tc` element (recursively) until we arrive at the + # -- "root" cell -- for the vertical span. + if tc.vMerge == "continue": + yield from iter_tc_cells(tc._tc_above) # pyright: ignore[reportPrivateUsage] + return + + # -- Otherwise, vMerge is either "restart" or None, meaning this `tc` holds the actual + # -- content of the cell (whether it is vertically merged or not). + cell = _Cell(tc, self.table) + for _ in range(tc.grid_span): + yield cell + + def _iter_row_cells() -> Iterator[_Cell]: + """Generate `_Cell` instance for each populated layout-grid cell in this row.""" + for tc in self._tr.tc_lst: + yield from iter_tc_cells(tc) + + return tuple(_iter_row_cells()) + + @property + def grid_cols_after(self) -> int: + """Count of unpopulated grid-columns after the last cell in this row. + + Word allows a row to "end early", meaning that one or more cells are not present at the + end of that row. + + Note these are not simply "empty" cells. The renderer reads this value and "skips" this + many columns after drawing the last cell. + + Note this also implies that not all rows are guaranteed to have the same number of cells, + e.g. `_Row.cells` could have length `n` for one row and `n - m` for the next row in the same + table. Visually this appears as a column (at the beginning or end, not in the middle) with + one or more cells missing. + """ + return self._tr.grid_after + + @property + def grid_cols_before(self) -> int: + """Count of unpopulated grid-columns before the first cell in this row. + + Word allows a row to "start late", meaning that one or more cells are not present at the + beginning of that row. + + Note these are not simply "empty" cells. The renderer reads this value and skips forward to + the table layout-grid position of the first cell in this row; the renderer "skips" this many + columns before drawing the first cell. + + Note this also implies that not all rows are guaranteed to have the same number of cells, + e.g. `_Row.cells` could have length `n` for one row and `n - m` for the next row in the same + table. + """ + return self._tr.grid_before + + @property + def height(self) -> Length | None: + """Return a |Length| object representing the height of this cell, or |None| if + no explicit height is set.""" + return self._tr.trHeight_val + + @height.setter + def height(self, value: Length | None): + self._tr.trHeight_val = value + + @property + def height_rule(self) -> WD_ROW_HEIGHT_RULE | None: + """Return the height rule of this cell as a member of the :ref:`WdRowHeightRule`. + + This value is |None| if no explicit height_rule is set. + """ + return self._tr.trHeight_hRule + + @height_rule.setter + def height_rule(self, value: WD_ROW_HEIGHT_RULE | None): + self._tr.trHeight_hRule = value + + @property + def table(self) -> Table: + """Reference to the |Table| object this row belongs to.""" + return self._parent.table + + @property + def _index(self) -> int: + """Index of this row in its table, starting from zero.""" + return self._tr.tr_idx + + +class _Rows(Parented): + """Sequence of |_Row| objects corresponding to the rows in a table. + + Supports ``len()``, iteration, indexed access, and slicing. + """ + + def __init__(self, tbl: CT_Tbl, parent: TableParent): + super(_Rows, self).__init__(parent) + self._parent = parent + self._tbl = tbl + + @overload + def __getitem__(self, idx: int) -> _Row: ... + + @overload + def __getitem__(self, idx: slice) -> list[_Row]: ... + + def __getitem__(self, idx: int | slice) -> _Row | list[_Row]: + """Provide indexed access, (e.g. `rows[0]` or `rows[1:3]`)""" + return list(self)[idx] + + def __iter__(self): + return (_Row(tr, self) for tr in self._tbl.tr_lst) + + def __len__(self): + return len(self._tbl.tr_lst) + + @property + def table(self) -> Table: + """Reference to the |Table| object this row collection belongs to.""" + return self._parent.table diff --git a/intelliaide/vendor/docx/templates/default-comments.xml b/intelliaide/vendor/docx/templates/default-comments.xml new file mode 100644 index 0000000..2a36ca9 --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-comments.xml @@ -0,0 +1,12 @@ + + diff --git a/intelliaide/vendor/docx/templates/default-docx-template/[Content_Types].xml b/intelliaide/vendor/docx/templates/default-docx-template/[Content_Types].xml new file mode 100644 index 0000000..a2e37e8 --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-docx-template/[Content_Types].xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/intelliaide/vendor/docx/templates/default-docx-template/_rels/.rels b/intelliaide/vendor/docx/templates/default-docx-template/_rels/.rels new file mode 100644 index 0000000..40657c7 --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-docx-template/_rels/.rels @@ -0,0 +1,7 @@ + + + + + + + diff --git a/intelliaide/vendor/docx/templates/default-docx-template/customXml/_rels/item1.xml.rels b/intelliaide/vendor/docx/templates/default-docx-template/customXml/_rels/item1.xml.rels new file mode 100644 index 0000000..8a65d21 --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-docx-template/customXml/_rels/item1.xml.rels @@ -0,0 +1,4 @@ + + + + diff --git a/intelliaide/vendor/docx/templates/default-docx-template/customXml/item1.xml b/intelliaide/vendor/docx/templates/default-docx-template/customXml/item1.xml new file mode 100644 index 0000000..94c8bab --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-docx-template/customXml/item1.xml @@ -0,0 +1,2 @@ + + diff --git a/intelliaide/vendor/docx/templates/default-docx-template/customXml/itemProps1.xml b/intelliaide/vendor/docx/templates/default-docx-template/customXml/itemProps1.xml new file mode 100644 index 0000000..6664fc0 --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-docx-template/customXml/itemProps1.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/intelliaide/vendor/docx/templates/default-docx-template/docProps/app.xml b/intelliaide/vendor/docx/templates/default-docx-template/docProps/app.xml new file mode 100644 index 0000000..bf89187 --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-docx-template/docProps/app.xml @@ -0,0 +1,36 @@ + + + + 0 + 1 + 0 + 0 + Microsoft Macintosh Word + 0 + 0 + 0 + false + + + + Title + + + 1 + + + + + + + + + + + false + 0 + false + + false + 14.0000 + diff --git a/intelliaide/vendor/docx/templates/default-docx-template/docProps/core.xml b/intelliaide/vendor/docx/templates/default-docx-template/docProps/core.xml new file mode 100644 index 0000000..55109c7 --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-docx-template/docProps/core.xml @@ -0,0 +1,13 @@ + + + + + python-docx + + generated by python-docx + + 1 + 2013-12-23T23:15:00Z + 2013-12-23T23:15:00Z + + diff --git a/intelliaide/vendor/docx/templates/default-docx-template/docProps/thumbnail.jpeg b/intelliaide/vendor/docx/templates/default-docx-template/docProps/thumbnail.jpeg new file mode 100644 index 0000000..d0c4f1f Binary files /dev/null and b/intelliaide/vendor/docx/templates/default-docx-template/docProps/thumbnail.jpeg differ diff --git a/intelliaide/vendor/docx/templates/default-docx-template/word/_rels/document.xml.rels b/intelliaide/vendor/docx/templates/default-docx-template/word/_rels/document.xml.rels new file mode 100644 index 0000000..de8ebbc --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-docx-template/word/_rels/document.xml.rels @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/intelliaide/vendor/docx/templates/default-docx-template/word/document.xml b/intelliaide/vendor/docx/templates/default-docx-template/word/document.xml new file mode 100644 index 0000000..3edb08e --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-docx-template/word/document.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/intelliaide/vendor/docx/templates/default-docx-template/word/fontTable.xml b/intelliaide/vendor/docx/templates/default-docx-template/word/fontTable.xml new file mode 100644 index 0000000..a2e0658 --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-docx-template/word/fontTable.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/intelliaide/vendor/docx/templates/default-docx-template/word/numbering.xml b/intelliaide/vendor/docx/templates/default-docx-template/word/numbering.xml new file mode 100644 index 0000000..cba57e2 --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-docx-template/word/numbering.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/intelliaide/vendor/docx/templates/default-docx-template/word/settings.xml b/intelliaide/vendor/docx/templates/default-docx-template/word/settings.xml new file mode 100644 index 0000000..1e84bf1 --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-docx-template/word/settings.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/intelliaide/vendor/docx/templates/default-docx-template/word/styles.xml b/intelliaide/vendor/docx/templates/default-docx-template/word/styles.xml new file mode 100644 index 0000000..46d1e98 --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-docx-template/word/styles.xml @@ -0,0 +1,11844 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/intelliaide/vendor/docx/templates/default-docx-template/word/stylesWithEffects.xml b/intelliaide/vendor/docx/templates/default-docx-template/word/stylesWithEffects.xml new file mode 100644 index 0000000..91c1734 --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-docx-template/word/stylesWithEffects.xml @@ -0,0 +1,11800 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/intelliaide/vendor/docx/templates/default-docx-template/word/theme/theme1.xml b/intelliaide/vendor/docx/templates/default-docx-template/word/theme/theme1.xml new file mode 100644 index 0000000..2b30074 --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-docx-template/word/theme/theme1.xml @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/intelliaide/vendor/docx/templates/default-docx-template/word/webSettings.xml b/intelliaide/vendor/docx/templates/default-docx-template/word/webSettings.xml new file mode 100644 index 0000000..189a20a --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-docx-template/word/webSettings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/intelliaide/vendor/docx/templates/default-footer.xml b/intelliaide/vendor/docx/templates/default-footer.xml new file mode 100644 index 0000000..944ae1a --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-footer.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/intelliaide/vendor/docx/templates/default-header.xml b/intelliaide/vendor/docx/templates/default-header.xml new file mode 100644 index 0000000..df6848c --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-header.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/intelliaide/vendor/docx/templates/default-settings.xml b/intelliaide/vendor/docx/templates/default-settings.xml new file mode 100644 index 0000000..fda1ade --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-settings.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + diff --git a/intelliaide/vendor/docx/templates/default-styles.xml b/intelliaide/vendor/docx/templates/default-styles.xml new file mode 100644 index 0000000..b8b97bc --- /dev/null +++ b/intelliaide/vendor/docx/templates/default-styles.xml @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/intelliaide/vendor/docx/templates/default.docx b/intelliaide/vendor/docx/templates/default.docx new file mode 100644 index 0000000..c22ff36 Binary files /dev/null and b/intelliaide/vendor/docx/templates/default.docx differ diff --git a/intelliaide/vendor/docx/text/__init__.py b/intelliaide/vendor/docx/text/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/intelliaide/vendor/docx/text/font.py b/intelliaide/vendor/docx/text/font.py new file mode 100644 index 0000000..0439f45 --- /dev/null +++ b/intelliaide/vendor/docx/text/font.py @@ -0,0 +1,428 @@ +"""Font-related proxy objects.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from docx.dml.color import ColorFormat +from docx.enum.text import WD_UNDERLINE +from docx.shared import ElementProxy, Emu + +if TYPE_CHECKING: + from docx.enum.text import WD_COLOR_INDEX + from docx.oxml.text.run import CT_R + from docx.shared import Length + + +class Font(ElementProxy): + """Proxy object for parent of a `` element and providing access to + character properties such as font name, font size, bold, and subscript.""" + + def __init__(self, r: CT_R, parent: Any | None = None): + super().__init__(r, parent) + self._element = r + self._r = r + + @property + def all_caps(self) -> bool | None: + """Read/write. + + Causes text in this font to appear in capital letters. + """ + return self._get_bool_prop("caps") + + @all_caps.setter + def all_caps(self, value: bool | None) -> None: + self._set_bool_prop("caps", value) + + @property + def bold(self) -> bool | None: + """Read/write. + + Causes text in this font to appear in bold. + """ + return self._get_bool_prop("b") + + @bold.setter + def bold(self, value: bool | None) -> None: + self._set_bool_prop("b", value) + + @property + def color(self): + """A |ColorFormat| object providing a way to get and set the text color for this + font.""" + return ColorFormat(self._element) + + @property + def complex_script(self) -> bool | None: + """Read/write tri-state value. + + When |True|, causes the characters in the run to be treated as complex script + regardless of their Unicode values. + """ + return self._get_bool_prop("cs") + + @complex_script.setter + def complex_script(self, value: bool | None) -> None: + self._set_bool_prop("cs", value) + + @property + def cs_bold(self) -> bool | None: + """Read/write tri-state value. + + When |True|, causes the complex script characters in the run to be displayed in + bold typeface. + """ + return self._get_bool_prop("bCs") + + @cs_bold.setter + def cs_bold(self, value: bool | None) -> None: + self._set_bool_prop("bCs", value) + + @property + def cs_italic(self) -> bool | None: + """Read/write tri-state value. + + When |True|, causes the complex script characters in the run to be displayed in + italic typeface. + """ + return self._get_bool_prop("iCs") + + @cs_italic.setter + def cs_italic(self, value: bool | None) -> None: + self._set_bool_prop("iCs", value) + + @property + def double_strike(self) -> bool | None: + """Read/write tri-state value. + + When |True|, causes the text in the run to appear with double strikethrough. + """ + return self._get_bool_prop("dstrike") + + @double_strike.setter + def double_strike(self, value: bool | None) -> None: + self._set_bool_prop("dstrike", value) + + @property + def emboss(self) -> bool | None: + """Read/write tri-state value. + + When |True|, causes the text in the run to appear as if raised off the page in + relief. + """ + return self._get_bool_prop("emboss") + + @emboss.setter + def emboss(self, value: bool | None) -> None: + self._set_bool_prop("emboss", value) + + @property + def hidden(self) -> bool | None: + """Read/write tri-state value. + + When |True|, causes the text in the run to be hidden from display, unless + applications settings force hidden text to be shown. + """ + return self._get_bool_prop("vanish") + + @hidden.setter + def hidden(self, value: bool | None) -> None: + self._set_bool_prop("vanish", value) + + @property + def highlight_color(self) -> WD_COLOR_INDEX | None: + """Color of highlighing applied or |None| if not highlighted.""" + rPr = self._element.rPr + if rPr is None: + return None + return rPr.highlight_val + + @highlight_color.setter + def highlight_color(self, value: WD_COLOR_INDEX | None): + rPr = self._element.get_or_add_rPr() + rPr.highlight_val = value + + @property + def italic(self) -> bool | None: + """Read/write tri-state value. + + When |True|, causes the text of the run to appear in italics. |None| indicates + the effective value is inherited from the style hierarchy. + """ + return self._get_bool_prop("i") + + @italic.setter + def italic(self, value: bool | None) -> None: + self._set_bool_prop("i", value) + + @property + def imprint(self) -> bool | None: + """Read/write tri-state value. + + When |True|, causes the text in the run to appear as if pressed into the page. + """ + return self._get_bool_prop("imprint") + + @imprint.setter + def imprint(self, value: bool | None) -> None: + self._set_bool_prop("imprint", value) + + @property + def math(self) -> bool | None: + """Read/write tri-state value. + + When |True|, specifies this run contains WML that should be handled as though it + was Office Open XML Math. + """ + return self._get_bool_prop("oMath") + + @math.setter + def math(self, value: bool | None) -> None: + self._set_bool_prop("oMath", value) + + @property + def name(self) -> str | None: + """The typeface name for this |Font|. + + Causes the text it controls to appear in the named font, if a matching font is + found. |None| indicates the typeface is inherited from the style hierarchy. + """ + rPr = self._element.rPr + if rPr is None: + return None + return rPr.rFonts_ascii + + @name.setter + def name(self, value: str | None) -> None: + rPr = self._element.get_or_add_rPr() + rPr.rFonts_ascii = value + rPr.rFonts_hAnsi = value + + @property + def no_proof(self) -> bool | None: + """Read/write tri-state value. + + When |True|, specifies that the contents of this run should not report any + errors when the document is scanned for spelling and grammar. + """ + return self._get_bool_prop("noProof") + + @no_proof.setter + def no_proof(self, value: bool | None) -> None: + self._set_bool_prop("noProof", value) + + @property + def outline(self) -> bool | None: + """Read/write tri-state value. + + When |True| causes the characters in the run to appear as if they have an + outline, by drawing a one pixel wide border around the inside and outside + borders of each character glyph. + """ + return self._get_bool_prop("outline") + + @outline.setter + def outline(self, value: bool | None) -> None: + self._set_bool_prop("outline", value) + + @property + def rtl(self) -> bool | None: + """Read/write tri-state value. + + When |True| causes the text in the run to have right-to-left characteristics. + """ + return self._get_bool_prop("rtl") + + @rtl.setter + def rtl(self, value: bool | None) -> None: + self._set_bool_prop("rtl", value) + + @property + def shadow(self) -> bool | None: + """Read/write tri-state value. + + When |True| causes the text in the run to appear as if each character has a + shadow. + """ + return self._get_bool_prop("shadow") + + @shadow.setter + def shadow(self, value: bool | None) -> None: + self._set_bool_prop("shadow", value) + + @property + def size(self) -> Length | None: + """Font height in English Metric Units (EMU). + + |None| indicates the font size should be inherited from the style hierarchy. + |Length| is a subclass of |int| having properties for convenient conversion into + points or other length units. The :class:`docx.shared.Pt` class allows + convenient specification of point values:: + + >>> font.size = Pt(24) + >>> font.size + 304800 + >>> font.size.pt + 24.0 + + """ + rPr = self._element.rPr + if rPr is None: + return None + return rPr.sz_val + + @size.setter + def size(self, emu: int | Length | None) -> None: + rPr = self._element.get_or_add_rPr() + rPr.sz_val = None if emu is None else Emu(emu) + + @property + def small_caps(self) -> bool | None: + """Read/write tri-state value. + + When |True| causes the lowercase characters in the run to appear as capital + letters two points smaller than the font size specified for the run. + """ + return self._get_bool_prop("smallCaps") + + @small_caps.setter + def small_caps(self, value: bool | None) -> None: + self._set_bool_prop("smallCaps", value) + + @property + def snap_to_grid(self) -> bool | None: + """Read/write tri-state value. + + When |True| causes the run to use the document grid characters per line settings + defined in the docGrid element when laying out the characters in this run. + """ + return self._get_bool_prop("snapToGrid") + + @snap_to_grid.setter + def snap_to_grid(self, value: bool | None) -> None: + self._set_bool_prop("snapToGrid", value) + + @property + def spec_vanish(self) -> bool | None: + """Read/write tri-state value. + + When |True|, specifies that the given run shall always behave as if it is + hidden, even when hidden text is being displayed in the current document. The + property has a very narrow, specialized use related to the table of contents. + Consult the spec (§17.3.2.36) for more details. + """ + return self._get_bool_prop("specVanish") + + @spec_vanish.setter + def spec_vanish(self, value: bool | None) -> None: + self._set_bool_prop("specVanish", value) + + @property + def strike(self) -> bool | None: + """Read/write tri-state value. + + When |True| causes the text in the run to appear with a single horizontal line + through the center of the line. + """ + return self._get_bool_prop("strike") + + @strike.setter + def strike(self, value: bool | None) -> None: + self._set_bool_prop("strike", value) + + @property + def subscript(self) -> bool | None: + """Boolean indicating whether the characters in this |Font| appear as subscript. + + |None| indicates the subscript/subscript value is inherited from the style + hierarchy. + """ + rPr = self._element.rPr + if rPr is None: + return None + return rPr.subscript + + @subscript.setter + def subscript(self, value: bool | None) -> None: + rPr = self._element.get_or_add_rPr() + rPr.subscript = value + + @property + def superscript(self) -> bool | None: + """Boolean indicating whether the characters in this |Font| appear as + superscript. + + |None| indicates the subscript/superscript value is inherited from the style + hierarchy. + """ + rPr = self._element.rPr + if rPr is None: + return None + return rPr.superscript + + @superscript.setter + def superscript(self, value: bool | None) -> None: + rPr = self._element.get_or_add_rPr() + rPr.superscript = value + + @property + def underline(self) -> bool | WD_UNDERLINE | None: + """The underline style for this |Font|. + + The value is one of |None|, |True|, |False|, or a member of :ref:`WdUnderline`. + + |None| indicates the font inherits its underline value from the style hierarchy. + |False| indicates no underline. |True| indicates single underline. The values + from :ref:`WdUnderline` are used to specify other outline styles such as double, + wavy, and dotted. + """ + rPr = self._element.rPr + if rPr is None: + return None + val = rPr.u_val + return ( + None + if val == WD_UNDERLINE.INHERITED + else True + if val == WD_UNDERLINE.SINGLE + else False + if val == WD_UNDERLINE.NONE + else val + ) + + @underline.setter + def underline(self, value: bool | WD_UNDERLINE | None) -> None: + rPr = self._element.get_or_add_rPr() + # -- works fine without these two mappings, but only because True == 1 and + # -- False == 0, which happen to match the mapping for WD_UNDERLINE.SINGLE + # -- and .NONE respectively. + val = ( + WD_UNDERLINE.SINGLE if value is True else WD_UNDERLINE.NONE if value is False else value + ) + rPr.u_val = val + + @property + def web_hidden(self) -> bool | None: + """Read/write tri-state value. + + When |True|, specifies that the contents of this run shall be hidden when the + document is displayed in web page view. + """ + return self._get_bool_prop("webHidden") + + @web_hidden.setter + def web_hidden(self, value: bool | None) -> None: + self._set_bool_prop("webHidden", value) + + def _get_bool_prop(self, name: str) -> bool | None: + """Return the value of boolean child of `w:rPr` having `name`.""" + rPr = self._element.rPr + if rPr is None: + return None + return rPr._get_bool_val(name) # pyright: ignore[reportPrivateUsage] + + def _set_bool_prop(self, name: str, value: bool | None): + """Assign `value` to the boolean child `name` of `w:rPr`.""" + rPr = self._element.get_or_add_rPr() + rPr._set_bool_val(name, value) # pyright: ignore[reportPrivateUsage] diff --git a/intelliaide/vendor/docx/text/hyperlink.py b/intelliaide/vendor/docx/text/hyperlink.py new file mode 100644 index 0000000..a23df1c --- /dev/null +++ b/intelliaide/vendor/docx/text/hyperlink.py @@ -0,0 +1,121 @@ +"""Hyperlink-related proxy objects for python-docx, Hyperlink in particular. + +A hyperlink occurs in a paragraph, at the same level as a Run, and a hyperlink itself +contains runs, which is where the visible text of the hyperlink is stored. So it's kind +of in-between, less than a paragraph and more than a run. So it gets its own module. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from docx.shared import Parented +from docx.text.run import Run + +if TYPE_CHECKING: + import docx.types as t + from docx.oxml.text.hyperlink import CT_Hyperlink + + +class Hyperlink(Parented): + """Proxy object wrapping a `` element. + + A hyperlink occurs as a child of a paragraph, at the same level as a Run. A + hyperlink itself contains runs, which is where the visible text of the hyperlink is + stored. + """ + + def __init__(self, hyperlink: CT_Hyperlink, parent: t.ProvidesStoryPart): + super().__init__(parent) + self._parent = parent + self._hyperlink = self._element = hyperlink + + @property + def address(self) -> str: + """The "URL" of the hyperlink (but not necessarily a web link). + + While commonly a web link like "https://google.com" the hyperlink address can + take a variety of forms including "internal links" to bookmarked locations + within the document. When this hyperlink is an internal "jump" to for example a + heading from the table-of-contents (TOC), the address is blank. The bookmark + reference (like "_Toc147925734") is stored in the `.fragment` property. + """ + rId = self._hyperlink.rId + return self._parent.part.rels[rId].target_ref if rId else "" + + @property + def contains_page_break(self) -> bool: + """True when the text of this hyperlink is broken across page boundaries. + + This is not uncommon and can happen for example when the hyperlink text is + multiple words and occurs in the last line of a page. Theoretically, a hyperlink + can contain more than one page break but that would be extremely uncommon in + practice. Still, this value should be understood to mean that "one-or-more" + rendered page breaks are present. + """ + return bool(self._hyperlink.lastRenderedPageBreaks) + + @property + def fragment(self) -> str: + """Reference like `#glossary` at end of URL that refers to a sub-resource. + + Note that this value does not include the fragment-separator character ("#"). + + This value is known as a "named anchor" in an HTML context and "anchor" in the + MS API, but an "anchor" element (``) represents a full hyperlink in HTML so + we avoid confusion by using the more precise RFC 3986 naming "URI fragment". + + These are also used to refer to bookmarks within the same document, in which + case the `.address` value with be blank ("") and this property will hold a + value like "_Toc147925734". + + To reliably get an entire web URL you will need to concatenate this with the + `.address` value, separated by "#" when both are present. Consider using the + `.url` property for that purpose. + + Word sometimes stores a fragment in this property (an XML attribute) and + sometimes with the address, depending on how the URL is inserted, so don't + depend on this field being empty to indicate no fragment is present. + """ + return self._hyperlink.anchor or "" + + @property + def runs(self) -> list[Run]: + """List of |Run| instances in this hyperlink. + + Together these define the visible text of the hyperlink. The text of a hyperlink + is typically contained in a single run will be broken into multiple runs if for + example part of the hyperlink is bold or the text was changed after the document + was saved. + """ + return [Run(r, self._parent) for r in self._hyperlink.r_lst] + + @property + def text(self) -> str: + """String formed by concatenating the text of each run in the hyperlink. + + Tabs and line breaks in the XML are mapped to ``\\t`` and ``\\n`` characters + respectively. Note that rendered page-breaks can occur within a hyperlink but + they are not reflected in this text. + """ + return self._hyperlink.text + + @property + def url(self) -> str: + """Convenience property to get web URLs from hyperlinks that contain them. + + This value is the empty string ("") when there is no address portion, so its + boolean value can also be used to distinguish external URIs from internal "jump" + hyperlinks like those found in a table-of-contents. + + Note that this value may also be a link to a file, so if you only want web-urls + you'll need to check for a protocol prefix like `https://`. + + When both an address and fragment are present, the return value joins the two + separated by the fragment-separator hash ("#"). Otherwise this value is the same + as that of the `.address` property. + """ + address, fragment = self.address, self.fragment + if not address: + return "" + return f"{address}#{fragment}" if fragment else address diff --git a/intelliaide/vendor/docx/text/pagebreak.py b/intelliaide/vendor/docx/text/pagebreak.py new file mode 100644 index 0000000..0977cce --- /dev/null +++ b/intelliaide/vendor/docx/text/pagebreak.py @@ -0,0 +1,104 @@ +"""Proxy objects related to rendered page-breaks.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from docx.oxml.text.pagebreak import CT_LastRenderedPageBreak +from docx.shared import Parented + +if TYPE_CHECKING: + import docx.types as t + from docx.text.paragraph import Paragraph + + +class RenderedPageBreak(Parented): + """A page-break inserted by Word during page-layout for print or display purposes. + + This usually does not correspond to a "hard" page-break inserted by the document + author, rather just that Word ran out of room on one page and needed to start + another. The position of these can change depending on the printer and page-size, as + well as margins, etc. They also will change in response to edits, but not until Word + loads and saves the document. + + Note these are never inserted by `python-docx` because it has no rendering function. + These are generally only useful for text-extraction of existing documents when + `python-docx` is being used solely as a document "reader". + + NOTE: a rendered page-break can occur within a hyperlink; consider a multi-word + hyperlink like "excellent Wikipedia article on LLMs" that happens to fall close to + the end of the last line on a page such that the page breaks between "Wikipedia" and + "article". In such a "page-breaks-in-hyperlink" case, THESE METHODS WILL "MOVE" THE + PAGE-BREAK to occur after the hyperlink, such that the entire hyperlink appears in + the paragraph returned by `.preceding_paragraph_fragment`. While this places the + "tail" text of the hyperlink on the "wrong" page, it avoids having two hyperlinks + each with a fragment of the actual text and pointing to the same address. + """ + + def __init__( + self, + lastRenderedPageBreak: CT_LastRenderedPageBreak, + parent: t.ProvidesStoryPart, + ): + super().__init__(parent) + self._element = lastRenderedPageBreak + self._lastRenderedPageBreak = lastRenderedPageBreak + + @property + def preceding_paragraph_fragment(self) -> Paragraph | None: + """A "loose" paragraph containing the content preceding this page-break. + + Compare `.following_paragraph_fragment` as these two are intended to be used + together. + + This value is `None` when no content precedes this page-break. This case is + common and occurs whenever a page breaks on an even paragraph boundary. + Returning `None` for this case avoids "inserting" a non-existent paragraph into + the content stream. Note that content can include DrawingML items like images or + charts. + + Note the returned paragraph *is divorced from the document body*. Any changes + made to it will not be reflected in the document. It is intended to provide a + familiar container (`Paragraph`) to interrogate for the content preceding this + page-break in the paragraph in which it occured. + + Contains the entire hyperlink when this break occurs within a hyperlink. + """ + if self._lastRenderedPageBreak.precedes_all_content: + return None + + from docx.text.paragraph import Paragraph + + return Paragraph(self._lastRenderedPageBreak.preceding_fragment_p, self._parent) + + @property + def following_paragraph_fragment(self) -> Paragraph | None: + """A "loose" paragraph containing the content following this page-break. + + HAS POTENTIALLY SURPRISING BEHAVIORS so read carefully to be sure this is what + you want. This is primarily targeted toward text-extraction use-cases for which + precisely associating text with the page it occurs on is important. + + Compare `.preceding_paragraph_fragment` as these two are intended to be used + together. + + This value is `None` when no content follows this page-break. This case is + unlikely to occur in practice because Word places even-paragraph-boundary + page-breaks on the paragraph *following* the page-break. Still, it is possible + and must be checked for. Returning `None` for this case avoids "inserting" an + extra, non-existent paragraph into the content stream. Note that content can + include DrawingML items like images or charts, not just text. + + The returned paragraph *is divorced from the document body*. Any changes made to + it will not be reflected in the document. It is intended to provide a container + (`Paragraph`) with familiar properties and methods that can be used to + characterize the paragraph content following a mid-paragraph page-break. + + Contains no portion of the hyperlink when this break occurs within a hyperlink. + """ + if self._lastRenderedPageBreak.follows_all_content: + return None + + from docx.text.paragraph import Paragraph + + return Paragraph(self._lastRenderedPageBreak.following_fragment_p, self._parent) diff --git a/intelliaide/vendor/docx/text/paragraph.py b/intelliaide/vendor/docx/text/paragraph.py new file mode 100644 index 0000000..234ea66 --- /dev/null +++ b/intelliaide/vendor/docx/text/paragraph.py @@ -0,0 +1,173 @@ +"""Paragraph-related proxy types.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Iterator, List, cast + +from docx.enum.style import WD_STYLE_TYPE +from docx.oxml.text.run import CT_R +from docx.shared import StoryChild +from docx.styles.style import ParagraphStyle +from docx.text.hyperlink import Hyperlink +from docx.text.pagebreak import RenderedPageBreak +from docx.text.parfmt import ParagraphFormat +from docx.text.run import Run + +if TYPE_CHECKING: + import docx.types as t + from docx.enum.text import WD_PARAGRAPH_ALIGNMENT + from docx.oxml.text.paragraph import CT_P + from docx.styles.style import CharacterStyle + + +class Paragraph(StoryChild): + """Proxy object wrapping a `` element.""" + + def __init__(self, p: CT_P, parent: t.ProvidesStoryPart): + super(Paragraph, self).__init__(parent) + self._p = self._element = p + + def add_run(self, text: str | None = None, style: str | CharacterStyle | None = None) -> Run: + """Append run containing `text` and having character-style `style`. + + `text` can contain tab (``\\t``) characters, which are converted to the + appropriate XML form for a tab. `text` can also include newline (``\\n``) or + carriage return (``\\r``) characters, each of which is converted to a line + break. When `text` is `None`, the new run is empty. + """ + r = self._p.add_r() + run = Run(r, self) + if text: + run.text = text + if style: + run.style = style + return run + + @property + def alignment(self) -> WD_PARAGRAPH_ALIGNMENT | None: + """A member of the :ref:`WdParagraphAlignment` enumeration specifying the + justification setting for this paragraph. + + A value of |None| indicates the paragraph has no directly-applied alignment + value and will inherit its alignment value from its style hierarchy. Assigning + |None| to this property removes any directly-applied alignment value. + """ + return self._p.alignment + + @alignment.setter + def alignment(self, value: WD_PARAGRAPH_ALIGNMENT): + self._p.alignment = value + + def clear(self): + """Return this same paragraph after removing all its content. + + Paragraph-level formatting, such as style, is preserved. + """ + self._p.clear_content() + return self + + @property + def contains_page_break(self) -> bool: + """`True` when one or more rendered page-breaks occur in this paragraph.""" + return bool(self._p.lastRenderedPageBreaks) + + @property + def hyperlinks(self) -> List[Hyperlink]: + """A |Hyperlink| instance for each hyperlink in this paragraph.""" + return [Hyperlink(hyperlink, self) for hyperlink in self._p.hyperlink_lst] + + def insert_paragraph_before( + self, text: str | None = None, style: str | ParagraphStyle | None = None + ) -> Paragraph: + """Return a newly created paragraph, inserted directly before this paragraph. + + If `text` is supplied, the new paragraph contains that text in a single run. If + `style` is provided, that style is assigned to the new paragraph. + """ + paragraph = self._insert_paragraph_before() + if text: + paragraph.add_run(text) + if style is not None: + paragraph.style = style + return paragraph + + def iter_inner_content(self) -> Iterator[Run | Hyperlink]: + """Generate the runs and hyperlinks in this paragraph, in the order they appear. + + The content in a paragraph consists of both runs and hyperlinks. This method + allows accessing each of those separately, in document order, for when the + precise position of the hyperlink within the paragraph text is important. Note + that a hyperlink itself contains runs. + """ + for r_or_hlink in self._p.inner_content_elements: + yield ( + Run(r_or_hlink, self) + if isinstance(r_or_hlink, CT_R) + else Hyperlink(r_or_hlink, self) + ) + + @property + def paragraph_format(self): + """The |ParagraphFormat| object providing access to the formatting properties + for this paragraph, such as line spacing and indentation.""" + return ParagraphFormat(self._element) + + @property + def rendered_page_breaks(self) -> List[RenderedPageBreak]: + """All rendered page-breaks in this paragraph. + + Most often an empty list, sometimes contains one page-break, but can contain + more than one is rare or contrived cases. + """ + return [RenderedPageBreak(lrpb, self) for lrpb in self._p.lastRenderedPageBreaks] + + @property + def runs(self) -> List[Run]: + """Sequence of |Run| instances corresponding to the elements in this + paragraph.""" + return [Run(r, self) for r in self._p.r_lst] + + @property + def style(self) -> ParagraphStyle | None: + """Read/Write. + + |_ParagraphStyle| object representing the style assigned to this paragraph. If + no explicit style is assigned to this paragraph, its value is the default + paragraph style for the document. A paragraph style name can be assigned in lieu + of a paragraph style object. Assigning |None| removes any applied style, making + its effective value the default paragraph style for the document. + """ + style_id = self._p.style + style = self.part.get_style(style_id, WD_STYLE_TYPE.PARAGRAPH) + return cast(ParagraphStyle, style) + + @style.setter + def style(self, style_or_name: str | ParagraphStyle | None): + style_id = self.part.get_style_id(style_or_name, WD_STYLE_TYPE.PARAGRAPH) + self._p.style = style_id + + @property + def text(self) -> str: + """The textual content of this paragraph. + + The text includes the visible-text portion of any hyperlinks in the paragraph. + Tabs and line breaks in the XML are mapped to ``\\t`` and ``\\n`` characters + respectively. + + Assigning text to this property causes all existing paragraph content to be + replaced with a single run containing the assigned text. A ``\\t`` character in + the text is mapped to a ```` element and each ``\\n`` or ``\\r`` + character is mapped to a line break. Paragraph-level formatting, such as style, + is preserved. All run-level formatting, such as bold or italic, is removed. + """ + return self._p.text + + @text.setter + def text(self, text: str | None): + self.clear() + self.add_run(text) + + def _insert_paragraph_before(self): + """Return a newly created paragraph, inserted directly before this paragraph.""" + p = self._p.add_p_before() + return Paragraph(p, self._parent) diff --git a/intelliaide/vendor/docx/text/parfmt.py b/intelliaide/vendor/docx/text/parfmt.py new file mode 100644 index 0000000..ea37437 --- /dev/null +++ b/intelliaide/vendor/docx/text/parfmt.py @@ -0,0 +1,286 @@ +"""Paragraph-related proxy types.""" + +from docx.enum.text import WD_LINE_SPACING +from docx.shared import ElementProxy, Emu, Length, Pt, Twips, lazyproperty +from docx.text.tabstops import TabStops + + +class ParagraphFormat(ElementProxy): + """Provides access to paragraph formatting such as justification, indentation, line + spacing, space before and after, and widow/orphan control.""" + + @property + def alignment(self): + """A member of the :ref:`WdParagraphAlignment` enumeration specifying the + justification setting for this paragraph. + + A value of |None| indicates paragraph alignment is inherited from the style + hierarchy. + """ + pPr = self._element.pPr + if pPr is None: + return None + return pPr.jc_val + + @alignment.setter + def alignment(self, value): + pPr = self._element.get_or_add_pPr() + pPr.jc_val = value + + @property + def first_line_indent(self): + """|Length| value specifying the relative difference in indentation for the + first line of the paragraph. + + A positive value causes the first line to be indented. A negative value produces + a hanging indent. |None| indicates first line indentation is inherited from the + style hierarchy. + """ + pPr = self._element.pPr + if pPr is None: + return None + return pPr.first_line_indent + + @first_line_indent.setter + def first_line_indent(self, value): + pPr = self._element.get_or_add_pPr() + pPr.first_line_indent = value + + @property + def keep_together(self): + """|True| if the paragraph should be kept "in one piece" and not broken across a + page boundary when the document is rendered. + + |None| indicates its effective value is inherited from the style hierarchy. + """ + pPr = self._element.pPr + if pPr is None: + return None + return pPr.keepLines_val + + @keep_together.setter + def keep_together(self, value): + self._element.get_or_add_pPr().keepLines_val = value + + @property + def keep_with_next(self): + """|True| if the paragraph should be kept on the same page as the subsequent + paragraph when the document is rendered. + + For example, this property could be used to keep a section heading on the same + page as its first paragraph. |None| indicates its effective value is inherited + from the style hierarchy. + """ + pPr = self._element.pPr + if pPr is None: + return None + return pPr.keepNext_val + + @keep_with_next.setter + def keep_with_next(self, value): + self._element.get_or_add_pPr().keepNext_val = value + + @property + def left_indent(self): + """|Length| value specifying the space between the left margin and the left side + of the paragraph. + + |None| indicates the left indent value is inherited from the style hierarchy. + Use an |Inches| value object as a convenient way to apply indentation in units + of inches. + """ + pPr = self._element.pPr + if pPr is None: + return None + return pPr.ind_left + + @left_indent.setter + def left_indent(self, value): + pPr = self._element.get_or_add_pPr() + pPr.ind_left = value + + @property + def line_spacing(self): + """|float| or |Length| value specifying the space between baselines in + successive lines of the paragraph. + + A value of |None| indicates line spacing is inherited from the style hierarchy. + A float value, e.g. ``2.0`` or ``1.75``, indicates spacing is applied in + multiples of line heights. A |Length| value such as ``Pt(12)`` indicates spacing + is a fixed height. The |Pt| value class is a convenient way to apply line + spacing in units of points. Assigning |None| resets line spacing to inherit from + the style hierarchy. + """ + pPr = self._element.pPr + if pPr is None: + return None + return self._line_spacing(pPr.spacing_line, pPr.spacing_lineRule) + + @line_spacing.setter + def line_spacing(self, value): + pPr = self._element.get_or_add_pPr() + if value is None: + pPr.spacing_line = None + pPr.spacing_lineRule = None + elif isinstance(value, Length): + pPr.spacing_line = value + if pPr.spacing_lineRule != WD_LINE_SPACING.AT_LEAST: + pPr.spacing_lineRule = WD_LINE_SPACING.EXACTLY + else: + pPr.spacing_line = Emu(value * Twips(240)) + pPr.spacing_lineRule = WD_LINE_SPACING.MULTIPLE + + @property + def line_spacing_rule(self): + """A member of the :ref:`WdLineSpacing` enumeration indicating how the value of + :attr:`line_spacing` should be interpreted. + + Assigning any of the :ref:`WdLineSpacing` members :attr:`SINGLE`, + :attr:`DOUBLE`, or :attr:`ONE_POINT_FIVE` will cause the value of + :attr:`line_spacing` to be updated to produce the corresponding line spacing. + """ + pPr = self._element.pPr + if pPr is None: + return None + return self._line_spacing_rule(pPr.spacing_line, pPr.spacing_lineRule) + + @line_spacing_rule.setter + def line_spacing_rule(self, value): + pPr = self._element.get_or_add_pPr() + if value == WD_LINE_SPACING.SINGLE: + pPr.spacing_line = Twips(240) + pPr.spacing_lineRule = WD_LINE_SPACING.MULTIPLE + elif value == WD_LINE_SPACING.ONE_POINT_FIVE: + pPr.spacing_line = Twips(360) + pPr.spacing_lineRule = WD_LINE_SPACING.MULTIPLE + elif value == WD_LINE_SPACING.DOUBLE: + pPr.spacing_line = Twips(480) + pPr.spacing_lineRule = WD_LINE_SPACING.MULTIPLE + else: + pPr.spacing_lineRule = value + + @property + def page_break_before(self): + """|True| if the paragraph should appear at the top of the page following the + prior paragraph. + + |None| indicates its effective value is inherited from the style hierarchy. + """ + pPr = self._element.pPr + if pPr is None: + return None + return pPr.pageBreakBefore_val + + @page_break_before.setter + def page_break_before(self, value): + self._element.get_or_add_pPr().pageBreakBefore_val = value + + @property + def right_indent(self): + """|Length| value specifying the space between the right margin and the right + side of the paragraph. + + |None| indicates the right indent value is inherited from the style hierarchy. + Use a |Cm| value object as a convenient way to apply indentation in units of + centimeters. + """ + pPr = self._element.pPr + if pPr is None: + return None + return pPr.ind_right + + @right_indent.setter + def right_indent(self, value): + pPr = self._element.get_or_add_pPr() + pPr.ind_right = value + + @property + def space_after(self): + """|Length| value specifying the spacing to appear between this paragraph and + the subsequent paragraph. + + |None| indicates this value is inherited from the style hierarchy. |Length| + objects provide convenience properties, such as :attr:`~.Length.pt` and + :attr:`~.Length.inches`, that allow easy conversion to various length units. + """ + pPr = self._element.pPr + if pPr is None: + return None + return pPr.spacing_after + + @space_after.setter + def space_after(self, value): + self._element.get_or_add_pPr().spacing_after = value + + @property + def space_before(self): + """|Length| value specifying the spacing to appear between this paragraph and + the prior paragraph. + + |None| indicates this value is inherited from the style hierarchy. |Length| + objects provide convenience properties, such as :attr:`~.Length.pt` and + :attr:`~.Length.cm`, that allow easy conversion to various length units. + """ + pPr = self._element.pPr + if pPr is None: + return None + return pPr.spacing_before + + @space_before.setter + def space_before(self, value): + self._element.get_or_add_pPr().spacing_before = value + + @lazyproperty + def tab_stops(self): + """|TabStops| object providing access to the tab stops defined for this + paragraph format.""" + pPr = self._element.get_or_add_pPr() + return TabStops(pPr) + + @property + def widow_control(self): + """|True| if the first and last lines in the paragraph remain on the same page + as the rest of the paragraph when Word repaginates the document. + + |None| indicates its effective value is inherited from the style hierarchy. + """ + pPr = self._element.pPr + if pPr is None: + return None + return pPr.widowControl_val + + @widow_control.setter + def widow_control(self, value): + self._element.get_or_add_pPr().widowControl_val = value + + @staticmethod + def _line_spacing(spacing_line, spacing_lineRule): + """Return the line spacing value calculated from the combination of + `spacing_line` and `spacing_lineRule`. + + Returns a |float| number of lines when `spacing_lineRule` is + ``WD_LINE_SPACING.MULTIPLE``, otherwise a |Length| object of absolute line + height is returned. Returns |None| when `spacing_line` is |None|. + """ + if spacing_line is None: + return None + if spacing_lineRule == WD_LINE_SPACING.MULTIPLE: + return spacing_line / Pt(12) + return spacing_line + + @staticmethod + def _line_spacing_rule(line, lineRule): + """Return the line spacing rule value calculated from the combination of `line` + and `lineRule`. + + Returns special members of the :ref:`WdLineSpacing` enumeration when line + spacing is single, double, or 1.5 lines. + """ + if lineRule == WD_LINE_SPACING.MULTIPLE: + if line == Twips(240): + return WD_LINE_SPACING.SINGLE + if line == Twips(360): + return WD_LINE_SPACING.ONE_POINT_FIVE + if line == Twips(480): + return WD_LINE_SPACING.DOUBLE + return lineRule diff --git a/intelliaide/vendor/docx/text/run.py b/intelliaide/vendor/docx/text/run.py new file mode 100644 index 0000000..57ea31f --- /dev/null +++ b/intelliaide/vendor/docx/text/run.py @@ -0,0 +1,257 @@ +"""Run-related proxy objects for python-docx, Run in particular.""" + +from __future__ import annotations + +from typing import IO, TYPE_CHECKING, Iterator, cast + +from docx.drawing import Drawing +from docx.enum.style import WD_STYLE_TYPE +from docx.enum.text import WD_BREAK +from docx.oxml.drawing import CT_Drawing +from docx.oxml.text.pagebreak import CT_LastRenderedPageBreak +from docx.shape import InlineShape +from docx.shared import StoryChild +from docx.styles.style import CharacterStyle +from docx.text.font import Font +from docx.text.pagebreak import RenderedPageBreak + +if TYPE_CHECKING: + import docx.types as t + from docx.enum.text import WD_UNDERLINE + from docx.oxml.text.run import CT_R, CT_Text + from docx.shared import Length + + +class Run(StoryChild): + """Proxy object wrapping `` element. + + Several of the properties on Run take a tri-state value, |True|, |False|, or |None|. + |True| and |False| correspond to on and off respectively. |None| indicates the + property is not specified directly on the run and its effective value is taken from + the style hierarchy. + """ + + def __init__(self, r: CT_R, parent: t.ProvidesStoryPart): + super().__init__(parent) + self._r = self._element = self.element = r + + def add_break(self, break_type: WD_BREAK = WD_BREAK.LINE): + """Add a break element of `break_type` to this run. + + `break_type` can take the values `WD_BREAK.LINE`, `WD_BREAK.PAGE`, and + `WD_BREAK.COLUMN` where `WD_BREAK` is imported from `docx.enum.text`. + `break_type` defaults to `WD_BREAK.LINE`. + """ + type_, clear = { + WD_BREAK.LINE: (None, None), + WD_BREAK.PAGE: ("page", None), + WD_BREAK.COLUMN: ("column", None), + WD_BREAK.LINE_CLEAR_LEFT: ("textWrapping", "left"), + WD_BREAK.LINE_CLEAR_RIGHT: ("textWrapping", "right"), + WD_BREAK.LINE_CLEAR_ALL: ("textWrapping", "all"), + }[break_type] + br = self._r.add_br() + if type_ is not None: + br.type = type_ + if clear is not None: + br.clear = clear + + def add_picture( + self, + image_path_or_stream: str | IO[bytes], + width: int | Length | None = None, + height: int | Length | None = None, + ) -> InlineShape: + """Return |InlineShape| containing image identified by `image_path_or_stream`. + + The picture is added to the end of this run. + + `image_path_or_stream` can be a path (a string) or a file-like object containing + a binary image. + + If neither width nor height is specified, the picture appears at + its native size. If only one is specified, it is used to compute a scaling + factor that is then applied to the unspecified dimension, preserving the aspect + ratio of the image. The native size of the picture is calculated using the dots- + per-inch (dpi) value specified in the image file, defaulting to 72 dpi if no + value is specified, as is often the case. + """ + inline = self.part.new_pic_inline(image_path_or_stream, width, height) + self._r.add_drawing(inline) + return InlineShape(inline) + + def add_tab(self) -> None: + """Add a ```` element at the end of the run, which Word interprets as a + tab character.""" + self._r.add_tab() + + def add_text(self, text: str): + """Returns a newly appended |_Text| object (corresponding to a new ```` + child element) to the run, containing `text`. + + Compare with the possibly more friendly approach of assigning text to the + :attr:`Run.text` property. + """ + t = self._r.add_t(text) + return _Text(t) + + @property + def bold(self) -> bool | None: + """Read/write tri-state value. + + When |True|, causes the text of the run to appear in bold face. When |False|, + the text unconditionally appears non-bold. When |None| the bold setting for this + run is inherited from the style hierarchy. + """ + return self.font.bold + + @bold.setter + def bold(self, value: bool | None): + self.font.bold = value + + def clear(self): + """Return reference to this run after removing all its content. + + All run formatting is preserved. + """ + self._r.clear_content() + return self + + @property + def contains_page_break(self) -> bool: + """`True` when one or more rendered page-breaks occur in this run. + + Note that "hard" page-breaks inserted by the author are not included. A hard + page-break gives rise to a rendered page-break in the right position so if those + were included that page-break would be "double-counted". + + It would be very rare for multiple rendered page-breaks to occur in a single + run, but it is possible. + """ + return bool(self._r.lastRenderedPageBreaks) + + @property + def font(self) -> Font: + """The |Font| object providing access to the character formatting properties for + this run, such as font name and size.""" + return Font(self._element) + + @property + def italic(self) -> bool | None: + """Read/write tri-state value. + + When |True|, causes the text of the run to appear in italics. When |False|, the + text unconditionally appears non-italic. When |None| the italic setting for this + run is inherited from the style hierarchy. + """ + return self.font.italic + + @italic.setter + def italic(self, value: bool | None): + self.font.italic = value + + def iter_inner_content(self) -> Iterator[str | Drawing | RenderedPageBreak]: + """Generate the content-items in this run in the order they appear. + + NOTE: only content-types currently supported by `python-docx` are generated. In + this version, that is text and rendered page-breaks. Drawing is included but + currently only provides access to its XML element (CT_Drawing) on its + `._drawing` attribute. `Drawing` attributes and methods may be expanded in + future releases. + + There are a number of element-types that can appear inside a run, but most of + those (w:br, w:cr, w:noBreakHyphen, w:t, w:tab) have a clear plain-text + equivalent. Any contiguous range of such elements is generated as a single + `str`. Rendered page-break and drawing elements are generated individually. Any + other elements are ignored. + """ + for item in self._r.inner_content_items: + if isinstance(item, str): + yield item + elif isinstance(item, CT_LastRenderedPageBreak): + yield RenderedPageBreak(item, self) + elif isinstance(item, CT_Drawing): # pyright: ignore[reportUnnecessaryIsInstance] + yield Drawing(item, self) + + def mark_comment_range(self, last_run: Run, comment_id: int) -> None: + """Mark the range of runs from this run to `last_run` (inclusive) as belonging to a comment. + + `comment_id` identfies the comment that references this range. + """ + # -- insert `w:commentRangeStart` with `comment_id` before this (first) run -- + self._r.insert_comment_range_start_above(comment_id) + + # -- insert `w:commentRangeEnd` and `w:commentReference` run with `comment_id` after + # -- `last_run` + last_run._r.insert_comment_range_end_and_reference_below(comment_id) + + @property + def style(self) -> CharacterStyle: + """Read/write. + + A |CharacterStyle| object representing the character style applied to this run. + The default character style for the document (often `Default Character Font`) is + returned if the run has no directly-applied character style. Setting this + property to |None| removes any directly-applied character style. + """ + style_id = self._r.style + return cast(CharacterStyle, self.part.get_style(style_id, WD_STYLE_TYPE.CHARACTER)) + + @style.setter + def style(self, style_or_name: str | CharacterStyle | None): + style_id = self.part.get_style_id(style_or_name, WD_STYLE_TYPE.CHARACTER) + self._r.style = style_id + + @property + def text(self) -> str: + """String formed by concatenating the text equivalent of each run. + + Each `` element adds the text characters it contains. A `` element + adds a `\\t` character. A `` or `` element each add a `\\n` + character. Note that a `` element can indicate a page break or column + break as well as a line break. Only line-break `` elements translate to + a `\\n` character. Others are ignored. All other content child elements, such as + ``, are ignored. + + Assigning text to this property has the reverse effect, translating each `\\t` + character to a `` element and each `\\n` or `\\r` character to a + `` element. Any existing run content is replaced. Run formatting is + preserved. + """ + return self._r.text + + @text.setter + def text(self, text: str): + self._r.text = text + + @property + def underline(self) -> bool | WD_UNDERLINE | None: + """The underline style for this |Run|. + + Value is one of |None|, |True|, |False|, or a member of :ref:`WdUnderline`. + + A value of |None| indicates the run has no directly-applied underline value and + so will inherit the underline value of its containing paragraph. Assigning + |None| to this property removes any directly-applied underline value. + + A value of |False| indicates a directly-applied setting of no underline, + overriding any inherited value. + + A value of |True| indicates single underline. + + The values from :ref:`WdUnderline` are used to specify other outline styles such + as double, wavy, and dotted. + """ + return self.font.underline + + @underline.setter + def underline(self, value: bool | WD_UNDERLINE | None): + self.font.underline = value + + +class _Text: + """Proxy object wrapping `` element.""" + + def __init__(self, t_elm: CT_Text): + super(_Text, self).__init__() + self._t = t_elm diff --git a/intelliaide/vendor/docx/text/tabstops.py b/intelliaide/vendor/docx/text/tabstops.py new file mode 100644 index 0000000..0f8c22c --- /dev/null +++ b/intelliaide/vendor/docx/text/tabstops.py @@ -0,0 +1,123 @@ +"""Tabstop-related proxy types.""" + +from docx.enum.text import WD_TAB_ALIGNMENT, WD_TAB_LEADER +from docx.shared import ElementProxy + + +class TabStops(ElementProxy): + """A sequence of |TabStop| objects providing access to the tab stops of a paragraph + or paragraph style. + + Supports iteration, indexed access, del, and len(). It is accesed using the + :attr:`~.ParagraphFormat.tab_stops` property of ParagraphFormat; it is not intended + to be constructed directly. + """ + + def __init__(self, element): + super(TabStops, self).__init__(element, None) + self._pPr = element + + def __delitem__(self, idx): + """Remove the tab at offset `idx` in this sequence.""" + tabs = self._pPr.tabs + try: + tabs.remove(tabs[idx]) + except (AttributeError, IndexError): + raise IndexError("tab index out of range") + + if len(tabs) == 0: + self._pPr.remove(tabs) + + def __getitem__(self, idx): + """Enables list-style access by index.""" + tabs = self._pPr.tabs + if tabs is None: + raise IndexError("TabStops object is empty") + tab = tabs.tab_lst[idx] + return TabStop(tab) + + def __iter__(self): + """Generate a TabStop object for each of the w:tab elements, in XML document + order.""" + tabs = self._pPr.tabs + if tabs is not None: + for tab in tabs.tab_lst: + yield TabStop(tab) + + def __len__(self): + tabs = self._pPr.tabs + if tabs is None: + return 0 + return len(tabs.tab_lst) + + def add_tab_stop(self, position, alignment=WD_TAB_ALIGNMENT.LEFT, leader=WD_TAB_LEADER.SPACES): + """Add a new tab stop at `position`, a |Length| object specifying the location + of the tab stop relative to the paragraph edge. + + A negative `position` value is valid and appears in hanging indentation. Tab + alignment defaults to left, but may be specified by passing a member of the + :ref:`WdTabAlignment` enumeration as `alignment`. An optional leader character + can be specified by passing a member of the :ref:`WdTabLeader` enumeration as + `leader`. + """ + tabs = self._pPr.get_or_add_tabs() + tab = tabs.insert_tab_in_order(position, alignment, leader) + return TabStop(tab) + + def clear_all(self): + """Remove all custom tab stops.""" + self._pPr._remove_tabs() + + +class TabStop(ElementProxy): + """An individual tab stop applying to a paragraph or style. + + Accessed using list semantics on its containing |TabStops| object. + """ + + def __init__(self, element): + super(TabStop, self).__init__(element, None) + self._tab = element + + @property + def alignment(self): + """A member of :ref:`WdTabAlignment` specifying the alignment setting for this + tab stop. + + Read/write. + """ + return self._tab.val + + @alignment.setter + def alignment(self, value): + self._tab.val = value + + @property + def leader(self): + """A member of :ref:`WdTabLeader` specifying a repeating character used as a + "leader", filling in the space spanned by this tab. + + Assigning |None| produces the same result as assigning `WD_TAB_LEADER.SPACES`. + Read/write. + """ + return self._tab.leader + + @leader.setter + def leader(self, value): + self._tab.leader = value + + @property + def position(self): + """A |Length| object representing the distance of this tab stop from the inside + edge of the paragraph. + + May be positive or negative. Read/write. + """ + return self._tab.pos + + @position.setter + def position(self, value): + tab = self._tab + tabs = tab.getparent() + self._tab = tabs.insert_tab_in_order(value, tab.val, tab.leader) + tabs.remove(tab) diff --git a/intelliaide/vendor/docx/types.py b/intelliaide/vendor/docx/types.py new file mode 100644 index 0000000..06d1a57 --- /dev/null +++ b/intelliaide/vendor/docx/types.py @@ -0,0 +1,34 @@ +"""Abstract types used by `python-docx`.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from typing_extensions import Protocol + +if TYPE_CHECKING: + from docx.opc.part import XmlPart + from docx.parts.story import StoryPart + + +class ProvidesStoryPart(Protocol): + """An object that provides access to the StoryPart. + + This type is for objects that have a story part like document or header as their + root part. + """ + + @property + def part(self) -> StoryPart: ... + + +class ProvidesXmlPart(Protocol): + """An object that provides access to its XmlPart. + + This type is for objects that need access to their part but it either isn't a + StoryPart or they don't care, possibly because they just need access to the package + or related parts. + """ + + @property + def part(self) -> XmlPart: ... diff --git a/intelliaide/vendor/drain3-0.9.11.dist-info/INSTALLER b/intelliaide/vendor/drain3-0.9.11.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/intelliaide/vendor/drain3-0.9.11.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/intelliaide/vendor/drain3-0.9.11.dist-info/METADATA b/intelliaide/vendor/drain3-0.9.11.dist-info/METADATA new file mode 100644 index 0000000..0d766f1 --- /dev/null +++ b/intelliaide/vendor/drain3-0.9.11.dist-info/METADATA @@ -0,0 +1,517 @@ +Metadata-Version: 2.4 +Name: drain3 +Version: 0.9.11 +Summary: Persistent & streaming log template miner +Home-page: https://github.com/IBM/Drain3 +Download-URL: https://github.com/IBM/Drain3/archive/v_01.tar.gz +Author: IBM Research Haifa +Author-email: david.ohana@ibm.com +License: MIT +Keywords: drain,log,parser,IBM,template,logs,miner +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Topic :: Software Development :: Libraries +Description-Content-Type: text/markdown +License-File: LICENSE.txt +Requires-Dist: jsonpickle==1.5.1 +Requires-Dist: cachetools==4.2.1 +Provides-Extra: kafka +Requires-Dist: kafka-python==2.0.1; extra == "kafka" +Provides-Extra: redis +Requires-Dist: redis==3.5.3; extra == "redis" +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: description-content-type +Dynamic: download-url +Dynamic: home-page +Dynamic: keywords +Dynamic: license +Dynamic: license-file +Dynamic: provides-extra +Dynamic: requires-dist +Dynamic: summary + +# Drain3 + +## Introduction + +Drain3 is an online log template miner that can extract templates (clusters) from a stream of log messages in a timely +manner. It employs a parse tree with fixed depth to guide the log group search process, which effectively avoids +constructing a very deep and unbalanced tree. + +Drain3 continuously learns on-the-fly and extracts log templates from raw log entries. + +#### Example: + +For the input: + +``` +connected to 10.0.0.1 +connected to 192.168.0.1 +Hex number 0xDEADBEAF +user davidoh logged in +user eranr logged in +``` + +Drain3 extracts the following templates: + +``` +ID=1 : size=2 : connected to <:IP:> +ID=2 : size=1 : Hex number <:HEX:> +ID=3 : size=2 : user <:*:> logged in +``` + +Full sample program output: + +``` +Starting Drain3 template miner +Checking for saved state +Saved state not found +Drain3 started with 'FILE' persistence +Starting training mode. Reading from std-in ('q' to finish) +> connected to 10.0.0.1 +Saving state of 1 clusters with 1 messages, 528 bytes, reason: cluster_created (1) +{"change_type": "cluster_created", "cluster_id": 1, "cluster_size": 1, "template_mined": "connected to <:IP:>", "cluster_count": 1} +Parameters: [ExtractedParameter(value='10.0.0.1', mask_name='IP')] +> connected to 192.168.0.1 +{"change_type": "none", "cluster_id": 1, "cluster_size": 2, "template_mined": "connected to <:IP:>", "cluster_count": 1} +Parameters: [ExtractedParameter(value='192.168.0.1', mask_name='IP')] +> Hex number 0xDEADBEAF +Saving state of 2 clusters with 3 messages, 584 bytes, reason: cluster_created (2) +{"change_type": "cluster_created", "cluster_id": 2, "cluster_size": 1, "template_mined": "Hex number <:HEX:>", "cluster_count": 2} +Parameters: [ExtractedParameter(value='0xDEADBEAF', mask_name='HEX')] +> user davidoh logged in +Saving state of 3 clusters with 4 messages, 648 bytes, reason: cluster_created (3) +{"change_type": "cluster_created", "cluster_id": 3, "cluster_size": 1, "template_mined": "user davidoh logged in", "cluster_count": 3} +Parameters: [] +> user eranr logged in +Saving state of 3 clusters with 5 messages, 644 bytes, reason: cluster_template_changed (3) +{"change_type": "cluster_template_changed", "cluster_id": 3, "cluster_size": 2, "template_mined": "user <:*:> logged in", "cluster_count": 3} +Parameters: [ExtractedParameter(value='eranr', mask_name='*')] +> q +Training done. Mined clusters: +ID=1 : size=2 : connected to <:IP:> +ID=2 : size=1 : Hex number <:HEX:> +ID=3 : size=2 : user <:*:> logged in +``` + +This project is an upgrade of the original [Drain](https://github.com/logpai/logparser/blob/master/logparser/Drain) +project by LogPAI from Python 2.7 to Python 3.6 or later with additional features and bug-fixes. + +Read more information about Drain from the following paper: + +- Pinjia He, Jieming Zhu, Zibin Zheng, and Michael R. + Lyu. [Drain: An Online Log Parsing Approach with Fixed Depth Tree](http://jiemingzhu.github.io/pub/pjhe_icws2017.pdf), + Proceedings of the 24th International Conference on Web Services (ICWS), 2017. + +A Drain3 use case is presented in this blog +post: [Use open source Drain3 log-template mining project to monitor for network outages](https://developer.ibm.com/blogs/how-mining-log-templates-can-help-ai-ops-in-cloud-scale-data-centers) +. + +#### New features + +- [**Persistence**](#persistence). Save and load Drain state into an [Apache Kafka](https://kafka.apache.org) + topic, [Redis](https://redis.io/) or a file. +- **Streaming**. Support feeding Drain with messages one-be-one. +- [**Masking**](#masking). Replace some message parts (e.g numbers, IPs, emails) with wildcards. This improves the + accuracy of template mining. +- [**Packaging**](#installation). As a pip package. +- [**Configuration**](#configuration). Support for configuring Drain3 using an `.ini` file or a configuration object. +- [**Memory efficiency**](#memory-efficiency). Decrease the memory footprint of internal data structures and introduce + cache to control max memory consumed (thanks to @StanislawSwierc) +- [**Inference mode**](#training-vs-inference-modes). In case you want to separate training and inference phase, Drain3 + provides a function for *fast* matching against already-learned clusters (templates) only, without the usage of + regular expressions. +- [**Parameter extraction**](#parameter-extraction). Accurate extraction of the variable parts from a log message as an + ordered list, based on its mined template and the defined masking instructions (thanks to @Impelon). + +#### Expected Input and Output + +Although Drain3 can be ingested with full raw log message, template mining accuracy can be improved if you feed it with +only the unstructured free-text portion of log messages, by first removing structured parts like timestamp, hostname. +severity, etc. + +The output is a dictionary with the following fields: + +- `change_type` - indicates either if a new template was identified, an existing template was changed or message added + to an existing cluster. +- `cluster_id` - Sequential ID of the cluster that the log belongs to. +- `cluster_size`- The size (message count) of the cluster that the log belongs to. +- `cluster_count` - Count clusters seen so far. +- `template_mined`- the last template of above cluster_id. + +## Configuration + +Drain3 is configured using [configparser](https://docs.python.org/3.4/library/configparser.html). By default, config +filename is `drain3.ini` in working directory. It can also be configured passing +a [TemplateMinerConfig](drain3/template_miner_config.py) object to the [TemplateMiner](drain3/template_miner.py) +constructor. + +Primary configuration parameters: + +- `[DRAIN]/sim_th` - similarity threshold. if percentage of similar tokens for a log message is below this number, a new + log cluster will be created (default 0.4) +- `[DRAIN]/depth` - max depth levels of log clusters. Minimum is 2. (default 4) +- `[DRAIN]/max_children` - max number of children of an internal node (default 100) +- `[DRAIN]/max_clusters` - max number of tracked clusters (unlimited by default). When this number is reached, model + starts replacing old clusters with a new ones according to the LRU cache eviction policy. +- `[DRAIN]/extra_delimiters` - delimiters to apply when splitting log message into words (in addition to whitespace) ( + default none). Format is a Python list e.g. `['_', ':']`. +- `[MASKING]/masking` - parameters masking - in json format (default "") +- `[MASKING]/mask_prefix` & `[MASKING]/mask_suffix` - the wrapping of identified parameters in templates. By default, it + is `<` and `>` respectively. +- `[SNAPSHOT]/snapshot_interval_minutes` - time interval for new snapshots (default 1) +- `[SNAPSHOT]/compress_state` - whether to compress the state before saving it. This can be useful when using Kafka + persistence. + +## Masking + +This feature allows masking of specific variable parts in log message with keywords, prior to passing to Drain. A +well-defined masking can improve template mining accuracy. + +Template parameters that do not match any custom mask in the preliminary masking phase are replaced with `<*>` by Drain +core. + +Use a list of regular expressions in the configuration file with the format `{'regex_pattern', 'mask_with'}` to set +custom masking. + +For example, following masking instructions in `drain3.ini` will mask IP addresses and integers: + +``` +[MASKING] +masking = [ + {"regex_pattern":"((?<=[^A-Za-z0-9])|^)(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})((?=[^A-Za-z0-9])|$)", "mask_with": "IP"}, + {"regex_pattern":"((?<=[^A-Za-z0-9])|^)([\\-\\+]?\\d+)((?=[^A-Za-z0-9])|$)", "mask_with": "NUM"}, + ] + ] +``` + +## Persistence + +The persistence feature saves and loads a snapshot of Drain3 state in a (compressed) json format. This feature adds +restart resiliency to Drain allowing continuation of activity and maintain learned knowledge across restarts. + +Drain3 state includes the search tree and all the clusters that were identified up until snapshot time. + +The snapshot also persist number of log messages matched each cluster, and it's `cluster_id`. + +An example of a snapshot: + +```json +{ + "clusters": [ + { + "cluster_id": 1, + "log_template_tokens": [ + "aa", + "aa", + "<*>" + ], + "py/object": "drain3_core.LogCluster", + "size": 2 + }, + { + "cluster_id": 2, + "log_template_tokens": [ + "My", + "IP", + "is", + "" + ], + "py/object": "drain3_core.LogCluster", + "size": 1 + } + ] +} +``` + +This example snapshot persist two clusters with the templates: + +`["aa", "aa", "<*>"]` - occurs twice + +`["My", "IP", "is", ""]` - occurs once + +Snapshots are created in the following events: + +- `cluster_created` - in any new template +- `cluster_template_changed` - in any update of a template +- `periodic` - after n minutes from the last snapshot. This is intended to save cluster sizes even if no new template + was identified. + +Drain3 currently supports the following persistence modes: + +- **Kafka** - The snapshot is saved in a dedicated topic used only for snapshots - the last message in this topic is the + last snapshot that will be loaded after restart. For Kafka persistence, you need to provide: `topic_name`. You may + also provide other `kwargs` that are supported by `kafka.KafkaConsumer` and `kafka.Producer` e.g `bootstrap_servers` + to change Kafka endpoint (default is `localhost:9092`). + +- **Redis** - The snapshot is saved to a key in Redis database (contributed by @matabares). + +- **File** - The snapshot is saved to a file. + +- **Memory** - The snapshot is saved an in-memory object. + +- **None** - No persistence. + +Drain3 persistence modes can be easily extended to another medium / database by inheriting +the [PersistenceHandler](drain3/persistence_handler.py) class. + +## Training vs. Inference modes + +In some use-cases, it is required to separate training and inference phases. + +In training phase you should call `template_miner.add_log_message(log_line)`. This will match log line against an +existing cluster (if similarity is above threshold) or create a new cluster. It may also change the template of an +existing cluster. + +In inference mode you should call `template_miner.match(log_line)`. This will match log line against previously learned +clusters only. No new clusters are created and templates of existing clusters are not changed. Match to existing cluster +has to be perfect, otherwise `None` is returned. You can use persistence option to load previously trained clusters +before inference. + +## Memory efficiency + +This feature limits the max memory used by the model. It is particularly important for large and possibly unbounded log +streams. This feature is controlled by the `max_clusters​` parameter, which sets the max number of clusters/templates +trarcked by the model. When the limit is reached, new templates start to replace the old ones according to the Least +Recently Used (LRU) eviction policy. This makes the model adapt quickly to the most recent templates in the log stream. + +## Parameter Extraction + +Drain3 supports retrieving an ordered list of variables in a log message, after its template was mined. Each parameter +is accompanied by the name of the mask that was matched, or `*` for the catch-all mask. + +Parameter extraction is performed by generating a regular expression that matches the template and then applying it on +the log message. When `exact_matching` is enabled (by default), the generated regex included the regular expression +defined in relevant masking instructions. If there are multiple masking instructions with the same name, either match +can satisfy the regex. It is possible to disable exact matching so that every variable is matched against a +non-whitespace character sequence. This may improve performance on expanse of accuracy. + +Parameter extraction regexes generated per template are cached by default, to improve performance. You can control cache +size with the ` MASKING/parameter_extraction_cache_capacity` configuration parameter. + +Sample usage: + +```python +result = template_miner.add_log_message(log_line) +params = template_miner.extract_parameters( + result["template_mined"], log_line, exact_matching=True) +``` + +For the input `"user johndoe logged in 11 minuts ago"`, the template would be: + +``` +"user <:*:> logged in <:NUM:> minuts ago" +``` + +... and the extracted parameters: + +``` +[ + ExtractedParameter(value='johndoe', mask_name='*'), + ExtractedParameter(value='11', mask_name='NUM') +] +``` + +## Installation + +Drain3 is available from [PyPI](https://pypi.org/project/drain3). To install use `pip`: + +``` +pip3 install drain3 +``` + +Note: If you decide to use Kafka or Redis persistence, you should install relevant client library explicitly, since it +is declared as an extra (optional) dependency, by either: + +``` +pip3 install kafka-python +``` + +-- or -- + +``` +pip3 install redis +``` + +## Examples + +In order to run the examples directly from the repository, you need to install dependencies. You can do that using * +pipenv* by executing the following command (assuming pipenv already installed): + +```shell +python3 -m pipenv sync +``` + +#### Example 1 - `drain_stdin_demo` + +Run [examples/drain_stdin_demo.py](examples/drain_stdin_demo.py) from the root folder of the repository by: + +``` +python3 -m pipenv run python -m examples.drain_stdin_demo +``` + +This example uses Drain3 on input from stdin and persist to either Kafka / file / no persistence. + +Change `persistence_type` variable in the example to change persistence mode. + +Enter several log lines using the command line. Press `q` to end online learn-and-match mode. + +Next, demo goes to match (inference) only mode, in which no new clusters are trained and input is matched against +previously trained clusters only. Press `q` again to finish execution. + +#### Example 2 - `drain_bigfile_demo` + +Run [examples/drain_bigfile_demo](examples/drain_bigfile_demo.py) from the root folder of the repository by: + +``` +python3 -m pipenv run python -m examples.drain_bigfile_demo +``` + +This example downloads a real-world log file (of an SSH server) and process all lines, then prints result clusters, +prefix tree and performance statistics. + +#### Sample config file + +An example `drain3.ini` file with masking instructions can be found in the [examples](examples) folder as well. + +## Contributing + +Our project welcomes external contributions. Please refer to [CONTRIBUTING.md](CONTRIBUTING.md) for further details. + +## Change Log + +##### v0.9.11 + +* Fixed possible DivideByZero error when the profiler is enabled - [Issue #65](https://github.com/IBM/Drain3/issues/65). + +##### v0.9.10 + +* Fixed compatibility issue with Python 3.10 caused by removal of `KeysView`. + +##### v0.9.9 + +* Added support for accurate log message parameter extraction in a new function - `extract_parameters()`. The + function `get_parameter_list()` is deprecated (Thanks to *@Impelon*). +* Refactored `AbstractMaskingInstruction` as a base class for `RegexMaskingInstruction`, allowing to introduce other + types of masking mechanisms. + +##### v0.9.8 + +* Added an option `full_search_strategy` option in `TemplateMiner.match()` and `Drain.match()`. See more info at + Issue [#48](https://github.com/IBM/Drain3/issues/48). +* Added an option to disable parameterization of tokens that contains digits in + configuration: `TemplateMinerConfig.parametrize_numeric_tokens` +* Loading Drain snapshot now only restores clusters state and not configuration parameters. This improves backwards + compatibility when introducing new Drain configuration parameters. + +##### v0.9.7 + +* Fixed bug in original Drain: log clusters were created multiple times for log messages with fewer tokens + than `max_node_depth`. +* Changed `depth` property name to a more descriptive name `max_node_depth` as Drain always subtracts 2 of `depth` + argument value. Also added `log_cluster_depth` property to reflect original value of depth argument (Breaking Change). +* Restricted `depth` param to minimum sensible value of 3. +* Added log cluster count to nodes in `Drain.print_tree()` +* Added optional log cluster details to `Drain.print_tree()` + +##### v0.9.6 + +* Fix issue https://github.com/IBM/Drain3/issues/38: Unnecessary update of LRU cache in case `max_clusters` is used ( + thanks *@StanislawSwierc*). + +##### v0.9.5 + +* Added: `TemplateMiner.match()` function for fast matching against existing clusters only. + +##### v0.9.4 + +* Added: `TemplateMiner.get_parameter_list()` function to extract template parameters for raw log message (thanks to * + @cwyalpha*) +* Added option to customize mask wrapper - Instead of the default `<*>`, `` etc, you can select any wrapper prefix + or suffix by overriding `TemplateMinerConfig.mask_prefix` and `TemplateMinerConfig.mask_prefix` +* Fixed: config `.ini` file is always read from same folder as source file in demos in tests (thanks *@RobinMaas95*) + +##### v0.9.3 + +* Fixed: comparison of type int with type str in function `add_seq_to_prefix_tree` #28 (bug introduced at v0.9.1) + +##### v0.9.2 + +* Updated jsonpickle version +* Keys `id_to_cluster` dict are now persisted by jsonpickle as `int` instead of `str` to avoid keys type conversion on + load snapshot which caused some issues. +* Added cachetools dependency to `setup.py`. + +##### v0.9.1 + +* Added option to configure `TemplateMiner` using a configuration object (without `.ini` file). +* Support for `print_tree()` to a file/stream. +* Added `MemoryBufferPersistence` +* Added unit tests for state save/load. +* Bug fix: missing type-conversion in state loading, introduced in v0.9.0 +* Refactor: Drain prefix tree keys are now of type `str` also for 1st level + (was `int` before), for type consistency. + +##### v0.9.0 + +* Decrease memory footprint of the main data structures. +* Added `max_clusters` option to limit the number of tracked clusters. +* Changed cluster identifier type from str to int +* Added more unit tests and CI + +##### v0.8.6 + +* Added `extra_delimiters` configuration option to Drain + +##### v0.8.5 + +* Profiler improvements + +##### v0.8.4 + +* Masking speed improvement + +##### v0.8.3 + +* Fix: profiler state after load from snapshot + +##### v0.8.2 + +* Fixed snapshot backward compatibility to v0.7.9 + +##### v0.8.1 + +* Bugfix in profiling configuration read + +##### v0.8.0 + +* Added time profiling support (disabled by default) +* Added cluster ID to snapshot reason log (credit: @boernd) +* Minor Readability and documentation improvements in Drain + +##### v0.7.9 + +* Fix: `KafkaPersistence` now accepts also `bootstrap_servers` as kwargs. + +##### v0.7.8 + +* Using `kafka-python` package instead of `kafka` (newer). +* Added support for specifying additional configuration as `kwargs` in Kafka persistence handler. + +##### v0.7.7 + +* Corrected default Drain config values. + +##### v0.7.6 + +* Improvement in config file handling (Note: new sections were added instead of `DEFAULT` section) + +##### v0.7.5 + +* Made Kafka and Redis optional requirements + diff --git a/intelliaide/vendor/drain3-0.9.11.dist-info/RECORD b/intelliaide/vendor/drain3-0.9.11.dist-info/RECORD new file mode 100644 index 0000000..5f65182 --- /dev/null +++ b/intelliaide/vendor/drain3-0.9.11.dist-info/RECORD @@ -0,0 +1,29 @@ +drain3-0.9.11.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +drain3-0.9.11.dist-info/METADATA,sha256=WHsShQDY3YhluWzid1ToSCccUtkMzdzsF7gAS700ULo,20090 +drain3-0.9.11.dist-info/RECORD,, +drain3-0.9.11.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +drain3-0.9.11.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91 +drain3-0.9.11.dist-info/licenses/LICENSE.txt,sha256=W7SO6tO8xWE_rk2KWakkjpo4KPLC2M93LFQ_LS0a7ow,1130 +drain3-0.9.11.dist-info/top_level.txt,sha256=ba95R2pAEjS5SaYAq2GTkskDgMDudW2oTGtNlr0cdVE,7 +drain3/__init__.py,sha256=b3r7xyKCiz-KeJVjPE1N5nmMUTp92O6vGLP4xY_pX-4,49 +drain3/__pycache__/__init__.cpython-312.pyc,, +drain3/__pycache__/drain.cpython-312.pyc,, +drain3/__pycache__/file_persistence.cpython-312.pyc,, +drain3/__pycache__/kafka_persistence.cpython-312.pyc,, +drain3/__pycache__/masking.cpython-312.pyc,, +drain3/__pycache__/memory_buffer_persistence.cpython-312.pyc,, +drain3/__pycache__/persistence_handler.cpython-312.pyc,, +drain3/__pycache__/redis_persistence.cpython-312.pyc,, +drain3/__pycache__/simple_profiler.cpython-312.pyc,, +drain3/__pycache__/template_miner.cpython-312.pyc,, +drain3/__pycache__/template_miner_config.cpython-312.pyc,, +drain3/drain.py,sha256=1ZTG-8nCgc7-hGNxjcvXtrLMomgtdEWde-E9n1YPLIk,16806 +drain3/file_persistence.py,sha256=Ijixm7F6qM4QtaH_1Xr9auZMov_ijox2ChoKfFw6TsY,477 +drain3/kafka_persistence.py,sha256=dvxE2VRRml2jqRy0FvNaGKvClp8q_89sF3bxH_1vfz4,1364 +drain3/masking.py,sha256=yQiYqTuonbXIhk2pa3_WtWftAvWn4PywHGh1-UO9vpM,2864 +drain3/memory_buffer_persistence.py,sha256=4gWDlWHt688o6ALOfLEJcaZCu9NlQ2sPhaiqzOpACys,306 +drain3/persistence_handler.py,sha256=xMLeIdiqrYjuijYdHgIQGmdV6ZjU_Qo52WX3OfHqYxA,228 +drain3/redis_persistence.py,sha256=AHJN_8DbQixentmcvG02tGDu8Vl_SlrCNCDl_Y_iqC0,842 +drain3/simple_profiler.py,sha256=kI_N25V7fLiq78uNwlsMcwH83pa_ZXzPwZfFf7q6-FA,5434 +drain3/template_miner.py,sha256=IVDz9zXuXyuQIKacyc-BbOUFYxSins17LXxW4DeyVnU,14370 +drain3/template_miner_config.py,sha256=8SwU4EJw7r9eNcpyI_B31XzsS95FMs1n1-xDLw-29jQ,3858 diff --git a/intelliaide/vendor/drain3-0.9.11.dist-info/REQUESTED b/intelliaide/vendor/drain3-0.9.11.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/intelliaide/vendor/drain3-0.9.11.dist-info/WHEEL b/intelliaide/vendor/drain3-0.9.11.dist-info/WHEEL new file mode 100644 index 0000000..14a883f --- /dev/null +++ b/intelliaide/vendor/drain3-0.9.11.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (82.0.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/intelliaide/vendor/drain3-0.9.11.dist-info/licenses/LICENSE.txt b/intelliaide/vendor/drain3-0.9.11.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000..727be5f --- /dev/null +++ b/intelliaide/vendor/drain3-0.9.11.dist-info/licenses/LICENSE.txt @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2020-2022 International Business Machines +and the Drain3 project contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/intelliaide/vendor/drain3-0.9.11.dist-info/top_level.txt b/intelliaide/vendor/drain3-0.9.11.dist-info/top_level.txt new file mode 100644 index 0000000..b9e117f --- /dev/null +++ b/intelliaide/vendor/drain3-0.9.11.dist-info/top_level.txt @@ -0,0 +1 @@ +drain3 diff --git a/intelliaide/vendor/drain3/__init__.py b/intelliaide/vendor/drain3/__init__.py new file mode 100644 index 0000000..1bb0d45 --- /dev/null +++ b/intelliaide/vendor/drain3/__init__.py @@ -0,0 +1,2 @@ +from drain3.template_miner import TemplateMiner + diff --git a/intelliaide/vendor/drain3/drain.py b/intelliaide/vendor/drain3/drain.py new file mode 100644 index 0000000..9873d07 --- /dev/null +++ b/intelliaide/vendor/drain3/drain.py @@ -0,0 +1,421 @@ +# SPDX-License-Identifier: MIT +# This file implements the Drain algorithm for log parsing. +# Based on https://github.com/logpai/logparser/blob/master/logparser/Drain/Drain.py by LogPAI team + +from typing import List, Dict, Sequence + +from cachetools import LRUCache, Cache + +from drain3.simple_profiler import Profiler, NullProfiler + + +class LogCluster: + __slots__ = ["log_template_tokens", "cluster_id", "size"] + + def __init__(self, log_template_tokens: list, cluster_id: int): + self.log_template_tokens = tuple(log_template_tokens) + self.cluster_id = cluster_id + self.size = 1 + + def get_template(self): + return ' '.join(self.log_template_tokens) + + def __str__(self): + return f"ID={str(self.cluster_id).ljust(5)} : size={str(self.size).ljust(10)}: {self.get_template()}" + + +class LogClusterCache(LRUCache): + """ + Least Recently Used (LRU) cache which allows callers to conditionally skip + cache eviction algorithm when accessing elements. + """ + + def __missing__(self, key): + return None + + def get(self, key): + """ + Returns the value of the item with the specified key without updating + the cache eviction algorithm. + """ + return Cache.__getitem__(self, key) + + +class Node: + __slots__ = ["key_to_child_node", "cluster_ids"] + + def __init__(self): + self.key_to_child_node: Dict[str, Node] = {} + self.cluster_ids: List[int] = [] + + +class Drain: + def __init__(self, + depth=4, + sim_th=0.4, + max_children=100, + max_clusters=None, + extra_delimiters=(), + profiler: Profiler = NullProfiler(), + param_str="<*>", + parametrize_numeric_tokens=True): + """ + Create a new Drain instance. + + :param depth: max depth levels of log clusters. Minimum is 2. + For example, for depth==4, Root is considered depth level 1. + Token count is considered depth level 2. + First log token is considered depth level 3. + Log clusters below first token node are considered depth level 4. + :param sim_th: similarity threshold - if percentage of similar tokens for a log message is below this + number, a new log cluster will be created. + :param max_children: max number of children of an internal node + :param max_clusters: max number of tracked clusters (unlimited by default). + When this number is reached, model starts replacing old clusters + with a new ones according to the LRU policy. + :param extra_delimiters: delimiters to apply when splitting log message into words (in addition to whitespace). + :param parametrize_numeric_tokens: whether to treat tokens that contains at least one digit + as template parameters. + """ + if depth < 3: + raise ValueError("depth argument must be at least 3") + + self.log_cluster_depth = depth + self.max_node_depth = depth - 2 # max depth of a prefix tree node, starting from zero + self.sim_th = sim_th + self.max_children = max_children + self.root_node = Node() + self.profiler = profiler + self.extra_delimiters = extra_delimiters + self.max_clusters = max_clusters + self.param_str = param_str + self.parametrize_numeric_tokens = parametrize_numeric_tokens + + # key: int, value: LogCluster + self.id_to_cluster = {} if max_clusters is None else LogClusterCache(maxsize=max_clusters) + self.clusters_counter = 0 + + @property + def clusters(self): + return self.id_to_cluster.values() + + @staticmethod + def has_numbers(s): + return any(char.isdigit() for char in s) + + def tree_search(self, root_node: Node, tokens: list, sim_th: float, include_params: bool): + + # at first level, children are grouped by token (word) count + token_count = len(tokens) + cur_node = root_node.key_to_child_node.get(str(token_count)) + + # no template with same token count yet + if cur_node is None: + return None + + # handle case of empty log string - return the single cluster in that group + if token_count == 0: + return self.id_to_cluster.get(cur_node.cluster_ids[0]) + + # find the leaf node for this log - a path of nodes matching the first N tokens (N=tree depth) + cur_node_depth = 1 + for token in tokens: + # at max depth + if cur_node_depth >= self.max_node_depth: + break + + # this is last token + if cur_node_depth == token_count: + break + + key_to_child_node = cur_node.key_to_child_node + cur_node = key_to_child_node.get(token) + if cur_node is None: # no exact next token exist, try wildcard node + cur_node = key_to_child_node.get(self.param_str) + if cur_node is None: # no wildcard node exist + return None + + cur_node_depth += 1 + + # get best match among all clusters with same prefix, or None if no match is above sim_th + cluster = self.fast_match(cur_node.cluster_ids, tokens, sim_th, include_params) + return cluster + + def add_seq_to_prefix_tree(self, root_node, cluster: LogCluster): + token_count = len(cluster.log_template_tokens) + token_count_str = str(token_count) + if token_count_str not in root_node.key_to_child_node: + first_layer_node = Node() + root_node.key_to_child_node[token_count_str] = first_layer_node + else: + first_layer_node = root_node.key_to_child_node[token_count_str] + + cur_node = first_layer_node + + # handle case of empty log string + if token_count == 0: + cur_node.cluster_ids = [cluster.cluster_id] + return + + current_depth = 1 + for token in cluster.log_template_tokens: + + # if at max depth or this is last token in template - add current log cluster to the leaf node + if current_depth >= self.max_node_depth or current_depth >= token_count: + # clean up stale clusters before adding a new one. + new_cluster_ids = [] + for cluster_id in cur_node.cluster_ids: + if cluster_id in self.id_to_cluster: + new_cluster_ids.append(cluster_id) + new_cluster_ids.append(cluster.cluster_id) + cur_node.cluster_ids = new_cluster_ids + break + + # if token not matched in this layer of existing tree. + if token not in cur_node.key_to_child_node: + if self.parametrize_numeric_tokens and self.has_numbers(token): + if self.param_str not in cur_node.key_to_child_node: + new_node = Node() + cur_node.key_to_child_node[self.param_str] = new_node + cur_node = new_node + else: + cur_node = cur_node.key_to_child_node[self.param_str] + + else: + if self.param_str in cur_node.key_to_child_node: + if len(cur_node.key_to_child_node) < self.max_children: + new_node = Node() + cur_node.key_to_child_node[token] = new_node + cur_node = new_node + else: + cur_node = cur_node.key_to_child_node[self.param_str] + else: + if len(cur_node.key_to_child_node) + 1 < self.max_children: + new_node = Node() + cur_node.key_to_child_node[token] = new_node + cur_node = new_node + elif len(cur_node.key_to_child_node) + 1 == self.max_children: + new_node = Node() + cur_node.key_to_child_node[self.param_str] = new_node + cur_node = new_node + else: + cur_node = cur_node.key_to_child_node[self.param_str] + + # if the token is matched + else: + cur_node = cur_node.key_to_child_node[token] + + current_depth += 1 + + # seq1 is a template, seq2 is the log to match + def get_seq_distance(self, seq1, seq2, include_params: bool): + assert len(seq1) == len(seq2) + + # sequences are empty - full match + if len(seq1) == 0: + return 1.0, 0 + + sim_tokens = 0 + param_count = 0 + + for token1, token2 in zip(seq1, seq2): + if token1 == self.param_str: + param_count += 1 + continue + if token1 == token2: + sim_tokens += 1 + + if include_params: + sim_tokens += param_count + + ret_val = float(sim_tokens) / len(seq1) + + return ret_val, param_count + + def fast_match(self, cluster_ids: Sequence, tokens: list, sim_th: float, include_params: bool): + """ + Find the best match for a log message (represented as tokens) versus a list of clusters + :param cluster_ids: List of clusters to match against (represented by their IDs) + :param tokens: the log message, separated to tokens. + :param sim_th: minimum required similarity threshold (None will be returned in no clusters reached it) + :param include_params: consider tokens matched to wildcard parameters in similarity threshold. + :return: Best match cluster or None + """ + match_cluster = None + + max_sim = -1 + max_param_count = -1 + max_cluster = None + + for cluster_id in cluster_ids: + # Try to retrieve cluster from cache with bypassing eviction + # algorithm as we are only testing candidates for a match. + cluster = self.id_to_cluster.get(cluster_id) + if cluster is None: + continue + cur_sim, param_count = self.get_seq_distance(cluster.log_template_tokens, tokens, include_params) + if cur_sim > max_sim or (cur_sim == max_sim and param_count > max_param_count): + max_sim = cur_sim + max_param_count = param_count + max_cluster = cluster + + if max_sim >= sim_th: + match_cluster = max_cluster + + return match_cluster + + def create_template(self, seq1, seq2): + assert len(seq1) == len(seq2) + ret_val = list(seq2) + + for i, (token1, token2) in enumerate(zip(seq1, seq2)): + if token1 != token2: + ret_val[i] = self.param_str + + return ret_val + + def print_tree(self, file=None, max_clusters=5): + self.print_node("root", self.root_node, 0, file, max_clusters) + + def print_node(self, token, node, depth, file, max_clusters): + out_str = '\t' * depth + + if depth == 0: + out_str += f'<{token}>' + elif depth == 1: + out_str += f'' + else: + out_str += f'"{token}"' + + if len(node.cluster_ids) > 0: + out_str += f" (cluster_count={len(node.cluster_ids)})" + + print(out_str, file=file) + + for token, child in node.key_to_child_node.items(): + self.print_node(token, child, depth + 1, file, max_clusters) + + for cid in node.cluster_ids[:max_clusters]: + cluster = self.id_to_cluster[cid] + out_str = '\t' * (depth + 1) + str(cluster) + print(out_str, file=file) + + def get_content_as_tokens(self, content): + content = content.strip() + for delimiter in self.extra_delimiters: + content = content.replace(delimiter, " ") + content_tokens = content.split() + return content_tokens + + def add_log_message(self, content: str): + content_tokens = self.get_content_as_tokens(content) + + if self.profiler: + self.profiler.start_section("tree_search") + match_cluster = self.tree_search(self.root_node, content_tokens, self.sim_th, False) + if self.profiler: + self.profiler.end_section() + + # Match no existing log cluster + if match_cluster is None: + if self.profiler: + self.profiler.start_section("create_cluster") + self.clusters_counter += 1 + cluster_id = self.clusters_counter + match_cluster = LogCluster(content_tokens, cluster_id) + self.id_to_cluster[cluster_id] = match_cluster + self.add_seq_to_prefix_tree(self.root_node, match_cluster) + update_type = "cluster_created" + + # Add the new log message to the existing cluster + else: + if self.profiler: + self.profiler.start_section("cluster_exist") + new_template_tokens = self.create_template(content_tokens, match_cluster.log_template_tokens) + if tuple(new_template_tokens) == match_cluster.log_template_tokens: + update_type = "none" + else: + match_cluster.log_template_tokens = tuple(new_template_tokens) + update_type = "cluster_template_changed" + match_cluster.size += 1 + # Touch cluster to update its state in the cache. + # noinspection PyStatementEffect + self.id_to_cluster[match_cluster.cluster_id] + + if self.profiler: + self.profiler.end_section() + + return match_cluster, update_type + + def get_clusters_ids_for_seq_len(self, seq_len: int): + """ + Return all clusters with the specified count of tokens + """ + + def append_clusters_recursive(node: Node, id_list_to_fill: list): + id_list_to_fill.extend(node.cluster_ids) + for child_node in node.key_to_child_node.values(): + append_clusters_recursive(child_node, id_list_to_fill) + + cur_node = self.root_node.key_to_child_node.get(str(seq_len)) + + # no template with same token count + if cur_node is None: + return [] + + target = [] + append_clusters_recursive(cur_node, target) + return target + + def match(self, content: str, full_search_strategy="never"): + """ + Match log message against an already existing cluster. + Match shall be perfect (sim_th=1.0). + New cluster will not be created as a result of this call, nor any cluster modifications. + + :param content: log message to match + :param full_search_strategy: when to perform full cluster search. + (1) "never" is the fastest, will always perform a tree search [O(log(n)] but might produce + false negatives (wrong mismatches) on some edge cases; + (2) "fallback" will perform a linear search [O(n)] among all clusters with the same token count, but only in + case tree search found no match. + It should not have false negatives, however tree-search may find a non-optimal match with + more wildcard parameters than necessary; + (3) "always" is the slowest. It will select the best match among all known clusters, by always evaluating + all clusters with the same token count, and selecting the cluster with perfect all token match and least + count of wildcard matches. + :return: Matched cluster or None if no match found. + """ + + assert full_search_strategy in ["always", "never", "fallback"] + + required_sim_th = 1.0 + content_tokens = self.get_content_as_tokens(content) + + # consider for future improvement: + # It is possible to implement a recursive tree_search (first try exact token match and fallback to + # wildcard match). This will be both accurate and more efficient than the linear full search + # also fast match can be optimized when exact match is required by early + # quitting on less than exact cluster matches. + def full_search(): + all_ids = self.get_clusters_ids_for_seq_len(len(content_tokens)) + cluster = self.fast_match(all_ids, content_tokens, required_sim_th, include_params=True) + return cluster + + if full_search_strategy == "always": + return full_search() + + match_cluster = self.tree_search(self.root_node, content_tokens, required_sim_th, include_params=True) + if match_cluster is not None: + return match_cluster + + if full_search_strategy == "never": + return None + + return full_search() + + def get_total_cluster_size(self): + size = 0 + for c in self.id_to_cluster.values(): + size += c.size + return size diff --git a/intelliaide/vendor/drain3/file_persistence.py b/intelliaide/vendor/drain3/file_persistence.py new file mode 100644 index 0000000..5042993 --- /dev/null +++ b/intelliaide/vendor/drain3/file_persistence.py @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: MIT + +import os +import pathlib + +from drain3.persistence_handler import PersistenceHandler + + +class FilePersistence(PersistenceHandler): + def __init__(self, file_path): + self.file_path = file_path + + def save_state(self, state): + pathlib.Path(self.file_path).write_bytes(state) + + def load_state(self): + if not os.path.exists(self.file_path): + return None + + return pathlib.Path(self.file_path).read_bytes() diff --git a/intelliaide/vendor/drain3/kafka_persistence.py b/intelliaide/vendor/drain3/kafka_persistence.py new file mode 100644 index 0000000..5efc8c7 --- /dev/null +++ b/intelliaide/vendor/drain3/kafka_persistence.py @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: MIT + +import kafka + +from drain3.persistence_handler import PersistenceHandler + + +class KafkaPersistence(PersistenceHandler): + + def __init__(self, topic, snapshot_poll_timeout_sec=60, **kafka_client_options): + self.topic = topic + self.kafka_client_options = kafka_client_options + self.producer = kafka.KafkaProducer(**self.kafka_client_options) + self.snapshot_poll_timeout_sec = snapshot_poll_timeout_sec + + def save_state(self, state): + self.producer.send(self.topic, value=state) + + def load_state(self): + consumer = kafka.KafkaConsumer(**self.kafka_client_options) + partition = kafka.TopicPartition(self.topic, 0) + consumer.assign([partition]) + end_offsets = consumer.end_offsets([partition]) + end_offset = list(end_offsets.values())[0] + if end_offset > 0: + consumer.seek(partition, end_offset - 1) + snapshot_poll_timeout_ms = self.snapshot_poll_timeout_sec * 1000 + records = consumer.poll(snapshot_poll_timeout_ms) + if not records: + raise RuntimeError(f"No message received from Kafka during restore even though end_offset>0") + last_msg = records[partition][0] + state = last_msg.value + else: + state = None + + consumer.close() + return state diff --git a/intelliaide/vendor/drain3/masking.py b/intelliaide/vendor/drain3/masking.py new file mode 100644 index 0000000..b463e77 --- /dev/null +++ b/intelliaide/vendor/drain3/masking.py @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: MIT + +import abc +import re +from typing import Collection, Optional + + +class AbstractMaskingInstruction(abc.ABC): + + def __init__(self, mask_with: str): + self.mask_with = mask_with + + @abc.abstractmethod + def mask(self, content: str, mask_prefix: str, mask_suffix: str) -> str: + """ + Mask content according to this instruction and return the result. + + :param content: text to apply masking to + :param mask_prefix: the prefix of any masks inserted + :param mask_suffix: the suffix of any masks inserted + """ + pass + + +class MaskingInstruction(AbstractMaskingInstruction): + + def __init__(self, pattern: str, mask_with: str): + super().__init__(mask_with) + self.regex = re.compile(pattern) + + @property + def pattern(self): + return self.regex.pattern + + def mask(self, content: str, mask_prefix: str, mask_suffix: str) -> str: + mask = mask_prefix + self.mask_with + mask_suffix + return self.regex.sub(mask, content) + + +# Alias for `MaskingInstruction`. +RegexMaskingInstruction = MaskingInstruction + + +class LogMasker: + + def __init__(self, masking_instructions: Collection[AbstractMaskingInstruction], + mask_prefix: str, mask_suffix: str): + self.mask_prefix = mask_prefix + self.mask_suffix = mask_suffix + self.masking_instructions = masking_instructions + self.mask_name_to_instructions = {} + for mi in self.masking_instructions: + self.mask_name_to_instructions.setdefault(mi.mask_with, []) + self.mask_name_to_instructions[mi.mask_with].append(mi) + + def mask(self, content: str) -> str: + for mi in self.masking_instructions: + content = mi.mask(content, self.mask_prefix, self.mask_suffix) + return content + + @property + def mask_names(self) -> Collection[str]: + return self.mask_name_to_instructions.keys() + + def instructions_by_mask_name(self, mask_name: str) -> Optional[Collection[AbstractMaskingInstruction]]: + return self.mask_name_to_instructions.get(mask_name, []) + +# Some masking examples +# --------------------- +# +# masking_instances = [ +# MaskingInstruction(r'((?<=[^A-Za-z0-9])|^)(([0-9a-f]{2,}:){3,}([0-9a-f]{2,}))((?=[^A-Za-z0-9])|$)', "ID"), +# MaskingInstruction(r'((?<=[^A-Za-z0-9])|^)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})((?=[^A-Za-z0-9])|$)', "IP"), +# MaskingInstruction(r'((?<=[^A-Za-z0-9])|^)([0-9a-f]{6,} ?){3,}((?=[^A-Za-z0-9])|$)', "SEQ"), +# MaskingInstruction(r'((?<=[^A-Za-z0-9])|^)([0-9A-F]{4} ?){4,}((?=[^A-Za-z0-9])|$)', "SEQ"), +# +# MaskingInstruction(r'((?<=[^A-Za-z0-9])|^)(0x[a-f0-9A-F]+)((?=[^A-Za-z0-9])|$)', "HEX"), +# MaskingInstruction(r'((?<=[^A-Za-z0-9])|^)([\-\+]?\d+)((?=[^A-Za-z0-9])|$)', "NUM"), +# MaskingInstruction(r'(?<=executed cmd )(".+?")', "CMD"), +# ] diff --git a/intelliaide/vendor/drain3/memory_buffer_persistence.py b/intelliaide/vendor/drain3/memory_buffer_persistence.py new file mode 100644 index 0000000..24d7393 --- /dev/null +++ b/intelliaide/vendor/drain3/memory_buffer_persistence.py @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: MIT + +from drain3.persistence_handler import PersistenceHandler + + +class MemoryBufferPersistence(PersistenceHandler): + def __init__(self): + self.state = None + + def save_state(self, state): + self.state = state + + def load_state(self): + return self.state \ No newline at end of file diff --git a/intelliaide/vendor/drain3/persistence_handler.py b/intelliaide/vendor/drain3/persistence_handler.py new file mode 100644 index 0000000..86e30ef --- /dev/null +++ b/intelliaide/vendor/drain3/persistence_handler.py @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: MIT + +from abc import ABC, abstractmethod + + +class PersistenceHandler(ABC): + + @abstractmethod + def save_state(self, state): + pass + + @abstractmethod + def load_state(self): + pass diff --git a/intelliaide/vendor/drain3/redis_persistence.py b/intelliaide/vendor/drain3/redis_persistence.py new file mode 100644 index 0000000..b2cfd67 --- /dev/null +++ b/intelliaide/vendor/drain3/redis_persistence.py @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: MIT + +import redis + +from drain3.persistence_handler import PersistenceHandler + + +class RedisPersistence(PersistenceHandler): + def __init__(self, redis_host, redis_port, redis_db, redis_pass, is_ssl, redis_key): + self.redis_host = redis_host + self.redis_port = redis_port + self.redis_db = redis_db + self.redis_pass = redis_pass + self.is_ssl = is_ssl + self.redis_key = redis_key + self.r = redis.Redis(host=self.redis_host, + port=self.redis_port, + db=self.redis_db, + password=self.redis_pass, + ssl=self.is_ssl) + + def save_state(self, state): + self.r.set(self.redis_key, state) + + def load_state(self): + return self.r.get(self.redis_key) diff --git a/intelliaide/vendor/drain3/simple_profiler.py b/intelliaide/vendor/drain3/simple_profiler.py new file mode 100644 index 0000000..6f45810 --- /dev/null +++ b/intelliaide/vendor/drain3/simple_profiler.py @@ -0,0 +1,151 @@ +# SPDX-License-Identifier: Apache-2.0 +# Based on https://github.com/davidohana/SimpleProfiler/blob/main/python/simple_profiler.py + +import os +import time + +from abc import ABC, abstractmethod + + +class Profiler(ABC): + + @abstractmethod + def start_section(self, section_name: str): + pass + + @abstractmethod + def end_section(self, section_name=""): + pass + + @abstractmethod + def report(self, period_sec=30): + pass + + +class NullProfiler(Profiler): + """A no-op profiler. Use it instead of SimpleProfiler in case you want to disable profiling.""" + + def start_section(self, section_name: str): + pass + + def end_section(self, section_name=""): + pass + + def report(self, period_sec=30): + pass + + +class SimpleProfiler(Profiler): + def __init__(self, reset_after_sample_count=0, enclosing_section_name="total", printer=print, report_sec=30): + self.printer = printer + self.enclosing_section_name = enclosing_section_name + self.reset_after_sample_count = reset_after_sample_count + self.report_sec = report_sec + + self.section_to_stats = {} + self.last_report_timestamp_sec = time.time() + self.last_started_section_name = "" + + def start_section(self, section_name: str): + """Start measuring a section""" + + if not section_name: + raise ValueError("Section name is empty") + self.last_started_section_name = section_name + + section = self.section_to_stats.get(section_name, None) + if section is None: + section = ProfiledSectionStats(section_name) + self.section_to_stats[section_name] = section + + if section.start_time_sec != 0: + raise ValueError(f"Section {section_name} is already started") + + section.start_time_sec = time.time() + + def end_section(self, name=""): + """End measuring a section. Leave section name empty to end the last started section.""" + + now = time.time() + + section_name = name + if not name: + section_name = self.last_started_section_name + + if not section_name: + raise ValueError("Neither section name is specified nor a section is started") + + section: ProfiledSectionStats = self.section_to_stats.get(section_name, None) + if section is None: + raise ValueError(f"Section {section_name} does not exist") + + if section.start_time_sec == 0: + raise ValueError(f"Section {section_name} was not started") + + took_sec = now - section.start_time_sec + if 0 < self.reset_after_sample_count == section.sample_count: + section.sample_count_batch = 0 + section.total_time_sec_batch = 0 + + section.sample_count += 1 + section.total_time_sec += took_sec + section.sample_count_batch += 1 + section.total_time_sec_batch += took_sec + section.start_time_sec = 0 + + def report(self, period_sec=30): + """Print results using [printer] function. By default prints to stdout.""" + if time.time() - self.last_report_timestamp_sec < period_sec: + return False + + enclosing_time_sec = 0 + if self.enclosing_section_name: + enclosing_section: ProfiledSectionStats = self.section_to_stats.get(self.enclosing_section_name, None) + if enclosing_section: + enclosing_time_sec = enclosing_section.total_time_sec + + include_batch_rates = self.reset_after_sample_count > 0 + + sections = self.section_to_stats.values() + sorted_sections = sorted(sections, key=lambda it: it.total_time_sec, reverse=True) + lines = map(lambda it: it.to_string(enclosing_time_sec, include_batch_rates), sorted_sections) + text = os.linesep.join(lines) + self.printer(text) + + self.last_report_timestamp_sec = time.time() + return True + + +class ProfiledSectionStats: + def __init__(self, section_name, start_time_sec=0, sample_count=0, total_time_sec=0, + sample_count_batch=0, total_time_sec_batch=0): + self.section_name = section_name + self.start_time_sec = start_time_sec + self.sample_count = sample_count + self.total_time_sec = total_time_sec + self.sample_count_batch = sample_count_batch + self.total_time_sec_batch = total_time_sec_batch + + def to_string(self, enclosing_time_sec: int, include_batch_rates: bool): + took_sec_text = f"{self.total_time_sec:>8.2f} s" + if enclosing_time_sec > 0: + took_sec_text += f" ({100 * self.total_time_sec / enclosing_time_sec:>6.2f}%)" + + ms_per_k_samples = f"{1000000 * self.total_time_sec / self.sample_count: 7.2f}" + + if self.total_time_sec > 0: + samples_per_sec = f"{self.sample_count / self.total_time_sec: 15,.2f}" + else: + samples_per_sec = "N/A" + + if include_batch_rates: + ms_per_k_samples += f" ({1000000 * self.total_time_sec_batch / self.sample_count_batch: 7.2f})" + if self.total_time_sec_batch > 0: + samples_per_sec += f" ({self.sample_count_batch / self.total_time_sec_batch: 15,.2f})" + else: + samples_per_sec += " (N/A)" + + return f"{self.section_name: <15}: took {took_sec_text}, " \ + f"{self.sample_count: >10,} samples, " \ + f"{ms_per_k_samples} ms / 1000 samples, " \ + f"{samples_per_sec} hz" diff --git a/intelliaide/vendor/drain3/template_miner.py b/intelliaide/vendor/drain3/template_miner.py new file mode 100644 index 0000000..6a057f2 --- /dev/null +++ b/intelliaide/vendor/drain3/template_miner.py @@ -0,0 +1,314 @@ +# SPDX-License-Identifier: MIT + +import base64 +import logging +import re +import time +import zlib +from typing import Optional, List, NamedTuple + +import jsonpickle +from cachetools import LRUCache, cachedmethod + +from drain3.drain import Drain, LogCluster +from drain3.masking import LogMasker +from drain3.persistence_handler import PersistenceHandler +from drain3.simple_profiler import SimpleProfiler, NullProfiler, Profiler +from drain3.template_miner_config import TemplateMinerConfig + +logger = logging.getLogger(__name__) + +config_filename = 'drain3.ini' + +ExtractedParameter = NamedTuple("ExtractedParameter", [("value", str), ("mask_name", str)]) + + +class TemplateMiner: + + def __init__(self, + persistence_handler: PersistenceHandler = None, + config: TemplateMinerConfig = None): + """ + Wrapper for Drain with persistence and masking support + + :param persistence_handler: The type of persistence to use. When None, no persistence is applied. + :param config: Configuration object. When none, configuration is loaded from default .ini file (if exist) + """ + logger.info("Starting Drain3 template miner") + + if config is None: + logger.info(f"Loading configuration from {config_filename}") + config = TemplateMinerConfig() + config.load(config_filename) + + self.config = config + + self.profiler: Profiler = NullProfiler() + if self.config.profiling_enabled: + self.profiler = SimpleProfiler() + + self.persistence_handler = persistence_handler + + param_str = self.config.mask_prefix + "*" + self.config.mask_suffix + self.drain = Drain( + sim_th=self.config.drain_sim_th, + depth=self.config.drain_depth, + max_children=self.config.drain_max_children, + max_clusters=self.config.drain_max_clusters, + extra_delimiters=self.config.drain_extra_delimiters, + profiler=self.profiler, + param_str=param_str, + parametrize_numeric_tokens=self.config.parametrize_numeric_tokens + ) + self.masker = LogMasker(self.config.masking_instructions, self.config.mask_prefix, self.config.mask_suffix) + self.parameter_extraction_cache = LRUCache(self.config.parameter_extraction_cache_capacity) + self.last_save_time = time.time() + if persistence_handler is not None: + self.load_state() + + def load_state(self): + logger.info("Checking for saved state") + + state = self.persistence_handler.load_state() + if state is None: + logger.info("Saved state not found") + return + + if self.config.snapshot_compress_state: + state = zlib.decompress(base64.b64decode(state)) + + loaded_drain: Drain = jsonpickle.loads(state, keys=True) + + # json-pickle encoded keys as string by default, so we have to convert those back to int + # this is only relevant for backwards compatibility when loading a snapshot of drain <= v0.9.1 + # which did not use json-pickle's keys=true + if len(loaded_drain.id_to_cluster) > 0 and isinstance(next(iter(loaded_drain.id_to_cluster.keys())), str): + loaded_drain.id_to_cluster = {int(k): v for k, v in list(loaded_drain.id_to_cluster.items())} + if self.config.drain_max_clusters: + cache = LRUCache(maxsize=self.config.drain_max_clusters) + cache.update(loaded_drain.id_to_cluster) + loaded_drain.id_to_cluster = cache + + self.drain.id_to_cluster = loaded_drain.id_to_cluster + self.drain.clusters_counter = loaded_drain.clusters_counter + self.drain.root_node = loaded_drain.root_node + + logger.info("Restored {0} clusters built from {1} messages".format( + len(loaded_drain.clusters), loaded_drain.get_total_cluster_size())) + + def save_state(self, snapshot_reason): + state = jsonpickle.dumps(self.drain, keys=True).encode('utf-8') + if self.config.snapshot_compress_state: + state = base64.b64encode(zlib.compress(state)) + + logger.info(f"Saving state of {len(self.drain.clusters)} clusters " + f"with {self.drain.get_total_cluster_size()} messages, {len(state)} bytes, " + f"reason: {snapshot_reason}") + self.persistence_handler.save_state(state) + + def get_snapshot_reason(self, change_type, cluster_id): + if change_type != "none": + return "{} ({})".format(change_type, cluster_id) + + diff_time_sec = time.time() - self.last_save_time + if diff_time_sec >= self.config.snapshot_interval_minutes * 60: + return "periodic" + + return None + + def add_log_message(self, log_message: str) -> dict: + self.profiler.start_section("total") + + self.profiler.start_section("mask") + masked_content = self.masker.mask(log_message) + self.profiler.end_section() + + self.profiler.start_section("drain") + cluster, change_type = self.drain.add_log_message(masked_content) + self.profiler.end_section("drain") + result = { + "change_type": change_type, + "cluster_id": cluster.cluster_id, + "cluster_size": cluster.size, + "template_mined": cluster.get_template(), + "cluster_count": len(self.drain.clusters) + } + + if self.persistence_handler is not None: + self.profiler.start_section("save_state") + snapshot_reason = self.get_snapshot_reason(change_type, cluster.cluster_id) + if snapshot_reason: + self.save_state(snapshot_reason) + self.last_save_time = time.time() + self.profiler.end_section() + + self.profiler.end_section("total") + self.profiler.report(self.config.profiling_report_sec) + return result + + def match(self, log_message: str, full_search_strategy="never") -> LogCluster: + """ + Mask log message and match against an already existing cluster. + Match shall be perfect (sim_th=1.0). + New cluster will not be created as a result of this call, nor any cluster modifications. + + :param log_message: log message to match + :param full_search_strategy: when to perform full cluster search. + (1) "never" is the fastest, will always perform a tree search [O(log(n)] but might produce + false negatives (wrong mismatches) on some edge cases; + (2) "fallback" will perform a linear search [O(n)] among all clusters with the same token count, but only in + case tree search found no match. + It should not have false negatives, however tree-search may find a non-optimal match with + more wildcard parameters than necessary; + (3) "always" is the slowest. It will select the best match among all known clusters, by always evaluating + all clusters with the same token count, and selecting the cluster with perfect all token match and least + count of wildcard matches. + :return: Matched cluster or None if no match found. + """ + + masked_content = self.masker.mask(log_message) + matched_cluster = self.drain.match(masked_content, full_search_strategy) + return matched_cluster + + def get_parameter_list(self, log_template: str, log_message: str) -> List[str]: + """ + Extract parameters from a log message according to a provided template that was generated + by calling `add_log_message()`. + + This function is deprecated. Please use extract_parameters instead. + + :param log_template: log template corresponding to the log message + :param log_message: log message to extract parameters from + :return: An ordered list of parameter values present in the log message. + """ + + extracted_parameters = self.extract_parameters(log_template, log_message, exact_matching=False) + if not extracted_parameters: + return [] + return [parameter.value for parameter in extracted_parameters] + + def extract_parameters(self, + log_template: str, + log_message: str, + exact_matching: bool = True) -> Optional[List[ExtractedParameter]]: + """ + Extract parameters from a log message according to a provided template that was generated + by calling `add_log_message()`. + + For most accurate results, it is recommended that + - Each `MaskingInstruction` has a unique `mask_with` value, + - No `MaskingInstruction` has a `mask_with` value of `*`, + - The regex-patterns of `MaskingInstruction` do not use unnamed back-references; + instead use back-references to named groups e.g. `(?P=some-name)`. + + :param log_template: log template corresponding to the log message + :param log_message: log message to extract parameters from + :param exact_matching: whether to apply the correct masking-patterns to match parameters, or try to approximate; + disabling exact_matching may be faster but may lead to situations in which parameters + are wrongly identified. + :return: A ordered list of ExtractedParameter for the log message + or None if log_message does not correspond to log_template. + """ + + for delimiter in self.config.drain_extra_delimiters: + log_message = re.sub(delimiter, " ", log_message) + + template_regex, param_group_name_to_mask_name = self._get_template_parameter_extraction_regex( + log_template, exact_matching) + + # Parameters are represented by specific named groups inside template_regex. + parameter_match = re.match(template_regex, log_message) + + # log template does not match template + if not parameter_match: + return None + + # create list of extracted parameters + extracted_parameters = [] + for group_name, parameter in parameter_match.groupdict().items(): + if group_name in param_group_name_to_mask_name: + mask_name = param_group_name_to_mask_name[group_name] + extracted_parameter = ExtractedParameter(parameter, mask_name) + extracted_parameters.append(extracted_parameter) + + return extracted_parameters + + @cachedmethod(lambda self: self.parameter_extraction_cache) + def _get_template_parameter_extraction_regex(self, log_template: str, exact_matching: bool): + param_group_name_to_mask_name = dict() + param_name_counter = [0] + + def get_next_param_name(): + param_group_name = "p_" + str(param_name_counter[0]) + param_name_counter[0] += 1 + return param_group_name + + # Create a named group with the respective patterns for the given mask-name. + def create_capture_regex(_mask_name): + allowed_patterns = [] + if exact_matching: + # get all possible regex patterns from masking instructions that match this mask name + masking_instructions = self.masker.instructions_by_mask_name(_mask_name) + for mi in masking_instructions: + # MaskingInstruction may already contain named groups. + # We replace group names in those named groups, to avoid conflicts due to duplicate names. + if hasattr(mi, 'regex'): + mi_groups = mi.regex.groupindex.keys() + pattern = mi.pattern + else: + # non regex masking instructions - support only non-exact matching + mi_groups = [] + pattern = ".+?" + + for group_name in mi_groups: + param_group_name = get_next_param_name() + + def replace_captured_param_name(param_pattern): + _search_str = param_pattern.format(group_name) + _replace_str = param_pattern.format(param_group_name) + return pattern.replace(_search_str, _replace_str) + + pattern = replace_captured_param_name("(?P={}") + pattern = replace_captured_param_name("(?P<{}>") + + # support unnamed back-references in masks (simple cases only) + pattern = re.sub(r"\\(?!0)\d{1,2}", r"(?:.+?)", pattern) + allowed_patterns.append(pattern) + + if not exact_matching or _mask_name == "*": + allowed_patterns.append(r".+?") + + # Give each capture group a unique name to avoid conflicts. + param_group_name = get_next_param_name() + param_group_name_to_mask_name[param_group_name] = _mask_name + joined_patterns = "|".join(allowed_patterns) + capture_regex = "(?P<{}>{})".format(param_group_name, joined_patterns) + return capture_regex + + # For every mask in the template, replace it with a named group of all + # possible masking-patterns it could represent (in order). + mask_names = set(self.masker.mask_names) + + # the Drain catch-all mask + mask_names.add("*") + + escaped_prefix = re.escape(self.masker.mask_prefix) + escaped_suffix = re.escape(self.masker.mask_suffix) + template_regex = re.escape(log_template) + + # replace each mask name with a proper regex that captures it + for mask_name in mask_names: + search_str = escaped_prefix + re.escape(mask_name) + escaped_suffix + while True: + rep_str = create_capture_regex(mask_name) + # Replace one-by-one to get a new param group name for each replacement. + template_regex_new = template_regex.replace(search_str, rep_str, 1) + # Break when all replaces for this mask are done. + if template_regex_new == template_regex: + break + template_regex = template_regex_new + + # match also messages with multiple spaces or other whitespace chars between tokens + template_regex = re.sub(r"\\ ", r"\\s+", template_regex) + template_regex = "^" + template_regex + "$" + return template_regex, param_group_name_to_mask_name diff --git a/intelliaide/vendor/drain3/template_miner_config.py b/intelliaide/vendor/drain3/template_miner_config.py new file mode 100644 index 0000000..ce6c0b3 --- /dev/null +++ b/intelliaide/vendor/drain3/template_miner_config.py @@ -0,0 +1,78 @@ +# SPDX-License-Identifier: MIT + +import ast +import configparser +import json +import logging + +from drain3.masking import MaskingInstruction + +logger = logging.getLogger(__name__) + + +class TemplateMinerConfig: + def __init__(self): + self.profiling_enabled = False + self.profiling_report_sec = 60 + self.snapshot_interval_minutes = 5 + self.snapshot_compress_state = True + self.drain_extra_delimiters = [] + self.drain_sim_th = 0.4 + self.drain_depth = 4 + self.drain_max_children = 100 + self.drain_max_clusters = None + self.masking_instructions = [] + self.mask_prefix = "<" + self.mask_suffix = ">" + self.parameter_extraction_cache_capacity = 3000 + self.parametrize_numeric_tokens = True + + def load(self, config_filename: str): + parser = configparser.ConfigParser() + read_files = parser.read(config_filename) + if len(read_files) == 0: + logger.warning(f"config file not found: {config_filename}") + + section_profiling = 'PROFILING' + section_snapshot = 'SNAPSHOT' + section_drain = 'DRAIN' + section_masking = 'MASKING' + + self.profiling_enabled = parser.getboolean(section_profiling, 'enabled', + fallback=self.profiling_enabled) + self.profiling_report_sec = parser.getint(section_profiling, 'report_sec', + fallback=self.profiling_report_sec) + + self.snapshot_interval_minutes = parser.getint(section_snapshot, 'snapshot_interval_minutes', + fallback=self.snapshot_interval_minutes) + self.snapshot_compress_state = parser.getboolean(section_snapshot, 'compress_state', + fallback=self.snapshot_compress_state) + + drain_extra_delimiters_str = parser.get(section_drain, 'extra_delimiters', + fallback=str(self.drain_extra_delimiters)) + self.drain_extra_delimiters = ast.literal_eval(drain_extra_delimiters_str) + + self.drain_sim_th = parser.getfloat(section_drain, 'sim_th', + fallback=self.drain_sim_th) + self.drain_depth = parser.getint(section_drain, 'depth', + fallback=self.drain_depth) + self.drain_max_children = parser.getint(section_drain, 'max_children', + fallback=self.drain_max_children) + self.drain_max_clusters = parser.getint(section_drain, 'max_clusters', + fallback=self.drain_max_clusters) + self.parametrize_numeric_tokens = parser.getboolean(section_drain, 'parametrize_numeric_tokens', + fallback=self.parametrize_numeric_tokens) + + masking_instructions_str = parser.get(section_masking, 'masking', + fallback=str(self.masking_instructions)) + self.mask_prefix = parser.get(section_masking, 'mask_prefix', fallback=self.mask_prefix) + self.mask_suffix = parser.get(section_masking, 'mask_suffix', fallback=self.mask_suffix) + self.parameter_extraction_cache_capacity = parser.get(section_masking, 'parameter_extraction_cache_capacity', + fallback=self.parameter_extraction_cache_capacity) + + masking_instructions = [] + masking_list = json.loads(masking_instructions_str) + for mi in masking_list: + instruction = MaskingInstruction(mi['regex_pattern'], mi['mask_with']) + masking_instructions.append(instruction) + self.masking_instructions = masking_instructions diff --git a/intelliaide/vendor/google/auth/__init__.py b/intelliaide/vendor/google/auth/__init__.py new file mode 100644 index 0000000..927efdd --- /dev/null +++ b/intelliaide/vendor/google/auth/__init__.py @@ -0,0 +1,34 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Auth Library for Python.""" + +import logging + +from google.auth import version as google_auth_version +from google.auth._default import ( + default, + load_credentials_from_dict, + load_credentials_from_file, +) + + +__version__ = google_auth_version.__version__ + + +__all__ = ["default", "load_credentials_from_file", "load_credentials_from_dict"] + + +# Set default logging handler to avoid "No handler found" warnings. +logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/intelliaide/vendor/google/auth/_agent_identity_utils.py b/intelliaide/vendor/google/auth/_agent_identity_utils.py new file mode 100644 index 0000000..8a1eddb --- /dev/null +++ b/intelliaide/vendor/google/auth/_agent_identity_utils.py @@ -0,0 +1,317 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helpers for Agent Identity credentials.""" + +import base64 +import hashlib +import logging +import os +import re +import time +from urllib.parse import quote, urlparse + +from google.auth import environment_vars, exceptions + +_LOGGER = logging.getLogger(__name__) + +CRYPTOGRAPHY_NOT_FOUND_ERROR = ( + "The cryptography library is required for certificate-based authentication." + "Please install it with `pip install google-auth[cryptography]`." +) + +# SPIFFE trust domain patterns for Agent Identities. +_AGENT_IDENTITY_SPIFFE_TRUST_DOMAIN_PATTERNS = [ + r"^agents\.global\.org-\d+\.system\.id\.goog$", + r"^agents\.global\.proj-\d+\.system\.id\.goog$", + r"^agents-nonprod\.global\.org-\d+\.system\.id\.goog$", + r"^agents-nonprod\.global\.proj-\d+\.system\.id\.goog$", +] + +_WELL_KNOWN_CERT_PATH = "/var/run/secrets/workload-spiffe-credentials/certificates.pem" + +# Constants for polling the certificate file. +_FAST_POLL_CYCLES = 50 +_FAST_POLL_INTERVAL = 0.1 # 100ms +_SLOW_POLL_INTERVAL = 0.5 # 500ms +_TOTAL_TIMEOUT = 30 # seconds + +# Calculate the number of slow poll cycles based on the total timeout. +_SLOW_POLL_CYCLES = int( + (_TOTAL_TIMEOUT - (_FAST_POLL_CYCLES * _FAST_POLL_INTERVAL)) / _SLOW_POLL_INTERVAL +) + +_POLLING_INTERVALS = ([_FAST_POLL_INTERVAL] * _FAST_POLL_CYCLES) + ( + [_SLOW_POLL_INTERVAL] * _SLOW_POLL_CYCLES +) + + +def _is_certificate_file_ready(path): + """Checks if a file exists and is not empty.""" + return path and os.path.exists(path) and os.path.getsize(path) > 0 + + +def get_agent_identity_certificate_path(): + """Gets the certificate path from the certificate config file. + + The path to the certificate config file is read from the + GOOGLE_API_CERTIFICATE_CONFIG environment variable. This function + implements a retry mechanism to handle cases where the environment + variable is set before the files are available on the filesystem. + + Returns: + str: The path to the leaf certificate file. + + Raises: + google.auth.exceptions.RefreshError: If the certificate config file + or the certificate file cannot be found after retries. + """ + import json + + cert_config_path = os.environ.get(environment_vars.GOOGLE_API_CERTIFICATE_CONFIG) + + # Check if the well-known workload directory is mounted. + well_known_dir = os.path.dirname(_WELL_KNOWN_CERT_PATH) + has_well_known_dir = os.path.exists(well_known_dir) + + # If we have neither a config path nor a well-known mount directory, exit immediately. + if not cert_config_path and not has_well_known_dir: + return None + + has_logged_config_warning = False + has_logged_cert_warning = False + + for interval in _POLLING_INTERVALS: + try: + # Path A: Config file is explicitly set + if cert_config_path: + with open(cert_config_path, "r") as f: + cert_config = json.load(f) + + cert_configs = ( + cert_config.get("cert_configs") + if isinstance(cert_config, dict) + else None + ) + workload_config = ( + cert_configs.get("workload") + if isinstance(cert_configs, dict) + else None + ) + + if ( + not isinstance(workload_config, dict) + or "cert_path" not in workload_config + ): + return None + + cert_path = workload_config["cert_path"] + if _is_certificate_file_ready(cert_path): + return cert_path + + # The config was parsed, but the cert file is not ready yet + target_path = cert_path + + # Path B: Config is NOT set, fallback to the well-known path + else: + if _is_certificate_file_ready(_WELL_KNOWN_CERT_PATH): + return _WELL_KNOWN_CERT_PATH + + # The well-known cert file is not ready yet + target_path = _WELL_KNOWN_CERT_PATH + + # Log a warning on the first failed attempt to load the certificate file + if not has_logged_cert_warning: + _LOGGER.warning( + "Certificate file not ready at %s. Retrying until startup timeout (up to %s seconds total)...", + target_path, + _TOTAL_TIMEOUT, + ) + has_logged_cert_warning = True + + except (IOError, ValueError, KeyError) as e: + if cert_config_path and os.path.exists(cert_config_path): + # If the file exists but has invalid JSON or is unreadable, + # we assume it is in its final format and fail-fast by returning None. + return None + + if not has_logged_config_warning and cert_config_path: + _LOGGER.warning( + "Certificate config file not found or incomplete: %s (from %s " + "environment variable). Retrying until startup timeout (up to %s seconds total)...", + e, + environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, + _TOTAL_TIMEOUT, + ) + has_logged_config_warning = True + pass + + # A sleep is required in two cases: + # 1. The config file is not found (the except block). + # 2. The config file/well-known path is found, but the certificate is not yet available. + # In both cases, we need to poll, so we sleep on every iteration + # that doesn't return a certificate. + time.sleep(interval) + + raise exceptions.RefreshError( + "Certificate config or certificate file not found after multiple retries. " + f"Token binding protection is failing. You can turn off this protection by setting " + f"{environment_vars.GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES} to false " + "to fall back to unbound tokens." + ) + + +def get_and_parse_agent_identity_certificate(): + """Gets and parses the agent identity certificate if not opted out. + + Checks if the user has opted out of certificate-bound tokens. If not, + it gets the certificate path, reads the file, and parses it. + + Returns: + The parsed certificate object if found and not opted out, otherwise None. + """ + # If the user has opted out of cert bound tokens, there is no need to + # look up the certificate. + is_opted_out = ( + os.environ.get( + environment_vars.GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES, + "true", + ).lower() + == "false" + ) + if is_opted_out: + return None + + cert_path = get_agent_identity_certificate_path() + if not cert_path: + return None + + with open(cert_path, "rb") as cert_file: + cert_bytes = cert_file.read() + + return parse_certificate(cert_bytes) + + +def parse_certificate(cert_bytes): + """Parses a PEM-encoded certificate. + + Args: + cert_bytes (bytes): The PEM-encoded certificate bytes. + + Returns: + cryptography.x509.Certificate: The parsed certificate object. + """ + try: + from cryptography import x509 + + return x509.load_pem_x509_certificate(cert_bytes) + except ImportError as e: + raise ImportError(CRYPTOGRAPHY_NOT_FOUND_ERROR) from e + + +def _is_agent_identity_certificate(cert): + """Checks if a certificate is an Agent Identity certificate. + + This is determined by checking the Subject Alternative Name (SAN) for a + SPIFFE ID with a trust domain matching Agent Identity patterns. + + Args: + cert (cryptography.x509.Certificate): The parsed certificate object. + + Returns: + bool: True if the certificate is an Agent Identity certificate, + False otherwise. + """ + try: + from cryptography import x509 + from cryptography.x509.oid import ExtensionOID + + try: + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + except x509.ExtensionNotFound: + return False + uris = ext.value.get_values_for_type(x509.UniformResourceIdentifier) + + for uri in uris: + parsed_uri = urlparse(uri) + if parsed_uri.scheme == "spiffe": + trust_domain = parsed_uri.netloc + for pattern in _AGENT_IDENTITY_SPIFFE_TRUST_DOMAIN_PATTERNS: + if re.match(pattern, trust_domain): + return True + return False + except ImportError as e: + raise ImportError(CRYPTOGRAPHY_NOT_FOUND_ERROR) from e + + +def calculate_certificate_fingerprint(cert): + """Calculates the URL-encoded, unpadded, base64-encoded SHA256 hash of a + DER-encoded certificate. + + Args: + cert (cryptography.x509.Certificate): The parsed certificate object. + + Returns: + str: The URL-encoded, unpadded, base64-encoded SHA256 fingerprint. + """ + try: + from cryptography.hazmat.primitives import serialization + + der_cert = cert.public_bytes(serialization.Encoding.DER) + fingerprint = hashlib.sha256(der_cert).digest() + # The certificate fingerprint is generated in two steps to align with GFE's + # expectations and ensure proper URL transmission: + # 1. Standard base64 encoding is applied, and padding ('=') is removed. + # 2. The resulting string is then URL-encoded to handle special characters + # ('+', '/') that would otherwise be misinterpreted in URL parameters. + base64_fingerprint = base64.b64encode(fingerprint).decode("utf-8") + unpadded_base64_fingerprint = base64_fingerprint.rstrip("=") + return quote(unpadded_base64_fingerprint) + except ImportError as e: + raise ImportError(CRYPTOGRAPHY_NOT_FOUND_ERROR) from e + + +def should_request_bound_token(cert): + """Determines if a bound token should be requested. + + This is based on the GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES + environment variable and whether the certificate is an agent identity cert. + + Args: + cert (cryptography.x509.Certificate): The parsed certificate object. + + Returns: + bool: True if a bound token should be requested, False otherwise. + """ + is_agent_cert = _is_agent_identity_certificate(cert) + is_opted_in = ( + os.environ.get( + environment_vars.GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES, + "true", + ).lower() + == "true" + ) + return is_agent_cert and is_opted_in + + +def get_cached_cert_fingerprint(cached_cert): + """Returns the fingerprint of the cached certificate.""" + if cached_cert: + cert_obj = parse_certificate(cached_cert) + cached_cert_fingerprint = calculate_certificate_fingerprint(cert_obj) + else: + raise ValueError("mTLS connection is not configured.") + return cached_cert_fingerprint diff --git a/intelliaide/vendor/google/auth/_cache.py b/intelliaide/vendor/google/auth/_cache.py new file mode 100644 index 0000000..0a4a2af --- /dev/null +++ b/intelliaide/vendor/google/auth/_cache.py @@ -0,0 +1,64 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from collections import OrderedDict + + +class LRUCache(dict): + def __init__(self, maxsize): + super().__init__() + self._order = OrderedDict() + self.maxsize = maxsize + + def clear(self): + super().clear() + self._order.clear() + + def get(self, key, default=None): + try: + value = super().__getitem__(key) + self._update(key) + return value + except KeyError: + return default + + def __getitem__(self, key): + value = super().__getitem__(key) + self._update(key) + return value + + def __setitem__(self, key, value): + maxsize = self.maxsize + if maxsize <= 0: + return + if key not in self: + while len(self) >= maxsize: + self.popitem() + super().__setitem__(key, value) + self._update(key) + + def __delitem__(self, key): + super().__delitem__(key) + del self._order[key] + + def popitem(self): + """Remove and return the least recently used key-value pair.""" + key, _ = self._order.popitem(last=False) + return key, super().pop(key) + + def _update(self, key): + try: + self._order.move_to_end(key) + except KeyError: + self._order[key] = None diff --git a/intelliaide/vendor/google/auth/_cloud_sdk.py b/intelliaide/vendor/google/auth/_cloud_sdk.py new file mode 100644 index 0000000..85b3c4f --- /dev/null +++ b/intelliaide/vendor/google/auth/_cloud_sdk.py @@ -0,0 +1,153 @@ +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helpers for reading the Google Cloud SDK's configuration.""" + +import os +import subprocess + +from google.auth import _helpers +from google.auth import environment_vars +from google.auth import exceptions + + +# The ~/.config subdirectory containing gcloud credentials. +_CONFIG_DIRECTORY = "gcloud" +# Windows systems store config at %APPDATA%\gcloud +_WINDOWS_CONFIG_ROOT_ENV_VAR = "APPDATA" +# The name of the file in the Cloud SDK config that contains default +# credentials. +_CREDENTIALS_FILENAME = "application_default_credentials.json" +# The name of the Cloud SDK shell script +_CLOUD_SDK_POSIX_COMMAND = "gcloud" +_CLOUD_SDK_WINDOWS_COMMAND = "gcloud.cmd" +# The command to get the Cloud SDK configuration +_CLOUD_SDK_CONFIG_GET_PROJECT_COMMAND = ("config", "get", "project") +# The command to get google user access token +_CLOUD_SDK_USER_ACCESS_TOKEN_COMMAND = ("auth", "print-access-token") +# Cloud SDK's application-default client ID +CLOUD_SDK_CLIENT_ID = ( + "764086051850-6qr4p6gpi6hn506pt8ejuq83di341hur.apps.googleusercontent.com" +) + + +def get_config_path(): + """Returns the absolute path the the Cloud SDK's configuration directory. + + Returns: + str: The Cloud SDK config path. + """ + # If the path is explicitly set, return that. + try: + return os.environ[environment_vars.CLOUD_SDK_CONFIG_DIR] + except KeyError: + pass + + # Non-windows systems store this at ~/.config/gcloud + if os.name != "nt": + return os.path.join(os.path.expanduser("~"), ".config", _CONFIG_DIRECTORY) + # Windows systems store config at %APPDATA%\gcloud + else: + try: + return os.path.join( + os.environ[_WINDOWS_CONFIG_ROOT_ENV_VAR], _CONFIG_DIRECTORY + ) + except KeyError: + # This should never happen unless someone is really + # messing with things, but we'll cover the case anyway. + drive = os.environ.get("SystemDrive", "C:") + return os.path.join(drive, "\\", _CONFIG_DIRECTORY) + + +def get_application_default_credentials_path(): + """Gets the path to the application default credentials file. + + The path may or may not exist. + + Returns: + str: The full path to application default credentials. + """ + config_path = get_config_path() + return os.path.join(config_path, _CREDENTIALS_FILENAME) + + +def _run_subprocess_ignore_stderr(command): + """Return subprocess.check_output with the given command and ignores stderr.""" + with open(os.devnull, "w") as devnull: + output = subprocess.check_output(command, stderr=devnull) + return output + + +def get_project_id(): + """Gets the project ID from the Cloud SDK. + + Returns: + Optional[str]: The project ID. + """ + if os.name == "nt": + command = _CLOUD_SDK_WINDOWS_COMMAND + else: + command = _CLOUD_SDK_POSIX_COMMAND + + try: + # Ignore the stderr coming from gcloud, so it won't be mixed into the output. + # https://github.com/googleapis/google-auth-library-python/issues/673 + project = _run_subprocess_ignore_stderr( + (command,) + _CLOUD_SDK_CONFIG_GET_PROJECT_COMMAND + ) + + # Turn bytes into a string and remove "\n" + project = _helpers.from_bytes(project).strip() + return project if project else None + except (subprocess.CalledProcessError, OSError, IOError): + return None + + +def get_auth_access_token(account=None): + """Load user access token with the ``gcloud auth print-access-token`` command. + + Args: + account (Optional[str]): Account to get the access token for. If not + specified, the current active account will be used. + + Returns: + str: The user access token. + + Raises: + google.auth.exceptions.UserAccessTokenError: if failed to get access + token from gcloud. + """ + if os.name == "nt": + command = _CLOUD_SDK_WINDOWS_COMMAND + else: + command = _CLOUD_SDK_POSIX_COMMAND + + try: + if account: + command = ( + (command,) + + _CLOUD_SDK_USER_ACCESS_TOKEN_COMMAND + + ("--account=" + account,) + ) + else: + command = (command,) + _CLOUD_SDK_USER_ACCESS_TOKEN_COMMAND + + access_token = subprocess.check_output(command, stderr=subprocess.STDOUT) + # remove the trailing "\n" + return access_token.decode("utf-8").strip() + except (subprocess.CalledProcessError, OSError, IOError) as caught_exc: + new_exc = exceptions.UserAccessTokenError( + "Failed to obtain access token", caught_exc + ) + raise new_exc from caught_exc diff --git a/intelliaide/vendor/google/auth/_credentials_async.py b/intelliaide/vendor/google/auth/_credentials_async.py new file mode 100644 index 0000000..760758d --- /dev/null +++ b/intelliaide/vendor/google/auth/_credentials_async.py @@ -0,0 +1,171 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Interfaces for credentials.""" + +import abc +import inspect + +from google.auth import credentials + + +class Credentials(credentials.Credentials, metaclass=abc.ABCMeta): + """Async inherited credentials class from google.auth.credentials. + The added functionality is the before_request call which requires + async/await syntax. + All credentials have a :attr:`token` that is used for authentication and + may also optionally set an :attr:`expiry` to indicate when the token will + no longer be valid. + + Most credentials will be :attr:`invalid` until :meth:`refresh` is called. + Credentials can do this automatically before the first HTTP request in + :meth:`before_request`. + + Although the token and expiration will change as the credentials are + :meth:`refreshed ` and used, credentials should be considered + immutable. Various credentials will accept configuration such as private + keys, scopes, and other options. These options are not changeable after + construction. Some classes will provide mechanisms to copy the credentials + with modifications such as :meth:`ScopedCredentials.with_scopes`. + """ + + async def before_request(self, request, method, url, headers): + """Performs credential-specific before request logic. + + Refreshes the credentials if necessary, then calls :meth:`apply` to + apply the token to the authentication header. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + method (str): The request's HTTP method or the RPC method being + invoked. + url (str): The request's URI or the RPC service's URI. + headers (Mapping): The request's headers. + """ + # pylint: disable=unused-argument + # (Subclasses may use these arguments to ascertain information about + # the http request.) + + if not self.valid: + if inspect.iscoroutinefunction(self.refresh): + await self.refresh(request) + else: + self.refresh(request) + self.apply(headers) + + +class CredentialsWithQuotaProject(credentials.CredentialsWithQuotaProject): + """Abstract base for credentials supporting ``with_quota_project`` factory""" + + +class AnonymousCredentials(credentials.AnonymousCredentials, Credentials): + """Credentials that do not provide any authentication information. + + These are useful in the case of services that support anonymous access or + local service emulators that do not use credentials. This class inherits + from the sync anonymous credentials file, but is kept if async credentials + is initialized and we would like anonymous credentials. + """ + + +class ReadOnlyScoped(credentials.ReadOnlyScoped, metaclass=abc.ABCMeta): + """Interface for credentials whose scopes can be queried. + + OAuth 2.0-based credentials allow limiting access using scopes as described + in `RFC6749 Section 3.3`_. + If a credential class implements this interface then the credentials either + use scopes in their implementation. + + Some credentials require scopes in order to obtain a token. You can check + if scoping is necessary with :attr:`requires_scopes`:: + + if credentials.requires_scopes: + # Scoping is required. + credentials = _credentials_async.with_scopes(scopes=['one', 'two']) + + Credentials that require scopes must either be constructed with scopes:: + + credentials = SomeScopedCredentials(scopes=['one', 'two']) + + Or must copy an existing instance using :meth:`with_scopes`:: + + scoped_credentials = _credentials_async.with_scopes(scopes=['one', 'two']) + + Some credentials have scopes but do not allow or require scopes to be set, + these credentials can be used as-is. + + .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3 + """ + + +class Scoped(credentials.Scoped): + """Interface for credentials whose scopes can be replaced while copying. + + OAuth 2.0-based credentials allow limiting access using scopes as described + in `RFC6749 Section 3.3`_. + If a credential class implements this interface then the credentials either + use scopes in their implementation. + + Some credentials require scopes in order to obtain a token. You can check + if scoping is necessary with :attr:`requires_scopes`:: + + if credentials.requires_scopes: + # Scoping is required. + credentials = _credentials_async.create_scoped(['one', 'two']) + + Credentials that require scopes must either be constructed with scopes:: + + credentials = SomeScopedCredentials(scopes=['one', 'two']) + + Or must copy an existing instance using :meth:`with_scopes`:: + + scoped_credentials = credentials.with_scopes(scopes=['one', 'two']) + + Some credentials have scopes but do not allow or require scopes to be set, + these credentials can be used as-is. + + .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3 + """ + + +def with_scopes_if_required(credentials, scopes): + """Creates a copy of the credentials with scopes if scoping is required. + + This helper function is useful when you do not know (or care to know) the + specific type of credentials you are using (such as when you use + :func:`google.auth.default`). This function will call + :meth:`Scoped.with_scopes` if the credentials are scoped credentials and if + the credentials require scoping. Otherwise, it will return the credentials + as-is. + + Args: + credentials (google.auth.credentials.Credentials): The credentials to + scope if necessary. + scopes (Sequence[str]): The list of scopes to use. + + Returns: + google.auth._credentials_async.Credentials: Either a new set of scoped + credentials, or the passed in credentials instance if no scoping + was required. + """ + if isinstance(credentials, Scoped) and credentials.requires_scopes: + return credentials.with_scopes(scopes) + else: + return credentials + + +class Signing(credentials.Signing, metaclass=abc.ABCMeta): + """Interface for credentials that can cryptographically sign messages.""" diff --git a/intelliaide/vendor/google/auth/_credentials_base.py b/intelliaide/vendor/google/auth/_credentials_base.py new file mode 100644 index 0000000..64d5ce3 --- /dev/null +++ b/intelliaide/vendor/google/auth/_credentials_base.py @@ -0,0 +1,75 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Interface for base credentials.""" + +import abc + +from google.auth import _helpers + + +class _BaseCredentials(metaclass=abc.ABCMeta): + """Base class for all credentials. + + All credentials have a :attr:`token` that is used for authentication and + may also optionally set an :attr:`expiry` to indicate when the token will + no longer be valid. + + Most credentials will be :attr:`invalid` until :meth:`refresh` is called. + Credentials can do this automatically before the first HTTP request in + :meth:`before_request`. + + Although the token and expiration will change as the credentials are + :meth:`refreshed ` and used, credentials should be considered + immutable. Various credentials will accept configuration such as private + keys, scopes, and other options. These options are not changeable after + construction. Some classes will provide mechanisms to copy the credentials + with modifications such as :meth:`ScopedCredentials.with_scopes`. + + Attributes: + token (Optional[str]): The bearer token that can be used in HTTP headers to make + authenticated requests. + """ + + def __init__(self): + self.token = None + + @abc.abstractmethod + def refresh(self, request): + """Refreshes the access token. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If the credentials could + not be refreshed. + """ + # pylint: disable=missing-raises-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError("Refresh must be implemented") + + def _apply(self, headers, token=None): + """Apply the token to the authentication header. + + Args: + headers (Mapping): The HTTP request headers. + token (Optional[str]): If specified, overrides the current access + token. + """ + headers["authorization"] = "Bearer {}".format( + _helpers.from_bytes(token or self.token) + ) diff --git a/intelliaide/vendor/google/auth/_default.py b/intelliaide/vendor/google/auth/_default.py new file mode 100644 index 0000000..cb40c1f --- /dev/null +++ b/intelliaide/vendor/google/auth/_default.py @@ -0,0 +1,748 @@ +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Application default credentials. + +Implements application default credentials and project ID detection. +""" +from __future__ import annotations + +import io +import json +import logging +import os +from typing import Optional, Sequence, TYPE_CHECKING +import warnings + +from google.auth import environment_vars +from google.auth import exceptions + +if TYPE_CHECKING: # pragma: NO COVER + import google.auth.credentials.Credentials # type: ignore + import google.auth.transport.Request # type: ignore + +_LOGGER = logging.getLogger(__name__) + +# Valid types accepted for file-based credentials. +_AUTHORIZED_USER_TYPE = "authorized_user" +_SERVICE_ACCOUNT_TYPE = "service_account" +_EXTERNAL_ACCOUNT_TYPE = "external_account" +_EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE = "external_account_authorized_user" +_IMPERSONATED_SERVICE_ACCOUNT_TYPE = "impersonated_service_account" +_GDCH_SERVICE_ACCOUNT_TYPE = "gdch_service_account" +_VALID_TYPES = ( + _AUTHORIZED_USER_TYPE, + _SERVICE_ACCOUNT_TYPE, + _EXTERNAL_ACCOUNT_TYPE, + _EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE, + _IMPERSONATED_SERVICE_ACCOUNT_TYPE, + _GDCH_SERVICE_ACCOUNT_TYPE, +) + +# Help message when no credentials can be found. +_CLOUD_SDK_MISSING_CREDENTIALS = """\ +Your default credentials were not found. To set up Application Default Credentials, \ +see https://cloud.google.com/docs/authentication/external/set-up-adc for more information.\ +""" + +# Warning when using Cloud SDK user credentials +_CLOUD_SDK_CREDENTIALS_WARNING = """\ +Your application has authenticated using end user credentials from Google \ +Cloud SDK without a quota project. You might receive a "quota exceeded" \ +or "API not enabled" error. See the following page for troubleshooting: \ +https://cloud.google.com/docs/authentication/adc-troubleshooting/user-creds. \ +""" + +_GENERIC_LOAD_METHOD_WARNING = """\ +The {} method is deprecated because of a potential security risk. + +This method does not validate the credential configuration. The security +risk occurs when a credential configuration is accepted from a source that +is not under your control and used without validation on your side. + +If you know that you will be loading credential configurations of a +specific type, it is recommended to use a credential-type-specific +load method. +This will ensure that an unexpected credential type with potential for +malicious intent is not loaded unintentionally. You might still have to do +validation for certain credential types. Please follow the recommendations +for that method. For example, if you want to load only service accounts, +you can create the service account credentials explicitly: + +``` +from google.oauth2 import service_account +creds = service_account.Credentials.from_service_account_file(filename) +``` + +If you are loading your credential configuration from an untrusted source and have +not mitigated the risks (e.g. by validating the configuration yourself), make +these changes as soon as possible to prevent security risks to your environment. + +Regardless of the method used, it is always your responsibility to validate +configurations received from external sources. + +Refer to https://cloud.google.com/docs/authentication/external/externally-sourced-credentials +for more details. +""" + +# The subject token type used for AWS external_account credentials. +_AWS_SUBJECT_TOKEN_TYPE = "urn:ietf:params:aws:token-type:aws4_request" + + +def _warn_about_problematic_credentials(credentials): + """Determines if the credentials are problematic. + + Credentials from the Cloud SDK that are associated with Cloud SDK's project + are problematic because they may not have APIs enabled and have limited + quota. If this is the case, warn about it. + """ + from google.auth import _cloud_sdk + + if credentials.client_id == _cloud_sdk.CLOUD_SDK_CLIENT_ID: + warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING) + + +def _warn_about_generic_load_method(method_name): # pragma: NO COVER + """Warns that a generic load method is being used. + + This is to discourage use of the generic load methods in favor of + more specific methods. The generic methods are more likely to lead to + security issues if the input is not validated. + + Args: + method_name (str): The name of the method being used. + """ + + warnings.warn(_GENERIC_LOAD_METHOD_WARNING.format(method_name), DeprecationWarning) + + +def load_credentials_from_file( + filename, scopes=None, default_scopes=None, quota_project_id=None, request=None +): + """Loads Google credentials from a file. + + The credentials file must be a service account key, stored authorized + user credentials, external account credentials, or impersonated service + account credentials. + + .. warning:: + Important: If you accept a credential configuration (credential JSON/File/Stream) + from an external source for authentication to Google Cloud Platform, you must + validate it before providing it to any Google API or client library. Providing an + unvalidated credential configuration to Google APIs or libraries can compromise + the security of your systems and data. For more information, refer to + `Validate credential configurations from external sources`_. + + .. _Validate credential configurations from external sources: + https://cloud.google.com/docs/authentication/external/externally-sourced-credentials + + Args: + filename (str): The full path to the credentials file. + scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If + specified, the credentials will automatically be scoped if + necessary + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + quota_project_id (Optional[str]): The project ID used for + quota and billing. + request (Optional[google.auth.transport.Request]): An object used to make + HTTP requests. This is used to determine the associated project ID + for a workload identity pool resource (external account credentials). + If not specified, then it will use a + google.auth.transport.requests.Request client to make requests. + + Returns: + Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded + credentials and the project ID. Authorized user credentials do not + have the project ID information. External account credentials project + IDs may not always be determined. + + Raises: + google.auth.exceptions.DefaultCredentialsError: if the file is in the + wrong format or is missing. + """ + _warn_about_generic_load_method("load_credentials_from_file") + + if not os.path.exists(filename): + raise exceptions.DefaultCredentialsError( + "File {} was not found.".format(filename) + ) + + with io.open(filename, "r") as file_obj: + try: + info = json.load(file_obj) + except ValueError as caught_exc: + new_exc = exceptions.DefaultCredentialsError( + "File {} is not a valid json file.".format(filename), caught_exc + ) + raise new_exc from caught_exc + return _load_credentials_from_info( + filename, info, scopes, default_scopes, quota_project_id, request + ) + + +def load_credentials_from_dict( + info, scopes=None, default_scopes=None, quota_project_id=None, request=None +): + """Loads Google credentials from a dict. + + The credentials file must be a service account key, stored authorized + user credentials, external account credentials, or impersonated service + account credentials. + + .. warning:: + Important: If you accept a credential configuration (credential JSON/File/Stream) + from an external source for authentication to Google Cloud Platform, you must + validate it before providing it to any Google API or client library. Providing an + unvalidated credential configuration to Google APIs or libraries can compromise + the security of your systems and data. For more information, refer to + `Validate credential configurations from external sources`_. + + .. _Validate credential configurations from external sources: + https://cloud.google.com/docs/authentication/external/externally-sourced-credentials + + Args: + info (Dict[str, Any]): A dict object containing the credentials + scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If + specified, the credentials will automatically be scoped if + necessary + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + quota_project_id (Optional[str]): The project ID used for + quota and billing. + request (Optional[google.auth.transport.Request]): An object used to make + HTTP requests. This is used to determine the associated project ID + for a workload identity pool resource (external account credentials). + If not specified, then it will use a + google.auth.transport.requests.Request client to make requests. + + Returns: + Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded + credentials and the project ID. Authorized user credentials do not + have the project ID information. External account credentials project + IDs may not always be determined. + + Raises: + google.auth.exceptions.DefaultCredentialsError: if the file is in the + wrong format or is missing. + """ + _warn_about_generic_load_method("load_credentials_from_dict") + if not isinstance(info, dict): + raise exceptions.DefaultCredentialsError( + "info object was of type {} but dict type was expected.".format(type(info)) + ) + + return _load_credentials_from_info( + "dict object", info, scopes, default_scopes, quota_project_id, request + ) + + +def _load_credentials_from_info( + filename, info, scopes, default_scopes, quota_project_id, request +): + from google.auth.credentials import CredentialsWithQuotaProject + + credential_type = info.get("type") + + if credential_type == _AUTHORIZED_USER_TYPE: + credentials, project_id = _get_authorized_user_credentials( + filename, info, scopes + ) + + elif credential_type == _SERVICE_ACCOUNT_TYPE: + credentials, project_id = _get_service_account_credentials( + filename, info, scopes, default_scopes + ) + + elif credential_type == _EXTERNAL_ACCOUNT_TYPE: + credentials, project_id = _get_external_account_credentials( + info, + filename, + scopes=scopes, + default_scopes=default_scopes, + request=request, + ) + + elif credential_type == _EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE: + credentials, project_id = _get_external_account_authorized_user_credentials( + filename, info, request + ) + + elif credential_type == _IMPERSONATED_SERVICE_ACCOUNT_TYPE: + credentials, project_id = _get_impersonated_service_account_credentials( + filename, info, scopes + ) + elif credential_type == _GDCH_SERVICE_ACCOUNT_TYPE: + credentials, project_id = _get_gdch_service_account_credentials(filename, info) + else: + raise exceptions.DefaultCredentialsError( + "The file {file} does not have a valid type. " + "Type is {type}, expected one of {valid_types}.".format( + file=filename, type=credential_type, valid_types=_VALID_TYPES + ) + ) + if isinstance(credentials, CredentialsWithQuotaProject): + credentials = _apply_quota_project_id(credentials, quota_project_id) + return credentials, project_id + + +def _get_gcloud_sdk_credentials(quota_project_id=None): + """Gets the credentials and project ID from the Cloud SDK.""" + from google.auth import _cloud_sdk + + _LOGGER.debug("Checking Cloud SDK credentials as part of auth process...") + + # Check if application default credentials exist. + credentials_filename = _cloud_sdk.get_application_default_credentials_path() + + if not os.path.isfile(credentials_filename): + _LOGGER.debug("Cloud SDK credentials not found on disk; not using them") + return None, None + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + credentials, project_id = load_credentials_from_file( + credentials_filename, quota_project_id=quota_project_id + ) + credentials._cred_file_path = credentials_filename + + if not project_id: + project_id = _cloud_sdk.get_project_id() + + return credentials, project_id + + +def _get_explicit_environ_credentials(quota_project_id=None): + """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment + variable.""" + from google.auth import _cloud_sdk + + cloud_sdk_adc_path = _cloud_sdk.get_application_default_credentials_path() + explicit_file = os.environ.get(environment_vars.CREDENTIALS, "") + + _LOGGER.debug( + "Checking '%s' for explicit credentials as part of auth process...", + explicit_file, + ) + + if explicit_file != "" and explicit_file == cloud_sdk_adc_path: + # Cloud sdk flow calls gcloud to fetch project id, so if the explicit + # file path is cloud sdk credentials path, then we should fall back + # to cloud sdk flow, otherwise project id cannot be obtained. + _LOGGER.debug( + "Explicit credentials path '%s' is the same as Cloud SDK credentials path, fall back to Cloud SDK credentials flow...", + explicit_file, + ) + return _get_gcloud_sdk_credentials(quota_project_id=quota_project_id) + + if explicit_file != "": + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + credentials, project_id = load_credentials_from_file( + os.environ[environment_vars.CREDENTIALS], + quota_project_id=quota_project_id, + ) + credentials._cred_file_path = f"{explicit_file} file via the GOOGLE_APPLICATION_CREDENTIALS environment variable" + return credentials, project_id + + else: + return None, None + + +def _get_gae_credentials(): + """Gets Google App Engine App Identity credentials and project ID.""" + # If not GAE gen1, prefer the metadata service even if the GAE APIs are + # available as per https://google.aip.dev/auth/4115. + if os.environ.get(environment_vars.LEGACY_APPENGINE_RUNTIME) != "python27": + return None, None + + # While this library is normally bundled with app_engine, there are + # some cases where it's not available, so we tolerate ImportError. + try: + _LOGGER.debug("Checking for App Engine runtime as part of auth process...") + import google.auth.app_engine as app_engine + except ImportError: + _LOGGER.warning("Import of App Engine auth library failed.") + return None, None + + try: + credentials = app_engine.Credentials() + project_id = app_engine.get_project_id() + return credentials, project_id + except EnvironmentError: + _LOGGER.debug( + "No App Engine library was found so cannot authentication via App Engine Identity Credentials." + ) + return None, None + + +def _get_gce_credentials(request=None, quota_project_id=None): + """Gets credentials and project ID from the GCE Metadata Service.""" + # While this library is normally bundled with compute_engine, there are + # some cases where it's not available, so we tolerate ImportError. + # Compute Engine requires optional `requests` dependency. + try: + from google.auth import compute_engine + from google.auth.compute_engine import _metadata + import google.auth.transport.requests + except ImportError: + _LOGGER.warning("Import of Compute Engine auth library failed.") + return None, None + + if request is None: + request = google.auth.transport.requests.Request() + + if _metadata.is_on_gce(request=request): + # Get the project ID. + try: + project_id = _metadata.get_project_id(request=request) + except exceptions.TransportError: + project_id = None + + cred = compute_engine.Credentials() + cred = _apply_quota_project_id(cred, quota_project_id) + + return cred, project_id + else: + _LOGGER.warning( + "Authentication failed using Compute Engine authentication due to unavailable metadata server." + ) + return None, None + + +def _get_external_account_credentials( + info, filename, scopes=None, default_scopes=None, request=None +): + """Loads external account Credentials from the parsed external account info. + + The credentials information must correspond to a supported external account + credentials. + + Args: + info (Mapping[str, str]): The external account info in Google format. + filename (str): The full path to the credentials file. + scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If + specified, the credentials will automatically be scoped if + necessary. + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + request (Optional[google.auth.transport.Request]): An object used to make + HTTP requests. This is used to determine the associated project ID + for a workload identity pool resource (external account credentials). + If not specified, then it will use a + google.auth.transport.requests.Request client to make requests. + + Returns: + Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded + credentials and the project ID. External account credentials project + IDs may not always be determined. + + Raises: + google.auth.exceptions.DefaultCredentialsError: if the info dictionary + is in the wrong format or is missing required information. + """ + # There are currently 3 types of external_account credentials. + if info.get("subject_token_type") == _AWS_SUBJECT_TOKEN_TYPE: + # Check if configuration corresponds to an AWS credentials. + from google.auth import aws + + credentials = aws.Credentials.from_info( + info, scopes=scopes, default_scopes=default_scopes + ) + elif ( + info.get("credential_source") is not None + and info.get("credential_source").get("executable") is not None + ): + from google.auth import pluggable + + credentials = pluggable.Credentials.from_info( + info, scopes=scopes, default_scopes=default_scopes + ) + else: + try: + # Check if configuration corresponds to an Identity Pool credentials. + from google.auth import identity_pool + + credentials = identity_pool.Credentials.from_info( + info, scopes=scopes, default_scopes=default_scopes + ) + except ValueError: + # If the configuration is invalid or does not correspond to any + # supported external_account credentials, raise an error. + raise exceptions.DefaultCredentialsError( + "Failed to load external account credentials from {}".format(filename) + ) + if request is None: + import google.auth.transport.requests + + request = google.auth.transport.requests.Request() + + return credentials, credentials.get_project_id(request=request) + + +def _get_external_account_authorized_user_credentials( + filename, info, scopes=None, default_scopes=None, request=None +): + try: + from google.auth import external_account_authorized_user + + credentials = external_account_authorized_user.Credentials.from_info(info) + except ValueError: + raise exceptions.DefaultCredentialsError( + "Failed to load external account authorized user credentials from {}".format( + filename + ) + ) + + return credentials, None + + +def _get_authorized_user_credentials(filename, info, scopes=None): + from google.oauth2 import credentials + + try: + credentials = credentials.Credentials.from_authorized_user_info( + info, scopes=scopes + ) + except ValueError as caught_exc: + msg = "Failed to load authorized user credentials from {}".format(filename) + new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) + raise new_exc from caught_exc + return credentials, None + + +def _get_service_account_credentials(filename, info, scopes=None, default_scopes=None): + from google.oauth2 import service_account + + try: + credentials = service_account.Credentials.from_service_account_info( + info, scopes=scopes, default_scopes=default_scopes + ) + except ValueError as caught_exc: + msg = "Failed to load service account credentials from {}".format(filename) + new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) + raise new_exc from caught_exc + return credentials, info.get("project_id") + + +def _get_impersonated_service_account_credentials(filename, info, scopes): + from google.auth import impersonated_credentials + + try: + credentials = ( + impersonated_credentials.Credentials.from_impersonated_service_account_info( + info, scopes=scopes + ) + ) + except ValueError as caught_exc: + msg = "Failed to load impersonated service account credentials from {}".format( + filename + ) + new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) + raise new_exc from caught_exc + return credentials, None + + +def _get_gdch_service_account_credentials(filename, info): + from google.oauth2 import gdch_credentials + + try: + credentials = ( + gdch_credentials.ServiceAccountCredentials.from_service_account_info(info) + ) + except ValueError as caught_exc: + msg = "Failed to load GDCH service account credentials from {}".format(filename) + new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) + raise new_exc from caught_exc + return credentials, info.get("project") + + +def get_api_key_credentials(key): + """Return credentials with the given API key.""" + from google.auth import api_key + + return api_key.Credentials(key) + + +def _apply_quota_project_id(credentials, quota_project_id): + if quota_project_id: + credentials = credentials.with_quota_project(quota_project_id) + else: + credentials = credentials.with_quota_project_from_environment() + + from google.oauth2 import credentials as authorized_user_credentials + + if isinstance(credentials, authorized_user_credentials.Credentials) and ( + not credentials.quota_project_id + ): + _warn_about_problematic_credentials(credentials) + return credentials + + +def default( + scopes: Optional[Sequence[str]] = None, + request: Optional["google.auth.transport.Request"] = None, + quota_project_id: Optional[str] = None, + default_scopes: Optional[Sequence[str]] = None, +) -> tuple["google.auth.credentials.Credentials", Optional[str]]: + """Gets the default credentials for the current environment. + + `Application Default Credentials`_ provides an easy way to obtain + credentials to call Google APIs for server-to-server or local applications. + This function acquires credentials from the environment in the following + order: + + 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set + to the path of a valid service account JSON private key file, then it is + loaded and returned. The project ID returned is the project ID defined + in the service account file if available (some older files do not + contain project ID information). + + If the environment variable is set to the path of a valid external + account JSON configuration file (workload identity federation), then the + configuration file is used to determine and retrieve the external + credentials from the current environment (AWS, Azure, etc). + These will then be exchanged for Google access tokens via the Google STS + endpoint. + The project ID returned in this case is the one corresponding to the + underlying workload identity pool resource if determinable. + + If the environment variable is set to the path of a valid GDCH service + account JSON file (`Google Distributed Cloud Hosted`_), then a GDCH + credential will be returned. The project ID returned is the project + specified in the JSON file. + 2. If the `Google Cloud SDK`_ is installed and has application default + credentials set they are loaded and returned. + + To enable application default credentials with the Cloud SDK run:: + + gcloud auth application-default login + + If the Cloud SDK has an active project, the project ID is returned. The + active project can be set using:: + + gcloud config set project + + 3. If the application is running in the `App Engine standard environment`_ + (first generation) then the credentials and project ID from the + `App Identity Service`_ are used. + 4. If the application is running in `Compute Engine`_ or `Cloud Run`_ or + the `App Engine flexible environment`_ or the `App Engine standard + environment`_ (second generation) then the credentials and project ID + are obtained from the `Metadata Service`_. + 5. If no credentials are found, + :class:`~google.auth.exceptions.DefaultCredentialsError` will be raised. + + .. _Application Default Credentials: https://developers.google.com\ + /identity/protocols/application-default-credentials + .. _Google Cloud SDK: https://cloud.google.com/sdk + .. _App Engine standard environment: https://cloud.google.com/appengine + .. _App Identity Service: https://cloud.google.com/appengine/docs/python\ + /appidentity/ + .. _Compute Engine: https://cloud.google.com/compute + .. _App Engine flexible environment: https://cloud.google.com\ + /appengine/flexible + .. _Metadata Service: https://cloud.google.com/compute/docs\ + /storing-retrieving-metadata + .. _Cloud Run: https://cloud.google.com/run + .. _Google Distributed Cloud Hosted: https://cloud.google.com/blog/topics\ + /hybrid-cloud/announcing-google-distributed-cloud-edge-and-hosted + + Example:: + + import google.auth + + credentials, project_id = google.auth.default() + + Args: + scopes (Sequence[str]): The list of scopes for the credentials. If + specified, the credentials will automatically be scoped if + necessary. + request (Optional[google.auth.transport.Request]): An object used to make + HTTP requests. This is used to either detect whether the application + is running on Compute Engine or to determine the associated project + ID for a workload identity pool resource (external account + credentials). If not specified, then it will either use the standard + library http client to make requests for Compute Engine credentials + or a google.auth.transport.requests.Request client for external + account credentials. + quota_project_id (Optional[str]): The project ID used for + quota and billing. + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + Returns: + Tuple[~google.auth.credentials.Credentials, Optional[str]]: + the current environment's credentials and project ID. Project ID + may be None, which indicates that the Project ID could not be + ascertained from the environment. + + Raises: + ~google.auth.exceptions.DefaultCredentialsError: + If no credentials were found, or if the credentials found were + invalid. + """ + from google.auth.credentials import with_scopes_if_required + from google.auth.credentials import CredentialsWithQuotaProject + + explicit_project_id = os.environ.get( + environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT) + ) + + checkers = ( + # Avoid passing scopes here to prevent passing scopes to user credentials. + # with_scopes_if_required() below will ensure scopes/default scopes are + # safely set on the returned credentials since requires_scopes will + # guard against setting scopes on user credentials. + lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id), + lambda: _get_gcloud_sdk_credentials(quota_project_id=quota_project_id), + _get_gae_credentials, + lambda: _get_gce_credentials(request, quota_project_id=quota_project_id), + ) + + for checker in checkers: + credentials, project_id = checker() + if credentials is not None: + credentials = with_scopes_if_required( + credentials, scopes, default_scopes=default_scopes + ) + + effective_project_id = explicit_project_id or project_id + + # For external account credentials, scopes are required to determine + # the project ID. Try to get the project ID again if not yet + # determined. + if not effective_project_id and callable( + getattr(credentials, "get_project_id", None) + ): + if request is None: + import google.auth.transport.requests + + request = google.auth.transport.requests.Request() + effective_project_id = credentials.get_project_id(request=request) + + if quota_project_id and isinstance( + credentials, CredentialsWithQuotaProject + ): + credentials = credentials.with_quota_project(quota_project_id) + + if not effective_project_id: + _LOGGER.warning( + "No project ID could be determined. Consider running " + "`gcloud config set project` or setting the %s " + "environment variable", + environment_vars.PROJECT, + ) + return credentials, effective_project_id + + raise exceptions.DefaultCredentialsError(_CLOUD_SDK_MISSING_CREDENTIALS) diff --git a/intelliaide/vendor/google/auth/_default_async.py b/intelliaide/vendor/google/auth/_default_async.py new file mode 100644 index 0000000..44bc671 --- /dev/null +++ b/intelliaide/vendor/google/auth/_default_async.py @@ -0,0 +1,288 @@ +# Copyright 2020 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Application default credentials. + +Implements application default credentials and project ID detection. +""" + +import io +import json +import os +import warnings + +from google.auth import _default +from google.auth import environment_vars +from google.auth import exceptions + + +def load_credentials_from_file(filename, scopes=None, quota_project_id=None): + """Loads Google credentials from a file. + + The credentials file must be a service account key or stored authorized + user credentials. + + Args: + filename (str): The full path to the credentials file. + scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If + specified, the credentials will automatically be scoped if + necessary + quota_project_id (Optional[str]): The project ID used for + quota and billing. + + Returns: + Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded + credentials and the project ID. Authorized user credentials do not + have the project ID information. + + Raises: + google.auth.exceptions.DefaultCredentialsError: if the file is in the + wrong format or is missing. + """ + if not os.path.exists(filename): + raise exceptions.DefaultCredentialsError( + "File {} was not found.".format(filename) + ) + + with io.open(filename, "r") as file_obj: + try: + info = json.load(file_obj) + except ValueError as caught_exc: + new_exc = exceptions.DefaultCredentialsError( + "File {} is not a valid json file.".format(filename), caught_exc + ) + raise new_exc from caught_exc + + # The type key should indicate that the file is either a service account + # credentials file or an authorized user credentials file. + credential_type = info.get("type") + + if credential_type == _default._AUTHORIZED_USER_TYPE: + from google.oauth2 import _credentials_async as credentials + + try: + credentials = credentials.Credentials.from_authorized_user_info( + info, scopes=scopes + ) + except ValueError as caught_exc: + msg = "Failed to load authorized user credentials from {}".format(filename) + new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) + raise new_exc from caught_exc + if quota_project_id: + credentials = credentials.with_quota_project(quota_project_id) + if not credentials.quota_project_id: + _default._warn_about_problematic_credentials(credentials) + return credentials, None + + elif credential_type == _default._SERVICE_ACCOUNT_TYPE: + from google.oauth2 import _service_account_async as service_account + + try: + credentials = service_account.Credentials.from_service_account_info( + info, scopes=scopes + ).with_quota_project(quota_project_id) + except ValueError as caught_exc: + msg = "Failed to load service account credentials from {}".format(filename) + new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) + raise new_exc from caught_exc + return credentials, info.get("project_id") + + else: + raise exceptions.DefaultCredentialsError( + "The file {file} does not have a valid type. " + "Type is {type}, expected one of {valid_types}.".format( + file=filename, type=credential_type, valid_types=_default._VALID_TYPES + ) + ) + + +def _get_gcloud_sdk_credentials(quota_project_id=None): + """Gets the credentials and project ID from the Cloud SDK.""" + from google.auth import _cloud_sdk + + # Check if application default credentials exist. + credentials_filename = _cloud_sdk.get_application_default_credentials_path() + + if not os.path.isfile(credentials_filename): + return None, None + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + credentials, project_id = load_credentials_from_file( + credentials_filename, quota_project_id=quota_project_id + ) + + if not project_id: + project_id = _cloud_sdk.get_project_id() + + return credentials, project_id + + +def _get_explicit_environ_credentials(quota_project_id=None): + """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment + variable.""" + from google.auth import _cloud_sdk + + cloud_sdk_adc_path = _cloud_sdk.get_application_default_credentials_path() + explicit_file = os.environ.get(environment_vars.CREDENTIALS) + + if explicit_file is not None and explicit_file == cloud_sdk_adc_path: + # Cloud sdk flow calls gcloud to fetch project id, so if the explicit + # file path is cloud sdk credentials path, then we should fall back + # to cloud sdk flow, otherwise project id cannot be obtained. + return _get_gcloud_sdk_credentials(quota_project_id=quota_project_id) + + if explicit_file is not None: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + credentials, project_id = load_credentials_from_file( + os.environ[environment_vars.CREDENTIALS], + quota_project_id=quota_project_id, + ) + + return credentials, project_id + + else: + return None, None + + +def _get_gae_credentials(): + """Gets Google App Engine App Identity credentials and project ID.""" + # While this library is normally bundled with app_engine, there are + # some cases where it's not available, so we tolerate ImportError. + + return _default._get_gae_credentials() + + +def _get_gce_credentials(request=None): + """Gets credentials and project ID from the GCE Metadata Service.""" + # Ping requires a transport, but we want application default credentials + # to require no arguments. So, we'll use the _http_client transport which + # uses http.client. This is only acceptable because the metadata server + # doesn't do SSL and never requires proxies. + + # While this library is normally bundled with compute_engine, there are + # some cases where it's not available, so we tolerate ImportError. + + return _default._get_gce_credentials(request) + + +def default_async(scopes=None, request=None, quota_project_id=None): + """Gets the default credentials for the current environment. + + `Application Default Credentials`_ provides an easy way to obtain + credentials to call Google APIs for server-to-server or local applications. + This function acquires credentials from the environment in the following + order: + + 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set + to the path of a valid service account JSON private key file, then it is + loaded and returned. The project ID returned is the project ID defined + in the service account file if available (some older files do not + contain project ID information). + 2. If the `Google Cloud SDK`_ is installed and has application default + credentials set they are loaded and returned. + + To enable application default credentials with the Cloud SDK run:: + + gcloud auth application-default login + + If the Cloud SDK has an active project, the project ID is returned. The + active project can be set using:: + + gcloud config set project + + 3. If the application is running in the `App Engine standard environment`_ + (first generation) then the credentials and project ID from the + `App Identity Service`_ are used. + 4. If the application is running in `Compute Engine`_ or `Cloud Run`_ or + the `App Engine flexible environment`_ or the `App Engine standard + environment`_ (second generation) then the credentials and project ID + are obtained from the `Metadata Service`_. + 5. If no credentials are found, + :class:`~google.auth.exceptions.DefaultCredentialsError` will be raised. + + .. _Application Default Credentials: https://developers.google.com\ + /identity/protocols/application-default-credentials + .. _Google Cloud SDK: https://cloud.google.com/sdk + .. _App Engine standard environment: https://cloud.google.com/appengine + .. _App Identity Service: https://cloud.google.com/appengine/docs/python\ + /appidentity/ + .. _Compute Engine: https://cloud.google.com/compute + .. _App Engine flexible environment: https://cloud.google.com\ + /appengine/flexible + .. _Metadata Service: https://cloud.google.com/compute/docs\ + /storing-retrieving-metadata + .. _Cloud Run: https://cloud.google.com/run + + Example:: + + import google.auth + + credentials, project_id = google.auth.default() + + Args: + scopes (Sequence[str]): The list of scopes for the credentials. If + specified, the credentials will automatically be scoped if + necessary. + request (google.auth.transport.Request): An object used to make + HTTP requests. This is used to detect whether the application + is running on Compute Engine. If not specified, then it will + use the standard library http client to make requests. + quota_project_id (Optional[str]): The project ID used for + quota and billing. + Returns: + Tuple[~google.auth.credentials.Credentials, Optional[str]]: + the current environment's credentials and project ID. Project ID + may be None, which indicates that the Project ID could not be + ascertained from the environment. + + Raises: + ~google.auth.exceptions.DefaultCredentialsError: + If no credentials were found, or if the credentials found were + invalid. + """ + from google.auth._credentials_async import with_scopes_if_required + from google.auth.credentials import CredentialsWithQuotaProject + + explicit_project_id = os.environ.get( + environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT) + ) + + checkers = ( + lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id), + lambda: _get_gcloud_sdk_credentials(quota_project_id=quota_project_id), + _get_gae_credentials, + lambda: _get_gce_credentials(request), + ) + + for checker in checkers: + credentials, project_id = checker() + if credentials is not None: + credentials = with_scopes_if_required(credentials, scopes) + if quota_project_id and isinstance( + credentials, CredentialsWithQuotaProject + ): + credentials = credentials.with_quota_project(quota_project_id) + effective_project_id = explicit_project_id or project_id + if not effective_project_id: + _default._LOGGER.warning( + "No project ID could be determined. Consider running " + "`gcloud config set project` or setting the %s " + "environment variable", + environment_vars.PROJECT, + ) + return credentials, effective_project_id + + raise exceptions.DefaultCredentialsError(_default._CLOUD_SDK_MISSING_CREDENTIALS) diff --git a/intelliaide/vendor/google/auth/_exponential_backoff.py b/intelliaide/vendor/google/auth/_exponential_backoff.py new file mode 100644 index 0000000..8985344 --- /dev/null +++ b/intelliaide/vendor/google/auth/_exponential_backoff.py @@ -0,0 +1,164 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import asyncio +import random +import time + +from google.auth import exceptions + +# The default amount of retry attempts +_DEFAULT_RETRY_TOTAL_ATTEMPTS = 3 + +# The default initial backoff period (1.0 second). +_DEFAULT_INITIAL_INTERVAL_SECONDS = 1.0 + +# The default randomization factor (0.1 which results in a random period ranging +# between 10% below and 10% above the retry interval). +_DEFAULT_RANDOMIZATION_FACTOR = 0.1 + +# The default multiplier value (2 which is 100% increase per back off). +_DEFAULT_MULTIPLIER = 2.0 + +"""Exponential Backoff Utility + +This is a private module that implements the exponential back off algorithm. +It can be used as a utility for code that needs to retry on failure, for example +an HTTP request. +""" + + +class _BaseExponentialBackoff: + """An exponential backoff iterator base class. + + Args: + total_attempts Optional[int]: + The maximum amount of retries that should happen. + The default value is 3 attempts. + initial_wait_seconds Optional[int]: + The amount of time to sleep in the first backoff. This parameter + should be in seconds. + The default value is 1 second. + randomization_factor Optional[float]: + The amount of jitter that should be in each backoff. For example, + a value of 0.1 will introduce a jitter range of 10% to the + current backoff period. + The default value is 0.1. + multiplier Optional[float]: + The backoff multipler. This adjusts how much each backoff will + increase. For example a value of 2.0 leads to a 200% backoff + on each attempt. If the initial_wait is 1.0 it would look like + this sequence [1.0, 2.0, 4.0, 8.0]. + The default value is 2.0. + """ + + def __init__( + self, + total_attempts=_DEFAULT_RETRY_TOTAL_ATTEMPTS, + initial_wait_seconds=_DEFAULT_INITIAL_INTERVAL_SECONDS, + randomization_factor=_DEFAULT_RANDOMIZATION_FACTOR, + multiplier=_DEFAULT_MULTIPLIER, + ): + if total_attempts < 1: + raise exceptions.InvalidValue( + f"total_attempts must be greater than or equal to 1 but was {total_attempts}" + ) + + self._total_attempts = total_attempts + self._initial_wait_seconds = initial_wait_seconds + + self._current_wait_in_seconds = self._initial_wait_seconds + + self._randomization_factor = randomization_factor + self._multiplier = multiplier + self._backoff_count = 0 + + @property + def total_attempts(self): + """The total amount of backoff attempts that will be made.""" + return self._total_attempts + + @property + def backoff_count(self): + """The current amount of backoff attempts that have been made.""" + return self._backoff_count + + def _reset(self): + self._backoff_count = 0 + self._current_wait_in_seconds = self._initial_wait_seconds + + def _calculate_jitter(self): + jitter_variance = self._current_wait_in_seconds * self._randomization_factor + jitter = random.uniform( + self._current_wait_in_seconds - jitter_variance, + self._current_wait_in_seconds + jitter_variance, + ) + + return jitter + + +class ExponentialBackoff(_BaseExponentialBackoff): + """An exponential backoff iterator. This can be used in a for loop to + perform requests with exponential backoff. + """ + + def __init__(self, *args, **kwargs): + super(ExponentialBackoff, self).__init__(*args, **kwargs) + + def __iter__(self): + self._reset() + return self + + def __next__(self): + if self._backoff_count >= self._total_attempts: + raise StopIteration + self._backoff_count += 1 + + if self._backoff_count <= 1: + return self._backoff_count + + jitter = self._calculate_jitter() + + time.sleep(jitter) + + self._current_wait_in_seconds *= self._multiplier + return self._backoff_count + + +class AsyncExponentialBackoff(_BaseExponentialBackoff): + """An async exponential backoff iterator. This can be used in a for loop to + perform async requests with exponential backoff. + """ + + def __init__(self, *args, **kwargs): + super(AsyncExponentialBackoff, self).__init__(*args, **kwargs) + + def __aiter__(self): + self._reset() + return self + + async def __anext__(self): + if self._backoff_count >= self._total_attempts: + raise StopAsyncIteration + self._backoff_count += 1 + + if self._backoff_count <= 1: + return self._backoff_count + + jitter = self._calculate_jitter() + + await asyncio.sleep(jitter) + + self._current_wait_in_seconds *= self._multiplier + return self._backoff_count diff --git a/intelliaide/vendor/google/auth/_helpers.py b/intelliaide/vendor/google/auth/_helpers.py new file mode 100644 index 0000000..0814622 --- /dev/null +++ b/intelliaide/vendor/google/auth/_helpers.py @@ -0,0 +1,534 @@ +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helper functions for commonly used utilities.""" + +import base64 +import calendar +import datetime +from email.message import Message +import hashlib +import json +import logging +import sys +from typing import Any, Dict, Mapping, Optional, Union +import urllib + +from google.auth import exceptions + + +# _BASE_LOGGER_NAME is the base logger for all google-based loggers. +_BASE_LOGGER_NAME = "google" + +# _LOGGING_INITIALIZED ensures that base logger is only configured once +# (unless already configured by the end-user). +_LOGGING_INITIALIZED = False + + +# The smallest MDS cache used by this library stores tokens until 4 minutes from +# expiry. +REFRESH_THRESHOLD = datetime.timedelta(minutes=3, seconds=45) + +# TODO(https://github.com/googleapis/google-auth-library-python/issues/1684): Audit and update the list below. +_SENSITIVE_FIELDS = { + "accessToken", + "access_token", + "id_token", + "client_id", + "refresh_token", + "client_secret", +} + + +def copy_docstring(source_class): + """Decorator that copies a method's docstring from another class. + + Args: + source_class (type): The class that has the documented method. + + Returns: + Callable: A decorator that will copy the docstring of the same + named method in the source class to the decorated method. + """ + + def decorator(method): + """Decorator implementation. + + Args: + method (Callable): The method to copy the docstring to. + + Returns: + Callable: the same method passed in with an updated docstring. + + Raises: + google.auth.exceptions.InvalidOperation: if the method already has a docstring. + """ + if method.__doc__: + raise exceptions.InvalidOperation("Method already has a docstring.") + + source_method = getattr(source_class, method.__name__) + method.__doc__ = source_method.__doc__ + + return method + + return decorator + + +def parse_content_type(header_value): + """Parse a 'content-type' header value to get just the plain media-type (without parameters). + + This is done using the class Message from email.message as suggested in PEP 594 + (because the cgi is now deprecated and will be removed in python 3.13, + see https://peps.python.org/pep-0594/#cgi). + + Args: + header_value (str): The value of a 'content-type' header as a string. + + Returns: + str: A string with just the lowercase media-type from the parsed 'content-type' header. + If the provided content-type is not parsable, returns 'text/plain', + the default value for textual files. + """ + m = Message() + m["content-type"] = header_value + return ( + m.get_content_type() + ) # Despite the name, actually returns just the media-type + + +def utcnow(): + """Returns the current UTC datetime. + + Returns: + datetime: The current time in UTC. + """ + # We used datetime.utcnow() before, since it's deprecated from python 3.12, + # we are using datetime.now(timezone.utc) now. "utcnow()" is offset-native + # (no timezone info), but "now()" is offset-aware (with timezone info). + # This will cause datetime comparison problem. For backward compatibility, + # we need to remove the timezone info. + now = datetime.datetime.now(datetime.timezone.utc) + now = now.replace(tzinfo=None) + return now + + +def utcfromtimestamp(timestamp): + """Returns the UTC datetime from a timestamp. + + Args: + timestamp (float): The timestamp to convert. + + Returns: + datetime: The time in UTC. + """ + # We used datetime.utcfromtimestamp() before, since it's deprecated from + # python 3.12, we are using datetime.fromtimestamp(timestamp, timezone.utc) + # now. "utcfromtimestamp()" is offset-native (no timezone info), but + # "fromtimestamp(timestamp, timezone.utc)" is offset-aware (with timezone + # info). This will cause datetime comparison problem. For backward + # compatibility, we need to remove the timezone info. + dt = datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc) + dt = dt.replace(tzinfo=None) + return dt + + +def datetime_to_secs(value): + """Convert a datetime object to the number of seconds since the UNIX epoch. + + Args: + value (datetime): The datetime to convert. + + Returns: + int: The number of seconds since the UNIX epoch. + """ + return calendar.timegm(value.utctimetuple()) + + +def to_bytes(value, encoding="utf-8"): + """Converts a string value to bytes, if necessary. + + Args: + value (Union[str, bytes]): The value to be converted. + encoding (str): The encoding to use to convert unicode to bytes. + Defaults to "utf-8". + + Returns: + bytes: The original value converted to bytes (if unicode) or as + passed in if it started out as bytes. + + Raises: + google.auth.exceptions.InvalidValue: If the value could not be converted to bytes. + """ + result = value.encode(encoding) if isinstance(value, str) else value + if isinstance(result, bytes): + return result + else: + raise exceptions.InvalidValue( + "{0!r} could not be converted to bytes".format(value) + ) + + +def from_bytes(value): + """Converts bytes to a string value, if necessary. + + Args: + value (Union[str, bytes]): The value to be converted. + + Returns: + str: The original value converted to unicode (if bytes) or as passed in + if it started out as unicode. + + Raises: + google.auth.exceptions.InvalidValue: If the value could not be converted to unicode. + """ + result = value.decode("utf-8") if isinstance(value, bytes) else value + if isinstance(result, str): + return result + else: + raise exceptions.InvalidValue( + "{0!r} could not be converted to unicode".format(value) + ) + + +def update_query(url, params, remove=None): + """Updates a URL's query parameters. + + Replaces any current values if they are already present in the URL. + + Args: + url (str): The URL to update. + params (Mapping[str, str]): A mapping of query parameter + keys to values. + remove (Sequence[str]): Parameters to remove from the query string. + + Returns: + str: The URL with updated query parameters. + + Examples: + + >>> url = 'http://example.com?a=1' + >>> update_query(url, {'a': '2'}) + http://example.com?a=2 + >>> update_query(url, {'b': '3'}) + http://example.com?a=1&b=3 + >> update_query(url, {'b': '3'}, remove=['a']) + http://example.com?b=3 + + """ + if remove is None: + remove = [] + + # Split the URL into parts. + parts = urllib.parse.urlparse(url) + # Parse the query string. + query_params = urllib.parse.parse_qs(parts.query) + # Update the query parameters with the new parameters. + query_params.update(params) + # Remove any values specified in remove. + query_params = { + key: value for key, value in query_params.items() if key not in remove + } + # Re-encoded the query string. + new_query = urllib.parse.urlencode(query_params, doseq=True) + # Unsplit the url. + new_parts = parts._replace(query=new_query) + return urllib.parse.urlunparse(new_parts) + + +def scopes_to_string(scopes): + """Converts scope value to a string suitable for sending to OAuth 2.0 + authorization servers. + + Args: + scopes (Sequence[str]): The sequence of scopes to convert. + + Returns: + str: The scopes formatted as a single string. + """ + return " ".join(scopes) + + +def string_to_scopes(scopes): + """Converts stringifed scopes value to a list. + + Args: + scopes (Union[Sequence, str]): The string of space-separated scopes + to convert. + Returns: + Sequence(str): The separated scopes. + """ + if not scopes: + return [] + + return scopes.split(" ") + + +def padded_urlsafe_b64decode(value): + """Decodes base64 strings lacking padding characters. + + Google infrastructure tends to omit the base64 padding characters. + + Args: + value (Union[str, bytes]): The encoded value. + + Returns: + bytes: The decoded value + """ + b64string = to_bytes(value) + padded = b64string + b"=" * (-len(b64string) % 4) + return base64.urlsafe_b64decode(padded) + + +def unpadded_urlsafe_b64encode(value): + """Encodes base64 strings removing any padding characters. + + `rfc 7515`_ defines Base64url to NOT include any padding + characters, but the stdlib doesn't do that by default. + + _rfc7515: https://tools.ietf.org/html/rfc7515#page-6 + + Args: + value (Union[str|bytes]): The bytes-like value to encode + + Returns: + Union[str|bytes]: The encoded value + """ + return base64.urlsafe_b64encode(value).rstrip(b"=") + + +def is_python_3(): + """Check if the Python interpreter is Python 2 or 3. + + Returns: + bool: True if the Python interpreter is Python 3 and False otherwise. + """ + + return sys.version_info > (3, 0) # pragma: NO COVER + + +def _hash_sensitive_info(data: Union[dict, list]) -> Union[dict, list, str]: + """ + Hashes sensitive information within a dictionary. + + Args: + data: The dictionary containing data to be processed. + + Returns: + A new dictionary with sensitive values replaced by their SHA512 hashes. + If the input is a list, returns a list with each element recursively processed. + If the input is neither a dict nor a list, returns the type of the input as a string. + + """ + if isinstance(data, dict): + hashed_data: Dict[Any, Union[Optional[str], dict, list]] = {} + for key, value in data.items(): + if key in _SENSITIVE_FIELDS and not isinstance(value, (dict, list)): + hashed_data[key] = _hash_value(value, key) + elif isinstance(value, (dict, list)): + hashed_data[key] = _hash_sensitive_info(value) + else: + hashed_data[key] = value + return hashed_data + elif isinstance(data, list): + hashed_list = [] + for val in data: + hashed_list.append(_hash_sensitive_info(val)) + return hashed_list + else: + # TODO(https://github.com/googleapis/google-auth-library-python/issues/1701): + # Investigate and hash sensitive info before logging when the data type is + # not a dict or a list. + return str(type(data)) + + +def _hash_value(value, field_name: str) -> Optional[str]: + """Hashes a value and returns a formatted hash string.""" + if value is None: + return None + encoded_value = str(value).encode("utf-8") + hash_object = hashlib.sha512() + hash_object.update(encoded_value) + hex_digest = hash_object.hexdigest() + return f"hashed_{field_name}-{hex_digest}" + + +def _logger_configured(logger: logging.Logger) -> bool: + """Determines whether `logger` has non-default configuration + + Args: + logger: The logger to check. + + Returns: + bool: Whether the logger has any non-default configuration. + """ + return ( + logger.handlers != [] or logger.level != logging.NOTSET or not logger.propagate + ) + + +def is_logging_enabled(logger: logging.Logger) -> bool: + """ + Checks if debug logging is enabled for the given logger. + + Args: + logger: The logging.Logger instance to check. + + Returns: + True if debug logging is enabled, False otherwise. + """ + # NOTE: Log propagation to the root logger is disabled unless + # the base logger i.e. logging.getLogger("google") is + # explicitly configured by the end user. Ideally this + # needs to happen in the client layer (already does for GAPICs). + # However, this is implemented here to avoid logging + # (if a root logger is configured) when a version of google-auth + # which supports logging is used with: + # - an older version of a GAPIC which does not support logging. + # - Apiary client which does not support logging. + global _LOGGING_INITIALIZED + if not _LOGGING_INITIALIZED: + base_logger = logging.getLogger(_BASE_LOGGER_NAME) + if not _logger_configured(base_logger): + base_logger.propagate = False + _LOGGING_INITIALIZED = True + + return logger.isEnabledFor(logging.DEBUG) + + +def request_log( + logger: logging.Logger, + method: str, + url: str, + body: Optional[bytes], + headers: Optional[Mapping[str, str]], +) -> None: + """ + Logs an HTTP request at the DEBUG level if logging is enabled. + + Args: + logger: The logging.Logger instance to use. + method: The HTTP method (e.g., "GET", "POST"). + url: The URL of the request. + body: The request body (can be None). + headers: The request headers (can be None). + """ + if is_logging_enabled(logger): + content_type = ( + headers["Content-Type"] if headers and "Content-Type" in headers else "" + ) + json_body = _parse_request_body(body, content_type=content_type) + logged_body = _hash_sensitive_info(json_body) + logger.debug( + "Making request...", + extra={ + "httpRequest": { + "method": method, + "url": url, + "body": logged_body, + "headers": headers, + } + }, + ) + + +def _parse_request_body(body: Optional[bytes], content_type: str = "") -> Any: + """ + Parses a request body, handling bytes and string types, and different content types. + + Args: + body (Optional[bytes]): The request body. + content_type (str): The content type of the request body, e.g., "application/json", + "application/x-www-form-urlencoded", or "text/plain". If empty, attempts + to parse as JSON. + + Returns: + Parsed body (dict, str, or None). + - JSON: Decodes if content_type is "application/json" or None (fallback). + - URL-encoded: Parses if content_type is "application/x-www-form-urlencoded". + - Plain text: Returns string if content_type is "text/plain". + - None: Returns if body is None, UTF-8 decode fails, or content_type is unknown. + """ + if body is None: + return None + try: + body_str = body.decode("utf-8") + except (UnicodeDecodeError, AttributeError): + return None + content_type = content_type.lower() + if not content_type or "application/json" in content_type: + try: + return json.loads(body_str) + except (TypeError, ValueError): + return body_str + if "application/x-www-form-urlencoded" in content_type: + parsed_query = urllib.parse.parse_qs(body_str) + result = {k: v[0] for k, v in parsed_query.items()} + return result + if "text/plain" in content_type: + return body_str + return None + + +def _parse_response(response: Any) -> Any: + """ + Parses a response, attempting to decode JSON. + + Args: + response: The response object to parse. This can be any type, but + it is expected to have a `json()` method if it contains JSON. + + Returns: + The parsed response. If the response contains valid JSON, the + decoded JSON object (e.g., a dictionary or list) is returned. + If the response does not have a `json()` method or if the JSON + decoding fails, None is returned. + """ + try: + json_response = response.json() + return json_response + except Exception: + # TODO(https://github.com/googleapis/google-auth-library-python/issues/1744): + # Parse and return response payload as json based on different content types. + return None + + +def _response_log_base(logger: logging.Logger, parsed_response: Any) -> None: + """ + Logs a parsed HTTP response at the DEBUG level. + + This internal helper function takes a parsed response and logs it + using the provided logger. It also applies a hashing function to + potentially sensitive information before logging. + + Args: + logger: The logging.Logger instance to use for logging. + parsed_response: The parsed HTTP response object (e.g., a dictionary, + list, or the original response if parsing failed). + """ + + logged_response = _hash_sensitive_info(parsed_response) + logger.debug("Response received...", extra={"httpResponse": logged_response}) + + +def response_log(logger: logging.Logger, response: Any) -> None: + """ + Logs an HTTP response at the DEBUG level if logging is enabled. + + Args: + logger: The logging.Logger instance to use. + response: The HTTP response object to log. + """ + if is_logging_enabled(logger): + json_response = _parse_response(response) + _response_log_base(logger, json_response) diff --git a/intelliaide/vendor/google/auth/_jwt_async.py b/intelliaide/vendor/google/auth/_jwt_async.py new file mode 100644 index 0000000..3a1abc5 --- /dev/null +++ b/intelliaide/vendor/google/auth/_jwt_async.py @@ -0,0 +1,164 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""JSON Web Tokens + +Provides support for creating (encoding) and verifying (decoding) JWTs, +especially JWTs generated and consumed by Google infrastructure. + +See `rfc7519`_ for more details on JWTs. + +To encode a JWT use :func:`encode`:: + + from google.auth import crypt + from google.auth import jwt_async + + signer = crypt.Signer(private_key) + payload = {'some': 'payload'} + encoded = jwt_async.encode(signer, payload) + +To decode a JWT and verify claims use :func:`decode`:: + + claims = jwt_async.decode(encoded, certs=public_certs) + +You can also skip verification:: + + claims = jwt_async.decode(encoded, verify=False) + +.. _rfc7519: https://tools.ietf.org/html/rfc7519 + + +NOTE: This async support is experimental and marked internal. This surface may +change in minor releases. +""" + +from google.auth import _credentials_async +from google.auth import jwt + + +def encode(signer, payload, header=None, key_id=None): + """Make a signed JWT. + + Args: + signer (google.auth.crypt.Signer): The signer used to sign the JWT. + payload (Mapping[str, str]): The JWT payload. + header (Mapping[str, str]): Additional JWT header payload. + key_id (str): The key id to add to the JWT header. If the + signer has a key id it will be used as the default. If this is + specified it will override the signer's key id. + + Returns: + bytes: The encoded JWT. + """ + return jwt.encode(signer, payload, header, key_id) + + +def decode(token, certs=None, verify=True, audience=None): + """Decode and verify a JWT. + + Args: + token (str): The encoded JWT. + certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The + certificate used to validate the JWT signature. If bytes or string, + it must the the public key certificate in PEM format. If a mapping, + it must be a mapping of key IDs to public key certificates in PEM + format. The mapping must contain the same key ID that's specified + in the token's header. + verify (bool): Whether to perform signature and claim validation. + Verification is done by default. + audience (str): The audience claim, 'aud', that this JWT should + contain. If None then the JWT's 'aud' parameter is not verified. + + Returns: + Mapping[str, str]: The deserialized JSON payload in the JWT. + + Raises: + ValueError: if any verification checks failed. + """ + + return jwt.decode(token, certs, verify, audience) + + +class Credentials( + jwt.Credentials, _credentials_async.Signing, _credentials_async.Credentials +): + """Credentials that use a JWT as the bearer token. + + These credentials require an "audience" claim. This claim identifies the + intended recipient of the bearer token. + + The constructor arguments determine the claims for the JWT that is + sent with requests. Usually, you'll construct these credentials with + one of the helper constructors as shown in the next section. + + To create JWT credentials using a Google service account private key + JSON file:: + + audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher' + credentials = jwt_async.Credentials.from_service_account_file( + 'service-account.json', + audience=audience) + + If you already have the service account file loaded and parsed:: + + service_account_info = json.load(open('service_account.json')) + credentials = jwt_async.Credentials.from_service_account_info( + service_account_info, + audience=audience) + + Both helper methods pass on arguments to the constructor, so you can + specify the JWT claims:: + + credentials = jwt_async.Credentials.from_service_account_file( + 'service-account.json', + audience=audience, + additional_claims={'meta': 'data'}) + + You can also construct the credentials directly if you have a + :class:`~google.auth.crypt.Signer` instance:: + + credentials = jwt_async.Credentials( + signer, + issuer='your-issuer', + subject='your-subject', + audience=audience) + + The claims are considered immutable. If you want to modify the claims, + you can easily create another instance using :meth:`with_claims`:: + + new_audience = ( + 'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber') + new_credentials = credentials.with_claims(audience=new_audience) + """ + + +class OnDemandCredentials( + jwt.OnDemandCredentials, _credentials_async.Signing, _credentials_async.Credentials +): + """On-demand JWT credentials. + + Like :class:`Credentials`, this class uses a JWT as the bearer token for + authentication. However, this class does not require the audience at + construction time. Instead, it will generate a new token on-demand for + each request using the request URI as the audience. It caches tokens + so that multiple requests to the same URI do not incur the overhead + of generating a new token every time. + + This behavior is especially useful for `gRPC`_ clients. A gRPC service may + have multiple audience and gRPC clients may not know all of the audiences + required for accessing a particular service. With these credentials, + no knowledge of the audiences is required ahead of time. + + .. _grpc: http://www.grpc.io/ + """ diff --git a/intelliaide/vendor/google/auth/_oauth2client.py b/intelliaide/vendor/google/auth/_oauth2client.py new file mode 100644 index 0000000..8032b26 --- /dev/null +++ b/intelliaide/vendor/google/auth/_oauth2client.py @@ -0,0 +1,167 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helpers for transitioning from oauth2client to google-auth. + +.. warning:: + This module is private as it is intended to assist first-party downstream + clients with the transition from oauth2client to google-auth. +""" + +from __future__ import absolute_import + +from google.auth import _helpers +import google.auth.app_engine +import google.auth.compute_engine +import google.oauth2.credentials +import google.oauth2.service_account + +try: + import oauth2client.client # type: ignore + import oauth2client.contrib.gce # type: ignore + import oauth2client.service_account # type: ignore +except ImportError as caught_exc: + raise ImportError("oauth2client is not installed.") from caught_exc + +try: + import oauth2client.contrib.appengine # type: ignore + + _HAS_APPENGINE = True +except ImportError: + _HAS_APPENGINE = False + + +_CONVERT_ERROR_TMPL = "Unable to convert {} to a google-auth credentials class." + + +def _convert_oauth2_credentials(credentials): + """Converts to :class:`google.oauth2.credentials.Credentials`. + + Args: + credentials (Union[oauth2client.client.OAuth2Credentials, + oauth2client.client.GoogleCredentials]): The credentials to + convert. + + Returns: + google.oauth2.credentials.Credentials: The converted credentials. + """ + new_credentials = google.oauth2.credentials.Credentials( + token=credentials.access_token, + refresh_token=credentials.refresh_token, + token_uri=credentials.token_uri, + client_id=credentials.client_id, + client_secret=credentials.client_secret, + scopes=credentials.scopes, + ) + + new_credentials._expires = credentials.token_expiry + + return new_credentials + + +def _convert_service_account_credentials(credentials): + """Converts to :class:`google.oauth2.service_account.Credentials`. + + Args: + credentials (Union[ + oauth2client.service_account.ServiceAccountCredentials, + oauth2client.service_account._JWTAccessCredentials]): The + credentials to convert. + + Returns: + google.oauth2.service_account.Credentials: The converted credentials. + """ + info = credentials.serialization_data.copy() + info["token_uri"] = credentials.token_uri + return google.oauth2.service_account.Credentials.from_service_account_info(info) + + +def _convert_gce_app_assertion_credentials(credentials): + """Converts to :class:`google.auth.compute_engine.Credentials`. + + Args: + credentials (oauth2client.contrib.gce.AppAssertionCredentials): The + credentials to convert. + + Returns: + google.oauth2.service_account.Credentials: The converted credentials. + """ + return google.auth.compute_engine.Credentials( + service_account_email=credentials.service_account_email + ) + + +def _convert_appengine_app_assertion_credentials(credentials): + """Converts to :class:`google.auth.app_engine.Credentials`. + + Args: + credentials (oauth2client.contrib.app_engine.AppAssertionCredentials): + The credentials to convert. + + Returns: + google.oauth2.service_account.Credentials: The converted credentials. + """ + # pylint: disable=invalid-name + return google.auth.app_engine.Credentials( + scopes=_helpers.string_to_scopes(credentials.scope), + service_account_id=credentials.service_account_id, + ) + + +_CLASS_CONVERSION_MAP = { + oauth2client.client.OAuth2Credentials: _convert_oauth2_credentials, + oauth2client.client.GoogleCredentials: _convert_oauth2_credentials, + oauth2client.service_account.ServiceAccountCredentials: _convert_service_account_credentials, + oauth2client.service_account._JWTAccessCredentials: _convert_service_account_credentials, + oauth2client.contrib.gce.AppAssertionCredentials: _convert_gce_app_assertion_credentials, +} + +if _HAS_APPENGINE: # pragma: no cover + _CLASS_CONVERSION_MAP[ + oauth2client.contrib.appengine.AppAssertionCredentials + ] = _convert_appengine_app_assertion_credentials + + +def convert(credentials): + """Convert oauth2client credentials to google-auth credentials. + + This class converts: + + - :class:`oauth2client.client.OAuth2Credentials` to + :class:`google.oauth2.credentials.Credentials`. + - :class:`oauth2client.client.GoogleCredentials` to + :class:`google.oauth2.credentials.Credentials`. + - :class:`oauth2client.service_account.ServiceAccountCredentials` to + :class:`google.oauth2.service_account.Credentials`. + - :class:`oauth2client.service_account._JWTAccessCredentials` to + :class:`google.oauth2.service_account.Credentials`. + - :class:`oauth2client.contrib.gce.AppAssertionCredentials` to + :class:`google.auth.compute_engine.Credentials`. + - :class:`oauth2client.contrib.appengine.AppAssertionCredentials` to + :class:`google.auth.app_engine.Credentials`. + + Returns: + google.auth.credentials.Credentials: The converted credentials. + + Raises: + ValueError: If the credentials could not be converted. + """ + + credentials_class = type(credentials) + + try: + return _CLASS_CONVERSION_MAP[credentials_class](credentials) + except KeyError as caught_exc: + new_exc = ValueError(_CONVERT_ERROR_TMPL.format(credentials_class)) + raise new_exc from caught_exc diff --git a/intelliaide/vendor/google/auth/_refresh_worker.py b/intelliaide/vendor/google/auth/_refresh_worker.py new file mode 100644 index 0000000..1bab21a --- /dev/null +++ b/intelliaide/vendor/google/auth/_refresh_worker.py @@ -0,0 +1,109 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import copy +import logging +import threading + +import google.auth.exceptions as e + +_LOGGER = logging.getLogger(__name__) + + +class RefreshThreadManager: + """ + Organizes exactly one background job that refresh a token. + """ + + def __init__(self): + """Initializes the manager.""" + + self._worker = None + self._lock = threading.Lock() # protects access to worker threads. + + def start_refresh(self, cred, request): + """Starts a refresh thread for the given credentials. + The credentials are refreshed using the request parameter. + request and cred MUST not be None + + Returns True if a background refresh was kicked off. False otherwise. + + Args: + cred: A credentials object. + request: A request object. + Returns: + bool + """ + if cred is None or request is None: + raise e.InvalidValue( + "Unable to start refresh. cred and request must be valid and instantiated objects." + ) + + with self._lock: + if self._worker is not None and self._worker._error_info is not None: + return False + + if self._worker is None or not self._worker.is_alive(): # pragma: NO COVER + self._worker = RefreshThread(cred=cred, request=copy.deepcopy(request)) + self._worker.start() + return True + + def clear_error(self): + """ + Removes any errors that were stored from previous background refreshes. + """ + with self._lock: + if self._worker: + self._worker._error_info = None + + def __getstate__(self): + """Pickle helper that serializes the _lock attribute.""" + state = self.__dict__.copy() + state["_lock"] = None + return state + + def __setstate__(self, state): + """Pickle helper that deserializes the _lock attribute.""" + state["_lock"] = threading.Lock() + self.__dict__.update(state) + + +class RefreshThread(threading.Thread): + """ + Thread that refreshes credentials. + """ + + def __init__(self, cred, request, **kwargs): + """Initializes the thread. + + Args: + cred: A Credential object to refresh. + request: A Request object used to perform a credential refresh. + **kwargs: Additional keyword arguments. + """ + + super().__init__(**kwargs) + self._cred = cred + self._request = request + self._error_info = None + + def run(self): + """ + Perform the credential refresh. + """ + try: + self._cred.refresh(self._request) + except Exception as err: # pragma: NO COVER + _LOGGER.error(f"Background refresh failed due to: {err}") + self._error_info = err diff --git a/intelliaide/vendor/google/auth/_regional_access_boundary_utils.py b/intelliaide/vendor/google/auth/_regional_access_boundary_utils.py new file mode 100644 index 0000000..8101191 --- /dev/null +++ b/intelliaide/vendor/google/auth/_regional_access_boundary_utils.py @@ -0,0 +1,386 @@ +# Copyright 2026 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Utilities for Regional Access Boundary management.""" + +import copy +import datetime +import functools +import logging +import os +import threading +from typing import NamedTuple, Optional, TYPE_CHECKING + +from google.auth import _helpers +from google.auth import environment_vars + +if TYPE_CHECKING: + import google.auth.credentials + import google.auth.transport + +_LOGGER = logging.getLogger(__name__) + + +@functools.lru_cache() +def is_regional_access_boundary_enabled(): + """Checks if Regional Access Boundary is enabled via environment variable. + + The environment variable is interpreted as a boolean with the following + (case-insensitive) rules: + - "true", "1" are considered true. + - Any other value (or unset) is considered false. + + Returns: + bool: True if Regional Access Boundary is enabled, False otherwise. + """ + value = os.environ.get(environment_vars.GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED) + if value is None: + return False + + return value.lower() in ("true", "1") + + +# The default lifetime for a cached Regional Access Boundary. +DEFAULT_REGIONAL_ACCESS_BOUNDARY_TTL = datetime.timedelta(hours=6) + +# The period of time prior to the boundary's expiration when a background refresh +# is proactively triggered. +REGIONAL_ACCESS_BOUNDARY_REFRESH_THRESHOLD = datetime.timedelta(hours=1) + +# The initial cooldown period for a failed Regional Access Boundary lookup. +DEFAULT_REGIONAL_ACCESS_BOUNDARY_COOLDOWN = datetime.timedelta(minutes=15) + +# The maximum cooldown period for a failed Regional Access Boundary lookup. +MAX_REGIONAL_ACCESS_BOUNDARY_COOLDOWN = datetime.timedelta(hours=6) + + +# The header key used for Regional Access Boundaries. +_REGIONAL_ACCESS_BOUNDARY_HEADER = "x-allowed-locations" + + +class _RegionalAccessBoundaryData(NamedTuple): + """Data container for a Regional Access Boundary snapshot. + + Attributes: + encoded_locations (Optional[str]): The encoded Regional Access Boundary string. + expiry (Optional[datetime.datetime]): The hard expiration time of the boundary data. + cooldown_expiry (Optional[datetime.datetime]): The time until which further lookups are skipped. + cooldown_duration (datetime.timedelta): The current duration for the exponential cooldown. + """ + + encoded_locations: Optional[str] + expiry: Optional[datetime.datetime] + cooldown_expiry: Optional[datetime.datetime] + cooldown_duration: datetime.timedelta + + +class _RegionalAccessBoundaryManager(object): + """Manages the Regional Access Boundary state and its background refresh. + + The actual data is held in an immutable `_RegionalAccessBoundaryData` object + and is swapped atomically to ensure thread-safe, lock-free reads. + """ + + def __init__(self): + self._data = _RegionalAccessBoundaryData( + encoded_locations=None, + expiry=None, + cooldown_expiry=None, + cooldown_duration=DEFAULT_REGIONAL_ACCESS_BOUNDARY_COOLDOWN, + ) + self.refresh_manager = _RegionalAccessBoundaryRefreshManager() + self._update_lock = threading.Lock() + self._use_blocking_regional_access_boundary_lookup = False + + def __getstate__(self): + """Pickle helper that serializes the _update_lock attribute.""" + state = self.__dict__.copy() + state["_update_lock"] = None + return state + + def __setstate__(self, state): + """Pickle helper that deserializes the _update_lock attribute.""" + self.__dict__.update(state) + self._update_lock = threading.Lock() + + def __eq__(self, other): + """Checks if two managers are equal.""" + if not isinstance(other, _RegionalAccessBoundaryManager): + return NotImplemented + return ( + self._data == other._data + and self._use_blocking_regional_access_boundary_lookup + == other._use_blocking_regional_access_boundary_lookup + ) + + def enable_blocking_lookup(self): + """Enables blocking Regional Access Boundary lookup. + + When enabled, the Regional Access Boundary lookup will be performed + synchronously in the calling thread instead of asynchronously in a + background thread. + """ + self._use_blocking_regional_access_boundary_lookup = True + + def set_initial_regional_access_boundary(self, encoded_locations=None, expiry=None): + """Manually sets the regional access boundary to the client provided seed. + + Args: + encoded_locations (Optional[str]): The encoded locations string. + expiry (Optional[datetime.datetime]): The expiry time for the boundary. + If encoded_locations is not provided, expiry is ignored. + """ + if not encoded_locations: + expiry = None + + self._data = _RegionalAccessBoundaryData( + encoded_locations=encoded_locations, + expiry=expiry, + cooldown_expiry=None, + cooldown_duration=DEFAULT_REGIONAL_ACCESS_BOUNDARY_COOLDOWN, + ) + + def apply_headers(self, headers): + """Applies the Regional Access Boundary header to the provided dictionary. + + If the boundary is valid, the 'x-allowed-locations' header is added + or updated. Otherwise, the header is removed to ensure no stale + data is sent. + + Args: + headers (MutableMapping[str, str]): The headers dictionary to update. + """ + rab_data = self._data + + if rab_data.encoded_locations and ( + rab_data.expiry is not None and _helpers.utcnow() < rab_data.expiry + ): + headers[_REGIONAL_ACCESS_BOUNDARY_HEADER] = rab_data.encoded_locations + else: + headers.pop(_REGIONAL_ACCESS_BOUNDARY_HEADER, None) + + def maybe_start_refresh(self, credentials, request): + """Starts a background thread to refresh the Regional Access Boundary if needed. + + Args: + credentials (google.auth.credentials.Credentials): The credentials to refresh. + request (google.auth.transport.Request): The object used to make HTTP requests. + """ + rab_data = self._data + + # Don't start a new refresh if the Regional Access Boundary info is still fresh. + if ( + rab_data.encoded_locations + and rab_data.expiry + and _helpers.utcnow() + < (rab_data.expiry - REGIONAL_ACCESS_BOUNDARY_REFRESH_THRESHOLD) + ): + return + + # Don't start a new refresh if the cooldown is still in effect. + if rab_data.cooldown_expiry and _helpers.utcnow() < rab_data.cooldown_expiry: + return + + # If all checks pass, start the background refresh. + if self._use_blocking_regional_access_boundary_lookup: + self.start_blocking_refresh(credentials, request) + else: + self.refresh_manager.start_refresh(credentials, request, self) + + def start_blocking_refresh(self, credentials, request): + """Initiates a blocking lookup of the Regional Access Boundary. + + If the lookup raises an exception, it is caught and logged as a warning, + and the lookup is treated as a failure (entering cooldown). Exceptions + are not propagated to the caller. + + Args: + credentials (google.auth.credentials.Credentials): The credentials to refresh. + request (google.auth.transport.Request): The object used to make HTTP requests. + """ + try: + # The fail_fast parameter is set to True to ensure we don't block the calling + # thread for too long. This will do two things: 1) set a timeout to 3s + # instead of the default 120s and 2) ensure we do not retry at all + regional_access_boundary_info = ( + credentials._lookup_regional_access_boundary(request, fail_fast=True) + ) + except Exception as e: + if _helpers.is_logging_enabled(_LOGGER): + _LOGGER.warning( + "Blocking Regional Access Boundary lookup raised an exception: %s", + e, + exc_info=True, + ) + regional_access_boundary_info = None + + self.process_regional_access_boundary_info(regional_access_boundary_info) + + def process_regional_access_boundary_info(self, regional_access_boundary_info): + """Processes the regional access boundary info and updates the state. + + Args: + regional_access_boundary_info (Optional[Mapping[str, str]]): The regional access + boundary info to process. + """ + with self._update_lock: + # Capture the current state before calculating updates. + current_data = self._data + + if regional_access_boundary_info: + # On success, update the boundary and its expiry, and clear any cooldown. + encoded_locations = regional_access_boundary_info.get( + "encodedLocations" + ) + updated_data = _RegionalAccessBoundaryData( + encoded_locations=encoded_locations, + expiry=_helpers.utcnow() + DEFAULT_REGIONAL_ACCESS_BOUNDARY_TTL, + cooldown_expiry=None, + cooldown_duration=DEFAULT_REGIONAL_ACCESS_BOUNDARY_COOLDOWN, + ) + if _helpers.is_logging_enabled(_LOGGER): + _LOGGER.debug("Regional Access Boundary lookup successful.") + else: + # On failure, calculate cooldown and update state. + if _helpers.is_logging_enabled(_LOGGER): + _LOGGER.warning( + "Regional Access Boundary lookup failed. Entering cooldown." + ) + + next_cooldown_expiry = ( + _helpers.utcnow() + current_data.cooldown_duration + ) + next_cooldown_duration = min( + current_data.cooldown_duration * 2, + MAX_REGIONAL_ACCESS_BOUNDARY_COOLDOWN, + ) + + # If the refresh failed, we keep reusing the existing data unless + # it has reached its hard expiration time. + if current_data.expiry and _helpers.utcnow() > current_data.expiry: + next_encoded_locations = None + next_expiry = None + else: + next_encoded_locations = current_data.encoded_locations + next_expiry = current_data.expiry + + updated_data = _RegionalAccessBoundaryData( + encoded_locations=next_encoded_locations, + expiry=next_expiry, + cooldown_expiry=next_cooldown_expiry, + cooldown_duration=next_cooldown_duration, + ) + + # Perform the atomic swap of the state object. + self._data = updated_data + + +class _RegionalAccessBoundaryRefreshThread(threading.Thread): + """Thread for background refreshing of the Regional Access Boundary.""" + + def __init__( + self, + credentials: "google.auth.credentials.CredentialsWithRegionalAccessBoundary", # noqa: F821 + request: "google.auth.transport.Request", # noqa: F821 + rab_manager: "_RegionalAccessBoundaryManager", + ): + super().__init__() + self.daemon = True + self._credentials = credentials + self._request = request + self._rab_manager = rab_manager + + def run(self): + """ + Performs the Regional Access Boundary lookup and updates the state. + + This method is run in a separate thread. It delegates the actual lookup + to the credentials object's `_lookup_regional_access_boundary` method. + Based on the lookup's outcome (success or complete failure after retries), + it updates the cached Regional Access Boundary information, + its expiry, its cooldown expiry, and its exponential cooldown duration. + """ + # Catch exceptions (e.g., from the underlying transport) to prevent the + # background thread from crashing. This ensures we can gracefully enter + # an exponential cooldown state on failure. + try: + regional_access_boundary_info = ( + self._credentials._lookup_regional_access_boundary(self._request) + ) + except Exception as e: + if _helpers.is_logging_enabled(_LOGGER): + _LOGGER.warning( + "Asynchronous Regional Access Boundary lookup raised an exception: %s", + e, + exc_info=True, + ) + regional_access_boundary_info = None + + self._rab_manager.process_regional_access_boundary_info( + regional_access_boundary_info + ) + + +class _RegionalAccessBoundaryRefreshManager(object): + """Manages a thread for background refreshing of the Regional Access Boundary.""" + + def __init__(self): + self._lock = threading.Lock() + self._worker = None + + def __getstate__(self): + """Pickle helper that serializes the _lock and _worker attributes.""" + state = self.__dict__.copy() + state["_lock"] = None + state["_worker"] = None + return state + + def __setstate__(self, state): + """Pickle helper that deserializes the _lock and _worker attributes.""" + self.__dict__.update(state) + self._lock = threading.Lock() + self._worker = None + + def start_refresh(self, credentials, request, rab_manager): + """ + Starts a background thread to refresh the Regional Access Boundary if one is not already running. + + Args: + credentials (CredentialsWithRegionalAccessBoundary): The credentials + to refresh. + request (google.auth.transport.Request): The object used to make + HTTP requests. + rab_manager (_RegionalAccessBoundaryManager): The manager container to update. + """ + with self._lock: + if self._worker and self._worker.is_alive(): + # A refresh is already in progress. + return + + try: + copied_request = copy.deepcopy(request) + except Exception as e: + if _helpers.is_logging_enabled(_LOGGER): + _LOGGER.warning( + "Could not deepcopy transport for background RAB refresh. " + "Skipping background refresh to avoid thread safety issues. " + "Exception: %s", + e, + ) + return + + self._worker = _RegionalAccessBoundaryRefreshThread( + credentials, copied_request, rab_manager + ) + self._worker.start() diff --git a/intelliaide/vendor/google/auth/_service_account_info.py b/intelliaide/vendor/google/auth/_service_account_info.py new file mode 100644 index 0000000..c432080 --- /dev/null +++ b/intelliaide/vendor/google/auth/_service_account_info.py @@ -0,0 +1,80 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helper functions for loading data from a Google service account file.""" + +import io +import json + +from google.auth import crypt +from google.auth import exceptions + + +def from_dict(data, require=None, use_rsa_signer=True): + """Validates a dictionary containing Google service account data. + + Creates and returns a :class:`google.auth.crypt.Signer` instance from the + private key specified in the data. + + Args: + data (Mapping[str, str]): The service account data + require (Sequence[str]): List of keys required to be present in the + info. + use_rsa_signer (Optional[bool]): Whether to use RSA signer or EC signer. + We use RSA signer by default. + + Returns: + google.auth.crypt.Signer: A signer created from the private key in the + service account file. + + Raises: + MalformedError: if the data was in the wrong format, or if one of the + required keys is missing. + """ + keys_needed = set(require if require is not None else []) + + missing = keys_needed.difference(data.keys()) + + if missing: + raise exceptions.MalformedError( + "Service account info was not in the expected format, missing " + "fields {}.".format(", ".join(missing)) + ) + + # Create a signer. + if use_rsa_signer: + signer = crypt.RSASigner.from_service_account_info(data) + else: + signer = crypt.EsSigner.from_service_account_info(data) + + return signer + + +def from_filename(filename, require=None, use_rsa_signer=True): + """Reads a Google service account JSON file and returns its parsed info. + + Args: + filename (str): The path to the service account .json file. + require (Sequence[str]): List of keys required to be present in the + info. + use_rsa_signer (Optional[bool]): Whether to use RSA signer or EC signer. + We use RSA signer by default. + + Returns: + Tuple[ Mapping[str, str], google.auth.crypt.Signer ]: The verified + info and a signer instance. + """ + with io.open(filename, "r", encoding="utf-8") as json_file: + data = json.load(json_file) + return data, from_dict(data, require=require, use_rsa_signer=use_rsa_signer) diff --git a/intelliaide/vendor/google/auth/aio/__init__.py b/intelliaide/vendor/google/auth/aio/__init__.py new file mode 100644 index 0000000..331708c --- /dev/null +++ b/intelliaide/vendor/google/auth/aio/__init__.py @@ -0,0 +1,25 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Auth AIO Library for Python.""" + +import logging + +from google.auth import version as google_auth_version + + +__version__ = google_auth_version.__version__ + +# Set default logging handler to avoid "No handler found" warnings. +logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/intelliaide/vendor/google/auth/aio/_helpers.py b/intelliaide/vendor/google/auth/aio/_helpers.py new file mode 100644 index 0000000..1d5a461 --- /dev/null +++ b/intelliaide/vendor/google/auth/aio/_helpers.py @@ -0,0 +1,62 @@ +# Copyright 2025 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helper functions for commonly used utilities.""" + + +import logging +from typing import Any + +from google.auth import _helpers + + +async def _parse_response_async(response: Any) -> Any: + """ + Parses an async response, attempting to decode JSON. + + Args: + response: The response object to parse. This can be any type, but + it is expected to have a `json()` method if it contains JSON. + + Returns: + The parsed response. If the response contains valid JSON, the + decoded JSON object (e.g., a dictionary) is returned. + If the response does not have a `json()` method or if the JSON + decoding fails, None is returned. + """ + try: + json_response = await response.json() + return json_response + except Exception: + # TODO(https://github.com/googleapis/google-auth-library-python/issues/1745): + # Parse and return response payload as json based on different content types. + return None + + +async def response_log_async(logger: logging.Logger, response: Any) -> None: + """ + Logs an Async HTTP response at the DEBUG level if logging is enabled. + + Args: + logger: The logging.Logger instance to use. + response: The HTTP response object to log. + """ + if _helpers.is_logging_enabled(logger): + # TODO(https://github.com/googleapis/google-auth-library-python/issues/1755): + # Parsing the response for async streaming logging results in + # the stream to be empty downstream. For now, we will not be logging + # the response for async responses until we investigate further. + # json_response = await _parse_response_async(response) + json_response = None + _helpers._response_log_base(logger, json_response) diff --git a/intelliaide/vendor/google/auth/aio/credentials.py b/intelliaide/vendor/google/auth/aio/credentials.py new file mode 100644 index 0000000..3bc6a5a --- /dev/null +++ b/intelliaide/vendor/google/auth/aio/credentials.py @@ -0,0 +1,143 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Interfaces for asynchronous credentials.""" + + +from google.auth import _helpers +from google.auth import exceptions +from google.auth._credentials_base import _BaseCredentials + + +class Credentials(_BaseCredentials): + """Base class for all asynchronous credentials. + + All credentials have a :attr:`token` that is used for authentication and + may also optionally set an :attr:`expiry` to indicate when the token will + no longer be valid. + + Most credentials will be :attr:`invalid` until :meth:`refresh` is called. + Credentials can do this automatically before the first HTTP request in + :meth:`before_request`. + + Although the token and expiration will change as the credentials are + :meth:`refreshed ` and used, credentials should be considered + immutable. Various credentials will accept configuration such as private + keys, scopes, and other options. These options are not changeable after + construction. Some classes will provide mechanisms to copy the credentials + with modifications such as :meth:`ScopedCredentials.with_scopes`. + """ + + def __init__(self): + super(Credentials, self).__init__() + + async def apply(self, headers, token=None): + """Apply the token to the authentication header. + + Args: + headers (Mapping): The HTTP request headers. + token (Optional[str]): If specified, overrides the current access + token. + """ + self._apply(headers, token=token) + + async def refresh(self, request): + """Refreshes the access token. + + Args: + request (google.auth.aio.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If the credentials could + not be refreshed. + """ + raise NotImplementedError("Refresh must be implemented") + + async def before_request(self, request, method, url, headers): + """Performs credential-specific before request logic. + + Refreshes the credentials if necessary, then calls :meth:`apply` to + apply the token to the authentication header. + + Args: + request (google.auth.aio.transport.Request): The object used to make + HTTP requests. + method (str): The request's HTTP method or the RPC method being + invoked. + url (str): The request's URI or the RPC service's URI. + headers (Mapping): The request's headers. + """ + await self.apply(headers) + + +class StaticCredentials(Credentials): + """Asynchronous Credentials representing an immutable access token. + + The credentials are considered immutable except the tokens which can be + configured in the constructor :: + + credentials = StaticCredentials(token="token123") + + StaticCredentials does not support :meth `refresh` and assumes that the configured + token is valid and not expired. StaticCredentials will never attempt to + refresh the token. + """ + + def __init__(self, token): + """ + Args: + token (str): The access token. + """ + super(StaticCredentials, self).__init__() + self.token = token + + @_helpers.copy_docstring(Credentials) + async def refresh(self, request): + raise exceptions.InvalidOperation("Static credentials cannot be refreshed.") + + # Note: before_request should never try to refresh access tokens. + # StaticCredentials intentionally does not support it. + @_helpers.copy_docstring(Credentials) + async def before_request(self, request, method, url, headers): + await self.apply(headers) + + +class AnonymousCredentials(Credentials): + """Asynchronous Credentials that do not provide any authentication information. + + These are useful in the case of services that support anonymous access or + local service emulators that do not use credentials. + """ + + async def refresh(self, request): + """Raises :class:``InvalidOperation``, anonymous credentials cannot be + refreshed.""" + raise exceptions.InvalidOperation("Anonymous credentials cannot be refreshed.") + + async def apply(self, headers, token=None): + """Anonymous credentials do nothing to the request. + + The optional ``token`` argument is not supported. + + Raises: + google.auth.exceptions.InvalidValue: If a token was specified. + """ + if token is not None: + raise exceptions.InvalidValue("Anonymous credentials don't support tokens.") + + async def before_request(self, request, method, url, headers): + """Anonymous credentials do nothing to the request.""" + pass diff --git a/intelliaide/vendor/google/auth/aio/transport/__init__.py b/intelliaide/vendor/google/auth/aio/transport/__init__.py new file mode 100644 index 0000000..166a3be --- /dev/null +++ b/intelliaide/vendor/google/auth/aio/transport/__init__.py @@ -0,0 +1,144 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Transport - Asynchronous HTTP client library support. + +:mod:`google.auth.aio` is designed to work with various asynchronous client libraries such +as aiohttp. In order to work across these libraries with different +interfaces some abstraction is needed. + +This module provides two interfaces that are implemented by transport adapters +to support HTTP libraries. :class:`Request` defines the interface expected by +:mod:`google.auth` to make asynchronous requests. :class:`Response` defines the interface +for the return value of :class:`Request`. +""" + +import abc +from typing import AsyncGenerator, Mapping, Optional + +import google.auth.transport + + +_DEFAULT_TIMEOUT_SECONDS = 180 + +DEFAULT_RETRYABLE_STATUS_CODES = google.auth.transport.DEFAULT_RETRYABLE_STATUS_CODES +"""Sequence[int]: HTTP status codes indicating a request can be retried. +""" + + +DEFAULT_MAX_RETRY_ATTEMPTS = 3 +"""int: How many times to retry a request.""" + + +class Response(metaclass=abc.ABCMeta): + """Asynchronous HTTP Response Interface.""" + + @property + @abc.abstractmethod + def status_code(self) -> int: + """ + The HTTP response status code. + + Returns: + int: The HTTP response status code. + + """ + raise NotImplementedError("status_code must be implemented.") + + @property + @abc.abstractmethod + def headers(self) -> Mapping[str, str]: + """The HTTP response headers. + + Returns: + Mapping[str, str]: The HTTP response headers. + """ + raise NotImplementedError("headers must be implemented.") + + @abc.abstractmethod + async def content(self, chunk_size: int) -> AsyncGenerator[bytes, None]: + """The raw response content. + + Args: + chunk_size (int): The size of each chunk. + + Yields: + AsyncGenerator[bytes, None]: An asynchronous generator yielding + response chunks as bytes. + """ + raise NotImplementedError("content must be implemented.") + + @abc.abstractmethod + async def read(self) -> bytes: + """Read the entire response content as bytes. + + Returns: + bytes: The entire response content. + """ + raise NotImplementedError("read must be implemented.") + + @abc.abstractmethod + async def close(self): + """Close the response after it is fully consumed to resource.""" + raise NotImplementedError("close must be implemented.") + + +class Request(metaclass=abc.ABCMeta): + """Interface for a callable that makes HTTP requests. + + Specific transport implementations should provide an implementation of + this that adapts their specific request / response API. + + .. automethod:: __call__ + """ + + @abc.abstractmethod + async def __call__( + self, + url: str, + method: str, + body: Optional[bytes], + headers: Optional[Mapping[str, str]], + timeout: float, + **kwargs + ) -> Response: + """Make an HTTP request. + + Args: + url (str): The URI to be requested. + method (str): The HTTP method to use for the request. Defaults + to 'GET'. + body (Optional[bytes]): The payload / body in HTTP request. + headers (Mapping[str, str]): Request headers. + timeout (float): The number of seconds to wait for a + response from the server. If not specified or if None, the + transport-specific default timeout will be used. + kwargs: Additional arguments passed on to the transport's + request method. + + Returns: + google.auth.aio.transport.Response: The HTTP response. + + Raises: + google.auth.exceptions.TransportError: If any exception occurred. + """ + # pylint: disable=redundant-returns-doc, missing-raises-doc + # (pylint doesn't play well with abstract docstrings.) + raise NotImplementedError("__call__ must be implemented.") + + async def close(self) -> None: + """ + Close the underlying session. + """ + raise NotImplementedError("close must be implemented.") diff --git a/intelliaide/vendor/google/auth/aio/transport/aiohttp.py b/intelliaide/vendor/google/auth/aio/transport/aiohttp.py new file mode 100644 index 0000000..642d159 --- /dev/null +++ b/intelliaide/vendor/google/auth/aio/transport/aiohttp.py @@ -0,0 +1,205 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Transport adapter for Asynchronous HTTP Requests based on aiohttp.""" + +import asyncio +import logging +from typing import AsyncGenerator, Mapping, Optional, TYPE_CHECKING, Union + +try: + import aiohttp # type: ignore +except ImportError as caught_exc: # pragma: NO COVER + raise ImportError( + "The aiohttp library is not installed from please install the aiohttp package to use the aiohttp transport." + ) from caught_exc + +from google.auth import _helpers +from google.auth import exceptions +from google.auth.aio import _helpers as _helpers_async +from google.auth.aio import transport + +if TYPE_CHECKING: # pragma: NO COVER + from aiohttp import ClientTimeout # type: ignore + +else: + try: + from aiohttp import ClientTimeout + except (ImportError, AttributeError): + ClientTimeout = None + +_LOGGER = logging.getLogger(__name__) + + +class Response(transport.Response): + """ + Represents an HTTP response and its data. It is returned by ``google.auth.aio.transport.sessions.AsyncAuthorizedSession``. + + Args: + response (aiohttp.ClientResponse): An instance of aiohttp.ClientResponse. + + Attributes: + status_code (int): The HTTP status code of the response. + headers (Mapping[str, str]): The HTTP headers of the response. + """ + + def __init__(self, response: aiohttp.ClientResponse): + self._response = response + + @property + @_helpers.copy_docstring(transport.Response) + def status_code(self) -> int: + return self._response.status + + @property + @_helpers.copy_docstring(transport.Response) + def headers(self) -> Mapping[str, str]: + return {key: value for key, value in self._response.headers.items()} + + @_helpers.copy_docstring(transport.Response) + async def content(self, chunk_size: int = 1024) -> AsyncGenerator[bytes, None]: + try: + async for chunk in self._response.content.iter_chunked( + chunk_size + ): # pragma: no branch + yield chunk + except aiohttp.ClientPayloadError as exc: + raise exceptions.ResponseError( + "Failed to read from the payload stream." + ) from exc + + @_helpers.copy_docstring(transport.Response) + async def read(self) -> bytes: + try: + return await self._response.read() + except aiohttp.ClientResponseError as exc: + raise exceptions.ResponseError("Failed to read the response body.") from exc + + @_helpers.copy_docstring(transport.Response) + async def close(self): + self._response.close() + + +class Request(transport.Request): + """Asynchronous Requests request adapter. + + This class is used internally for making requests using aiohttp + in a consistent way. If you use :class:`google.auth.aio.transport.sessions.AsyncAuthorizedSession` + you do not need to construct or use this class directly. + + This class can be useful if you want to configure a Request callable + with a custom ``aiohttp.ClientSession`` in :class:`AuthorizedSession` or if + you want to manually refresh a :class:`~google.auth.aio.credentials.Credentials` instance:: + + import aiohttp + import google.auth.aio.transport.aiohttp + + # Default example: + request = google.auth.aio.transport.aiohttp.Request() + await credentials.refresh(request) + + # Custom aiohttp Session Example: + session = session=aiohttp.ClientSession(auto_decompress=False) + request = google.auth.aio.transport.aiohttp.Request(session=session) + auth_session = google.auth.aio.transport.sessions.AsyncAuthorizedSession(auth_request=request) + + Args: + session (aiohttp.ClientSession): An instance :class:`aiohttp.ClientSession` used + to make HTTP requests. If not specified, a session will be created. + + .. automethod:: __call__ + """ + + def __init__(self, session: Optional[aiohttp.ClientSession] = None): + self._session = session + self._closed = False + + async def __call__( + self, + url: str, + method: str = "GET", + body: Optional[bytes] = None, + headers: Optional[Mapping[str, str]] = None, + timeout: Union[float, ClientTimeout] = transport._DEFAULT_TIMEOUT_SECONDS, + **kwargs, + ) -> transport.Response: + """ + Make an HTTP request using aiohttp. + + Args: + url (str): The URL to be requested. + method (Optional[str]): + The HTTP method to use for the request. Defaults to 'GET'. + body (Optional[bytes]): + The payload or body in HTTP request. + headers (Optional[Mapping[str, str]]): + Request headers. + timeout (float): The number of seconds to wait for a + response from the server. If not specified or if None, the + requests default timeout will be used. + kwargs: Additional arguments passed through to the underlying + aiohttp :meth:`aiohttp.Session.request` method. + + Returns: + google.auth.aio.transport.Response: The HTTP response. + + Raises: + - google.auth.exceptions.TransportError: If the request fails or if the session is closed. + - google.auth.exceptions.TimeoutError: If the request times out. + """ + + try: + if self._closed: + raise exceptions.TransportError("session is closed.") + + if not self._session: + self._session = aiohttp.ClientSession() + + if isinstance(timeout, aiohttp.ClientTimeout): + client_timeout = timeout + else: + client_timeout = aiohttp.ClientTimeout(total=timeout) + _helpers.request_log(_LOGGER, method, url, body, headers) + response = await self._session.request( + method, + url, + data=body, + headers=headers, + timeout=client_timeout, + **kwargs, + ) + await _helpers_async.response_log_async(_LOGGER, response) + return Response(response) + + except aiohttp.ClientError as caught_exc: + client_exc = exceptions.TransportError(f"Failed to send request to {url}.") + raise client_exc from caught_exc + + except asyncio.TimeoutError as caught_exc: + if isinstance(timeout, aiohttp.ClientTimeout): + timeout_seconds = timeout.total + else: + timeout_seconds = timeout + timeout_exc = exceptions.TimeoutError( + f"Request timed out after {timeout_seconds} seconds." + ) + raise timeout_exc from caught_exc + + async def close(self) -> None: + """ + Close the underlying aiohttp session to release the acquired resources. + """ + if not self._closed and self._session: + await self._session.close() + self._closed = True diff --git a/intelliaide/vendor/google/auth/aio/transport/mtls.py b/intelliaide/vendor/google/auth/aio/transport/mtls.py new file mode 100644 index 0000000..b85d30b --- /dev/null +++ b/intelliaide/vendor/google/auth/aio/transport/mtls.py @@ -0,0 +1,197 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Helper functions for mTLS in async for discovery of certs. +""" + +import asyncio +import contextlib +import logging +import os +import ssl +import tempfile +from typing import Optional + +from google.auth import exceptions +import google.auth.transport._mtls_helper +import google.auth.transport.mtls + +_LOGGER = logging.getLogger(__name__) + + +@contextlib.contextmanager +def _create_temp_file(content: bytes): + """Creates a temporary file with the given content. + + Args: + content (bytes): The content to write to the file. + + Yields: + str: The path to the temporary file. + """ + # Create a temporary file that is readable only by the owner. + fd, file_path = tempfile.mkstemp() + try: + with os.fdopen(fd, "wb") as f: + f.write(content) + yield file_path + finally: + # Securely delete the file after use. + if os.path.exists(file_path): + os.remove(file_path) + + +def make_client_cert_ssl_context( + cert_bytes: bytes, key_bytes: bytes, passphrase: Optional[bytes] = None +) -> ssl.SSLContext: + """Creates an SSLContext with the given client certificate and key. + This function writes the certificate and key to temporary files so that + ssl.create_default_context can load them, as the ssl module requires + file paths for client certificates. These temporary files are deleted + immediately after the SSL context is created. + Args: + cert_bytes (bytes): The client certificate content in PEM format. + key_bytes (bytes): The client private key content in PEM format. + passphrase (Optional[bytes]): The passphrase for the private key, if any. + Returns: + ssl.SSLContext: The configured SSL context with client certificate. + + Raises: + google.auth.exceptions.TransportError: If there is an error loading the certificate. + """ + with _create_temp_file(cert_bytes) as cert_path, _create_temp_file( + key_bytes + ) as key_path: + try: + context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) + context.load_cert_chain( + certfile=cert_path, keyfile=key_path, password=passphrase + ) + return context + except (ssl.SSLError, OSError, IOError, ValueError, RuntimeError) as exc: + raise exceptions.TransportError( + "Failed to load client certificate and key for mTLS." + ) from exc + + +async def _run_in_executor(func, *args): + """Run a blocking function in an executor to avoid blocking the event loop. + + This implements the non-blocking execution strategy for disk I/O operations. + """ + try: + # For python versions 3.9 and newer versions + return await asyncio.to_thread(func, *args) + except AttributeError: + # Fallback for older Python versions + loop = asyncio.get_running_loop() + return await loop.run_in_executor(None, func, *args) + + +def default_client_cert_source(): + """Get a callback which returns the default client SSL credentials. + + Returns: + Awaitable[Callable[[], Tuple[bytes, bytes]]]: A callback which returns the default + client certificate bytes and private key bytes, both in PEM format. + + Raises: + google.auth.exceptions.DefaultClientCertSourceError: If the default + client SSL credentials don't exist or are malformed. + """ + if not google.auth.transport.mtls.has_default_client_cert_source( + include_context_aware=False + ): + raise exceptions.MutualTLSChannelError( + "Default client cert source doesn't exist" + ) + + async def callback(): + try: + _, cert_bytes, key_bytes = await get_client_cert_and_key() + except (OSError, RuntimeError, ValueError) as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + raise new_exc from caught_exc + + return cert_bytes, key_bytes + + return callback + + +async def get_client_ssl_credentials( + certificate_config_path=None, +): + """Returns the client side certificate, private key and passphrase. + + We look for certificates and keys with the following order of priority: + 1. Certificate and key specified by certificate_config.json. + Currently, only X.509 workload certificates are supported. + + Args: + certificate_config_path (str): The certificate_config.json file path. + + Returns: + Tuple[bool, bytes, bytes, bytes]: + A boolean indicating if cert, key and passphrase are obtained, the + cert bytes and key bytes both in PEM format, and passphrase bytes. + + Raises: + google.auth.exceptions.ClientCertError: if problems occurs when getting + the cert, key and passphrase. + """ + + # Attempt to retrieve X.509 Workload cert and key. + cert, key = await _run_in_executor( + google.auth.transport._mtls_helper._get_workload_cert_and_key, + certificate_config_path, + False, + ) + + if cert and key: + return True, cert, key, None + + return False, None, None, None + + +async def get_client_cert_and_key(client_cert_callback=None): + """Returns the client side certificate and private key. The function first + tries to get certificate and key from client_cert_callback; if the callback + is None or doesn't provide certificate and key, the function tries application + default SSL credentials. + + Args: + client_cert_callback (Optional[Callable[[], (bytes, bytes)]]): An + optional callback which returns client certificate bytes and private + key bytes both in PEM format. + + Returns: + Tuple[bool, bytes, bytes]: + A boolean indicating if cert and key are obtained, the cert bytes + and key bytes both in PEM format. + + Raises: + google.auth.exceptions.ClientCertError: if problems occurs when getting + the cert and key. + """ + if client_cert_callback: + result = client_cert_callback() + try: + cert, key = await result + except TypeError: + cert, key = result + return True, cert, key + + has_cert, cert, key, _ = await get_client_ssl_credentials() + return has_cert, cert, key diff --git a/intelliaide/vendor/google/auth/aio/transport/sessions.py b/intelliaide/vendor/google/auth/aio/transport/sessions.py new file mode 100644 index 0000000..027cb09 --- /dev/null +++ b/intelliaide/vendor/google/auth/aio/transport/sessions.py @@ -0,0 +1,576 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import asyncio +from contextlib import asynccontextmanager +import functools +import time +from typing import Mapping, Optional, TYPE_CHECKING, Union + +from google.auth import _exponential_backoff, exceptions +from google.auth.aio import transport +from google.auth.aio.credentials import Credentials +from google.auth.aio.transport import mtls +from google.auth.exceptions import TimeoutError +import google.auth.transport._mtls_helper + +if TYPE_CHECKING: # pragma: NO COVER + import aiohttp + from aiohttp import ClientTimeout # type: ignore + +else: + try: + import aiohttp + from aiohttp import ClientTimeout + except (ImportError, AttributeError): + ClientTimeout = None + +# Tracks the internal aiohttp installation and usage +try: + from google.auth.aio.transport.aiohttp import Request as AiohttpRequest + + AIOHTTP_INSTALLED = True +except ImportError: # pragma: NO COVER + AIOHTTP_INSTALLED = False + + +@asynccontextmanager +async def timeout_guard(timeout): + """ + timeout_guard is an asynchronous context manager to apply a timeout to an asynchronous block of code. + + Args: + timeout (float): The time in seconds before the context manager times out. + + Raises: + google.auth.exceptions.TimeoutError: If the code within the context exceeds the provided timeout. + + Usage: + async with timeout_guard(10) as with_timeout: + await with_timeout(async_function()) + """ + start = time.monotonic() + total_timeout = timeout + + def _remaining_time(): + elapsed = time.monotonic() - start + remaining = total_timeout - elapsed + if remaining <= 0: + raise TimeoutError( + f"Context manager exceeded the configured timeout of {total_timeout}s." + ) + return remaining + + async def with_timeout(coro): + try: + remaining = _remaining_time() + response = await asyncio.wait_for(coro, remaining) + return response + except (asyncio.TimeoutError, TimeoutError) as e: + raise TimeoutError( + f"The operation {coro} exceeded the configured timeout of {total_timeout}s." + ) from e + + try: + yield with_timeout + + finally: + _remaining_time() + + +class AsyncAuthorizedSession: + """This is an asynchronous implementation of :class:`google.auth.requests.AuthorizedSession` class. + We utilize an instance of a class that implements :class:`google.auth.aio.transport.Request` configured + by the caller or otherwise default to `google.auth.aio.transport.aiohttp.Request` if the external aiohttp + package is installed. + + A Requests Session class with credentials. + + This class is used to perform asynchronous requests to API endpoints that require + authorization:: + + import aiohttp + from google.auth.aio.transport import sessions + + async with sessions.AsyncAuthorizedSession(credentials) as authed_session: + response = await authed_session.request( + 'GET', 'https://www.googleapis.com/storage/v1/b') + + The underlying :meth:`request` implementation handles adding the + credentials' headers to the request and refreshing credentials as needed. + + Args: + credentials (google.auth.aio.credentials.Credentials): + The credentials to add to the request. + auth_request (Optional[google.auth.aio.transport.Request]): + An instance of a class that implements + :class:`~google.auth.aio.transport.Request` used to make requests + and refresh credentials. If not passed, + an instance of :class:`~google.auth.aio.transport.aiohttp.Request` + is created. + + Raises: + - google.auth.exceptions.TransportError: If `auth_request` is `None` + and the external package `aiohttp` is not installed. + - google.auth.exceptions.InvalidType: If the provided credentials are + not of type `google.auth.aio.credentials.Credentials`. + """ + + def __init__( + self, credentials: Credentials, auth_request: Optional[transport.Request] = None + ): + if not isinstance(credentials, Credentials): + raise exceptions.InvalidType( + f"The configured credentials of type {type(credentials)} are invalid and must be of type `google.auth.aio.credentials.Credentials`" + ) + self._credentials = credentials + _auth_request = auth_request + if not _auth_request and AIOHTTP_INSTALLED: + _auth_request = AiohttpRequest() + self._is_mtls = False + self._mtls_init_task = None + self._cached_cert = None + if _auth_request is None: + raise exceptions.TransportError( + "`auth_request` must either be configured or the external package `aiohttp` must be installed to use the default value." + ) + self._auth_request = _auth_request + + async def configure_mtls_channel(self, client_cert_callback=None): + """Configure the client certificate and key for SSL connection. + + The function does nothing unless `GOOGLE_API_USE_CLIENT_CERTIFICATE` is + explicitly set to `true`. In this case if client certificate and key are + successfully obtained (from the given client_cert_callback or from application + default SSL credentials), the underlying transport will be reconfigured + to use mTLS. + Note: This function does nothing if the `aiohttp` library is not + installed. + Important: Calling this method will close any ongoing API requests associated + with the current session. To ensure a smooth transition, it is recommended + to call this during session initialization. + + Args: + client_cert_callback (Optional[Callable[[], (bytes, bytes)]]): + The optional callback returns the client certificate and private + key bytes both in PEM format. + If the callback is None, application default SSL credentials + will be used. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel + creation failed for any reason. + """ + if self._mtls_init_task is None: + + async def _do_configure(): + # Run the blocking check in an executor + use_client_cert = await mtls._run_in_executor( + google.auth.transport._mtls_helper.check_use_client_cert + ) + if not use_client_cert: + self._is_mtls = False + return + + try: + ( + self._is_mtls, + cert, + key, + ) = await mtls.get_client_cert_and_key(client_cert_callback) + + if self._is_mtls: + self._cached_cert = cert + ssl_context = await mtls._run_in_executor( + mtls.make_client_cert_ssl_context, cert, key + ) + + # Re-create the auth request with the new SSL context + if AIOHTTP_INSTALLED and isinstance( + self._auth_request, AiohttpRequest + ): + connector = aiohttp.TCPConnector(ssl=ssl_context) + new_session = aiohttp.ClientSession(connector=connector) + + old_auth_request = self._auth_request + self._auth_request = AiohttpRequest(session=new_session) + + await old_auth_request.close() + + except ( + exceptions.ClientCertError, + ImportError, + OSError, + ) as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + raise new_exc from caught_exc + + self._mtls_init_task = asyncio.create_task(_do_configure()) + + return await self._mtls_init_task + + async def request( + self, + method: str, + url: str, + data: Optional[bytes] = None, + headers: Optional[Mapping[str, str]] = None, + max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS, + timeout: Union[float, ClientTimeout] = transport._DEFAULT_TIMEOUT_SECONDS, + total_attempts: Optional[int] = transport.DEFAULT_MAX_RETRY_ATTEMPTS, + **kwargs, + ) -> transport.Response: + """ + Args: + method (str): The http method used to make the request. + url (str): The URI to be requested. + data (Optional[bytes]): The payload or body in HTTP request. + headers (Optional[Mapping[str, str]]): Request headers. + timeout (float, aiohttp.ClientTimeout): + The amount of time in seconds to wait for the server response + with each individual request. + max_allowed_time (float): + If the method runs longer than this, a ``Timeout`` exception is + automatically raised. Unlike the ``timeout`` parameter, this + value applies to the total method execution time, even if + multiple requests are made under the hood. + total_attempts (int): + The total number of retry attempts. + + Mind that it is not guaranteed that the timeout error is raised + at ``max_allowed_time``. It might take longer, for example, if + an underlying request takes a lot of time, but the request + itself does not timeout, e.g. if a large file is being + transmitted. The timeout error will be raised after such + request completes. + + Returns: + google.auth.aio.transport.Response: The HTTP response. + + Raises: + google.auth.exceptions.TimeoutError: If the method does not complete within + the configured `max_allowed_time` or the request exceeds the configured + `timeout`. + """ + if self._mtls_init_task: + try: + await self._mtls_init_task + except Exception: + # Suppress all exceptions from the background mTLS initialization task, + # allowing the request to fail naturally elsewhere. + pass + retries = _exponential_backoff.AsyncExponentialBackoff( + total_attempts=total_attempts, + ) + if headers is None: + headers = {} + async with timeout_guard(max_allowed_time) as with_timeout: + await with_timeout( + # Note: before_request will attempt to refresh credentials if expired. + self._credentials.before_request( + self._auth_request, method, url, headers + ) + ) + actual_timeout: float = 0.0 + if ClientTimeout is not None and isinstance(timeout, ClientTimeout): + actual_timeout = timeout.total if timeout.total is not None else 0.0 + elif isinstance(timeout, (int, float)): + actual_timeout = float(timeout) + # Workaround issue in python 3.9 related to code coverage by adding `# pragma: no branch` + # See https://github.com/googleapis/gapic-generator-python/pull/1174#issuecomment-1025132372 + async for _ in retries: # pragma: no branch + response = await with_timeout( + self._auth_request( + url, method, data, headers, actual_timeout, **kwargs + ) + ) + if response.status_code not in transport.DEFAULT_RETRYABLE_STATUS_CODES: + break + return response + + @functools.wraps(request) + async def get( + self, + url: str, + data: Optional[bytes] = None, + headers: Optional[Mapping[str, str]] = None, + max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS, + timeout: Union[float, ClientTimeout] = transport._DEFAULT_TIMEOUT_SECONDS, + total_attempts: Optional[int] = transport.DEFAULT_MAX_RETRY_ATTEMPTS, + **kwargs, + ) -> transport.Response: + """ + Args: + url (str): The URI to be requested. + data (Optional[bytes]): The payload or body in HTTP request. + headers (Optional[Mapping[str, str]]): Request headers. + max_allowed_time (float): + If the method runs longer than this, a ``Timeout`` exception is + automatically raised. Unlike the ``timeout`` parameter, this + value applies to the total method execution time, even if + multiple requests are made under the hood. + timeout (float, aiohttp.ClientTimeout): + The amount of time in seconds to wait for the server response + with each individual request. + total_attempts (int): + The total number of retry attempts. + + Mind that it is not guaranteed that the timeout error is raised + at ``max_allowed_time``. It might take longer, for example, if + an underlying request takes a lot of time, but the request + itself does not timeout, e.g. if a large file is being + transmitted. The timeout error will be raised after such + request completes. + + Returns: + google.auth.aio.transport.Response: The HTTP response. + + Raises: + google.auth.exceptions.TimeoutError: If the method does not complete within + the configured `max_allowed_time` or the request exceeds the configured + `timeout`. + """ + return await self.request( + "GET", + url, + data, + headers, + max_allowed_time, + timeout, + total_attempts, + **kwargs, + ) + + @functools.wraps(request) + async def post( + self, + url: str, + data: Optional[bytes] = None, + headers: Optional[Mapping[str, str]] = None, + max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS, + timeout: Union[float, ClientTimeout] = transport._DEFAULT_TIMEOUT_SECONDS, + total_attempts: Optional[int] = transport.DEFAULT_MAX_RETRY_ATTEMPTS, + **kwargs, + ) -> transport.Response: + """ + Args: + url (str): The URI to be requested. + data (Optional[bytes]): The payload or body in HTTP request. + headers (Optional[Mapping[str, str]]): Request headers. + max_allowed_time (float): + If the method runs longer than this, a ``Timeout`` exception is + automatically raised. Unlike the ``timeout`` parameter, this + value applies to the total method execution time, even if + multiple requests are made under the hood. + timeout (float, aiohttp.ClientTimeout): + The amount of time in seconds to wait for the server response + with each individual request. + total_attempts (int): + The total number of retry attempts. + + Mind that it is not guaranteed that the timeout error is raised + at ``max_allowed_time``. It might take longer, for example, if + an underlying request takes a lot of time, but the request + itself does not timeout, e.g. if a large file is being + transmitted. The timeout error will be raised after such + request completes. + + Returns: + google.auth.aio.transport.Response: The HTTP response. + + Raises: + google.auth.exceptions.TimeoutError: If the method does not complete within + the configured `max_allowed_time` or the request exceeds the configured + `timeout`. + """ + return await self.request( + "POST", + url, + data, + headers, + max_allowed_time, + timeout, + total_attempts, + **kwargs, + ) + + @functools.wraps(request) + async def put( + self, + url: str, + data: Optional[bytes] = None, + headers: Optional[Mapping[str, str]] = None, + max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS, + timeout: Union[float, ClientTimeout] = transport._DEFAULT_TIMEOUT_SECONDS, + total_attempts: Optional[int] = transport.DEFAULT_MAX_RETRY_ATTEMPTS, + **kwargs, + ) -> transport.Response: + """ + Args: + url (str): The URI to be requested. + data (Optional[bytes]): The payload or body in HTTP request. + headers (Optional[Mapping[str, str]]): Request headers. + max_allowed_time (float): + If the method runs longer than this, a ``Timeout`` exception is + automatically raised. Unlike the ``timeout`` parameter, this + value applies to the total method execution time, even if + multiple requests are made under the hood. + timeout (float, aiohttp.ClientTimeout): + The amount of time in seconds to wait for the server response + with each individual request. + total_attempts (int): + The total number of retry attempts. + + Mind that it is not guaranteed that the timeout error is raised + at ``max_allowed_time``. It might take longer, for example, if + an underlying request takes a lot of time, but the request + itself does not timeout, e.g. if a large file is being + transmitted. The timeout error will be raised after such + request completes. + + Returns: + google.auth.aio.transport.Response: The HTTP response. + + Raises: + google.auth.exceptions.TimeoutError: If the method does not complete within + the configured `max_allowed_time` or the request exceeds the configured + `timeout`. + """ + return await self.request( + "PUT", + url, + data, + headers, + max_allowed_time, + timeout, + total_attempts, + **kwargs, + ) + + @functools.wraps(request) + async def patch( + self, + url: str, + data: Optional[bytes] = None, + headers: Optional[Mapping[str, str]] = None, + max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS, + timeout: Union[float, ClientTimeout] = transport._DEFAULT_TIMEOUT_SECONDS, + total_attempts: Optional[int] = transport.DEFAULT_MAX_RETRY_ATTEMPTS, + **kwargs, + ) -> transport.Response: + """ + Args: + url (str): The URI to be requested. + data (Optional[bytes]): The payload or body in HTTP request. + headers (Optional[Mapping[str, str]]): Request headers. + max_allowed_time (float): + If the method runs longer than this, a ``Timeout`` exception is + automatically raised. Unlike the ``timeout`` parameter, this + value applies to the total method execution time, even if + multiple requests are made under the hood. + timeout (float, aiohttp.ClientTimeout): + The amount of time in seconds to wait for the server response + with each individual request. + total_attempts (int): + The total number of retry attempts. + + Mind that it is not guaranteed that the timeout error is raised + at ``max_allowed_time``. It might take longer, for example, if + an underlying request takes a lot of time, but the request + itself does not timeout, e.g. if a large file is being + transmitted. The timeout error will be raised after such + request completes. + + Returns: + google.auth.aio.transport.Response: The HTTP response. + + Raises: + google.auth.exceptions.TimeoutError: If the method does not complete within + the configured `max_allowed_time` or the request exceeds the configured + `timeout`. + """ + return await self.request( + "PATCH", + url, + data, + headers, + max_allowed_time, + timeout, + total_attempts, + **kwargs, + ) + + @functools.wraps(request) + async def delete( + self, + url: str, + data: Optional[bytes] = None, + headers: Optional[Mapping[str, str]] = None, + max_allowed_time: float = transport._DEFAULT_TIMEOUT_SECONDS, + timeout: Union[float, ClientTimeout] = transport._DEFAULT_TIMEOUT_SECONDS, + total_attempts: Optional[int] = transport.DEFAULT_MAX_RETRY_ATTEMPTS, + **kwargs, + ) -> transport.Response: + """ + Args: + url (str): The URI to be requested. + data (Optional[bytes]): The payload or body in HTTP request. + headers (Optional[Mapping[str, str]]): Request headers. + max_allowed_time (float): + If the method runs longer than this, a ``Timeout`` exception is + automatically raised. Unlike the ``timeout`` parameter, this + value applies to the total method execution time, even if + multiple requests are made under the hood. + timeout (float, aiohttp.ClientTimeout): + The amount of time in seconds to wait for the server response + with each individual request. + total_attempts (int): + The total number of retry attempts. + + Mind that it is not guaranteed that the timeout error is raised + at ``max_allowed_time``. It might take longer, for example, if + an underlying request takes a lot of time, but the request + itself does not timeout, e.g. if a large file is being + transmitted. The timeout error will be raised after such + request completes. + + Returns: + google.auth.aio.transport.Response: The HTTP response. + + Raises: + google.auth.exceptions.TimeoutError: If the method does not complete within + the configured `max_allowed_time` or the request exceeds the configured + `timeout`. + """ + return await self.request( + "DELETE", + url, + data, + headers, + max_allowed_time, + timeout, + total_attempts, + **kwargs, + ) + + @property + def is_mtls(self): + """Indicates if mutual TLS is enabled.""" + return self._is_mtls + + async def close(self) -> None: + """ + Close the underlying auth request session. + """ + await self._auth_request.close() diff --git a/intelliaide/vendor/google/auth/api_key.py b/intelliaide/vendor/google/auth/api_key.py new file mode 100644 index 0000000..4fdf7f2 --- /dev/null +++ b/intelliaide/vendor/google/auth/api_key.py @@ -0,0 +1,76 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google API key support. +This module provides authentication using the `API key`_. +.. _API key: + https://cloud.google.com/docs/authentication/api-keys/ +""" + +from google.auth import _helpers +from google.auth import credentials +from google.auth import exceptions + + +class Credentials(credentials.Credentials): + """API key credentials. + These credentials use API key to provide authorization to applications. + """ + + def __init__(self, token): + """ + Args: + token (str): API key string + Raises: + ValueError: If the provided API key is not a non-empty string. + """ + super(Credentials, self).__init__() + if not token: + raise exceptions.InvalidValue("Token must be a non-empty API key string") + self.token = token + + @property + def expired(self): + return False + + @property + def valid(self): + return True + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + return + + def apply(self, headers, token=None): + """Apply the API key token to the x-goog-api-key header. + Args: + headers (Mapping): The HTTP request headers. + token (Optional[str]): If specified, overrides the current access + token. + """ + headers["x-goog-api-key"] = token or self.token + + def before_request(self, request, method, url, headers): + """Performs credential-specific before request logic. + Refreshes the credentials if necessary, then calls :meth:`apply` to + apply the token to the x-goog-api-key header. + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + method (str): The request's HTTP method or the RPC method being + invoked. + url (str): The request's URI or the RPC service's URI. + headers (Mapping): The request's headers. + """ + self.apply(headers) diff --git a/intelliaide/vendor/google/auth/app_engine.py b/intelliaide/vendor/google/auth/app_engine.py new file mode 100644 index 0000000..49f6457 --- /dev/null +++ b/intelliaide/vendor/google/auth/app_engine.py @@ -0,0 +1,179 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google App Engine standard environment support. + +This module provides authentication and signing for applications running on App +Engine in the standard environment using the `App Identity API`_. + + +.. _App Identity API: + https://cloud.google.com/appengine/docs/python/appidentity/ +""" + + +from google.auth import _helpers +from google.auth import credentials +from google.auth import crypt +from google.auth import exceptions + +# pytype: disable=import-error +try: + from google.appengine.api import app_identity # type: ignore +except ImportError: + app_identity = None # type: ignore +# pytype: enable=import-error + + +class Signer(crypt.Signer): + """Signs messages using the App Engine App Identity service. + + This can be used in place of :class:`google.auth.crypt.Signer` when + running in the App Engine standard environment. + """ + + @property + def key_id(self): + """Optional[str]: The key ID used to identify this private key. + + .. warning:: + This is always ``None``. The key ID used by App Engine can not + be reliably determined ahead of time. + """ + return None + + @_helpers.copy_docstring(crypt.Signer) + def sign(self, message): + message = _helpers.to_bytes(message) + _, signature = app_identity.sign_blob(message) + return signature + + +def get_project_id(): + """Gets the project ID for the current App Engine application. + + Returns: + str: The project ID + + Raises: + google.auth.exceptions.OSError: If the App Engine APIs are unavailable. + """ + # pylint: disable=missing-raises-doc + # Pylint rightfully thinks google.auth.exceptions.OSError is OSError, but doesn't + # realize it's a valid alias. + if app_identity is None: + raise exceptions.OSError("The App Engine APIs are not available.") + return app_identity.get_application_id() + + +class Credentials( + credentials.Scoped, credentials.Signing, credentials.CredentialsWithQuotaProject +): + """App Engine standard environment credentials. + + These credentials use the App Engine App Identity API to obtain access + tokens. + """ + + def __init__( + self, + scopes=None, + default_scopes=None, + service_account_id=None, + quota_project_id=None, + ): + """ + Args: + scopes (Sequence[str]): Scopes to request from the App Identity + API. + default_scopes (Sequence[str]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + service_account_id (str): The service account ID passed into + :func:`google.appengine.api.app_identity.get_access_token`. + If not specified, the default application service account + ID will be used. + quota_project_id (Optional[str]): The project ID used for quota + and billing. + + Raises: + google.auth.exceptions.OSError: If the App Engine APIs are unavailable. + """ + # pylint: disable=missing-raises-doc + # Pylint rightfully thinks google.auth.exceptions.OSError is OSError, but doesn't + # realize it's a valid alias. + if app_identity is None: + raise exceptions.OSError("The App Engine APIs are not available.") + + super(Credentials, self).__init__() + self._scopes = scopes + self._default_scopes = default_scopes + self._service_account_id = service_account_id + self._signer = Signer() + self._quota_project_id = quota_project_id + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + scopes = self._scopes if self._scopes is not None else self._default_scopes + # pylint: disable=unused-argument + token, ttl = app_identity.get_access_token(scopes, self._service_account_id) + expiry = _helpers.utcfromtimestamp(ttl) + + self.token, self.expiry = token, expiry + + @property + def service_account_email(self): + """The service account email.""" + if self._service_account_id is None: + self._service_account_id = app_identity.get_service_account_name() + return self._service_account_id + + @property + def requires_scopes(self): + """Checks if the credentials requires scopes. + + Returns: + bool: True if there are no scopes set otherwise False. + """ + return not self._scopes and not self._default_scopes + + @_helpers.copy_docstring(credentials.Scoped) + def with_scopes(self, scopes, default_scopes=None): + return self.__class__( + scopes=scopes, + default_scopes=default_scopes, + service_account_id=self._service_account_id, + quota_project_id=self.quota_project_id, + ) + + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + return self.__class__( + scopes=self._scopes, + service_account_id=self._service_account_id, + quota_project_id=quota_project_id, + ) + + @_helpers.copy_docstring(credentials.Signing) + def sign_bytes(self, message): + return self._signer.sign(message) + + @property # type: ignore + @_helpers.copy_docstring(credentials.Signing) + def signer_email(self): + return self.service_account_email + + @property # type: ignore + @_helpers.copy_docstring(credentials.Signing) + def signer(self): + return self._signer diff --git a/intelliaide/vendor/google/auth/aws.py b/intelliaide/vendor/google/auth/aws.py new file mode 100644 index 0000000..c640568 --- /dev/null +++ b/intelliaide/vendor/google/auth/aws.py @@ -0,0 +1,863 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""AWS Credentials and AWS Signature V4 Request Signer. + +This module provides credentials to access Google Cloud resources from Amazon +Web Services (AWS) workloads. These credentials are recommended over the +use of service account credentials in AWS as they do not involve the management +of long-live service account private keys. + +AWS Credentials are initialized using external_account arguments which are +typically loaded from the external credentials JSON file. + +This module also provides a definition for an abstract AWS security credentials supplier. +This supplier can be implemented to return valid AWS security credentials and an AWS region +and used to create AWS credentials. The credentials will then call the +supplier instead of using pre-defined methods such as calling the EC2 metadata endpoints. + +This module also provides a basic implementation of the +`AWS Signature Version 4`_ request signing algorithm. + +AWS Credentials use serialized signed requests to the +`AWS STS GetCallerIdentity`_ API that can be exchanged for Google access tokens +via the GCP STS endpoint. + +.. _AWS Signature Version 4: https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html +.. _AWS STS GetCallerIdentity: https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html +""" + +import abc +from dataclasses import dataclass +import hashlib +import hmac +import http.client as http_client +import json +import os +import posixpath +import re +from typing import Optional +import urllib +from urllib.parse import urljoin + +from google.auth import _helpers +from google.auth import environment_vars +from google.auth import exceptions +from google.auth import external_account + +# AWS Signature Version 4 signing algorithm identifier. +_AWS_ALGORITHM = "AWS4-HMAC-SHA256" +# The termination string for the AWS credential scope value as defined in +# https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html +_AWS_REQUEST_TYPE = "aws4_request" +# The AWS authorization header name for the security session token if available. +_AWS_SECURITY_TOKEN_HEADER = "x-amz-security-token" +# The AWS authorization header name for the auto-generated date. +_AWS_DATE_HEADER = "x-amz-date" +# The default AWS regional credential verification URL. +_DEFAULT_AWS_REGIONAL_CREDENTIAL_VERIFICATION_URL = ( + "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15" +) +# IMDSV2 session token lifetime. This is set to a low value because the session token is used immediately. +_IMDSV2_SESSION_TOKEN_TTL_SECONDS = "300" + + +class RequestSigner(object): + """Implements an AWS request signer based on the AWS Signature Version 4 signing + process. + https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html + """ + + def __init__(self, region_name): + """Instantiates an AWS request signer used to compute authenticated signed + requests to AWS APIs based on the AWS Signature Version 4 signing process. + + Args: + region_name (str): The AWS region to use. + """ + + self._region_name = region_name + + def get_request_options( + self, + aws_security_credentials, + url, + method, + request_payload="", + additional_headers={}, + ): + """Generates the signed request for the provided HTTP request for calling + an AWS API. This follows the steps described at: + https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html + + Args: + aws_security_credentials (AWSSecurityCredentials): The AWS security credentials. + url (str): The AWS service URL containing the canonical URI and + query string. + method (str): The HTTP method used to call this API. + request_payload (Optional[str]): The optional request payload if + available. + additional_headers (Optional[Mapping[str, str]]): The optional + additional headers needed for the requested AWS API. + + Returns: + Mapping[str, str]: The AWS signed request dictionary object. + """ + + additional_headers = additional_headers or {} + + uri = urllib.parse.urlparse(url) + # Normalize the URL path. This is needed for the canonical_uri. + # os.path.normpath can't be used since it normalizes "/" paths + # to "\\" in Windows OS. + normalized_uri = urllib.parse.urlparse( + urljoin(url, posixpath.normpath(uri.path)) + ) + # Validate provided URL. + if not uri.hostname or uri.scheme != "https": + raise exceptions.InvalidResource("Invalid AWS service URL") + + header_map = _generate_authentication_header_map( + host=uri.hostname, + canonical_uri=normalized_uri.path or "/", + canonical_querystring=_get_canonical_querystring(uri.query), + method=method, + region=self._region_name, + aws_security_credentials=aws_security_credentials, + request_payload=request_payload, + additional_headers=additional_headers, + ) + headers = { + "Authorization": header_map.get("authorization_header"), + "host": uri.hostname, + } + # Add x-amz-date if available. + if "amz_date" in header_map: + headers[_AWS_DATE_HEADER] = header_map.get("amz_date") + # Append additional optional headers, eg. X-Amz-Target, Content-Type, etc. + for key in additional_headers: + headers[key] = additional_headers[key] + + # Add session token if available. + if aws_security_credentials.session_token is not None: + headers[_AWS_SECURITY_TOKEN_HEADER] = aws_security_credentials.session_token + + signed_request = {"url": url, "method": method, "headers": headers} + if request_payload: + signed_request["data"] = request_payload + return signed_request + + +def _get_canonical_querystring(query): + """Generates the canonical query string given a raw query string. + Logic is based on + https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html + + Args: + query (str): The raw query string. + + Returns: + str: The canonical query string. + """ + # Parse raw query string. + querystring = urllib.parse.parse_qs(query) + querystring_encoded_map = {} + for key in querystring: + quote_key = urllib.parse.quote(key, safe="-_.~") + # URI encode key. + querystring_encoded_map[quote_key] = [] + for item in querystring[key]: + # For each key, URI encode all values for that key. + querystring_encoded_map[quote_key].append( + urllib.parse.quote(item, safe="-_.~") + ) + # Sort values for each key. + querystring_encoded_map[quote_key].sort() + # Sort keys. + sorted_keys = list(querystring_encoded_map.keys()) + sorted_keys.sort() + # Reconstruct the query string. Preserve keys with multiple values. + querystring_encoded_pairs = [] + for key in sorted_keys: + for item in querystring_encoded_map[key]: + querystring_encoded_pairs.append("{}={}".format(key, item)) + return "&".join(querystring_encoded_pairs) + + +def _sign(key, msg): + """Creates the HMAC-SHA256 hash of the provided message using the provided + key. + + Args: + key (str): The HMAC-SHA256 key to use. + msg (str): The message to hash. + + Returns: + str: The computed hash bytes. + """ + return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest() + + +def _get_signing_key(key, date_stamp, region_name, service_name): + """Calculates the signing key used to calculate the signature for + AWS Signature Version 4 based on: + https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html + + Args: + key (str): The AWS secret access key. + date_stamp (str): The '%Y%m%d' date format. + region_name (str): The AWS region. + service_name (str): The AWS service name, eg. sts. + + Returns: + str: The signing key bytes. + """ + k_date = _sign(("AWS4" + key).encode("utf-8"), date_stamp) + k_region = _sign(k_date, region_name) + k_service = _sign(k_region, service_name) + k_signing = _sign(k_service, "aws4_request") + return k_signing + + +def _generate_authentication_header_map( + host, + canonical_uri, + canonical_querystring, + method, + region, + aws_security_credentials, + request_payload="", + additional_headers={}, +): + """Generates the authentication header map needed for generating the AWS + Signature Version 4 signed request. + + Args: + host (str): The AWS service URL hostname. + canonical_uri (str): The AWS service URL path name. + canonical_querystring (str): The AWS service URL query string. + method (str): The HTTP method used to call this API. + region (str): The AWS region. + aws_security_credentials (AWSSecurityCredentials): The AWS security credentials. + request_payload (Optional[str]): The optional request payload if + available. + additional_headers (Optional[Mapping[str, str]]): The optional + additional headers needed for the requested AWS API. + + Returns: + Mapping[str, str]: The AWS authentication header dictionary object. + This contains the x-amz-date and authorization header information. + """ + # iam.amazonaws.com host => iam service. + # sts.us-east-2.amazonaws.com host => sts service. + service_name = host.split(".")[0] + + current_time = _helpers.utcnow() + amz_date = current_time.strftime("%Y%m%dT%H%M%SZ") + date_stamp = current_time.strftime("%Y%m%d") + + # Change all additional headers to be lower case. + full_headers = {} + for key in additional_headers: + full_headers[key.lower()] = additional_headers[key] + # Add AWS session token if available. + if aws_security_credentials.session_token is not None: + full_headers[ + _AWS_SECURITY_TOKEN_HEADER + ] = aws_security_credentials.session_token + + # Required headers + full_headers["host"] = host + # Do not use generated x-amz-date if the date header is provided. + # Previously the date was not fixed with x-amz- and could be provided + # manually. + # https://github.com/boto/botocore/blob/879f8440a4e9ace5d3cf145ce8b3d5e5ffb892ef/tests/unit/auth/aws4_testsuite/get-header-value-trim.req + if "date" not in full_headers: + full_headers[_AWS_DATE_HEADER] = amz_date + + # Header keys need to be sorted alphabetically. + canonical_headers = "" + header_keys = list(full_headers.keys()) + header_keys.sort() + for key in header_keys: + canonical_headers = "{}{}:{}\n".format( + canonical_headers, key, full_headers[key] + ) + signed_headers = ";".join(header_keys) + + payload_hash = hashlib.sha256((request_payload or "").encode("utf-8")).hexdigest() + + # https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html + canonical_request = "{}\n{}\n{}\n{}\n{}\n{}".format( + method, + canonical_uri, + canonical_querystring, + canonical_headers, + signed_headers, + payload_hash, + ) + + credential_scope = "{}/{}/{}/{}".format( + date_stamp, region, service_name, _AWS_REQUEST_TYPE + ) + + # https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html + string_to_sign = "{}\n{}\n{}\n{}".format( + _AWS_ALGORITHM, + amz_date, + credential_scope, + hashlib.sha256(canonical_request.encode("utf-8")).hexdigest(), + ) + + # https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html + signing_key = _get_signing_key( + aws_security_credentials.secret_access_key, date_stamp, region, service_name + ) + signature = hmac.new( + signing_key, string_to_sign.encode("utf-8"), hashlib.sha256 + ).hexdigest() + + # https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html + authorization_header = "{} Credential={}/{}, SignedHeaders={}, Signature={}".format( + _AWS_ALGORITHM, + aws_security_credentials.access_key_id, + credential_scope, + signed_headers, + signature, + ) + + authentication_header = {"authorization_header": authorization_header} + # Do not use generated x-amz-date if the date header is provided. + if "date" not in full_headers: + authentication_header["amz_date"] = amz_date + return authentication_header + + +@dataclass +class AwsSecurityCredentials: + """A class that models AWS security credentials with an optional session token. + + Attributes: + access_key_id (str): The AWS security credentials access key id. + secret_access_key (str): The AWS security credentials secret access key. + session_token (Optional[str]): The optional AWS security credentials session token. This should be set when using temporary credentials. + """ + + access_key_id: str + secret_access_key: str + session_token: Optional[str] = None + + +class AwsSecurityCredentialsSupplier(metaclass=abc.ABCMeta): + """Base class for AWS security credential suppliers. This can be implemented with custom logic to retrieve + AWS security credentials to exchange for a Google Cloud access token. The AWS external account credential does + not cache the AWS security credentials, so caching logic should be added in the implementation. + """ + + @abc.abstractmethod + def get_aws_security_credentials(self, context, request): + """Returns the AWS security credentials for the requested context. + + .. warning: This is not cached by the calling Google credential, so caching logic should be implemented in the supplier. + + Args: + context (google.auth.externalaccount.SupplierContext): The context object + containing information about the requested audience and subject token type. + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If an error is encountered during + security credential retrieval logic. + + Returns: + AwsSecurityCredentials: The requested AWS security credentials. + """ + raise NotImplementedError("") + + @abc.abstractmethod + def get_aws_region(self, context, request): + """Returns the AWS region for the requested context. + + Args: + context (google.auth.externalaccount.SupplierContext): The context object + containing information about the requested audience and subject token type. + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If an error is encountered during + region retrieval logic. + + Returns: + str: The AWS region. + """ + raise NotImplementedError("") + + +class _DefaultAwsSecurityCredentialsSupplier(AwsSecurityCredentialsSupplier): + """Default implementation of AWS security credentials supplier. Supports retrieving + credentials and region via EC2 metadata endpoints and environment variables. + """ + + def __init__(self, credential_source): + self._region_url = credential_source.get("region_url") + self._security_credentials_url = credential_source.get("url") + self._imdsv2_session_token_url = credential_source.get( + "imdsv2_session_token_url" + ) + + @_helpers.copy_docstring(AwsSecurityCredentialsSupplier) + def get_aws_security_credentials(self, context, request): + # Check environment variables for permanent credentials first. + # https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html + env_aws_access_key_id = os.environ.get(environment_vars.AWS_ACCESS_KEY_ID) + env_aws_secret_access_key = os.environ.get( + environment_vars.AWS_SECRET_ACCESS_KEY + ) + # This is normally not available for permanent credentials. + env_aws_session_token = os.environ.get(environment_vars.AWS_SESSION_TOKEN) + if env_aws_access_key_id and env_aws_secret_access_key: + return AwsSecurityCredentials( + env_aws_access_key_id, env_aws_secret_access_key, env_aws_session_token + ) + + imdsv2_session_token = self._get_imdsv2_session_token(request) + role_name = self._get_metadata_role_name(request, imdsv2_session_token) + + # Get security credentials. + credentials = self._get_metadata_security_credentials( + request, role_name, imdsv2_session_token + ) + + return AwsSecurityCredentials( + credentials.get("AccessKeyId"), + credentials.get("SecretAccessKey"), + credentials.get("Token"), + ) + + @_helpers.copy_docstring(AwsSecurityCredentialsSupplier) + def get_aws_region(self, context, request): + # The AWS metadata server is not available in some AWS environments + # such as AWS lambda. Instead, it is available via environment + # variable. + env_aws_region = os.environ.get(environment_vars.AWS_REGION) + if env_aws_region is not None: + return env_aws_region + + env_aws_region = os.environ.get(environment_vars.AWS_DEFAULT_REGION) + if env_aws_region is not None: + return env_aws_region + + if not self._region_url: + raise exceptions.RefreshError("Unable to determine AWS region") + + headers = None + imdsv2_session_token = self._get_imdsv2_session_token(request) + if imdsv2_session_token is not None: + headers = {"X-aws-ec2-metadata-token": imdsv2_session_token} + + response = request(url=self._region_url, method="GET", headers=headers) + + # Support both string and bytes type response.data. + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + if response.status != http_client.OK: + raise exceptions.RefreshError( + "Unable to retrieve AWS region: {}".format(response_body) + ) + + # This endpoint will return the region in format: us-east-2b. + # Only the us-east-2 part should be used. + return response_body[:-1] + + def _get_imdsv2_session_token(self, request): + if request is not None and self._imdsv2_session_token_url is not None: + headers = { + "X-aws-ec2-metadata-token-ttl-seconds": _IMDSV2_SESSION_TOKEN_TTL_SECONDS + } + + imdsv2_session_token_response = request( + url=self._imdsv2_session_token_url, method="PUT", headers=headers + ) + + if imdsv2_session_token_response.status != http_client.OK: + raise exceptions.RefreshError( + "Unable to retrieve AWS Session Token: {}".format( + imdsv2_session_token_response.data + ) + ) + + return imdsv2_session_token_response.data + else: + return None + + def _get_metadata_security_credentials( + self, request, role_name, imdsv2_session_token + ): + """Retrieves the AWS security credentials required for signing AWS + requests from the AWS metadata server. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + role_name (str): The AWS role name required by the AWS metadata + server security_credentials endpoint in order to return the + credentials. + imdsv2_session_token (str): The AWS IMDSv2 session token to be added as a + header in the requests to AWS metadata endpoint. + + Returns: + Mapping[str, str]: The AWS metadata server security credentials + response. + + Raises: + google.auth.exceptions.RefreshError: If an error occurs while + retrieving the AWS security credentials. + """ + if imdsv2_session_token is not None: + headers = {"X-aws-ec2-metadata-token": imdsv2_session_token} + else: + headers = None + + response = request( + url="{}/{}".format(self._security_credentials_url, role_name), + method="GET", + headers=headers, + ) + + # support both string and bytes type response.data + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + if response.status != http_client.OK: + raise exceptions.RefreshError( + "Unable to retrieve AWS security credentials: {}".format(response_body) + ) + + credentials_response = json.loads(response_body) + + return credentials_response + + def _get_metadata_role_name(self, request, imdsv2_session_token): + """Retrieves the AWS role currently attached to the current AWS + workload by querying the AWS metadata server. This is needed for the + AWS metadata server security credentials endpoint in order to retrieve + the AWS security credentials needed to sign requests to AWS APIs. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + imdsv2_session_token (str): The AWS IMDSv2 session token to be added as a + header in the requests to AWS metadata endpoint. + + Returns: + str: The AWS role name. + + Raises: + google.auth.exceptions.RefreshError: If an error occurs while + retrieving the AWS role name. + """ + if self._security_credentials_url is None: + raise exceptions.RefreshError( + "Unable to determine the AWS metadata server security credentials endpoint" + ) + + headers = None + if imdsv2_session_token is not None: + headers = {"X-aws-ec2-metadata-token": imdsv2_session_token} + + response = request( + url=self._security_credentials_url, method="GET", headers=headers + ) + + # support both string and bytes type response.data + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + if response.status != http_client.OK: + raise exceptions.RefreshError( + "Unable to retrieve AWS role name {}".format(response_body) + ) + + return response_body + + +class Credentials(external_account.Credentials): + """AWS external account credentials. + This is used to exchange serialized AWS signature v4 signed requests to + AWS STS GetCallerIdentity service for Google access tokens. + """ + + def __init__( + self, + audience, + subject_token_type, + token_url=external_account._DEFAULT_TOKEN_URL, + credential_source=None, + aws_security_credentials_supplier=None, + *args, + **kwargs + ): + """Instantiates an AWS workload external account credentials object. + + Args: + audience (str): The STS audience field. + subject_token_type (str): The subject token type based on the Oauth2.0 token exchange spec. + Expected values include:: + + “urn:ietf:params:aws:token-type:aws4_request” + + token_url (Optional [str]): The STS endpoint URL. If not provided, will default to "https://sts.googleapis.com/v1/token". + credential_source (Optional [Mapping]): The credential source dictionary used + to provide instructions on how to retrieve external credential to be exchanged for Google access tokens. + Either a credential source or an AWS security credentials supplier must be provided. + + Example credential_source for AWS credential:: + + { + "environment_id": "aws1", + "regional_cred_verification_url": "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15", + "region_url": "http://169.254.169.254/latest/meta-data/placement/availability-zone", + "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials", + imdsv2_session_token_url": "http://169.254.169.254/latest/api/token" + } + + aws_security_credentials_supplier (Optional [AwsSecurityCredentialsSupplier]): Optional AWS security credentials supplier. + This will be called to supply valid AWS security credentails which will then + be exchanged for Google access tokens. Either an AWS security credentials supplier + or a credential source must be provided. + args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. + kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. + + Raises: + google.auth.exceptions.RefreshError: If an error is encountered during + access token retrieval logic. + ValueError: For invalid parameters. + + .. note:: Typically one of the helper constructors + :meth:`from_file` or + :meth:`from_info` are used instead of calling the constructor directly. + """ + super(Credentials, self).__init__( + audience=audience, + subject_token_type=subject_token_type, + token_url=token_url, + credential_source=credential_source, + *args, + **kwargs + ) + if credential_source is None and aws_security_credentials_supplier is None: + raise exceptions.InvalidValue( + "A valid credential source or AWS security credentials supplier must be provided." + ) + if ( + credential_source is not None + and aws_security_credentials_supplier is not None + ): + raise exceptions.InvalidValue( + "AWS credential cannot have both a credential source and an AWS security credentials supplier." + ) + + if aws_security_credentials_supplier: + self._aws_security_credentials_supplier = aws_security_credentials_supplier + # The regional cred verification URL would normally be provided through the credential source. So set it to the default one here. + self._cred_verification_url = ( + _DEFAULT_AWS_REGIONAL_CREDENTIAL_VERIFICATION_URL + ) + else: + environment_id = credential_source.get("environment_id") or "" + self._aws_security_credentials_supplier = ( + _DefaultAwsSecurityCredentialsSupplier(credential_source) + ) + self._cred_verification_url = credential_source.get( + "regional_cred_verification_url" + ) + + # Get the environment ID, i.e. "aws1". Currently, only one version supported (1). + matches = re.match(r"^(aws)([\d]+)$", environment_id) + if matches: + env_id, env_version = matches.groups() + else: + env_id, env_version = (None, None) + + if env_id != "aws" or self._cred_verification_url is None: + raise exceptions.InvalidResource( + "No valid AWS 'credential_source' provided" + ) + elif env_version is None or int(env_version) != 1: + raise exceptions.InvalidValue( + "aws version '{}' is not supported in the current build.".format( + env_version + ) + ) + + self._target_resource = audience + self._request_signer = None + + def retrieve_subject_token(self, request): + """Retrieves the subject token using the credential_source object. + The subject token is a serialized `AWS GetCallerIdentity signed request`_. + + The logic is summarized as: + + Retrieve the AWS region from the AWS_REGION or AWS_DEFAULT_REGION + environment variable or from the AWS metadata server availability-zone + if not found in the environment variable. + + Check AWS credentials in environment variables. If not found, retrieve + from the AWS metadata server security-credentials endpoint. + + When retrieving AWS credentials from the metadata server + security-credentials endpoint, the AWS role needs to be determined by + calling the security-credentials endpoint without any argument. Then the + credentials can be retrieved via: security-credentials/role_name + + Generate the signed request to AWS STS GetCallerIdentity action. + + Inject x-goog-cloud-target-resource into header and serialize the + signed request. This will be the subject-token to pass to GCP STS. + + .. _AWS GetCallerIdentity signed request: + https://cloud.google.com/iam/docs/access-resources-aws#exchange-token + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + Returns: + str: The retrieved subject token. + """ + + # Initialize the request signer if not yet initialized after determining + # the current AWS region. + if self._request_signer is None: + self._region = self._aws_security_credentials_supplier.get_aws_region( + self._supplier_context, request + ) + self._request_signer = RequestSigner(self._region) + + # Retrieve the AWS security credentials needed to generate the signed + # request. + aws_security_credentials = ( + self._aws_security_credentials_supplier.get_aws_security_credentials( + self._supplier_context, request + ) + ) + # Generate the signed request to AWS STS GetCallerIdentity API. + # Use the required regional endpoint. Otherwise, the request will fail. + request_options = self._request_signer.get_request_options( + aws_security_credentials, + self._cred_verification_url.replace("{region}", self._region), + "POST", + ) + # The GCP STS endpoint expects the headers to be formatted as: + # [ + # {key: 'x-amz-date', value: '...'}, + # {key: 'Authorization', value: '...'}, + # ... + # ] + # And then serialized as: + # quote(json.dumps({ + # url: '...', + # method: 'POST', + # headers: [{key: 'x-amz-date', value: '...'}, ...] + # })) + request_headers = request_options.get("headers") + # The full, canonical resource name of the workload identity pool + # provider, with or without the HTTPS prefix. + # Including this header as part of the signature is recommended to + # ensure data integrity. + request_headers["x-goog-cloud-target-resource"] = self._target_resource + + # Serialize AWS signed request. + aws_signed_req = {} + aws_signed_req["url"] = request_options.get("url") + aws_signed_req["method"] = request_options.get("method") + aws_signed_req["headers"] = [] + # Reformat header to GCP STS expected format. + for key in request_headers.keys(): + aws_signed_req["headers"].append( + {"key": key, "value": request_headers[key]} + ) + + return urllib.parse.quote( + json.dumps(aws_signed_req, separators=(",", ":"), sort_keys=True) + ) + + def _create_default_metrics_options(self): + metrics_options = super(Credentials, self)._create_default_metrics_options() + metrics_options["source"] = "aws" + if self._has_custom_supplier(): + metrics_options["source"] = "programmatic" + return metrics_options + + def _has_custom_supplier(self): + return self._credential_source is None + + def _constructor_args(self): + args = super(Credentials, self)._constructor_args() + # If a custom supplier was used, append it to the args dict. + if self._has_custom_supplier(): + args.update( + { + "aws_security_credentials_supplier": self._aws_security_credentials_supplier + } + ) + return args + + @classmethod + def from_info(cls, info, **kwargs): + """Creates an AWS Credentials instance from parsed external account info. + + Args: + info (Mapping[str, str]): The AWS external account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.aws.Credentials: The constructed credentials. + + Raises: + ValueError: For invalid parameters. + """ + aws_security_credentials_supplier = info.get( + "aws_security_credentials_supplier" + ) + kwargs.update( + {"aws_security_credentials_supplier": aws_security_credentials_supplier} + ) + return super(Credentials, cls).from_info(info, **kwargs) + + @classmethod + def from_file(cls, filename, **kwargs): + """Creates an AWS Credentials instance from an external account json file. + + Args: + filename (str): The path to the AWS external account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.aws.Credentials: The constructed credentials. + """ + return super(Credentials, cls).from_file(filename, **kwargs) diff --git a/intelliaide/vendor/google/auth/compute_engine/__init__.py b/intelliaide/vendor/google/auth/compute_engine/__init__.py new file mode 100644 index 0000000..7e1206f --- /dev/null +++ b/intelliaide/vendor/google/auth/compute_engine/__init__.py @@ -0,0 +1,22 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Compute Engine authentication.""" + +from google.auth.compute_engine._metadata import detect_gce_residency_linux +from google.auth.compute_engine.credentials import Credentials +from google.auth.compute_engine.credentials import IDTokenCredentials + + +__all__ = ["Credentials", "IDTokenCredentials", "detect_gce_residency_linux"] diff --git a/intelliaide/vendor/google/auth/compute_engine/_metadata.py b/intelliaide/vendor/google/auth/compute_engine/_metadata.py new file mode 100644 index 0000000..aae724a --- /dev/null +++ b/intelliaide/vendor/google/auth/compute_engine/_metadata.py @@ -0,0 +1,504 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Provides helper methods for talking to the Compute Engine metadata server. + +See https://cloud.google.com/compute/docs/metadata for more details. +""" + +import datetime +import http.client as http_client +import json +import logging +import os +from urllib.parse import urljoin + +import requests + +from google.auth import _helpers +from google.auth import environment_vars +from google.auth import exceptions +from google.auth import metrics +from google.auth import transport +from google.auth._exponential_backoff import ExponentialBackoff +from google.auth.compute_engine import _mtls + + +_LOGGER = logging.getLogger(__name__) + +_GCE_DEFAULT_MDS_IP = "169.254.169.254" +_GCE_DEFAULT_HOST = "metadata.google.internal" +_GCE_DEFAULT_MDS_HOSTS = [_GCE_DEFAULT_HOST, _GCE_DEFAULT_MDS_IP] + +# Environment variable GCE_METADATA_HOST is originally named +# GCE_METADATA_ROOT. For compatibility reasons, here it checks +# the new variable first; if not set, the system falls back +# to the old variable. +_GCE_METADATA_HOST = os.getenv(environment_vars.GCE_METADATA_HOST, None) +if not _GCE_METADATA_HOST: + _GCE_METADATA_HOST = os.getenv( + environment_vars.GCE_METADATA_ROOT, _GCE_DEFAULT_HOST + ) + + +def _validate_gce_mds_configured_environment(): + """Validates the GCE metadata server environment configuration for mTLS. + + mTLS is only supported when connecting to the default metadata server hosts. + If we are in strict mode (which requires mTLS), ensure that the metadata host + has not been overridden to a custom value (which means mTLS will fail). + + Raises: + google.auth.exceptions.MutualTLSChannelError: if the environment + configuration is invalid for mTLS. + """ + mode = _mtls._parse_mds_mode() + if mode == _mtls.MdsMtlsMode.STRICT: + # mTLS is only supported when connecting to the default metadata host. + # Raise an exception if we are in strict mode (which requires mTLS) + # but the metadata host has been overridden to a custom MDS. (which means mTLS will fail) + if _GCE_METADATA_HOST not in _GCE_DEFAULT_MDS_HOSTS: + raise exceptions.MutualTLSChannelError( + "Mutual TLS is required, but the metadata host has been overridden. " + "mTLS is only supported when connecting to the default metadata host." + ) + + +def _get_metadata_root(use_mtls: bool): + """Returns the metadata server root URL.""" + + scheme = "https" if use_mtls else "http" + return "{}://{}/computeMetadata/v1/".format(scheme, _GCE_METADATA_HOST) + + +def _get_metadata_ip_root(use_mtls: bool): + """Returns the metadata server IP root URL.""" + scheme = "https" if use_mtls else "http" + return "{}://{}".format( + scheme, os.getenv(environment_vars.GCE_METADATA_IP, _GCE_DEFAULT_MDS_IP) + ) + + +_METADATA_FLAVOR_HEADER = "metadata-flavor" +_METADATA_FLAVOR_VALUE = "Google" +_METADATA_HEADERS = {_METADATA_FLAVOR_HEADER: _METADATA_FLAVOR_VALUE} + +# Timeout in seconds to wait for the GCE metadata server when detecting the +# GCE environment. +try: + _METADATA_DEFAULT_TIMEOUT = int(os.getenv(environment_vars.GCE_METADATA_TIMEOUT, 3)) +except ValueError: # pragma: NO COVER + _METADATA_DEFAULT_TIMEOUT = 3 + +# The number of tries to perform when waiting for the GCE metadata server +# when detecting the GCE environment. +try: + _METADATA_DETECT_RETRIES = int( + os.getenv(environment_vars.GCE_METADATA_DETECT_RETRIES, 3) + ) +except ValueError: # pragma: NO COVER + _METADATA_DETECT_RETRIES = 3 + +# This is used to disable checking for the GCE metadata server and directly +# assuming it's not available. +_NO_GCE_CHECK = os.getenv(environment_vars.NO_GCE_CHECK) == "true" + +# Detect GCE Residency +_GOOGLE = "Google" +_GCE_PRODUCT_NAME_FILE = "/sys/class/dmi/id/product_name" + + +def is_on_gce(request): + """Checks to see if the code runs on Google Compute Engine + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + + Returns: + bool: True if the code runs on Google Compute Engine, False otherwise. + """ + if _NO_GCE_CHECK: + return False + + if ping(request): + return True + + if os.name == "nt": + # TODO: implement GCE residency detection on Windows + return False + + # Detect GCE residency on Linux + return detect_gce_residency_linux() + + +def detect_gce_residency_linux(): + """Detect Google Compute Engine residency by smbios check on Linux + + Returns: + bool: True if the GCE product name file is detected, False otherwise. + """ + try: + with open(_GCE_PRODUCT_NAME_FILE, "r") as file_obj: + content = file_obj.read().strip() + + except Exception: + return False + + return content.startswith(_GOOGLE) + + +def _prepare_request_for_mds(request, use_mtls=False) -> None: + """Prepares a request for the metadata server. + + This will check if mTLS should be used and mount the mTLS adapter if needed. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. If mTLS is enabled, and the request supports sessions, + the request will have the mTLS adapter mounted. Otherwise, there + will be no change. + use_mtls (bool): Whether to use mTLS for the request. + + + """ + # Only modify the request if mTLS is enabled, and request supports sessions. + if use_mtls and hasattr(request, "session"): + # Ensure the request has a session to mount the adapter to. + if not request.session: + request.session = requests.Session() + + adapter = _mtls.MdsMtlsAdapter() + # Mount the adapter for all default GCE metadata hosts. + for host in _GCE_DEFAULT_MDS_HOSTS: + request.session.mount(f"https://{host}/", adapter) + + +def ping( + request, timeout=_METADATA_DEFAULT_TIMEOUT, retry_count=_METADATA_DETECT_RETRIES +): + """Checks to see if the metadata server is available. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + timeout (int): How long to wait for the metadata server to respond. + retry_count (int): How many times to attempt connecting to metadata + server using above timeout. + + Returns: + bool: True if the metadata server is reachable, False otherwise. + """ + use_mtls = _mtls.should_use_mds_mtls() + _prepare_request_for_mds(request, use_mtls=use_mtls) + # NOTE: The explicit ``timeout`` is a workaround. The underlying + # issue is that resolving an unknown host on some networks will take + # 20-30 seconds; making this timeout short fixes the issue, but + # could lead to false negatives in the event that we are on GCE, but + # the metadata resolution was particularly slow. The latter case is + # "unlikely". + headers = _METADATA_HEADERS.copy() + headers[metrics.API_CLIENT_HEADER] = metrics.mds_ping() + + backoff = ExponentialBackoff(total_attempts=retry_count) + + for attempt in backoff: + try: + response = request( + url=_get_metadata_ip_root(use_mtls), + method="GET", + headers=headers, + timeout=timeout, + ) + + metadata_flavor = response.headers.get(_METADATA_FLAVOR_HEADER) + return ( + response.status == http_client.OK + and metadata_flavor == _METADATA_FLAVOR_VALUE + ) + + except exceptions.TransportError as e: + _LOGGER.warning( + "Compute Engine Metadata server unavailable on " + "attempt %s of %s. Reason: %s", + attempt, + retry_count, + e, + ) + + return False + + +def get( + request, + path, + root=None, + params=None, + recursive=False, + retry_count=5, + headers=None, + return_none_for_not_found_error=False, + timeout=_METADATA_DEFAULT_TIMEOUT, +): + """Fetch a resource from the metadata server. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + path (str): The resource to retrieve. For example, + ``'instance/service-accounts/default'``. + root (Optional[str]): The full path to the metadata server root. If not + provided, the default root will be used. + params (Optional[Mapping[str, str]]): A mapping of query parameter + keys to values. + recursive (bool): Whether to do a recursive query of metadata. See + https://cloud.google.com/compute/docs/metadata#aggcontents for more + details. + retry_count (int): How many times to attempt connecting to metadata + server using above timeout. + headers (Optional[Mapping[str, str]]): Headers for the request. + return_none_for_not_found_error (Optional[bool]): If True, returns None + for 404 error instead of throwing an exception. + timeout (int): How long to wait, in seconds for the metadata server to respond. + + Returns: + Union[Mapping, str]: If the metadata server returns JSON, a mapping of + the decoded JSON is returned. Otherwise, the response content is + returned as a string. + + Raises: + google.auth.exceptions.TransportError: if an error occurred while + retrieving metadata. + google.auth.exceptions.MutualTLSChannelError: if using mtls and the environment + configuration is invalid for mTLS (for example, the metadata host + has been overridden in strict mTLS mode). + + """ + use_mtls = _mtls.should_use_mds_mtls() + # Prepare the request object for mTLS if needed. + # This will create a new request object with the mTLS session. + _prepare_request_for_mds(request, use_mtls=use_mtls) + + if root is None: + root = _get_metadata_root(use_mtls) + + # mTLS is only supported when connecting to the default metadata host. + # If we are in strict mode (which requires mTLS), ensure that the metadata host + # has not been overridden to a non-default host value (which means mTLS will fail). + _validate_gce_mds_configured_environment() + + base_url = urljoin(root, path) + query_params = {} if params is None else params + + headers_to_use = _METADATA_HEADERS.copy() + if headers: + headers_to_use.update(headers) + + if recursive: + query_params["recursive"] = "true" + + url = _helpers.update_query(base_url, query_params) + + backoff = ExponentialBackoff(total_attempts=retry_count) + last_exception = None + for attempt in backoff: + try: + response = request( + url=url, method="GET", headers=headers_to_use, timeout=timeout + ) + if response.status in transport.DEFAULT_RETRYABLE_STATUS_CODES: + _LOGGER.warning( + "Compute Engine Metadata server unavailable on " + "attempt %s of %s. Response status: %s", + attempt, + retry_count, + response.status, + ) + last_exception = None + continue + else: + last_exception = None + break + + except exceptions.TransportError as e: + _LOGGER.warning( + "Compute Engine Metadata server unavailable on " + "attempt %s of %s. Reason: %s", + attempt, + retry_count, + e, + ) + last_exception = e + else: + if last_exception: + raise exceptions.TransportError( + "Failed to retrieve {} from the Google Compute Engine " + "metadata service. Compute Engine Metadata server unavailable. " + "Last exception: {}".format(url, last_exception) + ) from last_exception + else: + error_details = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + raise exceptions.TransportError( + "Failed to retrieve {} from the Google Compute Engine " + "metadata service. Compute Engine Metadata server unavailable. " + "Response status: {}\nResponse details:\n{}".format( + url, response.status, error_details + ) + ) + + content = _helpers.from_bytes(response.data) + + if response.status == http_client.NOT_FOUND and return_none_for_not_found_error: + return None + + if response.status == http_client.OK: + if ( + _helpers.parse_content_type(response.headers["content-type"]) + == "application/json" + ): + try: + return json.loads(content) + except ValueError as caught_exc: + new_exc = exceptions.TransportError( + "Received invalid JSON from the Google Compute Engine " + "metadata service: {:.20}".format(content) + ) + raise new_exc from caught_exc + else: + return content + + raise exceptions.TransportError( + "Failed to retrieve {} from the Google Compute Engine " + "metadata service. Status: {} Response:\n{}".format( + url, response.status, response.data + ), + response, + ) + + +def get_project_id(request): + """Get the Google Cloud Project ID from the metadata server. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + + Returns: + str: The project ID + + Raises: + google.auth.exceptions.TransportError: if an error occurred while + retrieving metadata. + """ + return get(request, "project/project-id") + + +def get_universe_domain(request): + """Get the universe domain value from the metadata server. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + + Returns: + str: The universe domain value. If the universe domain endpoint is not + not found, return the default value, which is googleapis.com + + Raises: + google.auth.exceptions.TransportError: if an error other than + 404 occurs while retrieving metadata. + """ + universe_domain = get( + request, "universe/universe-domain", return_none_for_not_found_error=True + ) + if not universe_domain: + return "googleapis.com" + return universe_domain + + +def get_service_account_info(request, service_account="default"): + """Get information about a service account from the metadata server. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + service_account (str): The string 'default' or a service account email + address. The determines which service account for which to acquire + information. + + Returns: + Mapping: The service account's information, for example:: + + { + 'email': '...', + 'scopes': ['scope', ...], + 'aliases': ['default', '...'] + } + + Raises: + google.auth.exceptions.TransportError: if an error occurred while + retrieving metadata. + """ + path = "instance/service-accounts/{0}/".format(service_account) + # See https://cloud.google.com/compute/docs/metadata#aggcontents + # for more on the use of 'recursive'. + return get(request, path, params={"recursive": "true"}) + + +def get_service_account_token(request, service_account="default", scopes=None): + """Get the OAuth 2.0 access token for a service account. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + service_account (str): The string 'default' or a service account email + address. The determines which service account for which to acquire + an access token. + scopes (Optional[Union[str, List[str]]]): Optional string or list of + strings with auth scopes. + Returns: + Tuple[str, datetime]: The access token and its expiration. + + Raises: + google.auth.exceptions.TransportError: if an error occurred while + retrieving metadata. + """ + from google.auth import _agent_identity_utils + + params = {} + if scopes: + if not isinstance(scopes, str): + scopes = ",".join(scopes) + params["scopes"] = scopes + + cert = _agent_identity_utils.get_and_parse_agent_identity_certificate() + if cert: + if _agent_identity_utils.should_request_bound_token(cert): + fingerprint = _agent_identity_utils.calculate_certificate_fingerprint(cert) + params["bindCertificateFingerprint"] = fingerprint + + metrics_header = { + metrics.API_CLIENT_HEADER: metrics.token_request_access_token_mds() + } + + path = "instance/service-accounts/{0}/token".format(service_account) + token_json = get(request, path, params=params, headers=metrics_header) + token_expiry = _helpers.utcnow() + datetime.timedelta( + seconds=token_json["expires_in"] + ) + return token_json["access_token"], token_expiry diff --git a/intelliaide/vendor/google/auth/compute_engine/_mtls.py b/intelliaide/vendor/google/auth/compute_engine/_mtls.py new file mode 100644 index 0000000..a427e66 --- /dev/null +++ b/intelliaide/vendor/google/auth/compute_engine/_mtls.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Mutual TLS for Google Compute Engine metadata server.""" + +from dataclasses import dataclass, field +import enum +import logging +import os +from pathlib import Path +import ssl +from urllib.parse import urlparse, urlunparse + +import requests +from requests.adapters import HTTPAdapter + +from google.auth import environment_vars, exceptions + + +_LOGGER = logging.getLogger(__name__) + +_WINDOWS_OS_NAME = "nt" + +# MDS mTLS certificate paths based on OS. +# Documentation to well known locations can be found at: +# https://cloud.google.com/compute/docs/metadata/overview#https-mds-certificates +_WINDOWS_MTLS_COMPONENTS_BASE_PATH = Path("C:/ProgramData/Google/ComputeEngine") +_MTLS_COMPONENTS_BASE_PATH = Path("/run/google-mds-mtls") + + +def _get_mds_root_crt_path(): + if os.name == _WINDOWS_OS_NAME: + return _WINDOWS_MTLS_COMPONENTS_BASE_PATH / "mds-mtls-root.crt" + else: + return _MTLS_COMPONENTS_BASE_PATH / "root.crt" + + +def _get_mds_client_combined_cert_path(): + if os.name == _WINDOWS_OS_NAME: + return _WINDOWS_MTLS_COMPONENTS_BASE_PATH / "mds-mtls-client.key" + else: + return _MTLS_COMPONENTS_BASE_PATH / "client.key" + + +@dataclass +class MdsMtlsConfig: + ca_cert_path: Path = field( + default_factory=_get_mds_root_crt_path + ) # path to CA certificate + client_combined_cert_path: Path = field( + default_factory=_get_mds_client_combined_cert_path + ) # path to file containing client certificate and key + + +def _certs_exist(mds_mtls_config: MdsMtlsConfig): + """Checks if the mTLS certificates exist.""" + return os.path.exists(mds_mtls_config.ca_cert_path) and os.path.exists( + mds_mtls_config.client_combined_cert_path + ) + + +class MdsMtlsMode(enum.Enum): + """MDS mTLS mode. Used to configure connection behavior when connecting to MDS. + + STRICT: Always use HTTPS/mTLS. If certificates are not found locally, an error will be returned. + NONE: Never use mTLS. Requests will use regular HTTP. + DEFAULT: Use mTLS if certificates are found locally, otherwise use regular HTTP. + """ + + STRICT = "strict" + NONE = "none" + DEFAULT = "default" + + +def _parse_mds_mode(): + """Parses the GCE_METADATA_MTLS_MODE environment variable.""" + mode_str = os.environ.get(environment_vars.GCE_METADATA_MTLS_MODE, "none").lower() + try: + return MdsMtlsMode(mode_str) + except ValueError: + raise ValueError( + "Invalid value for GCE_METADATA_MTLS_MODE. Must be one of 'strict', 'none', or 'default'." + ) + + +def should_use_mds_mtls(mds_mtls_config: MdsMtlsConfig = MdsMtlsConfig()): + """Determines if mTLS should be used for the metadata server.""" + mode = _parse_mds_mode() + if mode == MdsMtlsMode.STRICT: + if not _certs_exist(mds_mtls_config): + raise exceptions.MutualTLSChannelError( + "mTLS certificates not found in strict mode." + ) + return True + elif mode == MdsMtlsMode.NONE: + return False + else: # Default mode + return _certs_exist(mds_mtls_config) + + +class MdsMtlsAdapter(HTTPAdapter): + """An HTTP adapter that uses mTLS for the metadata server.""" + + def __init__( + self, mds_mtls_config: MdsMtlsConfig = MdsMtlsConfig(), *args, **kwargs + ): + self.ssl_context = ssl.create_default_context() + self.ssl_context.load_verify_locations(cafile=mds_mtls_config.ca_cert_path) + self.ssl_context.load_cert_chain( + certfile=mds_mtls_config.client_combined_cert_path + ) + super(MdsMtlsAdapter, self).__init__(*args, **kwargs) + + def init_poolmanager(self, *args, **kwargs): + kwargs["ssl_context"] = self.ssl_context + return super(MdsMtlsAdapter, self).init_poolmanager(*args, **kwargs) + + def proxy_manager_for(self, *args, **kwargs): + kwargs["ssl_context"] = self.ssl_context + return super(MdsMtlsAdapter, self).proxy_manager_for(*args, **kwargs) + + def send(self, request, **kwargs): + # If we are in strict mode, always use mTLS (no HTTP fallback) + if _parse_mds_mode() == MdsMtlsMode.STRICT: + return super(MdsMtlsAdapter, self).send(request, **kwargs) + + # In default mode, attempt mTLS first, then fallback to HTTP on failure + try: + response = super(MdsMtlsAdapter, self).send(request, **kwargs) + response.raise_for_status() + return response + except ( + ssl.SSLError, + requests.exceptions.SSLError, + requests.exceptions.HTTPError, + ) as e: + _LOGGER.warning( + "mTLS connection to Compute Engine Metadata server failed. " + "Falling back to standard HTTP. Reason: %s", + e, + ) + # Fallback to standard HTTP + parsed_original_url = urlparse(request.url) + http_fallback_url = urlunparse(parsed_original_url._replace(scheme="http")) + request.url = http_fallback_url + + # Use a standard HTTPAdapter for the fallback + http_adapter = HTTPAdapter() + return http_adapter.send(request, **kwargs) diff --git a/intelliaide/vendor/google/auth/compute_engine/credentials.py b/intelliaide/vendor/google/auth/compute_engine/credentials.py new file mode 100644 index 0000000..b91e06c --- /dev/null +++ b/intelliaide/vendor/google/auth/compute_engine/credentials.py @@ -0,0 +1,567 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Compute Engine credentials. + +This module provides authentication for an application running on Google +Compute Engine using the Compute Engine metadata server. + +""" + +import datetime +import logging +from typing import Optional, TYPE_CHECKING + + +from google.auth import _helpers +from google.auth import credentials +from google.auth import exceptions +from google.auth import iam +from google.auth import jwt +from google.auth import metrics +from google.auth.compute_engine import _metadata +from google.oauth2 import _client + +if TYPE_CHECKING: # pragma: NO COVER + import google.auth.transport + +_LOGGER = logging.getLogger(__name__) + + +class Credentials( + credentials.Scoped, + credentials.CredentialsWithQuotaProject, + credentials.CredentialsWithUniverseDomain, + credentials.CredentialsWithRegionalAccessBoundary, +): + """Compute Engine Credentials. + + These credentials use the Google Compute Engine metadata server to obtain + OAuth 2.0 access tokens associated with the instance's service account, + and are also used for Cloud Run, Flex and App Engine (except for the Python + 2.7 runtime, which is supported only on older versions of this library). + + For more information about Compute Engine authentication, including how + to configure scopes, see the `Compute Engine authentication + documentation`_. + + .. note:: On Compute Engine the metadata server ignores requested scopes. + On Cloud Run, Flex and App Engine the server honours requested scopes. + + .. _Compute Engine authentication documentation: + https://cloud.google.com/compute/docs/authentication#using + """ + + def __init__( + self, + service_account_email="default", + quota_project_id=None, + scopes=None, + default_scopes=None, + universe_domain=None, + trust_boundary=None, + ): + """ + Args: + service_account_email (str): The service account email to use, or + 'default'. A Compute Engine instance may have multiple service + accounts. + quota_project_id (Optional[str]): The project ID used for quota and + billing. + scopes (Optional[Sequence[str]]): The list of scopes for the credentials. + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + universe_domain (Optional[str]): The universe domain. If not + provided or None, credential will attempt to fetch the value + from metadata server. If metadata server doesn't have universe + domain endpoint, then the default googleapis.com will be used. + trust_boundary (Mapping[str,str]): A credential trust boundary. + """ + super(Credentials, self).__init__() + self._service_account_email = service_account_email + self._quota_project_id = quota_project_id + self._scopes = scopes + self._default_scopes = default_scopes + self._universe_domain_cached = False + if universe_domain: + self._universe_domain = universe_domain + self._universe_domain_cached = True + + self._trust_boundary = trust_boundary + + def _retrieve_info(self, request): + """Retrieve information about the service account. + + Updates the scopes and retrieves the full service account email. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + """ + info = _metadata.get_service_account_info( + request, service_account=self._service_account_email + ) + + if not info or "email" not in info: + raise exceptions.RefreshError( + "Unexpected response from metadata server: " + "service account info is missing 'email' field." + ) + + self._service_account_email = info["email"] + + # Don't override scopes requested by the user. + if self._scopes is None: + self._scopes = info.get("scopes") + + def _metric_header_for_usage(self): + return metrics.CRED_TYPE_SA_MDS + + def _perform_refresh_token(self, request): + """Refresh the access token and scopes. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If the Compute Engine metadata + service can't be reached if if the instance has not + credentials. + """ + try: + self._retrieve_info(request) + scopes = self._scopes if self._scopes is not None else self._default_scopes + # Always fetch token with default service account email. + self.token, self.expiry = _metadata.get_service_account_token( + request, service_account="default", scopes=scopes + ) + except exceptions.TransportError as caught_exc: + new_exc = exceptions.RefreshError(caught_exc) + raise new_exc from caught_exc + + def _build_regional_access_boundary_lookup_url( + self, request: "Optional[google.auth.transport.Request]" = None # noqa: F821 + ): + """Builds and returns the URL for the regional access boundary lookup API for GCE. + + Args: + request (Optional[google.auth.transport.Request]): The object used to make + HTTP requests. + + Returns: + Optional[str]: The URL for the regional access boundary lookup, + or None if it fails to fetch the service account email from + the metadata server (due to TransportError or missing email field). + """ + # If the service account email is 'default', we need to get the + # actual email address from the metadata server. + if self._service_account_email == "default": + if request is None: + try: + from google.auth.transport import requests as google_auth_requests + + request = google_auth_requests.Request() + except ImportError: + from google.auth.transport import _http_client + + request = _http_client.Request() + try: + info = _metadata.get_service_account_info(request, "default") + if not info or "email" not in info: + _LOGGER.error( + "Unexpected response from metadata server: " + "service account info is missing 'email' field. Cannot build Regional Access Boundary lookup URL." + ) + return None + self._service_account_email = info["email"] + + except exceptions.TransportError as e: + # If fetching the service account email fails due to a transport error, + # it means we cannot build the regional access boundary lookup URL. + _LOGGER.error( + "Failed to get service account email to build Regional Access Boundary lookup URL: %s", + e, + ) + return None + + return iam._SERVICE_ACCOUNT_REGIONAL_ACCESS_BOUNDARY_LOOKUP_ENDPOINT.format( + service_account_email=self.service_account_email + ) + + @property + def service_account_email(self): + """The service account email. + + .. note:: This is not guaranteed to be set until :meth:`refresh` has been + called. + """ + return self._service_account_email + + @property + def requires_scopes(self): + return not self._scopes + + @property + def universe_domain(self): + if self._universe_domain_cached: + return self._universe_domain + + try: + from google.auth.transport import requests as google_auth_requests + + request = google_auth_requests.Request() + except ImportError: + from google.auth.transport import _http_client + + request = _http_client.Request() + + self._universe_domain = _metadata.get_universe_domain(request) + self._universe_domain_cached = True + return self._universe_domain + + @_helpers.copy_docstring(credentials.Credentials) + def get_cred_info(self): + return { + "credential_source": "metadata server", + "credential_type": "VM credentials", + "principal": self.service_account_email, + } + + def _make_copy(self): + creds = self.__class__( + service_account_email=self._service_account_email, + quota_project_id=self._quota_project_id, + scopes=self._scopes, + default_scopes=self._default_scopes, + universe_domain=self._universe_domain, + trust_boundary=self._trust_boundary, + ) + creds._universe_domain_cached = self._universe_domain_cached + self._copy_regional_access_boundary_manager(creds) + return creds + + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + creds = self._make_copy() + creds._quota_project_id = quota_project_id + return creds + + @_helpers.copy_docstring(credentials.Scoped) + def with_scopes(self, scopes, default_scopes=None): + # Compute Engine credentials can not be scoped (the metadata service + # ignores the scopes parameter). App Engine, Cloud Run and Flex support + # requesting scopes. + creds = self._make_copy() + creds._scopes = scopes + creds._default_scopes = default_scopes + return creds + + @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain) + def with_universe_domain(self, universe_domain): + creds = self._make_copy() + creds._universe_domain = universe_domain + creds._universe_domain_cached = True + return creds + + +_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds +_DEFAULT_TOKEN_URI = "https://www.googleapis.com/oauth2/v4/token" + + +class IDTokenCredentials( + credentials.CredentialsWithQuotaProject, + credentials.Signing, + credentials.CredentialsWithTokenUri, +): + """Open ID Connect ID Token-based service account credentials. + + These credentials relies on the default service account of a GCE instance. + + ID token can be requested from `GCE metadata server identity endpoint`_, IAM + token endpoint or other token endpoints you specify. If metadata server + identity endpoint is not used, the GCE instance must have been started with + a service account that has access to the IAM Cloud API. + + .. _GCE metadata server identity endpoint: + https://cloud.google.com/compute/docs/instances/verifying-instance-identity + """ + + def __init__( + self, + request, + target_audience, + token_uri=None, + additional_claims=None, + service_account_email=None, + signer=None, + use_metadata_identity_endpoint=False, + quota_project_id=None, + ): + """ + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + target_audience (str): The intended audience for these credentials, + used when requesting the ID Token. The ID Token's ``aud`` claim + will be set to this string. + token_uri (str): The OAuth 2.0 Token URI. + additional_claims (Mapping[str, str]): Any additional claims for + the JWT assertion used in the authorization grant. + service_account_email (str): Optional explicit service account to + use to sign JWT tokens. + By default, this is the default GCE service account. + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + In case the signer is specified, the request argument will be + ignored. + use_metadata_identity_endpoint (bool): Whether to use GCE metadata + identity endpoint. For backward compatibility the default value + is False. If set to True, ``token_uri``, ``additional_claims``, + ``service_account_email``, ``signer`` argument should not be set; + otherwise ValueError will be raised. + quota_project_id (Optional[str]): The project ID used for quota and + billing. + + Raises: + ValueError: + If ``use_metadata_identity_endpoint`` is set to True, and one of + ``token_uri``, ``additional_claims``, ``service_account_email``, + ``signer`` arguments is set. + """ + super(IDTokenCredentials, self).__init__() + + self._quota_project_id = quota_project_id + self._use_metadata_identity_endpoint = use_metadata_identity_endpoint + self._target_audience = target_audience + + if use_metadata_identity_endpoint: + if token_uri or additional_claims or service_account_email or signer: + raise ValueError( + "If use_metadata_identity_endpoint is set, token_uri, " + "additional_claims, service_account_email, signer arguments" + " must not be set" + ) + self._token_uri = None + self._additional_claims = None + self._signer = None + + if service_account_email is None: + sa_info = _metadata.get_service_account_info(request) + self._service_account_email = sa_info["email"] + else: + self._service_account_email = service_account_email + + if not use_metadata_identity_endpoint: + if signer is None: + signer = iam.Signer( + request=request, + credentials=Credentials(), + service_account_email=self._service_account_email, + ) + self._signer = signer + self._token_uri = token_uri or _DEFAULT_TOKEN_URI + + if additional_claims is not None: + self._additional_claims = additional_claims + else: + self._additional_claims = {} + + def with_target_audience(self, target_audience): + """Create a copy of these credentials with the specified target + audience. + Args: + target_audience (str): The intended audience for these credentials, + used when requesting the ID Token. + Returns: + google.auth.service_account.IDTokenCredentials: A new credentials + instance. + """ + # since the signer is already instantiated, + # the request is not needed + if self._use_metadata_identity_endpoint: + return self.__class__( + None, + target_audience=target_audience, + use_metadata_identity_endpoint=True, + quota_project_id=self._quota_project_id, + ) + else: + return self.__class__( + None, + service_account_email=self._service_account_email, + token_uri=self._token_uri, + target_audience=target_audience, + additional_claims=self._additional_claims.copy(), + signer=self.signer, + use_metadata_identity_endpoint=False, + quota_project_id=self._quota_project_id, + ) + + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + # since the signer is already instantiated, + # the request is not needed + if self._use_metadata_identity_endpoint: + return self.__class__( + None, + target_audience=self._target_audience, + use_metadata_identity_endpoint=True, + quota_project_id=quota_project_id, + ) + else: + return self.__class__( + None, + service_account_email=self._service_account_email, + token_uri=self._token_uri, + target_audience=self._target_audience, + additional_claims=self._additional_claims.copy(), + signer=self.signer, + use_metadata_identity_endpoint=False, + quota_project_id=quota_project_id, + ) + + @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) + def with_token_uri(self, token_uri): + # since the signer is already instantiated, + # the request is not needed + if self._use_metadata_identity_endpoint: + raise ValueError( + "If use_metadata_identity_endpoint is set, token_uri" " must not be set" + ) + else: + return self.__class__( + None, + service_account_email=self._service_account_email, + token_uri=token_uri, + target_audience=self._target_audience, + additional_claims=self._additional_claims.copy(), + signer=self.signer, + use_metadata_identity_endpoint=False, + quota_project_id=self.quota_project_id, + ) + + def _make_authorization_grant_assertion(self): + """Create the OAuth 2.0 assertion. + This assertion is used during the OAuth 2.0 grant to acquire an + ID token. + Returns: + bytes: The authorization grant assertion. + """ + now = _helpers.utcnow() + lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS) + expiry = now + lifetime + + payload = { + "iat": _helpers.datetime_to_secs(now), + "exp": _helpers.datetime_to_secs(expiry), + # The issuer must be the service account email. + "iss": self.service_account_email, + # The audience must be the auth token endpoint's URI + "aud": self._token_uri, + # The target audience specifies which service the ID token is + # intended for. + "target_audience": self._target_audience, + } + + payload.update(self._additional_claims) + + token = jwt.encode(self._signer, payload) + + return token + + def _call_metadata_identity_endpoint(self, request): + """Request ID token from metadata identity endpoint. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Returns: + Tuple[str, datetime.datetime]: The ID token and the expiry of the ID token. + + Raises: + google.auth.exceptions.RefreshError: If the Compute Engine metadata + service can't be reached or if the instance has no credentials. + ValueError: If extracting expiry from the obtained ID token fails. + """ + try: + path = "instance/service-accounts/default/identity" + params = {"audience": self._target_audience, "format": "full"} + metrics_header = { + metrics.API_CLIENT_HEADER: metrics.token_request_id_token_mds() + } + id_token = _metadata.get( + request, path, params=params, headers=metrics_header + ) + except exceptions.TransportError as caught_exc: + new_exc = exceptions.RefreshError(caught_exc) + raise new_exc from caught_exc + + _, payload, _, _ = jwt._unverified_decode(id_token) + return id_token, _helpers.utcfromtimestamp(payload["exp"]) + + def refresh(self, request): + """Refreshes the ID token. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If the credentials could + not be refreshed. + ValueError: If extracting expiry from the obtained ID token fails. + """ + if self._use_metadata_identity_endpoint: + self.token, self.expiry = self._call_metadata_identity_endpoint(request) + else: + assertion = self._make_authorization_grant_assertion() + access_token, expiry, _ = _client.id_token_jwt_grant( + request, self._token_uri, assertion + ) + self.token = access_token + self.expiry = expiry + + @property # type: ignore + @_helpers.copy_docstring(credentials.Signing) + def signer(self): + return self._signer + + def sign_bytes(self, message): + """Signs the given message. + + Args: + message (bytes): The message to sign. + + Returns: + bytes: The message's cryptographic signature. + + Raises: + ValueError: + Signer is not available if metadata identity endpoint is used. + """ + if self._use_metadata_identity_endpoint: + raise exceptions.InvalidOperation( + "Signer is not available if metadata identity endpoint is used" + ) + return self._signer.sign(message) + + @property + def service_account_email(self): + """The service account email.""" + return self._service_account_email + + @property + def signer_email(self): + return self._service_account_email diff --git a/intelliaide/vendor/google/auth/credentials.py b/intelliaide/vendor/google/auth/credentials.py new file mode 100644 index 0000000..4a686cb --- /dev/null +++ b/intelliaide/vendor/google/auth/credentials.py @@ -0,0 +1,786 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Interfaces for credentials.""" + +import abc +from enum import Enum +import logging +import os +from typing import Dict, List, Optional, TYPE_CHECKING +from urllib.parse import urlparse +import warnings + + +from google.auth import _helpers, environment_vars +from google.auth import _regional_access_boundary_utils +from google.auth import exceptions +from google.auth import metrics +from google.auth._credentials_base import _BaseCredentials +from google.auth._refresh_worker import RefreshThreadManager + +if TYPE_CHECKING: # pragma: NO COVER + import google.auth.transport + +DEFAULT_UNIVERSE_DOMAIN = "googleapis.com" + +# These constants are deprecated and no longer used. +# They are kept solely for backward compatibility with older implementations. +NO_OP_TRUST_BOUNDARY_LOCATIONS: List[str] = [] +NO_OP_TRUST_BOUNDARY_ENCODED_LOCATIONS = "0x0" + +_LOGGER = logging.getLogger("google.auth._default") + + +class Credentials(_BaseCredentials): + """Base class for all credentials. + + All credentials have a :attr:`token` that is used for authentication and + may also optionally set an :attr:`expiry` to indicate when the token will + no longer be valid. + + Most credentials will be :attr:`invalid` until :meth:`refresh` is called. + Credentials can do this automatically before the first HTTP request in + :meth:`before_request`. + + Although the token and expiration will change as the credentials are + :meth:`refreshed ` and used, credentials should be considered + immutable. Various credentials will accept configuration such as private + keys, scopes, and other options. These options are not changeable after + construction. Some classes will provide mechanisms to copy the credentials + with modifications such as :meth:`ScopedCredentials.with_scopes`. + """ + + def __init__(self): + super(Credentials, self).__init__() + + self.expiry = None + """Optional[datetime]: When the token expires and is no longer valid. + If this is None, the token is assumed to never expire.""" + self._quota_project_id = None + """Optional[str]: Project to use for quota and billing purposes.""" + self._trust_boundary = None + """Optional[dict]: Cache of a trust boundary response which has a list + of allowed regions and an encoded string representation of credentials + trust boundary.""" + self._universe_domain = DEFAULT_UNIVERSE_DOMAIN + """Optional[str]: The universe domain value, default is googleapis.com + """ + + self._use_non_blocking_refresh = False + self._refresh_worker = RefreshThreadManager() + + @property + def expired(self): + """Checks if the credentials are expired. + + Note that credentials can be invalid but not expired because + Credentials with :attr:`expiry` set to None is considered to never + expire. + + .. deprecated:: v2.24.0 + Prefer checking :attr:`token_state` instead. + """ + if not self.expiry: + return False + # Remove some threshold from expiry to err on the side of reporting + # expiration early so that we avoid the 401-refresh-retry loop. + skewed_expiry = self.expiry - _helpers.REFRESH_THRESHOLD + return _helpers.utcnow() >= skewed_expiry + + @property + def valid(self): + """Checks the validity of the credentials. + + This is True if the credentials have a :attr:`token` and the token + is not :attr:`expired`. + + .. deprecated:: v2.24.0 + Prefer checking :attr:`token_state` instead. + """ + return self.token is not None and not self.expired + + @property + def token_state(self): + """ + See `:obj:`TokenState` + """ + if self.token is None: + return TokenState.INVALID + + # Credentials that can't expire are always treated as fresh. + if self.expiry is None: + return TokenState.FRESH + + expired = _helpers.utcnow() >= self.expiry + if expired: + return TokenState.INVALID + + is_stale = _helpers.utcnow() >= (self.expiry - _helpers.REFRESH_THRESHOLD) + if is_stale: + return TokenState.STALE + + return TokenState.FRESH + + @property + def quota_project_id(self): + """Project to use for quota and billing purposes.""" + return self._quota_project_id + + @property + def universe_domain(self): + """The universe domain value.""" + return self._universe_domain + + def get_cred_info(self): + """The credential information JSON. + + The credential information will be added to auth related error messages + by client library. + + Returns: + Mapping[str, str]: The credential information JSON. + """ + return None + + @abc.abstractmethod + def refresh(self, request): + """Refreshes the access token. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If the credentials could + not be refreshed. + """ + # pylint: disable=missing-raises-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError("Refresh must be implemented") + + def _metric_header_for_usage(self): + """The x-goog-api-client header for token usage metric. + + This header will be added to the API service requests in before_request + method. For example, "cred-type/sa-jwt" means service account self + signed jwt access token is used in the API service request + authorization header. Children credentials classes need to override + this method to provide the header value, if the token usage metric is + needed. + + Returns: + str: The x-goog-api-client header value. + """ + return None + + def apply(self, headers, token=None): + """Apply the token to the authentication header. + + Args: + headers (Mapping): The HTTP request headers. + token (Optional[str]): If specified, overrides the current access + token. + """ + self._apply(headers, token) + if self.quota_project_id: + headers["x-goog-user-project"] = self.quota_project_id + + def _blocking_refresh(self, request): + if not self.valid: + self.refresh(request) + + def _non_blocking_refresh(self, request): + use_blocking_refresh_fallback = False + + if self.token_state == TokenState.STALE: + use_blocking_refresh_fallback = not self._refresh_worker.start_refresh( + self, request + ) + + if self.token_state == TokenState.INVALID or use_blocking_refresh_fallback: + self.refresh(request) + # If the blocking refresh succeeds then we can clear the error info + # on the background refresh worker, and perform refreshes in a + # background thread. + self._refresh_worker.clear_error() + + def before_request(self, request, method, url, headers): + """Performs credential-specific before request logic. + + Refreshes the credentials if necessary, then calls :meth:`apply` to + apply the token to the authentication header. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + method (str): The request's HTTP method or the RPC method being + invoked. + url (str): The request's URI or the RPC service's URI. + headers (Mapping): The request's headers. + """ + # pylint: disable=unused-argument + # (Subclasses may use these arguments to ascertain information about + # the http request.) + if self._use_non_blocking_refresh: + self._non_blocking_refresh(request) + else: + self._blocking_refresh(request) + + metrics.add_metric_header(headers, self._metric_header_for_usage()) + self.apply(headers) + + def with_non_blocking_refresh(self): + self._use_non_blocking_refresh = True + + +class CredentialsWithQuotaProject(Credentials): + """Abstract base for credentials supporting ``with_quota_project`` factory""" + + def with_quota_project(self, quota_project_id): + """Returns a copy of these credentials with a modified quota project. + + Args: + quota_project_id (str): The project to use for quota and + billing purposes + + Returns: + google.auth.credentials.Credentials: A new credentials instance. + """ + raise NotImplementedError("This credential does not support quota project.") + + def with_quota_project_from_environment(self): + quota_from_env = os.environ.get(environment_vars.GOOGLE_CLOUD_QUOTA_PROJECT) + if quota_from_env: + return self.with_quota_project(quota_from_env) + return self + + +class CredentialsWithTokenUri(Credentials): + """Abstract base for credentials supporting ``with_token_uri`` factory""" + + def with_token_uri(self, token_uri): + """Returns a copy of these credentials with a modified token uri. + + Args: + token_uri (str): The uri to use for fetching/exchanging tokens + + Returns: + google.auth.credentials.Credentials: A new credentials instance. + """ + raise NotImplementedError("This credential does not use token uri.") + + +class CredentialsWithUniverseDomain(Credentials): + """Abstract base for credentials supporting ``with_universe_domain`` factory""" + + def with_universe_domain(self, universe_domain): + """Returns a copy of these credentials with a modified universe domain. + + Args: + universe_domain (str): The universe domain to use + + Returns: + google.auth.credentials.Credentials: A new credentials instance. + """ + raise NotImplementedError( + "This credential does not support with_universe_domain." + ) + + +class CredentialsWithRegionalAccessBoundary(Credentials): + """Abstract base for credentials supporting regional access boundary configuration.""" + + def __init__(self): + super().__init__() + self._rab_manager = ( + _regional_access_boundary_utils._RegionalAccessBoundaryManager() + ) + + @property + def regional_access_boundary(self): + """Optional[str]: The encoded Regional Access Boundary locations.""" + return self._rab_manager._data.encoded_locations + + @property + def regional_access_boundary_expiry(self): + """Optional[datetime.datetime]: The expiration time of the Regional Access Boundary.""" + return self._rab_manager._data.expiry + + @abc.abstractmethod + def _perform_refresh_token(self, request): + """Refreshes the access token. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If the credentials could + not be refreshed. + """ + raise NotImplementedError("_perform_refresh_token must be implemented") + + def with_trust_boundary(self, trust_boundary): + """Returns a copy of these credentials. + + .. deprecated:: + Manual Regional Access Boundary overrides are not supported. + This method is maintained for backwards compatibility and + returns a copy of the credentials without modifying the + Regional Access Boundary state. + + Args: + trust_boundary (Mapping[str, str]): Ignored. + + Returns: + google.auth.credentials.Credentials: A new credentials instance. + """ + import warnings + + warnings.warn( + "with_trust_boundary is deprecated and has no effect.", + DeprecationWarning, + stacklevel=2, + ) + make_copy = getattr(self, "_make_copy", None) + if make_copy: + return make_copy() + else: + raise NotImplementedError( + "This credential does not support trust boundaries." + ) + + def _copy_regional_access_boundary_manager(self, target): + """Copies the regional access boundary manager to another instance.""" + # Create a new manager for the clone to isolate background refresh locks and threads, + # but share the immutable data reference to avoid unnecessary initial lookups. + new_manager = _regional_access_boundary_utils._RegionalAccessBoundaryManager() + new_manager._data = self._rab_manager._data + target._rab_manager = new_manager + + def _set_regional_access_boundary(self, seed): + """Applies the regional_access_boundary provided via the seed on these + credentials. This is intended for internal use only as invalid + seeds would produce unexpected results until automatic recovery is supported. + Currently this is used by the gcloud CLI and therefore changes to the + contract MUST be backwards compatible (e.g. the method signature must be + unchanged and the credentials with the RAB set must be returned). + + + Returns: + google.auth.credentials.Credentials: The credentials instance. + """ + self._rab_manager.set_initial_regional_access_boundary( + encoded_locations=seed.get("encodedLocations", None), + expiry=seed.get("expiry", None), + ) + return self + + def _set_blocking_regional_access_boundary_lookup(self): + """Enables the blocking lookup mode on these credentials. + This is intended for internal use only as blocking lookup requires additional + care and consideration. Currently this is used by the gcloud CLI and + therefore changes to the contract MUST be backwards compatible (e.g. the + method signature must be unchanged and the credentials with the + blocking lookup flag set to true must be returned). + + Returns: + google.auth.credentials.Credentials: The credentials instance. + """ + self._rab_manager.enable_blocking_lookup() + return self + + def _maybe_start_regional_access_boundary_refresh(self, request, url): + """ + Starts a background thread to refresh the Regional Access Boundary if needed. + + This method checks if a refresh is necessary and if one is not already + in progress or in a cooldown period. If so, it starts a background + thread to perform the lookup. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + url (str): The URL of the request. + """ + try: + # Do not perform a lookup if the request is for a regional endpoint. + hostname = urlparse(url).hostname + if hostname and ( + hostname.endswith(".rep.googleapis.com") + or hostname.endswith(".rep.sandbox.googleapis.com") + ): + return + except (ValueError, TypeError): + # If the URL is malformed, proceed with the default lookup behavior. + pass + + # A refresh is only needed if the feature is enabled. + if not self._is_regional_access_boundary_lookup_required(): + return + + # Start the background refresh if needed. + self._rab_manager.maybe_start_refresh(self, request) + + def _is_regional_access_boundary_lookup_required(self): + """Checks if a Regional Access Boundary lookup is required. + + A lookup is required if the feature is enabled via an environment + variable and the universe domain is supported. + + Returns: + bool: True if a Regional Access Boundary lookup is required, False otherwise. + """ + # 1. Check if the feature is enabled. + if not _regional_access_boundary_utils.is_regional_access_boundary_enabled(): + return False + + # 2. Skip for non-default universe domains. + if self.universe_domain != DEFAULT_UNIVERSE_DOMAIN: + return False + + return True + + def apply(self, headers, token=None): + """Apply the token to the authentication header.""" + super().apply(headers, token) + self._rab_manager.apply_headers(headers) + + def before_request(self, request, method, url, headers): + """Refreshes the access token and triggers the Regional Access Boundary + lookup if necessary. + """ + if self._use_non_blocking_refresh: + self._non_blocking_refresh(request) + else: + self._blocking_refresh(request) + + self._maybe_start_regional_access_boundary_refresh(request, url) + + metrics.add_metric_header(headers, self._metric_header_for_usage()) + self.apply(headers) + + def refresh(self, request): + """Refreshes the access token. + + This method calls the subclass's token refresh logic. The Regional + Access Boundary is refreshed separately in a non-blocking way. + """ + self._perform_refresh_token(request) + + def _lookup_regional_access_boundary( + self, + request: "google.auth.transport.Request", # noqa: F821 + fail_fast: bool = False, + ) -> "Optional[Dict[str, str]]": + """Calls the Regional Access Boundary lookup API to retrieve the Regional Access Boundary information. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + fail_fast (bool): Whether the lookup should fail fast (short timeout, no retries). + + Returns: + Optional[Dict[str, str]]: The Regional Access Boundary information returned by the lookup API, or None if the lookup failed. + """ + from google.oauth2 import _client + + url = self._build_regional_access_boundary_lookup_url(request=request) + if not url: + _LOGGER.error("Failed to build Regional Access Boundary lookup URL.") + return None + + headers: Dict[str, str] = {} + self._apply(headers) + self._rab_manager.apply_headers(headers) + return _client._lookup_regional_access_boundary( + request, url, headers=headers, fail_fast=fail_fast + ) + + @abc.abstractmethod + def _build_regional_access_boundary_lookup_url( + self, request: "Optional[google.auth.transport.Request]" = None # noqa: F821 + ): + """ + Builds and returns the URL for the Regional Access Boundary lookup API. + + This method should be implemented by subclasses to provide the + specific URL based on the credential type and its properties. + + Args: + request (Optional[google.auth.transport.Request]): The object used + to make HTTP requests. In some subclasses, this may be used to + make an initial network call to resolve required metadata for the + URL. + + Returns: + str: The URL for the Regional Access Boundary lookup endpoint, or None + if lookup should be skipped (e.g., for non-applicable universe domains). + """ + raise NotImplementedError( + "_build_regional_access_boundary_lookup_url must be implemented" + ) + + +class AnonymousCredentials(Credentials): + """Credentials that do not provide any authentication information. + + These are useful in the case of services that support anonymous access or + local service emulators that do not use credentials. + """ + + @property + def expired(self): + """Returns `False`, anonymous credentials never expire.""" + return False + + @property + def valid(self): + """Returns `True`, anonymous credentials are always valid.""" + return True + + def refresh(self, request): + """Raises :class:``InvalidOperation``, anonymous credentials cannot be + refreshed.""" + raise exceptions.InvalidOperation("Anonymous credentials cannot be refreshed.") + + def apply(self, headers, token=None): + """Anonymous credentials do nothing to the request. + + The optional ``token`` argument is not supported. + + Raises: + google.auth.exceptions.InvalidValue: If a token was specified. + """ + if token is not None: + raise exceptions.InvalidValue("Anonymous credentials don't support tokens.") + + def before_request(self, request, method, url, headers): + """Anonymous credentials do nothing to the request.""" + + +class ReadOnlyScoped(metaclass=abc.ABCMeta): + """Interface for credentials whose scopes can be queried. + + OAuth 2.0-based credentials allow limiting access using scopes as described + in `RFC6749 Section 3.3`_. + If a credential class implements this interface then the credentials either + use scopes in their implementation. + + Some credentials require scopes in order to obtain a token. You can check + if scoping is necessary with :attr:`requires_scopes`:: + + if credentials.requires_scopes: + # Scoping is required. + credentials = credentials.with_scopes(scopes=['one', 'two']) + + Credentials that require scopes must either be constructed with scopes:: + + credentials = SomeScopedCredentials(scopes=['one', 'two']) + + Or must copy an existing instance using :meth:`with_scopes`:: + + scoped_credentials = credentials.with_scopes(scopes=['one', 'two']) + + Some credentials have scopes but do not allow or require scopes to be set, + these credentials can be used as-is. + + .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3 + """ + + def __init__(self): + super(ReadOnlyScoped, self).__init__() + self._scopes = None + self._default_scopes = None + + @property + def scopes(self): + """Sequence[str]: the credentials' current set of scopes.""" + return self._scopes + + @property + def default_scopes(self): + """Sequence[str]: the credentials' current set of default scopes.""" + return self._default_scopes + + @abc.abstractproperty + def requires_scopes(self): + """True if these credentials require scopes to obtain an access token.""" + return False + + def has_scopes(self, scopes): + """Checks if the credentials have the given scopes. + + .. warning: This method is not guaranteed to be accurate if the + credentials are :attr:`~Credentials.invalid`. + + Args: + scopes (Sequence[str]): The list of scopes to check. + + Returns: + bool: True if the credentials have the given scopes. + """ + credential_scopes = ( + self._scopes if self._scopes is not None else self._default_scopes + ) + return set(scopes).issubset(set(credential_scopes or [])) + + +class Scoped(ReadOnlyScoped): + """Interface for credentials whose scopes can be replaced while copying. + + OAuth 2.0-based credentials allow limiting access using scopes as described + in `RFC6749 Section 3.3`_. + If a credential class implements this interface then the credentials either + use scopes in their implementation. + + Some credentials require scopes in order to obtain a token. You can check + if scoping is necessary with :attr:`requires_scopes`:: + + if credentials.requires_scopes: + # Scoping is required. + credentials = credentials.create_scoped(['one', 'two']) + + Credentials that require scopes must either be constructed with scopes:: + + credentials = SomeScopedCredentials(scopes=['one', 'two']) + + Or must copy an existing instance using :meth:`with_scopes`:: + + scoped_credentials = credentials.with_scopes(scopes=['one', 'two']) + + Some credentials have scopes but do not allow or require scopes to be set, + these credentials can be used as-is. + + .. _RFC6749 Section 3.3: https://tools.ietf.org/html/rfc6749#section-3.3 + """ + + @abc.abstractmethod + def with_scopes(self, scopes, default_scopes=None): + """Create a copy of these credentials with the specified scopes. + + Args: + scopes (Sequence[str]): The list of scopes to attach to the + current credentials. + + Raises: + NotImplementedError: If the credentials' scopes can not be changed. + This can be avoided by checking :attr:`requires_scopes` before + calling this method. + """ + raise NotImplementedError("This class does not require scoping.") + + +def with_scopes_if_required(credentials, scopes, default_scopes=None): + """Creates a copy of the credentials with scopes if scoping is required. + + This helper function is useful when you do not know (or care to know) the + specific type of credentials you are using (such as when you use + :func:`google.auth.default`). This function will call + :meth:`Scoped.with_scopes` if the credentials are scoped credentials and if + the credentials require scoping. Otherwise, it will return the credentials + as-is. + + Args: + credentials (google.auth.credentials.Credentials): The credentials to + scope if necessary. + scopes (Sequence[str]): The list of scopes to use. + default_scopes (Sequence[str]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + + Returns: + google.auth.credentials.Credentials: Either a new set of scoped + credentials, or the passed in credentials instance if no scoping + was required. + """ + if isinstance(credentials, Scoped) and credentials.requires_scopes: + return credentials.with_scopes(scopes, default_scopes=default_scopes) + else: + return credentials + + +class Signing(metaclass=abc.ABCMeta): + """Interface for credentials that can cryptographically sign messages.""" + + @abc.abstractmethod + def sign_bytes(self, message): + """Signs the given message. + + Args: + message (bytes): The message to sign. + + Returns: + bytes: The message's cryptographic signature. + """ + # pylint: disable=missing-raises-doc,redundant-returns-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError("Sign bytes must be implemented.") + + @abc.abstractproperty + def signer_email(self): + """Optional[str]: An email address that identifies the signer.""" + # pylint: disable=missing-raises-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError("Signer email must be implemented.") + + @abc.abstractproperty + def signer(self): + """google.auth.crypt.Signer: The signer used to sign bytes.""" + # pylint: disable=missing-raises-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError("Signer must be implemented.") + + +class TokenState(Enum): + """ + Tracks the state of a token. + FRESH: The token is valid. It is not expired or close to expired, or the token has no expiry. + STALE: The token is close to expired, and should be refreshed. The token can be used normally. + INVALID: The token is expired or invalid. The token cannot be used for a normal operation. + """ + + FRESH = 1 + STALE = 2 + INVALID = 3 + + +class CredentialsWithTrustBoundary(CredentialsWithRegionalAccessBoundary): + """Abstract base for credentials supporting legacy trust boundary configuration. + + .. deprecated:: + Use :class:`~google.auth.credentials.CredentialsWithRegionalAccessBoundary` instead. + """ + + def __init__(self): + super().__init__() + warnings.warn( + "CredentialsWithTrustBoundary is deprecated. Use CredentialsWithRegionalAccessBoundary.", + DeprecationWarning, + stacklevel=2, + ) + + @abc.abstractmethod + def _build_trust_boundary_lookup_url(self): + """Deprecated: Implement _build_regional_access_boundary_lookup_url instead.""" + raise NotImplementedError() + + def _build_regional_access_boundary_lookup_url(self, request=None): + warnings.warn( + "CredentialsWithTrustBoundary is deprecated. Use CredentialsWithRegionalAccessBoundary.", + DeprecationWarning, + stacklevel=2, + ) + return self._build_trust_boundary_lookup_url() diff --git a/intelliaide/vendor/google/auth/crypt/__init__.py b/intelliaide/vendor/google/auth/crypt/__init__.py new file mode 100644 index 0000000..e56bc7b --- /dev/null +++ b/intelliaide/vendor/google/auth/crypt/__init__.py @@ -0,0 +1,96 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Cryptography helpers for verifying and signing messages. + +The simplest way to verify signatures is using :func:`verify_signature`:: + + cert = open('certs.pem').read() + valid = crypt.verify_signature(message, signature, cert) + +If you're going to verify many messages with the same certificate, you can use +:class:`RSAVerifier`:: + + cert = open('certs.pem').read() + verifier = crypt.RSAVerifier.from_string(cert) + valid = verifier.verify(message, signature) + +To sign messages use :class:`RSASigner` with a private key:: + + private_key = open('private_key.pem').read() + signer = crypt.RSASigner.from_string(private_key) + signature = signer.sign(message) + +The code above also works for :class:`ES256Signer` and :class:`ES256Verifier`. +Note that these two classes are only available if your `cryptography` dependency +version is at least 1.4.0. +""" + +from google.auth.crypt import base +from google.auth.crypt import es +from google.auth.crypt import es256 +from google.auth.crypt import rsa + +EsSigner = es.EsSigner +EsVerifier = es.EsVerifier +ES256Signer = es256.ES256Signer +ES256Verifier = es256.ES256Verifier + + +# Aliases to maintain the v1.0.0 interface, as the crypt module was split +# into submodules. +Signer = base.Signer +Verifier = base.Verifier +RSASigner = rsa.RSASigner +RSAVerifier = rsa.RSAVerifier + + +def verify_signature(message, signature, certs, verifier_cls=rsa.RSAVerifier): + """Verify an RSA or ECDSA cryptographic signature. + + Checks that the provided ``signature`` was generated from ``bytes`` using + the private key associated with the ``cert``. + + Args: + message (Union[str, bytes]): The plaintext message. + signature (Union[str, bytes]): The cryptographic signature to check. + certs (Union[Sequence, str, bytes]): The certificate or certificates + to use to check the signature. + verifier_cls (Optional[~google.auth.crypt.base.Signer]): Which verifier + class to use for verification. This can be used to select different + algorithms, such as RSA or ECDSA. Default value is :class:`RSAVerifier`. + + Returns: + bool: True if the signature is valid, otherwise False. + """ + if isinstance(certs, (str, bytes)): + certs = [certs] + + for cert in certs: + verifier = verifier_cls.from_string(cert) + if verifier.verify(message, signature): + return True + return False + + +__all__ = [ + "EsSigner", + "EsVerifier", + "ES256Signer", + "ES256Verifier", + "RSASigner", + "RSAVerifier", + "Signer", + "Verifier", +] diff --git a/intelliaide/vendor/google/auth/crypt/_cryptography_rsa.py b/intelliaide/vendor/google/auth/crypt/_cryptography_rsa.py new file mode 100644 index 0000000..1a3e9ff --- /dev/null +++ b/intelliaide/vendor/google/auth/crypt/_cryptography_rsa.py @@ -0,0 +1,151 @@ +# Copyright 2017 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""RSA verifier and signer that use the ``cryptography`` library. + +This is a much faster implementation than the default (in +``google.auth.crypt._python_rsa``), which depends on the pure-Python +``rsa`` library. +""" + +import cryptography.exceptions +from cryptography.hazmat import backends +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import padding +import cryptography.x509 + +from google.auth import _helpers +from google.auth.crypt import base + +_CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----" +_BACKEND = backends.default_backend() +_PADDING = padding.PKCS1v15() +_SHA256 = hashes.SHA256() + + +class RSAVerifier(base.Verifier): + """Verifies RSA cryptographic signatures using public keys. + + Args: + public_key ( + cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey): + The public key used to verify signatures. + """ + + def __init__(self, public_key): + self._pubkey = public_key + + @_helpers.copy_docstring(base.Verifier) + def verify(self, message, signature): + message = _helpers.to_bytes(message) + try: + self._pubkey.verify(signature, message, _PADDING, _SHA256) + return True + except (ValueError, cryptography.exceptions.InvalidSignature): + return False + + @classmethod + def from_string(cls, public_key): + """Construct an Verifier instance from a public key or public + certificate string. + + Args: + public_key (Union[str, bytes]): The public key in PEM format or the + x509 public key certificate. + + Returns: + Verifier: The constructed verifier. + + Raises: + ValueError: If the public key can't be parsed. + """ + public_key_data = _helpers.to_bytes(public_key) + + if _CERTIFICATE_MARKER in public_key_data: + cert = cryptography.x509.load_pem_x509_certificate( + public_key_data, _BACKEND + ) + pubkey = cert.public_key() + + else: + pubkey = serialization.load_pem_public_key(public_key_data, _BACKEND) + + return cls(pubkey) + + +class RSASigner(base.Signer, base.FromServiceAccountMixin): + """Signs messages with an RSA private key. + + Args: + private_key ( + cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey): + The private key to sign with. + key_id (str): Optional key ID used to identify this private key. This + can be useful to associate the private key with its associated + public key or certificate. + """ + + def __init__(self, private_key, key_id=None): + self._key = private_key + self._key_id = key_id + + @property # type: ignore + @_helpers.copy_docstring(base.Signer) + def key_id(self): + return self._key_id + + @_helpers.copy_docstring(base.Signer) + def sign(self, message): + message = _helpers.to_bytes(message) + return self._key.sign(message, _PADDING, _SHA256) + + @classmethod + def from_string(cls, key, key_id=None): + """Construct a RSASigner from a private key in PEM format. + + Args: + key (Union[bytes, str]): Private key in PEM format. + key_id (str): An optional key id used to identify the private key. + + Returns: + google.auth.crypt._cryptography_rsa.RSASigner: The + constructed signer. + + Raises: + ValueError: If ``key`` is not ``bytes`` or ``str`` (unicode). + UnicodeDecodeError: If ``key`` is ``bytes`` but cannot be decoded + into a UTF-8 ``str``. + ValueError: If ``cryptography`` "Could not deserialize key data." + """ + key = _helpers.to_bytes(key) + private_key = serialization.load_pem_private_key( + key, password=None, backend=_BACKEND + ) + return cls(private_key, key_id=key_id) + + def __getstate__(self): + """Pickle helper that serializes the _key attribute.""" + state = self.__dict__.copy() + state["_key"] = self._key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ) + return state + + def __setstate__(self, state): + """Pickle helper that deserializes the _key attribute.""" + state["_key"] = serialization.load_pem_private_key(state["_key"], None) + self.__dict__.update(state) diff --git a/intelliaide/vendor/google/auth/crypt/_helpers.py b/intelliaide/vendor/google/auth/crypt/_helpers.py new file mode 100644 index 0000000..e69de29 diff --git a/intelliaide/vendor/google/auth/crypt/_python_rsa.py b/intelliaide/vendor/google/auth/crypt/_python_rsa.py new file mode 100644 index 0000000..d9305e8 --- /dev/null +++ b/intelliaide/vendor/google/auth/crypt/_python_rsa.py @@ -0,0 +1,199 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Pure-Python RSA cryptography implementation. + +Uses the ``rsa``, ``pyasn1`` and ``pyasn1_modules`` packages +to parse PEM files storing PKCS#1 or PKCS#8 keys as well as +certificates. There is no support for p12 files. +""" + +from __future__ import absolute_import + +import io +import warnings + +from pyasn1.codec.der import decoder # type: ignore +from pyasn1_modules import pem # type: ignore +from pyasn1_modules.rfc2459 import Certificate # type: ignore +from pyasn1_modules.rfc5208 import PrivateKeyInfo # type: ignore +import rsa # type: ignore + +from google.auth import _helpers +from google.auth import exceptions +from google.auth.crypt import base + +_POW2 = (128, 64, 32, 16, 8, 4, 2, 1) +_CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----" +_PKCS1_MARKER = ("-----BEGIN RSA PRIVATE KEY-----", "-----END RSA PRIVATE KEY-----") +_PKCS8_MARKER = ("-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----") +_PKCS8_SPEC = PrivateKeyInfo() + +_warning_msg = ( + "The 'rsa' library is deprecated and will be removed in a future release. " + "Please migrate to 'cryptography'." +) + + +def _bit_list_to_bytes(bit_list): + """Converts an iterable of 1s and 0s to bytes. + + Combines the list 8 at a time, treating each group of 8 bits + as a single byte. + + Args: + bit_list (Sequence): Sequence of 1s and 0s. + + Returns: + bytes: The decoded bytes. + """ + num_bits = len(bit_list) + byte_vals = bytearray() + for start in range(0, num_bits, 8): + curr_bits = bit_list[start : start + 8] + char_val = sum(val * digit for val, digit in zip(_POW2, curr_bits)) + byte_vals.append(char_val) + return bytes(byte_vals) + + +class RSAVerifier(base.Verifier): + """Verifies RSA cryptographic signatures using public keys. + + .. deprecated:: + The `rsa` library has been archived. Please migrate to + `cryptography`. + + Args: + public_key (rsa.key.PublicKey): The public key used to verify + signatures. + """ + + def __init__(self, public_key): + warnings.warn( + _warning_msg, + category=DeprecationWarning, + stacklevel=2, + ) + self._pubkey = public_key + + @_helpers.copy_docstring(base.Verifier) + def verify(self, message, signature): + message = _helpers.to_bytes(message) + try: + return rsa.pkcs1.verify(message, signature, self._pubkey) + except (ValueError, rsa.pkcs1.VerificationError): + return False + + @classmethod + def from_string(cls, public_key): + """Construct an Verifier instance from a public key or public + certificate string. + + Args: + public_key (Union[str, bytes]): The public key in PEM format or the + x509 public key certificate. + + Returns: + google.auth.crypt._python_rsa.RSAVerifier: The constructed verifier. + + Raises: + ValueError: If the public_key can't be parsed. + """ + public_key = _helpers.to_bytes(public_key) + is_x509_cert = _CERTIFICATE_MARKER in public_key + + # If this is a certificate, extract the public key info. + if is_x509_cert: + der = rsa.pem.load_pem(public_key, "CERTIFICATE") + asn1_cert, remaining = decoder.decode(der, asn1Spec=Certificate()) + if remaining != b"": + raise exceptions.InvalidValue("Unused bytes", remaining) + + cert_info = asn1_cert["tbsCertificate"]["subjectPublicKeyInfo"] + key_bytes = _bit_list_to_bytes(cert_info["subjectPublicKey"]) + pubkey = rsa.PublicKey.load_pkcs1(key_bytes, "DER") + else: + pubkey = rsa.PublicKey.load_pkcs1(public_key, "PEM") + return cls(pubkey) + + +class RSASigner(base.Signer, base.FromServiceAccountMixin): + """Signs messages with an RSA private key. + + .. deprecated:: + The `rsa` library has been archived. Please migrate to + `cryptography`. + + Args: + private_key (rsa.key.PrivateKey): The private key to sign with. + key_id (str): Optional key ID used to identify this private key. This + can be useful to associate the private key with its associated + public key or certificate. + """ + + def __init__(self, private_key, key_id=None): + warnings.warn( + _warning_msg, + category=DeprecationWarning, + stacklevel=2, + ) + self._key = private_key + self._key_id = key_id + + @property # type: ignore + @_helpers.copy_docstring(base.Signer) + def key_id(self): + return self._key_id + + @_helpers.copy_docstring(base.Signer) + def sign(self, message): + message = _helpers.to_bytes(message) + return rsa.pkcs1.sign(message, self._key, "SHA-256") + + @classmethod + def from_string(cls, key, key_id=None): + """Construct an Signer instance from a private key in PEM format. + + Args: + key (str): Private key in PEM format. + key_id (str): An optional key id used to identify the private key. + + Returns: + google.auth.crypt.Signer: The constructed signer. + + Raises: + ValueError: If the key cannot be parsed as PKCS#1 or PKCS#8 in + PEM format. + """ + key = _helpers.from_bytes(key) # PEM expects str in Python 3 + marker_id, key_bytes = pem.readPemBlocksFromFile( + io.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER + ) + + # Key is in pkcs1 format. + if marker_id == 0: + private_key = rsa.key.PrivateKey.load_pkcs1(key_bytes, format="DER") + # Key is in pkcs8. + elif marker_id == 1: + key_info, remaining = decoder.decode(key_bytes, asn1Spec=_PKCS8_SPEC) + if remaining != b"": + raise exceptions.InvalidValue("Unused bytes", remaining) + private_key_info = key_info.getComponentByName("privateKey") + private_key = rsa.key.PrivateKey.load_pkcs1( + private_key_info.asOctets(), format="DER" + ) + else: + raise exceptions.MalformedError("No key could be detected.") + + return cls(private_key, key_id=key_id) diff --git a/intelliaide/vendor/google/auth/crypt/base.py b/intelliaide/vendor/google/auth/crypt/base.py new file mode 100644 index 0000000..ad871c3 --- /dev/null +++ b/intelliaide/vendor/google/auth/crypt/base.py @@ -0,0 +1,127 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Base classes for cryptographic signers and verifiers.""" + +import abc +import io +import json + +from google.auth import exceptions + +_JSON_FILE_PRIVATE_KEY = "private_key" +_JSON_FILE_PRIVATE_KEY_ID = "private_key_id" + + +class Verifier(metaclass=abc.ABCMeta): + """Abstract base class for crytographic signature verifiers.""" + + @abc.abstractmethod + def verify(self, message, signature): + """Verifies a message against a cryptographic signature. + + Args: + message (Union[str, bytes]): The message to verify. + signature (Union[str, bytes]): The cryptography signature to check. + + Returns: + bool: True if message was signed by the private key associated + with the public key that this object was constructed with. + """ + # pylint: disable=missing-raises-doc,redundant-returns-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError("Verify must be implemented") + + +class Signer(metaclass=abc.ABCMeta): + """Abstract base class for cryptographic signers.""" + + @abc.abstractproperty + def key_id(self): + """Optional[str]: The key ID used to identify this private key.""" + raise NotImplementedError("Key id must be implemented") + + @abc.abstractmethod + def sign(self, message): + """Signs a message. + + Args: + message (Union[str, bytes]): The message to be signed. + + Returns: + bytes: The signature of the message. + """ + # pylint: disable=missing-raises-doc,redundant-returns-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError("Sign must be implemented") + + +class FromServiceAccountMixin(metaclass=abc.ABCMeta): + """Mix-in to enable factory constructors for a Signer.""" + + @abc.abstractmethod + def from_string(cls, key, key_id=None): + """Construct an Signer instance from a private key string. + + Args: + key (str): Private key as a string. + key_id (str): An optional key id used to identify the private key. + + Returns: + google.auth.crypt.Signer: The constructed signer. + + Raises: + ValueError: If the key cannot be parsed. + """ + raise NotImplementedError("from_string must be implemented") + + @classmethod + def from_service_account_info(cls, info): + """Creates a Signer instance instance from a dictionary containing + service account info in Google format. + + Args: + info (Mapping[str, str]): The service account info in Google + format. + + Returns: + google.auth.crypt.Signer: The constructed signer. + + Raises: + ValueError: If the info is not in the expected format. + """ + if _JSON_FILE_PRIVATE_KEY not in info: + raise exceptions.MalformedError( + "The private_key field was not found in the service account " "info." + ) + + return cls.from_string( + info[_JSON_FILE_PRIVATE_KEY], info.get(_JSON_FILE_PRIVATE_KEY_ID) + ) + + @classmethod + def from_service_account_file(cls, filename): + """Creates a Signer instance from a service account .json file + in Google format. + + Args: + filename (str): The path to the service account .json file. + + Returns: + google.auth.crypt.Signer: The constructed signer. + """ + with io.open(filename, "r", encoding="utf-8") as json_file: + data = json.load(json_file) + + return cls.from_service_account_info(data) diff --git a/intelliaide/vendor/google/auth/crypt/es.py b/intelliaide/vendor/google/auth/crypt/es.py new file mode 100644 index 0000000..dbbe56b --- /dev/null +++ b/intelliaide/vendor/google/auth/crypt/es.py @@ -0,0 +1,221 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""ECDSA verifier and signer that use the ``cryptography`` library. +""" + +from dataclasses import dataclass +from typing import Any, Dict, Optional, Union + +import cryptography.exceptions +from cryptography.hazmat import backends +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature +from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature +import cryptography.x509 + +from google.auth import _helpers +from google.auth.crypt import base + + +_CERTIFICATE_MARKER = b"-----BEGIN CERTIFICATE-----" +_BACKEND = backends.default_backend() +_PADDING = padding.PKCS1v15() + + +@dataclass +class _ESAttributes: + """A class that models ECDSA attributes. + + Attributes: + rs_size (int): Size for ASN.1 r and s size. + sha_algo (hashes.HashAlgorithm): Hash algorithm. + algorithm (str): Algorithm name. + """ + + rs_size: int + sha_algo: hashes.HashAlgorithm + algorithm: str + + @classmethod + def from_key( + cls, key: Union[ec.EllipticCurvePublicKey, ec.EllipticCurvePrivateKey] + ): + return cls.from_curve(key.curve) + + @classmethod + def from_curve(cls, curve: ec.EllipticCurve): + # ECDSA raw signature has (r||s) format where r,s are two + # integers of size 32 bytes for P-256 curve and 48 bytes + # for P-384 curve. For P-256 curve, we use SHA256 hash algo, + # and for P-384 curve we use SHA384 algo. + if isinstance(curve, ec.SECP384R1): + return cls(48, hashes.SHA384(), "ES384") + else: + # default to ES256 + return cls(32, hashes.SHA256(), "ES256") + + +class EsVerifier(base.Verifier): + """Verifies ECDSA cryptographic signatures using public keys. + + Args: + public_key ( + cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey): + The public key used to verify signatures. + """ + + def __init__(self, public_key: ec.EllipticCurvePublicKey) -> None: + self._pubkey = public_key + self._attributes = _ESAttributes.from_key(public_key) + + @_helpers.copy_docstring(base.Verifier) + def verify(self, message: bytes, signature: bytes) -> bool: + # First convert (r||s) raw signature to ASN1 encoded signature. + sig_bytes = _helpers.to_bytes(signature) + if len(sig_bytes) != self._attributes.rs_size * 2: + return False + r = int.from_bytes(sig_bytes[: self._attributes.rs_size], byteorder="big") + s = int.from_bytes(sig_bytes[self._attributes.rs_size :], byteorder="big") + asn1_sig = encode_dss_signature(r, s) + + message = _helpers.to_bytes(message) + try: + self._pubkey.verify(asn1_sig, message, ec.ECDSA(self._attributes.sha_algo)) + return True + except (ValueError, cryptography.exceptions.InvalidSignature): + return False + + @classmethod + def from_string(cls, public_key: Union[str, bytes]) -> "EsVerifier": + """Construct a Verifier instance from a public key or public + certificate string. + + Args: + public_key (Union[str, bytes]): The public key in PEM format or the + x509 public key certificate. + + Returns: + google.auth.crypt.Verifier: The constructed verifier. + + Raises: + ValueError: If the public key can't be parsed. + """ + public_key_data = _helpers.to_bytes(public_key) + + if _CERTIFICATE_MARKER in public_key_data: + cert = cryptography.x509.load_pem_x509_certificate( + public_key_data, _BACKEND + ) + pubkey = cert.public_key() # type: Any + + else: + pubkey = serialization.load_pem_public_key(public_key_data, _BACKEND) + + if not isinstance(pubkey, ec.EllipticCurvePublicKey): + raise TypeError("Expected public key of type EllipticCurvePublicKey") + + return cls(pubkey) + + +class EsSigner(base.Signer, base.FromServiceAccountMixin): + """Signs messages with an ECDSA private key. + + Args: + private_key ( + cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey): + The private key to sign with. + key_id (str): Optional key ID used to identify this private key. This + can be useful to associate the private key with its associated + public key or certificate. + """ + + def __init__( + self, private_key: ec.EllipticCurvePrivateKey, key_id: Optional[str] = None + ) -> None: + self._key = private_key + self._key_id = key_id + self._attributes = _ESAttributes.from_key(private_key) + + @property + def algorithm(self) -> str: + """Name of the algorithm used to sign messages. + Returns: + str: The algorithm name. + """ + return self._attributes.algorithm + + @property # type: ignore + @_helpers.copy_docstring(base.Signer) + def key_id(self) -> Optional[str]: + return self._key_id + + @_helpers.copy_docstring(base.Signer) + def sign(self, message: bytes) -> bytes: + message = _helpers.to_bytes(message) + asn1_signature = self._key.sign(message, ec.ECDSA(self._attributes.sha_algo)) + + # Convert ASN1 encoded signature to (r||s) raw signature. + (r, s) = decode_dss_signature(asn1_signature) + return r.to_bytes(self._attributes.rs_size, byteorder="big") + s.to_bytes( + self._attributes.rs_size, byteorder="big" + ) + + @classmethod + def from_string( + cls, key: Union[bytes, str], key_id: Optional[str] = None + ) -> "EsSigner": + """Construct a RSASigner from a private key in PEM format. + + Args: + key (Union[bytes, str]): Private key in PEM format. + key_id (str): An optional key id used to identify the private key. + + Returns: + google.auth.crypt._cryptography_rsa.RSASigner: The + constructed signer. + + Raises: + ValueError: If ``key`` is not ``bytes`` or ``str`` (unicode). + UnicodeDecodeError: If ``key`` is ``bytes`` but cannot be decoded + into a UTF-8 ``str``. + ValueError: If ``cryptography`` "Could not deserialize key data." + """ + key_bytes = _helpers.to_bytes(key) + private_key = serialization.load_pem_private_key( + key_bytes, password=None, backend=_BACKEND + ) + + if not isinstance(private_key, ec.EllipticCurvePrivateKey): + raise TypeError("Expected private key of type EllipticCurvePrivateKey") + + return cls(private_key, key_id=key_id) + + def __getstate__(self) -> Dict[str, Any]: + """Pickle helper that serializes the _key attribute.""" + state = self.__dict__.copy() + state["_key"] = self._key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ) + return state + + def __setstate__(self, state: Dict[str, Any]) -> None: + """Pickle helper that deserializes the _key attribute.""" + state["_key"] = serialization.load_pem_private_key(state["_key"], None) + self.__dict__.update(state) diff --git a/intelliaide/vendor/google/auth/crypt/es256.py b/intelliaide/vendor/google/auth/crypt/es256.py new file mode 100644 index 0000000..e7bda5d --- /dev/null +++ b/intelliaide/vendor/google/auth/crypt/es256.py @@ -0,0 +1,45 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""ECDSA (ES256) verifier and signer that use the ``cryptography`` library. +""" + +from google.auth.crypt.es import EsSigner +from google.auth.crypt.es import EsVerifier + + +class ES256Verifier(EsVerifier): + """Verifies ECDSA cryptographic signatures using public keys. + + Args: + public_key (cryptography.hazmat.primitives.asymmetric.ec.ECDSAPublicKey): The public key used to verify + signatures. + """ + + pass + + +class ES256Signer(EsSigner): + """Signs messages with an ECDSA private key. + + Args: + private_key ( + cryptography.hazmat.primitives.asymmetric.ec.ECDSAPrivateKey): + The private key to sign with. + key_id (str): Optional key ID used to identify this private key. This + can be useful to associate the private key with its associated + public key or certificate. + """ + + pass diff --git a/intelliaide/vendor/google/auth/crypt/rsa.py b/intelliaide/vendor/google/auth/crypt/rsa.py new file mode 100644 index 0000000..639be90 --- /dev/null +++ b/intelliaide/vendor/google/auth/crypt/rsa.py @@ -0,0 +1,132 @@ +# Copyright 2017 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +RSA cryptography signer and verifier. + +This file provides a shared wrapper, that defers to _python_rsa or _cryptography_rsa +for implmentations using different third party libraries +""" + +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey + +from google.auth import _helpers +from google.auth.crypt import _cryptography_rsa +from google.auth.crypt import base + +RSA_KEY_MODULE_PREFIX = "rsa.key" + + +class RSAVerifier(base.Verifier): + """Verifies RSA cryptographic signatures using public keys. + + Args: + public_key (Union["rsa.key.PublicKey", cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey]): + The public key used to verify signatures. + Raises: + ImportError: if called with an rsa.key.PublicKey, when the rsa library is not installed + ValueError: if an unrecognized public key is provided + """ + + def __init__(self, public_key): + module_str = public_key.__class__.__module__ + if isinstance(public_key, RSAPublicKey): + impl_lib = _cryptography_rsa + elif module_str.startswith(RSA_KEY_MODULE_PREFIX): + from google.auth.crypt import _python_rsa + + impl_lib = _python_rsa + else: + raise ValueError(f"unrecognized public key type: {type(public_key)}") + self._impl = impl_lib.RSAVerifier(public_key) + + @_helpers.copy_docstring(base.Verifier) + def verify(self, message, signature): + return self._impl.verify(message, signature) + + @classmethod + def from_string(cls, public_key): + """Construct a Verifier instance from a public key or public + certificate string. + + Args: + public_key (Union[str, bytes]): The public key in PEM format or the + x509 public key certificate. + + Returns: + google.auth.crypt.Verifier: The constructed verifier. + + Raises: + ValueError: If the public_key can't be parsed. + """ + instance = cls.__new__(cls) + instance._impl = _cryptography_rsa.RSAVerifier.from_string(public_key) + return instance + + +class RSASigner(base.Signer, base.FromServiceAccountMixin): + """Signs messages with an RSA private key. + + Args: + private_key (Union["rsa.key.PrivateKey", cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey]): + The private key to sign with. + key_id (str): Optional key ID used to identify this private key. This + can be useful to associate the private key with its associated + public key or certificate. + + Raises: + ImportError: if called with an rsa.key.PrivateKey, when the rsa library is not installed + ValueError: if an unrecognized public key is provided + """ + + def __init__(self, private_key, key_id=None): + module_str = private_key.__class__.__module__ + if isinstance(private_key, RSAPrivateKey): + impl_lib = _cryptography_rsa + elif module_str.startswith(RSA_KEY_MODULE_PREFIX): + from google.auth.crypt import _python_rsa + + impl_lib = _python_rsa + else: + raise ValueError(f"unrecognized private key type: {type(private_key)}") + self._impl = impl_lib.RSASigner(private_key, key_id=key_id) + + @property # type: ignore + @_helpers.copy_docstring(base.Signer) + def key_id(self): + return self._impl.key_id + + @_helpers.copy_docstring(base.Signer) + def sign(self, message): + return self._impl.sign(message) + + @classmethod + def from_string(cls, key, key_id=None): + """Construct a Signer instance from a private key in PEM format. + + Args: + key (str): Private key in PEM format. + key_id (str): An optional key id used to identify the private key. + + Returns: + google.auth.crypt.Signer: The constructed signer. + + Raises: + ValueError: If the key cannot be parsed as PKCS#1 or PKCS#8 in + PEM format. + """ + instance = cls.__new__(cls) + instance._impl = _cryptography_rsa.RSASigner.from_string(key, key_id=key_id) + return instance diff --git a/intelliaide/vendor/google/auth/downscoped.py b/intelliaide/vendor/google/auth/downscoped.py new file mode 100644 index 0000000..ea75be9 --- /dev/null +++ b/intelliaide/vendor/google/auth/downscoped.py @@ -0,0 +1,512 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Downscoping with Credential Access Boundaries + +This module provides the ability to downscope credentials using +`Downscoping with Credential Access Boundaries`_. This is useful to restrict the +Identity and Access Management (IAM) permissions that a short-lived credential +can use. + +To downscope permissions of a source credential, a Credential Access Boundary +that specifies which resources the new credential can access, as well as +an upper bound on the permissions that are available on each resource, has to +be defined. A downscoped credential can then be instantiated using the source +credential and the Credential Access Boundary. + +The common pattern of usage is to have a token broker with elevated access +generate these downscoped credentials from higher access source credentials and +pass the downscoped short-lived access tokens to a token consumer via some +secure authenticated channel for limited access to Google Cloud Storage +resources. + +For example, a token broker can be set up on a server in a private network. +Various workloads (token consumers) in the same network will send authenticated +requests to that broker for downscoped tokens to access or modify specific google +cloud storage buckets. + +The broker will instantiate downscoped credentials instances that can be used to +generate short lived downscoped access tokens that can be passed to the token +consumer. These downscoped access tokens can be injected by the consumer into +google.oauth2.Credentials and used to initialize a storage client instance to +access Google Cloud Storage resources with restricted access. + +Note: Only Cloud Storage supports Credential Access Boundaries. Other Google +Cloud services do not support this feature. + +.. _Downscoping with Credential Access Boundaries: https://cloud.google.com/iam/docs/downscoping-short-lived-credentials +""" + +import datetime + +from google.auth import _helpers +from google.auth import credentials +from google.auth import exceptions +from google.oauth2 import sts + +# The maximum number of access boundary rules a Credential Access Boundary can +# contain. +_MAX_ACCESS_BOUNDARY_RULES_COUNT = 10 +# The token exchange grant_type used for exchanging credentials. +_STS_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange" +# The token exchange requested_token_type. This is always an access_token. +_STS_REQUESTED_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token" +# The STS token URL used to exchanged a short lived access token for a downscoped one. +_STS_TOKEN_URL_PATTERN = "https://sts.{}/v1/token" +# The subject token type to use when exchanging a short lived access token for a +# downscoped token. +_STS_SUBJECT_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token" + + +class CredentialAccessBoundary(object): + """Defines a Credential Access Boundary which contains a list of access boundary + rules. Each rule contains information on the resource that the rule applies to, + the upper bound of the permissions that are available on that resource and an + optional condition to further restrict permissions. + """ + + def __init__(self, rules=[]): + """Instantiates a Credential Access Boundary. A Credential Access Boundary + can contain up to 10 access boundary rules. + + Args: + rules (Sequence[google.auth.downscoped.AccessBoundaryRule]): The list of + access boundary rules limiting the access that a downscoped credential + will have. + Raises: + InvalidType: If any of the rules are not a valid type. + InvalidValue: If the provided rules exceed the maximum allowed. + """ + self.rules = rules + + @property + def rules(self): + """Returns the list of access boundary rules defined on the Credential + Access Boundary. + + Returns: + Tuple[google.auth.downscoped.AccessBoundaryRule, ...]: The list of access + boundary rules defined on the Credential Access Boundary. These are returned + as an immutable tuple to prevent modification. + """ + return tuple(self._rules) + + @rules.setter + def rules(self, value): + """Updates the current rules on the Credential Access Boundary. This will overwrite + the existing set of rules. + + Args: + value (Sequence[google.auth.downscoped.AccessBoundaryRule]): The list of + access boundary rules limiting the access that a downscoped credential + will have. + Raises: + InvalidType: If any of the rules are not a valid type. + InvalidValue: If the provided rules exceed the maximum allowed. + """ + if len(value) > _MAX_ACCESS_BOUNDARY_RULES_COUNT: + raise exceptions.InvalidValue( + "Credential access boundary rules can have a maximum of {} rules.".format( + _MAX_ACCESS_BOUNDARY_RULES_COUNT + ) + ) + for access_boundary_rule in value: + if not isinstance(access_boundary_rule, AccessBoundaryRule): + raise exceptions.InvalidType( + "List of rules provided do not contain a valid 'google.auth.downscoped.AccessBoundaryRule'." + ) + # Make a copy of the original list. + self._rules = list(value) + + def add_rule(self, rule): + """Adds a single access boundary rule to the existing rules. + + Args: + rule (google.auth.downscoped.AccessBoundaryRule): The access boundary rule, + limiting the access that a downscoped credential will have, to be added to + the existing rules. + Raises: + InvalidType: If any of the rules are not a valid type. + InvalidValue: If the provided rules exceed the maximum allowed. + """ + if len(self.rules) == _MAX_ACCESS_BOUNDARY_RULES_COUNT: + raise exceptions.InvalidValue( + "Credential access boundary rules can have a maximum of {} rules.".format( + _MAX_ACCESS_BOUNDARY_RULES_COUNT + ) + ) + if not isinstance(rule, AccessBoundaryRule): + raise exceptions.InvalidType( + "The provided rule does not contain a valid 'google.auth.downscoped.AccessBoundaryRule'." + ) + self._rules.append(rule) + + def to_json(self): + """Generates the dictionary representation of the Credential Access Boundary. + This uses the format expected by the Security Token Service API as documented in + `Defining a Credential Access Boundary`_. + + .. _Defining a Credential Access Boundary: + https://cloud.google.com/iam/docs/downscoping-short-lived-credentials#define-boundary + + Returns: + Mapping: Credential Access Boundary Rule represented in a dictionary object. + """ + rules = [] + for access_boundary_rule in self.rules: + rules.append(access_boundary_rule.to_json()) + + return {"accessBoundary": {"accessBoundaryRules": rules}} + + +class AccessBoundaryRule(object): + """Defines an access boundary rule which contains information on the resource that + the rule applies to, the upper bound of the permissions that are available on that + resource and an optional condition to further restrict permissions. + """ + + def __init__( + self, available_resource, available_permissions, availability_condition=None + ): + """Instantiates a single access boundary rule. + + Args: + available_resource (str): The full resource name of the Cloud Storage bucket + that the rule applies to. Use the format + "//storage.googleapis.com/projects/_/buckets/bucket-name". + available_permissions (Sequence[str]): A list defining the upper bound that + the downscoped token will have on the available permissions for the + resource. Each value is the identifier for an IAM predefined role or + custom role, with the prefix "inRole:". For example: + "inRole:roles/storage.objectViewer". + Only the permissions in these roles will be available. + availability_condition (Optional[google.auth.downscoped.AvailabilityCondition]): + Optional condition that restricts the availability of permissions to + specific Cloud Storage objects. + + Raises: + InvalidType: If any of the parameters are not of the expected types. + InvalidValue: If any of the parameters are not of the expected values. + """ + self.available_resource = available_resource + self.available_permissions = available_permissions + self.availability_condition = availability_condition + + @property + def available_resource(self): + """Returns the current available resource. + + Returns: + str: The current available resource. + """ + return self._available_resource + + @available_resource.setter + def available_resource(self, value): + """Updates the current available resource. + + Args: + value (str): The updated value of the available resource. + + Raises: + google.auth.exceptions.InvalidType: If the value is not a string. + """ + if not isinstance(value, str): + raise exceptions.InvalidType( + "The provided available_resource is not a string." + ) + self._available_resource = value + + @property + def available_permissions(self): + """Returns the current available permissions. + + Returns: + Tuple[str, ...]: The current available permissions. These are returned + as an immutable tuple to prevent modification. + """ + return tuple(self._available_permissions) + + @available_permissions.setter + def available_permissions(self, value): + """Updates the current available permissions. + + Args: + value (Sequence[str]): The updated value of the available permissions. + + Raises: + InvalidType: If the value is not a list of strings. + InvalidValue: If the value is not valid. + """ + for available_permission in value: + if not isinstance(available_permission, str): + raise exceptions.InvalidType( + "Provided available_permissions are not a list of strings." + ) + if available_permission.find("inRole:") != 0: + raise exceptions.InvalidValue( + "available_permissions must be prefixed with 'inRole:'." + ) + # Make a copy of the original list. + self._available_permissions = list(value) + + @property + def availability_condition(self): + """Returns the current availability condition. + + Returns: + Optional[google.auth.downscoped.AvailabilityCondition]: The current + availability condition. + """ + return self._availability_condition + + @availability_condition.setter + def availability_condition(self, value): + """Updates the current availability condition. + + Args: + value (Optional[google.auth.downscoped.AvailabilityCondition]): The updated + value of the availability condition. + + Raises: + google.auth.exceptions.InvalidType: If the value is not of type google.auth.downscoped.AvailabilityCondition + or None. + """ + if not isinstance(value, AvailabilityCondition) and value is not None: + raise exceptions.InvalidType( + "The provided availability_condition is not a 'google.auth.downscoped.AvailabilityCondition' or None." + ) + self._availability_condition = value + + def to_json(self): + """Generates the dictionary representation of the access boundary rule. + This uses the format expected by the Security Token Service API as documented in + `Defining a Credential Access Boundary`_. + + .. _Defining a Credential Access Boundary: + https://cloud.google.com/iam/docs/downscoping-short-lived-credentials#define-boundary + + Returns: + Mapping: The access boundary rule represented in a dictionary object. + """ + json = { + "availablePermissions": list(self.available_permissions), + "availableResource": self.available_resource, + } + if self.availability_condition: + json["availabilityCondition"] = self.availability_condition.to_json() + return json + + +class AvailabilityCondition(object): + """An optional condition that can be used as part of a Credential Access Boundary + to further restrict permissions.""" + + def __init__(self, expression, title=None, description=None): + """Instantiates an availability condition using the provided expression and + optional title or description. + + Args: + expression (str): A condition expression that specifies the Cloud Storage + objects where permissions are available. For example, this expression + makes permissions available for objects whose name starts with "customer-a": + "resource.name.startsWith('projects/_/buckets/example-bucket/objects/customer-a')" + title (Optional[str]): An optional short string that identifies the purpose of + the condition. + description (Optional[str]): Optional details about the purpose of the condition. + + Raises: + InvalidType: If any of the parameters are not of the expected types. + InvalidValue: If any of the parameters are not of the expected values. + """ + self.expression = expression + self.title = title + self.description = description + + @property + def expression(self): + """Returns the current condition expression. + + Returns: + str: The current conditon expression. + """ + return self._expression + + @expression.setter + def expression(self, value): + """Updates the current condition expression. + + Args: + value (str): The updated value of the condition expression. + + Raises: + google.auth.exceptions.InvalidType: If the value is not of type string. + """ + if not isinstance(value, str): + raise exceptions.InvalidType("The provided expression is not a string.") + self._expression = value + + @property + def title(self): + """Returns the current title. + + Returns: + Optional[str]: The current title. + """ + return self._title + + @title.setter + def title(self, value): + """Updates the current title. + + Args: + value (Optional[str]): The updated value of the title. + + Raises: + google.auth.exceptions.InvalidType: If the value is not of type string or None. + """ + if not isinstance(value, str) and value is not None: + raise exceptions.InvalidType("The provided title is not a string or None.") + self._title = value + + @property + def description(self): + """Returns the current description. + + Returns: + Optional[str]: The current description. + """ + return self._description + + @description.setter + def description(self, value): + """Updates the current description. + + Args: + value (Optional[str]): The updated value of the description. + + Raises: + google.auth.exceptions.InvalidType: If the value is not of type string or None. + """ + if not isinstance(value, str) and value is not None: + raise exceptions.InvalidType( + "The provided description is not a string or None." + ) + self._description = value + + def to_json(self): + """Generates the dictionary representation of the availability condition. + This uses the format expected by the Security Token Service API as documented in + `Defining a Credential Access Boundary`_. + + .. _Defining a Credential Access Boundary: + https://cloud.google.com/iam/docs/downscoping-short-lived-credentials#define-boundary + + Returns: + Mapping[str, str]: The availability condition represented in a dictionary + object. + """ + json = {"expression": self.expression} + if self.title: + json["title"] = self.title + if self.description: + json["description"] = self.description + return json + + +class Credentials(credentials.CredentialsWithQuotaProject): + """Defines a set of Google credentials that are downscoped from an existing set + of Google OAuth2 credentials. This is useful to restrict the Identity and Access + Management (IAM) permissions that a short-lived credential can use. + The common pattern of usage is to have a token broker with elevated access + generate these downscoped credentials from higher access source credentials and + pass the downscoped short-lived access tokens to a token consumer via some + secure authenticated channel for limited access to Google Cloud Storage + resources. + """ + + def __init__( + self, + source_credentials, + credential_access_boundary, + quota_project_id=None, + universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, + ): + """Instantiates a downscoped credentials object using the provided source + credentials and credential access boundary rules. + To downscope permissions of a source credential, a Credential Access Boundary + that specifies which resources the new credential can access, as well as an + upper bound on the permissions that are available on each resource, has to be + defined. A downscoped credential can then be instantiated using the source + credential and the Credential Access Boundary. + + Args: + source_credentials (google.auth.credentials.Credentials): The source credentials + to be downscoped based on the provided Credential Access Boundary rules. + credential_access_boundary (google.auth.downscoped.CredentialAccessBoundary): + The Credential Access Boundary which contains a list of access boundary + rules. Each rule contains information on the resource that the rule applies to, + the upper bound of the permissions that are available on that resource and an + optional condition to further restrict permissions. + quota_project_id (Optional[str]): The optional quota project ID. + universe_domain (Optional[str]): The universe domain value, default is googleapis.com + Raises: + google.auth.exceptions.RefreshError: If the source credentials + return an error on token refresh. + google.auth.exceptions.OAuthError: If the STS token exchange + endpoint returned an error during downscoped token generation. + """ + + super(Credentials, self).__init__() + self._source_credentials = source_credentials + self._credential_access_boundary = credential_access_boundary + self._quota_project_id = quota_project_id + self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN + self._sts_client = sts.Client( + _STS_TOKEN_URL_PATTERN.format(self.universe_domain) + ) + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + # Generate an access token from the source credentials. + self._source_credentials.refresh(request) + now = _helpers.utcnow() + # Exchange the access token for a downscoped access token. + response_data = self._sts_client.exchange_token( + request=request, + grant_type=_STS_GRANT_TYPE, + subject_token=self._source_credentials.token, + subject_token_type=_STS_SUBJECT_TOKEN_TYPE, + requested_token_type=_STS_REQUESTED_TOKEN_TYPE, + additional_options=self._credential_access_boundary.to_json(), + ) + self.token = response_data.get("access_token") + # For downscoping CAB flow, the STS endpoint may not return the expiration + # field for some flows. The generated downscoped token should always have + # the same expiration time as the source credentials. When no expires_in + # field is returned in the response, we can just get the expiration time + # from the source credentials. + if response_data.get("expires_in"): + lifetime = datetime.timedelta(seconds=response_data.get("expires_in")) + self.expiry = now + lifetime + else: + self.expiry = self._source_credentials.expiry + + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + return self.__class__( + self._source_credentials, + self._credential_access_boundary, + quota_project_id=quota_project_id, + ) diff --git a/intelliaide/vendor/google/auth/environment_vars.py b/intelliaide/vendor/google/auth/environment_vars.py new file mode 100644 index 0000000..c7d7064 --- /dev/null +++ b/intelliaide/vendor/google/auth/environment_vars.py @@ -0,0 +1,131 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Environment variables used by :mod:`google.auth`.""" + + +PROJECT = "GOOGLE_CLOUD_PROJECT" +"""Environment variable defining default project. + +This used by :func:`google.auth.default` to explicitly set a project ID. This +environment variable is also used by the Google Cloud Python Library. +""" + +LEGACY_PROJECT = "GCLOUD_PROJECT" +"""Previously used environment variable defining the default project. + +This environment variable is used instead of the current one in some +situations (such as Google App Engine). +""" + +GOOGLE_CLOUD_QUOTA_PROJECT = "GOOGLE_CLOUD_QUOTA_PROJECT" +"""Environment variable defining the project to be used for +quota and billing.""" + +CREDENTIALS = "GOOGLE_APPLICATION_CREDENTIALS" +"""Environment variable defining the location of Google application default +credentials.""" + +# The environment variable name which can replace ~/.config if set. +CLOUD_SDK_CONFIG_DIR = "CLOUDSDK_CONFIG" +"""Environment variable defines the location of Google Cloud SDK's config +files.""" + +# These two variables allow for customization of the addresses used when +# contacting the GCE metadata service. +GCE_METADATA_HOST = "GCE_METADATA_HOST" +"""Environment variable providing an alternate hostname or host:port to be +used for GCE metadata requests. + +This environment variable was originally named GCE_METADATA_ROOT. The system will +check this environemnt variable first; should there be no value present, +the system will fall back to the old variable. +""" + +GCE_METADATA_ROOT = "GCE_METADATA_ROOT" +"""Old environment variable for GCE_METADATA_HOST.""" + +GCE_METADATA_IP = "GCE_METADATA_IP" +"""Environment variable providing an alternate ip:port to be used for ip-only +GCE metadata requests.""" + +GCE_METADATA_TIMEOUT = "GCE_METADATA_TIMEOUT" +"""Environment variable defining the timeout in seconds to wait for the +GCE metadata server when detecting the GCE environment. +""" + +GCE_METADATA_DETECT_RETRIES = "GCE_METADATA_DETECT_RETRIES" +"""Environment variable representing the number of retries that should be +attempted on metadata lookup. +""" + +NO_GCE_CHECK = "NO_GCE_CHECK" +"""Environment variable controlling whether to check if running on GCE or not. + +The default value is false. Users have to explicitly set this value to true +in order to disable the GCE check.""" + +GCE_METADATA_MTLS_MODE = "GCE_METADATA_MTLS_MODE" +"""Environment variable controlling the mTLS behavior for GCE metadata requests. + +Can be one of "strict", "none", or "default". +""" + +GOOGLE_API_USE_CLIENT_CERTIFICATE = "GOOGLE_API_USE_CLIENT_CERTIFICATE" +"""Environment variable controlling whether to use client certificate or not. + +The default value is false. Users have to explicitly set this value to true +in order to use client certificate to establish a mutual TLS channel.""" + +LEGACY_APPENGINE_RUNTIME = "APPENGINE_RUNTIME" +"""Gen1 environment variable defining the App Engine Runtime. + +Used to distinguish between GAE gen1 and GAE gen2+. +""" + +# AWS environment variables used with AWS workload identity pools to retrieve +# AWS security credentials and the AWS region needed to create a serialized +# signed requests to the AWS STS GetCalledIdentity API that can be exchanged +# for a Google access tokens via the GCP STS endpoint. +# When not available the AWS metadata server is used to retrieve these values. +AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID" +AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY" +AWS_SESSION_TOKEN = "AWS_SESSION_TOKEN" +AWS_REGION = "AWS_REGION" +AWS_DEFAULT_REGION = "AWS_DEFAULT_REGION" + +GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED = "GOOGLE_AUTH_TRUST_BOUNDARY_ENABLED" +"""Environment variable controlling whether to enable trust boundary feature. +The default value is false. Users have to explicitly set this value to true.""" + +GOOGLE_API_CERTIFICATE_CONFIG = "GOOGLE_API_CERTIFICATE_CONFIG" +"""Environment variable defining the location of Google API certificate config +file.""" + +CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE = ( + "CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE" +) +"""Environment variable controlling whether to use client certificate or not. +This variable is the fallback of GOOGLE_API_USE_CLIENT_CERTIFICATE.""" + +CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH = ( + "CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH" +) +"""Environment variable defining the location of Google API certificate config +file. This variable is the fallback of GOOGLE_API_CERTIFICATE_CONFIG.""" + +GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES = ( + "GOOGLE_API_PREVENT_AGENT_TOKEN_SHARING_FOR_GCP_SERVICES" +) +"""Environment variable to prevent agent token sharing for GCP services.""" diff --git a/intelliaide/vendor/google/auth/exceptions.py b/intelliaide/vendor/google/auth/exceptions.py new file mode 100644 index 0000000..feb9f74 --- /dev/null +++ b/intelliaide/vendor/google/auth/exceptions.py @@ -0,0 +1,108 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Exceptions used in the google.auth package.""" + + +class GoogleAuthError(Exception): + """Base class for all google.auth errors.""" + + def __init__(self, *args, **kwargs): + super(GoogleAuthError, self).__init__(*args) + retryable = kwargs.get("retryable", False) + self._retryable = retryable + + @property + def retryable(self): + return self._retryable + + +class TransportError(GoogleAuthError): + """Used to indicate an error occurred during an HTTP request.""" + + +class RefreshError(GoogleAuthError): + """Used to indicate that an refreshing the credentials' access token + failed.""" + + +class UserAccessTokenError(GoogleAuthError): + """Used to indicate ``gcloud auth print-access-token`` command failed.""" + + +class DefaultCredentialsError(GoogleAuthError): + """Used to indicate that acquiring default credentials failed.""" + + +class MutualTLSChannelError(GoogleAuthError): + """Used to indicate that mutual TLS channel creation is failed, or mutual + TLS channel credentials is missing or invalid.""" + + +class ClientCertError(GoogleAuthError): + """Used to indicate that client certificate is missing or invalid.""" + + @property + def retryable(self): + return False + + +class OAuthError(GoogleAuthError): + """Used to indicate an error occurred during an OAuth related HTTP + request.""" + + +class ReauthFailError(RefreshError): + """An exception for when reauth failed.""" + + def __init__(self, message=None, **kwargs): + super(ReauthFailError, self).__init__( + "Reauthentication failed. {0}".format(message), **kwargs + ) + + +class ReauthSamlChallengeFailError(ReauthFailError): + """An exception for SAML reauth challenge failures.""" + + +class MalformedError(DefaultCredentialsError, ValueError): + """An exception for malformed data.""" + + +class InvalidResource(DefaultCredentialsError, ValueError): + """An exception for URL error.""" + + +class InvalidOperation(DefaultCredentialsError, ValueError): + """An exception for invalid operation.""" + + +class InvalidValue(DefaultCredentialsError, ValueError): + """Used to wrap general ValueError of python.""" + + +class InvalidType(DefaultCredentialsError, TypeError): + """Used to wrap general TypeError of python.""" + + +class OSError(DefaultCredentialsError, EnvironmentError): + """Used to wrap EnvironmentError(OSError after python3.3).""" + + +class TimeoutError(GoogleAuthError): + """Used to indicate a timeout error occurred during an HTTP request.""" + + +class ResponseError(GoogleAuthError): + """Used to indicate an error occurred when reading an HTTP response.""" diff --git a/intelliaide/vendor/google/auth/external_account.py b/intelliaide/vendor/google/auth/external_account.py new file mode 100644 index 0000000..b490f36 --- /dev/null +++ b/intelliaide/vendor/google/auth/external_account.py @@ -0,0 +1,745 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""External Account Credentials. + +This module provides credentials that exchange workload identity pool external +credentials for Google access tokens. This facilitates accessing Google Cloud +Platform resources from on-prem and non-Google Cloud platforms (e.g. AWS, +Microsoft Azure, OIDC identity providers), using native credentials retrieved +from the current environment without the need to copy, save and manage +long-lived service account credentials. + +Specifically, this is intended to use access tokens acquired using the GCP STS +token exchange endpoint following the `OAuth 2.0 Token Exchange`_ spec. + +.. _OAuth 2.0 Token Exchange: https://tools.ietf.org/html/rfc8693 +""" + +import abc +import copy +from dataclasses import dataclass +import datetime +import functools +import io +import json +import logging +import re +from typing import Optional, TYPE_CHECKING + + +from google.auth import _helpers +from google.auth import credentials +from google.auth import exceptions +from google.auth import iam +from google.auth import impersonated_credentials +from google.auth import metrics +from google.oauth2 import sts +from google.oauth2 import utils + +if TYPE_CHECKING: # pragma: NO COVER + import google.auth.transport + +_LOGGER = logging.getLogger(__name__) + +# External account JSON type identifier. +_EXTERNAL_ACCOUNT_JSON_TYPE = "external_account" +# The token exchange grant_type used for exchanging credentials. +_STS_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange" +# The token exchange requested_token_type. This is always an access_token. +_STS_REQUESTED_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token" +# Cloud resource manager URL used to retrieve project information. +_CLOUD_RESOURCE_MANAGER = "https://cloudresourcemanager.{universe_domain}/v1/projects/" +# Default Google sts token url. +_DEFAULT_TOKEN_URL = "https://sts.{universe_domain}/v1/token" + + +@dataclass +class SupplierContext: + """A context class that contains information about the requested third party credential that is passed + to AWS security credential and subject token suppliers. + + Attributes: + subject_token_type (str): The requested subject token type based on the Oauth2.0 token exchange spec. + Expected values include:: + + “urn:ietf:params:oauth:token-type:jwt” + “urn:ietf:params:oauth:token-type:id-token” + “urn:ietf:params:oauth:token-type:saml2” + “urn:ietf:params:aws:token-type:aws4_request” + + audience (str): The requested audience for the subject token. + """ + + subject_token_type: str + audience: str + + +class Credentials( + credentials.Scoped, + credentials.CredentialsWithQuotaProject, + credentials.CredentialsWithTokenUri, + credentials.CredentialsWithRegionalAccessBoundary, + metaclass=abc.ABCMeta, +): + """Base class for all external account credentials. + + This is used to instantiate Credentials for exchanging external account + credentials for Google access token and authorizing requests to Google APIs. + The base class implements the common logic for exchanging external account + credentials for Google access tokens. + + **IMPORTANT**: + This class does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + """ + + def __init__( + self, + audience, + subject_token_type, + token_url, + credential_source, + service_account_impersonation_url=None, + service_account_impersonation_options=None, + client_id=None, + client_secret=None, + token_info_url=None, + quota_project_id=None, + scopes=None, + default_scopes=None, + workforce_pool_user_project=None, + universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, + ): + """Instantiates an external account credentials object. + + Args: + audience (str): The STS audience field. + subject_token_type (str): The subject token type based on the Oauth2.0 token exchange spec. + Expected values include:: + + “urn:ietf:params:oauth:token-type:jwt” + “urn:ietf:params:oauth:token-type:id-token” + “urn:ietf:params:oauth:token-type:saml2” + “urn:ietf:params:aws:token-type:aws4_request” + + token_url (str): The STS endpoint URL. + credential_source (Mapping): The credential source dictionary. + service_account_impersonation_url (Optional[str]): The optional service account + impersonation generateAccessToken URL. + client_id (Optional[str]): The optional client ID. + client_secret (Optional[str]): The optional client secret. + token_info_url (str): The optional STS endpoint URL for token introspection. + quota_project_id (Optional[str]): The optional quota project ID. + scopes (Optional[Sequence[str]]): Optional scopes to request during the + authorization grant. + default_scopes (Optional[Sequence[str]]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + workforce_pool_user_project (Optona[str]): The optional workforce pool user + project number when the credential corresponds to a workforce pool and not + a workload identity pool. The underlying principal must still have + serviceusage.services.use IAM permission to use the project for + billing/quota. + universe_domain (str): The universe domain. The default universe + domain is googleapis.com. + trust_boundary (str): String representation of trust boundary meta. + Raises: + google.auth.exceptions.RefreshError: If the generateAccessToken + endpoint returned an error. + """ + super(Credentials, self).__init__() + self._audience = audience + self._subject_token_type = subject_token_type + self._universe_domain = universe_domain + self._token_url = token_url + if self._token_url == _DEFAULT_TOKEN_URL: + self._token_url = self._token_url.replace( + "{universe_domain}", self._universe_domain + ) + self._cloud_resource_manager_url = _CLOUD_RESOURCE_MANAGER.replace( + "{universe_domain}", self._universe_domain + ) + self._token_info_url = token_info_url + self._credential_source = credential_source + self._service_account_impersonation_url = service_account_impersonation_url + self._service_account_impersonation_options = ( + service_account_impersonation_options or {} + ) + self._client_id = client_id + self._client_secret = client_secret + self._quota_project_id = quota_project_id + self._scopes = scopes + self._default_scopes = default_scopes + self._workforce_pool_user_project = workforce_pool_user_project + self._trust_boundary = trust_boundary + + if self._client_id: + self._client_auth = utils.ClientAuthentication( + utils.ClientAuthType.basic, self._client_id, self._client_secret + ) + else: + self._client_auth = None + self._sts_client = sts.Client(self._token_url, self._client_auth) + + self._metrics_options = self._create_default_metrics_options() + + self._impersonated_credentials = None + self._project_id = None + self._supplier_context = SupplierContext( + self._subject_token_type, self._audience + ) + self._cred_file_path = None + + if not self.is_workforce_pool and self._workforce_pool_user_project: + # Workload identity pools do not support workforce pool user projects. + raise exceptions.InvalidValue( + "workforce_pool_user_project should not be set for non-workforce pool " + "credentials" + ) + + @property + def info(self): + """Generates the dictionary representation of the current credentials. + + Returns: + Mapping: The dictionary representation of the credentials. This is the + reverse of "from_info" defined on the subclasses of this class. It is + useful for serializing the current credentials so it can deserialized + later. + """ + config_info = self._constructor_args() + config_info.update( + type=_EXTERNAL_ACCOUNT_JSON_TYPE, + service_account_impersonation=config_info.pop( + "service_account_impersonation_options", None + ), + ) + config_info.pop("scopes", None) + config_info.pop("default_scopes", None) + return {key: value for key, value in config_info.items() if value is not None} + + def _constructor_args(self): + args = { + "audience": self._audience, + "subject_token_type": self._subject_token_type, + "token_url": self._token_url, + "token_info_url": self._token_info_url, + "service_account_impersonation_url": self._service_account_impersonation_url, + "service_account_impersonation_options": copy.deepcopy( + self._service_account_impersonation_options + ) + or None, + "credential_source": copy.deepcopy(self._credential_source), + "quota_project_id": self._quota_project_id, + "client_id": self._client_id, + "client_secret": self._client_secret, + "workforce_pool_user_project": self._workforce_pool_user_project, + "scopes": self._scopes, + "default_scopes": self._default_scopes, + "universe_domain": self._universe_domain, + "trust_boundary": self._trust_boundary, + } + if not self.is_workforce_pool: + args.pop("workforce_pool_user_project") + return args + + @property + def service_account_email(self): + """Returns the service account email if service account impersonation is used. + + Returns: + Optional[str]: The service account email if impersonation is used. Otherwise + None is returned. + """ + if self._service_account_impersonation_url: + # Parse email from URL. The formal looks as follows: + # https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/name@project-id.iam.gserviceaccount.com:generateAccessToken + url = self._service_account_impersonation_url + start_index = url.rfind("/") + end_index = url.find(":generateAccessToken") + if start_index != -1 and end_index != -1 and start_index < end_index: + start_index = start_index + 1 + return url[start_index:end_index] + return None + + @property + def is_user(self): + """Returns whether the credentials represent a user (True) or workload (False). + Workloads behave similarly to service accounts. Currently workloads will use + service account impersonation but will eventually not require impersonation. + As a result, this property is more reliable than the service account email + property in determining if the credentials represent a user or workload. + + Returns: + bool: True if the credentials represent a user. False if they represent a + workload. + """ + # If service account impersonation is used, the credentials will always represent a + # service account. + if self._service_account_impersonation_url: + return False + return self.is_workforce_pool + + @property + def is_workforce_pool(self): + """Returns whether the credentials represent a workforce pool (True) or + workload (False) based on the credentials' audience. + + This will also return True for impersonated workforce pool credentials. + + Returns: + bool: True if the credentials represent a workforce pool. False if they + represent a workload. + """ + # Workforce pools representing users have the following audience format: + # //iam.googleapis.com/locations/$location/workforcePools/$poolId/providers/$providerId + p = re.compile(r"//iam\.googleapis\.com/locations/[^/]+/workforcePools/") + return p.match(self._audience or "") is not None + + @property + def requires_scopes(self): + """Checks if the credentials requires scopes. + + Returns: + bool: True if there are no scopes set otherwise False. + """ + return not self._scopes and not self._default_scopes + + @property + def project_number(self): + """Optional[str]: The project number corresponding to the workload identity pool.""" + + # STS audience pattern: + # //iam.googleapis.com/projects/$PROJECT_NUMBER/locations/... + components = self._audience.split("/") + try: + project_index = components.index("projects") + if project_index + 1 < len(components): + return components[project_index + 1] or None + except ValueError: + return None + + @property + def token_info_url(self): + """Optional[str]: The STS token introspection endpoint.""" + + return self._token_info_url + + @_helpers.copy_docstring(credentials.Credentials) + def get_cred_info(self): + if self._cred_file_path: + cred_info_json = { + "credential_source": self._cred_file_path, + "credential_type": "external account credentials", + } + if self.service_account_email: + cred_info_json["principal"] = self.service_account_email + return cred_info_json + return None + + @_helpers.copy_docstring(credentials.Scoped) + def with_scopes(self, scopes, default_scopes=None): + kwargs = self._constructor_args() + kwargs.update(scopes=scopes, default_scopes=default_scopes) + scoped = self.__class__(**kwargs) + scoped._cred_file_path = self._cred_file_path + scoped._metrics_options = self._metrics_options + self._copy_regional_access_boundary_manager(scoped) + return scoped + + @abc.abstractmethod + def retrieve_subject_token(self, request): + """Retrieves the subject token using the credential_source object. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + Returns: + str: The retrieved subject token. + """ + # pylint: disable=missing-raises-doc + # (pylint doesn't recognize that this is abstract) + raise NotImplementedError("retrieve_subject_token must be implemented") + + def get_project_id(self, request): + """Retrieves the project ID corresponding to the workload identity or workforce pool. + For workforce pool credentials, it returns the project ID corresponding to + the workforce_pool_user_project. + + When not determinable, None is returned. + + This is introduced to support the current pattern of using the Auth library: + + credentials, project_id = google.auth.default() + + The resource may not have permission (resourcemanager.projects.get) to + call this API or the required scopes may not be selected: + https://cloud.google.com/resource-manager/reference/rest/v1/projects/get#authorization-scopes + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + Returns: + Optional[str]: The project ID corresponding to the workload identity pool + or workforce pool if determinable. + """ + if self._project_id: + # If already retrieved, return the cached project ID value. + return self._project_id + scopes = self._scopes if self._scopes is not None else self._default_scopes + # Scopes are required in order to retrieve a valid access token. + project_number = self.project_number or self._workforce_pool_user_project + if project_number and scopes: + headers = {} + url = "{}{}".format(self._cloud_resource_manager_url, project_number) + self.before_request(request, "GET", url, headers) + response = request(url=url, method="GET", headers=headers) + + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + response_data = json.loads(response_body) + + if response.status == 200: + # Cache result as this field is immutable. + self._project_id = response_data.get("projectId") + return self._project_id + + return None + + def refresh(self, request): + """Refreshes the access token. + + For impersonated credentials, this method will refresh the underlying + source credentials and the impersonated credentials. + """ + self._perform_refresh_token(request) + + def _maybe_start_regional_access_boundary_refresh(self, request, url): + """Starts a background thread to refresh the Regional Access Boundary if needed. + + For impersonated credentials, this delegates the logic to the + underlying impersonated credentials. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + url (str): The URL of the request. + """ + if getattr(self, "_impersonated_credentials", None): + self._impersonated_credentials._maybe_start_regional_access_boundary_refresh( + request, url + ) + return + + super()._maybe_start_regional_access_boundary_refresh(request, url) + + def _perform_refresh_token(self, request, cert_fingerprint=None): + scopes = self._scopes if self._scopes is not None else self._default_scopes + + # Inject client certificate into request. + if self._mtls_required(): + request = functools.partial( + request, cert=self._get_mtls_cert_and_key_paths() + ) + + if self._should_initialize_impersonated_credentials(): + self._impersonated_credentials = self._initialize_impersonated_credentials() + + if self._impersonated_credentials: + self._impersonated_credentials.refresh(request) + self.token = self._impersonated_credentials.token + self.expiry = self._impersonated_credentials.expiry + # Propagate the inner RAB manager to ensure downstream injections + # apply the target service account's RAB. + self._rab_manager = self._impersonated_credentials._rab_manager + else: + now = _helpers.utcnow() + additional_options = {} + # Do not pass workforce_pool_user_project when client authentication + # is used. The client ID is sufficient for determining the user project. + if self._workforce_pool_user_project and not self._client_id: + additional_options["userProject"] = self._workforce_pool_user_project + + if cert_fingerprint: + additional_options["bindCertFingerprint"] = cert_fingerprint + + additional_headers = { + metrics.API_CLIENT_HEADER: metrics.byoid_metrics_header( + self._metrics_options + ) + } + response_data = self._sts_client.exchange_token( + request=request, + grant_type=_STS_GRANT_TYPE, + subject_token=self.retrieve_subject_token(request), + subject_token_type=self._subject_token_type, + audience=self._audience, + scopes=scopes, + requested_token_type=_STS_REQUESTED_TOKEN_TYPE, + additional_options=additional_options if additional_options else None, + additional_headers=additional_headers, + ) + self.token = response_data.get("access_token") + expires_in = response_data.get("expires_in") + # Some services do not respect the OAUTH2.0 RFC and send expires_in as a + # JSON String. + if isinstance(expires_in, str): + expires_in = int(expires_in) + + lifetime = datetime.timedelta(seconds=expires_in) + + self.expiry = now + lifetime + + def _build_regional_access_boundary_lookup_url( + self, request: "Optional[google.auth.transport.Request]" = None # noqa: F821 + ): + """Builds and returns the URL for the Regional Access Boundary lookup API.""" + if getattr(self, "_impersonated_credentials", None): + # Impersonated credentials independently fetch and manage their own RAB. + return None + + url = None + # Try to parse as a workload identity pool. + # Audience format: //iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID + workload_match = re.search( + r"projects/([^/]+)/locations/global/workloadIdentityPools/([^/]+)", + self._audience, + ) + if workload_match: + project_number, pool_id = workload_match.groups() + url = iam._WORKLOAD_IDENTITY_POOL_REGIONAL_ACCESS_BOUNDARY_LOOKUP_ENDPOINT.format( + project_number=project_number, + pool_id=pool_id, + ) + else: + # If that fails, try to parse as a workforce pool. + # Audience format: //iam.googleapis.com/locations/global/workforcePools/POOL_ID/providers/PROVIDER_ID + workforce_match = re.search( + r"locations/[^/]+/workforcePools/([^/]+)", self._audience + ) + if workforce_match: + pool_id = workforce_match.groups()[0] + url = ( + iam._WORKFORCE_POOL_REGIONAL_ACCESS_BOUNDARY_LOOKUP_ENDPOINT.format( + pool_id=pool_id + ) + ) + + if url: + return url + else: + # If both fail, the audience format is invalid. + _LOGGER.error( + "Invalid audience format for Regional Access Boundary lookup: %s", + self._audience, + ) + return None + + def _make_copy(self): + kwargs = self._constructor_args() + new_cred = self.__class__(**kwargs) + new_cred._cred_file_path = self._cred_file_path + new_cred._metrics_options = self._metrics_options + self._copy_regional_access_boundary_manager(new_cred) + return new_cred + + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + # Return copy of instance with the provided quota project ID. + cred = self._make_copy() + cred._quota_project_id = quota_project_id + return cred + + @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) + def with_token_uri(self, token_uri): + cred = self._make_copy() + cred._token_url = token_uri + return cred + + @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain) + def with_universe_domain(self, universe_domain): + cred = self._make_copy() + cred._universe_domain = universe_domain + return cred + + def _should_initialize_impersonated_credentials(self): + return ( + self._service_account_impersonation_url is not None + and self._impersonated_credentials is None + ) + + def _initialize_impersonated_credentials(self): + """Generates an impersonated credentials. + + For more details, see `projects.serviceAccounts.generateAccessToken`_. + + .. _projects.serviceAccounts.generateAccessToken: https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken + + Returns: + impersonated_credentials.Credential: The impersonated credentials + object. + + Raises: + google.auth.exceptions.RefreshError: If the generateAccessToken + endpoint returned an error. + """ + # Return copy of instance with no service account impersonation. + kwargs = self._constructor_args() + kwargs.update( + service_account_impersonation_url=None, + service_account_impersonation_options={}, + ) + source_credentials = self.__class__(**kwargs) + source_credentials._metrics_options = self._metrics_options + + # Determine target_principal. + target_principal = self.service_account_email + if not target_principal: + raise exceptions.RefreshError( + "Unable to determine target principal from service account impersonation URL." + ) + + scopes = self._scopes if self._scopes is not None else self._default_scopes + # Initialize and return impersonated credentials. + return impersonated_credentials.Credentials( + source_credentials=source_credentials, + target_principal=target_principal, + target_scopes=scopes, + quota_project_id=self._quota_project_id, + iam_endpoint_override=self._service_account_impersonation_url, + lifetime=self._service_account_impersonation_options.get( + "token_lifetime_seconds" + ), + trust_boundary=self._trust_boundary, + ) + + def _create_default_metrics_options(self): + metrics_options = {} + if self._service_account_impersonation_url: + metrics_options["sa-impersonation"] = "true" + else: + metrics_options["sa-impersonation"] = "false" + if self._service_account_impersonation_options.get("token_lifetime_seconds"): + metrics_options["config-lifetime"] = "true" + else: + metrics_options["config-lifetime"] = "false" + + return metrics_options + + def _mtls_required(self): + """Returns a boolean representing whether the current credential is configured + for mTLS and should add a certificate to the outgoing calls to the sts and service + account impersonation endpoint. + + Returns: + bool: True if the credential is configured for mTLS, False if it is not. + """ + return False + + def _get_mtls_cert_and_key_paths(self): + """Gets the file locations for a certificate and private key file + to be used for configuring mTLS for the sts and service account + impersonation calls. Currently only expected to return a value when using + X509 workload identity federation. + + Returns: + Tuple[str, str]: The cert and key file locations as strings in a tuple. + + Raises: + NotImplementedError: When the current credential is not configured for + mTLS. + """ + raise NotImplementedError( + "_get_mtls_cert_and_key_location must be implemented." + ) + + @classmethod + def from_info(cls, info, **kwargs): + """Creates a Credentials instance from parsed external account info. + + **IMPORTANT**: + This method does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using with this method. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + + Args: + info (Mapping[str, str]): The external account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.identity_pool.Credentials: The constructed + credentials. + + Raises: + InvalidValue: For invalid parameters. + """ + return cls( + audience=info.get("audience"), + subject_token_type=info.get("subject_token_type"), + token_url=info.get("token_url"), + token_info_url=info.get("token_info_url"), + service_account_impersonation_url=info.get( + "service_account_impersonation_url" + ), + service_account_impersonation_options=info.get( + "service_account_impersonation" + ) + or {}, + client_id=info.get("client_id"), + client_secret=info.get("client_secret"), + credential_source=info.get("credential_source"), + quota_project_id=info.get("quota_project_id"), + workforce_pool_user_project=info.get("workforce_pool_user_project"), + universe_domain=info.get( + "universe_domain", credentials.DEFAULT_UNIVERSE_DOMAIN + ), + trust_boundary=info.get("trust_boundary"), + **kwargs + ) + + @classmethod + def from_file(cls, filename, **kwargs): + """Creates a Credentials instance from an external account json file. + + **IMPORTANT**: + This method does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using with this method. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + + Args: + filename (str): The path to the external account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.identity_pool.Credentials: The constructed + credentials. + """ + with io.open(filename, "r", encoding="utf-8") as json_file: + data = json.load(json_file) + return cls.from_info(data, **kwargs) diff --git a/intelliaide/vendor/google/auth/external_account_authorized_user.py b/intelliaide/vendor/google/auth/external_account_authorized_user.py new file mode 100644 index 0000000..d292589 --- /dev/null +++ b/intelliaide/vendor/google/auth/external_account_authorized_user.py @@ -0,0 +1,472 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""External Account Authorized User Credentials. +This module provides credentials based on OAuth 2.0 access and refresh tokens. +These credentials usually access resources on behalf of a user (resource +owner). + +Specifically, these are sourced using external identities via Workforce Identity Federation. + +Obtaining the initial access and refresh token can be done through the Google Cloud CLI. + +Example credential: +{ + "type": "external_account_authorized_user", + "audience": "//iam.googleapis.com/locations/global/workforcePools/$WORKFORCE_POOL_ID/providers/$PROVIDER_ID", + "refresh_token": "refreshToken", + "token_url": "https://sts.googleapis.com/v1/oauth/token", + "token_info_url": "https://sts.googleapis.com/v1/instrospect", + "client_id": "clientId", + "client_secret": "clientSecret" +} +""" + +import datetime +import io +import json +import logging +import re +from typing import Optional, TYPE_CHECKING + + +from google.auth import _helpers +from google.auth import credentials +from google.auth import exceptions +from google.auth import iam +from google.oauth2 import sts +from google.oauth2 import utils + +if TYPE_CHECKING: # pragma: NO COVER + import google.auth.transport + +_LOGGER = logging.getLogger(__name__) + +_EXTERNAL_ACCOUNT_AUTHORIZED_USER_JSON_TYPE = "external_account_authorized_user" + + +class Credentials( + credentials.CredentialsWithQuotaProject, + credentials.ReadOnlyScoped, + credentials.CredentialsWithTokenUri, + credentials.CredentialsWithRegionalAccessBoundary, +): + """Credentials for External Account Authorized Users. + + This is used to instantiate Credentials for exchanging refresh tokens from + authorized users for Google access token and authorizing requests to Google + APIs. + + The credentials are considered immutable. If you want to modify the + quota project, use `with_quota_project` and if you want to modify the token + uri, use `with_token_uri`. + + **IMPORTANT**: + This class does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + """ + + def __init__( + self, + token=None, + expiry=None, + refresh_token=None, + audience=None, + client_id=None, + client_secret=None, + token_url=None, + token_info_url=None, + revoke_url=None, + scopes=None, + quota_project_id=None, + universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, + ): + """Instantiates a external account authorized user credentials object. + + Args: + token (str): The OAuth 2.0 access token. Can be None if refresh information + is provided. + expiry (datetime.datetime): The optional expiration datetime of the OAuth 2.0 access + token. + refresh_token (str): The optional OAuth 2.0 refresh token. If specified, + credentials can be refreshed. + audience (str): The optional STS audience which contains the resource name for the workforce + pool and the provider identifier in that pool. + client_id (str): The OAuth 2.0 client ID. Must be specified for refresh, can be left as + None if the token can not be refreshed. + client_secret (str): The OAuth 2.0 client secret. Must be specified for refresh, can be + left as None if the token can not be refreshed. + token_url (str): The optional STS token exchange endpoint for refresh. Must be specified for + refresh, can be left as None if the token can not be refreshed. + token_info_url (str): The optional STS endpoint URL for token introspection. + revoke_url (str): The optional STS endpoint URL for revoking tokens. + quota_project_id (str): The optional project ID used for quota and billing. + This project may be different from the project used to + create the credentials. + universe_domain (Optional[str]): The universe domain. The default value + is googleapis.com. + trust_boundary (Mapping[str,str]): A credential trust boundary. + + Returns: + google.auth.external_account_authorized_user.Credentials: The + constructed credentials. + """ + super(Credentials, self).__init__() + + self.token = token + self.expiry = expiry + self._audience = audience + self._refresh_token = refresh_token + self._token_url = token_url + self._token_info_url = token_info_url + self._client_id = client_id + self._client_secret = client_secret + self._revoke_url = revoke_url + self._quota_project_id = quota_project_id + self._scopes = scopes + self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN + self._cred_file_path = None + self._trust_boundary = trust_boundary + + if not self.valid and not self.can_refresh: + raise exceptions.InvalidOperation( + "Token should be created with fields to make it valid (`token` and " + "`expiry`), or fields to allow it to refresh (`refresh_token`, " + "`token_url`, `client_id`, `client_secret`)." + ) + + self._client_auth = None + if self._client_id: + self._client_auth = utils.ClientAuthentication( + utils.ClientAuthType.basic, self._client_id, self._client_secret + ) + self._sts_client = sts.Client(self._token_url, self._client_auth) + + @property + def info(self): + """Generates the serializable dictionary representation of the current + credentials. + + Returns: + Mapping: The dictionary representation of the credentials. This is the + reverse of the "from_info" method defined in this class. It is + useful for serializing the current credentials so it can deserialized + later. + """ + config_info = self.constructor_args() + config_info.update(type=_EXTERNAL_ACCOUNT_AUTHORIZED_USER_JSON_TYPE) + if config_info["expiry"]: + config_info["expiry"] = config_info["expiry"].isoformat() + "Z" + + return {key: value for key, value in config_info.items() if value is not None} + + def constructor_args(self): + return { + "audience": self._audience, + "refresh_token": self._refresh_token, + "token_url": self._token_url, + "token_info_url": self._token_info_url, + "client_id": self._client_id, + "client_secret": self._client_secret, + "token": self.token, + "expiry": self.expiry, + "revoke_url": self._revoke_url, + "scopes": self._scopes, + "quota_project_id": self._quota_project_id, + "universe_domain": self._universe_domain, + "trust_boundary": self._trust_boundary, + } + + @property + def scopes(self): + """Optional[str]: The OAuth 2.0 permission scopes.""" + return self._scopes + + @property + def requires_scopes(self): + """False: OAuth 2.0 credentials have their scopes set when + the initial token is requested and can not be changed.""" + return False + + @property + def client_id(self): + """Optional[str]: The OAuth 2.0 client ID.""" + return self._client_id + + @property + def client_secret(self): + """Optional[str]: The OAuth 2.0 client secret.""" + return self._client_secret + + @property + def audience(self): + """Optional[str]: The STS audience which contains the resource name for the + workforce pool and the provider identifier in that pool.""" + return self._audience + + @property + def refresh_token(self): + """Optional[str]: The OAuth 2.0 refresh token.""" + return self._refresh_token + + @property + def token_url(self): + """Optional[str]: The STS token exchange endpoint for refresh.""" + return self._token_url + + @property + def token_info_url(self): + """Optional[str]: The STS endpoint for token info.""" + return self._token_info_url + + @property + def revoke_url(self): + """Optional[str]: The STS endpoint for token revocation.""" + return self._revoke_url + + @property + def is_user(self): + """True: This credential always represents a user.""" + return True + + @property + def can_refresh(self): + return all( + ( + self._refresh_token, + self._token_url, + self._client_id, + self._client_secret, + ) + ) + + def get_project_id(self, request=None): + """Retrieves the project ID corresponding to the workload identity or workforce pool. + For workforce pool credentials, it returns the project ID corresponding to + the workforce_pool_user_project. + + When not determinable, None is returned. + + Args: + request (google.auth.transport.requests.Request): Request object. + Unused here, but passed from _default.default(). + + Return: + str: project ID is not determinable for this credential type so it returns None + """ + + return None + + def to_json(self, strip=None): + """Utility function that creates a JSON representation of this + credential. + Args: + strip (Sequence[str]): Optional list of members to exclude from the + generated JSON. + Returns: + str: A JSON representation of this instance. When converted into + a dictionary, it can be passed to from_info() + to create a new instance. + """ + strip = strip if strip else [] + return json.dumps({k: v for (k, v) in self.info.items() if k not in strip}) + + def _perform_refresh_token(self, request): + """Refreshes the access token. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If the credentials could + not be refreshed. + """ + if not self.can_refresh: + raise exceptions.RefreshError( + "The credentials do not contain the necessary fields need to " + "refresh the access token. You must specify refresh_token, " + "token_url, client_id, and client_secret." + ) + + now = _helpers.utcnow() + response_data = self._sts_client.refresh_token(request, self._refresh_token) + + self.token = response_data.get("access_token") + + lifetime = datetime.timedelta(seconds=response_data.get("expires_in")) + self.expiry = now + lifetime + + if "refresh_token" in response_data: + self._refresh_token = response_data["refresh_token"] + + def _build_regional_access_boundary_lookup_url( + self, request: "Optional[google.auth.transport.Request]" = None # noqa: F821 + ): + """Builds and returns the URL for the Regional Access Boundary lookup API. + + Returns: + Optional[str]: The URL for the Regional Access Boundary lookup endpoint, or None + if the URL cannot be built due to an invalid workforce pool audience format. + """ + # Audience format: //iam.googleapis.com/locations/global/workforcePools/POOL_ID/providers/PROVIDER_ID + match = re.search(r"locations/[^/]+/workforcePools/([^/]+)", self._audience) + + if not match: + _LOGGER.error( + "Invalid workforce pool audience format for Regional Access Boundary lookup: %s", + self._audience, + ) + return None + + pool_id = match.groups()[0] + + return iam._WORKFORCE_POOL_REGIONAL_ACCESS_BOUNDARY_LOOKUP_ENDPOINT.format( + pool_id=pool_id + ) + + def revoke(self, request): + """Revokes the refresh token. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.OAuthError: If the token could not be + revoked. + """ + if not self._revoke_url or not self._refresh_token: + raise exceptions.OAuthError( + "The credentials do not contain the necessary fields to " + "revoke the refresh token. You must specify revoke_url and " + "refresh_token." + ) + + self._sts_client.revoke_token( + request, self._refresh_token, "refresh_token", self._revoke_url + ) + self.token = None + self._refresh_token = None + + @_helpers.copy_docstring(credentials.Credentials) + def get_cred_info(self): + if self._cred_file_path: + return { + "credential_source": self._cred_file_path, + "credential_type": "external account authorized user credentials", + } + return None + + def _make_copy(self): + kwargs = self.constructor_args() + cred = self.__class__(**kwargs) + cred._cred_file_path = self._cred_file_path + self._copy_regional_access_boundary_manager(cred) + return cred + + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + cred = self._make_copy() + cred._quota_project_id = quota_project_id + return cred + + @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) + def with_token_uri(self, token_uri): + cred = self._make_copy() + cred._token_url = token_uri + return cred + + @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain) + def with_universe_domain(self, universe_domain): + cred = self._make_copy() + cred._universe_domain = universe_domain + return cred + + @classmethod + def from_info(cls, info, **kwargs): + """Creates a Credentials instance from parsed external account info. + + **IMPORTANT**: + This method does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using with this method. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + + Args: + info (Mapping[str, str]): The external account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.external_account_authorized_user.Credentials: The + constructed credentials. + + Raises: + ValueError: For invalid parameters. + """ + expiry = info.get("expiry") + if expiry: + expiry = datetime.datetime.strptime( + expiry.rstrip("Z").split(".")[0], "%Y-%m-%dT%H:%M:%S" + ) + return cls( + audience=info.get("audience"), + refresh_token=info.get("refresh_token"), + token_url=info.get("token_url"), + token_info_url=info.get("token_info_url"), + client_id=info.get("client_id"), + client_secret=info.get("client_secret"), + token=info.get("token"), + expiry=expiry, + revoke_url=info.get("revoke_url"), + quota_project_id=info.get("quota_project_id"), + scopes=info.get("scopes"), + universe_domain=info.get( + "universe_domain", credentials.DEFAULT_UNIVERSE_DOMAIN + ), + trust_boundary=info.get("trust_boundary"), + **kwargs + ) + + @classmethod + def from_file(cls, filename, **kwargs): + """Creates a Credentials instance from an external account json file. + + **IMPORTANT**: + This method does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using with this method. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + + Args: + filename (str): The path to the external account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.external_account_authorized_user.Credentials: The + constructed credentials. + """ + with io.open(filename, "r", encoding="utf-8") as json_file: + data = json.load(json_file) + return cls.from_info(data, **kwargs) diff --git a/intelliaide/vendor/google/auth/iam.py b/intelliaide/vendor/google/auth/iam.py new file mode 100644 index 0000000..00b6e06 --- /dev/null +++ b/intelliaide/vendor/google/auth/iam.py @@ -0,0 +1,143 @@ +# Copyright 2017 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tools for using the Google `Cloud Identity and Access Management (IAM) +API`_'s auth-related functionality. + +.. _Cloud Identity and Access Management (IAM) API: + https://cloud.google.com/iam/docs/ +""" + +import base64 +import http.client as http_client +import json + +from google.auth import _exponential_backoff +from google.auth import _helpers +from google.auth import credentials +from google.auth import crypt +from google.auth import exceptions +from google.auth.transport import _mtls_helper + +IAM_RETRY_CODES = { + http_client.INTERNAL_SERVER_ERROR, + http_client.BAD_GATEWAY, + http_client.SERVICE_UNAVAILABLE, + http_client.GATEWAY_TIMEOUT, +} + +_IAM_SCOPE = ["https://www.googleapis.com/auth/iam"] + +# Determine if we should use mTLS. +if ( + hasattr(_mtls_helper, "check_use_client_cert") + and _mtls_helper.check_use_client_cert() +): + # Construct the template domain using the library's DEFAULT_UNIVERSE_DOMAIN constant. + _IAM_DOMAIN = f"iamcredentials.mtls.{credentials.DEFAULT_UNIVERSE_DOMAIN}" +else: + _IAM_DOMAIN = f"iamcredentials.{credentials.DEFAULT_UNIVERSE_DOMAIN}" + +# 3. Create the common base URL template +# We use double brackets {{}} so .format() can be called later for the email. +_IAM_BASE_URL = f"https://{_IAM_DOMAIN}/v1/projects/-/serviceAccounts/{{}}" + +# 4. Define the endpoints as templates +_IAM_ENDPOINT = _IAM_BASE_URL + ":generateAccessToken" +_IAM_SIGN_ENDPOINT = _IAM_BASE_URL + ":signBlob" +_IAM_SIGNJWT_ENDPOINT = _IAM_BASE_URL + ":signJwt" +_IAM_IDTOKEN_ENDPOINT = _IAM_BASE_URL + ":generateIdToken" + + +# Regional Access Boundary (RAB) Lookup Endpoints +_SERVICE_ACCOUNT_REGIONAL_ACCESS_BOUNDARY_LOOKUP_ENDPOINT = f"https://{_IAM_DOMAIN}/v1/projects/-/serviceAccounts/{{service_account_email}}/allowedLocations" +_WORKFORCE_POOL_REGIONAL_ACCESS_BOUNDARY_LOOKUP_ENDPOINT = f"https://{_IAM_DOMAIN}/v1/locations/global/workforcePools/{{pool_id}}/allowedLocations" +_WORKLOAD_IDENTITY_POOL_REGIONAL_ACCESS_BOUNDARY_LOOKUP_ENDPOINT = f"https://{_IAM_DOMAIN}/v1/projects/{{project_number}}/locations/global/workloadIdentityPools/{{pool_id}}/allowedLocations" + + +class Signer(crypt.Signer): + """Signs messages using the IAM `signBlob API`_. + + This is useful when you need to sign bytes but do not have access to the + credential's private key file. + + .. _signBlob API: + https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts + /signBlob + """ + + def __init__(self, request, credentials, service_account_email): + """ + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + credentials (google.auth.credentials.Credentials): The credentials + that will be used to authenticate the request to the IAM API. + The credentials must have of one the following scopes: + + - https://www.googleapis.com/auth/iam + - https://www.googleapis.com/auth/cloud-platform + service_account_email (str): The service account email identifying + which service account to use to sign bytes. Often, this can + be the same as the service account email in the given + credentials. + """ + self._request = request + self._credentials = credentials + self._service_account_email = service_account_email + + def _make_signing_request(self, message): + """Makes a request to the API signBlob API.""" + message = _helpers.to_bytes(message) + + method = "POST" + url = _IAM_SIGN_ENDPOINT.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, self._credentials.universe_domain + ).format(self._service_account_email) + headers = {"Content-Type": "application/json"} + body = json.dumps( + {"payload": base64.b64encode(message).decode("utf-8")} + ).encode("utf-8") + + retries = _exponential_backoff.ExponentialBackoff() + for _ in retries: + self._credentials.before_request(self._request, method, url, headers) + + response = self._request(url=url, method=method, body=body, headers=headers) + + if response.status in IAM_RETRY_CODES: + continue + + if response.status != http_client.OK: + raise exceptions.TransportError( + "Error calling the IAM signBlob API: {}".format(response.data) + ) + + return json.loads(response.data.decode("utf-8")) + raise exceptions.TransportError("exhausted signBlob endpoint retries") + + @property + def key_id(self): + """Optional[str]: The key ID used to identify this private key. + + .. warning:: + This is always ``None``. The key ID used by IAM can not + be reliably determined ahead of time. + """ + return None + + @_helpers.copy_docstring(crypt.Signer) + def sign(self, message): + response = self._make_signing_request(message) + return base64.b64decode(response["signedBlob"]) diff --git a/intelliaide/vendor/google/auth/identity_pool.py b/intelliaide/vendor/google/auth/identity_pool.py new file mode 100644 index 0000000..30819ef --- /dev/null +++ b/intelliaide/vendor/google/auth/identity_pool.py @@ -0,0 +1,574 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Identity Pool Credentials. + +This module provides credentials to access Google Cloud resources from on-prem +or non-Google Cloud platforms which support external credentials (e.g. OIDC ID +tokens) retrieved from local file locations or local servers. This includes +Microsoft Azure and OIDC identity providers (e.g. K8s workloads registered with +Hub with Hub workload identity enabled). + +These credentials are recommended over the use of service account credentials +in on-prem/non-Google Cloud platforms as they do not involve the management of +long-live service account private keys. + +Identity Pool Credentials are initialized using external_account +arguments which are typically loaded from an external credentials file or +an external credentials URL. + +This module also provides a definition for an abstract subject token supplier. +This supplier can be implemented to return a valid OIDC or SAML2.0 subject token +and used to create Identity Pool credentials. The credentials will then call the +supplier instead of using pre-defined methods such as reading a local file or +calling a URL. +""" + +try: + from collections.abc import Mapping +# Python 2.7 compatibility +except ImportError: # pragma: NO COVER + from collections import Mapping # type: ignore +import abc +import base64 +import json +import os +from typing import NamedTuple + +from google.auth import _helpers +from google.auth import exceptions +from google.auth import external_account +from google.auth.transport import _mtls_helper + + +class SubjectTokenSupplier(metaclass=abc.ABCMeta): + """Base class for subject token suppliers. This can be implemented with custom logic to retrieve + a subject token to exchange for a Google Cloud access token when using Workload or + Workforce Identity Federation. The identity pool credential does not cache the subject token, + so caching logic should be added in the implementation. + """ + + @abc.abstractmethod + def get_subject_token(self, context, request): + """Returns the requested subject token. The subject token must be valid. + + .. warning: This is not cached by the calling Google credential, so caching logic should be implemented in the supplier. + + Args: + context (google.auth.externalaccount.SupplierContext): The context object + containing information about the requested audience and subject token type. + request (google.auth.transport.Request): The object used to make + HTTP requests. + + Raises: + google.auth.exceptions.RefreshError: If an error is encountered during + subject token retrieval logic. + + Returns: + str: The requested subject token string. + """ + raise NotImplementedError("") + + +class _TokenContent(NamedTuple): + """Models the token content response from file and url internal suppliers. + Attributes: + content (str): The string content of the file or URL response. + location (str): The location the content was retrieved from. This will either be a file location or a URL. + """ + + content: str + location: str + + +class _FileSupplier(SubjectTokenSupplier): + """Internal implementation of subject token supplier which supports reading a subject token from a file.""" + + def __init__(self, path, format_type, subject_token_field_name): + self._path = path + self._format_type = format_type + self._subject_token_field_name = subject_token_field_name + + @_helpers.copy_docstring(SubjectTokenSupplier) + def get_subject_token(self, context, request): + if not os.path.exists(self._path): + raise exceptions.RefreshError("File '{}' was not found.".format(self._path)) + + with open(self._path, "r", encoding="utf-8") as file_obj: + token_content = _TokenContent(file_obj.read(), self._path) + + return _parse_token_data( + token_content, self._format_type, self._subject_token_field_name + ) + + +class _UrlSupplier(SubjectTokenSupplier): + """Internal implementation of subject token supplier which supports retrieving a subject token by calling a URL endpoint.""" + + def __init__(self, url, format_type, subject_token_field_name, headers): + self._url = url + self._format_type = format_type + self._subject_token_field_name = subject_token_field_name + self._headers = headers + + @_helpers.copy_docstring(SubjectTokenSupplier) + def get_subject_token(self, context, request): + response = request(url=self._url, method="GET", headers=self._headers) + + # support both string and bytes type response.data + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + if response.status != 200: + raise exceptions.RefreshError( + "Unable to retrieve Identity Pool subject token", response_body + ) + token_content = _TokenContent(response_body, self._url) + return _parse_token_data( + token_content, self._format_type, self._subject_token_field_name + ) + + +class _X509Supplier(SubjectTokenSupplier): + """Internal supplier for X509 workload credentials. This class is used internally and always returns an empty string as the subject token.""" + + def __init__(self, trust_chain_path, leaf_cert_callback): + self._trust_chain_path = trust_chain_path + self._leaf_cert_callback = leaf_cert_callback + + @_helpers.copy_docstring(SubjectTokenSupplier) + def get_subject_token(self, context, request): + # Import OpennSSL inline because it is an extra import only required by customers + # using mTLS. + from OpenSSL import crypto + + leaf_cert = crypto.load_certificate( + crypto.FILETYPE_PEM, self._leaf_cert_callback() + ) + trust_chain = self._read_trust_chain() + cert_chain = [] + + cert_chain.append(_X509Supplier._encode_cert(leaf_cert)) + + if trust_chain is None or len(trust_chain) == 0: + return json.dumps(cert_chain) + + # Append the first cert if it is not the leaf cert. + first_cert = _X509Supplier._encode_cert(trust_chain[0]) + if first_cert != cert_chain[0]: + cert_chain.append(first_cert) + + for i in range(1, len(trust_chain)): + encoded = _X509Supplier._encode_cert(trust_chain[i]) + # Check if the current cert is the leaf cert and raise an exception if it is. + if encoded == cert_chain[0]: + raise exceptions.RefreshError( + "The leaf certificate must be at the top of the trust chain file" + ) + else: + cert_chain.append(encoded) + return json.dumps(cert_chain) + + def _read_trust_chain(self): + # Import OpennSSL inline because it is an extra import only required by customers + # using mTLS. + from OpenSSL import crypto + + certificate_trust_chain = [] + # If no trust chain path was provided, return an empty list. + if self._trust_chain_path is None or self._trust_chain_path == "": + return certificate_trust_chain + try: + # Open the trust chain file. + with open(self._trust_chain_path, "rb") as f: + trust_chain_data = f.read() + # Split PEM data into individual certificates. + cert_blocks = trust_chain_data.split(b"-----BEGIN CERTIFICATE-----") + for cert_block in cert_blocks: + # Skip empty blocks. + if cert_block.strip(): + cert_data = b"-----BEGIN CERTIFICATE-----" + cert_block + try: + # Load each certificate and add it to the trust chain. + cert = crypto.load_certificate( + crypto.FILETYPE_PEM, cert_data + ) + certificate_trust_chain.append(cert) + except Exception as e: + raise exceptions.RefreshError( + "Error loading PEM certificates from the trust chain file '{}'".format( + self._trust_chain_path + ) + ) from e + return certificate_trust_chain + except FileNotFoundError: + raise exceptions.RefreshError( + "Trust chain file '{}' was not found.".format(self._trust_chain_path) + ) + + def _encode_cert(cert): + # Import OpennSSL inline because it is an extra import only required by customers + # using mTLS. + from OpenSSL import crypto + + return base64.b64encode( + crypto.dump_certificate(crypto.FILETYPE_ASN1, cert) + ).decode("utf-8") + + +def _parse_token_data(token_content, format_type="text", subject_token_field_name=None): + if format_type == "text": + token = token_content.content + else: + try: + # Parse file content as JSON. + response_data = json.loads(token_content.content) + # Get the subject_token. + token = response_data[subject_token_field_name] + except (KeyError, ValueError): + raise exceptions.RefreshError( + "Unable to parse subject_token from JSON file '{}' using key '{}'".format( + token_content.location, subject_token_field_name + ) + ) + if not token: + raise exceptions.RefreshError( + "Missing subject_token in the credential_source file" + ) + return token + + +class Credentials(external_account.Credentials): + """External account credentials sourced from files and URLs. + + **IMPORTANT**: + This class does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + """ + + def __init__( + self, + audience, + subject_token_type, + token_url=external_account._DEFAULT_TOKEN_URL, + credential_source=None, + subject_token_supplier=None, + *args, + **kwargs + ): + """Instantiates an external account credentials object from a file/URL. + + Args: + audience (str): The STS audience field. + subject_token_type (str): The subject token type based on the Oauth2.0 token exchange spec. + Expected values include:: + + “urn:ietf:params:oauth:token-type:jwt” + “urn:ietf:params:oauth:token-type:id-token” + “urn:ietf:params:oauth:token-type:saml2” + + token_url (Optional [str]): The STS endpoint URL. If not provided, will default to "https://sts.googleapis.com/v1/token". + credential_source (Optional [Mapping]): The credential source dictionary used to + provide instructions on how to retrieve external credential to be + exchanged for Google access tokens. Either a credential source or + a subject token supplier must be provided. + + Example credential_source for url-sourced credential:: + + { + "url": "http://www.example.com", + "format": { + "type": "json", + "subject_token_field_name": "access_token", + }, + "headers": {"foo": "bar"}, + } + + Example credential_source for file-sourced credential:: + + { + "file": "/path/to/token/file.txt" + } + subject_token_supplier (Optional [SubjectTokenSupplier]): Optional subject token supplier. + This will be called to supply a valid subject token which will then + be exchanged for Google access tokens. Either a subject token supplier + or a credential source must be provided. + args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. + kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. + + Raises: + google.auth.exceptions.RefreshError: If an error is encountered during + access token retrieval logic. + ValueError: For invalid parameters. + + .. note:: Typically one of the helper constructors + :meth:`from_file` or + :meth:`from_info` are used instead of calling the constructor directly. + """ + + super(Credentials, self).__init__( + audience=audience, + subject_token_type=subject_token_type, + token_url=token_url, + credential_source=credential_source, + *args, + **kwargs + ) + if credential_source is None and subject_token_supplier is None: + raise exceptions.InvalidValue( + "A valid credential source or a subject token supplier must be provided." + ) + if credential_source is not None and subject_token_supplier is not None: + raise exceptions.InvalidValue( + "Identity pool credential cannot have both a credential source and a subject token supplier." + ) + + if subject_token_supplier is not None: + self._subject_token_supplier = subject_token_supplier + self._credential_source_file = None + self._credential_source_url = None + self._credential_source_certificate = None + else: + if not isinstance(credential_source, Mapping): + self._credential_source_executable = None + raise exceptions.MalformedError( + "Invalid credential_source. The credential_source is not a dict." + ) + self._credential_source_file = credential_source.get("file") + self._credential_source_url = credential_source.get("url") + self._credential_source_certificate = credential_source.get("certificate") + + # environment_id is only supported in AWS or dedicated future external + # account credentials. + if "environment_id" in credential_source: + raise exceptions.MalformedError( + "Invalid Identity Pool credential_source field 'environment_id'" + ) + + # check that only one of file, url, or certificate are provided. + self._validate_single_source() + + if self._credential_source_certificate: + self._validate_certificate_config() + else: + self._validate_file_or_url_config(credential_source) + + if self._credential_source_file: + self._subject_token_supplier = _FileSupplier( + self._credential_source_file, + self._credential_source_format_type, + self._credential_source_field_name, + ) + elif self._credential_source_url: + self._subject_token_supplier = _UrlSupplier( + self._credential_source_url, + self._credential_source_format_type, + self._credential_source_field_name, + self._credential_source_headers, + ) + else: # self._credential_source_certificate + self._subject_token_supplier = _X509Supplier( + self._trust_chain_path, self._get_cert_bytes + ) + + @_helpers.copy_docstring(external_account.Credentials) + def retrieve_subject_token(self, request): + return self._subject_token_supplier.get_subject_token( + self._supplier_context, request + ) + + def _get_mtls_cert_and_key_paths(self): + if self._credential_source_certificate is None: + raise exceptions.RefreshError( + 'The credential is not configured to use mtls requests. The credential should include a "certificate" section in the credential source.' + ) + else: + return _mtls_helper._get_workload_cert_and_key_paths( + self._certificate_config_location + ) + + def _get_cert_bytes(self): + cert_path, _ = self._get_mtls_cert_and_key_paths() + return _mtls_helper._read_cert_file(cert_path) + + def _mtls_required(self): + return self._credential_source_certificate is not None + + def _create_default_metrics_options(self): + metrics_options = super(Credentials, self)._create_default_metrics_options() + # Check that credential source is a dict before checking for credential type. This check needs to be done + # here because the external_account credential constructor needs to pass the metrics options to the + # impersonated credential object before the identity_pool credentials are validated. + if isinstance(self._credential_source, Mapping): + if self._credential_source.get("file"): + metrics_options["source"] = "file" + elif self._credential_source.get("url"): + metrics_options["source"] = "url" + else: + metrics_options["source"] = "x509" + else: + metrics_options["source"] = "programmatic" + return metrics_options + + def _has_custom_supplier(self): + return self._credential_source is None + + def _constructor_args(self): + args = super(Credentials, self)._constructor_args() + # If a custom supplier was used, append it to the args dict. + if self._has_custom_supplier(): + args.update({"subject_token_supplier": self._subject_token_supplier}) + return args + + def _validate_certificate_config(self): + self._certificate_config_location = self._credential_source_certificate.get( + "certificate_config_location" + ) + use_default = self._credential_source_certificate.get( + "use_default_certificate_config" + ) + self._trust_chain_path = self._credential_source_certificate.get( + "trust_chain_path" + ) + if self._certificate_config_location and use_default: + raise exceptions.MalformedError( + "Invalid certificate configuration, certificate_config_location cannot be specified when use_default_certificate_config = true." + ) + if not self._certificate_config_location and not use_default: + raise exceptions.MalformedError( + "Invalid certificate configuration, use_default_certificate_config should be true if no certificate_config_location is provided." + ) + + def _validate_file_or_url_config(self, credential_source): + self._credential_source_headers = credential_source.get("headers") + credential_source_format = credential_source.get("format", {}) + # Get credential_source format type. When not provided, this + # defaults to text. + self._credential_source_format_type = ( + credential_source_format.get("type") or "text" + ) + if self._credential_source_format_type not in ["text", "json"]: + raise exceptions.MalformedError( + "Invalid credential_source format '{}'".format( + self._credential_source_format_type + ) + ) + # For JSON types, get the required subject_token field name. + if self._credential_source_format_type == "json": + self._credential_source_field_name = credential_source_format.get( + "subject_token_field_name" + ) + if self._credential_source_field_name is None: + raise exceptions.MalformedError( + "Missing subject_token_field_name for JSON credential_source format" + ) + else: + self._credential_source_field_name = None + + def _validate_single_source(self): + credential_sources = [ + self._credential_source_file, + self._credential_source_url, + self._credential_source_certificate, + ] + valid_credential_sources = list( + filter(lambda source: source is not None, credential_sources) + ) + + if len(valid_credential_sources) > 1: + raise exceptions.MalformedError( + "Ambiguous credential_source. 'file', 'url', and 'certificate' are mutually exclusive.." + ) + if len(valid_credential_sources) != 1: + raise exceptions.MalformedError( + "Missing credential_source. A 'file', 'url', or 'certificate' must be provided." + ) + + @classmethod + def from_info(cls, info, **kwargs): + """Creates an Identity Pool Credentials instance from parsed external account info. + + **IMPORTANT**: + This method does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using with this method. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + + Args: + info (Mapping[str, str]): The Identity Pool external account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.identity_pool.Credentials: The constructed + credentials. + + Raises: + ValueError: For invalid parameters. + """ + subject_token_supplier = info.get("subject_token_supplier") + kwargs.update({"subject_token_supplier": subject_token_supplier}) + return super(Credentials, cls).from_info(info, **kwargs) + + @classmethod + def from_file(cls, filename, **kwargs): + """Creates an IdentityPool Credentials instance from an external account json file. + + **IMPORTANT**: + This method does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using with this method. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + + Args: + filename (str): The path to the IdentityPool external account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.identity_pool.Credentials: The constructed + credentials. + """ + return super(Credentials, cls).from_file(filename, **kwargs) + + def refresh(self, request): + """Refreshes the access token. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + """ + from google.auth import _agent_identity_utils + + cert_fingerprint = None + # Check if the credential is X.509 based. + if self._credential_source_certificate is not None: + cert_bytes = self._get_cert_bytes() + cert = _agent_identity_utils.parse_certificate(cert_bytes) + if _agent_identity_utils.should_request_bound_token(cert): + cert_fingerprint = ( + _agent_identity_utils.calculate_certificate_fingerprint(cert) + ) + + self._perform_refresh_token(request, cert_fingerprint=cert_fingerprint) diff --git a/intelliaide/vendor/google/auth/impersonated_credentials.py b/intelliaide/vendor/google/auth/impersonated_credentials.py new file mode 100644 index 0000000..45db79d --- /dev/null +++ b/intelliaide/vendor/google/auth/impersonated_credentials.py @@ -0,0 +1,713 @@ +# Copyright 2018 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Impersonated credentials. + +This module provides authentication for applications where local credentials +impersonates a remote service account using `IAM Credentials API`_. + +This class can be used to impersonate a service account as long as the original +Credential object has the "Service Account Token Creator" role on the target +service account. + + .. _IAM Credentials API: + https://cloud.google.com/iam/credentials/reference/rest/ +""" + +import base64 +import copy +from datetime import datetime +import http.client as http_client +import json +import logging +from typing import Optional, TYPE_CHECKING + + +from google.auth import _exponential_backoff +from google.auth import _helpers +from google.auth import credentials +from google.auth import exceptions +from google.auth import iam +from google.auth import jwt +from google.auth import metrics +from google.oauth2 import _client + +if TYPE_CHECKING: # pragma: NO COVER + import google.auth.transport + +_LOGGER = logging.getLogger(__name__) + +_REFRESH_ERROR = "Unable to acquire impersonated credentials" + +_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds + +_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" + + +_SOURCE_CREDENTIAL_AUTHORIZED_USER_TYPE = "authorized_user" +_SOURCE_CREDENTIAL_SERVICE_ACCOUNT_TYPE = "service_account" +_SOURCE_CREDENTIAL_EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE = ( + "external_account_authorized_user" +) + + +def _make_iam_token_request( + request, + principal, + headers, + body, + universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, + iam_endpoint_override=None, +): + """Makes a request to the Google Cloud IAM service for an access token. + Args: + request (Request): The Request object to use. + principal (str): The principal to request an access token for. + headers (Mapping[str, str]): Map of headers to transmit. + body (Mapping[str, str]): JSON Payload body for the iamcredentials + API call. + iam_endpoint_override (Optiona[str]): The full IAM endpoint override + with the target_principal embedded. This is useful when supporting + impersonation with regional endpoints. + + Raises: + google.auth.exceptions.TransportError: Raised if there is an underlying + HTTP connection error + google.auth.exceptions.RefreshError: Raised if the impersonated + credentials are not available. Common reasons are + `iamcredentials.googleapis.com` is not enabled or the + `Service Account Token Creator` is not assigned + """ + iam_endpoint = iam_endpoint_override or iam._IAM_ENDPOINT.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, universe_domain + ).format(principal) + + body = json.dumps(body).encode("utf-8") + + response = request(url=iam_endpoint, method="POST", headers=headers, body=body) + + # support both string and bytes type response.data + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + if response.status != http_client.OK: + raise exceptions.RefreshError(_REFRESH_ERROR, response_body) + + try: + token_response = json.loads(response_body) + token = token_response["accessToken"] + expiry = datetime.strptime(token_response["expireTime"], "%Y-%m-%dT%H:%M:%SZ") + + return token, expiry + + except (KeyError, ValueError) as caught_exc: + new_exc = exceptions.RefreshError( + "{}: No access token or invalid expiration in response.".format( + _REFRESH_ERROR + ), + response_body, + ) + raise new_exc from caught_exc + + +class Credentials( + credentials.Scoped, + credentials.CredentialsWithQuotaProject, + credentials.Signing, + credentials.CredentialsWithRegionalAccessBoundary, +): + """This module defines impersonated credentials which are essentially + impersonated identities. + + Impersonated Credentials allows credentials issued to a user or + service account to impersonate another. The target service account must + grant the originating credential principal the + `Service Account Token Creator`_ IAM role: + + For more information about Token Creator IAM role and + IAMCredentials API, see + `Creating Short-Lived Service Account Credentials`_. + + .. _Service Account Token Creator: + https://cloud.google.com/iam/docs/service-accounts#the_service_account_token_creator_role + + .. _Creating Short-Lived Service Account Credentials: + https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials + + Usage: + + First grant source_credentials the `Service Account Token Creator` + role on the target account to impersonate. In this example, the + service account represented by svc_account.json has the + token creator role on + `impersonated-account@_project_.iam.gserviceaccount.com`. + + Enable the IAMCredentials API on the source project: + `gcloud services enable iamcredentials.googleapis.com`. + + Initialize a source credential which does not have access to + list bucket:: + + from google.oauth2 import service_account + + target_scopes = [ + 'https://www.googleapis.com/auth/devstorage.read_only'] + + source_credentials = ( + service_account.Credentials.from_service_account_file( + '/path/to/svc_account.json', + scopes=target_scopes)) + + Now use the source credentials to acquire credentials to impersonate + another service account:: + + from google.auth import impersonated_credentials + + target_credentials = impersonated_credentials.Credentials( + source_credentials=source_credentials, + target_principal='impersonated-account@_project_.iam.gserviceaccount.com', + target_scopes = target_scopes, + lifetime=500) + + Resource access is granted:: + + client = storage.Client(credentials=target_credentials) + buckets = client.list_buckets(project='your_project') + for bucket in buckets: + print(bucket.name) + + **IMPORTANT**: + This class does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + """ + + def __init__( + self, + source_credentials, + target_principal, + target_scopes, + delegates=None, + subject=None, + lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, + quota_project_id=None, + iam_endpoint_override=None, + trust_boundary=None, + ): + """ + Args: + source_credentials (google.auth.Credentials): The source credential + used as to acquire the impersonated credentials. + target_principal (str): The service account to impersonate. + target_scopes (Sequence[str]): Scopes to request during the + authorization grant. + delegates (Sequence[str]): The chained list of delegates required + to grant the final access_token. If set, the sequence of + identities must have "Service Account Token Creator" capability + granted to the prceeding identity. For example, if set to + [serviceAccountB, serviceAccountC], the source_credential + must have the Token Creator role on serviceAccountB. + serviceAccountB must have the Token Creator on + serviceAccountC. + Finally, C must have Token Creator on target_principal. + If left unset, source_credential must have that role on + target_principal. + lifetime (int): Number of seconds the delegated credential should + be valid for (upto 3600). + quota_project_id (Optional[str]): The project ID used for quota and billing. + This project may be different from the project used to + create the credentials. + iam_endpoint_override (Optional[str]): The full IAM endpoint override + with the target_principal embedded. This is useful when supporting + impersonation with regional endpoints. + subject (Optional[str]): sub field of a JWT. This field should only be set + if you wish to impersonate as a user. This feature is useful when + using domain wide delegation. + trust_boundary (Mapping[str,str]): A credential trust boundary. + """ + + super(Credentials, self).__init__() + + self._source_credentials = copy.copy(source_credentials) + # Service account source credentials must have the _IAM_SCOPE + # added to refresh correctly. User credentials cannot have + # their original scopes modified. + if isinstance(self._source_credentials, credentials.Scoped): + self._source_credentials = self._source_credentials.with_scopes( + iam._IAM_SCOPE + ) + # If the source credential is service account and self signed jwt + # is needed, we need to create a jwt credential inside it + if ( + hasattr(self._source_credentials, "_create_self_signed_jwt") + and self._source_credentials._always_use_jwt_access + ): + self._source_credentials._create_self_signed_jwt(None) + + self._universe_domain = source_credentials.universe_domain + self._target_principal = target_principal + self._target_scopes = target_scopes + self._delegates = delegates + self._subject = subject + self._lifetime = lifetime or _DEFAULT_TOKEN_LIFETIME_SECS + self.token = None + self.expiry = _helpers.utcnow() + self._quota_project_id = quota_project_id + self._iam_endpoint_override = iam_endpoint_override + self._cred_file_path = None + + self._trust_boundary = trust_boundary + + def _metric_header_for_usage(self): + return metrics.CRED_TYPE_SA_IMPERSONATE + + def _perform_refresh_token(self, request): + """Updates credentials with a new access_token representing + the impersonated account. + + Args: + request (google.auth.transport.requests.Request): Request object + to use for refreshing credentials. + """ + + # Refresh our source credentials if it is not valid. + if ( + self._source_credentials.token_state == credentials.TokenState.STALE + or self._source_credentials.token_state == credentials.TokenState.INVALID + ): + self._source_credentials.refresh(request) + + body = { + "delegates": self._delegates, + "scope": self._target_scopes, + "lifetime": str(self._lifetime) + "s", + } + + headers = { + "Content-Type": "application/json", + metrics.API_CLIENT_HEADER: metrics.token_request_access_token_impersonate(), + } + + # Apply the source credentials authentication info. + self._source_credentials.apply(headers) + + # If a subject is specified a domain-wide delegation auth-flow is initiated + # to impersonate as the provided subject (user). + if self._subject: + if self.universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN: + raise exceptions.GoogleAuthError( + "Domain-wide delegation is not supported in universes other " + + "than googleapis.com" + ) + + now = _helpers.utcnow() + payload = { + "iss": self._target_principal, + "scope": _helpers.scopes_to_string(self._target_scopes or ()), + "sub": self._subject, + "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT, + "iat": _helpers.datetime_to_secs(now), + "exp": _helpers.datetime_to_secs(now) + _DEFAULT_TOKEN_LIFETIME_SECS, + } + + assertion = _sign_jwt_request( + request=request, + principal=self._target_principal, + headers=headers, + payload=payload, + delegates=self._delegates, + ) + + self.token, self.expiry, _ = _client.jwt_grant( + request, _GOOGLE_OAUTH2_TOKEN_ENDPOINT, assertion + ) + + return + + self.token, self.expiry = _make_iam_token_request( + request=request, + principal=self._target_principal, + headers=headers, + body=body, + universe_domain=self.universe_domain, + iam_endpoint_override=self._iam_endpoint_override, + ) + + def _build_regional_access_boundary_lookup_url( + self, request: "Optional[google.auth.transport.Request]" = None # noqa: F821 + ): + """Builds and returns the URL for the Regional Access Boundary lookup API. + + This method constructs the specific URL for the IAM Credentials API's + `allowedLocations` endpoint, using the credential's universe domain + and service account email. + + Returns: + Optional[str]: The URL for the Regional Access Boundary lookup endpoint, or None + if the service account email is missing. + """ + if not self.service_account_email: + _LOGGER.error( + "Service account email is required to build the Regional Access Boundary lookup URL for impersonated credentials." + ) + return None + return iam._SERVICE_ACCOUNT_REGIONAL_ACCESS_BOUNDARY_LOOKUP_ENDPOINT.format( + service_account_email=self.service_account_email + ) + + def sign_bytes(self, message): + from google.auth.transport.requests import AuthorizedSession + + iam_sign_endpoint = iam._IAM_SIGN_ENDPOINT.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, self.universe_domain + ).format(self._target_principal) + + body = { + "payload": base64.b64encode(message).decode("utf-8"), + "delegates": self._delegates, + } + + headers = {"Content-Type": "application/json"} + + authed_session = AuthorizedSession(self._source_credentials) + + try: + retries = _exponential_backoff.ExponentialBackoff() + for _ in retries: + response = authed_session.post( + url=iam_sign_endpoint, headers=headers, json=body + ) + if response.status_code in iam.IAM_RETRY_CODES: + continue + if response.status_code != http_client.OK: + raise exceptions.TransportError( + "Error calling sign_bytes: {}".format(response.json()) + ) + + return base64.b64decode(response.json()["signedBlob"]) + finally: + authed_session.close() + raise exceptions.TransportError("exhausted signBlob endpoint retries") + + @property + def signer_email(self): + return self._target_principal + + @property + def service_account_email(self): + return self._target_principal + + @property + def signer(self): + return self + + @property + def requires_scopes(self): + return not self._target_scopes + + @_helpers.copy_docstring(credentials.Credentials) + def get_cred_info(self): + if self._cred_file_path: + return { + "credential_source": self._cred_file_path, + "credential_type": "impersonated credentials", + "principal": self._target_principal, + } + return None + + def _make_copy(self): + cred = self.__class__( + self._source_credentials, + target_principal=self._target_principal, + target_scopes=self._target_scopes, + delegates=self._delegates, + lifetime=self._lifetime, + quota_project_id=self._quota_project_id, + iam_endpoint_override=self._iam_endpoint_override, + trust_boundary=self._trust_boundary, + ) + cred._cred_file_path = self._cred_file_path + self._copy_regional_access_boundary_manager(cred) + return cred + + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + cred = self._make_copy() + cred._quota_project_id = quota_project_id + return cred + + @_helpers.copy_docstring(credentials.Scoped) + def with_scopes(self, scopes, default_scopes=None): + cred = self._make_copy() + cred._target_scopes = scopes or default_scopes + return cred + + @classmethod + def from_impersonated_service_account_info(cls, info, scopes=None): + """Creates a Credentials instance from parsed impersonated service account credentials info. + + **IMPORTANT**: + This method does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using with this method. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + + Args: + info (Mapping[str, str]): The impersonated service account credentials info in Google + format. + scopes (Sequence[str]): Optional list of scopes to include in the + credentials. + + Returns: + google.oauth2.credentials.Credentials: The constructed + credentials. + + Raises: + InvalidType: If the info["source_credentials"] are not a supported impersonation type + InvalidValue: If the info["service_account_impersonation_url"] is not in the expected format. + ValueError: If the info is not in the expected format. + """ + + source_credentials_info = info.get("source_credentials") + source_credentials_type = source_credentials_info.get("type") + if source_credentials_type == _SOURCE_CREDENTIAL_AUTHORIZED_USER_TYPE: + from google.oauth2 import credentials + + source_credentials = credentials.Credentials.from_authorized_user_info( + source_credentials_info + ) + elif source_credentials_type == _SOURCE_CREDENTIAL_SERVICE_ACCOUNT_TYPE: + from google.oauth2 import service_account + + source_credentials = service_account.Credentials.from_service_account_info( + source_credentials_info + ) + elif ( + source_credentials_type + == _SOURCE_CREDENTIAL_EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE + ): + from google.auth import external_account_authorized_user + + source_credentials = external_account_authorized_user.Credentials.from_info( + source_credentials_info + ) + else: + raise exceptions.InvalidType( + "source credential of type {} is not supported.".format( + source_credentials_type + ) + ) + + impersonation_url = info.get("service_account_impersonation_url") + start_index = impersonation_url.rfind("/") + end_index = impersonation_url.find(":generateAccessToken") + if start_index == -1 or end_index == -1 or start_index > end_index: + raise exceptions.InvalidValue( + "Cannot extract target principal from {}".format(impersonation_url) + ) + target_principal = impersonation_url[start_index + 1 : end_index] + delegates = info.get("delegates") + quota_project_id = info.get("quota_project_id") + scopes = scopes or info.get("scopes") + trust_boundary = info.get("trust_boundary") + + return cls( + source_credentials, + target_principal, + scopes, + delegates, + quota_project_id=quota_project_id, + trust_boundary=trust_boundary, + ) + + +class IDTokenCredentials(credentials.CredentialsWithQuotaProject): + """Open ID Connect ID Token-based service account credentials.""" + + def __init__( + self, + target_credentials, + target_audience=None, + include_email=False, + quota_project_id=None, + ): + """ + Args: + target_credentials (google.auth.Credentials): The target + credential used as to acquire the id tokens for. + target_audience (string): Audience to issue the token for. + include_email (bool): Include email in IdToken + quota_project_id (Optional[str]): The project ID used for + quota and billing. + """ + super(IDTokenCredentials, self).__init__() + + if not isinstance(target_credentials, Credentials): + raise exceptions.GoogleAuthError( + "Provided Credential must be " "impersonated_credentials" + ) + self._target_credentials = target_credentials + self._target_audience = target_audience + self._include_email = include_email + self._quota_project_id = quota_project_id + + def from_credentials(self, target_credentials, target_audience=None): + return self.__class__( + target_credentials=target_credentials, + target_audience=target_audience, + include_email=self._include_email, + quota_project_id=self._quota_project_id, + ) + + def with_target_audience(self, target_audience): + return self.__class__( + target_credentials=self._target_credentials, + target_audience=target_audience, + include_email=self._include_email, + quota_project_id=self._quota_project_id, + ) + + def with_include_email(self, include_email): + return self.__class__( + target_credentials=self._target_credentials, + target_audience=self._target_audience, + include_email=include_email, + quota_project_id=self._quota_project_id, + ) + + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + return self.__class__( + target_credentials=self._target_credentials, + target_audience=self._target_audience, + include_email=self._include_email, + quota_project_id=quota_project_id, + ) + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + from google.auth.transport.requests import AuthorizedSession + + iam_sign_endpoint = iam._IAM_IDTOKEN_ENDPOINT.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, + self._target_credentials.universe_domain, + ).format(self._target_credentials.signer_email) + + body = { + "audience": self._target_audience, + "delegates": self._target_credentials._delegates, + "includeEmail": self._include_email, + } + + headers = { + "Content-Type": "application/json", + metrics.API_CLIENT_HEADER: metrics.token_request_id_token_impersonate(), + } + + authed_session = AuthorizedSession( + self._target_credentials._source_credentials, auth_request=request + ) + + try: + response = authed_session.post( + url=iam_sign_endpoint, + headers=headers, + data=json.dumps(body).encode("utf-8"), + ) + finally: + authed_session.close() + + if response.status_code != http_client.OK: + raise exceptions.RefreshError( + "Error getting ID token: {}".format(response.json()) + ) + + try: + id_token = response.json()["token"] + except (KeyError, ValueError) as caught_exc: + new_exc = exceptions.RefreshError( + "No ID token in response.", response.json() + ) + raise new_exc from caught_exc + + self.token = id_token + self.expiry = _helpers.utcfromtimestamp( + jwt.decode(id_token, verify=False)["exp"] + ) + + +def _sign_jwt_request(request, principal, headers, payload, delegates=[]): + """Makes a request to the Google Cloud IAM service to sign a JWT using a + service account's system-managed private key. + Args: + request (Request): The Request object to use. + principal (str): The principal to request an access token for. + headers (Mapping[str, str]): Map of headers to transmit. + payload (Mapping[str, str]): The JWT payload to sign. Must be a + serialized JSON object that contains a JWT Claims Set. + delegates (Sequence[str]): The chained list of delegates required + to grant the final access_token. If set, the sequence of + identities must have "Service Account Token Creator" capability + granted to the prceeding identity. For example, if set to + [serviceAccountB, serviceAccountC], the source_credential + must have the Token Creator role on serviceAccountB. + serviceAccountB must have the Token Creator on + serviceAccountC. + Finally, C must have Token Creator on target_principal. + If left unset, source_credential must have that role on + target_principal. + + Raises: + google.auth.exceptions.TransportError: Raised if there is an underlying + HTTP connection error + google.auth.exceptions.RefreshError: Raised if the impersonated + credentials are not available. Common reasons are + `iamcredentials.googleapis.com` is not enabled or the + `Service Account Token Creator` is not assigned + """ + iam_endpoint = iam._IAM_SIGNJWT_ENDPOINT.format(principal) + + body = {"delegates": delegates, "payload": json.dumps(payload)} + body = json.dumps(body).encode("utf-8") + + response = request(url=iam_endpoint, method="POST", headers=headers, body=body) + + # support both string and bytes type response.data + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + if response.status != http_client.OK: + raise exceptions.RefreshError(_REFRESH_ERROR, response_body) + + try: + jwt_response = json.loads(response_body) + signed_jwt = jwt_response["signedJwt"] + return signed_jwt + + except (KeyError, ValueError) as caught_exc: + new_exc = exceptions.RefreshError( + "{}: No signed JWT in response.".format(_REFRESH_ERROR), response_body + ) + raise new_exc from caught_exc diff --git a/intelliaide/vendor/google/auth/jwt.py b/intelliaide/vendor/google/auth/jwt.py new file mode 100644 index 0000000..b6fe607 --- /dev/null +++ b/intelliaide/vendor/google/auth/jwt.py @@ -0,0 +1,877 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""JSON Web Tokens + +Provides support for creating (encoding) and verifying (decoding) JWTs, +especially JWTs generated and consumed by Google infrastructure. + +See `rfc7519`_ for more details on JWTs. + +To encode a JWT use :func:`encode`:: + + from google.auth import crypt + from google.auth import jwt + + signer = crypt.Signer(private_key) + payload = {'some': 'payload'} + encoded = jwt.encode(signer, payload) + +To decode a JWT and verify claims use :func:`decode`:: + + claims = jwt.decode(encoded, certs=public_certs) + +You can also skip verification:: + + claims = jwt.decode(encoded, verify=False) + +.. _rfc7519: https://tools.ietf.org/html/rfc7519 + +""" + +try: + from collections.abc import Mapping +# Python 2.7 compatibility +except ImportError: # pragma: NO COVER + from collections import Mapping # type: ignore +import copy +import datetime +import json +import urllib + +from google.auth import _cache +from google.auth import _helpers +from google.auth import _service_account_info +from google.auth import crypt +from google.auth import exceptions +import google.auth.credentials + +try: + from google.auth.crypt import es +except ImportError: # pragma: NO COVER + es = None # type: ignore + +_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds +_DEFAULT_MAX_CACHE_SIZE = 10 +_ALGORITHM_TO_VERIFIER_CLASS = {"RS256": crypt.RSAVerifier} +_CRYPTOGRAPHY_BASED_ALGORITHMS = frozenset(["ES256", "ES384"]) + +if es is not None: # pragma: NO COVER + _ALGORITHM_TO_VERIFIER_CLASS["ES256"] = es.EsVerifier # type: ignore + _ALGORITHM_TO_VERIFIER_CLASS["ES384"] = es.EsVerifier # type: ignore + + +def encode(signer, payload, header=None, key_id=None): + """Make a signed JWT. + + Args: + signer (google.auth.crypt.Signer): The signer used to sign the JWT. + payload (Mapping[str, str]): The JWT payload. + header (Mapping[str, str]): Additional JWT header payload. + key_id (str): The key id to add to the JWT header. If the + signer has a key id it will be used as the default. If this is + specified it will override the signer's key id. + + Returns: + bytes: The encoded JWT. + """ + if header is None: + header = {} + + if key_id is None: + key_id = signer.key_id + + header.update({"typ": "JWT"}) + + if "alg" not in header: + if es is not None and isinstance(signer, es.EsSigner): + header.update({"alg": signer.algorithm}) + else: + header.update({"alg": "RS256"}) + + if key_id is not None: + header["kid"] = key_id + + segments = [ + _helpers.unpadded_urlsafe_b64encode(json.dumps(header).encode("utf-8")), + _helpers.unpadded_urlsafe_b64encode(json.dumps(payload).encode("utf-8")), + ] + + signing_input = b".".join(segments) + signature = signer.sign(signing_input) + segments.append(_helpers.unpadded_urlsafe_b64encode(signature)) + + return b".".join(segments) + + +def _decode_jwt_segment(encoded_section): + """Decodes a single JWT segment.""" + section_bytes = _helpers.padded_urlsafe_b64decode(encoded_section) + try: + return json.loads(section_bytes.decode("utf-8")) + except ValueError as caught_exc: + new_exc = exceptions.MalformedError( + "Can't parse segment: {0}".format(section_bytes) + ) + raise new_exc from caught_exc + + +def _unverified_decode(token): + """Decodes a token and does no verification. + + Args: + token (Union[str, bytes]): The encoded JWT. + + Returns: + Tuple[Mapping, Mapping, str, str]: header, payload, signed_section, and + signature. + + Raises: + google.auth.exceptions.MalformedError: if there are an incorrect amount of segments in the token or segments of the wrong type. + """ + token = _helpers.to_bytes(token) + + if token.count(b".") != 2: + raise exceptions.MalformedError( + "Wrong number of segments in token: {0}".format(token) + ) + + encoded_header, encoded_payload, signature = token.split(b".") + signed_section = encoded_header + b"." + encoded_payload + signature = _helpers.padded_urlsafe_b64decode(signature) + + # Parse segments + header = _decode_jwt_segment(encoded_header) + payload = _decode_jwt_segment(encoded_payload) + + if not isinstance(header, Mapping): + raise exceptions.MalformedError( + "Header segment should be a JSON object: {0}".format(encoded_header) + ) + + if not isinstance(payload, Mapping): + raise exceptions.MalformedError( + "Payload segment should be a JSON object: {0}".format(encoded_payload) + ) + + return header, payload, signed_section, signature + + +def decode_header(token): + """Return the decoded header of a token. + + No verification is done. This is useful to extract the key id from + the header in order to acquire the appropriate certificate to verify + the token. + + Args: + token (Union[str, bytes]): the encoded JWT. + + Returns: + Mapping: The decoded JWT header. + """ + header, _, _, _ = _unverified_decode(token) + return header + + +def _verify_iat_and_exp(payload, clock_skew_in_seconds=0): + """Verifies the ``iat`` (Issued At) and ``exp`` (Expires) claims in a token + payload. + + Args: + payload (Mapping[str, str]): The JWT payload. + clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` + validation. + + Raises: + google.auth.exceptions.InvalidValue: if value validation failed. + google.auth.exceptions.MalformedError: if schema validation failed. + """ + now = _helpers.datetime_to_secs(_helpers.utcnow()) + + # Make sure the iat and exp claims are present. + for key in ("iat", "exp"): + if key not in payload: + raise exceptions.MalformedError( + "Token does not contain required claim {}".format(key) + ) + + # Make sure the token wasn't issued in the future. + iat = payload["iat"] + # Err on the side of accepting a token that is slightly early to account + # for clock skew. + earliest = iat - clock_skew_in_seconds + if now < earliest: + raise exceptions.InvalidValue( + "Token used too early, {} < {}. Check that your computer's clock is set correctly.".format( + now, iat + ) + ) + + # Make sure the token wasn't issued in the past. + exp = payload["exp"] + # Err on the side of accepting a token that is slightly out of date + # to account for clow skew. + latest = exp + clock_skew_in_seconds + if latest < now: + raise exceptions.InvalidValue("Token expired, {} < {}".format(latest, now)) + + +def decode(token, certs=None, verify=True, audience=None, clock_skew_in_seconds=0): + """Decode and verify a JWT. + + Args: + token (str): The encoded JWT. + certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The + certificate used to validate the JWT signature. If bytes or string, + it must the the public key certificate in PEM format. If a mapping, + it must be a mapping of key IDs to public key certificates in PEM + format. The mapping must contain the same key ID that's specified + in the token's header. + verify (bool): Whether to perform signature and claim validation. + Verification is done by default. + audience (str or list): The audience claim, 'aud', that this JWT should + contain. Or a list of audience claims. If None then the JWT's 'aud' + parameter is not verified. + clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` + validation. + + Returns: + Mapping[str, str]: The deserialized JSON payload in the JWT. + + Raises: + google.auth.exceptions.InvalidValue: if value validation failed. + google.auth.exceptions.MalformedError: if schema validation failed. + """ + header, payload, signed_section, signature = _unverified_decode(token) + + if not verify: + return payload + + # Pluck the key id and algorithm from the header and make sure we have + # a verifier that can support it. + key_alg = header.get("alg") + key_id = header.get("kid") + + try: + verifier_cls = _ALGORITHM_TO_VERIFIER_CLASS[key_alg] + except KeyError as exc: + if key_alg in _CRYPTOGRAPHY_BASED_ALGORITHMS: + raise exceptions.InvalidValue( + "The key algorithm {} requires the cryptography package to be installed.".format( + key_alg + ) + ) from exc + else: + raise exceptions.InvalidValue( + "Unsupported signature algorithm {}".format(key_alg) + ) from exc + # If certs is specified as a dictionary of key IDs to certificates, then + # use the certificate identified by the key ID in the token header. + if isinstance(certs, Mapping): + if key_id: + if key_id not in certs: + raise exceptions.MalformedError( + "Certificate for key id {} not found.".format(key_id) + ) + certs_to_check = [certs[key_id]] + # If there's no key id in the header, check against all of the certs. + else: + certs_to_check = certs.values() + else: + certs_to_check = certs + + # Verify that the signature matches the message. + if not crypt.verify_signature( + signed_section, signature, certs_to_check, verifier_cls + ): + raise exceptions.MalformedError("Could not verify token signature.") + + # Verify the issued at and created times in the payload. + _verify_iat_and_exp(payload, clock_skew_in_seconds) + + # Check audience. + if audience is not None: + claim_audience = payload.get("aud") + if isinstance(audience, str): + audience = [audience] + if claim_audience not in audience: + raise exceptions.InvalidValue( + "Token has wrong audience {}, expected one of {}".format( + claim_audience, audience + ) + ) + + return payload + + +class Credentials( + google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject +): + """Credentials that use a JWT as the bearer token. + + These credentials require an "audience" claim. This claim identifies the + intended recipient of the bearer token. + + The constructor arguments determine the claims for the JWT that is + sent with requests. Usually, you'll construct these credentials with + one of the helper constructors as shown in the next section. + + To create JWT credentials using a Google service account private key + JSON file:: + + audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher' + credentials = jwt.Credentials.from_service_account_file( + 'service-account.json', + audience=audience) + + If you already have the service account file loaded and parsed:: + + service_account_info = json.load(open('service_account.json')) + credentials = jwt.Credentials.from_service_account_info( + service_account_info, + audience=audience) + + Both helper methods pass on arguments to the constructor, so you can + specify the JWT claims:: + + credentials = jwt.Credentials.from_service_account_file( + 'service-account.json', + audience=audience, + additional_claims={'meta': 'data'}) + + You can also construct the credentials directly if you have a + :class:`~google.auth.crypt.Signer` instance:: + + credentials = jwt.Credentials( + signer, + issuer='your-issuer', + subject='your-subject', + audience=audience) + + The claims are considered immutable. If you want to modify the claims, + you can easily create another instance using :meth:`with_claims`:: + + new_audience = ( + 'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber') + new_credentials = credentials.with_claims(audience=new_audience) + """ + + def __init__( + self, + signer, + issuer, + subject, + audience, + additional_claims=None, + token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, + quota_project_id=None, + ): + """ + Args: + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + issuer (str): The `iss` claim. + subject (str): The `sub` claim. + audience (str): the `aud` claim. The intended audience for the + credentials. + additional_claims (Mapping[str, str]): Any additional claims for + the JWT payload. + token_lifetime (int): The amount of time in seconds for + which the token is valid. Defaults to 1 hour. + quota_project_id (Optional[str]): The project ID used for quota + and billing. + """ + super(Credentials, self).__init__() + self._signer = signer + self._issuer = issuer + self._subject = subject + self._audience = audience + self._token_lifetime = token_lifetime + self._quota_project_id = quota_project_id + + if additional_claims is None: + additional_claims = {} + + self._additional_claims = additional_claims + + @classmethod + def _from_signer_and_info(cls, signer, info, **kwargs): + """Creates a Credentials instance from a signer and service account + info. + + Args: + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + info (Mapping[str, str]): The service account info. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.Credentials: The constructed credentials. + + Raises: + google.auth.exceptions.MalformedError: If the info is not in the expected format. + """ + kwargs.setdefault("subject", info["client_email"]) + kwargs.setdefault("issuer", info["client_email"]) + return cls(signer, **kwargs) + + @classmethod + def from_service_account_info(cls, info, **kwargs): + """Creates an Credentials instance from a dictionary. + + Args: + info (Mapping[str, str]): The service account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.Credentials: The constructed credentials. + + Raises: + google.auth.exceptions.MalformedError: If the info is not in the expected format. + """ + signer = _service_account_info.from_dict(info, require=["client_email"]) + return cls._from_signer_and_info(signer, info, **kwargs) + + @classmethod + def from_service_account_file(cls, filename, **kwargs): + """Creates a Credentials instance from a service account .json file + in Google format. + + Args: + filename (str): The path to the service account .json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.Credentials: The constructed credentials. + """ + info, signer = _service_account_info.from_filename( + filename, require=["client_email"] + ) + return cls._from_signer_and_info(signer, info, **kwargs) + + @classmethod + def from_signing_credentials(cls, credentials, audience, **kwargs): + """Creates a new :class:`google.auth.jwt.Credentials` instance from an + existing :class:`google.auth.credentials.Signing` instance. + + The new instance will use the same signer as the existing instance and + will use the existing instance's signer email as the issuer and + subject by default. + + Example:: + + svc_creds = service_account.Credentials.from_service_account_file( + 'service_account.json') + audience = ( + 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher') + jwt_creds = jwt.Credentials.from_signing_credentials( + svc_creds, audience=audience) + + Args: + credentials (google.auth.credentials.Signing): The credentials to + use to construct the new credentials. + audience (str): the `aud` claim. The intended audience for the + credentials. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.Credentials: A new Credentials instance. + """ + kwargs.setdefault("issuer", credentials.signer_email) + kwargs.setdefault("subject", credentials.signer_email) + return cls(credentials.signer, audience=audience, **kwargs) + + def with_claims( + self, issuer=None, subject=None, audience=None, additional_claims=None + ): + """Returns a copy of these credentials with modified claims. + + Args: + issuer (str): The `iss` claim. If unspecified the current issuer + claim will be used. + subject (str): The `sub` claim. If unspecified the current subject + claim will be used. + audience (str): the `aud` claim. If unspecified the current + audience claim will be used. + additional_claims (Mapping[str, str]): Any additional claims for + the JWT payload. This will be merged with the current + additional claims. + + Returns: + google.auth.jwt.Credentials: A new credentials instance. + """ + new_additional_claims = copy.deepcopy(self._additional_claims) + new_additional_claims.update(additional_claims or {}) + + return self.__class__( + self._signer, + issuer=issuer if issuer is not None else self._issuer, + subject=subject if subject is not None else self._subject, + audience=audience if audience is not None else self._audience, + additional_claims=new_additional_claims, + quota_project_id=self._quota_project_id, + ) + + @_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + return self.__class__( + self._signer, + issuer=self._issuer, + subject=self._subject, + audience=self._audience, + additional_claims=self._additional_claims, + quota_project_id=quota_project_id, + ) + + def _make_jwt(self): + """Make a signed JWT. + + Returns: + Tuple[bytes, datetime]: The encoded JWT and the expiration. + """ + now = _helpers.utcnow() + lifetime = datetime.timedelta(seconds=self._token_lifetime) + expiry = now + lifetime + + payload = { + "iss": self._issuer, + "sub": self._subject, + "iat": _helpers.datetime_to_secs(now), + "exp": _helpers.datetime_to_secs(expiry), + } + if self._audience: + payload["aud"] = self._audience + + payload.update(self._additional_claims) + + jwt = encode(self._signer, payload) + + return jwt, expiry + + def refresh(self, request): + """Refreshes the access token. + + Args: + request (Any): Unused. + """ + # pylint: disable=unused-argument + # (pylint doesn't correctly recognize overridden methods.) + self.token, self.expiry = self._make_jwt() + + @_helpers.copy_docstring(google.auth.credentials.Signing) + def sign_bytes(self, message): + return self._signer.sign(message) + + @property # type: ignore + @_helpers.copy_docstring(google.auth.credentials.Signing) + def signer_email(self): + return self._issuer + + @property # type: ignore + @_helpers.copy_docstring(google.auth.credentials.Signing) + def signer(self): + return self._signer + + @property # type: ignore + def additional_claims(self): + """Additional claims the JWT object was created with.""" + return self._additional_claims + + +class OnDemandCredentials( + google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject +): + """On-demand JWT credentials. + + Like :class:`Credentials`, this class uses a JWT as the bearer token for + authentication. However, this class does not require the audience at + construction time. Instead, it will generate a new token on-demand for + each request using the request URI as the audience. It caches tokens + so that multiple requests to the same URI do not incur the overhead + of generating a new token every time. + + This behavior is especially useful for `gRPC`_ clients. A gRPC service may + have multiple audience and gRPC clients may not know all of the audiences + required for accessing a particular service. With these credentials, + no knowledge of the audiences is required ahead of time. + + .. _grpc: http://www.grpc.io/ + """ + + def __init__( + self, + signer, + issuer, + subject, + additional_claims=None, + token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, + max_cache_size=_DEFAULT_MAX_CACHE_SIZE, + quota_project_id=None, + ): + """ + Args: + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + issuer (str): The `iss` claim. + subject (str): The `sub` claim. + additional_claims (Mapping[str, str]): Any additional claims for + the JWT payload. + token_lifetime (int): The amount of time in seconds for + which the token is valid. Defaults to 1 hour. + max_cache_size (int): The maximum number of JWT tokens to keep in + cache. Tokens are cached using :class:`google.auth._cache.LRUCache`. + quota_project_id (Optional[str]): The project ID used for quota + and billing. + + """ + super(OnDemandCredentials, self).__init__() + self._signer = signer + self._issuer = issuer + self._subject = subject + self._token_lifetime = token_lifetime + self._quota_project_id = quota_project_id + + if additional_claims is None: + additional_claims = {} + + self._additional_claims = additional_claims + self._cache = _cache.LRUCache(maxsize=max_cache_size) + + @classmethod + def _from_signer_and_info(cls, signer, info, **kwargs): + """Creates an OnDemandCredentials instance from a signer and service + account info. + + Args: + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + info (Mapping[str, str]): The service account info. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.OnDemandCredentials: The constructed credentials. + + Raises: + google.auth.exceptions.MalformedError: If the info is not in the expected format. + """ + kwargs.setdefault("subject", info["client_email"]) + kwargs.setdefault("issuer", info["client_email"]) + return cls(signer, **kwargs) + + @classmethod + def from_service_account_info(cls, info, **kwargs): + """Creates an OnDemandCredentials instance from a dictionary. + + Args: + info (Mapping[str, str]): The service account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.OnDemandCredentials: The constructed credentials. + + Raises: + google.auth.exceptions.MalformedError: If the info is not in the expected format. + """ + signer = _service_account_info.from_dict(info, require=["client_email"]) + return cls._from_signer_and_info(signer, info, **kwargs) + + @classmethod + def from_service_account_file(cls, filename, **kwargs): + """Creates an OnDemandCredentials instance from a service account .json + file in Google format. + + Args: + filename (str): The path to the service account .json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.OnDemandCredentials: The constructed credentials. + """ + info, signer = _service_account_info.from_filename( + filename, require=["client_email"] + ) + return cls._from_signer_and_info(signer, info, **kwargs) + + @classmethod + def from_signing_credentials(cls, credentials, **kwargs): + """Creates a new :class:`google.auth.jwt.OnDemandCredentials` instance + from an existing :class:`google.auth.credentials.Signing` instance. + + The new instance will use the same signer as the existing instance and + will use the existing instance's signer email as the issuer and + subject by default. + + Example:: + + svc_creds = service_account.Credentials.from_service_account_file( + 'service_account.json') + jwt_creds = jwt.OnDemandCredentials.from_signing_credentials( + svc_creds) + + Args: + credentials (google.auth.credentials.Signing): The credentials to + use to construct the new credentials. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.Credentials: A new Credentials instance. + """ + kwargs.setdefault("issuer", credentials.signer_email) + kwargs.setdefault("subject", credentials.signer_email) + return cls(credentials.signer, **kwargs) + + def with_claims(self, issuer=None, subject=None, additional_claims=None): + """Returns a copy of these credentials with modified claims. + + Args: + issuer (str): The `iss` claim. If unspecified the current issuer + claim will be used. + subject (str): The `sub` claim. If unspecified the current subject + claim will be used. + additional_claims (Mapping[str, str]): Any additional claims for + the JWT payload. This will be merged with the current + additional claims. + + Returns: + google.auth.jwt.OnDemandCredentials: A new credentials instance. + """ + new_additional_claims = copy.deepcopy(self._additional_claims) + new_additional_claims.update(additional_claims or {}) + + return self.__class__( + self._signer, + issuer=issuer if issuer is not None else self._issuer, + subject=subject if subject is not None else self._subject, + additional_claims=new_additional_claims, + max_cache_size=self._cache.maxsize, + quota_project_id=self._quota_project_id, + ) + + @_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + return self.__class__( + self._signer, + issuer=self._issuer, + subject=self._subject, + additional_claims=self._additional_claims, + max_cache_size=self._cache.maxsize, + quota_project_id=quota_project_id, + ) + + @property + def valid(self): + """Checks the validity of the credentials. + + These credentials are always valid because it generates tokens on + demand. + """ + return True + + def _make_jwt_for_audience(self, audience): + """Make a new JWT for the given audience. + + Args: + audience (str): The intended audience. + + Returns: + Tuple[bytes, datetime]: The encoded JWT and the expiration. + """ + now = _helpers.utcnow() + lifetime = datetime.timedelta(seconds=self._token_lifetime) + expiry = now + lifetime + + payload = { + "iss": self._issuer, + "sub": self._subject, + "iat": _helpers.datetime_to_secs(now), + "exp": _helpers.datetime_to_secs(expiry), + "aud": audience, + } + + payload.update(self._additional_claims) + + jwt = encode(self._signer, payload) + + return jwt, expiry + + def _get_jwt_for_audience(self, audience): + """Get a JWT For a given audience. + + If there is already an existing, non-expired token in the cache for + the audience, that token is used. Otherwise, a new token will be + created. + + Args: + audience (str): The intended audience. + + Returns: + bytes: The encoded JWT. + """ + token, expiry = self._cache.get(audience, (None, None)) + + if token is None or expiry < _helpers.utcnow(): + token, expiry = self._make_jwt_for_audience(audience) + self._cache[audience] = token, expiry + + return token + + def refresh(self, request): + """Raises an exception, these credentials can not be directly + refreshed. + + Args: + request (Any): Unused. + + Raises: + google.auth.RefreshError + """ + # pylint: disable=unused-argument + # (pylint doesn't correctly recognize overridden methods.) + raise exceptions.RefreshError( + "OnDemandCredentials can not be directly refreshed." + ) + + def before_request(self, request, method, url, headers): + """Performs credential-specific before request logic. + + Args: + request (Any): Unused. JWT credentials do not need to make an + HTTP request to refresh. + method (str): The request's HTTP method. + url (str): The request's URI. This is used as the audience claim + when generating the JWT. + headers (Mapping): The request's headers. + """ + # pylint: disable=unused-argument + # (pylint doesn't correctly recognize overridden methods.) + parts = urllib.parse.urlsplit(url) + # Strip query string and fragment + audience = urllib.parse.urlunsplit( + (parts.scheme, parts.netloc, parts.path, "", "") + ) + token = self._get_jwt_for_audience(audience) + self.apply(headers, token=token) + + @_helpers.copy_docstring(google.auth.credentials.Signing) + def sign_bytes(self, message): + return self._signer.sign(message) + + @property # type: ignore + @_helpers.copy_docstring(google.auth.credentials.Signing) + def signer_email(self): + return self._issuer + + @property # type: ignore + @_helpers.copy_docstring(google.auth.credentials.Signing) + def signer(self): + return self._signer diff --git a/intelliaide/vendor/google/auth/metrics.py b/intelliaide/vendor/google/auth/metrics.py new file mode 100644 index 0000000..5511f58 --- /dev/null +++ b/intelliaide/vendor/google/auth/metrics.py @@ -0,0 +1,156 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" We use x-goog-api-client header to report metrics. This module provides +the constants and helper methods to construct x-goog-api-client header. +""" + +import platform + +from google.auth import version + + +API_CLIENT_HEADER = "x-goog-api-client" + +# BYOID Specific consts +BYOID_HEADER_SECTION = "google-byoid-sdk" + +# Auth request type +REQUEST_TYPE_ACCESS_TOKEN = "auth-request-type/at" +REQUEST_TYPE_ID_TOKEN = "auth-request-type/it" +REQUEST_TYPE_MDS_PING = "auth-request-type/mds" +REQUEST_TYPE_REAUTH_START = "auth-request-type/re-start" +REQUEST_TYPE_REAUTH_CONTINUE = "auth-request-type/re-cont" + +# Credential type +CRED_TYPE_USER = "cred-type/u" +CRED_TYPE_SA_ASSERTION = "cred-type/sa" +CRED_TYPE_SA_JWT = "cred-type/jwt" +CRED_TYPE_SA_MDS = "cred-type/mds" +CRED_TYPE_SA_IMPERSONATE = "cred-type/imp" + + +# Versions +def python_and_auth_lib_version(): + return "gl-python/{} auth/{}".format(platform.python_version(), version.__version__) + + +# Token request metric header values + + +# x-goog-api-client header value for access token request via metadata server. +# Example: "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/mds" +def token_request_access_token_mds(): + return "{} {} {}".format( + python_and_auth_lib_version(), REQUEST_TYPE_ACCESS_TOKEN, CRED_TYPE_SA_MDS + ) + + +# x-goog-api-client header value for ID token request via metadata server. +# Example: "gl-python/3.7 auth/1.1 auth-request-type/it cred-type/mds" +def token_request_id_token_mds(): + return "{} {} {}".format( + python_and_auth_lib_version(), REQUEST_TYPE_ID_TOKEN, CRED_TYPE_SA_MDS + ) + + +# x-goog-api-client header value for impersonated credentials access token request. +# Example: "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/imp" +def token_request_access_token_impersonate(): + return "{} {} {}".format( + python_and_auth_lib_version(), + REQUEST_TYPE_ACCESS_TOKEN, + CRED_TYPE_SA_IMPERSONATE, + ) + + +# x-goog-api-client header value for impersonated credentials ID token request. +# Example: "gl-python/3.7 auth/1.1 auth-request-type/it cred-type/imp" +def token_request_id_token_impersonate(): + return "{} {} {}".format( + python_and_auth_lib_version(), REQUEST_TYPE_ID_TOKEN, CRED_TYPE_SA_IMPERSONATE + ) + + +# x-goog-api-client header value for service account credentials access token +# request (assertion flow). +# Example: "gl-python/3.7 auth/1.1 auth-request-type/at cred-type/sa" +def token_request_access_token_sa_assertion(): + return "{} {} {}".format( + python_and_auth_lib_version(), REQUEST_TYPE_ACCESS_TOKEN, CRED_TYPE_SA_ASSERTION + ) + + +# x-goog-api-client header value for service account credentials ID token +# request (assertion flow). +# Example: "gl-python/3.7 auth/1.1 auth-request-type/it cred-type/sa" +def token_request_id_token_sa_assertion(): + return "{} {} {}".format( + python_and_auth_lib_version(), REQUEST_TYPE_ID_TOKEN, CRED_TYPE_SA_ASSERTION + ) + + +# x-goog-api-client header value for user credentials token request. +# Example: "gl-python/3.7 auth/1.1 cred-type/u" +def token_request_user(): + return "{} {}".format(python_and_auth_lib_version(), CRED_TYPE_USER) + + +# Miscellenous metrics + + +# x-goog-api-client header value for metadata server ping. +# Example: "gl-python/3.7 auth/1.1 auth-request-type/mds" +def mds_ping(): + return "{} {}".format(python_and_auth_lib_version(), REQUEST_TYPE_MDS_PING) + + +# x-goog-api-client header value for reauth start endpoint calls. +# Example: "gl-python/3.7 auth/1.1 auth-request-type/re-start" +def reauth_start(): + return "{} {}".format(python_and_auth_lib_version(), REQUEST_TYPE_REAUTH_START) + + +# x-goog-api-client header value for reauth continue endpoint calls. +# Example: "gl-python/3.7 auth/1.1 cred-type/re-cont" +def reauth_continue(): + return "{} {}".format(python_and_auth_lib_version(), REQUEST_TYPE_REAUTH_CONTINUE) + + +# x-goog-api-client header value for BYOID calls to the Security Token Service exchange token endpoint. +# Example: "gl-python/3.7 auth/1.1 google-byoid-sdk source/aws sa-impersonation/true sa-impersonation/true" +def byoid_metrics_header(metrics_options): + header = "{} {}".format(python_and_auth_lib_version(), BYOID_HEADER_SECTION) + for key, value in metrics_options.items(): + header = "{} {}/{}".format(header, key, value) + return header + + +def add_metric_header(headers, metric_header_value): + """Add x-goog-api-client header with the given value. + + Args: + headers (Mapping[str, str]): The headers to which we will add the + metric header. + metric_header_value (Optional[str]): If value is None, do nothing; + if headers already has a x-goog-api-client header, append the value + to the existing header; otherwise add a new x-goog-api-client + header with the given value. + """ + if not metric_header_value: + return + if API_CLIENT_HEADER not in headers: + headers[API_CLIENT_HEADER] = metric_header_value + else: + headers[API_CLIENT_HEADER] += " " + metric_header_value diff --git a/intelliaide/vendor/google/auth/pluggable.py b/intelliaide/vendor/google/auth/pluggable.py new file mode 100644 index 0000000..b7d832d --- /dev/null +++ b/intelliaide/vendor/google/auth/pluggable.py @@ -0,0 +1,445 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Pluggable Credentials. +Pluggable Credentials are initialized using external_account arguments which +are typically loaded from third-party executables. Unlike other +credentials that can be initialized with a list of explicit arguments, secrets +or credentials, external account clients use the environment and hints/guidelines +provided by the external_account JSON file to retrieve credentials and exchange +them for Google access tokens. + +Example credential_source for pluggable credential: +{ + "executable": { + "command": "/path/to/get/credentials.sh --arg1=value1 --arg2=value2", + "timeout_millis": 5000, + "output_file": "/path/to/generated/cached/credentials" + } +} +""" + +try: + from collections.abc import Mapping +# Python 2.7 compatibility +except ImportError: # pragma: NO COVER + from collections import Mapping # type: ignore +import json +import os +import shlex +import subprocess +import sys +import time + +from google.auth import _helpers +from google.auth import exceptions +from google.auth import external_account + +# The max supported executable spec version. +EXECUTABLE_SUPPORTED_MAX_VERSION = 1 + +EXECUTABLE_TIMEOUT_MILLIS_DEFAULT = 30 * 1000 # 30 seconds +EXECUTABLE_TIMEOUT_MILLIS_LOWER_BOUND = 5 * 1000 # 5 seconds +EXECUTABLE_TIMEOUT_MILLIS_UPPER_BOUND = 120 * 1000 # 2 minutes + +EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_LOWER_BOUND = 30 * 1000 # 30 seconds +EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_UPPER_BOUND = 30 * 60 * 1000 # 30 minutes + + +class Credentials(external_account.Credentials): + """External account credentials sourced from executables. + + **IMPORTANT**: + This class does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + """ + + def __init__( + self, + audience, + subject_token_type, + token_url, + credential_source, + *args, + **kwargs + ): + """Instantiates an external account credentials object from a executables. + + Args: + audience (str): The STS audience field. + subject_token_type (str): The subject token type. + token_url (str): The STS endpoint URL. + credential_source (Mapping): The credential source dictionary used to + provide instructions on how to retrieve external credential to be + exchanged for Google access tokens. + + Example credential_source for pluggable credential: + + { + "executable": { + "command": "/path/to/get/credentials.sh --arg1=value1 --arg2=value2", + "timeout_millis": 5000, + "output_file": "/path/to/generated/cached/credentials" + } + } + args (List): Optional positional arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. + kwargs (Mapping): Optional keyword arguments passed into the underlying :meth:`~external_account.Credentials.__init__` method. + + Raises: + google.auth.exceptions.RefreshError: If an error is encountered during + access token retrieval logic. + google.auth.exceptions.InvalidValue: For invalid parameters. + google.auth.exceptions.MalformedError: For invalid parameters. + + .. note:: Typically one of the helper constructors + :meth:`from_file` or + :meth:`from_info` are used instead of calling the constructor directly. + """ + + self.interactive = kwargs.pop("interactive", False) + super(Credentials, self).__init__( + audience=audience, + subject_token_type=subject_token_type, + token_url=token_url, + credential_source=credential_source, + *args, + **kwargs + ) + if not isinstance(credential_source, Mapping): + self._credential_source_executable = None + raise exceptions.MalformedError( + "Missing credential_source. The credential_source is not a dict." + ) + self._credential_source_executable = credential_source.get("executable") + if not self._credential_source_executable: + raise exceptions.MalformedError( + "Missing credential_source. An 'executable' must be provided." + ) + self._credential_source_executable_command = ( + self._credential_source_executable.get("command") + ) + self._credential_source_executable_timeout_millis = ( + self._credential_source_executable.get("timeout_millis") + ) + self._credential_source_executable_interactive_timeout_millis = ( + self._credential_source_executable.get("interactive_timeout_millis") + ) + self._credential_source_executable_output_file = ( + self._credential_source_executable.get("output_file") + ) + + # Dummy value. This variable is only used via injection, not exposed to ctor + self._tokeninfo_username = "" + + if not self._credential_source_executable_command: + raise exceptions.MalformedError( + "Missing command field. Executable command must be provided." + ) + if not self._credential_source_executable_timeout_millis: + self._credential_source_executable_timeout_millis = ( + EXECUTABLE_TIMEOUT_MILLIS_DEFAULT + ) + elif ( + self._credential_source_executable_timeout_millis + < EXECUTABLE_TIMEOUT_MILLIS_LOWER_BOUND + or self._credential_source_executable_timeout_millis + > EXECUTABLE_TIMEOUT_MILLIS_UPPER_BOUND + ): + raise exceptions.InvalidValue("Timeout must be between 5 and 120 seconds.") + + if self._credential_source_executable_interactive_timeout_millis: + if ( + self._credential_source_executable_interactive_timeout_millis + < EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_LOWER_BOUND + or self._credential_source_executable_interactive_timeout_millis + > EXECUTABLE_INTERACTIVE_TIMEOUT_MILLIS_UPPER_BOUND + ): + raise exceptions.InvalidValue( + "Interactive timeout must be between 30 seconds and 30 minutes." + ) + + @_helpers.copy_docstring(external_account.Credentials) + def retrieve_subject_token(self, request): + self._validate_running_mode() + + # Check output file. + if self._credential_source_executable_output_file is not None: + try: + with open( + self._credential_source_executable_output_file, encoding="utf-8" + ) as output_file: + response = json.load(output_file) + except Exception: + pass + else: + try: + # If the cached response is expired, _parse_subject_token will raise an error which will be ignored and we will call the executable again. + subject_token = self._parse_subject_token(response) + if ( + "expiration_time" not in response + ): # Always treat missing expiration_time as expired and proceed to executable run. + raise exceptions.RefreshError + except (exceptions.MalformedError, exceptions.InvalidValue): + raise + except exceptions.RefreshError: + pass + else: + return subject_token + + # Inject env vars. + env = os.environ.copy() + self._inject_env_variables(env) + env["GOOGLE_EXTERNAL_ACCOUNT_REVOKE"] = "0" + + # Run executable. + exe_timeout = ( + self._credential_source_executable_interactive_timeout_millis / 1000 + if self.interactive + else self._credential_source_executable_timeout_millis / 1000 + ) + exe_stdin = sys.stdin if self.interactive else None + exe_stdout = sys.stdout if self.interactive else subprocess.PIPE + exe_stderr = sys.stdout if self.interactive else subprocess.STDOUT + + result = subprocess.run( + shlex.split(self._credential_source_executable_command), + timeout=exe_timeout, + stdin=exe_stdin, + stdout=exe_stdout, + stderr=exe_stderr, + env=env, + ) + if result.returncode != 0: + raise exceptions.RefreshError( + "Executable exited with non-zero return code {}. Error: {}".format( + result.returncode, result.stdout + ) + ) + + # Handle executable output. + response = json.loads(result.stdout.decode("utf-8")) if result.stdout else None + if not response and self._credential_source_executable_output_file is not None: + response = json.load( + open(self._credential_source_executable_output_file, encoding="utf-8") + ) + + subject_token = self._parse_subject_token(response) + return subject_token + + def revoke(self, request): + """Revokes the subject token using the credential_source object. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + Raises: + google.auth.exceptions.RefreshError: If the executable revocation + not properly executed. + + """ + if not self.interactive: + raise exceptions.InvalidValue( + "Revoke is only enabled under interactive mode." + ) + self._validate_running_mode() + + # Inject variables + env = os.environ.copy() + self._inject_env_variables(env) + env["GOOGLE_EXTERNAL_ACCOUNT_REVOKE"] = "1" + + # Run executable + result = subprocess.run( + shlex.split(self._credential_source_executable_command), + timeout=self._credential_source_executable_interactive_timeout_millis + / 1000, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=env, + ) + + if result.returncode != 0: + raise exceptions.RefreshError( + "Auth revoke failed on executable. Exit with non-zero return code {}. Error: {}".format( + result.returncode, result.stdout + ) + ) + + response = json.loads(result.stdout.decode("utf-8")) + self._validate_revoke_response(response) + + @property + def external_account_id(self): + """Returns the external account identifier. + + When service account impersonation is used the identifier is the service + account email. + + Without service account impersonation, this returns None, unless it is + being used by the Google Cloud CLI which populates this field. + """ + + return self.service_account_email or self._tokeninfo_username + + @classmethod + def from_info(cls, info, **kwargs): + """Creates a Pluggable Credentials instance from parsed external account info. + + **IMPORTANT**: + This method does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using with this method. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + + Args: + info (Mapping[str, str]): The Pluggable external account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.pluggable.Credentials: The constructed + credentials. + + Raises: + google.auth.exceptions.InvalidValue: For invalid parameters. + google.auth.exceptions.MalformedError: For invalid parameters. + """ + return super(Credentials, cls).from_info(info, **kwargs) + + @classmethod + def from_file(cls, filename, **kwargs): + """Creates an Pluggable Credentials instance from an external account json file. + + **IMPORTANT**: + This method does not validate the credential configuration. A security + risk occurs when a credential configuration configured with malicious urls + is used. + When the credential configuration is accepted from an + untrusted source, you should validate it before using with this method. + Refer https://cloud.google.com/docs/authentication/external/externally-sourced-credentials for more details. + + Args: + filename (str): The path to the Pluggable external account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.pluggable.Credentials: The constructed + credentials. + """ + return super(Credentials, cls).from_file(filename, **kwargs) + + def _inject_env_variables(self, env): + env["GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE"] = self._audience + env["GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE"] = self._subject_token_type + env["GOOGLE_EXTERNAL_ACCOUNT_ID"] = self.external_account_id + env["GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE"] = "1" if self.interactive else "0" + + if self._service_account_impersonation_url is not None: + env[ + "GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL" + ] = self.service_account_email + if self._credential_source_executable_output_file is not None: + env[ + "GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE" + ] = self._credential_source_executable_output_file + + def _parse_subject_token(self, response): + self._validate_response_schema(response) + if not response["success"]: + if "code" not in response or "message" not in response: + raise exceptions.MalformedError( + "Error code and message fields are required in the response." + ) + raise exceptions.RefreshError( + "Executable returned unsuccessful response: code: {}, message: {}.".format( + response["code"], response["message"] + ) + ) + if "expiration_time" in response and response["expiration_time"] < time.time(): + raise exceptions.RefreshError( + "The token returned by the executable is expired." + ) + if "token_type" not in response: + raise exceptions.MalformedError( + "The executable response is missing the token_type field." + ) + if ( + response["token_type"] == "urn:ietf:params:oauth:token-type:jwt" + or response["token_type"] == "urn:ietf:params:oauth:token-type:id_token" + ): # OIDC + return response["id_token"] + elif response["token_type"] == "urn:ietf:params:oauth:token-type:saml2": # SAML + return response["saml_response"] + else: + raise exceptions.RefreshError("Executable returned unsupported token type.") + + def _validate_revoke_response(self, response): + self._validate_response_schema(response) + if not response["success"]: + raise exceptions.RefreshError("Revoke failed with unsuccessful response.") + + def _validate_response_schema(self, response): + if "version" not in response: + raise exceptions.MalformedError( + "The executable response is missing the version field." + ) + if response["version"] > EXECUTABLE_SUPPORTED_MAX_VERSION: + raise exceptions.RefreshError( + "Executable returned unsupported version {}.".format( + response["version"] + ) + ) + + if "success" not in response: + raise exceptions.MalformedError( + "The executable response is missing the success field." + ) + + def _validate_running_mode(self): + env_allow_executables = os.environ.get( + "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES" + ) + if env_allow_executables != "1": + raise exceptions.MalformedError( + "Executables need to be explicitly allowed (set GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES to '1') to run." + ) + + if self.interactive and not self._credential_source_executable_output_file: + raise exceptions.MalformedError( + "An output_file must be specified in the credential configuration for interactive mode." + ) + + if ( + self.interactive + and not self._credential_source_executable_interactive_timeout_millis + ): + raise exceptions.InvalidOperation( + "Interactive mode cannot run without an interactive timeout." + ) + + if self.interactive and not self.is_workforce_pool: + raise exceptions.InvalidValue( + "Interactive mode is only enabled for workforce pool." + ) + + def _create_default_metrics_options(self): + metrics_options = super(Credentials, self)._create_default_metrics_options() + metrics_options["source"] = "executable" + return metrics_options diff --git a/intelliaide/vendor/google/auth/py.typed b/intelliaide/vendor/google/auth/py.typed new file mode 100644 index 0000000..aa7b689 --- /dev/null +++ b/intelliaide/vendor/google/auth/py.typed @@ -0,0 +1,2 @@ +# Marker file for PEP 561. +# The google-auth package uses inline types. diff --git a/intelliaide/vendor/google/auth/transport/__init__.py b/intelliaide/vendor/google/auth/transport/__init__.py new file mode 100644 index 0000000..4575763 --- /dev/null +++ b/intelliaide/vendor/google/auth/transport/__init__.py @@ -0,0 +1,104 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Transport - HTTP client library support. + +:mod:`google.auth` is designed to work with various HTTP client libraries such +as urllib3 and requests. In order to work across these libraries with different +interfaces some abstraction is needed. + +This module provides two interfaces that are implemented by transport adapters +to support HTTP libraries. :class:`Request` defines the interface expected by +:mod:`google.auth` to make requests. :class:`Response` defines the interface +for the return value of :class:`Request`. +""" + +import abc +import http.client as http_client + +DEFAULT_RETRYABLE_STATUS_CODES = ( + http_client.INTERNAL_SERVER_ERROR, + http_client.SERVICE_UNAVAILABLE, + http_client.GATEWAY_TIMEOUT, + http_client.REQUEST_TIMEOUT, + http_client.TOO_MANY_REQUESTS, +) +"""Sequence[int]: HTTP status codes indicating a request can be retried. +""" + + +DEFAULT_REFRESH_STATUS_CODES = (http_client.UNAUTHORIZED,) +"""Sequence[int]: Which HTTP status code indicate that credentials should be +refreshed. +""" + +DEFAULT_MAX_REFRESH_ATTEMPTS = 2 +"""int: How many times to refresh the credentials and retry a request.""" + + +class Response(metaclass=abc.ABCMeta): + """HTTP Response data.""" + + @abc.abstractproperty + def status(self): + """int: The HTTP status code.""" + raise NotImplementedError("status must be implemented.") + + @abc.abstractproperty + def headers(self): + """Mapping[str, str]: The HTTP response headers.""" + raise NotImplementedError("headers must be implemented.") + + @abc.abstractproperty + def data(self): + """bytes: The response body.""" + raise NotImplementedError("data must be implemented.") + + +class Request(metaclass=abc.ABCMeta): + """Interface for a callable that makes HTTP requests. + + Specific transport implementations should provide an implementation of + this that adapts their specific request / response API. + + .. automethod:: __call__ + """ + + @abc.abstractmethod + def __call__( + self, url, method="GET", body=None, headers=None, timeout=None, **kwargs + ): + """Make an HTTP request. + + Args: + url (str): The URI to be requested. + method (str): The HTTP method to use for the request. Defaults + to 'GET'. + body (bytes): The payload / body in HTTP request. + headers (Mapping[str, str]): Request headers. + timeout (Optional[int]): The number of seconds to wait for a + response from the server. If not specified or if None, the + transport-specific default timeout will be used. + kwargs: Additionally arguments passed on to the transport's + request method. + + Returns: + Response: The HTTP response. + + Raises: + google.auth.exceptions.TransportError: If any exception occurred. + """ + # pylint: disable=redundant-returns-doc, missing-raises-doc + # (pylint doesn't play well with abstract docstrings.) + raise NotImplementedError("__call__ must be implemented.") diff --git a/intelliaide/vendor/google/auth/transport/_aiohttp_requests.py b/intelliaide/vendor/google/auth/transport/_aiohttp_requests.py new file mode 100644 index 0000000..e832196 --- /dev/null +++ b/intelliaide/vendor/google/auth/transport/_aiohttp_requests.py @@ -0,0 +1,396 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Transport adapter for Async HTTP (aiohttp). + +NOTE: This async support is experimental and marked internal. This surface may +change in minor releases. +""" + +from __future__ import absolute_import + +import asyncio +import functools +import logging + +import aiohttp # type: ignore +import urllib3 # type: ignore + +from google.auth import _helpers +from google.auth import exceptions +from google.auth import transport +from google.auth.aio import _helpers as _helpers_async +from google.auth.transport import requests + + +_LOGGER = logging.getLogger(__name__) + +# Timeout can be re-defined depending on async requirement. Currently made 60s more than +# sync timeout. +_DEFAULT_TIMEOUT = 180 # in seconds + + +class _CombinedResponse(transport.Response): + """ + In order to more closely resemble the `requests` interface, where a raw + and deflated content could be accessed at once, this class lazily reads the + stream in `transport.Response` so both return forms can be used. + + The gzip and deflate transfer-encodings are automatically decoded for you + because the default parameter for autodecompress into the ClientSession is set + to False, and therefore we add this class to act as a wrapper for a user to be + able to access both the raw and decoded response bodies - mirroring the sync + implementation. + """ + + def __init__(self, response): + self._response = response + self._raw_content = None + + def _is_compressed(self): + headers = self._response.headers + return "Content-Encoding" in headers and ( + headers["Content-Encoding"] == "gzip" + or headers["Content-Encoding"] == "deflate" + ) + + @property + def status(self): + return self._response.status + + @property + def headers(self): + return self._response.headers + + @property + def data(self): + return self._response.content + + async def raw_content(self): + if self._raw_content is None: + self._raw_content = await self._response.content.read() + return self._raw_content + + async def content(self): + # Load raw_content if necessary + await self.raw_content() + if self._is_compressed(): + decoder = urllib3.response.MultiDecoder( + self._response.headers["Content-Encoding"] + ) + decompressed = decoder.decompress(self._raw_content) + return decompressed + + return self._raw_content + + +class _Response(transport.Response): + """ + Requests transport response adapter. + + Args: + response (requests.Response): The raw Requests response. + """ + + def __init__(self, response): + self._response = response + + @property + def status(self): + return self._response.status + + @property + def headers(self): + return self._response.headers + + @property + def data(self): + return self._response.content + + +class Request(transport.Request): + """Requests request adapter. + + This class is used internally for making requests using asyncio transports + in a consistent way. If you use :class:`AuthorizedSession` you do not need + to construct or use this class directly. + + This class can be useful if you want to manually refresh a + :class:`~google.auth.credentials.Credentials` instance:: + + import google.auth.transport.aiohttp_requests + + request = google.auth.transport.aiohttp_requests.Request() + + credentials.refresh(request) + + Args: + session (aiohttp.ClientSession): An instance :class:`aiohttp.ClientSession` used + to make HTTP requests. If not specified, a session will be created. + + .. automethod:: __call__ + """ + + def __init__(self, session=None): + # TODO: Use auto_decompress property for aiohttp 3.7+ + if session is not None and session._auto_decompress: + raise exceptions.InvalidOperation( + "Client sessions with auto_decompress=True are not supported." + ) + self.session = session + + async def __call__( + self, + url, + method="GET", + body=None, + headers=None, + timeout=_DEFAULT_TIMEOUT, + **kwargs, + ): + """ + Make an HTTP request using aiohttp. + + Args: + url (str): The URL to be requested. + method (Optional[str]): + The HTTP method to use for the request. Defaults to 'GET'. + body (Optional[bytes]): + The payload or body in HTTP request. + headers (Optional[Mapping[str, str]]): + Request headers. + timeout (Optional[int]): The number of seconds to wait for a + response from the server. If not specified or if None, the + requests default timeout will be used. + kwargs: Additional arguments passed through to the underlying + requests :meth:`requests.Session.request` method. + + Returns: + google.auth.transport.Response: The HTTP response. + + Raises: + google.auth.exceptions.TransportError: If any exception occurred. + """ + + try: + if self.session is None: # pragma: NO COVER + self.session = aiohttp.ClientSession( + auto_decompress=False + ) # pragma: NO COVER + _helpers.request_log(_LOGGER, method, url, body, headers) + response = await self.session.request( + method, url, data=body, headers=headers, timeout=timeout, **kwargs + ) + await _helpers_async.response_log_async(_LOGGER, response) + return _CombinedResponse(response) + + except aiohttp.ClientError as caught_exc: + new_exc = exceptions.TransportError(caught_exc) + raise new_exc from caught_exc + + except asyncio.TimeoutError as caught_exc: + new_exc = exceptions.TransportError(caught_exc) + raise new_exc from caught_exc + + +class AuthorizedSession(aiohttp.ClientSession): + """This is an async implementation of the Authorized Session class. We utilize an + aiohttp transport instance, and the interface mirrors the google.auth.transport.requests + Authorized Session class, except for the change in the transport used in the async use case. + + A Requests Session class with credentials. + + This class is used to perform requests to API endpoints that require + authorization:: + + from google.auth.transport import aiohttp_requests + + async with aiohttp_requests.AuthorizedSession(credentials) as authed_session: + response = await authed_session.request( + 'GET', 'https://www.googleapis.com/storage/v1/b') + + The underlying :meth:`request` implementation handles adding the + credentials' headers to the request and refreshing credentials as needed. + + Args: + credentials (google.auth._credentials_async.Credentials): + The credentials to add to the request. + refresh_status_codes (Sequence[int]): Which HTTP status codes indicate + that credentials should be refreshed and the request should be + retried. + max_refresh_attempts (int): The maximum number of times to attempt to + refresh the credentials and retry the request. + refresh_timeout (Optional[int]): The timeout value in seconds for + credential refresh HTTP requests. + auth_request (google.auth.transport.aiohttp_requests.Request): + (Optional) An instance of + :class:`~google.auth.transport.aiohttp_requests.Request` used when + refreshing credentials. If not passed, + an instance of :class:`~google.auth.transport.aiohttp_requests.Request` + is created. + kwargs: Additional arguments passed through to the underlying + ClientSession :meth:`aiohttp.ClientSession` object. + """ + + def __init__( + self, + credentials, + refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES, + max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS, + refresh_timeout=None, + auth_request=None, + auto_decompress=False, + **kwargs, + ): + super(AuthorizedSession, self).__init__(**kwargs) + self.credentials = credentials + self._refresh_status_codes = refresh_status_codes + self._max_refresh_attempts = max_refresh_attempts + self._refresh_timeout = refresh_timeout + self._is_mtls = False + self._auth_request = auth_request + self._auth_request_session = None + self._loop = asyncio.get_event_loop() + self._refresh_lock = asyncio.Lock() + self._auto_decompress = auto_decompress + + async def request( + self, + method, + url, + data=None, + headers=None, + max_allowed_time=None, + timeout=_DEFAULT_TIMEOUT, + auto_decompress=False, + **kwargs, + ): + """Implementation of Authorized Session aiohttp request. + + Args: + method (str): + The http request method used (e.g. GET, PUT, DELETE) + url (str): + The url at which the http request is sent. + data (Optional[dict]): Dictionary, list of tuples, bytes, or file-like + object to send in the body of the Request. + headers (Optional[dict]): Dictionary of HTTP Headers to send with the + Request. + timeout (Optional[Union[float, aiohttp.ClientTimeout]]): + The amount of time in seconds to wait for the server response + with each individual request. Can also be passed as an + ``aiohttp.ClientTimeout`` object. + max_allowed_time (Optional[float]): + If the method runs longer than this, a ``Timeout`` exception is + automatically raised. Unlike the ``timeout`` parameter, this + value applies to the total method execution time, even if + multiple requests are made under the hood. + + Mind that it is not guaranteed that the timeout error is raised + at ``max_allowed_time``. It might take longer, for example, if + an underlying request takes a lot of time, but the request + itself does not timeout, e.g. if a large file is being + transmitted. The timeout error will be raised after such + request completes. + """ + # Headers come in as bytes which isn't expected behavior, the resumable + # media libraries in some cases expect a str type for the header values, + # but sometimes the operations return these in bytes types. + if headers: + for key in headers.keys(): + if type(headers[key]) is bytes: + headers[key] = headers[key].decode("utf-8") + + async with aiohttp.ClientSession( + auto_decompress=self._auto_decompress, + trust_env=kwargs.get("trust_env", False), + ) as self._auth_request_session: + auth_request = Request(self._auth_request_session) + self._auth_request = auth_request + + # Use a kwarg for this instead of an attribute to maintain + # thread-safety. + _credential_refresh_attempt = kwargs.pop("_credential_refresh_attempt", 0) + # Make a copy of the headers. They will be modified by the credentials + # and we want to pass the original headers if we recurse. + request_headers = headers.copy() if headers is not None else {} + + # Do not apply the timeout unconditionally in order to not override the + # _auth_request's default timeout. + auth_request = ( + self._auth_request + if timeout is None + else functools.partial(self._auth_request, timeout=timeout) + ) + + remaining_time = max_allowed_time + + with requests.TimeoutGuard(remaining_time, asyncio.TimeoutError) as guard: + await self.credentials.before_request( + auth_request, method, url, request_headers + ) + + with requests.TimeoutGuard(remaining_time, asyncio.TimeoutError) as guard: + response = await super(AuthorizedSession, self).request( + method, + url, + data=data, + headers=request_headers, + timeout=timeout, + **kwargs, + ) + + remaining_time = guard.remaining_timeout + + if ( + response.status in self._refresh_status_codes + and _credential_refresh_attempt < self._max_refresh_attempts + ): + requests._LOGGER.info( + "Refreshing credentials due to a %s response. Attempt %s/%s.", + response.status, + _credential_refresh_attempt + 1, + self._max_refresh_attempts, + ) + + # Do not apply the timeout unconditionally in order to not override the + # _auth_request's default timeout. + auth_request = ( + self._auth_request + if timeout is None + else functools.partial(self._auth_request, timeout=timeout) + ) + + with requests.TimeoutGuard( + remaining_time, asyncio.TimeoutError + ) as guard: + async with self._refresh_lock: + await self._loop.run_in_executor( + None, self.credentials.refresh, auth_request + ) + + remaining_time = guard.remaining_timeout + + return await self.request( + method, + url, + data=data, + headers=headers, + max_allowed_time=remaining_time, + timeout=timeout, + _credential_refresh_attempt=_credential_refresh_attempt + 1, + **kwargs, + ) + + return response diff --git a/intelliaide/vendor/google/auth/transport/_custom_tls_signer.py b/intelliaide/vendor/google/auth/transport/_custom_tls_signer.py new file mode 100644 index 0000000..9279158 --- /dev/null +++ b/intelliaide/vendor/google/auth/transport/_custom_tls_signer.py @@ -0,0 +1,283 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Code for configuring client side TLS to offload the signing operation to +signing libraries. +""" + +import ctypes +import json +import logging +import os +import sys + +import cffi # type: ignore + +from google.auth import exceptions + +_LOGGER = logging.getLogger(__name__) + +# C++ offload lib requires google-auth lib to provide the following callback: +# using SignFunc = int (*)(unsigned char *sig, size_t *sig_len, +# const unsigned char *tbs, size_t tbs_len) +# The bytes to be signed and the length are provided via `tbs` and `tbs_len`, +# the callback computes the signature, and write the signature and its length +# into `sig` and `sig_len`. +# If the signing is successful, the callback returns 1, otherwise it returns 0. +SIGN_CALLBACK_CTYPE = ctypes.CFUNCTYPE( + ctypes.c_int, # return type + ctypes.POINTER(ctypes.c_ubyte), # sig + ctypes.POINTER(ctypes.c_size_t), # sig_len + ctypes.POINTER(ctypes.c_ubyte), # tbs + ctypes.c_size_t, # tbs_len +) + + +# Cast SSL_CTX* to void* +def _cast_ssl_ctx_to_void_p_pyopenssl(ssl_ctx): + return ctypes.cast(int(cffi.FFI().cast("intptr_t", ssl_ctx)), ctypes.c_void_p) + + +# Cast SSL_CTX* to void* +def _cast_ssl_ctx_to_void_p_stdlib(context): + return ctypes.c_void_p.from_address( + id(context) + ctypes.sizeof(ctypes.c_void_p) * 2 + ) + + +# Load offload library and set up the function types. +def load_offload_lib(offload_lib_path): + _LOGGER.debug("loading offload library from %s", offload_lib_path) + + # winmode parameter is only available for python 3.8+. + lib = ( + ctypes.CDLL(offload_lib_path, winmode=0) + if sys.version_info >= (3, 8) and os.name == "nt" + else ctypes.CDLL(offload_lib_path) + ) + + # Set up types for: + # int ConfigureSslContext(SignFunc sign_func, const char *cert, SSL_CTX *ctx) + lib.ConfigureSslContext.argtypes = [ + SIGN_CALLBACK_CTYPE, + ctypes.c_char_p, + ctypes.c_void_p, + ] + lib.ConfigureSslContext.restype = ctypes.c_int + + return lib + + +# Load signer library and set up the function types. +# See: https://github.com/googleapis/enterprise-certificate-proxy/blob/main/cshared/main.go +def load_signer_lib(signer_lib_path): + _LOGGER.debug("loading signer library from %s", signer_lib_path) + + # winmode parameter is only available for python 3.8+. + lib = ( + ctypes.CDLL(signer_lib_path, winmode=0) + if sys.version_info >= (3, 8) and os.name == "nt" + else ctypes.CDLL(signer_lib_path) + ) + + # Set up types for: + # func GetCertPemForPython(configFilePath *C.char, certHolder *byte, certHolderLen int) + lib.GetCertPemForPython.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int] + # Returns: certLen + lib.GetCertPemForPython.restype = ctypes.c_int + + # Set up types for: + # func SignForPython(configFilePath *C.char, digest *byte, digestLen int, + # sigHolder *byte, sigHolderLen int) + lib.SignForPython.argtypes = [ + ctypes.c_char_p, + ctypes.c_char_p, + ctypes.c_int, + ctypes.c_char_p, + ctypes.c_int, + ] + # Returns: the signature length + lib.SignForPython.restype = ctypes.c_int + + return lib + + +def load_provider_lib(provider_lib_path): + _LOGGER.debug("loading provider library from %s", provider_lib_path) + + # winmode parameter is only available for python 3.8+. + lib = ( + ctypes.CDLL(provider_lib_path, winmode=0) + if sys.version_info >= (3, 8) and os.name == "nt" + else ctypes.CDLL(provider_lib_path) + ) + + lib.ECP_attach_to_ctx.argtypes = [ctypes.c_void_p, ctypes.c_char_p] + lib.ECP_attach_to_ctx.restype = ctypes.c_int + + return lib + + +# Computes SHA256 hash. +def _compute_sha256_digest(to_be_signed, to_be_signed_len): + from cryptography.hazmat.primitives import hashes + + data = ctypes.string_at(to_be_signed, to_be_signed_len) + hash = hashes.Hash(hashes.SHA256()) + hash.update(data) + return hash.finalize() + + +# Create the signing callback. The actual signing work is done by the +# `SignForPython` method from the signer lib. +def get_sign_callback(signer_lib, config_file_path): + def sign_callback(sig, sig_len, tbs, tbs_len): + _LOGGER.debug("calling sign callback...") + + digest = _compute_sha256_digest(tbs, tbs_len) + digestArray = ctypes.c_char * len(digest) + + # reserve 2000 bytes for the signature, shoud be more then enough. + # RSA signature is 256 bytes, EC signature is 70~72. + sig_holder_len = 2000 + sig_holder = ctypes.create_string_buffer(sig_holder_len) + + signature_len = signer_lib.SignForPython( + config_file_path.encode(), # configFilePath + digestArray.from_buffer(bytearray(digest)), # digest + len(digest), # digestLen + sig_holder, # sigHolder + sig_holder_len, # sigHolderLen + ) + + if signature_len == 0: + # signing failed, return 0 + return 0 + + sig_len[0] = signature_len + bs = bytearray(sig_holder) + for i in range(signature_len): + sig[i] = bs[i] + + return 1 + + return SIGN_CALLBACK_CTYPE(sign_callback) + + +# Obtain the certificate bytes by calling the `GetCertPemForPython` method from +# the signer lib. The method is called twice, the first time is to compute the +# cert length, then we create a buffer to hold the cert, and call it again to +# fill the buffer. +def get_cert(signer_lib, config_file_path): + # First call to calculate the cert length + cert_len = signer_lib.GetCertPemForPython( + config_file_path.encode(), # configFilePath + None, # certHolder + 0, # certHolderLen + ) + if cert_len == 0: + raise exceptions.MutualTLSChannelError("failed to get certificate") + + # Then we create an array to hold the cert, and call again to fill the cert + cert_holder = ctypes.create_string_buffer(cert_len) + signer_lib.GetCertPemForPython( + config_file_path.encode(), # configFilePath + cert_holder, # certHolder + cert_len, # certHolderLen + ) + return bytes(cert_holder) + + +class CustomTlsSigner(object): + def __init__(self, enterprise_cert_file_path): + """ + This class loads the offload and signer library, and calls APIs from + these libraries to obtain the cert and a signing callback, and attach + them to SSL context. The cert and the signing callback will be used + for client authentication in TLS handshake. + + Args: + enterprise_cert_file_path (str): the path to a enterprise cert JSON + file. The file should contain the following field: + + { + "libs": { + "ecp_client": "...", + "tls_offload": "..." + } + } + """ + self._enterprise_cert_file_path = enterprise_cert_file_path + self._cert = None + self._sign_callback = None + self._provider_lib = None + + def load_libraries(self): + with open(self._enterprise_cert_file_path, "r") as f: + enterprise_cert_json = json.load(f) + libs = enterprise_cert_json.get("libs", {}) + + signer_library = libs.get("ecp_client", None) + offload_library = libs.get("tls_offload", None) + provider_library = libs.get("ecp_provider", None) + + # Using newer provider implementation. This is mutually exclusive to the + # offload implementation. + if provider_library: + self._provider_lib = load_provider_lib(provider_library) + return + + # Using old offload implementation + if offload_library and signer_library: + self._offload_lib = load_offload_lib(offload_library) + self._signer_lib = load_signer_lib(signer_library) + self.set_up_custom_key() + return + + raise exceptions.MutualTLSChannelError("enterprise cert file is invalid") + + def set_up_custom_key(self): + # We need to keep a reference of the cert and sign callback so it won't + # be garbage collected, otherwise it will crash when used by signer lib. + self._cert = get_cert(self._signer_lib, self._enterprise_cert_file_path) + self._sign_callback = get_sign_callback( + self._signer_lib, self._enterprise_cert_file_path + ) + + def should_use_provider(self): + if self._provider_lib: + return True + return False + + def attach_to_ssl_context(self, ctx): + if self.should_use_provider(): + if not self._provider_lib.ECP_attach_to_ctx( + _cast_ssl_ctx_to_void_p_stdlib(ctx), + self._enterprise_cert_file_path.encode("ascii"), + ): + raise exceptions.MutualTLSChannelError( + "failed to configure ECP Provider SSL context" + ) + elif self._offload_lib and self._signer_lib: + if not self._offload_lib.ConfigureSslContext( + self._sign_callback, + ctypes.c_char_p(self._cert), + _cast_ssl_ctx_to_void_p_pyopenssl(ctx._ctx._context), + ): + raise exceptions.MutualTLSChannelError( + "failed to configure ECP Offload SSL context" + ) + else: + raise exceptions.MutualTLSChannelError("Invalid ECP configuration.") diff --git a/intelliaide/vendor/google/auth/transport/_http_client.py b/intelliaide/vendor/google/auth/transport/_http_client.py new file mode 100644 index 0000000..bcfc2b2 --- /dev/null +++ b/intelliaide/vendor/google/auth/transport/_http_client.py @@ -0,0 +1,114 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Transport adapter for http.client, for internal use only.""" + +import http.client as http_client +import logging +import socket +import urllib + +from google.auth import _helpers +from google.auth import exceptions +from google.auth import transport + +_LOGGER = logging.getLogger(__name__) + + +class Response(transport.Response): + """http.client transport response adapter. + + Args: + response (http.client.HTTPResponse): The raw http client response. + """ + + def __init__(self, response): + self._status = response.status + self._headers = {key.lower(): value for key, value in response.getheaders()} + self._data = response.read() + + @property + def status(self): + return self._status + + @property + def headers(self): + return self._headers + + @property + def data(self): + return self._data + + +class Request(transport.Request): + """http.client transport request adapter.""" + + def __call__( + self, url, method="GET", body=None, headers=None, timeout=None, **kwargs + ): + """Make an HTTP request using http.client. + + Args: + url (str): The URI to be requested. + method (str): The HTTP method to use for the request. Defaults + to 'GET'. + body (bytes): The payload / body in HTTP request. + headers (Mapping): Request headers. + timeout (Optional(int)): The number of seconds to wait for a + response from the server. If not specified or if None, the + socket global default timeout will be used. + kwargs: Additional arguments passed throught to the underlying + :meth:`~http.client.HTTPConnection.request` method. + + Returns: + Response: The HTTP response. + + Raises: + google.auth.exceptions.TransportError: If any exception occurred. + """ + # socket._GLOBAL_DEFAULT_TIMEOUT is the default in http.client. + if timeout is None: + timeout = socket._GLOBAL_DEFAULT_TIMEOUT + + # http.client doesn't allow None as the headers argument. + if headers is None: + headers = {} + + # http.client needs the host and path parts specified separately. + parts = urllib.parse.urlsplit(url) + path = urllib.parse.urlunsplit( + ("", "", parts.path, parts.query, parts.fragment) + ) + + if parts.scheme != "http": + raise exceptions.TransportError( + "http.client transport only supports the http scheme, {}" + " was specified".format(parts.scheme) + ) + + connection = http_client.HTTPConnection(parts.netloc, timeout=timeout) + + try: + _helpers.request_log(_LOGGER, method, url, body, headers) + connection.request(method, path, body=body, headers=headers, **kwargs) + response = connection.getresponse() + _helpers.response_log(_LOGGER, response) + return Response(response) + + except (http_client.HTTPException, socket.error) as caught_exc: + new_exc = exceptions.TransportError(caught_exc) + raise new_exc from caught_exc + + finally: + connection.close() diff --git a/intelliaide/vendor/google/auth/transport/_mtls_helper.py b/intelliaide/vendor/google/auth/transport/_mtls_helper.py new file mode 100644 index 0000000..d645029 --- /dev/null +++ b/intelliaide/vendor/google/auth/transport/_mtls_helper.py @@ -0,0 +1,531 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Helper functions for getting mTLS cert and key.""" + +import json +import logging +from os import environ, getenv, path +import re +import subprocess + +from google.auth import _agent_identity_utils +from google.auth import environment_vars +from google.auth import exceptions + +CONTEXT_AWARE_METADATA_PATH = "~/.secureConnect/context_aware_metadata.json" + +# Default gcloud config path, to be used with path.expanduser for cross-platform compatibility. +CERTIFICATE_CONFIGURATION_DEFAULT_PATH = "~/.config/gcloud/certificate_config.json" +_CERT_PROVIDER_COMMAND = "cert_provider_command" +_CERT_REGEX = re.compile( + b"-----BEGIN CERTIFICATE-----.+-----END CERTIFICATE-----\r?\n?", re.DOTALL +) + +# support various format of key files, e.g. +# "-----BEGIN PRIVATE KEY-----...", +# "-----BEGIN EC PRIVATE KEY-----...", +# "-----BEGIN RSA PRIVATE KEY-----..." +# "-----BEGIN ENCRYPTED PRIVATE KEY-----" +_KEY_REGEX = re.compile( + b"-----BEGIN [A-Z ]*PRIVATE KEY-----.+-----END [A-Z ]*PRIVATE KEY-----\r?\n?", + re.DOTALL, +) + +_LOGGER = logging.getLogger(__name__) + + +_PASSPHRASE_REGEX = re.compile( + b"-----BEGIN PASSPHRASE-----(.+)-----END PASSPHRASE-----", re.DOTALL +) + +# Temporary patch to accomodate incorrect cert config in Cloud Run prod environment. +_WELL_KNOWN_CLOUD_RUN_CERT_PATH = ( + "/var/run/secrets/workload-spiffe-credentials/certificates.pem" +) +_WELL_KNOWN_CLOUD_RUN_KEY_PATH = ( + "/var/run/secrets/workload-spiffe-credentials/private_key.pem" +) +_INCORRECT_CLOUD_RUN_CERT_PATH = ( + "/var/lib/volumes/certificate/workload-certificates/certificates.pem" +) +_INCORRECT_CLOUD_RUN_KEY_PATH = ( + "/var/lib/volumes/certificate/workload-certificates/private_key.pem" +) + + +def _check_config_path(config_path): + """Checks for config file path. If it exists, returns the absolute path with user expansion; + otherwise returns None. + + Args: + config_path (str): The config file path for either context_aware_metadata.json or certificate_config.json for example + + Returns: + str: absolute path if exists and None otherwise. + """ + config_path = path.expanduser(config_path) + if not path.exists(config_path): + _LOGGER.debug("%s is not found.", config_path) + return None + return config_path + + +def _load_json_file(path): + """Reads and loads JSON from the given path. Used to read both X509 workload certificate and + secure connect configurations. + + Args: + path (str): the path to read from. + + Returns: + Dict[str, str]: The JSON stored at the file. + + Raises: + google.auth.exceptions.ClientCertError: If failed to parse the file as JSON. + """ + try: + with open(path) as f: + json_data = json.load(f) + except ValueError as caught_exc: + new_exc = exceptions.ClientCertError(caught_exc) + raise new_exc from caught_exc + + return json_data + + +def _get_workload_cert_and_key( + certificate_config_path=None, include_context_aware=True +): + """Read the workload identity cert and key files specified in the certificate config provided. + If no config path is provided, check the environment variable: "GOOGLE_API_CERTIFICATE_CONFIG" + first, then the well known gcloud location: "~/.config/gcloud/certificate_config.json". + + Args: + certificate_config_path (string): The certificate config path. If no path is provided, + the environment variable will be checked first, then the well known gcloud location. + include_context_aware (bool): If context aware metadata path should be checked for the + SecureConnect mTLS configuration. + + Returns: + Tuple[Optional[bytes], Optional[bytes]]: client certificate bytes in PEM format and key + bytes in PEM format. + + Raises: + google.auth.exceptions.ClientCertError: if problems occurs when retrieving + the certificate or key information. + """ + + cert_path, key_path = _get_workload_cert_and_key_paths( + certificate_config_path, include_context_aware + ) + + if cert_path is None and key_path is None: + return None, None + + return _read_cert_and_key_files(cert_path, key_path) + + +def _get_cert_config_path(certificate_config_path=None, include_context_aware=True): + """Get the certificate configuration path based on the following order: + + 1: Explicit override, if set + 2: Environment variable, if set + 3: Well-known location + + Returns "None" if the selected config file does not exist. + + Args: + certificate_config_path (string): The certificate config path. If provided, the well known + location and environment variable will be ignored. + include_context_aware (bool): If context aware metadata path should be checked for the + SecureConnect mTLS configuration. + + Returns: + The absolute path of the certificate config file, and None if the file does not exist. + """ + + if certificate_config_path is None: + env_path = environ.get(environment_vars.GOOGLE_API_CERTIFICATE_CONFIG, None) + if env_path is not None and env_path != "": + certificate_config_path = env_path + else: + env_path = environ.get( + environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH, + None, + ) + if include_context_aware and env_path is not None and env_path != "": + certificate_config_path = env_path + else: + certificate_config_path = CERTIFICATE_CONFIGURATION_DEFAULT_PATH + + certificate_config_path = path.expanduser(certificate_config_path) + if not path.exists(certificate_config_path): + return None + return certificate_config_path + + +def _get_workload_cert_and_key_paths(config_path, include_context_aware=True): + absolute_path = _get_cert_config_path(config_path, include_context_aware) + if absolute_path is None: + return None, None + + data = _load_json_file(absolute_path) + + if "cert_configs" not in data: + raise exceptions.ClientCertError( + 'Certificate config file {} is in an invalid format, a "cert configs" object is expected'.format( + absolute_path + ) + ) + cert_configs = data["cert_configs"] + + # We return None, None if the expected workload fields are not present. + # The certificate config might be present for other types of connections (e.g. gECC), + # and we want to gracefully fallback to testing other mTLS configurations + # like SecureConnect instead of throwing an exception. + + if "workload" not in cert_configs: + return None, None + workload = cert_configs["workload"] + + if "cert_path" not in workload: + return None, None + cert_path = workload["cert_path"] + + if "key_path" not in workload: + return None, None + key_path = workload["key_path"] + + # == BEGIN Temporary Cloud Run PATCH == + # See https://github.com/googleapis/google-auth-library-python/issues/1881 + if (cert_path == _INCORRECT_CLOUD_RUN_CERT_PATH) and ( + key_path == _INCORRECT_CLOUD_RUN_KEY_PATH + ): + if not path.exists(cert_path) and not path.exists(key_path): + _LOGGER.debug( + "Applying Cloud Run certificate path patch. " + "Configured paths not found: %s, %s. " + "Using well-known paths: %s, %s", + cert_path, + key_path, + _WELL_KNOWN_CLOUD_RUN_CERT_PATH, + _WELL_KNOWN_CLOUD_RUN_KEY_PATH, + ) + cert_path = _WELL_KNOWN_CLOUD_RUN_CERT_PATH + key_path = _WELL_KNOWN_CLOUD_RUN_KEY_PATH + # == END Temporary Cloud Run PATCH == + + return cert_path, key_path + + +def _read_cert_and_key_files(cert_path, key_path): + cert_data = _read_cert_file(cert_path) + key_data = _read_key_file(key_path) + + return cert_data, key_data + + +def _read_cert_file(cert_path): + with open(cert_path, "rb") as cert_file: + cert_data = cert_file.read() + + cert_match = re.findall(_CERT_REGEX, cert_data) + if len(cert_match) != 1: + raise exceptions.ClientCertError( + "Certificate file {} is in an invalid format, a single PEM formatted certificate is expected".format( + cert_path + ) + ) + return cert_match[0] + + +def _read_key_file(key_path): + with open(key_path, "rb") as key_file: + key_data = key_file.read() + + key_match = re.findall(_KEY_REGEX, key_data) + if len(key_match) != 1: + raise exceptions.ClientCertError( + "Private key file {} is in an invalid format, a single PEM formatted private key is expected".format( + key_path + ) + ) + + return key_match[0] + + +def _run_cert_provider_command(command, expect_encrypted_key=False): + """Run the provided command, and return client side mTLS cert, key and + passphrase. + + Args: + command (List[str]): cert provider command. + expect_encrypted_key (bool): If encrypted private key is expected. + + Returns: + Tuple[bytes, bytes, bytes]: client certificate bytes in PEM format, key + bytes in PEM format and passphrase bytes. + + Raises: + google.auth.exceptions.ClientCertError: if problems occurs when running + the cert provider command or generating cert, key and passphrase. + """ + try: + process = subprocess.Popen( + command, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + stdout, stderr = process.communicate() + except OSError as caught_exc: + new_exc = exceptions.ClientCertError(caught_exc) + raise new_exc from caught_exc + + # Check cert provider command execution error. + if process.returncode != 0: + raise exceptions.ClientCertError( + "Cert provider command returns non-zero status code %s" % process.returncode + ) + + # Extract certificate (chain), key and passphrase. + cert_match = re.findall(_CERT_REGEX, stdout) + if len(cert_match) != 1: + raise exceptions.ClientCertError("Client SSL certificate is missing or invalid") + key_match = re.findall(_KEY_REGEX, stdout) + if len(key_match) != 1: + raise exceptions.ClientCertError("Client SSL key is missing or invalid") + passphrase_match = re.findall(_PASSPHRASE_REGEX, stdout) + + if expect_encrypted_key: + if len(passphrase_match) != 1: + raise exceptions.ClientCertError("Passphrase is missing or invalid") + if b"ENCRYPTED" not in key_match[0]: + raise exceptions.ClientCertError("Encrypted private key is expected") + return cert_match[0], key_match[0], passphrase_match[0].strip() + + if b"ENCRYPTED" in key_match[0]: + raise exceptions.ClientCertError("Encrypted private key is not expected") + if len(passphrase_match) > 0: + raise exceptions.ClientCertError("Passphrase is not expected") + return cert_match[0], key_match[0], None + + +def get_client_ssl_credentials( + generate_encrypted_key=False, + context_aware_metadata_path=CONTEXT_AWARE_METADATA_PATH, + certificate_config_path=None, +): + """Returns the client side certificate, private key and passphrase. + + We look for certificates and keys with the following order of priority: + 1. Certificate and key specified by certificate_config.json. + Currently, only X.509 workload certificates are supported. + 2. Certificate and key specified by context aware metadata (i.e. SecureConnect). + + Args: + generate_encrypted_key (bool): If set to True, encrypted private key + and passphrase will be generated; otherwise, unencrypted private key + will be generated and passphrase will be None. This option only + affects keys obtained via context_aware_metadata.json. + context_aware_metadata_path (str): The context_aware_metadata.json file path. + certificate_config_path (str): The certificate_config.json file path. + + Returns: + Tuple[bool, bytes, bytes, bytes]: + A boolean indicating if cert, key and passphrase are obtained, the + cert bytes and key bytes both in PEM format, and passphrase bytes. + + Raises: + google.auth.exceptions.ClientCertError: if problems occurs when getting + the cert, key and passphrase. + """ + + # 1. Attempt to retrieve X.509 Workload cert and key. + cert, key = _get_workload_cert_and_key(certificate_config_path) + if cert and key: + return True, cert, key, None + + # 2. Check for context aware metadata json + metadata_path = _check_config_path(context_aware_metadata_path) + + if metadata_path: + metadata_json = _load_json_file(metadata_path) + + if _CERT_PROVIDER_COMMAND not in metadata_json: + raise exceptions.ClientCertError("Cert provider command is not found") + + command = metadata_json[_CERT_PROVIDER_COMMAND] + + if generate_encrypted_key and "--with_passphrase" not in command: + command.append("--with_passphrase") + + # Execute the command. + cert, key, passphrase = _run_cert_provider_command( + command, expect_encrypted_key=generate_encrypted_key + ) + return True, cert, key, passphrase + + return False, None, None, None + + +def get_client_cert_and_key(client_cert_callback=None): + """Returns the client side certificate and private key. The function first + tries to get certificate and key from client_cert_callback; if the callback + is None or doesn't provide certificate and key, the function tries application + default SSL credentials. + + Args: + client_cert_callback (Optional[Callable[[], (bytes, bytes)]]): An + optional callback which returns client certificate bytes and private + key bytes both in PEM format. + + Returns: + Tuple[bool, bytes, bytes]: + A boolean indicating if cert and key are obtained, the cert bytes + and key bytes both in PEM format. + + Raises: + google.auth.exceptions.ClientCertError: if problems occurs when getting + the cert and key. + """ + if client_cert_callback: + cert, key = client_cert_callback() + return True, cert, key + + has_cert, cert, key, _ = get_client_ssl_credentials(generate_encrypted_key=False) + return has_cert, cert, key + + +def decrypt_private_key(key, passphrase): + """A helper function to decrypt the private key with the given passphrase. + google-auth library doesn't support passphrase protected private key for + mutual TLS channel. This helper function can be used to decrypt the + passphrase protected private key in order to estalish mutual TLS channel. + + For example, if you have a function which produces client cert, passphrase + protected private key and passphrase, you can convert it to a client cert + callback function accepted by google-auth:: + + from google.auth.transport import _mtls_helper + + def your_client_cert_function(): + return cert, encrypted_key, passphrase + + # callback accepted by google-auth for mutual TLS channel. + def client_cert_callback(): + cert, encrypted_key, passphrase = your_client_cert_function() + decrypted_key = _mtls_helper.decrypt_private_key(encrypted_key, + passphrase) + return cert, decrypted_key + + Args: + key (bytes): The private key bytes in PEM format. + passphrase (bytes): The passphrase bytes. + + Returns: + bytes: The decrypted private key in PEM format. + + Raises: + ImportError: If pyOpenSSL is not installed. + OpenSSL.crypto.Error: If there is any problem decrypting the private key. + """ + from OpenSSL import crypto + + # First convert encrypted_key_bytes to PKey object + pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key, passphrase=passphrase) + + # Then dump the decrypted key bytes + return crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey) + + +def check_use_client_cert(): + """Returns boolean for whether the client certificate should be used for mTLS. + + If GOOGLE_API_USE_CLIENT_CERTIFICATE is set to true or false, a corresponding + bool value will be returned. If the value is set to an unexpected string, it + will default to False. + If GOOGLE_API_USE_CLIENT_CERTIFICATE is unset, the value will be inferred + by reading a file pointed at by GOOGLE_API_CERTIFICATE_CONFIG, and verifying + it contains a "workload" section. If so, the function will return True, + otherwise False. + + Returns: + bool: Whether the client certificate should be used for mTLS connection. + """ + use_client_cert = getenv(environment_vars.GOOGLE_API_USE_CLIENT_CERTIFICATE) + if use_client_cert is None or use_client_cert == "": + use_client_cert = getenv( + environment_vars.CLOUDSDK_CONTEXT_AWARE_USE_CLIENT_CERTIFICATE + ) + + # Check if the value of GOOGLE_API_USE_CLIENT_CERTIFICATE is set. + if use_client_cert: + return use_client_cert.lower() == "true" + else: + # Check if the value of GOOGLE_API_CERTIFICATE_CONFIG is set. + cert_path = getenv(environment_vars.GOOGLE_API_CERTIFICATE_CONFIG) + if cert_path is None: + cert_path = getenv( + environment_vars.CLOUDSDK_CONTEXT_AWARE_CERTIFICATE_CONFIG_FILE_PATH + ) + + if cert_path: + try: + with open(cert_path, "r") as f: + content = json.load(f) + # verify json has workload key + content["cert_configs"]["workload"] + return True + except ( + FileNotFoundError, + OSError, + KeyError, + TypeError, + json.JSONDecodeError, + ) as e: + _LOGGER.debug("error decoding certificate: %s", e) + return False + + +def check_parameters_for_unauthorized_response(cached_cert): + """Returns the cached and current cert fingerprint for reconfiguring mTLS. + + Args: + cached_cert(bytes): The cached client certificate. + + Returns: + bytes: The client callback cert bytes. + bytes: The client callback key bytes. + str: The base64-encoded SHA256 cached fingerprint. + str: The base64-encoded SHA256 current cert fingerprint. + """ + call_cert_bytes, call_key_bytes = call_client_cert_callback() + cert_obj = _agent_identity_utils.parse_certificate(call_cert_bytes) + current_cert_fingerprint = _agent_identity_utils.calculate_certificate_fingerprint( + cert_obj + ) + if cached_cert: + cached_fingerprint = _agent_identity_utils.get_cached_cert_fingerprint( + cached_cert + ) + else: + cached_fingerprint = current_cert_fingerprint + return call_cert_bytes, call_key_bytes, cached_fingerprint, current_cert_fingerprint + + +def call_client_cert_callback(): + """Calls the client cert callback and returns the certificate and key.""" + _, cert_bytes, key_bytes, passphrase = get_client_ssl_credentials( + generate_encrypted_key=True + ) + return cert_bytes, key_bytes diff --git a/intelliaide/vendor/google/auth/transport/_requests_base.py b/intelliaide/vendor/google/auth/transport/_requests_base.py new file mode 100644 index 0000000..0608223 --- /dev/null +++ b/intelliaide/vendor/google/auth/transport/_requests_base.py @@ -0,0 +1,53 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Transport adapter for Base Requests.""" +# NOTE: The coverage for this file is temporarily disabled in `.coveragerc` +# since it is currently unused. + +import abc + + +_DEFAULT_TIMEOUT = 120 # in second + + +class _BaseAuthorizedSession(metaclass=abc.ABCMeta): + """Base class for a Request Session with credentials. This class is intended to capture + the common logic between synchronous and asynchronous request sessions and is not intended to + be instantiated directly. + + Args: + credentials (google.auth._credentials_base.BaseCredentials): The credentials to + add to the request. + """ + + def __init__(self, credentials): + self.credentials = credentials + + @abc.abstractmethod + def request( + self, + method, + url, + data=None, + headers=None, + max_allowed_time=None, + timeout=_DEFAULT_TIMEOUT, + **kwargs + ): + raise NotImplementedError("Request must be implemented") + + @abc.abstractmethod + def close(self): + raise NotImplementedError("Close must be implemented") diff --git a/intelliaide/vendor/google/auth/transport/grpc.py b/intelliaide/vendor/google/auth/transport/grpc.py new file mode 100644 index 0000000..e541d20 --- /dev/null +++ b/intelliaide/vendor/google/auth/transport/grpc.py @@ -0,0 +1,337 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Authorization support for gRPC.""" + +from __future__ import absolute_import + +import logging + +from google.auth import exceptions +from google.auth.transport import _mtls_helper +from google.oauth2 import service_account + +try: + import grpc # type: ignore +except ImportError as caught_exc: # pragma: NO COVER + raise ImportError( + "gRPC is not installed from please install the grpcio package to use the gRPC transport." + ) from caught_exc + +_LOGGER = logging.getLogger(__name__) + + +class AuthMetadataPlugin(grpc.AuthMetadataPlugin): + """A `gRPC AuthMetadataPlugin`_ that inserts the credentials into each + request. + + .. _gRPC AuthMetadataPlugin: + http://www.grpc.io/grpc/python/grpc.html#grpc.AuthMetadataPlugin + + Args: + credentials (google.auth.credentials.Credentials): The credentials to + add to requests. + request (google.auth.transport.Request): A HTTP transport request + object used to refresh credentials as needed. + default_host (Optional[str]): A host like "pubsub.googleapis.com". + This is used when a self-signed JWT is created from service + account credentials. + """ + + def __init__(self, credentials, request, default_host=None): + # pylint: disable=no-value-for-parameter + # pylint doesn't realize that the super method takes no arguments + # because this class is the same name as the superclass. + super(AuthMetadataPlugin, self).__init__() + self._credentials = credentials + self._request = request + self._default_host = default_host + + def _get_authorization_headers(self, context): + """Gets the authorization headers for a request. + + Returns: + Sequence[Tuple[str, str]]: A list of request headers (key, value) + to add to the request. + """ + headers = {} + + # https://google.aip.dev/auth/4111 + # Attempt to use self-signed JWTs when a service account is used. + # A default host must be explicitly provided since it cannot always + # be determined from the context.service_url. + if isinstance(self._credentials, service_account.Credentials): + self._credentials._create_self_signed_jwt( + "https://{}/".format(self._default_host) if self._default_host else None + ) + + self._credentials.before_request( + self._request, context.method_name, context.service_url, headers + ) + + return list(headers.items()) + + def __call__(self, context, callback): + """Passes authorization metadata into the given callback. + + Args: + context (grpc.AuthMetadataContext): The RPC context. + callback (grpc.AuthMetadataPluginCallback): The callback that will + be invoked to pass in the authorization metadata. + """ + callback(self._get_authorization_headers(context), None) + + +def secure_authorized_channel( + credentials, + request, + target, + ssl_credentials=None, + client_cert_callback=None, + **kwargs +): + """Creates a secure authorized gRPC channel. + + This creates a channel with SSL and :class:`AuthMetadataPlugin`. This + channel can be used to create a stub that can make authorized requests. + Users can configure client certificate or rely on device certificates to + establish a mutual TLS channel, if the `GOOGLE_API_USE_CLIENT_CERTIFICATE` + variable is explicitly set to `true`. + + Example:: + + import google.auth + import google.auth.transport.grpc + import google.auth.transport.requests + from google.cloud.speech.v1 import cloud_speech_pb2 + + # Get credentials. + credentials, _ = google.auth.default() + + # Get an HTTP request function to refresh credentials. + request = google.auth.transport.requests.Request() + + # Create a channel. + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, regular_endpoint, request, + ssl_credentials=grpc.ssl_channel_credentials()) + + # Use the channel to create a stub. + cloud_speech.create_Speech_stub(channel) + + Usage: + + There are actually a couple of options to create a channel, depending on if + you want to create a regular or mutual TLS channel. + + First let's list the endpoints (regular vs mutual TLS) to choose from:: + + regular_endpoint = 'speech.googleapis.com:443' + mtls_endpoint = 'speech.mtls.googleapis.com:443' + + Option 1: create a regular (non-mutual) TLS channel by explicitly setting + the ssl_credentials:: + + regular_ssl_credentials = grpc.ssl_channel_credentials() + + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, request, regular_endpoint, + ssl_credentials=regular_ssl_credentials) + + Option 2: create a mutual TLS channel by calling a callback which returns + the client side certificate and the key (Note that + `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be explicitly + set to `true`):: + + def my_client_cert_callback(): + code_to_load_client_cert_and_key() + if loaded: + return (pem_cert_bytes, pem_key_bytes) + raise MyClientCertFailureException() + + try: + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, request, mtls_endpoint, + client_cert_callback=my_client_cert_callback) + except MyClientCertFailureException: + # handle the exception + + Option 3: use application default SSL credentials. It searches and uses + the command in a context aware metadata file, which is available on devices + with endpoint verification support (Note that + `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be explicitly + set to `true`). + See https://cloud.google.com/endpoint-verification/docs/overview:: + + try: + default_ssl_credentials = SslCredentials() + except: + # Exception can be raised if the context aware metadata is malformed. + # See :class:`SslCredentials` for the possible exceptions. + + # Choose the endpoint based on the SSL credentials type. + if default_ssl_credentials.is_mtls: + endpoint_to_use = mtls_endpoint + else: + endpoint_to_use = regular_endpoint + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, request, endpoint_to_use, + ssl_credentials=default_ssl_credentials) + + Option 4: not setting ssl_credentials and client_cert_callback. For devices + without endpoint verification support or `GOOGLE_API_USE_CLIENT_CERTIFICATE` + environment variable is not `true`, a regular TLS channel is created; + otherwise, a mutual TLS channel is created, however, the call should be + wrapped in a try/except block in case of malformed context aware metadata. + + The following code uses regular_endpoint, it works the same no matter the + created channle is regular or mutual TLS. Regular endpoint ignores client + certificate and key:: + + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, request, regular_endpoint) + + The following code uses mtls_endpoint, if the created channle is regular, + and API mtls_endpoint is confgured to require client SSL credentials, API + calls using this channel will be rejected:: + + channel = google.auth.transport.grpc.secure_authorized_channel( + credentials, request, mtls_endpoint) + + Args: + credentials (google.auth.credentials.Credentials): The credentials to + add to requests. + request (google.auth.transport.Request): A HTTP transport request + object used to refresh credentials as needed. Even though gRPC + is a separate transport, there's no way to refresh the credentials + without using a standard http transport. + target (str): The host and port of the service. + ssl_credentials (grpc.ChannelCredentials): Optional SSL channel + credentials. This can be used to specify different certificates. + This argument is mutually exclusive with client_cert_callback; + providing both will raise an exception. + If ssl_credentials and client_cert_callback are None, application + default SSL credentials are used if `GOOGLE_API_USE_CLIENT_CERTIFICATE` + environment variable is explicitly set to `true`, otherwise one way TLS + SSL credentials are used. + client_cert_callback (Callable[[], (bytes, bytes)]): Optional + callback function to obtain client certicate and key for mutual TLS + connection. This argument is mutually exclusive with + ssl_credentials; providing both will raise an exception. + This argument does nothing unless `GOOGLE_API_USE_CLIENT_CERTIFICATE` + environment variable is explicitly set to `true`. + kwargs: Additional arguments to pass to :func:`grpc.secure_channel`. + + Returns: + grpc.Channel: The created gRPC channel. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel + creation failed for any reason. + """ + # Create the metadata plugin for inserting the authorization header. + metadata_plugin = AuthMetadataPlugin(credentials, request) + + # Create a set of grpc.CallCredentials using the metadata plugin. + google_auth_credentials = grpc.metadata_call_credentials(metadata_plugin) + + if ssl_credentials and client_cert_callback: + raise exceptions.MalformedError( + "Received both ssl_credentials and client_cert_callback; " + "these are mutually exclusive." + ) + + # If SSL credentials are not explicitly set, try client_cert_callback and ADC. + if not ssl_credentials: + use_client_cert = _mtls_helper.check_use_client_cert() + if use_client_cert and client_cert_callback: + # Use the callback if provided. + cert, key = client_cert_callback() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + elif use_client_cert: + # Use application default SSL credentials. + adc_ssl_credentils = SslCredentials() + ssl_credentials = adc_ssl_credentils.ssl_credentials + else: + ssl_credentials = grpc.ssl_channel_credentials() + + # Combine the ssl credentials and the authorization credentials. + composite_credentials = grpc.composite_channel_credentials( + ssl_credentials, google_auth_credentials + ) + + return grpc.secure_channel(target, composite_credentials, **kwargs) + + +class SslCredentials: + """Class for application default SSL credentials. + + The behavior is controlled by `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment + variable whose default value is `false`. Client certificate will not be used + unless the environment variable is explicitly set to `true`. See + https://google.aip.dev/auth/4114 + + If the environment variable is `true`, then for devices with endpoint verification + support, a device certificate will be automatically loaded and mutual TLS will + be established. + See https://cloud.google.com/endpoint-verification/docs/overview. + """ + + def __init__(self): + use_client_cert = _mtls_helper.check_use_client_cert() + if not use_client_cert: + self._is_mtls = False + else: + # Load client SSL credentials. + metadata_path = _mtls_helper._check_config_path( + _mtls_helper.CONTEXT_AWARE_METADATA_PATH + ) + self._is_mtls = metadata_path is not None + + @property + def ssl_credentials(self): + """Get the created SSL channel credentials. + + For devices with endpoint verification support, if the device certificate + loading has any problems, corresponding exceptions will be raised. For + a device without endpoint verification support, no exceptions will be + raised. + + Returns: + grpc.ChannelCredentials: The created grpc channel credentials. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel + creation failed for any reason. + """ + if self._is_mtls: + try: + _, cert, key, _ = _mtls_helper.get_client_ssl_credentials() + self._ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + except exceptions.ClientCertError as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + raise new_exc from caught_exc + else: + self._ssl_credentials = grpc.ssl_channel_credentials() + + return self._ssl_credentials + + @property + def is_mtls(self): + """Indicates if the created SSL channel credentials is mutual TLS.""" + return self._is_mtls diff --git a/intelliaide/vendor/google/auth/transport/mtls.py b/intelliaide/vendor/google/auth/transport/mtls.py new file mode 100644 index 0000000..666a6ca --- /dev/null +++ b/intelliaide/vendor/google/auth/transport/mtls.py @@ -0,0 +1,142 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Utilites for mutual TLS.""" + +from os import getenv + +from google.auth import exceptions +from google.auth.transport import _mtls_helper + + +def has_default_client_cert_source(include_context_aware=True): + """Check if default client SSL credentials exists on the device. + + Args: + include_context_aware (bool): include_context_aware indicates if context_aware + path location will be checked or should it be skipped. + + Returns: + bool: indicating if the default client cert source exists. + """ + if ( + include_context_aware + and _mtls_helper._check_config_path(_mtls_helper.CONTEXT_AWARE_METADATA_PATH) + is not None + ): + return True + if ( + _mtls_helper._check_config_path( + _mtls_helper.CERTIFICATE_CONFIGURATION_DEFAULT_PATH + ) + is not None + ): + return True + cert_config_path = getenv("GOOGLE_API_CERTIFICATE_CONFIG") + if ( + cert_config_path + and _mtls_helper._check_config_path(cert_config_path) is not None + ): + return True + return False + + +def default_client_cert_source(): + """Get a callback which returns the default client SSL credentials. + + Returns: + Callable[[], [bytes, bytes]]: A callback which returns the default + client certificate bytes and private key bytes, both in PEM format. + + Raises: + google.auth.exceptions.DefaultClientCertSourceError: If the default + client SSL credentials don't exist or are malformed. + """ + if not has_default_client_cert_source(include_context_aware=True): + raise exceptions.MutualTLSChannelError( + "Default client cert source doesn't exist" + ) + + def callback(): + try: + _, cert_bytes, key_bytes = _mtls_helper.get_client_cert_and_key() + except (OSError, RuntimeError, ValueError) as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + raise new_exc from caught_exc + + return cert_bytes, key_bytes + + return callback + + +def default_client_encrypted_cert_source(cert_path, key_path): + """Get a callback which returns the default encrpyted client SSL credentials. + + Args: + cert_path (str): The cert file path. The default client certificate will + be written to this file when the returned callback is called. + key_path (str): The key file path. The default encrypted client key will + be written to this file when the returned callback is called. + + Returns: + Callable[[], [str, str, bytes]]: A callback which generates the default + client certificate, encrpyted private key and passphrase. It writes + the certificate and private key into the cert_path and key_path, and + returns the cert_path, key_path and passphrase bytes. + + Raises: + google.auth.exceptions.DefaultClientCertSourceError: If any problem + occurs when loading or saving the client certificate and key. + """ + if not has_default_client_cert_source(include_context_aware=True): + raise exceptions.MutualTLSChannelError( + "Default client encrypted cert source doesn't exist" + ) + + def callback(): + try: + ( + _, + cert_bytes, + key_bytes, + passphrase_bytes, + ) = _mtls_helper.get_client_ssl_credentials(generate_encrypted_key=True) + with open(cert_path, "wb") as cert_file: + cert_file.write(cert_bytes) + with open(key_path, "wb") as key_file: + key_file.write(key_bytes) + except (exceptions.ClientCertError, OSError) as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + raise new_exc from caught_exc + + return cert_path, key_path, passphrase_bytes + + return callback + + +def should_use_client_cert(): + """Returns boolean for whether the client certificate should be used for mTLS. + + This is a wrapper around _mtls_helper.check_use_client_cert(). + If GOOGLE_API_USE_CLIENT_CERTIFICATE is set to true or false, a corresponding + bool value will be returned + If GOOGLE_API_USE_CLIENT_CERTIFICATE is unset, the value will be inferred by + reading a file pointed at by GOOGLE_API_CERTIFICATE_CONFIG, and verifying it + contains a "workload" section. If so, the function will return True, + otherwise False. + + Returns: + bool: indicating whether the client certificate should be used for mTLS. + """ + return _mtls_helper.check_use_client_cert() diff --git a/intelliaide/vendor/google/auth/transport/requests.py b/intelliaide/vendor/google/auth/transport/requests.py new file mode 100644 index 0000000..9735762 --- /dev/null +++ b/intelliaide/vendor/google/auth/transport/requests.py @@ -0,0 +1,634 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Transport adapter for Requests.""" + +from __future__ import absolute_import + +import functools +import http.client as http_client +import logging +import numbers +import time +from typing import Optional + +try: + import requests +except ImportError as caught_exc: # pragma: NO COVER + raise ImportError( + "The requests library is not installed from please install the requests package to use the requests transport." + ) from caught_exc +import requests.adapters # pylint: disable=ungrouped-imports +import requests.exceptions # pylint: disable=ungrouped-imports +from requests.packages.urllib3.util.ssl_ import ( # type: ignore + create_urllib3_context, +) # pylint: disable=ungrouped-imports + +from google.auth import _helpers +from google.auth import exceptions +from google.auth import transport +from google.auth.transport import _mtls_helper +import google.auth.transport._mtls_helper +from google.oauth2 import service_account + +_LOGGER = logging.getLogger(__name__) + +_DEFAULT_TIMEOUT = 120 # in seconds + + +class _Response(transport.Response): + """Requests transport response adapter. + + Args: + response (requests.Response): The raw Requests response. + """ + + def __init__(self, response): + self._response = response + + @property + def status(self): + return self._response.status_code + + @property + def headers(self): + return self._response.headers + + @property + def data(self): + return self._response.content + + +class TimeoutGuard(object): + """A context manager raising an error if the suite execution took too long. + + Args: + timeout (Union[None, Union[float, Tuple[float, float]]]): + The maximum number of seconds a suite can run without the context + manager raising a timeout exception on exit. If passed as a tuple, + the smaller of the values is taken as a timeout. If ``None``, a + timeout error is never raised. + timeout_error_type (Optional[Exception]): + The type of the error to raise on timeout. Defaults to + :class:`requests.exceptions.Timeout`. + """ + + def __init__(self, timeout, timeout_error_type=requests.exceptions.Timeout): + self._timeout = timeout + self.remaining_timeout = timeout + self._timeout_error_type = timeout_error_type + + def __enter__(self): + self._start = time.time() + return self + + def __exit__(self, exc_type, exc_value, traceback): + if exc_value: + return # let the error bubble up automatically + + if self._timeout is None: + return # nothing to do, the timeout was not specified + + elapsed = time.time() - self._start + deadline_hit = False + + if isinstance(self._timeout, numbers.Number): + self.remaining_timeout = self._timeout - elapsed + deadline_hit = self.remaining_timeout <= 0 + else: + self.remaining_timeout = tuple(x - elapsed for x in self._timeout) + deadline_hit = min(self.remaining_timeout) <= 0 + + if deadline_hit: + raise self._timeout_error_type() + + +class Request(transport.Request): + """Requests request adapter. + + This class is used internally for making requests using various transports + in a consistent way. If you use :class:`AuthorizedSession` you do not need + to construct or use this class directly. + + This class can be useful if you want to manually refresh a + :class:`~google.auth.credentials.Credentials` instance:: + + import google.auth.transport.requests + import requests + + request = google.auth.transport.requests.Request() + + credentials.refresh(request) + + Args: + session (requests.Session): An instance :class:`requests.Session` used + to make HTTP requests. If not specified, a session will be created. + + .. automethod:: __call__ + """ + + def __init__(self, session: Optional[requests.Session] = None) -> None: + if not session: + session = requests.Session() + + self.session = session + + def __del__(self): + try: + if hasattr(self, "session") and self.session is not None: + self.session.close() + except TypeError: + # NOTE: For certain Python binary built, the queue.Empty exception + # might not be considered a normal Python exception causing + # TypeError. + pass + + def __call__( + self, + url, + method="GET", + body=None, + headers=None, + timeout=_DEFAULT_TIMEOUT, + **kwargs + ): + """Make an HTTP request using requests. + + Args: + url (str): The URI to be requested. + method (str): The HTTP method to use for the request. Defaults + to 'GET'. + body (bytes): The payload or body in HTTP request. + headers (Mapping[str, str]): Request headers. + timeout (Optional[int]): The number of seconds to wait for a + response from the server. If not specified or if None, the + requests default timeout will be used. + kwargs: Additional arguments passed through to the underlying + requests :meth:`~requests.Session.request` method. + + Returns: + google.auth.transport.Response: The HTTP response. + + Raises: + google.auth.exceptions.TransportError: If any exception occurred. + """ + try: + _helpers.request_log(_LOGGER, method, url, body, headers) + response = self.session.request( + method, url, data=body, headers=headers, timeout=timeout, **kwargs + ) + _helpers.response_log(_LOGGER, response) + return _Response(response) + except requests.exceptions.RequestException as caught_exc: + new_exc = exceptions.TransportError(caught_exc) + raise new_exc from caught_exc + + +class _MutualTlsAdapter(requests.adapters.HTTPAdapter): + """ + A TransportAdapter that enables mutual TLS. + + Args: + cert (bytes): client certificate in PEM format + key (bytes): client private key in PEM format + + Raises: + ImportError: if certifi or pyOpenSSL is not installed + OpenSSL.crypto.Error: if client cert or key is invalid + """ + + def __init__(self, cert, key): + import certifi + from OpenSSL import crypto + import urllib3.contrib.pyopenssl # type: ignore + + urllib3.contrib.pyopenssl.inject_into_urllib3() + + pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key) + x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cert) + + ctx_poolmanager = create_urllib3_context() + ctx_poolmanager.load_verify_locations(cafile=certifi.where()) + ctx_poolmanager._ctx.use_certificate(x509) + ctx_poolmanager._ctx.use_privatekey(pkey) + self._ctx_poolmanager = ctx_poolmanager + + ctx_proxymanager = create_urllib3_context() + ctx_proxymanager.load_verify_locations(cafile=certifi.where()) + ctx_proxymanager._ctx.use_certificate(x509) + ctx_proxymanager._ctx.use_privatekey(pkey) + self._ctx_proxymanager = ctx_proxymanager + + super(_MutualTlsAdapter, self).__init__() + + def init_poolmanager(self, *args, **kwargs): + kwargs["ssl_context"] = self._ctx_poolmanager + super(_MutualTlsAdapter, self).init_poolmanager(*args, **kwargs) + + def proxy_manager_for(self, *args, **kwargs): + kwargs["ssl_context"] = self._ctx_proxymanager + return super(_MutualTlsAdapter, self).proxy_manager_for(*args, **kwargs) + + +class _MutualTlsOffloadAdapter(requests.adapters.HTTPAdapter): + """ + A TransportAdapter that enables mutual TLS and offloads the client side + signing operation to the signing library. + + Args: + enterprise_cert_file_path (str): the path to a enterprise cert JSON + file. The file should contain the following field: + + { + "libs": { + "signer_library": "...", + "offload_library": "..." + } + } + + Raises: + ImportError: if certifi or pyOpenSSL is not installed + google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel + creation failed for any reason. + """ + + def __init__(self, enterprise_cert_file_path): + import certifi + from google.auth.transport import _custom_tls_signer + + self.signer = _custom_tls_signer.CustomTlsSigner(enterprise_cert_file_path) + self.signer.load_libraries() + + import urllib3.contrib.pyopenssl + + urllib3.contrib.pyopenssl.inject_into_urllib3() + + poolmanager = create_urllib3_context() + poolmanager.load_verify_locations(cafile=certifi.where()) + self.signer.attach_to_ssl_context(poolmanager) + self._ctx_poolmanager = poolmanager + + proxymanager = create_urllib3_context() + proxymanager.load_verify_locations(cafile=certifi.where()) + self.signer.attach_to_ssl_context(proxymanager) + self._ctx_proxymanager = proxymanager + + super(_MutualTlsOffloadAdapter, self).__init__() + + def init_poolmanager(self, *args, **kwargs): + kwargs["ssl_context"] = self._ctx_poolmanager + super(_MutualTlsOffloadAdapter, self).init_poolmanager(*args, **kwargs) + + def proxy_manager_for(self, *args, **kwargs): + kwargs["ssl_context"] = self._ctx_proxymanager + return super(_MutualTlsOffloadAdapter, self).proxy_manager_for(*args, **kwargs) + + +class AuthorizedSession(requests.Session): + """A Requests Session class with credentials. + + This class is used to perform requests to API endpoints that require + authorization:: + + from google.auth.transport.requests import AuthorizedSession + + authed_session = AuthorizedSession(credentials) + + response = authed_session.request( + 'GET', 'https://www.googleapis.com/storage/v1/b') + + + The underlying :meth:`request` implementation handles adding the + credentials' headers to the request and refreshing credentials as needed. + + This class also supports mutual TLS via :meth:`configure_mtls_channel` + method. In order to use this method, the `GOOGLE_API_USE_CLIENT_CERTIFICATE` + environment variable must be explicitly set to ``true``, otherwise it does + nothing. Assume the environment is set to ``true``, the method behaves in the + following manner: + + If client_cert_callback is provided, client certificate and private + key are loaded using the callback; if client_cert_callback is None, + application default SSL credentials will be used. Exceptions are raised if + there are problems with the certificate, private key, or the loading process, + so it should be called within a try/except block. + + First we set the environment variable to ``true``, then create an :class:`AuthorizedSession` + instance and specify the endpoints:: + + regular_endpoint = 'https://pubsub.googleapis.com/v1/projects/{my_project_id}/topics' + mtls_endpoint = 'https://pubsub.mtls.googleapis.com/v1/projects/{my_project_id}/topics' + + authed_session = AuthorizedSession(credentials) + + Now we can pass a callback to :meth:`configure_mtls_channel`:: + + def my_cert_callback(): + # some code to load client cert bytes and private key bytes, both in + # PEM format. + some_code_to_load_client_cert_and_key() + if loaded: + return cert, key + raise MyClientCertFailureException() + + # Always call configure_mtls_channel within a try/except block. + try: + authed_session.configure_mtls_channel(my_cert_callback) + except: + # handle exceptions. + + if authed_session.is_mtls: + response = authed_session.request('GET', mtls_endpoint) + else: + response = authed_session.request('GET', regular_endpoint) + + + You can alternatively use application default SSL credentials like this:: + + try: + authed_session.configure_mtls_channel() + except: + # handle exceptions. + + Args: + credentials (google.auth.credentials.Credentials): The credentials to + add to the request. + refresh_status_codes (Sequence[int]): Which HTTP status codes indicate + that credentials should be refreshed and the request should be + retried. + max_refresh_attempts (int): The maximum number of times to attempt to + refresh the credentials and retry the request. + refresh_timeout (Optional[int]): The timeout value in seconds for + credential refresh HTTP requests. + auth_request (google.auth.transport.requests.Request): + (Optional) An instance of + :class:`~google.auth.transport.requests.Request` used when + refreshing credentials. If not passed, + an instance of :class:`~google.auth.transport.requests.Request` + is created. + default_host (Optional[str]): A host like "pubsub.googleapis.com". + This is used when a self-signed JWT is created from service + account credentials. + """ + + def __init__( + self, + credentials, + refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES, + max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS, + refresh_timeout=None, + auth_request=None, + default_host=None, + ): + super(AuthorizedSession, self).__init__() + self.credentials = credentials + self._refresh_status_codes = refresh_status_codes + self._max_refresh_attempts = max_refresh_attempts + self._refresh_timeout = refresh_timeout + self._is_mtls = False + self._default_host = default_host + + if auth_request is None: + self._auth_request_session = requests.Session() + + # Using an adapter to make HTTP requests robust to network errors. + # This adapter retrys HTTP requests when network errors occur + # and the requests seems safely retryable. + retry_adapter = requests.adapters.HTTPAdapter(max_retries=3) + self._auth_request_session.mount("https://", retry_adapter) + + # Do not pass `self` as the session here, as it can lead to + # infinite recursion. + auth_request = Request(self._auth_request_session) + else: + self._auth_request_session = None + + # Request instance used by internal methods (for example, + # credentials.refresh). + self._auth_request = auth_request + + # https://google.aip.dev/auth/4111 + # Attempt to use self-signed JWTs when a service account is used. + if isinstance(self.credentials, service_account.Credentials): + self.credentials._create_self_signed_jwt( + "https://{}/".format(self._default_host) if self._default_host else None + ) + + def configure_mtls_channel(self, client_cert_callback=None): + """Configure the client certificate and key for SSL connection. + + The function does nothing unless `GOOGLE_API_USE_CLIENT_CERTIFICATE` is + explicitly set to `true`. In this case if client certificate and key are + successfully obtained (from the given client_cert_callback or from application + default SSL credentials), a :class:`_MutualTlsAdapter` instance will be mounted + to "https://" prefix. + + Args: + client_cert_callback (Optional[Callable[[], (bytes, bytes)]]): + The optional callback returns the client certificate and private + key bytes both in PEM format. + If the callback is None, application default SSL credentials + will be used. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel + creation failed for any reason. + """ + use_client_cert = google.auth.transport._mtls_helper.check_use_client_cert() + if not use_client_cert: + self._is_mtls = False + return + try: + import OpenSSL + except ImportError as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + raise new_exc from caught_exc + + try: + ( + self._is_mtls, + cert, + key, + ) = google.auth.transport._mtls_helper.get_client_cert_and_key( + client_cert_callback + ) + + if self._is_mtls: + mtls_adapter = _MutualTlsAdapter(cert, key) + self._cached_cert = cert + self.mount("https://", mtls_adapter) + except ( + exceptions.ClientCertError, + ImportError, + OpenSSL.crypto.Error, + ) as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + raise new_exc from caught_exc + + def request( + self, + method, + url, + data=None, + headers=None, + max_allowed_time=None, + timeout=_DEFAULT_TIMEOUT, + **kwargs + ): + """Implementation of Requests' request. + + Args: + timeout (Optional[Union[float, Tuple[float, float]]]): + The amount of time in seconds to wait for the server response + with each individual request. Can also be passed as a tuple + ``(connect_timeout, read_timeout)``. See :meth:`requests.Session.request` + documentation for details. + max_allowed_time (Optional[float]): + If the method runs longer than this, a ``Timeout`` exception is + automatically raised. Unlike the ``timeout`` parameter, this + value applies to the total method execution time, even if + multiple requests are made under the hood. + + Mind that it is not guaranteed that the timeout error is raised + at ``max_allowed_time``. It might take longer, for example, if + an underlying request takes a lot of time, but the request + itself does not timeout, e.g. if a large file is being + transmitted. The timeout error will be raised after such + request completes. + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS + channel creation fails for any reason. + ValueError: If the client certificate is invalid. + """ + # pylint: disable=arguments-differ + # Requests has a ton of arguments to request, but only two + # (method, url) are required. We pass through all of the other + # arguments to super, so no need to exhaustively list them here. + + # Use a kwarg for this instead of an attribute to maintain + # thread-safety. + _credential_refresh_attempt = kwargs.pop("_credential_refresh_attempt", 0) + + # Make a copy of the headers. They will be modified by the credentials + # and we want to pass the original headers if we recurse. + request_headers = headers.copy() if headers is not None else {} + + # Do not apply the timeout unconditionally in order to not override the + # _auth_request's default timeout. + auth_request = ( + self._auth_request + if timeout is None + else functools.partial(self._auth_request, timeout=timeout) + ) + + remaining_time = max_allowed_time + + with TimeoutGuard(remaining_time) as guard: + self.credentials.before_request(auth_request, method, url, request_headers) + remaining_time = guard.remaining_timeout + + with TimeoutGuard(remaining_time) as guard: + _helpers.request_log(_LOGGER, method, url, data, headers) + response = super(AuthorizedSession, self).request( + method, + url, + data=data, + headers=request_headers, + timeout=timeout, + **kwargs + ) + remaining_time = guard.remaining_timeout + + # If the response indicated that the credentials needed to be + # refreshed, then refresh the credentials and re-attempt the + # request. + # A stored token may expire between the time it is retrieved and + # the time the request is made, so we may need to try twice. + if ( + response.status_code in self._refresh_status_codes + and _credential_refresh_attempt < self._max_refresh_attempts + ): + # Handle unauthorized permission error(401 status code) + if response.status_code == http_client.UNAUTHORIZED: + if self.is_mtls: + ( + call_cert_bytes, + call_key_bytes, + cached_fingerprint, + current_cert_fingerprint, + ) = _mtls_helper.check_parameters_for_unauthorized_response( + self._cached_cert + ) + if cached_fingerprint != current_cert_fingerprint: + try: + _LOGGER.info( + "Client certificate has changed, reconfiguring mTLS " + "channel." + ) + self.configure_mtls_channel( + lambda: (call_cert_bytes, call_key_bytes) + ) + except Exception as e: + _LOGGER.error("Failed to reconfigure mTLS channel: %s", e) + raise exceptions.MutualTLSChannelError( + "Failed to reconfigure mTLS channel" + ) from e + else: + _LOGGER.info( + "Skipping reconfiguration of mTLS channel because the client" + " certificate has not changed." + ) + _LOGGER.info( + "Refreshing credentials due to a %s response. Attempt %s/%s.", + response.status_code, + _credential_refresh_attempt + 1, + self._max_refresh_attempts, + ) + + # Do not apply the timeout unconditionally in order to not override the + # _auth_request's default timeout. + auth_request = ( + self._auth_request + if timeout is None + else functools.partial(self._auth_request, timeout=timeout) + ) + + with TimeoutGuard(remaining_time) as guard: + self.credentials.refresh(auth_request) + remaining_time = guard.remaining_timeout + + # Recurse. Pass in the original headers, not our modified set, but + # do pass the adjusted max allowed time (i.e. the remaining total time). + return self.request( + method, + url, + data=data, + headers=headers, + max_allowed_time=remaining_time, + timeout=timeout, + _credential_refresh_attempt=_credential_refresh_attempt + 1, + **kwargs + ) + + return response + + @property + def is_mtls(self): + """Indicates if the created SSL channel is mutual TLS.""" + return self._is_mtls + + def close(self): + if self._auth_request_session is not None: + self._auth_request_session.close() + super(AuthorizedSession, self).close() diff --git a/intelliaide/vendor/google/auth/transport/urllib3.py b/intelliaide/vendor/google/auth/transport/urllib3.py new file mode 100644 index 0000000..de07007 --- /dev/null +++ b/intelliaide/vendor/google/auth/transport/urllib3.py @@ -0,0 +1,493 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Transport adapter for urllib3.""" + +from __future__ import absolute_import + +import http.client as http_client +import logging +import warnings + +# Certifi is Mozilla's certificate bundle. Urllib3 needs a certificate bundle +# to verify HTTPS requests, and certifi is the recommended and most reliable +# way to get a root certificate bundle. See +# http://urllib3.readthedocs.io/en/latest/user-guide.html\ +# #certificate-verification +# For more details. +try: + import certifi +except ImportError: # pragma: NO COVER + certifi = None # type: ignore + +try: + import urllib3 # type: ignore + import urllib3.exceptions # type: ignore + from packaging import version # type: ignore +except ImportError as caught_exc: # pragma: NO COVER + raise ImportError( + "" + f"Error: {caught_exc}." + " The 'google-auth' library requires the extras installed " + "for urllib3 network transport." + "\n" + "Please install the necessary dependencies using pip:\n" + " pip install google-auth[urllib3]\n" + "\n" + "(Note: Using '[urllib3]' ensures the specific dependencies needed for this feature are installed. " + "We recommend running this command in your virtual environment.)" + ) from caught_exc + + +from google.auth import _helpers +from google.auth import exceptions +from google.auth import transport +from google.auth.transport import _mtls_helper +from google.oauth2 import service_account + +if version.parse(urllib3.__version__) >= version.parse("2.0.0"): # pragma: NO COVER + RequestMethods = urllib3._request_methods.RequestMethods # type: ignore +else: # pragma: NO COVER + RequestMethods = urllib3.request.RequestMethods # type: ignore + +_LOGGER = logging.getLogger(__name__) + + +class _Response(transport.Response): + """urllib3 transport response adapter. + + Args: + response (urllib3.response.HTTPResponse): The raw urllib3 response. + """ + + def __init__(self, response): + self._response = response + + @property + def status(self): + return self._response.status + + @property + def headers(self): + return self._response.headers + + @property + def data(self): + return self._response.data + + +class Request(transport.Request): + """urllib3 request adapter. + + This class is used internally for making requests using various transports + in a consistent way. If you use :class:`AuthorizedHttp` you do not need + to construct or use this class directly. + + This class can be useful if you want to manually refresh a + :class:`~google.auth.credentials.Credentials` instance:: + + import google.auth.transport.urllib3 + import urllib3 + + http = urllib3.PoolManager() + request = google.auth.transport.urllib3.Request(http) + + credentials.refresh(request) + + Args: + http (urllib3.PoolManager): An instance of a urllib3 class that implements + the request interface (e.g. :class:`urllib3.PoolManager`). + + .. automethod:: __call__ + """ + + def __init__(self, http): + self.http = http + + def __call__( + self, url, method="GET", body=None, headers=None, timeout=None, **kwargs + ): + """Make an HTTP request using urllib3. + + Args: + url (str): The URI to be requested. + method (str): The HTTP method to use for the request. Defaults + to 'GET'. + body (bytes): The payload / body in HTTP request. + headers (Mapping[str, str]): Request headers. + timeout (Optional[int]): The number of seconds to wait for a + response from the server. If not specified or if None, the + urllib3 default timeout will be used. + kwargs: Additional arguments passed throught to the underlying + urllib3 :meth:`urlopen` method. + + Returns: + google.auth.transport.Response: The HTTP response. + + Raises: + google.auth.exceptions.TransportError: If any exception occurred. + """ + # urllib3 uses a sentinel default value for timeout, so only set it if + # specified. + if timeout is not None: + kwargs["timeout"] = timeout + + try: + _helpers.request_log(_LOGGER, method, url, body, headers) + response = self.http.request( + method, url, body=body, headers=headers, **kwargs + ) + _helpers.response_log(_LOGGER, response) + return _Response(response) + except urllib3.exceptions.HTTPError as caught_exc: + new_exc = exceptions.TransportError(caught_exc) + raise new_exc from caught_exc + + +def _make_default_http(): + if certifi is not None: + return urllib3.PoolManager(cert_reqs="CERT_REQUIRED", ca_certs=certifi.where()) + else: + return urllib3.PoolManager() + + +def _make_mutual_tls_http(cert, key): + """Create a mutual TLS HTTP connection with the given client cert and key. + See https://github.com/urllib3/urllib3/issues/474#issuecomment-253168415 + + Args: + cert (bytes): client certificate in PEM format + key (bytes): client private key in PEM format + + Returns: + urllib3.PoolManager: Mutual TLS HTTP connection. + + Raises: + ImportError: If certifi or pyOpenSSL is not installed. + OpenSSL.crypto.Error: If the cert or key is invalid. + """ + import certifi + from OpenSSL import crypto + import urllib3.contrib.pyopenssl # type: ignore + + urllib3.contrib.pyopenssl.inject_into_urllib3() + ctx = urllib3.util.ssl_.create_urllib3_context() + ctx.load_verify_locations(cafile=certifi.where()) + + pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key) + x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cert) + + ctx._ctx.use_certificate(x509) + ctx._ctx.use_privatekey(pkey) + + http = urllib3.PoolManager(ssl_context=ctx) + return http + + +class AuthorizedHttp(RequestMethods): # type: ignore + """A urllib3 HTTP class with credentials. + + This class is used to perform requests to API endpoints that require + authorization:: + + from google.auth.transport.urllib3 import AuthorizedHttp + + authed_http = AuthorizedHttp(credentials) + + response = authed_http.request( + 'GET', 'https://www.googleapis.com/storage/v1/b') + + This class implements the urllib3 request interface and can be + used just like any other :class:`urllib3.PoolManager`. + + The underlying :meth:`urlopen` implementation handles adding the + credentials' headers to the request and refreshing credentials as needed. + + This class also supports mutual TLS via :meth:`configure_mtls_channel` + method. In order to use this method, the `GOOGLE_API_USE_CLIENT_CERTIFICATE` + environment variable must be explicitly set to `true`, otherwise it does + nothing. Assume the environment is set to `true`, the method behaves in the + following manner: + If client_cert_callback is provided, client certificate and private + key are loaded using the callback; if client_cert_callback is None, + application default SSL credentials will be used. Exceptions are raised if + there are problems with the certificate, private key, or the loading process, + so it should be called within a try/except block. + + First we set the environment variable to `true`, then create an :class:`AuthorizedHttp` + instance and specify the endpoints:: + + regular_endpoint = 'https://pubsub.googleapis.com/v1/projects/{my_project_id}/topics' + mtls_endpoint = 'https://pubsub.mtls.googleapis.com/v1/projects/{my_project_id}/topics' + + authed_http = AuthorizedHttp(credentials) + + Now we can pass a callback to :meth:`configure_mtls_channel`:: + + def my_cert_callback(): + # some code to load client cert bytes and private key bytes, both in + # PEM format. + some_code_to_load_client_cert_and_key() + if loaded: + return cert, key + raise MyClientCertFailureException() + + # Always call configure_mtls_channel within a try/except block. + try: + is_mtls = authed_http.configure_mtls_channel(my_cert_callback) + except: + # handle exceptions. + + if is_mtls: + response = authed_http.request('GET', mtls_endpoint) + else: + response = authed_http.request('GET', regular_endpoint) + + You can alternatively use application default SSL credentials like this:: + + try: + is_mtls = authed_http.configure_mtls_channel() + except: + # handle exceptions. + + Args: + credentials (google.auth.credentials.Credentials): The credentials to + add to the request. + http (urllib3.PoolManager): The underlying HTTP object to + use to make requests. If not specified, a + :class:`urllib3.PoolManager` instance will be constructed with + sane defaults. + refresh_status_codes (Sequence[int]): Which HTTP status codes indicate + that credentials should be refreshed and the request should be + retried. + max_refresh_attempts (int): The maximum number of times to attempt to + refresh the credentials and retry the request. + default_host (Optional[str]): A host like "pubsub.googleapis.com". + This is used when a self-signed JWT is created from service + account credentials. + """ + + def __init__( + self, + credentials, + http=None, + refresh_status_codes=transport.DEFAULT_REFRESH_STATUS_CODES, + max_refresh_attempts=transport.DEFAULT_MAX_REFRESH_ATTEMPTS, + default_host=None, + ): + if http is None: + self.http = _make_default_http() + self._has_user_provided_http = False + else: + self.http = http + self._has_user_provided_http = True + + self.credentials = credentials + self._refresh_status_codes = refresh_status_codes + self._max_refresh_attempts = max_refresh_attempts + self._default_host = default_host + # Request instance used by internal methods (for example, + # credentials.refresh). + self._request = Request(self.http) + self._is_mtls = False + + # https://google.aip.dev/auth/4111 + # Attempt to use self-signed JWTs when a service account is used. + if isinstance(self.credentials, service_account.Credentials): + self.credentials._create_self_signed_jwt( + "https://{}/".format(self._default_host) if self._default_host else None + ) + + super(AuthorizedHttp, self).__init__() + + def configure_mtls_channel(self, client_cert_callback=None): + """Configures mutual TLS channel using the given client_cert_callback or + application default SSL credentials. The behavior is controlled by + `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable. + (1) If the environment variable value is `true`, the function returns True + if the channel is mutual TLS and False otherwise. The `http` provided + in the constructor will be overwritten. + (2) If the environment variable is not set or `false`, the function does + nothing and it always return False. + + Args: + client_cert_callback (Optional[Callable[[], (bytes, bytes)]]): + The optional callback returns the client certificate and private + key bytes both in PEM format. + If the callback is None, application default SSL credentials + will be used. + + Returns: + True if the channel is mutual TLS and False otherwise. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS channel + creation failed for any reason. + """ + use_client_cert = transport._mtls_helper.check_use_client_cert() + if not use_client_cert: + self._is_mtls = False + return False + else: + self._is_mtls = True + try: + import OpenSSL + except ImportError as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + raise new_exc from caught_exc + + try: + found_cert_key, cert, key = transport._mtls_helper.get_client_cert_and_key( + client_cert_callback + ) + + if found_cert_key: + self.http = _make_mutual_tls_http(cert, key) + self._cached_cert = cert + else: + self.http = _make_default_http() + except ( + exceptions.ClientCertError, + ImportError, + OpenSSL.crypto.Error, + ) as caught_exc: + new_exc = exceptions.MutualTLSChannelError(caught_exc) + raise new_exc from caught_exc + + if self._has_user_provided_http: + self._has_user_provided_http = False + warnings.warn( + "`http` provided in the constructor is overwritten", UserWarning + ) + + return found_cert_key + + def urlopen(self, method, url, body=None, headers=None, **kwargs): + """Implementation of urllib3's urlopen.""" + # pylint: disable=arguments-differ + # We use kwargs to collect additional args that we don't need to + # introspect here. However, we do explicitly collect the two + # positional arguments. + + # Use a kwarg for this instead of an attribute to maintain + # thread-safety. + _credential_refresh_attempt = kwargs.pop("_credential_refresh_attempt", 0) + + if headers is None: + headers = self.headers + + use_mtls = False + if self._is_mtls: + MTLS_URL_PREFIXES = ["mtls.googleapis.com", "mtls.sandbox.googleapis.com"] + use_mtls = any([prefix in url for prefix in MTLS_URL_PREFIXES]) + + # Make a copy of the headers. They will be modified by the credentials + # and we want to pass the original headers if we recurse. + request_headers = headers.copy() + + self.credentials.before_request(self._request, method, url, request_headers) + + response = self.http.urlopen( + method, url, body=body, headers=request_headers, **kwargs + ) + + # If the response indicated that the credentials needed to be + # refreshed, then refresh the credentials and re-attempt the + # request. + # A stored token may expire between the time it is retrieved and + # the time the request is made, so we may need to try twice. + # The reason urllib3's retries aren't used is because they + # don't allow you to modify the request headers. :/ + if ( + response.status in self._refresh_status_codes + and _credential_refresh_attempt < self._max_refresh_attempts + ): + if response.status == http_client.UNAUTHORIZED: + if use_mtls: + ( + call_cert_bytes, + call_key_bytes, + cached_fingerprint, + current_cert_fingerprint, + ) = _mtls_helper.check_parameters_for_unauthorized_response( + self._cached_cert + ) + if cached_fingerprint != current_cert_fingerprint: + try: + _LOGGER.info( + "Client certificate has changed, reconfiguring mTLS " + "channel." + ) + self.configure_mtls_channel( + client_cert_callback=lambda: ( + call_cert_bytes, + call_key_bytes, + ) + ) + except Exception as e: + _LOGGER.error("Failed to reconfigure mTLS channel: %s", e) + raise exceptions.MutualTLSChannelError( + "Failed to reconfigure mTLS channel" + ) from e + + else: + _LOGGER.info( + "Skipping reconfiguration of mTLS channel because the " + "client certificate has not changed." + ) + + _LOGGER.info( + "Refreshing credentials due to a %s response. Attempt %s/%s.", + response.status, + _credential_refresh_attempt + 1, + self._max_refresh_attempts, + ) + + self.credentials.refresh(self._request) + + # Recurse. Pass in the original headers, not our modified set. + return self.urlopen( + method, + url, + body=body, + headers=headers, + _credential_refresh_attempt=_credential_refresh_attempt + 1, + **kwargs, + ) + + return response + + # Proxy methods for compliance with the urllib3.PoolManager interface + + def __enter__(self): + """Proxy to ``self.http``.""" + return self.http.__enter__() + + def __exit__(self, exc_type, exc_val, exc_tb): + """Proxy to ``self.http``.""" + return self.http.__exit__(exc_type, exc_val, exc_tb) + + def __del__(self): + if hasattr(self, "http") and self.http is not None: + self.http.clear() + + @property + def headers(self): + """Proxy to ``self.http``.""" + return self.http.headers + + @headers.setter + def headers(self, value): + """Proxy to ``self.http``.""" + self.http.headers = value diff --git a/intelliaide/vendor/google/auth/version.py b/intelliaide/vendor/google/auth/version.py new file mode 100644 index 0000000..4c624ee --- /dev/null +++ b/intelliaide/vendor/google/auth/version.py @@ -0,0 +1,15 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__version__ = "2.53.0" diff --git a/intelliaide/vendor/google/oauth2/__init__.py b/intelliaide/vendor/google/oauth2/__init__.py new file mode 100644 index 0000000..4fb71fd --- /dev/null +++ b/intelliaide/vendor/google/oauth2/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google OAuth 2.0 Library for Python.""" diff --git a/intelliaide/vendor/google/oauth2/_client.py b/intelliaide/vendor/google/oauth2/_client.py new file mode 100644 index 0000000..1c7ba46 --- /dev/null +++ b/intelliaide/vendor/google/oauth2/_client.py @@ -0,0 +1,649 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""OAuth 2.0 client. + +This is a client for interacting with an OAuth 2.0 authorization server's +token endpoint. + +For more information about the token endpoint, see +`Section 3.1 of rfc6749`_ + +.. _Section 3.1 of rfc6749: https://tools.ietf.org/html/rfc6749#section-3.2 +""" + +import datetime +import http.client as http_client +import json +import logging +import urllib + +from google.auth import _exponential_backoff +from google.auth import _helpers +from google.auth import credentials +from google.auth import exceptions +from google.auth import jwt +from google.auth import metrics +from google.auth import transport + +_LOGGER = logging.getLogger(__name__) + +_URLENCODED_CONTENT_TYPE = "application/x-www-form-urlencoded" +_JSON_CONTENT_TYPE = "application/json" +_JWT_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer" +_REFRESH_GRANT_TYPE = "refresh_token" +_BLOCKING_REGIONAL_ACCESS_BOUNDARY_LOOKUP_TIMEOUT = 3 + + +def _handle_error_response(response_data, retryable_error): + """Translates an error response into an exception. + + Args: + response_data (Mapping | str): The decoded response data. + retryable_error Optional[bool]: A boolean indicating if an error is retryable. + Defaults to False. + + Raises: + google.auth.exceptions.RefreshError: The errors contained in response_data. + """ + + retryable_error = retryable_error if retryable_error else False + + if isinstance(response_data, str): + raise exceptions.RefreshError(response_data, retryable=retryable_error) + try: + error_details = "{}: {}".format( + response_data["error"], response_data.get("error_description") + ) + # If no details could be extracted, use the response data. + except (KeyError, ValueError): + error_details = json.dumps(response_data) + + raise exceptions.RefreshError( + error_details, response_data, retryable=retryable_error + ) + + +def _can_retry(status_code, response_data): + """Checks if a request can be retried by inspecting the status code + and response body of the request. + + Args: + status_code (int): The response status code. + response_data (Mapping | str): The decoded response data. + + Returns: + bool: True if the response is retryable. False otherwise. + """ + if status_code in transport.DEFAULT_RETRYABLE_STATUS_CODES: + return True + + try: + # For a failed response, response_body could be a string + error_desc = response_data.get("error_description") or "" + error_code = response_data.get("error") or "" + + if not isinstance(error_code, str) or not isinstance(error_desc, str): + return False + + # Per Oauth 2.0 RFC https://www.rfc-editor.org/rfc/rfc6749.html#section-4.1.2.1 + # This is needed because a redirect will not return a 500 status code. + retryable_error_descriptions = { + "internal_failure", + "server_error", + "temporarily_unavailable", + } + + if any(e in retryable_error_descriptions for e in (error_code, error_desc)): + return True + + except AttributeError: + pass + + return False + + +def _parse_expiry(response_data): + """Parses the expiry field from a response into a datetime. + + Args: + response_data (Mapping): The JSON-parsed response data. + + Returns: + Optional[datetime]: The expiration or ``None`` if no expiration was + specified. + """ + expires_in = response_data.get("expires_in", None) + + if expires_in is not None: + # Some services do not respect the OAUTH2.0 RFC and send expires_in as a + # JSON String. + if isinstance(expires_in, str): + expires_in = int(expires_in) + + return _helpers.utcnow() + datetime.timedelta(seconds=expires_in) + else: + return None + + +def _token_endpoint_request_no_throw( + request, + token_uri, + body, + access_token=None, + use_json=False, + can_retry=True, + headers=None, + **kwargs +): + """Makes a request to the OAuth 2.0 authorization server's token endpoint. + This function doesn't throw on response errors. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + body (Mapping[str, str]): The parameters to send in the request body. + access_token (Optional(str)): The access token needed to make the request. + use_json (Optional(bool)): Use urlencoded format or json format for the + content type. The default value is False. + can_retry (bool): Enable or disable request retry behavior. + headers (Optional[Mapping[str, str]]): The headers for the request. + kwargs: Additional arguments passed on to the request method. The + kwargs will be passed to `requests.request` method, see: + https://docs.python-requests.org/en/latest/api/#requests.request. + For example, you can use `cert=("cert_pem_path", "key_pem_path")` + to set up client side SSL certificate, and use + `verify="ca_bundle_path"` to set up the CA certificates for sever + side SSL certificate verification. + + Returns: + Tuple(bool, Mapping[str, str], Optional[bool]): A boolean indicating + if the request is successful, a mapping for the JSON-decoded response + data and in the case of an error a boolean indicating if the error + is retryable. + """ + if use_json: + headers_to_use = {"Content-Type": _JSON_CONTENT_TYPE} + body = json.dumps(body).encode("utf-8") + else: + headers_to_use = {"Content-Type": _URLENCODED_CONTENT_TYPE} + body = urllib.parse.urlencode(body).encode("utf-8") + + if access_token: + headers_to_use["Authorization"] = "Bearer {}".format(access_token) + + if headers: + headers_to_use.update(headers) + + response_data = {} + retryable_error = False + + retries = _exponential_backoff.ExponentialBackoff() + for _ in retries: + response = request( + method="POST", url=token_uri, headers=headers_to_use, body=body, **kwargs + ) + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + try: + # response_body should be a JSON + response_data = json.loads(response_body) + except ValueError: + response_data = response_body + + if response.status == http_client.OK: + return True, response_data, None + + retryable_error = _can_retry( + status_code=response.status, response_data=response_data + ) + + if not can_retry or not retryable_error: + return False, response_data, retryable_error + + return False, response_data, retryable_error + + +def _token_endpoint_request( + request, + token_uri, + body, + access_token=None, + use_json=False, + can_retry=True, + headers=None, + **kwargs +): + """Makes a request to the OAuth 2.0 authorization server's token endpoint. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + body (Mapping[str, str]): The parameters to send in the request body. + access_token (Optional(str)): The access token needed to make the request. + use_json (Optional(bool)): Use urlencoded format or json format for the + content type. The default value is False. + can_retry (bool): Enable or disable request retry behavior. + headers (Optional[Mapping[str, str]]): The headers for the request. + kwargs: Additional arguments passed on to the request method. The + kwargs will be passed to `requests.request` method, see: + https://docs.python-requests.org/en/latest/api/#requests.request. + For example, you can use `cert=("cert_pem_path", "key_pem_path")` + to set up client side SSL certificate, and use + `verify="ca_bundle_path"` to set up the CA certificates for sever + side SSL certificate verification. + + Returns: + Mapping[str, str]: The JSON-decoded response data. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + """ + + ( + response_status_ok, + response_data, + retryable_error, + ) = _token_endpoint_request_no_throw( + request, + token_uri, + body, + access_token=access_token, + use_json=use_json, + can_retry=can_retry, + headers=headers, + **kwargs + ) + if not response_status_ok: + _handle_error_response(response_data, retryable_error) + return response_data + + +def jwt_grant(request, token_uri, assertion, can_retry=True): + """Implements the JWT Profile for OAuth 2.0 Authorization Grants. + + For more details, see `rfc7523 section 4`_. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + assertion (str): The OAuth 2.0 assertion. + can_retry (bool): Enable or disable request retry behavior. + + Returns: + Tuple[str, Optional[datetime], Mapping[str, str]]: The access token, + expiration, and additional data returned by the token endpoint. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + + .. _rfc7523 section 4: https://tools.ietf.org/html/rfc7523#section-4 + """ + body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE} + + response_data = _token_endpoint_request( + request, + token_uri, + body, + can_retry=can_retry, + headers={ + metrics.API_CLIENT_HEADER: metrics.token_request_access_token_sa_assertion() + }, + ) + + try: + access_token = response_data["access_token"] + except KeyError as caught_exc: + new_exc = exceptions.RefreshError( + "No access token in response.", response_data, retryable=False + ) + raise new_exc from caught_exc + + expiry = _parse_expiry(response_data) + + return access_token, expiry, response_data + + +def call_iam_generate_id_token_endpoint( + request, + iam_id_token_endpoint, + signer_email, + audience, + access_token, + universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, +): + """Call iam.generateIdToken endpoint to get ID token. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + iam_id_token_endpoint (str): The IAM ID token endpoint to use. + signer_email (str): The signer email used to form the IAM + generateIdToken endpoint. + audience (str): The audience for the ID token. + access_token (str): The access token used to call the IAM endpoint. + universe_domain (str): The universe domain for the request. The + default is ``googleapis.com``. + + Returns: + Tuple[str, datetime]: The ID token and expiration. + """ + body = {"audience": audience, "includeEmail": "true", "useEmailAzp": "true"} + + response_data = _token_endpoint_request( + request, + iam_id_token_endpoint.replace( + credentials.DEFAULT_UNIVERSE_DOMAIN, universe_domain + ).format(signer_email), + body, + access_token=access_token, + use_json=True, + ) + + try: + id_token = response_data["token"] + except KeyError as caught_exc: + new_exc = exceptions.RefreshError( + "No ID token in response.", response_data, retryable=False + ) + raise new_exc from caught_exc + + payload = jwt.decode(id_token, verify=False) + expiry = _helpers.utcfromtimestamp(payload["exp"]) + + return id_token, expiry + + +def id_token_jwt_grant(request, token_uri, assertion, can_retry=True): + """Implements the JWT Profile for OAuth 2.0 Authorization Grants, but + requests an OpenID Connect ID Token instead of an access token. + + This is a variant on the standard JWT Profile that is currently unique + to Google. This was added for the benefit of authenticating to services + that require ID Tokens instead of access tokens or JWT bearer tokens. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorization server's token endpoint + URI. + assertion (str): JWT token signed by a service account. The token's + payload must include a ``target_audience`` claim. + can_retry (bool): Enable or disable request retry behavior. + + Returns: + Tuple[str, Optional[datetime], Mapping[str, str]]: + The (encoded) Open ID Connect ID Token, expiration, and additional + data returned by the endpoint. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + """ + body = {"assertion": assertion, "grant_type": _JWT_GRANT_TYPE} + + response_data = _token_endpoint_request( + request, + token_uri, + body, + can_retry=can_retry, + headers={ + metrics.API_CLIENT_HEADER: metrics.token_request_id_token_sa_assertion() + }, + ) + + try: + id_token = response_data["id_token"] + except KeyError as caught_exc: + new_exc = exceptions.RefreshError( + "No ID token in response.", response_data, retryable=False + ) + raise new_exc from caught_exc + + payload = jwt.decode(id_token, verify=False) + expiry = _helpers.utcfromtimestamp(payload["exp"]) + + return id_token, expiry, response_data + + +def _handle_refresh_grant_response(response_data, refresh_token): + """Extract tokens from refresh grant response. + + Args: + response_data (Mapping[str, str]): Refresh grant response data. + refresh_token (str): Current refresh token. + + Returns: + Tuple[str, str, Optional[datetime], Mapping[str, str]]: The access token, + refresh token, expiration, and additional data returned by the token + endpoint. If response_data doesn't have refresh token, then the current + refresh token will be returned. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + """ + try: + access_token = response_data["access_token"] + except KeyError as caught_exc: + new_exc = exceptions.RefreshError( + "No access token in response.", response_data, retryable=False + ) + raise new_exc from caught_exc + + refresh_token = response_data.get("refresh_token", refresh_token) + expiry = _parse_expiry(response_data) + + return access_token, refresh_token, expiry, response_data + + +def refresh_grant( + request, + token_uri, + refresh_token, + client_id, + client_secret, + scopes=None, + rapt_token=None, + can_retry=True, +): + """Implements the OAuth 2.0 refresh token grant. + + For more details, see `rfc678 section 6`_. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + refresh_token (str): The refresh token to use to get a new access + token. + client_id (str): The OAuth 2.0 application's client ID. + client_secret (str): The Oauth 2.0 appliaction's client secret. + scopes (Optional(Sequence[str])): Scopes to request. If present, all + scopes must be authorized for the refresh token. Useful if refresh + token has a wild card scope (e.g. + 'https://www.googleapis.com/auth/any-api'). + rapt_token (Optional(str)): The reauth Proof Token. + can_retry (bool): Enable or disable request retry behavior. + + Returns: + Tuple[str, str, Optional[datetime], Mapping[str, str]]: The access + token, new or current refresh token, expiration, and additional data + returned by the token endpoint. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + + .. _rfc6748 section 6: https://tools.ietf.org/html/rfc6749#section-6 + """ + body = { + "grant_type": _REFRESH_GRANT_TYPE, + "client_id": client_id, + "client_secret": client_secret, + "refresh_token": refresh_token, + } + if scopes: + body["scope"] = " ".join(scopes) + if rapt_token: + body["rapt"] = rapt_token + + response_data = _token_endpoint_request( + request, token_uri, body, can_retry=can_retry + ) + return _handle_refresh_grant_response(response_data, refresh_token) + + +def _lookup_regional_access_boundary(request, url, headers=None, fail_fast=False): + """Implements the global lookup of a credential Regional Access Boundary. + For the lookup, we send a request to the global lookup endpoint and then + parse the response. Service account credentials, workload identity + pools and workforce pools implementation may have Regional Access Boundaries configured. + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + url (str): The Regional Access Boundary lookup url. + headers (Optional[Mapping[str, str]]): The headers for the request. + fail_fast (bool): Whether the lookup should fail fast (uses a short timeout and no retries). + Returns: + Optional[Mapping[str,list|str]]: A dictionary containing + "locations" as a list of allowed locations as strings and + "encodedLocations" as a hex string. + e.g: + { + "locations": [ + "us-central1", "us-east1", "europe-west1", "asia-east1" + ], + "encodedLocations": "0xA30" + } + """ + + response_data = _lookup_regional_access_boundary_request( + request, url, headers=headers, fail_fast=fail_fast + ) + if response_data is None: + # Error was already logged by _lookup_regional_access_boundary_request + return None + + if "encodedLocations" not in response_data: + _LOGGER.error( + "Regional Access Boundary response malformed: missing 'encodedLocations' key in %s", + response_data, + ) + return None + return response_data + + +def _lookup_regional_access_boundary_request( + request, url, can_retry=True, headers=None, fail_fast=False +): + """Makes a request to the Regional Access Boundary lookup endpoint. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + url (str): The Regional Access Boundary lookup url. + can_retry (bool): Enable or disable request retry behavior. Defaults to true. + headers (Optional[Mapping[str, str]]): The headers for the request. + fail_fast (bool): Whether the lookup should fail fast (uses a short timeout and no retries). + + Returns: + Optional[Mapping[str, str]]: The JSON-decoded response data on success, or None on failure. + """ + ( + response_status_ok, + response_data, + retryable_error, + ) = _lookup_regional_access_boundary_request_no_throw( + request, url, can_retry=can_retry, headers=headers, fail_fast=fail_fast + ) + if not response_status_ok: + _LOGGER.warning( + "Regional Access Boundary HTTP request failed after retries: response_data=%s, retryable_error=%s", + response_data, + retryable_error, + ) + return None + return response_data + + +def _lookup_regional_access_boundary_request_no_throw( + request, url, can_retry=True, headers=None, fail_fast=False +): + """Makes a request to the Regional Access Boundary lookup endpoint. This + function doesn't throw on response errors. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + url (str): The Regional Access Boundary lookup url. + can_retry (bool): Enable or disable request retry behavior. Defaults to true. + headers (Optional[Mapping[str, str]]): The headers for the request. + fail_fast (bool): Whether the lookup should fail fast (uses a short timeout and no retries). + + Returns: + Tuple(bool, Mapping[str, str], Optional[bool]): A boolean indicating + if the request is successful, a mapping for the JSON-decoded response + data and in the case of an error a boolean indicating if the error + is retryable. + """ + + response_data = {} + retryable_error = False + + timeout = _BLOCKING_REGIONAL_ACCESS_BOUNDARY_LOOKUP_TIMEOUT if fail_fast else None + total_attempts = 1 if fail_fast else 6 + retries = _exponential_backoff.ExponentialBackoff(total_attempts=total_attempts) + + for _ in retries: + response = request(method="GET", url=url, headers=headers, timeout=timeout) + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + try: + # response_body should be a JSON + response_data = json.loads(response_body) + except ValueError: + response_data = response_body + + if response.status == http_client.OK: + return True, response_data, None + + retryable_error = _can_retry( + status_code=response.status, response_data=response_data + ) + # Add 502 (Bad Gateway) as a retryable error for RAB lookups. + if response.status == http_client.BAD_GATEWAY: + retryable_error = True + + if not can_retry or not retryable_error: + return False, response_data, retryable_error + + return False, response_data, retryable_error diff --git a/intelliaide/vendor/google/oauth2/_client_async.py b/intelliaide/vendor/google/oauth2/_client_async.py new file mode 100644 index 0000000..a6201fb --- /dev/null +++ b/intelliaide/vendor/google/oauth2/_client_async.py @@ -0,0 +1,290 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""OAuth 2.0 async client. + +This is a client for interacting with an OAuth 2.0 authorization server's +token endpoint. + +For more information about the token endpoint, see +`Section 3.1 of rfc6749`_ + +.. _Section 3.1 of rfc6749: https://tools.ietf.org/html/rfc6749#section-3.2 +""" + +import http.client as http_client +import json +import urllib + +from google.auth import _exponential_backoff +from google.auth import _helpers +from google.auth import exceptions +from google.auth import jwt +from google.oauth2 import _client as client + + +async def _token_endpoint_request_no_throw( + request, token_uri, body, access_token=None, use_json=False, can_retry=True +): + """Makes a request to the OAuth 2.0 authorization server's token endpoint. + This function doesn't throw on response errors. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + body (Mapping[str, str]): The parameters to send in the request body. + access_token (Optional(str)): The access token needed to make the request. + use_json (Optional(bool)): Use urlencoded format or json format for the + content type. The default value is False. + can_retry (bool): Enable or disable request retry behavior. + + Returns: + Tuple(bool, Mapping[str, str], Optional[bool]): A boolean indicating + if the request is successful, a mapping for the JSON-decoded response + data and in the case of an error a boolean indicating if the error + is retryable. + """ + if use_json: + headers = {"Content-Type": client._JSON_CONTENT_TYPE} + body = json.dumps(body).encode("utf-8") + else: + headers = {"Content-Type": client._URLENCODED_CONTENT_TYPE} + body = urllib.parse.urlencode(body).encode("utf-8") + + if access_token: + headers["Authorization"] = "Bearer {}".format(access_token) + + response_data = {} + retryable_error = False + + retries = _exponential_backoff.ExponentialBackoff() + for _ in retries: + response = await request( + method="POST", url=token_uri, headers=headers, body=body + ) + + # Using data.read() resulted in zlib decompression errors. This may require future investigation. + response_body1 = await response.content() + + response_body = ( + response_body1.decode("utf-8") + if hasattr(response_body1, "decode") + else response_body1 + ) + + try: + response_data = json.loads(response_body) + except ValueError: + response_data = response_body + + if response.status == http_client.OK: + return True, response_data, None + + retryable_error = client._can_retry( + status_code=response.status, response_data=response_data + ) + + if not can_retry or not retryable_error: + return False, response_data, retryable_error + + return False, response_data, retryable_error + + +async def _token_endpoint_request( + request, token_uri, body, access_token=None, use_json=False, can_retry=True +): + """Makes a request to the OAuth 2.0 authorization server's token endpoint. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + body (Mapping[str, str]): The parameters to send in the request body. + access_token (Optional(str)): The access token needed to make the request. + use_json (Optional(bool)): Use urlencoded format or json format for the + content type. The default value is False. + can_retry (bool): Enable or disable request retry behavior. + + Returns: + Mapping[str, str]: The JSON-decoded response data. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + """ + + ( + response_status_ok, + response_data, + retryable_error, + ) = await _token_endpoint_request_no_throw( + request, + token_uri, + body, + access_token=access_token, + use_json=use_json, + can_retry=can_retry, + ) + if not response_status_ok: + client._handle_error_response(response_data, retryable_error) + return response_data + + +async def jwt_grant(request, token_uri, assertion, can_retry=True): + """Implements the JWT Profile for OAuth 2.0 Authorization Grants. + + For more details, see `rfc7523 section 4`_. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + assertion (str): The OAuth 2.0 assertion. + can_retry (bool): Enable or disable request retry behavior. + + Returns: + Tuple[str, Optional[datetime], Mapping[str, str]]: The access token, + expiration, and additional data returned by the token endpoint. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + + .. _rfc7523 section 4: https://tools.ietf.org/html/rfc7523#section-4 + """ + body = {"assertion": assertion, "grant_type": client._JWT_GRANT_TYPE} + + response_data = await _token_endpoint_request( + request, token_uri, body, can_retry=can_retry + ) + + try: + access_token = response_data["access_token"] + except KeyError as caught_exc: + new_exc = exceptions.RefreshError( + "No access token in response.", response_data, retryable=False + ) + raise new_exc from caught_exc + + expiry = client._parse_expiry(response_data) + + return access_token, expiry, response_data + + +async def id_token_jwt_grant(request, token_uri, assertion, can_retry=True): + """Implements the JWT Profile for OAuth 2.0 Authorization Grants, but + requests an OpenID Connect ID Token instead of an access token. + + This is a variant on the standard JWT Profile that is currently unique + to Google. This was added for the benefit of authenticating to services + that require ID Tokens instead of access tokens or JWT bearer tokens. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorization server's token endpoint + URI. + assertion (str): JWT token signed by a service account. The token's + payload must include a ``target_audience`` claim. + can_retry (bool): Enable or disable request retry behavior. + + Returns: + Tuple[str, Optional[datetime], Mapping[str, str]]: + The (encoded) Open ID Connect ID Token, expiration, and additional + data returned by the endpoint. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + """ + body = {"assertion": assertion, "grant_type": client._JWT_GRANT_TYPE} + + response_data = await _token_endpoint_request( + request, token_uri, body, can_retry=can_retry + ) + + try: + id_token = response_data["id_token"] + except KeyError as caught_exc: + new_exc = exceptions.RefreshError( + "No ID token in response.", response_data, retryable=False + ) + raise new_exc from caught_exc + + payload = jwt.decode(id_token, verify=False) + expiry = _helpers.utcfromtimestamp(payload["exp"]) + + return id_token, expiry, response_data + + +async def refresh_grant( + request, + token_uri, + refresh_token, + client_id, + client_secret, + scopes=None, + rapt_token=None, + can_retry=True, +): + """Implements the OAuth 2.0 refresh token grant. + + For more details, see `rfc678 section 6`_. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + refresh_token (str): The refresh token to use to get a new access + token. + client_id (str): The OAuth 2.0 application's client ID. + client_secret (str): The Oauth 2.0 appliaction's client secret. + scopes (Optional(Sequence[str])): Scopes to request. If present, all + scopes must be authorized for the refresh token. Useful if refresh + token has a wild card scope (e.g. + 'https://www.googleapis.com/auth/any-api'). + rapt_token (Optional(str)): The reauth Proof Token. + can_retry (bool): Enable or disable request retry behavior. + + Returns: + Tuple[str, Optional[str], Optional[datetime], Mapping[str, str]]: The + access token, new or current refresh token, expiration, and additional data + returned by the token endpoint. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + + .. _rfc6748 section 6: https://tools.ietf.org/html/rfc6749#section-6 + """ + body = { + "grant_type": client._REFRESH_GRANT_TYPE, + "client_id": client_id, + "client_secret": client_secret, + "refresh_token": refresh_token, + } + if scopes: + body["scope"] = " ".join(scopes) + if rapt_token: + body["rapt"] = rapt_token + + response_data = await _token_endpoint_request( + request, token_uri, body, can_retry=can_retry + ) + return client._handle_refresh_grant_response(response_data, refresh_token) diff --git a/intelliaide/vendor/google/oauth2/_credentials_async.py b/intelliaide/vendor/google/oauth2/_credentials_async.py new file mode 100644 index 0000000..b5561aa --- /dev/null +++ b/intelliaide/vendor/google/oauth2/_credentials_async.py @@ -0,0 +1,118 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""OAuth 2.0 Async Credentials. + +This module provides credentials based on OAuth 2.0 access and refresh tokens. +These credentials usually access resources on behalf of a user (resource +owner). + +Specifically, this is intended to use access tokens acquired using the +`Authorization Code grant`_ and can refresh those tokens using a +optional `refresh token`_. + +Obtaining the initial access and refresh token is outside of the scope of this +module. Consult `rfc6749 section 4.1`_ for complete details on the +Authorization Code grant flow. + +.. _Authorization Code grant: https://tools.ietf.org/html/rfc6749#section-1.3.1 +.. _refresh token: https://tools.ietf.org/html/rfc6749#section-6 +.. _rfc6749 section 4.1: https://tools.ietf.org/html/rfc6749#section-4.1 +""" + +from google.auth import _credentials_async as credentials +from google.auth import _helpers +from google.auth import exceptions +from google.oauth2 import _reauth_async as reauth +from google.oauth2 import credentials as oauth2_credentials + + +class Credentials(oauth2_credentials.Credentials): + """Credentials using OAuth 2.0 access and refresh tokens. + + The credentials are considered immutable. If you want to modify the + quota project, use :meth:`with_quota_project` or :: + + credentials = credentials.with_quota_project('myproject-123) + """ + + @_helpers.copy_docstring(credentials.Credentials) + async def refresh(self, request): + if ( + self._refresh_token is None + or self._token_uri is None + or self._client_id is None + or self._client_secret is None + ): + raise exceptions.RefreshError( + "The credentials do not contain the necessary fields need to " + "refresh the access token. You must specify refresh_token, " + "token_uri, client_id, and client_secret." + ) + + ( + access_token, + refresh_token, + expiry, + grant_response, + rapt_token, + ) = await reauth.refresh_grant( + request, + self._token_uri, + self._refresh_token, + self._client_id, + self._client_secret, + scopes=self._scopes, + rapt_token=self._rapt_token, + enable_reauth_refresh=self._enable_reauth_refresh, + ) + + self.token = access_token + self.expiry = expiry + self._refresh_token = refresh_token + self._id_token = grant_response.get("id_token") + self._rapt_token = rapt_token + + if self._scopes and "scope" in grant_response: + requested_scopes = frozenset(self._scopes) + granted_scopes = frozenset(grant_response["scope"].split()) + scopes_requested_but_not_granted = requested_scopes - granted_scopes + if scopes_requested_but_not_granted: + raise exceptions.RefreshError( + "Not all requested scopes were granted by the " + "authorization server, missing scopes {}.".format( + ", ".join(scopes_requested_but_not_granted) + ) + ) + + @_helpers.copy_docstring(credentials.Credentials) + async def before_request(self, request, method, url, headers): + if not self.valid: + await self.refresh(request) + self.apply(headers) + + +class UserAccessTokenCredentials(oauth2_credentials.UserAccessTokenCredentials): + """Access token credentials for user account. + + Obtain the access token for a given user account or the current active + user account with the ``gcloud auth print-access-token`` command. + + Args: + account (Optional[str]): Account to get the access token for. If not + specified, the current active account will be used. + quota_project_id (Optional[str]): The project ID used for quota + and billing. + + """ diff --git a/intelliaide/vendor/google/oauth2/_id_token_async.py b/intelliaide/vendor/google/oauth2/_id_token_async.py new file mode 100644 index 0000000..a7f77a1 --- /dev/null +++ b/intelliaide/vendor/google/oauth2/_id_token_async.py @@ -0,0 +1,287 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google ID Token helpers. + +Provides support for verifying `OpenID Connect ID Tokens`_, especially ones +generated by Google infrastructure. + +To parse and verify an ID Token issued by Google's OAuth 2.0 authorization +server use :func:`verify_oauth2_token`. To verify an ID Token issued by +Firebase, use :func:`verify_firebase_token`. + +A general purpose ID Token verifier is available as :func:`verify_token`. + +Example:: + + from google.oauth2 import _id_token_async + from google.auth.transport import aiohttp_requests + + request = aiohttp_requests.Request() + + id_info = await _id_token_async.verify_oauth2_token( + token, request, 'my-client-id.example.com') + + if id_info['iss'] != 'https://accounts.google.com': + raise ValueError('Wrong issuer.') + + userid = id_info['sub'] + +By default, this will re-fetch certificates for each verification. Because +Google's public keys are only changed infrequently (on the order of once per +day), you may wish to take advantage of caching to reduce latency and the +potential for network errors. This can be accomplished using an external +library like `CacheControl`_ to create a cache-aware +:class:`google.auth.transport.Request`:: + + import cachecontrol + import google.auth.transport.requests + import requests + + session = requests.session() + cached_session = cachecontrol.CacheControl(session) + request = google.auth.transport.requests.Request(session=cached_session) + +.. _OpenID Connect ID Token: + http://openid.net/specs/openid-connect-core-1_0.html#IDToken +.. _CacheControl: https://cachecontrol.readthedocs.io +""" + +import http.client as http_client +import json +import os + +from google.auth import environment_vars +from google.auth import exceptions +from google.auth import jwt +from google.auth.transport import requests +from google.oauth2 import id_token as sync_id_token + + +async def _fetch_certs(request, certs_url): + """Fetches certificates. + + Google-style cerificate endpoints return JSON in the format of + ``{'key id': 'x509 certificate'}``. + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. This must be an aiohttp request. + certs_url (str): The certificate endpoint URL. + + Returns: + Mapping[str, str]: A mapping of public key ID to x.509 certificate + data. + """ + response = await request(certs_url, method="GET") + + if response.status != http_client.OK: + raise exceptions.TransportError( + "Could not fetch certificates at {}".format(certs_url) + ) + + data = await response.content() + + return json.loads(data) + + +async def verify_token( + id_token, + request, + audience=None, + certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL, + clock_skew_in_seconds=0, +): + """Verifies an ID token and returns the decoded token. + + Args: + id_token (Union[str, bytes]): The encoded token. + request (google.auth.transport.Request): The object used to make + HTTP requests. This must be an aiohttp request. + audience (str): The audience that this token is intended for. If None + then the audience is not verified. + certs_url (str): The URL that specifies the certificates to use to + verify the token. This URL should return JSON in the format of + ``{'key id': 'x509 certificate'}``. + clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` + validation. + + Returns: + Mapping[str, Any]: The decoded token. + """ + certs = await _fetch_certs(request, certs_url) + + return jwt.decode( + id_token, + certs=certs, + audience=audience, + clock_skew_in_seconds=clock_skew_in_seconds, + ) + + +async def verify_oauth2_token( + id_token, request, audience=None, clock_skew_in_seconds=0 +): + """Verifies an ID Token issued by Google's OAuth 2.0 authorization server. + + Args: + id_token (Union[str, bytes]): The encoded token. + request (google.auth.transport.Request): The object used to make + HTTP requests. This must be an aiohttp request. + audience (str): The audience that this token is intended for. This is + typically your application's OAuth 2.0 client ID. If None then the + audience is not verified. + clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` + validation. + + Returns: + Mapping[str, Any]: The decoded token. + + Raises: + exceptions.GoogleAuthError: If the issuer is invalid. + """ + idinfo = await verify_token( + id_token, + request, + audience=audience, + certs_url=sync_id_token._GOOGLE_OAUTH2_CERTS_URL, + clock_skew_in_seconds=clock_skew_in_seconds, + ) + + if idinfo["iss"] not in sync_id_token._GOOGLE_ISSUERS: + raise exceptions.GoogleAuthError( + "Wrong issuer. 'iss' should be one of the following: {}".format( + sync_id_token._GOOGLE_ISSUERS + ) + ) + + return idinfo + + +async def verify_firebase_token( + id_token, request, audience=None, clock_skew_in_seconds=0 +): + """Verifies an ID Token issued by Firebase Authentication. + + Args: + id_token (Union[str, bytes]): The encoded token. + request (google.auth.transport.Request): The object used to make + HTTP requests. This must be an aiohttp request. + audience (str): The audience that this token is intended for. This is + typically your Firebase application ID. If None then the audience + is not verified. + clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` + validation. + + Returns: + Mapping[str, Any]: The decoded token. + """ + return await verify_token( + id_token, + request, + audience=audience, + certs_url=sync_id_token._GOOGLE_APIS_CERTS_URL, + clock_skew_in_seconds=clock_skew_in_seconds, + ) + + +async def fetch_id_token(request, audience): + """Fetch the ID Token from the current environment. + + This function acquires ID token from the environment in the following order. + See https://google.aip.dev/auth/4110. + + 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set + to the path of a valid service account JSON file, then ID token is + acquired using this service account credentials. + 2. If the application is running in Compute Engine, App Engine or Cloud Run, + then the ID token are obtained from the metadata server. + 3. If metadata server doesn't exist and no valid service account credentials + are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will + be raised. + + Example:: + + import google.oauth2._id_token_async + import google.auth.transport.aiohttp_requests + + request = google.auth.transport.aiohttp_requests.Request() + target_audience = "https://pubsub.googleapis.com" + + id_token = await google.oauth2._id_token_async.fetch_id_token(request, target_audience) + + Args: + request (google.auth.transport.aiohttp_requests.Request): A callable used to make + HTTP requests. + audience (str): The audience that this ID token is intended for. + + Returns: + str: The ID token. + + Raises: + ~google.auth.exceptions.DefaultCredentialsError: + If metadata server doesn't exist and no valid service account + credentials are found. + """ + # 1. Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment + # variable. + credentials_filename = os.environ.get(environment_vars.CREDENTIALS) + if credentials_filename: + if not ( + os.path.exists(credentials_filename) + and os.path.isfile(credentials_filename) + ): + raise exceptions.DefaultCredentialsError( + "GOOGLE_APPLICATION_CREDENTIALS path is either not found or invalid." + ) + + try: + with open(credentials_filename, "r") as f: + from google.oauth2 import _service_account_async as service_account + + info = json.load(f) + if info.get("type") == "service_account": + credentials = ( + service_account.IDTokenCredentials.from_service_account_info( + info, target_audience=audience + ) + ) + await credentials.refresh(request) + return credentials.token + except ValueError as caught_exc: + new_exc = exceptions.DefaultCredentialsError( + "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.", + caught_exc, + ) + raise new_exc from caught_exc + + # 2. Try to fetch ID token from metada server if it exists. The code works + # for GAE and Cloud Run metadata server as well. + try: + from google.auth import compute_engine + from google.auth.compute_engine import _metadata + + request_new = requests.Request() + if _metadata.ping(request_new): + credentials = compute_engine.IDTokenCredentials( + request_new, audience, use_metadata_identity_endpoint=True + ) + credentials.refresh(request_new) + return credentials.token + except (ImportError, exceptions.TransportError): + pass + + raise exceptions.DefaultCredentialsError( + "Neither metadata server or valid service account credentials are found." + ) diff --git a/intelliaide/vendor/google/oauth2/_reauth_async.py b/intelliaide/vendor/google/oauth2/_reauth_async.py new file mode 100644 index 0000000..eeb8e9f --- /dev/null +++ b/intelliaide/vendor/google/oauth2/_reauth_async.py @@ -0,0 +1,330 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""A module that provides functions for handling rapt authentication. + +Reauth is a process of obtaining additional authentication (such as password, +security token, etc.) while refreshing OAuth 2.0 credentials for a user. + +Credentials that use the Reauth flow must have the reauth scope, +``https://www.googleapis.com/auth/accounts.reauth``. + +This module provides a high-level function for executing the Reauth process, +:func:`refresh_grant`, and lower-level helpers for doing the individual +steps of the reauth process. + +Those steps are: + +1. Obtaining a list of challenges from the reauth server. +2. Running through each challenge and sending the result back to the reauth + server. +3. Refreshing the access token using the returned rapt token. +""" + +import sys + +from google.auth import exceptions +from google.oauth2 import _client +from google.oauth2 import _client_async +from google.oauth2 import challenges +from google.oauth2 import reauth + + +async def _get_challenges( + request, supported_challenge_types, access_token, requested_scopes=None +): + """Does initial request to reauth API to get the challenges. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. This must be an aiohttp request. + supported_challenge_types (Sequence[str]): list of challenge names + supported by the manager. + access_token (str): Access token with reauth scopes. + requested_scopes (Optional(Sequence[str])): Authorized scopes for the credentials. + + Returns: + dict: The response from the reauth API. + """ + body = {"supportedChallengeTypes": supported_challenge_types} + if requested_scopes: + body["oauthScopesForDomainPolicyLookup"] = requested_scopes + + return await _client_async._token_endpoint_request( + request, + reauth._REAUTH_API + ":start", + body, + access_token=access_token, + use_json=True, + ) + + +async def _send_challenge_result( + request, session_id, challenge_id, client_input, access_token +): + """Attempt to refresh access token by sending next challenge result. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. This must be an aiohttp request. + session_id (str): session id returned by the initial reauth call. + challenge_id (str): challenge id returned by the initial reauth call. + client_input: dict with a challenge-specific client input. For example: + ``{'credential': password}`` for password challenge. + access_token (str): Access token with reauth scopes. + + Returns: + dict: The response from the reauth API. + """ + body = { + "sessionId": session_id, + "challengeId": challenge_id, + "action": "RESPOND", + "proposalResponse": client_input, + } + + return await _client_async._token_endpoint_request( + request, + reauth._REAUTH_API + "/{}:continue".format(session_id), + body, + access_token=access_token, + use_json=True, + ) + + +async def _run_next_challenge(msg, request, access_token): + """Get the next challenge from msg and run it. + + Args: + msg (dict): Reauth API response body (either from the initial request to + https://reauth.googleapis.com/v2/sessions:start or from sending the + previous challenge response to + https://reauth.googleapis.com/v2/sessions/id:continue) + request (google.auth.transport.Request): A callable used to make + HTTP requests. This must be an aiohttp request. + access_token (str): reauth access token + + Returns: + dict: The response from the reauth API. + + Raises: + google.auth.exceptions.ReauthError: if reauth failed. + """ + for challenge in msg["challenges"]: + if challenge["status"] != "READY": + # Skip non-activated challenges. + continue + c = challenges.AVAILABLE_CHALLENGES.get(challenge["challengeType"], None) + if not c: + raise exceptions.ReauthFailError( + "Unsupported challenge type {0}. Supported types: {1}".format( + challenge["challengeType"], + ",".join(list(challenges.AVAILABLE_CHALLENGES.keys())), + ) + ) + if not c.is_locally_eligible: + raise exceptions.ReauthFailError( + "Challenge {0} is not locally eligible".format( + challenge["challengeType"] + ) + ) + client_input = c.obtain_challenge_input(challenge) + if not client_input: + return None + return await _send_challenge_result( + request, + msg["sessionId"], + challenge["challengeId"], + client_input, + access_token, + ) + return None + + +async def _obtain_rapt(request, access_token, requested_scopes): + """Given an http request method and reauth access token, get rapt token. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. This must be an aiohttp request. + access_token (str): reauth access token + requested_scopes (Sequence[str]): scopes required by the client application + + Returns: + str: The rapt token. + + Raises: + google.auth.exceptions.ReauthError: if reauth failed + """ + msg = await _get_challenges( + request, + list(challenges.AVAILABLE_CHALLENGES.keys()), + access_token, + requested_scopes, + ) + + if msg["status"] == reauth._AUTHENTICATED: + return msg["encodedProofOfReauthToken"] + + for _ in range(0, reauth.RUN_CHALLENGE_RETRY_LIMIT): + if not ( + msg["status"] == reauth._CHALLENGE_REQUIRED + or msg["status"] == reauth._CHALLENGE_PENDING + ): + raise exceptions.ReauthFailError( + "Reauthentication challenge failed due to API error: {}".format( + msg["status"] + ) + ) + + if not reauth.is_interactive(): + raise exceptions.ReauthFailError( + "Reauthentication challenge could not be answered because you are not" + " in an interactive session." + ) + + msg = await _run_next_challenge(msg, request, access_token) + + if msg["status"] == reauth._AUTHENTICATED: + return msg["encodedProofOfReauthToken"] + + # If we got here it means we didn't get authenticated. + raise exceptions.ReauthFailError("Failed to obtain rapt token.") + + +async def get_rapt_token( + request, client_id, client_secret, refresh_token, token_uri, scopes=None +): + """Given an http request method and refresh_token, get rapt token. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. This must be an aiohttp request. + client_id (str): client id to get access token for reauth scope. + client_secret (str): client secret for the client_id + refresh_token (str): refresh token to refresh access token + token_uri (str): uri to refresh access token + scopes (Optional(Sequence[str])): scopes required by the client application + + Returns: + str: The rapt token. + Raises: + google.auth.exceptions.RefreshError: If reauth failed. + """ + sys.stderr.write("Reauthentication required.\n") + + # Get access token for reauth. + access_token, _, _, _ = await _client_async.refresh_grant( + request=request, + client_id=client_id, + client_secret=client_secret, + refresh_token=refresh_token, + token_uri=token_uri, + scopes=[reauth._REAUTH_SCOPE], + ) + + # Get rapt token from reauth API. + rapt_token = await _obtain_rapt(request, access_token, requested_scopes=scopes) + + return rapt_token + + +async def refresh_grant( + request, + token_uri, + refresh_token, + client_id, + client_secret, + scopes=None, + rapt_token=None, + enable_reauth_refresh=False, +): + """Implements the reauthentication flow. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. This must be an aiohttp request. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + refresh_token (str): The refresh token to use to get a new access + token. + client_id (str): The OAuth 2.0 application's client ID. + client_secret (str): The Oauth 2.0 appliaction's client secret. + scopes (Optional(Sequence[str])): Scopes to request. If present, all + scopes must be authorized for the refresh token. Useful if refresh + token has a wild card scope (e.g. + 'https://www.googleapis.com/auth/any-api'). + rapt_token (Optional(str)): The rapt token for reauth. + enable_reauth_refresh (Optional[bool]): Whether reauth refresh flow + should be used. The default value is False. This option is for + gcloud only, other users should use the default value. + + Returns: + Tuple[str, Optional[str], Optional[datetime], Mapping[str, str], str]: The + access token, new refresh token, expiration, the additional data + returned by the token endpoint, and the rapt token. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + """ + body = { + "grant_type": _client._REFRESH_GRANT_TYPE, + "client_id": client_id, + "client_secret": client_secret, + "refresh_token": refresh_token, + } + if scopes: + body["scope"] = " ".join(scopes) + if rapt_token: + body["rapt"] = rapt_token + + ( + response_status_ok, + response_data, + retryable_error, + ) = await _client_async._token_endpoint_request_no_throw(request, token_uri, body) + if ( + not response_status_ok + and response_data.get("error") == reauth._REAUTH_NEEDED_ERROR + and ( + response_data.get("error_subtype") + == reauth._REAUTH_NEEDED_ERROR_INVALID_RAPT + or response_data.get("error_subtype") + == reauth._REAUTH_NEEDED_ERROR_RAPT_REQUIRED + ) + ): + if not enable_reauth_refresh: + raise exceptions.RefreshError( + "Reauthentication is needed. Please run `gcloud auth application-default login` to reauthenticate." + ) + + rapt_token = await get_rapt_token( + request, client_id, client_secret, refresh_token, token_uri, scopes=scopes + ) + body["rapt"] = rapt_token + ( + response_status_ok, + response_data, + retryable_error, + ) = await _client_async._token_endpoint_request_no_throw( + request, token_uri, body + ) + + if not response_status_ok: + _client._handle_error_response(response_data, retryable_error) + refresh_response = _client._handle_refresh_grant_response( + response_data, refresh_token + ) + return refresh_response + (rapt_token,) diff --git a/intelliaide/vendor/google/oauth2/_service_account_async.py b/intelliaide/vendor/google/oauth2/_service_account_async.py new file mode 100644 index 0000000..fa6cfb7 --- /dev/null +++ b/intelliaide/vendor/google/oauth2/_service_account_async.py @@ -0,0 +1,147 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Service Accounts: JSON Web Token (JWT) Profile for OAuth 2.0 + +NOTE: This file adds asynchronous refresh methods to both credentials +classes, and therefore async/await syntax is required when calling this +method when using service account credentials with asynchronous functionality. +Otherwise, all other methods are inherited from the regular service account +credentials file google.oauth2.service_account + +""" + +from google.auth import _credentials_async as credentials_async +from google.auth import _helpers +from google.oauth2 import _client_async +from google.oauth2 import service_account + + +class Credentials( + service_account.Credentials, credentials_async.Scoped, credentials_async.Credentials +): + """Service account credentials + + Usually, you'll create these credentials with one of the helper + constructors. To create credentials using a Google service account + private key JSON file:: + + credentials = _service_account_async.Credentials.from_service_account_file( + 'service-account.json') + + Or if you already have the service account file loaded:: + + service_account_info = json.load(open('service_account.json')) + credentials = _service_account_async.Credentials.from_service_account_info( + service_account_info) + + Both helper methods pass on arguments to the constructor, so you can + specify additional scopes and a subject if necessary:: + + credentials = _service_account_async.Credentials.from_service_account_file( + 'service-account.json', + scopes=['email'], + subject='user@example.com') + + The credentials are considered immutable. If you want to modify the scopes + or the subject used for delegation, use :meth:`with_scopes` or + :meth:`with_subject`:: + + scoped_credentials = credentials.with_scopes(['email']) + delegated_credentials = credentials.with_subject(subject) + + To add a quota project, use :meth:`with_quota_project`:: + + credentials = credentials.with_quota_project('myproject-123') + """ + + @_helpers.copy_docstring(credentials_async.Credentials) + async def refresh(self, request): + assertion = self._make_authorization_grant_assertion() + access_token, expiry, _ = await _client_async.jwt_grant( + request, self._token_uri, assertion + ) + self.token = access_token + self.expiry = expiry + + @_helpers.copy_docstring(credentials_async.Credentials) + async def before_request(self, request, method, url, headers): + # Explicit override to bypass synchronous CredentialsWithRegionalAccessBoundary. + await credentials_async.Credentials.before_request( + self, request, method, url, headers + ) + + +class IDTokenCredentials( + service_account.IDTokenCredentials, + credentials_async.Signing, + credentials_async.Credentials, +): + """Open ID Connect ID Token-based service account credentials. + + These credentials are largely similar to :class:`.Credentials`, but instead + of using an OAuth 2.0 Access Token as the bearer token, they use an Open + ID Connect ID Token as the bearer token. These credentials are useful when + communicating to services that require ID Tokens and can not accept access + tokens. + + Usually, you'll create these credentials with one of the helper + constructors. To create credentials using a Google service account + private key JSON file:: + + credentials = ( + _service_account_async.IDTokenCredentials.from_service_account_file( + 'service-account.json')) + + Or if you already have the service account file loaded:: + + service_account_info = json.load(open('service_account.json')) + credentials = ( + _service_account_async.IDTokenCredentials.from_service_account_info( + service_account_info)) + + Both helper methods pass on arguments to the constructor, so you can + specify additional scopes and a subject if necessary:: + + credentials = ( + _service_account_async.IDTokenCredentials.from_service_account_file( + 'service-account.json', + scopes=['email'], + subject='user@example.com')) + + The credentials are considered immutable. If you want to modify the scopes + or the subject used for delegation, use :meth:`with_scopes` or + :meth:`with_subject`:: + + scoped_credentials = credentials.with_scopes(['email']) + delegated_credentials = credentials.with_subject(subject) + + """ + + @_helpers.copy_docstring(credentials_async.Credentials) + async def refresh(self, request): + assertion = self._make_authorization_grant_assertion() + access_token, expiry, _ = await _client_async.id_token_jwt_grant( + request, self._token_uri, assertion + ) + self.token = access_token + self.expiry = expiry + + @_helpers.copy_docstring(credentials_async.Credentials) + async def before_request(self, request, method, url, headers): + # Explicit override to bypass synchronous CredentialsWithRegionalAccessBoundary + # and disable Regional Access Boundary refresh for async credentials. + await credentials_async.Credentials.before_request( + self, request, method, url, headers + ) diff --git a/intelliaide/vendor/google/oauth2/challenges.py b/intelliaide/vendor/google/oauth2/challenges.py new file mode 100644 index 0000000..59a2f9b --- /dev/null +++ b/intelliaide/vendor/google/oauth2/challenges.py @@ -0,0 +1,281 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" Challenges for reauthentication. +""" + +import abc +import base64 +import getpass +import sys + +from google.auth import _helpers +from google.auth import exceptions +from google.oauth2 import webauthn_handler_factory +from google.oauth2.webauthn_types import ( + AuthenticationExtensionsClientInputs, + GetRequest, + PublicKeyCredentialDescriptor, +) + + +REAUTH_ORIGIN = "https://accounts.google.com" +SAML_CHALLENGE_MESSAGE = ( + "Please run `gcloud auth login` to complete reauthentication with SAML." +) +WEBAUTHN_TIMEOUT_MS = 120000 # Two minute timeout + + +def get_user_password(text): + """Get password from user. + + Override this function with a different logic if you are using this library + outside a CLI. + + Args: + text (str): message for the password prompt. + + Returns: + str: password string. + """ + return getpass.getpass(text) + + +class ReauthChallenge(metaclass=abc.ABCMeta): + """Base class for reauth challenges.""" + + @property + @abc.abstractmethod + def name(self): # pragma: NO COVER + """Returns the name of the challenge.""" + raise NotImplementedError("name property must be implemented") + + @property + @abc.abstractmethod + def is_locally_eligible(self): # pragma: NO COVER + """Returns true if a challenge is supported locally on this machine.""" + raise NotImplementedError("is_locally_eligible property must be implemented") + + @abc.abstractmethod + def obtain_challenge_input(self, metadata): # pragma: NO COVER + """Performs logic required to obtain credentials and returns it. + + Args: + metadata (Mapping): challenge metadata returned in the 'challenges' field in + the initial reauth request. Includes the 'challengeType' field + and other challenge-specific fields. + + Returns: + response that will be send to the reauth service as the content of + the 'proposalResponse' field in the request body. Usually a dict + with the keys specific to the challenge. For example, + ``{'credential': password}`` for password challenge. + """ + raise NotImplementedError("obtain_challenge_input method must be implemented") + + +class PasswordChallenge(ReauthChallenge): + """Challenge that asks for user's password.""" + + @property + def name(self): + return "PASSWORD" + + @property + def is_locally_eligible(self): + return True + + @_helpers.copy_docstring(ReauthChallenge) + def obtain_challenge_input(self, unused_metadata): + passwd = get_user_password("Please enter your password:") + if not passwd: + passwd = " " # avoid the server crashing in case of no password :D + return {"credential": passwd} + + +class SecurityKeyChallenge(ReauthChallenge): + """Challenge that asks for user's security key touch.""" + + @property + def name(self): + return "SECURITY_KEY" + + @property + def is_locally_eligible(self): + return True + + @_helpers.copy_docstring(ReauthChallenge) + def obtain_challenge_input(self, metadata): + # Check if there is an available Webauthn Handler, if not use pyu2f + try: + factory = webauthn_handler_factory.WebauthnHandlerFactory() + webauthn_handler = factory.get_handler() + if webauthn_handler is not None: + sys.stderr.write("Please insert and touch your security key\n") + return self._obtain_challenge_input_webauthn(metadata, webauthn_handler) + except Exception: + # Attempt pyu2f if exception in webauthn flow + pass + + try: + import pyu2f.convenience.authenticator # type: ignore + import pyu2f.errors # type: ignore + import pyu2f.model # type: ignore + except ImportError: + raise exceptions.ReauthFailError( + "pyu2f dependency is required to use Security key reauth feature. " + "It can be installed via `pip install pyu2f` or `pip install google-auth[reauth]`." + ) + sk = metadata["securityKey"] + challenges = sk["challenges"] + # Read both 'applicationId' and 'relyingPartyId', if they are the same, use + # applicationId, if they are different, use relyingPartyId first and retry + # with applicationId + application_id = sk["applicationId"] + relying_party_id = sk["relyingPartyId"] + + if application_id != relying_party_id: + application_parameters = [relying_party_id, application_id] + else: + application_parameters = [application_id] + + challenge_data = [] + for c in challenges: + kh = c["keyHandle"].encode("ascii") + key = pyu2f.model.RegisteredKey(bytearray(base64.urlsafe_b64decode(kh))) + challenge = c["challenge"].encode("ascii") + challenge = base64.urlsafe_b64decode(challenge) + challenge_data.append({"key": key, "challenge": challenge}) + + # Track number of tries to suppress error message until all application_parameters + # are tried. + tries = 0 + for app_id in application_parameters: + try: + tries += 1 + api = pyu2f.convenience.authenticator.CreateCompositeAuthenticator( + REAUTH_ORIGIN + ) + response = api.Authenticate( + app_id, challenge_data, print_callback=sys.stderr.write + ) + return {"securityKey": response} + except pyu2f.errors.U2FError as e: + if e.code == pyu2f.errors.U2FError.DEVICE_INELIGIBLE: + # Only show error if all app_ids have been tried + if tries == len(application_parameters): + sys.stderr.write("Ineligible security key.\n") + return None + continue + if e.code == pyu2f.errors.U2FError.TIMEOUT: + sys.stderr.write( + "Timed out while waiting for security key touch.\n" + ) + else: + raise e + except pyu2f.errors.PluginError as e: + sys.stderr.write("Plugin error: {}.\n".format(e)) + continue + except pyu2f.errors.NoDeviceFoundError: + sys.stderr.write("No security key found.\n") + return None + + def _obtain_challenge_input_webauthn(self, metadata, webauthn_handler): + sk = metadata.get("securityKey") + if sk is None: + raise exceptions.InvalidValue("securityKey is None") + challenges = sk.get("challenges") + application_id = sk.get("applicationId") + relying_party_id = sk.get("relyingPartyId") + if challenges is None or len(challenges) < 1: + raise exceptions.InvalidValue("challenges is None or empty") + if application_id is None: + raise exceptions.InvalidValue("application_id is None") + if relying_party_id is None: + raise exceptions.InvalidValue("relying_party_id is None") + + allow_credentials = [] + for challenge in challenges: + kh = challenge.get("keyHandle") + if kh is None: + raise exceptions.InvalidValue("keyHandle is None") + key_handle = self._unpadded_urlsafe_b64recode(kh) + allow_credentials.append(PublicKeyCredentialDescriptor(id=key_handle)) + + extension = AuthenticationExtensionsClientInputs(appid=application_id) + + challenge = challenges[0].get("challenge") + if challenge is None: + raise exceptions.InvalidValue("challenge is None") + + get_request = GetRequest( + origin=REAUTH_ORIGIN, + rpid=relying_party_id, + challenge=self._unpadded_urlsafe_b64recode(challenge), + timeout_ms=WEBAUTHN_TIMEOUT_MS, + allow_credentials=allow_credentials, + user_verification="preferred", + extensions=extension, + ) + + try: + get_response = webauthn_handler.get(get_request) + except Exception as e: + sys.stderr.write("Webauthn Error: {}.\n".format(e)) + raise e + + response = { + "clientData": get_response.response.client_data_json, + "authenticatorData": get_response.response.authenticator_data, + "signatureData": get_response.response.signature, + "applicationId": application_id, + "keyHandle": get_response.id, + "securityKeyReplyType": 2, + } + return {"securityKey": response} + + def _unpadded_urlsafe_b64recode(self, s): + """Converts standard b64 encoded string to url safe b64 encoded string + with no padding.""" + b = base64.urlsafe_b64decode(s) + return base64.urlsafe_b64encode(b).decode().rstrip("=") + + +class SamlChallenge(ReauthChallenge): + """Challenge that asks the users to browse to their ID Providers. + + Currently SAML challenge is not supported. When obtaining the challenge + input, exception will be raised to instruct the users to run + `gcloud auth login` for reauthentication. + """ + + @property + def name(self): + return "SAML" + + @property + def is_locally_eligible(self): + return True + + def obtain_challenge_input(self, metadata): + # Magic Arch has not fully supported returning a proper dedirect URL + # for programmatic SAML users today. So we error our here and request + # users to use gcloud to complete a login. + raise exceptions.ReauthSamlChallengeFailError(SAML_CHALLENGE_MESSAGE) + + +AVAILABLE_CHALLENGES = { + challenge.name: challenge + for challenge in [SecurityKeyChallenge(), PasswordChallenge(), SamlChallenge()] +} diff --git a/intelliaide/vendor/google/oauth2/credentials.py b/intelliaide/vendor/google/oauth2/credentials.py new file mode 100644 index 0000000..724cf98 --- /dev/null +++ b/intelliaide/vendor/google/oauth2/credentials.py @@ -0,0 +1,640 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""OAuth 2.0 Credentials. + +This module provides credentials based on OAuth 2.0 access and refresh tokens. +These credentials usually access resources on behalf of a user (resource +owner). + +Specifically, this is intended to use access tokens acquired using the +`Authorization Code grant`_ and can refresh those tokens using a +optional `refresh token`_. + +Obtaining the initial access and refresh token is outside of the scope of this +module. Consult `rfc6749 section 4.1`_ for complete details on the +Authorization Code grant flow. + +.. _Authorization Code grant: https://tools.ietf.org/html/rfc6749#section-1.3.1 +.. _refresh token: https://tools.ietf.org/html/rfc6749#section-6 +.. _rfc6749 section 4.1: https://tools.ietf.org/html/rfc6749#section-4.1 +""" + +from datetime import datetime +import io +import json +import logging +import warnings + +from google.auth import _cloud_sdk +from google.auth import _helpers +from google.auth import _regional_access_boundary_utils +from google.auth import credentials +from google.auth import exceptions +from google.auth import metrics +from google.oauth2 import reauth + +_LOGGER = logging.getLogger(__name__) + + +# The Google OAuth 2.0 token endpoint. Used for authorized user credentials. +_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" + +# The Google OAuth 2.0 token info endpoint. Used for getting token info JSON from access tokens. +_GOOGLE_OAUTH2_TOKEN_INFO_ENDPOINT = "https://oauth2.googleapis.com/tokeninfo" + + +class Credentials( + credentials.CredentialsWithRegionalAccessBoundary, + credentials.ReadOnlyScoped, + credentials.CredentialsWithQuotaProject, +): + """Credentials using OAuth 2.0 access and refresh tokens. + + The credentials are considered immutable except the tokens and the token + expiry, which are updated after refresh. If you want to modify the quota + project, use :meth:`with_quota_project` or :: + + credentials = credentials.with_quota_project('myproject-123') + + Reauth is disabled by default. To enable reauth, set the + `enable_reauth_refresh` parameter to True in the constructor. Note that + reauth feature is intended for gcloud to use only. + If reauth is enabled, `pyu2f` dependency has to be installed in order to use security + key reauth feature. Dependency can be installed via `pip install pyu2f` or `pip install + google-auth[reauth]`. + """ + + def __init__( + self, + token, + refresh_token=None, + id_token=None, + token_uri=None, + client_id=None, + client_secret=None, + scopes=None, + default_scopes=None, + quota_project_id=None, + expiry=None, + rapt_token=None, + refresh_handler=None, + enable_reauth_refresh=False, + granted_scopes=None, + trust_boundary=None, + universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, + account=None, + ): + """ + Args: + token (Optional(str)): The OAuth 2.0 access token. Can be None + if refresh information is provided. + refresh_token (str): The OAuth 2.0 refresh token. If specified, + credentials can be refreshed. + id_token (str): The Open ID Connect ID Token. + token_uri (str): The OAuth 2.0 authorization server's token + endpoint URI. Must be specified for refresh, can be left as + None if the token can not be refreshed. + client_id (str): The OAuth 2.0 client ID. Must be specified for + refresh, can be left as None if the token can not be refreshed. + client_secret(str): The OAuth 2.0 client secret. Must be specified + for refresh, can be left as None if the token can not be + refreshed. + scopes (Sequence[str]): The scopes used to obtain authorization. + This parameter is used by :meth:`has_scopes`. OAuth 2.0 + credentials can not request additional scopes after + authorization. The scopes must be derivable from the refresh + token if refresh information is provided (e.g. The refresh + token scopes are a superset of this or contain a wild card + scope like 'https://www.googleapis.com/auth/any-api'). + default_scopes (Sequence[str]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + quota_project_id (Optional[str]): The project ID used for quota and billing. + This project may be different from the project used to + create the credentials. + rapt_token (Optional[str]): The reauth Proof Token. + refresh_handler (Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]): + A callable which takes in the HTTP request callable and the list of + OAuth scopes and when called returns an access token string for the + requested scopes and its expiry datetime. This is useful when no + refresh tokens are provided and tokens are obtained by calling + some external process on demand. It is particularly useful for + retrieving downscoped tokens from a token broker. + enable_reauth_refresh (Optional[bool]): Whether reauth refresh flow + should be used. This flag is for gcloud to use only. + granted_scopes (Optional[Sequence[str]]): The scopes that were consented/granted by the user. + This could be different from the requested scopes and it could be empty if granted + and requested scopes were same. + trust_boundary (str): String representation of trust boundary meta. + universe_domain (Optional[str]): The universe domain. The default + universe domain is googleapis.com. + account (Optional[str]): The account associated with the credential. + """ + super(Credentials, self).__init__() + self.token = token + self.expiry = expiry + self._refresh_token = refresh_token + self._id_token = id_token + if scopes is not None and isinstance(scopes, set): + self._scopes = list(scopes) + else: + self._scopes = scopes + self._default_scopes = default_scopes + self._granted_scopes = granted_scopes + self._token_uri = token_uri + self._client_id = client_id + self._client_secret = client_secret + self._quota_project_id = quota_project_id + self._rapt_token = rapt_token + self.refresh_handler = refresh_handler + self._enable_reauth_refresh = enable_reauth_refresh + self._trust_boundary = trust_boundary + self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN + self._account = account or "" + self._cred_file_path = None + + def __getstate__(self): + """A __getstate__ method must exist for the __setstate__ to be called + This is identical to the default implementation. + See https://docs.python.org/3.7/library/pickle.html#object.__setstate__ + """ + state_dict = self.__dict__.copy() + # Remove _refresh_handler function as there are limitations pickling and + # unpickling certain callables (lambda, functools.partial instances) + # because they need to be importable. + # Instead, the refresh_handler setter should be used to repopulate this. + if "_refresh_handler" in state_dict: + del state_dict["_refresh_handler"] + + if "_refresh_worker" in state_dict: + del state_dict["_refresh_worker"] + return state_dict + + def __setstate__(self, d): + """Credentials pickled with older versions of the class do not have + all the attributes.""" + self.token = d.get("token") + self.expiry = d.get("expiry") + self._refresh_token = d.get("_refresh_token") + self._id_token = d.get("_id_token") + self._scopes = d.get("_scopes") + self._default_scopes = d.get("_default_scopes") + self._granted_scopes = d.get("_granted_scopes") + self._token_uri = d.get("_token_uri") + self._client_id = d.get("_client_id") + self._client_secret = d.get("_client_secret") + self._quota_project_id = d.get("_quota_project_id") + self._rapt_token = d.get("_rapt_token") + self._enable_reauth_refresh = d.get("_enable_reauth_refresh") + self._trust_boundary = d.get("_trust_boundary") + self._universe_domain = ( + d.get("_universe_domain") or credentials.DEFAULT_UNIVERSE_DOMAIN + ) + self._cred_file_path = d.get("_cred_file_path") + # The refresh_handler setter should be used to repopulate this. + self._refresh_handler = None + self._refresh_worker = None + self._use_non_blocking_refresh = d.get("_use_non_blocking_refresh", False) + self._account = d.get("_account", "") + self._rab_manager = d.get("_rab_manager") or ( + _regional_access_boundary_utils._RegionalAccessBoundaryManager() + ) + + @property + def refresh_token(self): + """Optional[str]: The OAuth 2.0 refresh token.""" + return self._refresh_token + + @property + def scopes(self): + """Optional[Sequence[str]]: The OAuth 2.0 permission scopes.""" + return self._scopes + + @property + def granted_scopes(self): + """Optional[Sequence[str]]: The OAuth 2.0 permission scopes that were granted by the user.""" + return self._granted_scopes + + @property + def token_uri(self): + """Optional[str]: The OAuth 2.0 authorization server's token endpoint + URI.""" + return self._token_uri + + @property + def id_token(self): + """Optional[str]: The Open ID Connect ID Token. + + Depending on the authorization server and the scopes requested, this + may be populated when credentials are obtained and updated when + :meth:`refresh` is called. This token is a JWT. It can be verified + and decoded using :func:`google.oauth2.id_token.verify_oauth2_token`. + """ + return self._id_token + + @property + def client_id(self): + """Optional[str]: The OAuth 2.0 client ID.""" + return self._client_id + + @property + def client_secret(self): + """Optional[str]: The OAuth 2.0 client secret.""" + return self._client_secret + + @property + def requires_scopes(self): + """False: OAuth 2.0 credentials have their scopes set when + the initial token is requested and can not be changed.""" + return False + + @property + def rapt_token(self): + """Optional[str]: The reauth Proof Token.""" + return self._rapt_token + + @property + def refresh_handler(self): + """Returns the refresh handler if available. + + Returns: + Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]: + The current refresh handler. + """ + return self._refresh_handler + + @refresh_handler.setter + def refresh_handler(self, value): + """Updates the current refresh handler. + + Args: + value (Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]): + The updated value of the refresh handler. + + Raises: + TypeError: If the value is not a callable or None. + """ + if not callable(value) and value is not None: + raise TypeError("The provided refresh_handler is not a callable or None.") + self._refresh_handler = value + + @property + def account(self): + """str: The user account associated with the credential. If the account is unknown an empty string is returned.""" + return self._account + + def _make_copy(self): + cred = self.__class__( + self.token, + refresh_token=self.refresh_token, + id_token=self.id_token, + token_uri=self.token_uri, + client_id=self.client_id, + client_secret=self.client_secret, + scopes=self.scopes, + default_scopes=self.default_scopes, + granted_scopes=self.granted_scopes, + quota_project_id=self.quota_project_id, + rapt_token=self.rapt_token, + enable_reauth_refresh=self._enable_reauth_refresh, + trust_boundary=self._trust_boundary, + universe_domain=self._universe_domain, + account=self._account, + ) + cred._cred_file_path = self._cred_file_path + return cred + + @_helpers.copy_docstring(credentials.Credentials) + def get_cred_info(self): + if self._cred_file_path: + cred_info = { + "credential_source": self._cred_file_path, + "credential_type": "user credentials", + } + if self.account: + cred_info["principal"] = self.account + return cred_info + return None + + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + cred = self._make_copy() + cred._quota_project_id = quota_project_id + return cred + + @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) + def with_token_uri(self, token_uri): + cred = self._make_copy() + cred._token_uri = token_uri + return cred + + def with_account(self, account): + """Returns a copy of these credentials with a modified account. + + Args: + account (str): The account to set + + Returns: + google.oauth2.credentials.Credentials: A new credentials instance. + """ + cred = self._make_copy() + cred._account = account + return cred + + @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain) + def with_universe_domain(self, universe_domain): + cred = self._make_copy() + cred._universe_domain = universe_domain + return cred + + def _metric_header_for_usage(self): + return metrics.CRED_TYPE_USER + + def _build_regional_access_boundary_lookup_url(self, request=None): + """Builds the URL for Regional Access Boundary lookup. + + OAuth 2.0 credentials do not support independent Regional Access Boundary + lookup. However, they may support a seeded Regional Access Boundary + provided externally (e.g., from gcloud). + + Returns: + None: This credential type does not support RAB lookup. + """ + return None + + def _is_regional_access_boundary_lookup_required(self): + """OAuth 2.0 credentials do not support independent lookup.""" + return False + + def _perform_refresh_token(self, request): + if self._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN: + raise exceptions.RefreshError( + "User credential refresh is only supported in the default " + "googleapis.com universe domain, but the current universe " + "domain is {}. If you created the credential with an access " + "token, it's likely that the provided token is expired now, " + "please update your code with a valid token.".format( + self._universe_domain + ) + ) + + scopes = self._scopes if self._scopes is not None else self._default_scopes + # Use refresh handler if available and no refresh token is + # available. This is useful in general when tokens are obtained by calling + # some external process on demand. It is particularly useful for retrieving + # downscoped tokens from a token broker. + if self._refresh_token is None and self.refresh_handler: + token, expiry = self.refresh_handler(request, scopes=scopes) + # Validate returned data. + if not isinstance(token, str): + raise exceptions.RefreshError( + "The refresh_handler returned token is not a string." + ) + if not isinstance(expiry, datetime): + raise exceptions.RefreshError( + "The refresh_handler returned expiry is not a datetime object." + ) + if _helpers.utcnow() >= expiry - _helpers.REFRESH_THRESHOLD: + raise exceptions.RefreshError( + "The credentials returned by the refresh_handler are " + "already expired." + ) + self.token = token + self.expiry = expiry + return + + if ( + self._refresh_token is None + or self._token_uri is None + or self._client_id is None + or self._client_secret is None + ): + raise exceptions.RefreshError( + "The credentials do not contain the necessary fields need to " + "refresh the access token. You must specify refresh_token, " + "token_uri, client_id, and client_secret." + ) + + ( + access_token, + refresh_token, + expiry, + grant_response, + rapt_token, + ) = reauth.refresh_grant( + request, + self._token_uri, + self._refresh_token, + self._client_id, + self._client_secret, + scopes=scopes, + rapt_token=self._rapt_token, + enable_reauth_refresh=self._enable_reauth_refresh, + ) + + self.token = access_token + self.expiry = expiry + self._refresh_token = refresh_token + self._id_token = grant_response.get("id_token") + self._rapt_token = rapt_token + + if scopes and "scope" in grant_response: + requested_scopes = frozenset(scopes) + self._granted_scopes = grant_response["scope"].split() + granted_scopes = frozenset(self._granted_scopes) + scopes_requested_but_not_granted = requested_scopes - granted_scopes + if scopes_requested_but_not_granted: + # User might be presented with unbundled scopes at the time of + # consent. So it is a valid scenario to not have all the requested + # scopes as part of granted scopes but log a warning in case the + # developer wants to debug the scenario. + _LOGGER.warning( + "Not all requested scopes were granted by the " + "authorization server, missing scopes {}.".format( + ", ".join(scopes_requested_but_not_granted) + ) + ) + + @classmethod + def from_authorized_user_info(cls, info, scopes=None): + """Creates a Credentials instance from parsed authorized user info. + + Args: + info (Mapping[str, str]): The authorized user info in Google + format. + scopes (Sequence[str]): Optional list of scopes to include in the + credentials. + + Returns: + google.oauth2.credentials.Credentials: The constructed + credentials. + + Raises: + ValueError: If the info is not in the expected format. + """ + keys_needed = set(("refresh_token", "client_id", "client_secret")) + missing = keys_needed.difference(info.keys()) + + if missing: + raise ValueError( + "Authorized user info was not in the expected format, missing " + "fields {}.".format(", ".join(missing)) + ) + + # access token expiry (datetime obj); auto-expire if not saved + expiry = info.get("expiry") + if expiry: + expiry = datetime.strptime( + expiry.rstrip("Z").split(".")[0], "%Y-%m-%dT%H:%M:%S" + ) + else: + expiry = _helpers.utcnow() - _helpers.REFRESH_THRESHOLD + + # process scopes, which needs to be a seq + if scopes is None and "scopes" in info: + scopes = info.get("scopes") + if isinstance(scopes, str): + scopes = scopes.split(" ") + + return cls( + token=info.get("token"), + refresh_token=info.get("refresh_token"), + token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT, # always overrides + scopes=scopes, + client_id=info.get("client_id"), + client_secret=info.get("client_secret"), + quota_project_id=info.get("quota_project_id"), # may not exist + expiry=expiry, + rapt_token=info.get("rapt_token"), # may not exist + trust_boundary=info.get("trust_boundary"), # may not exist + universe_domain=info.get("universe_domain"), # may not exist + account=info.get("account", ""), # may not exist + ) + + @classmethod + def from_authorized_user_file(cls, filename, scopes=None): + """Creates a Credentials instance from an authorized user json file. + + Args: + filename (str): The path to the authorized user json file. + scopes (Sequence[str]): Optional list of scopes to include in the + credentials. + + Returns: + google.oauth2.credentials.Credentials: The constructed + credentials. + + Raises: + ValueError: If the file is not in the expected format. + """ + with io.open(filename, "r", encoding="utf-8") as json_file: + data = json.load(json_file) + return cls.from_authorized_user_info(data, scopes) + + def to_json(self, strip=None): + """Utility function that creates a JSON representation of a Credentials + object. + + Args: + strip (Sequence[str]): Optional list of members to exclude from the + generated JSON. + + Returns: + str: A JSON representation of this instance. When converted into + a dictionary, it can be passed to from_authorized_user_info() + to create a new credential instance. + """ + prep = { + "token": self.token, + "refresh_token": self.refresh_token, + "token_uri": self.token_uri, + "client_id": self.client_id, + "client_secret": self.client_secret, + "scopes": self.scopes, + "rapt_token": self.rapt_token, + "universe_domain": self._universe_domain, + "account": self._account, + } + if self.expiry: # flatten expiry timestamp + prep["expiry"] = self.expiry.isoformat() + "Z" + + # Remove empty entries (those which are None) + prep = {k: v for k, v in prep.items() if v is not None} + + # Remove entries that explicitely need to be removed + if strip is not None: + prep = {k: v for k, v in prep.items() if k not in strip} + + return json.dumps(prep) + + +class UserAccessTokenCredentials(credentials.CredentialsWithQuotaProject): + """Access token credentials for user account. + + Obtain the access token for a given user account or the current active + user account with the ``gcloud auth print-access-token`` command. + + Args: + account (Optional[str]): Account to get the access token for. If not + specified, the current active account will be used. + quota_project_id (Optional[str]): The project ID used for quota + and billing. + """ + + def __init__(self, account=None, quota_project_id=None): + warnings.warn( + "UserAccessTokenCredentials is deprecated, please use " + "google.oauth2.credentials.Credentials instead. To use " + "that credential type, simply run " + "`gcloud auth application-default login` and let the " + "client libraries pick up the application default credentials." + ) + super(UserAccessTokenCredentials, self).__init__() + self._account = account + self._quota_project_id = quota_project_id + + def with_account(self, account): + """Create a new instance with the given account. + + Args: + account (str): Account to get the access token for. + + Returns: + google.oauth2.credentials.UserAccessTokenCredentials: The created + credentials with the given account. + """ + return self.__class__(account=account, quota_project_id=self._quota_project_id) + + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + return self.__class__(account=self._account, quota_project_id=quota_project_id) + + def refresh(self, request): + """Refreshes the access token. + + Args: + request (google.auth.transport.Request): This argument is required + by the base class interface but not used in this implementation, + so just set it to `None`. + + Raises: + google.auth.exceptions.UserAccessTokenError: If the access token + refresh failed. + """ + self.token = _cloud_sdk.get_auth_access_token(self._account) + + @_helpers.copy_docstring(credentials.Credentials) + def before_request(self, request, method, url, headers): + self.refresh(request) + self.apply(headers) diff --git a/intelliaide/vendor/google/oauth2/gdch_credentials.py b/intelliaide/vendor/google/oauth2/gdch_credentials.py new file mode 100644 index 0000000..7410cfc --- /dev/null +++ b/intelliaide/vendor/google/oauth2/gdch_credentials.py @@ -0,0 +1,251 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Experimental GDCH credentials support. +""" + +import datetime + +from google.auth import _helpers +from google.auth import _service_account_info +from google.auth import credentials +from google.auth import exceptions +from google.auth import jwt +from google.oauth2 import _client + + +TOKEN_EXCHANGE_TYPE = "urn:ietf:params:oauth:token-type:token-exchange" +ACCESS_TOKEN_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token" +SERVICE_ACCOUNT_TOKEN_TYPE = "urn:k8s:params:oauth:token-type:serviceaccount" +JWT_LIFETIME = datetime.timedelta(seconds=3600) # 1 hour + + +class ServiceAccountCredentials(credentials.Credentials): + """Credentials for GDCH (`Google Distributed Cloud Hosted`_) for service + account users. + + .. _Google Distributed Cloud Hosted: + https://cloud.google.com/blog/topics/hybrid-cloud/\ + announcing-google-distributed-cloud-edge-and-hosted + + To create a GDCH service account credential, first create a JSON file of + the following format:: + + { + "type": "gdch_service_account", + "format_version": "1", + "project": "", + "private_key_id": "", + "private_key": "-----BEGIN EC PRIVATE KEY-----\n\n-----END EC PRIVATE KEY-----\n", + "name": "", + "ca_cert_path": "", + "token_uri": "https://service-identity./authenticate" + } + + The "format_version" field stands for the format of the JSON file. For now + it is always "1". The `private_key_id` and `private_key` is used for signing. + The `ca_cert_path` is used for token server TLS certificate verification. + + After the JSON file is created, set `GOOGLE_APPLICATION_CREDENTIALS` environment + variable to the JSON file path, then use the following code to create the + credential:: + + import google.auth + + credential, _ = google.auth.default() + credential = credential.with_gdch_audience("") + + We can also create the credential directly:: + + from google.oauth import gdch_credentials + + credential = gdch_credentials.ServiceAccountCredentials.from_service_account_file("") + credential = credential.with_gdch_audience("") + + The token is obtained in the following way. This class first creates a + self signed JWT. It uses the `name` value as the `iss` and `sub` claim, and + the `token_uri` as the `aud` claim, and signs the JWT with the `private_key`. + It then sends the JWT to the `token_uri` to exchange a final token for + `audience`. + """ + + def __init__( + self, signer, service_identity_name, project, audience, token_uri, ca_cert_path + ): + """ + Args: + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + service_identity_name (str): The service identity name. It will be + used as the `iss` and `sub` claim in the self signed JWT. + project (str): The project. + audience (str): The audience for the final token. + token_uri (str): The token server uri. + ca_cert_path (str): The CA cert path for token server side TLS + certificate verification. If the token server uses well known + CA, then this parameter can be `None`. + """ + super(ServiceAccountCredentials, self).__init__() + self._signer = signer + self._service_identity_name = service_identity_name + self._project = project + self._audience = audience + self._token_uri = token_uri + self._ca_cert_path = ca_cert_path + + def _create_jwt(self): + now = _helpers.utcnow() + expiry = now + JWT_LIFETIME + iss_sub_value = "system:serviceaccount:{}:{}".format( + self._project, self._service_identity_name + ) + + payload = { + "iss": iss_sub_value, + "sub": iss_sub_value, + "aud": self._token_uri, + "iat": _helpers.datetime_to_secs(now), + "exp": _helpers.datetime_to_secs(expiry), + } + + return _helpers.from_bytes(jwt.encode(self._signer, payload)) + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + import google.auth.transport.requests + + if not isinstance(request, google.auth.transport.requests.Request): + raise exceptions.RefreshError( + "For GDCH service account credentials, request must be a google.auth.transport.requests.Request object" + ) + + # Create a self signed JWT, and do token exchange. + jwt_token = self._create_jwt() + request_body = { + "grant_type": TOKEN_EXCHANGE_TYPE, + "audience": self._audience, + "requested_token_type": ACCESS_TOKEN_TOKEN_TYPE, + "subject_token": jwt_token, + "subject_token_type": SERVICE_ACCOUNT_TOKEN_TYPE, + } + response_data = _client._token_endpoint_request( + request, + self._token_uri, + request_body, + access_token=None, + use_json=True, + verify=self._ca_cert_path, + ) + + self.token, _, self.expiry, _ = _client._handle_refresh_grant_response( + response_data, None + ) + + def with_gdch_audience(self, audience): + """Create a copy of GDCH credentials with the specified audience. + + Args: + audience (str): The intended audience for GDCH credentials. + """ + return self.__class__( + self._signer, + self._service_identity_name, + self._project, + audience, + self._token_uri, + self._ca_cert_path, + ) + + @classmethod + def _from_signer_and_info(cls, signer, info): + """Creates a Credentials instance from a signer and service account + info. + + Args: + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + info (Mapping[str, str]): The service account info. + + Returns: + google.oauth2.gdch_credentials.ServiceAccountCredentials: The constructed + credentials. + + Raises: + ValueError: If the info is not in the expected format. + """ + if info["format_version"] != "1": + raise ValueError("Only format version 1 is supported") + + return cls( + signer, + info["name"], # service_identity_name + info["project"], + None, # audience + info["token_uri"], + info.get("ca_cert_path", None), + ) + + @classmethod + def from_service_account_info(cls, info): + """Creates a Credentials instance from parsed service account info. + + Args: + info (Mapping[str, str]): The service account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.oauth2.gdch_credentials.ServiceAccountCredentials: The constructed + credentials. + + Raises: + ValueError: If the info is not in the expected format. + """ + signer = _service_account_info.from_dict( + info, + require=[ + "format_version", + "private_key_id", + "private_key", + "name", + "project", + "token_uri", + ], + use_rsa_signer=False, + ) + return cls._from_signer_and_info(signer, info) + + @classmethod + def from_service_account_file(cls, filename): + """Creates a Credentials instance from a service account json file. + + Args: + filename (str): The path to the service account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.oauth2.gdch_credentials.ServiceAccountCredentials: The constructed + credentials. + """ + info, signer = _service_account_info.from_filename( + filename, + require=[ + "format_version", + "private_key_id", + "private_key", + "name", + "project", + "token_uri", + ], + use_rsa_signer=False, + ) + return cls._from_signer_and_info(signer, info) diff --git a/intelliaide/vendor/google/oauth2/id_token.py b/intelliaide/vendor/google/oauth2/id_token.py new file mode 100644 index 0000000..d21be1a --- /dev/null +++ b/intelliaide/vendor/google/oauth2/id_token.py @@ -0,0 +1,373 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google ID Token helpers. + +Provides support for verifying `OpenID Connect ID Tokens`_, especially ones +generated by Google infrastructure. + +To parse and verify an ID Token issued by Google's OAuth 2.0 authorization +server use :func:`verify_oauth2_token`. To verify an ID Token issued by +Firebase, use :func:`verify_firebase_token`. + +A general purpose ID Token verifier is available as :func:`verify_token`. + +Example:: + + from google.oauth2 import id_token + from google.auth.transport import requests + + request = requests.Request() + + id_info = id_token.verify_oauth2_token( + token, request, 'my-client-id.example.com') + + userid = id_info['sub'] + +By default, this will re-fetch certificates for each verification. Because +Google's public keys are only changed infrequently (on the order of once per +day), you may wish to take advantage of caching to reduce latency and the +potential for network errors. This can be accomplished using an external +library like `CacheControl`_ to create a cache-aware +:class:`google.auth.transport.Request`:: + + import cachecontrol + import google.auth.transport.requests + import requests + + session = requests.session() + cached_session = cachecontrol.CacheControl(session) + request = google.auth.transport.requests.Request(session=cached_session) + +.. _OpenID Connect ID Tokens: + http://openid.net/specs/openid-connect-core-1_0.html#IDToken +.. _CacheControl: https://cachecontrol.readthedocs.io +""" +from __future__ import annotations + +import http.client as http_client +import json +import os +from typing import Any, Mapping, Union + +from google.auth import environment_vars +from google.auth import exceptions +from google.auth import jwt +from google.auth import transport + + +# The URL that provides public certificates for verifying ID tokens issued +# by Google's OAuth 2.0 authorization server. +_GOOGLE_OAUTH2_CERTS_URL = "https://www.googleapis.com/oauth2/v1/certs" + +# The URL that provides public certificates for verifying ID tokens issued +# by Firebase and the Google APIs infrastructure +_GOOGLE_APIS_CERTS_URL = ( + "https://www.googleapis.com/robot/v1/metadata/x509" + "/securetoken@system.gserviceaccount.com" +) + +_GOOGLE_ISSUERS = ["accounts.google.com", "https://accounts.google.com"] + + +def _fetch_certs(request, certs_url): + """Fetches certificates. + + Google-style certificate endpoints return JSON in the format of + ``{'key id': 'x509 certificate'}`` or a certificate array according + to the JWK spec (see https://tools.ietf.org/html/rfc7517). + + Args: + request (google.auth.transport.Request): The object used to make + HTTP requests. + certs_url (str): The certificate endpoint URL. + + Returns: + Mapping[str, str] | Mapping[str, list]: A mapping of public keys + in x.509 or JWK spec. + """ + response = request(certs_url, method="GET") + + if response.status != http_client.OK: + raise exceptions.TransportError( + "Could not fetch certificates at {}".format(certs_url) + ) + + return json.loads(response.data.decode("utf-8")) + + +def verify_token( + id_token: Union[str, bytes], + request: transport.Request, + audience: Union[str, list[str], None] = None, + certs_url: str = _GOOGLE_OAUTH2_CERTS_URL, + clock_skew_in_seconds: int = 0, +) -> Mapping[str, Any]: + """Verifies an ID token and returns the decoded token. + + Args: + id_token (Union[str, bytes]): The encoded token. + request (google.auth.transport.Request): The object used to make + HTTP requests. + audience (str or list): The audience or audiences that this token is + intended for. If None then the audience is not verified. + certs_url (str): The URL that specifies the certificates to use to + verify the token. This URL should return JSON in the format of + ``{'key id': 'x509 certificate'}`` or a certificate array according to + the JWK spec (see https://tools.ietf.org/html/rfc7517). + clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` + validation. + + Returns: + Mapping[str, Any]: The decoded token. + """ + certs = _fetch_certs(request, certs_url) + + if "keys" in certs: + try: + import jwt as jwt_lib # type: ignore + except ImportError as caught_exc: # pragma: NO COVER + raise ImportError( + "The pyjwt library is not installed, please install the pyjwt package to use the jwk certs format." + ) from caught_exc + jwks_client = jwt_lib.PyJWKClient(certs_url) + signing_key = jwks_client.get_signing_key_from_jwt(id_token) + return jwt_lib.decode( + id_token, + signing_key.key, + algorithms=[signing_key.algorithm_name], + audience=audience, + ) + else: + return jwt.decode( + id_token, + certs=certs, + audience=audience, + clock_skew_in_seconds=clock_skew_in_seconds, + ) + + +def verify_oauth2_token(id_token, request, audience=None, clock_skew_in_seconds=0): + """Verifies an ID Token issued by Google's OAuth 2.0 authorization server. + + Args: + id_token (Union[str, bytes]): The encoded token. + request (google.auth.transport.Request): The object used to make + HTTP requests. + audience (str): The audience that this token is intended for. This is + typically your application's OAuth 2.0 client ID. If None then the + audience is not verified. + clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` + validation. + + Returns: + Mapping[str, Any]: The decoded token. + + Raises: + exceptions.GoogleAuthError: If the issuer is invalid. + ValueError: If token verification fails + """ + idinfo = verify_token( + id_token, + request, + audience=audience, + certs_url=_GOOGLE_OAUTH2_CERTS_URL, + clock_skew_in_seconds=clock_skew_in_seconds, + ) + + if idinfo["iss"] not in _GOOGLE_ISSUERS: + raise exceptions.GoogleAuthError( + "Wrong issuer. 'iss' should be one of the following: {}".format( + _GOOGLE_ISSUERS + ) + ) + + return idinfo + + +def verify_firebase_token(id_token, request, audience=None, clock_skew_in_seconds=0): + """Verifies an ID Token issued by Firebase Authentication. + + Args: + id_token (Union[str, bytes]): The encoded token. + request (google.auth.transport.Request): The object used to make + HTTP requests. + audience (str): The audience that this token is intended for. This is + typically your Firebase application ID. If None then the audience + is not verified. + clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` + validation. + + Returns: + Mapping[str, Any]: The decoded token. + """ + return verify_token( + id_token, + request, + audience=audience, + certs_url=_GOOGLE_APIS_CERTS_URL, + clock_skew_in_seconds=clock_skew_in_seconds, + ) + + +def fetch_id_token_credentials(audience, request=None): + """Create the ID Token credentials from the current environment. + + This function acquires ID token from the environment in the following order. + See https://google.aip.dev/auth/4110. + + 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set + to the path of a valid service account JSON file, then ID token is + acquired using this service account credentials. + 2. If the application is running in Compute Engine, App Engine or Cloud Run, + then the ID token are obtained from the metadata server. + 3. If metadata server doesn't exist and no valid service account credentials + are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will + be raised. + + Example:: + + import google.oauth2.id_token + import google.auth.transport.requests + + request = google.auth.transport.requests.Request() + target_audience = "https://pubsub.googleapis.com" + + # Create ID token credentials. + credentials = google.oauth2.id_token.fetch_id_token_credentials(target_audience, request=request) + + # Refresh the credential to obtain an ID token. + credentials.refresh(request) + + id_token = credentials.token + id_token_expiry = credentials.expiry + + Args: + audience (str): The audience that this ID token is intended for. + request (Optional[google.auth.transport.Request]): A callable used to make + HTTP requests. A request object will be created if not provided. + + Returns: + google.auth.credentials.Credentials: The ID token credentials. + + Raises: + ~google.auth.exceptions.DefaultCredentialsError: + If metadata server doesn't exist and no valid service account + credentials are found. + """ + # 1. Try to get credentials from the GOOGLE_APPLICATION_CREDENTIALS environment + # variable. + credentials_filename = os.environ.get(environment_vars.CREDENTIALS) + if credentials_filename: + if not ( + os.path.exists(credentials_filename) + and os.path.isfile(credentials_filename) + ): + raise exceptions.DefaultCredentialsError( + "GOOGLE_APPLICATION_CREDENTIALS path is either not found or invalid." + ) + + try: + with open(credentials_filename, "r") as f: + from google.oauth2 import service_account + + info = json.load(f) + if info.get("type") == "service_account": + return service_account.IDTokenCredentials.from_service_account_info( + info, target_audience=audience + ) + elif info.get("type") == "impersonated_service_account": + from google.auth import impersonated_credentials + + target_credentials = impersonated_credentials.Credentials.from_impersonated_service_account_info( + info + ) + + return impersonated_credentials.IDTokenCredentials( + target_credentials=target_credentials, + target_audience=audience, + include_email=True, + ) + except ValueError as caught_exc: + new_exc = exceptions.DefaultCredentialsError( + "GOOGLE_APPLICATION_CREDENTIALS is not valid service account credentials.", + caught_exc, + ) + raise new_exc from caught_exc + + # 2. Try to fetch ID token from metada server if it exists. The code + # works for GAE and Cloud Run metadata server as well. + try: + from google.auth import compute_engine + from google.auth.compute_engine import _metadata + + # Create a request object if not provided. + if not request: + import google.auth.transport.requests + + request = google.auth.transport.requests.Request() + + if _metadata.ping(request): + return compute_engine.IDTokenCredentials( + request, audience, use_metadata_identity_endpoint=True + ) + except (ImportError, exceptions.TransportError): + pass + + raise exceptions.DefaultCredentialsError( + "Neither metadata server or valid service account credentials are found." + ) + + +def fetch_id_token(request, audience): + """Fetch the ID Token from the current environment. + + This function acquires ID token from the environment in the following order. + See https://google.aip.dev/auth/4110. + + 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set + to the path of a valid service account JSON file, then ID token is + acquired using this service account credentials. + 2. If the application is running in Compute Engine, App Engine or Cloud Run, + then the ID token are obtained from the metadata server. + 3. If metadata server doesn't exist and no valid service account credentials + are found, :class:`~google.auth.exceptions.DefaultCredentialsError` will + be raised. + + Example:: + + import google.oauth2.id_token + import google.auth.transport.requests + + request = google.auth.transport.requests.Request() + target_audience = "https://pubsub.googleapis.com" + + id_token = google.oauth2.id_token.fetch_id_token(request, target_audience) + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + audience (str): The audience that this ID token is intended for. + + Returns: + str: The ID token. + + Raises: + ~google.auth.exceptions.DefaultCredentialsError: + If metadata server doesn't exist and no valid service account + credentials are found. + """ + id_token_credentials = fetch_id_token_credentials(audience, request=request) + id_token_credentials.refresh(request) + return id_token_credentials.token diff --git a/intelliaide/vendor/google/oauth2/py.typed b/intelliaide/vendor/google/oauth2/py.typed new file mode 100644 index 0000000..d82ed62 --- /dev/null +++ b/intelliaide/vendor/google/oauth2/py.typed @@ -0,0 +1,2 @@ +# Marker file for PEP 561. +# The google-oauth2 package uses inline types. diff --git a/intelliaide/vendor/google/oauth2/reauth.py b/intelliaide/vendor/google/oauth2/reauth.py new file mode 100644 index 0000000..abf691d --- /dev/null +++ b/intelliaide/vendor/google/oauth2/reauth.py @@ -0,0 +1,373 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""A module that provides functions for handling rapt authentication. + +Reauth is a process of obtaining additional authentication (such as password, +security token, etc.) while refreshing OAuth 2.0 credentials for a user. + +Credentials that use the Reauth flow must have the reauth scope, +``https://www.googleapis.com/auth/accounts.reauth``. + +This module provides a high-level function for executing the Reauth process, +:func:`refresh_grant`, and lower-level helpers for doing the individual +steps of the reauth process. + +Those steps are: + +1. Obtaining a list of challenges from the reauth server. +2. Running through each challenge and sending the result back to the reauth + server. +3. Refreshing the access token using the returned rapt token. +""" + +import sys + +from google.auth import exceptions +from google.auth import metrics +from google.oauth2 import _client +from google.oauth2 import challenges + + +_REAUTH_SCOPE = "https://www.googleapis.com/auth/accounts.reauth" +_REAUTH_API = "https://reauth.googleapis.com/v2/sessions" + +_REAUTH_NEEDED_ERROR = "invalid_grant" +_REAUTH_NEEDED_ERROR_INVALID_RAPT = "invalid_rapt" +_REAUTH_NEEDED_ERROR_RAPT_REQUIRED = "rapt_required" + +_AUTHENTICATED = "AUTHENTICATED" +_CHALLENGE_REQUIRED = "CHALLENGE_REQUIRED" +_CHALLENGE_PENDING = "CHALLENGE_PENDING" + + +# Override this global variable to set custom max number of rounds of reauth +# challenges should be run. +RUN_CHALLENGE_RETRY_LIMIT = 5 + + +def is_interactive(): + """Check if we are in an interractive environment. + + Override this function with a different logic if you are using this library + outside a CLI. + + If the rapt token needs refreshing, the user needs to answer the challenges. + If the user is not in an interractive environment, the challenges can not + be answered and we just wait for timeout for no reason. + + Returns: + bool: True if is interactive environment, False otherwise. + """ + + return sys.stdin.isatty() + + +def _get_challenges( + request, supported_challenge_types, access_token, requested_scopes=None +): + """Does initial request to reauth API to get the challenges. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + supported_challenge_types (Sequence[str]): list of challenge names + supported by the manager. + access_token (str): Access token with reauth scopes. + requested_scopes (Optional(Sequence[str])): Authorized scopes for the credentials. + + Returns: + dict: The response from the reauth API. + """ + body = {"supportedChallengeTypes": supported_challenge_types} + if requested_scopes: + body["oauthScopesForDomainPolicyLookup"] = requested_scopes + metrics_header = {metrics.API_CLIENT_HEADER: metrics.reauth_start()} + + return _client._token_endpoint_request( + request, + _REAUTH_API + ":start", + body, + access_token=access_token, + use_json=True, + headers=metrics_header, + ) + + +def _send_challenge_result( + request, session_id, challenge_id, client_input, access_token +): + """Attempt to refresh access token by sending next challenge result. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + session_id (str): session id returned by the initial reauth call. + challenge_id (str): challenge id returned by the initial reauth call. + client_input: dict with a challenge-specific client input. For example: + ``{'credential': password}`` for password challenge. + access_token (str): Access token with reauth scopes. + + Returns: + dict: The response from the reauth API. + """ + body = { + "sessionId": session_id, + "challengeId": challenge_id, + "action": "RESPOND", + "proposalResponse": client_input, + } + metrics_header = {metrics.API_CLIENT_HEADER: metrics.reauth_continue()} + + return _client._token_endpoint_request( + request, + _REAUTH_API + "/{}:continue".format(session_id), + body, + access_token=access_token, + use_json=True, + headers=metrics_header, + ) + + +def _run_next_challenge(msg, request, access_token): + """Get the next challenge from msg and run it. + + Args: + msg (dict): Reauth API response body (either from the initial request to + https://reauth.googleapis.com/v2/sessions:start or from sending the + previous challenge response to + https://reauth.googleapis.com/v2/sessions/id:continue) + request (google.auth.transport.Request): A callable used to make + HTTP requests. + access_token (str): reauth access token + + Returns: + dict: The response from the reauth API. + + Raises: + google.auth.exceptions.ReauthError: if reauth failed. + """ + for challenge in msg["challenges"]: + if challenge["status"] != "READY": + # Skip non-activated challenges. + continue + c = challenges.AVAILABLE_CHALLENGES.get(challenge["challengeType"], None) + if not c: + raise exceptions.ReauthFailError( + "Unsupported challenge type {0}. Supported types: {1}".format( + challenge["challengeType"], + ",".join(list(challenges.AVAILABLE_CHALLENGES.keys())), + ) + ) + if not c.is_locally_eligible: + raise exceptions.ReauthFailError( + "Challenge {0} is not locally eligible".format( + challenge["challengeType"] + ) + ) + client_input = c.obtain_challenge_input(challenge) + if not client_input: + return None + return _send_challenge_result( + request, + msg["sessionId"], + challenge["challengeId"], + client_input, + access_token, + ) + return None + + +def _obtain_rapt(request, access_token, requested_scopes): + """Given an http request method and reauth access token, get rapt token. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + access_token (str): reauth access token + requested_scopes (Sequence[str]): scopes required by the client application + + Returns: + str: The rapt token. + + Raises: + google.auth.exceptions.ReauthError: if reauth failed + """ + msg = _get_challenges( + request, + list(challenges.AVAILABLE_CHALLENGES.keys()), + access_token, + requested_scopes, + ) + + if msg["status"] == _AUTHENTICATED: + return msg["encodedProofOfReauthToken"] + + for _ in range(0, RUN_CHALLENGE_RETRY_LIMIT): + if not ( + msg["status"] == _CHALLENGE_REQUIRED or msg["status"] == _CHALLENGE_PENDING + ): + raise exceptions.ReauthFailError( + "Reauthentication challenge failed due to API error: {}".format( + msg["status"] + ) + ) + + if not is_interactive(): + raise exceptions.ReauthFailError( + "Reauthentication challenge could not be answered because you are not" + " in an interactive session." + ) + + msg = _run_next_challenge(msg, request, access_token) + + if not msg: + raise exceptions.ReauthFailError("Failed to obtain rapt token.") + if msg["status"] == _AUTHENTICATED: + return msg["encodedProofOfReauthToken"] + + # If we got here it means we didn't get authenticated. + raise exceptions.ReauthFailError("Failed to obtain rapt token.") + + +def get_rapt_token( + request, client_id, client_secret, refresh_token, token_uri, scopes=None +): + """Given an http request method and refresh_token, get rapt token. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + client_id (str): client id to get access token for reauth scope. + client_secret (str): client secret for the client_id + refresh_token (str): refresh token to refresh access token + token_uri (str): uri to refresh access token + scopes (Optional(Sequence[str])): scopes required by the client application + + Returns: + str: The rapt token. + Raises: + google.auth.exceptions.RefreshError: If reauth failed. + """ + sys.stderr.write("Reauthentication required.\n") + + # Get access token for reauth. + access_token, _, _, _ = _client.refresh_grant( + request=request, + client_id=client_id, + client_secret=client_secret, + refresh_token=refresh_token, + token_uri=token_uri, + scopes=[_REAUTH_SCOPE], + ) + + # Get rapt token from reauth API. + rapt_token = _obtain_rapt(request, access_token, requested_scopes=scopes) + sys.stderr.write("Reauthentication successful.\n") + + return rapt_token + + +def refresh_grant( + request, + token_uri, + refresh_token, + client_id, + client_secret, + scopes=None, + rapt_token=None, + enable_reauth_refresh=False, +): + """Implements the reauthentication flow. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token_uri (str): The OAuth 2.0 authorizations server's token endpoint + URI. + refresh_token (str): The refresh token to use to get a new access + token. + client_id (str): The OAuth 2.0 application's client ID. + client_secret (str): The Oauth 2.0 appliaction's client secret. + scopes (Optional(Sequence[str])): Scopes to request. If present, all + scopes must be authorized for the refresh token. Useful if refresh + token has a wild card scope (e.g. + 'https://www.googleapis.com/auth/any-api'). + rapt_token (Optional(str)): The rapt token for reauth. + enable_reauth_refresh (Optional[bool]): Whether reauth refresh flow + should be used. The default value is False. This option is for + gcloud only, other users should use the default value. + + Returns: + Tuple[str, Optional[str], Optional[datetime], Mapping[str, str], str]: The + access token, new refresh token, expiration, the additional data + returned by the token endpoint, and the rapt token. + + Raises: + google.auth.exceptions.RefreshError: If the token endpoint returned + an error. + """ + body = { + "grant_type": _client._REFRESH_GRANT_TYPE, + "client_id": client_id, + "client_secret": client_secret, + "refresh_token": refresh_token, + } + if scopes: + body["scope"] = " ".join(scopes) + if rapt_token: + body["rapt"] = rapt_token + metrics_header = {metrics.API_CLIENT_HEADER: metrics.token_request_user()} + + ( + response_status_ok, + response_data, + retryable_error, + ) = _client._token_endpoint_request_no_throw( + request, token_uri, body, headers=metrics_header + ) + + if not response_status_ok and isinstance(response_data, str): + raise exceptions.RefreshError(response_data, retryable=False) + + if ( + not response_status_ok + and response_data.get("error") == _REAUTH_NEEDED_ERROR + and ( + response_data.get("error_subtype") == _REAUTH_NEEDED_ERROR_INVALID_RAPT + or response_data.get("error_subtype") == _REAUTH_NEEDED_ERROR_RAPT_REQUIRED + ) + ): + if not enable_reauth_refresh: + raise exceptions.RefreshError( + "Reauthentication is needed. Please run `gcloud auth application-default login` to reauthenticate." + ) + + rapt_token = get_rapt_token( + request, client_id, client_secret, refresh_token, token_uri, scopes=scopes + ) + body["rapt"] = rapt_token + ( + response_status_ok, + response_data, + retryable_error, + ) = _client._token_endpoint_request_no_throw( + request, token_uri, body, headers=metrics_header + ) + + if not response_status_ok: + _client._handle_error_response(response_data, retryable_error) + return _client._handle_refresh_grant_response(response_data, refresh_token) + ( + rapt_token, + ) diff --git a/intelliaide/vendor/google/oauth2/service_account.py b/intelliaide/vendor/google/oauth2/service_account.py new file mode 100644 index 0000000..5c19b8f --- /dev/null +++ b/intelliaide/vendor/google/oauth2/service_account.py @@ -0,0 +1,882 @@ +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Service Accounts: JSON Web Token (JWT) Profile for OAuth 2.0 + +This module implements the JWT Profile for OAuth 2.0 Authorization Grants +as defined by `RFC 7523`_ with particular support for how this RFC is +implemented in Google's infrastructure. Google refers to these credentials +as *Service Accounts*. + +Service accounts are used for server-to-server communication, such as +interactions between a web application server and a Google service. The +service account belongs to your application instead of to an individual end +user. In contrast to other OAuth 2.0 profiles, no users are involved and your +application "acts" as the service account. + +Typically an application uses a service account when the application uses +Google APIs to work with its own data rather than a user's data. For example, +an application that uses Google Cloud Datastore for data persistence would use +a service account to authenticate its calls to the Google Cloud Datastore API. +However, an application that needs to access a user's Drive documents would +use the normal OAuth 2.0 profile. + +Additionally, Google Apps domain administrators can grant service accounts +`domain-wide delegation`_ authority to access user data on behalf of users in +the domain. + +This profile uses a JWT to acquire an OAuth 2.0 access token. The JWT is used +in place of the usual authorization token returned during the standard +OAuth 2.0 Authorization Code grant. The JWT is only used for this purpose, as +the acquired access token is used as the bearer token when making requests +using these credentials. + +This profile differs from normal OAuth 2.0 profile because no user consent +step is required. The use of the private key allows this profile to assert +identity directly. + +This profile also differs from the :mod:`google.auth.jwt` authentication +because the JWT credentials use the JWT directly as the bearer token. This +profile instead only uses the JWT to obtain an OAuth 2.0 access token. The +obtained OAuth 2.0 access token is used as the bearer token. + +Domain-wide delegation +---------------------- + +Domain-wide delegation allows a service account to access user data on +behalf of any user in a Google Apps domain without consent from the user. +For example, an application that uses the Google Calendar API to add events to +the calendars of all users in a Google Apps domain would use a service account +to access the Google Calendar API on behalf of users. + +The Google Apps administrator must explicitly authorize the service account to +do this. This authorization step is referred to as "delegating domain-wide +authority" to a service account. + +You can use domain-wise delegation by creating a set of credentials with a +specific subject using :meth:`~Credentials.with_subject`. + +.. _RFC 7523: https://tools.ietf.org/html/rfc7523 +""" + +import copy +import datetime +import logging +from typing import Optional, TYPE_CHECKING + + +from google.auth import _helpers +from google.auth import _service_account_info +from google.auth import credentials +from google.auth import exceptions +from google.auth import iam +from google.auth import jwt +from google.auth import metrics +from google.oauth2 import _client + +if TYPE_CHECKING: # pragma: NO COVER + import google.auth.transport + +_LOGGER = logging.getLogger(__name__) + +_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds +_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" + + +class Credentials( + credentials.Signing, + credentials.Scoped, + credentials.CredentialsWithQuotaProject, + credentials.CredentialsWithTokenUri, + credentials.CredentialsWithRegionalAccessBoundary, +): + """Service account credentials + + Usually, you'll create these credentials with one of the helper + constructors. To create credentials using a Google service account + private key JSON file:: + + credentials = service_account.Credentials.from_service_account_file( + 'service-account.json') + + Or if you already have the service account file loaded:: + + service_account_info = json.load(open('service_account.json')) + credentials = service_account.Credentials.from_service_account_info( + service_account_info) + + Both helper methods pass on arguments to the constructor, so you can + specify additional scopes and a subject if necessary:: + + credentials = service_account.Credentials.from_service_account_file( + 'service-account.json', + scopes=['email'], + subject='user@example.com') + + The credentials are considered immutable. If you want to modify the scopes + or the subject used for delegation, use :meth:`with_scopes` or + :meth:`with_subject`:: + + scoped_credentials = credentials.with_scopes(['email']) + delegated_credentials = credentials.with_subject(subject) + + To add a quota project, use :meth:`with_quota_project`:: + + credentials = credentials.with_quota_project('myproject-123') + """ + + def __init__( + self, + signer, + service_account_email, + token_uri, + scopes=None, + default_scopes=None, + subject=None, + project_id=None, + quota_project_id=None, + additional_claims=None, + always_use_jwt_access=False, + universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, + trust_boundary=None, + ): + """ + Args: + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + service_account_email (str): The service account's email. + scopes (Sequence[str]): User-defined scopes to request during the + authorization grant. + default_scopes (Sequence[str]): Default scopes passed by a + Google client library. Use 'scopes' for user-defined scopes. + token_uri (str): The OAuth 2.0 Token URI. + subject (str): For domain-wide delegation, the email address of the + user to for which to request delegated access. + project_id (str): Project ID associated with the service account + credential. + quota_project_id (Optional[str]): The project ID used for quota and + billing. + additional_claims (Mapping[str, str]): Any additional claims for + the JWT assertion used in the authorization grant. + always_use_jwt_access (Optional[bool]): Whether self signed JWT should + be always used. + universe_domain (str): The universe domain. The default + universe domain is googleapis.com. For default value self + signed jwt is used for token refresh. + trust_boundary (Mapping[str,str]): A credential trust boundary. + + .. note:: Typically one of the helper constructors + :meth:`from_service_account_file` or + :meth:`from_service_account_info` are used instead of calling the + constructor directly. + """ + super(Credentials, self).__init__() + + self._cred_file_path = None + self._scopes = scopes + self._default_scopes = default_scopes + self._signer = signer + self._service_account_email = service_account_email + self._subject = subject + self._project_id = project_id + self._quota_project_id = quota_project_id + self._token_uri = token_uri + self._always_use_jwt_access = always_use_jwt_access + self._universe_domain = universe_domain or credentials.DEFAULT_UNIVERSE_DOMAIN + + if universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN: + self._always_use_jwt_access = True + + self._jwt_credentials = None + + if additional_claims is not None: + self._additional_claims = additional_claims + else: + self._additional_claims = {} + + self._trust_boundary = trust_boundary + + @classmethod + def _from_signer_and_info(cls, signer, info, **kwargs): + """Creates a Credentials instance from a signer and service account + info. + + Args: + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + info (Mapping[str, str]): The service account info. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.Credentials: The constructed credentials. + + Raises: + ValueError: If the info is not in the expected format. + """ + return cls( + signer, + service_account_email=info["client_email"], + token_uri=info["token_uri"], + project_id=info.get("project_id"), + universe_domain=info.get( + "universe_domain", credentials.DEFAULT_UNIVERSE_DOMAIN + ), + trust_boundary=info.get("trust_boundary"), + **kwargs, + ) + + @classmethod + def from_service_account_info(cls, info, **kwargs): + """Creates a Credentials instance from parsed service account info. + + Args: + info (Mapping[str, str]): The service account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.service_account.Credentials: The constructed + credentials. + + Raises: + ValueError: If the info is not in the expected format. + """ + signer = _service_account_info.from_dict( + info, require=["client_email", "token_uri"] + ) + return cls._from_signer_and_info(signer, info, **kwargs) + + @classmethod + def from_service_account_file(cls, filename, **kwargs): + """Creates a Credentials instance from a service account json file. + + Args: + filename (str): The path to the service account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.service_account.Credentials: The constructed + credentials. + """ + info, signer = _service_account_info.from_filename( + filename, require=["client_email", "token_uri"] + ) + return cls._from_signer_and_info(signer, info, **kwargs) + + @property + def service_account_email(self): + """The service account email.""" + return self._service_account_email + + @property + def project_id(self): + """Project ID associated with this credential.""" + return self._project_id + + @property + def requires_scopes(self): + """Checks if the credentials requires scopes. + + Returns: + bool: True if there are no scopes set otherwise False. + """ + return True if not self._scopes else False + + def _make_copy(self): + cred = self.__class__( + self._signer, + service_account_email=self._service_account_email, + scopes=copy.copy(self._scopes), + default_scopes=copy.copy(self._default_scopes), + token_uri=self._token_uri, + subject=self._subject, + project_id=self._project_id, + quota_project_id=self._quota_project_id, + additional_claims=self._additional_claims.copy(), + always_use_jwt_access=self._always_use_jwt_access, + universe_domain=self._universe_domain, + trust_boundary=self._trust_boundary, + ) + cred._cred_file_path = self._cred_file_path + self._copy_regional_access_boundary_manager(cred) + return cred + + @_helpers.copy_docstring(credentials.Scoped) + def with_scopes(self, scopes, default_scopes=None): + cred = self._make_copy() + cred._scopes = scopes + cred._default_scopes = default_scopes + return cred + + def with_always_use_jwt_access(self, always_use_jwt_access): + """Create a copy of these credentials with the specified always_use_jwt_access value. + + Args: + always_use_jwt_access (bool): Whether always use self signed JWT or not. + + Returns: + google.auth.service_account.Credentials: A new credentials + instance. + Raises: + google.auth.exceptions.InvalidValue: If the universe domain is not + default and always_use_jwt_access is False. + """ + cred = self._make_copy() + if ( + cred._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN + and not always_use_jwt_access + ): + raise exceptions.InvalidValue( + "always_use_jwt_access should be True for non-default universe domain" + ) + cred._always_use_jwt_access = always_use_jwt_access + return cred + + @_helpers.copy_docstring(credentials.CredentialsWithUniverseDomain) + def with_universe_domain(self, universe_domain): + cred = self._make_copy() + cred._universe_domain = universe_domain + if universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN: + cred._always_use_jwt_access = True + return cred + + def with_subject(self, subject): + """Create a copy of these credentials with the specified subject. + + Args: + subject (str): The subject claim. + + Returns: + google.auth.service_account.Credentials: A new credentials + instance. + """ + cred = self._make_copy() + cred._subject = subject + return cred + + def with_claims(self, additional_claims): + """Returns a copy of these credentials with modified claims. + + Args: + additional_claims (Mapping[str, str]): Any additional claims for + the JWT payload. This will be merged with the current + additional claims. + + Returns: + google.auth.service_account.Credentials: A new credentials + instance. + """ + new_additional_claims = copy.deepcopy(self._additional_claims) + new_additional_claims.update(additional_claims or {}) + cred = self._make_copy() + cred._additional_claims = new_additional_claims + return cred + + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + cred = self._make_copy() + cred._quota_project_id = quota_project_id + return cred + + @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) + def with_token_uri(self, token_uri): + cred = self._make_copy() + cred._token_uri = token_uri + return cred + + def _make_authorization_grant_assertion(self): + """Create the OAuth 2.0 assertion. + + This assertion is used during the OAuth 2.0 grant to acquire an + access token. + + Returns: + bytes: The authorization grant assertion. + """ + now = _helpers.utcnow() + lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS) + expiry = now + lifetime + + payload = { + "iat": _helpers.datetime_to_secs(now), + "exp": _helpers.datetime_to_secs(expiry), + # The issuer must be the service account email. + "iss": self._service_account_email, + # The audience must be the auth token endpoint's URI + "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT, + "scope": _helpers.scopes_to_string(self._scopes or ()), + } + + payload.update(self._additional_claims) + + # The subject can be a user email for domain-wide delegation. + if self._subject: + payload.setdefault("sub", self._subject) + + token = jwt.encode(self._signer, payload) + + return token + + def _use_self_signed_jwt(self): + # Since domain wide delegation doesn't work with self signed JWT. If + # subject exists, then we should not use self signed JWT. + return self._subject is None and self._jwt_credentials is not None + + def _metric_header_for_usage(self): + if self._use_self_signed_jwt(): + return metrics.CRED_TYPE_SA_JWT + return metrics.CRED_TYPE_SA_ASSERTION + + @_helpers.copy_docstring(credentials.CredentialsWithRegionalAccessBoundary) + def _perform_refresh_token(self, request): + if self._always_use_jwt_access and not self._jwt_credentials: + # If self signed jwt should be used but jwt credential is not + # created, try to create one with scopes + self._create_self_signed_jwt(None) + + if ( + self._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN + and self._subject + ): + raise exceptions.RefreshError( + "domain wide delegation is not supported for non-default universe domain" + ) + + if self._use_self_signed_jwt(): + self._jwt_credentials.refresh(request) + self.token = self._jwt_credentials.token.decode() + self.expiry = self._jwt_credentials.expiry + else: + assertion = self._make_authorization_grant_assertion() + access_token, expiry, _ = _client.jwt_grant( + request, self._token_uri, assertion + ) + self.token = access_token + self.expiry = expiry + + def _create_self_signed_jwt(self, audience): + """Create a self-signed JWT from the credentials if requirements are met. + + Args: + audience (str): The service URL. ``https://[API_ENDPOINT]/`` + """ + # https://google.aip.dev/auth/4111 + if self._always_use_jwt_access: + if self._scopes: + additional_claims = {"scope": " ".join(self._scopes)} + if ( + self._jwt_credentials is None + or self._jwt_credentials.additional_claims != additional_claims + ): + self._jwt_credentials = jwt.Credentials.from_signing_credentials( + self, None, additional_claims=additional_claims + ) + elif audience: + if ( + self._jwt_credentials is None + or self._jwt_credentials._audience != audience + ): + self._jwt_credentials = jwt.Credentials.from_signing_credentials( + self, audience + ) + elif self._default_scopes: + additional_claims = {"scope": " ".join(self._default_scopes)} + if ( + self._jwt_credentials is None + or additional_claims != self._jwt_credentials.additional_claims + ): + self._jwt_credentials = jwt.Credentials.from_signing_credentials( + self, None, additional_claims=additional_claims + ) + elif not self._scopes and audience: + self._jwt_credentials = jwt.Credentials.from_signing_credentials( + self, audience + ) + + def _build_regional_access_boundary_lookup_url( + self, request: "Optional[google.auth.transport.Request]" = None # noqa: F821 + ): + """Builds and returns the URL for the Regional Access Boundary lookup API. + + This method constructs the specific URL for the IAM Credentials API's + `allowedLocations` endpoint, using the credential's universe domain + and service account email. + + Returns: + Optional[str]: The URL for the Regional Access Boundary lookup endpoint, or None + if the service account email is missing. + """ + if not self.service_account_email: + _LOGGER.error( + "Service account email is required to build the Regional Access Boundary lookup URL for service account credentials." + ) + return None + return iam._SERVICE_ACCOUNT_REGIONAL_ACCESS_BOUNDARY_LOOKUP_ENDPOINT.format( + service_account_email=self._service_account_email, + ) + + @_helpers.copy_docstring(credentials.Signing) + def sign_bytes(self, message): + return self._signer.sign(message) + + @property # type: ignore + @_helpers.copy_docstring(credentials.Signing) + def signer(self): + return self._signer + + @property # type: ignore + @_helpers.copy_docstring(credentials.Signing) + def signer_email(self): + return self._service_account_email + + @_helpers.copy_docstring(credentials.Credentials) + def get_cred_info(self): + if self._cred_file_path: + return { + "credential_source": self._cred_file_path, + "credential_type": "service account credentials", + "principal": self.service_account_email, + } + return None + + +class IDTokenCredentials( + credentials.Signing, + credentials.CredentialsWithQuotaProject, + credentials.CredentialsWithTokenUri, +): + """Open ID Connect ID Token-based service account credentials. + + These credentials are largely similar to :class:`.Credentials`, but instead + of using an OAuth 2.0 Access Token as the bearer token, they use an Open + ID Connect ID Token as the bearer token. These credentials are useful when + communicating to services that require ID Tokens and can not accept access + tokens. + + Usually, you'll create these credentials with one of the helper + constructors. To create credentials using a Google service account + private key JSON file:: + + credentials = ( + service_account.IDTokenCredentials.from_service_account_file( + 'service-account.json')) + + + Or if you already have the service account file loaded:: + + service_account_info = json.load(open('service_account.json')) + credentials = ( + service_account.IDTokenCredentials.from_service_account_info( + service_account_info)) + + + Both helper methods pass on arguments to the constructor, so you can + specify additional scopes and a subject if necessary:: + + credentials = ( + service_account.IDTokenCredentials.from_service_account_file( + 'service-account.json', + scopes=['email'], + subject='user@example.com')) + + + The credentials are considered immutable. If you want to modify the scopes + or the subject used for delegation, use :meth:`with_scopes` or + :meth:`with_subject`:: + + scoped_credentials = credentials.with_scopes(['email']) + delegated_credentials = credentials.with_subject(subject) + + """ + + def __init__( + self, + signer, + service_account_email, + token_uri, + target_audience, + additional_claims=None, + quota_project_id=None, + universe_domain=credentials.DEFAULT_UNIVERSE_DOMAIN, + ): + """ + Args: + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + service_account_email (str): The service account's email. + token_uri (str): The OAuth 2.0 Token URI. + target_audience (str): The intended audience for these credentials, + used when requesting the ID Token. The ID Token's ``aud`` claim + will be set to this string. + additional_claims (Mapping[str, str]): Any additional claims for + the JWT assertion used in the authorization grant. + quota_project_id (Optional[str]): The project ID used for quota and billing. + universe_domain (str): The universe domain. The default + universe domain is googleapis.com. For default value IAM ID + token endponint is used for token refresh. Note that + iam.serviceAccountTokenCreator role is required to use the IAM + endpoint. + + .. note:: Typically one of the helper constructors + :meth:`from_service_account_file` or + :meth:`from_service_account_info` are used instead of calling the + constructor directly. + """ + super(IDTokenCredentials, self).__init__() + self._signer = signer + self._service_account_email = service_account_email + self._token_uri = token_uri + self._target_audience = target_audience + self._quota_project_id = quota_project_id + self._use_iam_endpoint = False + + if not universe_domain: + self._universe_domain = credentials.DEFAULT_UNIVERSE_DOMAIN + else: + self._universe_domain = universe_domain + self._iam_id_token_endpoint = iam._IAM_IDTOKEN_ENDPOINT.replace( + "googleapis.com", self._universe_domain + ) + + if self._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN: + self._use_iam_endpoint = True + + if additional_claims is not None: + self._additional_claims = additional_claims + else: + self._additional_claims = {} + + @classmethod + def _from_signer_and_info(cls, signer, info, **kwargs): + """Creates a credentials instance from a signer and service account + info. + + Args: + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + info (Mapping[str, str]): The service account info. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.jwt.IDTokenCredentials: The constructed credentials. + + Raises: + ValueError: If the info is not in the expected format. + """ + kwargs.setdefault("service_account_email", info["client_email"]) + kwargs.setdefault("token_uri", info["token_uri"]) + if "universe_domain" in info: + kwargs["universe_domain"] = info["universe_domain"] + return cls(signer, **kwargs) + + @classmethod + def from_service_account_info(cls, info, **kwargs): + """Creates a credentials instance from parsed service account info. + + Args: + info (Mapping[str, str]): The service account info in Google + format. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.service_account.IDTokenCredentials: The constructed + credentials. + + Raises: + ValueError: If the info is not in the expected format. + """ + signer = _service_account_info.from_dict( + info, require=["client_email", "token_uri"] + ) + return cls._from_signer_and_info(signer, info, **kwargs) + + @classmethod + def from_service_account_file(cls, filename, **kwargs): + """Creates a credentials instance from a service account json file. + + Args: + filename (str): The path to the service account json file. + kwargs: Additional arguments to pass to the constructor. + + Returns: + google.auth.service_account.IDTokenCredentials: The constructed + credentials. + """ + info, signer = _service_account_info.from_filename( + filename, require=["client_email", "token_uri"] + ) + return cls._from_signer_and_info(signer, info, **kwargs) + + def _make_copy(self): + cred = self.__class__( + self._signer, + service_account_email=self._service_account_email, + token_uri=self._token_uri, + target_audience=self._target_audience, + additional_claims=self._additional_claims.copy(), + quota_project_id=self.quota_project_id, + universe_domain=self._universe_domain, + ) + # _use_iam_endpoint is not exposed in the constructor + cred._use_iam_endpoint = self._use_iam_endpoint + return cred + + def with_target_audience(self, target_audience): + """Create a copy of these credentials with the specified target + audience. + + Args: + target_audience (str): The intended audience for these credentials, + used when requesting the ID Token. + + Returns: + google.auth.service_account.IDTokenCredentials: A new credentials + instance. + """ + cred = self._make_copy() + cred._target_audience = target_audience + return cred + + def _with_use_iam_endpoint(self, use_iam_endpoint): + """Create a copy of these credentials with the use_iam_endpoint value. + + Args: + use_iam_endpoint (bool): If True, IAM generateIdToken endpoint will + be used instead of the token_uri. Note that + iam.serviceAccountTokenCreator role is required to use the IAM + endpoint. The default value is False. This feature is currently + experimental and subject to change without notice. + + Returns: + google.auth.service_account.IDTokenCredentials: A new credentials + instance. + Raises: + google.auth.exceptions.InvalidValue: If the universe domain is not + default and use_iam_endpoint is False. + """ + cred = self._make_copy() + if ( + cred._universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN + and not use_iam_endpoint + ): + raise exceptions.InvalidValue( + "use_iam_endpoint should be True for non-default universe domain" + ) + cred._use_iam_endpoint = use_iam_endpoint + return cred + + @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject) + def with_quota_project(self, quota_project_id): + cred = self._make_copy() + cred._quota_project_id = quota_project_id + return cred + + @_helpers.copy_docstring(credentials.CredentialsWithTokenUri) + def with_token_uri(self, token_uri): + cred = self._make_copy() + cred._token_uri = token_uri + return cred + + def _make_authorization_grant_assertion(self): + """Create the OAuth 2.0 assertion. + + This assertion is used during the OAuth 2.0 grant to acquire an + ID token. + + Returns: + bytes: The authorization grant assertion. + """ + now = _helpers.utcnow() + lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS) + expiry = now + lifetime + + payload = { + "iat": _helpers.datetime_to_secs(now), + "exp": _helpers.datetime_to_secs(expiry), + # The issuer must be the service account email. + "iss": self.service_account_email, + # The audience must be the auth token endpoint's URI + "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT, + # The target audience specifies which service the ID token is + # intended for. + "target_audience": self._target_audience, + } + + payload.update(self._additional_claims) + + token = jwt.encode(self._signer, payload) + + return token + + def _refresh_with_iam_endpoint(self, request): + """Use IAM generateIdToken endpoint to obtain an ID token. + + It works as follows: + + 1. First we create a self signed jwt with + https://www.googleapis.com/auth/iam being the scope. + + 2. Next we use the self signed jwt as the access token, and make a POST + request to IAM generateIdToken endpoint. The request body is: + { + "audience": self._target_audience, + "includeEmail": "true", + "useEmailAzp": "true", + } + + If the request is succesfully, it will return {"token":"the ID token"}, + and we can extract the ID token and compute its expiry. + """ + jwt_credentials = jwt.Credentials.from_signing_credentials( + self, + None, + additional_claims={"scope": "https://www.googleapis.com/auth/iam"}, + ) + jwt_credentials.refresh(request) + + self.token, self.expiry = _client.call_iam_generate_id_token_endpoint( + request, + self._iam_id_token_endpoint, + self.signer_email, + self._target_audience, + jwt_credentials.token.decode(), + self._universe_domain, + ) + + @_helpers.copy_docstring(credentials.Credentials) + def refresh(self, request): + if self._use_iam_endpoint: + self._refresh_with_iam_endpoint(request) + else: + assertion = self._make_authorization_grant_assertion() + access_token, expiry, _ = _client.id_token_jwt_grant( + request, self._token_uri, assertion + ) + self.token = access_token + self.expiry = expiry + + @property + def service_account_email(self): + """The service account email.""" + return self._service_account_email + + @_helpers.copy_docstring(credentials.Signing) + def sign_bytes(self, message): + return self._signer.sign(message) + + @property # type: ignore + @_helpers.copy_docstring(credentials.Signing) + def signer(self): + return self._signer + + @property # type: ignore + @_helpers.copy_docstring(credentials.Signing) + def signer_email(self): + return self._service_account_email diff --git a/intelliaide/vendor/google/oauth2/sts.py b/intelliaide/vendor/google/oauth2/sts.py new file mode 100644 index 0000000..60d6f83 --- /dev/null +++ b/intelliaide/vendor/google/oauth2/sts.py @@ -0,0 +1,201 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""OAuth 2.0 Token Exchange Spec. + +This module defines a token exchange utility based on the `OAuth 2.0 Token +Exchange`_ spec. This will be mainly used to exchange external credentials +for GCP access tokens in workload identity pools to access Google APIs. + +The implementation will support various types of client authentication as +allowed in the spec. + +A deviation on the spec will be for additional Google specific options that +cannot be easily mapped to parameters defined in the RFC. + +The returned dictionary response will be based on the `rfc8693 section 2.2.1`_ +spec JSON response. + +.. _OAuth 2.0 Token Exchange: https://tools.ietf.org/html/rfc8693 +.. _rfc8693 section 2.2.1: https://tools.ietf.org/html/rfc8693#section-2.2.1 +""" + +import http.client as http_client +import json +import urllib + +from google.oauth2 import utils + + +_URLENCODED_HEADERS = {"Content-Type": "application/x-www-form-urlencoded"} + + +class Client(utils.OAuthClientAuthHandler): + """Implements the OAuth 2.0 token exchange spec based on + https://tools.ietf.org/html/rfc8693. + """ + + def __init__(self, token_exchange_endpoint, client_authentication=None): + """Initializes an STS client instance. + + Args: + token_exchange_endpoint (str): The token exchange endpoint. + client_authentication (Optional(google.oauth2.oauth2_utils.ClientAuthentication)): + The optional OAuth client authentication credentials if available. + """ + super(Client, self).__init__(client_authentication) + self._token_exchange_endpoint = token_exchange_endpoint + + def _make_request(self, request, headers, request_body, url=None): + # Initialize request headers. + request_headers = _URLENCODED_HEADERS.copy() + + # Inject additional headers. + if headers: + for k, v in dict(headers).items(): + request_headers[k] = v + + # Apply OAuth client authentication. + self.apply_client_authentication_options(request_headers, request_body) + + # Use default token exchange endpoint if no url is provided. + url = url or self._token_exchange_endpoint + + # Execute request. + response = request( + url=url, + method="POST", + headers=request_headers, + body=urllib.parse.urlencode(request_body).encode("utf-8"), + ) + + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + # If non-200 response received, translate to OAuthError exception. + if response.status != http_client.OK: + utils.handle_error_response(response_body) + + # A successful token revocation returns an empty response body. + if not response_body: + return {} + + # Other successful responses should be valid JSON. + return json.loads(response_body) + + def exchange_token( + self, + request, + grant_type, + subject_token, + subject_token_type, + resource=None, + audience=None, + scopes=None, + requested_token_type=None, + actor_token=None, + actor_token_type=None, + additional_options=None, + additional_headers=None, + ): + """Exchanges the provided token for another type of token based on the + rfc8693 spec. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + grant_type (str): The OAuth 2.0 token exchange grant type. + subject_token (str): The OAuth 2.0 token exchange subject token. + subject_token_type (str): The OAuth 2.0 token exchange subject token type. + resource (Optional[str]): The optional OAuth 2.0 token exchange resource field. + audience (Optional[str]): The optional OAuth 2.0 token exchange audience field. + scopes (Optional[Sequence[str]]): The optional list of scopes to use. + requested_token_type (Optional[str]): The optional OAuth 2.0 token exchange requested + token type. + actor_token (Optional[str]): The optional OAuth 2.0 token exchange actor token. + actor_token_type (Optional[str]): The optional OAuth 2.0 token exchange actor token type. + additional_options (Optional[Mapping[str, str]]): The optional additional + non-standard Google specific options. + additional_headers (Optional[Mapping[str, str]]): The optional additional + headers to pass to the token exchange endpoint. + + Returns: + Mapping[str, str]: The token exchange JSON-decoded response data containing + the requested token and its expiration time. + + Raises: + google.auth.exceptions.OAuthError: If the token endpoint returned + an error. + """ + # Initialize request body. + request_body = { + "grant_type": grant_type, + "resource": resource, + "audience": audience, + "scope": " ".join(scopes or []), + "requested_token_type": requested_token_type, + "subject_token": subject_token, + "subject_token_type": subject_token_type, + "actor_token": actor_token, + "actor_token_type": actor_token_type, + "options": None, + } + # Add additional non-standard options. + if additional_options: + request_body["options"] = urllib.parse.quote(json.dumps(additional_options)) + # Remove empty fields in request body. + for k, v in dict(request_body).items(): + if v is None or v == "": + del request_body[k] + + return self._make_request(request, additional_headers, request_body) + + def refresh_token(self, request, refresh_token): + """Exchanges a refresh token for an access token based on the + RFC6749 spec. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + subject_token (str): The OAuth 2.0 refresh token. + """ + + return self._make_request( + request, + None, + {"grant_type": "refresh_token", "refresh_token": refresh_token}, + ) + + def revoke_token(self, request, token, token_type_hint, revoke_url): + """Revokes the provided token based on the RFC7009 spec. + + Args: + request (google.auth.transport.Request): A callable used to make + HTTP requests. + token (str): The OAuth 2.0 token to revoke. + token_type_hint (str): Hint for the type of token being revoked. + revoke_url (str): The STS endpoint URL for revoking tokens. + + Raises: + google.auth.exceptions.OAuthError: If the token revocation endpoint + returned an error. + """ + request_body = {"token": token} + if token_type_hint: + request_body["token_type_hint"] = token_type_hint + + return self._make_request(request, None, request_body, revoke_url) diff --git a/intelliaide/vendor/google/oauth2/utils.py b/intelliaide/vendor/google/oauth2/utils.py new file mode 100644 index 0000000..d72ff19 --- /dev/null +++ b/intelliaide/vendor/google/oauth2/utils.py @@ -0,0 +1,168 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""OAuth 2.0 Utilities. + +This module provides implementations for various OAuth 2.0 utilities. +This includes `OAuth error handling`_ and +`Client authentication for OAuth flows`_. + +OAuth error handling +-------------------- +This will define interfaces for handling OAuth related error responses as +stated in `RFC 6749 section 5.2`_. +This will include a common function to convert these HTTP error responses to a +:class:`google.auth.exceptions.OAuthError` exception. + + +Client authentication for OAuth flows +------------------------------------- +We introduce an interface for defining client authentication credentials based +on `RFC 6749 section 2.3.1`_. This will expose the following +capabilities: + + * Ability to support basic authentication via request header. + * Ability to support bearer token authentication via request header. + * Ability to support client ID / secret authentication via request body. + +.. _RFC 6749 section 2.3.1: https://tools.ietf.org/html/rfc6749#section-2.3.1 +.. _RFC 6749 section 5.2: https://tools.ietf.org/html/rfc6749#section-5.2 +""" + +import abc +import base64 +import enum +import json + +from google.auth import exceptions + + +# OAuth client authentication based on +# https://tools.ietf.org/html/rfc6749#section-2.3. +class ClientAuthType(enum.Enum): + basic = 1 + request_body = 2 + + +class ClientAuthentication(object): + """Defines the client authentication credentials for basic and request-body + types based on https://tools.ietf.org/html/rfc6749#section-2.3.1. + """ + + def __init__(self, client_auth_type, client_id, client_secret=None): + """Instantiates a client authentication object containing the client ID + and secret credentials for basic and response-body auth. + + Args: + client_auth_type (google.oauth2.oauth_utils.ClientAuthType): The + client authentication type. + client_id (str): The client ID. + client_secret (Optional[str]): The client secret. + """ + self.client_auth_type = client_auth_type + self.client_id = client_id + self.client_secret = client_secret + + +class OAuthClientAuthHandler(metaclass=abc.ABCMeta): + """Abstract class for handling client authentication in OAuth-based + operations. + """ + + def __init__(self, client_authentication=None): + """Instantiates an OAuth client authentication handler. + + Args: + client_authentication (Optional[google.oauth2.utils.ClientAuthentication]): + The OAuth client authentication credentials if available. + """ + super(OAuthClientAuthHandler, self).__init__() + self._client_authentication = client_authentication + + def apply_client_authentication_options( + self, headers, request_body=None, bearer_token=None + ): + """Applies client authentication on the OAuth request's headers or POST + body. + + Args: + headers (Mapping[str, str]): The HTTP request header. + request_body (Optional[Mapping[str, str]]): The HTTP request body + dictionary. For requests that do not support request body, this + is None and will be ignored. + bearer_token (Optional[str]): The optional bearer token. + """ + # Inject authenticated header. + self._inject_authenticated_headers(headers, bearer_token) + # Inject authenticated request body. + if bearer_token is None: + self._inject_authenticated_request_body(request_body) + + def _inject_authenticated_headers(self, headers, bearer_token=None): + if bearer_token is not None: + headers["Authorization"] = "Bearer %s" % bearer_token + elif ( + self._client_authentication is not None + and self._client_authentication.client_auth_type is ClientAuthType.basic + ): + username = self._client_authentication.client_id + password = self._client_authentication.client_secret or "" + + credentials = base64.b64encode( + ("%s:%s" % (username, password)).encode() + ).decode() + headers["Authorization"] = "Basic %s" % credentials + + def _inject_authenticated_request_body(self, request_body): + if ( + self._client_authentication is not None + and self._client_authentication.client_auth_type + is ClientAuthType.request_body + ): + if request_body is None: + raise exceptions.OAuthError( + "HTTP request does not support request-body" + ) + else: + request_body["client_id"] = self._client_authentication.client_id + request_body["client_secret"] = ( + self._client_authentication.client_secret or "" + ) + + +def handle_error_response(response_body): + """Translates an error response from an OAuth operation into an + OAuthError exception. + + Args: + response_body (str): The decoded response data. + + Raises: + google.auth.exceptions.OAuthError + """ + try: + error_components = [] + error_data = json.loads(response_body) + + error_components.append("Error code {}".format(error_data["error"])) + if "error_description" in error_data: + error_components.append(": {}".format(error_data["error_description"])) + if "error_uri" in error_data: + error_components.append(" - {}".format(error_data["error_uri"])) + error_details = "".join(error_components) + # If no details could be extracted, use the response data. + except (KeyError, ValueError): + error_details = response_body + + raise exceptions.OAuthError(error_details, response_body) diff --git a/intelliaide/vendor/google/oauth2/webauthn_handler.py b/intelliaide/vendor/google/oauth2/webauthn_handler.py new file mode 100644 index 0000000..d46832d --- /dev/null +++ b/intelliaide/vendor/google/oauth2/webauthn_handler.py @@ -0,0 +1,82 @@ +import abc +import os +import struct +import subprocess + +from google.auth import exceptions +from google.oauth2.webauthn_types import GetRequest, GetResponse + + +class WebAuthnHandler(abc.ABC): + @abc.abstractmethod + def is_available(self) -> bool: + """Check whether this WebAuthn handler is available""" + raise NotImplementedError("is_available method must be implemented") + + @abc.abstractmethod + def get(self, get_request: GetRequest) -> GetResponse: + """WebAuthn get (assertion)""" + raise NotImplementedError("get method must be implemented") + + +class PluginHandler(WebAuthnHandler): + """Offloads WebAuthn get request to a pluggable command-line tool. + + Offloads WebAuthn get to a plugin which takes the form of a + command-line tool. The command-line tool is configurable via the + PluginHandler._ENV_VAR environment variable. + + The WebAuthn plugin should implement the following interface: + + Communication occurs over stdin/stdout, and messages are both sent and + received in the form: + + [4 bytes - payload size (little-endian)][variable bytes - json payload] + """ + + _ENV_VAR = "GOOGLE_AUTH_WEBAUTHN_PLUGIN" + + def is_available(self) -> bool: + try: + self._find_plugin() + except Exception: + return False + else: + return True + + def get(self, get_request: GetRequest) -> GetResponse: + request_json = get_request.to_json() + cmd = self._find_plugin() + response_json = self._call_plugin(cmd, request_json) + return GetResponse.from_json(response_json) + + def _call_plugin(self, cmd: str, input_json: str) -> str: + # Calculate length of input + input_length = len(input_json) + length_bytes_le = struct.pack(" str: + plugin_cmd = os.environ.get(PluginHandler._ENV_VAR) + if plugin_cmd is None: + raise exceptions.InvalidResource( + "{} env var is not set".format(PluginHandler._ENV_VAR) + ) + return plugin_cmd diff --git a/intelliaide/vendor/google/oauth2/webauthn_handler_factory.py b/intelliaide/vendor/google/oauth2/webauthn_handler_factory.py new file mode 100644 index 0000000..184329f --- /dev/null +++ b/intelliaide/vendor/google/oauth2/webauthn_handler_factory.py @@ -0,0 +1,16 @@ +from typing import List, Optional + +from google.oauth2.webauthn_handler import PluginHandler, WebAuthnHandler + + +class WebauthnHandlerFactory: + handlers: List[WebAuthnHandler] + + def __init__(self): + self.handlers = [PluginHandler()] + + def get_handler(self) -> Optional[WebAuthnHandler]: + for handler in self.handlers: + if handler.is_available(): + return handler + return None diff --git a/intelliaide/vendor/google/oauth2/webauthn_types.py b/intelliaide/vendor/google/oauth2/webauthn_types.py new file mode 100644 index 0000000..24e984f --- /dev/null +++ b/intelliaide/vendor/google/oauth2/webauthn_types.py @@ -0,0 +1,156 @@ +from dataclasses import dataclass +import json +from typing import Any, Dict, List, Optional + +from google.auth import exceptions + + +@dataclass(frozen=True) +class PublicKeyCredentialDescriptor: + """Descriptor for a security key based credential. + + https://www.w3.org/TR/webauthn-3/#dictionary-credential-descriptor + + Args: + id: credential id (key handle). + transports: <'usb'|'nfc'|'ble'|'internal'> List of supported transports. + """ + + id: str + transports: Optional[List[str]] = None + + def to_dict(self): + cred = {"type": "public-key", "id": self.id} + if self.transports: + cred["transports"] = self.transports + return cred + + +@dataclass +class AuthenticationExtensionsClientInputs: + """Client extensions inputs for WebAuthn extensions. + + Args: + appid: app id that can be asserted with in addition to rpid. + https://www.w3.org/TR/webauthn-3/#sctn-appid-extension + """ + + appid: Optional[str] = None + + def to_dict(self): + extensions = {} + if self.appid: + extensions["appid"] = self.appid + return extensions + + +@dataclass +class GetRequest: + """WebAuthn get request + + Args: + origin: Origin where the WebAuthn get assertion takes place. + rpid: Relying Party ID. + challenge: raw challenge. + timeout_ms: Timeout number in millisecond. + allow_credentials: List of allowed credentials. + user_verification: <'required'|'preferred'|'discouraged'> User verification requirement. + extensions: WebAuthn authentication extensions inputs. + """ + + origin: str + rpid: str + challenge: str + timeout_ms: Optional[int] = None + allow_credentials: Optional[List[PublicKeyCredentialDescriptor]] = None + user_verification: Optional[str] = None + extensions: Optional[AuthenticationExtensionsClientInputs] = None + + def to_json(self) -> str: + req_options: Dict[str, Any] = {"rpId": self.rpid, "challenge": self.challenge} + if self.timeout_ms: + req_options["timeout"] = self.timeout_ms + if self.allow_credentials: + req_options["allowCredentials"] = [ + c.to_dict() for c in self.allow_credentials + ] + if self.user_verification: + req_options["userVerification"] = self.user_verification + if self.extensions: + req_options["extensions"] = self.extensions.to_dict() + return json.dumps( + {"type": "get", "origin": self.origin, "requestData": req_options} + ) + + +@dataclass(frozen=True) +class AuthenticatorAssertionResponse: + """Authenticator response to a WebAuthn get (assertion) request. + + https://www.w3.org/TR/webauthn-3/#authenticatorassertionresponse + + Args: + client_data_json: client data JSON. + authenticator_data: authenticator data. + signature: signature. + user_handle: user handle. + """ + + client_data_json: str + authenticator_data: str + signature: str + user_handle: Optional[str] + + +@dataclass(frozen=True) +class GetResponse: + """WebAuthn get (assertion) response. + + Args: + id: credential id (key handle). + response: The authenticator assertion response. + authenticator_attachment: <'cross-platform'|'platform'> The attachment status of the authenticator. + client_extension_results: WebAuthn authentication extensions output results in a dictionary. + """ + + id: str + response: AuthenticatorAssertionResponse + authenticator_attachment: Optional[str] + client_extension_results: Optional[Dict] + + @staticmethod + def from_json(json_str: str): + """Verify and construct GetResponse from a JSON string.""" + try: + resp_json = json.loads(json_str) + except ValueError: + raise exceptions.MalformedError("Invalid Get JSON response") + if resp_json.get("type") != "getResponse": + raise exceptions.MalformedError( + "Invalid Get response type: {}".format(resp_json.get("type")) + ) + pk_cred = resp_json.get("responseData") + if pk_cred is None: + if resp_json.get("error"): + raise exceptions.ReauthFailError( + "WebAuthn.get failure: {}".format(resp_json["error"]) + ) + else: + raise exceptions.MalformedError("Get response is empty") + if pk_cred.get("type") != "public-key": + raise exceptions.MalformedError( + "Invalid credential type: {}".format(pk_cred.get("type")) + ) + assertion_json = pk_cred["response"] + assertion_resp = AuthenticatorAssertionResponse( + client_data_json=assertion_json["clientDataJSON"], + authenticator_data=assertion_json["authenticatorData"], + signature=assertion_json["signature"], + user_handle=assertion_json.get("userHandle"), + ) + return GetResponse( + id=pk_cred["id"], + response=assertion_resp, + authenticator_attachment=pk_cred.get("authenticatorAttachment"), + client_extension_results=pk_cred.get("clientExtensionResults"), + ) diff --git a/intelliaide/vendor/google_auth-2.53.0.dist-info/INSTALLER b/intelliaide/vendor/google_auth-2.53.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/intelliaide/vendor/google_auth-2.53.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/intelliaide/vendor/google_auth-2.53.0.dist-info/METADATA b/intelliaide/vendor/google_auth-2.53.0.dist-info/METADATA new file mode 100644 index 0000000..575e09b --- /dev/null +++ b/intelliaide/vendor/google_auth-2.53.0.dist-info/METADATA @@ -0,0 +1,165 @@ +Metadata-Version: 2.4 +Name: google-auth +Version: 2.53.0 +Summary: Google Authentication Library +Home-page: https://github.com/googleapis/google-auth-library-python +Author: Google Cloud Platform +Author-email: googleapis-packages@google.com +License: Apache 2.0 +Keywords: google auth oauth client +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: POSIX +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: OS Independent +Classifier: Topic :: Internet :: WWW/HTTP +Requires-Python: >=3.10 +License-File: LICENSE +Requires-Dist: pyasn1-modules>=0.2.1 +Requires-Dist: cryptography>=38.0.3 +Provides-Extra: cryptography +Requires-Dist: cryptography>=38.0.3; extra == "cryptography" +Provides-Extra: aiohttp +Requires-Dist: aiohttp<4.0.0,>=3.8.0; extra == "aiohttp" +Requires-Dist: requests<3.0.0,>=2.20.0; extra == "aiohttp" +Provides-Extra: enterprise-cert +Requires-Dist: pyopenssl; extra == "enterprise-cert" +Provides-Extra: pyopenssl +Requires-Dist: pyopenssl>=20.0.0; extra == "pyopenssl" +Provides-Extra: pyjwt +Requires-Dist: pyjwt>=2.0; extra == "pyjwt" +Provides-Extra: reauth +Requires-Dist: pyu2f>=0.1.5; extra == "reauth" +Provides-Extra: requests +Requires-Dist: requests<3.0.0,>=2.20.0; extra == "requests" +Provides-Extra: testing +Requires-Dist: grpcio; extra == "testing" +Requires-Dist: flask; extra == "testing" +Requires-Dist: freezegun; extra == "testing" +Requires-Dist: pyjwt>=2.0; extra == "testing" +Requires-Dist: pytest; extra == "testing" +Requires-Dist: pytest-cov; extra == "testing" +Requires-Dist: pytest-localserver; extra == "testing" +Requires-Dist: pyopenssl>=20.0.0; extra == "testing" +Requires-Dist: pyu2f>=0.1.5; extra == "testing" +Requires-Dist: responses; extra == "testing" +Requires-Dist: urllib3; extra == "testing" +Requires-Dist: packaging; extra == "testing" +Requires-Dist: aiohttp<4.0.0,>=3.8.0; extra == "testing" +Requires-Dist: requests<3.0.0,>=2.20.0; extra == "testing" +Requires-Dist: aioresponses; extra == "testing" +Requires-Dist: pytest-asyncio; extra == "testing" +Requires-Dist: pyopenssl<24.3.0; extra == "testing" +Requires-Dist: aiohttp<3.10.0; extra == "testing" +Provides-Extra: urllib3 +Requires-Dist: urllib3; extra == "urllib3" +Requires-Dist: packaging; extra == "urllib3" +Provides-Extra: rsa +Requires-Dist: rsa<5,>=3.1.4; extra == "rsa" +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: home-page +Dynamic: keywords +Dynamic: license +Dynamic: license-file +Dynamic: provides-extra +Dynamic: requires-dist +Dynamic: requires-python +Dynamic: summary + +Google Auth Python Library +========================== + +|pypi| + +This library simplifies using Google's various server-to-server authentication +mechanisms to access Google APIs. + +.. |pypi| image:: https://img.shields.io/pypi/v/google-auth.svg + :target: https://pypi.python.org/pypi/google-auth + +Installing +---------- + +You can install using `pip`_:: + + $ pip install google-auth + +.. _pip: https://pip.pypa.io/en/stable/ + +For more information on setting up your Python development environment, please refer to `Python Development Environment Setup Guide`_ for Google Cloud Platform. + +.. _`Python Development Environment Setup Guide`: https://cloud.google.com/python/docs/setup + +Extras +------ + +google-auth has few extras that you can install. For example:: + + $ pip install google-auth[pyopenssl] + +Note that the extras pyopenssl and enterprise_cert should not be used together because they use conflicting versions of `cryptography`_. + +.. _`cryptography`: https://cryptography.io/en/latest/ + +Supported Python Versions +^^^^^^^^^^^^^^^^^^^^^^^^^ +Python >= 3.10 + +Unsupported Python Versions +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +- Python == 2.7: The last version of this library with support for Python 2.7 + was `google.auth == 1.34.0`. + +- Python 3.5: The last version of this library with support for Python 3.5 + was `google.auth == 1.23.0`. + +- Python 3.6: The last version of this library with support for Python 3.6 + was `google.auth == 2.22.0`. + +- Python 3.7: The last version of this library with support for Python 3.7 + was `google.auth == 2.45.0`. + + +Documentation +------------- + +Google Auth Python Library has usage and reference documentation at https://googleapis.dev/python/google-auth/latest/index.html. + +Current Maintainers +------------------- +- googleapis-auth@google.com + +Authors +------- + +- `@theacodes `_ (Thea Flowers) +- `@dhermes `_ (Danny Hermes) +- `@lukesneeringer `_ (Luke Sneeringer) +- `@busunkim96 `_ (Bu Sun Kim) + +Contributing +------------ + +Contributions to this library are always welcome and highly encouraged. + +See `CONTRIBUTING.rst`_ for more information on how to get started. + +.. _CONTRIBUTING.rst: https://github.com/googleapis/google-auth-library-python/blob/main/CONTRIBUTING.rst + +License +------- + +Apache 2.0 - See `the LICENSE`_ for more information. + +.. _the LICENSE: https://github.com/googleapis/google-auth-library-python/blob/main/LICENSE diff --git a/intelliaide/vendor/google_auth-2.53.0.dist-info/RECORD b/intelliaide/vendor/google_auth-2.53.0.dist-info/RECORD new file mode 100644 index 0000000..6f78ce5 --- /dev/null +++ b/intelliaide/vendor/google_auth-2.53.0.dist-info/RECORD @@ -0,0 +1,165 @@ +google/auth/__init__.py,sha256=Aq1Iysvxvj5sqbsNHPFR5CMbBJs6JCKCCmn5JtNaKQY,1064 +google/auth/__pycache__/__init__.cpython-312.pyc,, +google/auth/__pycache__/_agent_identity_utils.cpython-312.pyc,, +google/auth/__pycache__/_cache.cpython-312.pyc,, +google/auth/__pycache__/_cloud_sdk.cpython-312.pyc,, +google/auth/__pycache__/_credentials_async.cpython-312.pyc,, +google/auth/__pycache__/_credentials_base.cpython-312.pyc,, +google/auth/__pycache__/_default.cpython-312.pyc,, +google/auth/__pycache__/_default_async.cpython-312.pyc,, +google/auth/__pycache__/_exponential_backoff.cpython-312.pyc,, +google/auth/__pycache__/_helpers.cpython-312.pyc,, +google/auth/__pycache__/_jwt_async.cpython-312.pyc,, +google/auth/__pycache__/_oauth2client.cpython-312.pyc,, +google/auth/__pycache__/_refresh_worker.cpython-312.pyc,, +google/auth/__pycache__/_regional_access_boundary_utils.cpython-312.pyc,, +google/auth/__pycache__/_service_account_info.cpython-312.pyc,, +google/auth/__pycache__/api_key.cpython-312.pyc,, +google/auth/__pycache__/app_engine.cpython-312.pyc,, +google/auth/__pycache__/aws.cpython-312.pyc,, +google/auth/__pycache__/credentials.cpython-312.pyc,, +google/auth/__pycache__/downscoped.cpython-312.pyc,, +google/auth/__pycache__/environment_vars.cpython-312.pyc,, +google/auth/__pycache__/exceptions.cpython-312.pyc,, +google/auth/__pycache__/external_account.cpython-312.pyc,, +google/auth/__pycache__/external_account_authorized_user.cpython-312.pyc,, +google/auth/__pycache__/iam.cpython-312.pyc,, +google/auth/__pycache__/identity_pool.cpython-312.pyc,, +google/auth/__pycache__/impersonated_credentials.cpython-312.pyc,, +google/auth/__pycache__/jwt.cpython-312.pyc,, +google/auth/__pycache__/metrics.cpython-312.pyc,, +google/auth/__pycache__/pluggable.cpython-312.pyc,, +google/auth/__pycache__/version.cpython-312.pyc,, +google/auth/_agent_identity_utils.py,sha256=x73jZ_QPtJGE7Bp1-gpi9PJq5DNmCQbnkUjrK8acqnI,11517 +google/auth/_cache.py,sha256=9xbqdkwFa-9W5tzb2WM_bNzFwZ6hKazMcsPugqHuAyA,1863 +google/auth/_cloud_sdk.py,sha256=pwX6vZhYqEpu3vkSNxJ2MI5wMT5t-xX7273QqCtWFZk,5211 +google/auth/_credentials_async.py,sha256=bHB28wMULOIEMmYqKEOU06A4co7uIXPcnfVC_TaA6KY,6802 +google/auth/_credentials_base.py,sha256=KxdCZyoFyvrfWhJbNnuYkpUhxs0bmbxYvcH8-xp5hUs,2692 +google/auth/_default.py,sha256=FqwhclHCgKNZz1p-lMREv6mY9lKTn2le6CPxckGitxk,31271 +google/auth/_default_async.py,sha256=ZkU8jig_lEb_IpsYT8DBzNPLRGgfYNb_QbL0S3lpjks,11848 +google/auth/_exponential_backoff.py,sha256=qxA9ek80rBkoARx0Egl2b1MlYU0D-pQNVqgCmUh-lgU,5372 +google/auth/_helpers.py,sha256=aLk0KE2T8XWepwngfPGqSANOFB0a4Pym80FCmTdFSco,17363 +google/auth/_jwt_async.py,sha256=5mGab5CkdnBMkQkS4mtNkwFkktp1jBw6G1sYQk8bYKY,5972 +google/auth/_oauth2client.py,sha256=9pBShk1ekwkPNoD5WoQJNQHHD3luNfzpBVoQJo8n1rc,5875 +google/auth/_refresh_worker.py,sha256=zT2jj-PjPtjF3Bqg_n4t1CdL32yBVEUhPKZQwoNu-Xg,3379 +google/auth/_regional_access_boundary_utils.py,sha256=jp7vzofME65Ksn9l6ovqS_aVIob-11LYgvNlruVQFfA,15414 +google/auth/_service_account_info.py,sha256=EdOEZql9_mG2cJ54KmL3Q5jO3C3wcadwlCObg4gKjus,2813 +google/auth/aio/__init__.py,sha256=e3ToAxXNHhqJLBgW8B66650xdqrTCZDLcwP2p5DhCPM,869 +google/auth/aio/__pycache__/__init__.cpython-312.pyc,, +google/auth/aio/__pycache__/_helpers.cpython-312.pyc,, +google/auth/aio/__pycache__/credentials.cpython-312.pyc,, +google/auth/aio/_helpers.py,sha256=glCa_-GYxDrcPBFt80LBhMz_V7vLjxAEEWfOZB8lwT0,2334 +google/auth/aio/credentials.py,sha256=lXY0_SJ9c36Mzp47fN4a8JT5HfNzFQtIIwsZEwzoDR4,5273 +google/auth/aio/transport/__init__.py,sha256=8dQWHpube1IeWw02q6AvMRTM5V_2iN4y3UMLenbmUUQ,4692 +google/auth/aio/transport/__pycache__/__init__.cpython-312.pyc,, +google/auth/aio/transport/__pycache__/aiohttp.cpython-312.pyc,, +google/auth/aio/transport/__pycache__/mtls.cpython-312.pyc,, +google/auth/aio/transport/__pycache__/sessions.cpython-312.pyc,, +google/auth/aio/transport/aiohttp.py,sha256=Gweuf3GcNG1_1_GmPpZvcFRXMYvE3MgSPT7XDZSlSAc,7552 +google/auth/aio/transport/mtls.py,sha256=Xpd57h-DHmgjv8dQdjsqxj-ftr_pfhWAWY5_gYJKgPw,6801 +google/auth/aio/transport/sessions.py,sha256=Xrhnl9cLP5egcoQuG8ensQBmCu454j7k1mlpDNLYPzc,23961 +google/auth/api_key.py,sha256=PeieTYceHJIFCo0zQo1EA9NEDL_Ie6S78qmD-6Ig17s,2583 +google/auth/app_engine.py,sha256=mQ7NrKQYd3LiZ7F_ttlqs9Tjf4EEtJNHTPfpixCycW0,6096 +google/auth/aws.py,sha256=aVSDOH2pnECuEYjxn_pHGu8447xyB3P9sAiCQem4ePo,34573 +google/auth/compute_engine/__init__.py,sha256=BqeTka-oyHFATkys3SGKRlOyWQ8mVV0vVaP2hOwV4Qw,910 +google/auth/compute_engine/__pycache__/__init__.cpython-312.pyc,, +google/auth/compute_engine/__pycache__/_metadata.cpython-312.pyc,, +google/auth/compute_engine/__pycache__/_mtls.cpython-312.pyc,, +google/auth/compute_engine/__pycache__/credentials.cpython-312.pyc,, +google/auth/compute_engine/_metadata.py,sha256=g44MLHIWkwM2g59-YEWsbSASJCfLRR6JdJO1H9xDciw,17859 +google/auth/compute_engine/_mtls.py,sha256=8EQrVyWVQPuAnuG8C4uq29OaerpsmFxGXruw3Rfuxco,5720 +google/auth/compute_engine/credentials.py,sha256=MsFquoNefl-Hcl7ZlzmDnNqnu8_IFhF0oUOLHHmVeHo,21944 +google/auth/credentials.py,sha256=KqtYVGHpPJh6BEeiIFvhHJZ6W1s1JpkI6pU0VT4M5-I,29028 +google/auth/crypt/__init__.py,sha256=ukDmKrdUmHqGc5rDLcoel_TKXdV5wX05r5Gqi9hnWBE,3144 +google/auth/crypt/__pycache__/__init__.cpython-312.pyc,, +google/auth/crypt/__pycache__/_cryptography_rsa.cpython-312.pyc,, +google/auth/crypt/__pycache__/_helpers.cpython-312.pyc,, +google/auth/crypt/__pycache__/_python_rsa.cpython-312.pyc,, +google/auth/crypt/__pycache__/base.cpython-312.pyc,, +google/auth/crypt/__pycache__/es.cpython-312.pyc,, +google/auth/crypt/__pycache__/es256.cpython-312.pyc,, +google/auth/crypt/__pycache__/rsa.cpython-312.pyc,, +google/auth/crypt/_cryptography_rsa.py,sha256=o2QTRkfDRLtEBiq-fbpbTWypvxaxUDwzlx2NpXG9o0w,5158 +google/auth/crypt/_helpers.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +google/auth/crypt/_python_rsa.py,sha256=Exqz9OgDm-3JHHd636gnAdXdcH6pjDWBNJz21uDRnUE,6747 +google/auth/crypt/base.py,sha256=3CJrnQsppR6h-WnTRqSrt1hPEQVkHcwNIVJ_B5M00hY,4190 +google/auth/crypt/es.py,sha256=3eHSDU_lh95pMYE3eIGErGHPETqsRCHeK2VtQnqVPk8,8019 +google/auth/crypt/es256.py,sha256=cFaDuQRdXx3f_aVy-HH0VQ6G49VNSAhB04jnbjyxHNI,1454 +google/auth/crypt/rsa.py,sha256=coLr0FFyuDAmDaoXefpriH_naAJbmgwFORUDVYGdBYU,4770 +google/auth/downscoped.py,sha256=KmmC8lbBWUFUsIYt1VpcbTXs3yYJYXteH19qdZQgobA,21793 +google/auth/environment_vars.py,sha256=elIkXWjxdlOsf-CQid8UFcXcAIHtg17wi6otcveYCsQ,5163 +google/auth/exceptions.py,sha256=8oEeB_1UirJIyBoglQRpCFjCOcxcTaLr6EpBrrxjeEs,3193 +google/auth/external_account.py,sha256=BjNWaDWxOCCPHAoxOcuTWTuY5T-Arqkh2aRlUWY640E,31161 +google/auth/external_account_authorized_user.py,sha256=4_skldJ-9v5pG_fMhbEJeSMkvsasREbiQtD_-v5PPYE,17725 +google/auth/iam.py,sha256=Fc-wdlFaixOi1lKnneSNItQA29_xkqH7vB-P-qOS2h0,5607 +google/auth/identity_pool.py,sha256=doIKIMU7_c9GbDrXqv12A1BS9Bb6AgL-uSt72QpsjuQ,24697 +google/auth/impersonated_credentials.py,sha256=QRGtYzyRyJKgTsZOJIK7434HAARRRvyOIE_4Ysm8LJQ,27592 +google/auth/jwt.py,sha256=CxVlg9VxMbgdSaT7_4hISDNkL9eBVNi4jCmpFJXiOJM,31178 +google/auth/metrics.py,sha256=usjITvTFmO0YogJJDi31_hgl2kgENuKMfgWy6LTGhxg,5616 +google/auth/pluggable.py,sha256=N9lxIiiegT6DIv3poc5B3B-6OlVmFRZr-AmNO6Xzid4,18371 +google/auth/py.typed,sha256=l05_LTgi3oy-einKBrw66s6aavgzG2o-SekKPOY3ayM,74 +google/auth/transport/__init__.py,sha256=vTdWUDBCXWF6wk3Xzu7L5I1lbtGYTxONi-IKYOmDdFM,3654 +google/auth/transport/__pycache__/__init__.cpython-312.pyc,, +google/auth/transport/__pycache__/_aiohttp_requests.cpython-312.pyc,, +google/auth/transport/__pycache__/_custom_tls_signer.cpython-312.pyc,, +google/auth/transport/__pycache__/_http_client.cpython-312.pyc,, +google/auth/transport/__pycache__/_mtls_helper.cpython-312.pyc,, +google/auth/transport/__pycache__/_requests_base.cpython-312.pyc,, +google/auth/transport/__pycache__/grpc.cpython-312.pyc,, +google/auth/transport/__pycache__/mtls.cpython-312.pyc,, +google/auth/transport/__pycache__/requests.cpython-312.pyc,, +google/auth/transport/__pycache__/urllib3.cpython-312.pyc,, +google/auth/transport/_aiohttp_requests.py,sha256=aJNNT09VISGPwcGS8DVR8G1l_DeOvnlJ2Z7kcAjXs14,14819 +google/auth/transport/_custom_tls_signer.py,sha256=ilLlKFNognkvOWcnlYAWmUIUo9YhSrTSAKLa05zQ8Do,9989 +google/auth/transport/_http_client.py,sha256=_lE2TaQOb2hZq5OrblNAYbpZzLX18LGXSyaNJuXHJ2w,3798 +google/auth/transport/_mtls_helper.py,sha256=PcYF7siMNiHMce_tXZa_oAgMaON3mBkJqqF5SqVXhm0,19684 +google/auth/transport/_requests_base.py,sha256=4y0tTMR_hPGeAmBSyCU6_moh99ZxJZ4CF79E2s-t7TA,1657 +google/auth/transport/grpc.py,sha256=cRYju1kWZ2YE5IlagRMh9trAW8J5kmmIrBmyZW7IGik,13742 +google/auth/transport/mtls.py,sha256=dfOH5kaz4R9pNdm5l-cRQc1PfpGldpS3FBwdSF72KfY,5163 +google/auth/transport/requests.py,sha256=--9PLv4Jaiz1HiIzl_tVKJ0Z1k-3XPQ3HBsXVBaIf4U,24332 +google/auth/transport/urllib3.py,sha256=bmYdTtBa3Yni5Q1pvpqbuHwF70g0gz7RfNsR6hDFz7g,18349 +google/auth/version.py,sha256=xf23cqk02vMB1EsBABhBNmv5OM15QNA1Fh4ha5h68Ww,598 +google/oauth2/__init__.py,sha256=iDyTpxuh864rLf4YyINgeO4wMJLQ53EUMPJpiw1GA3U,619 +google/oauth2/__pycache__/__init__.cpython-312.pyc,, +google/oauth2/__pycache__/_client.cpython-312.pyc,, +google/oauth2/__pycache__/_client_async.cpython-312.pyc,, +google/oauth2/__pycache__/_credentials_async.cpython-312.pyc,, +google/oauth2/__pycache__/_id_token_async.cpython-312.pyc,, +google/oauth2/__pycache__/_reauth_async.cpython-312.pyc,, +google/oauth2/__pycache__/_service_account_async.cpython-312.pyc,, +google/oauth2/__pycache__/challenges.cpython-312.pyc,, +google/oauth2/__pycache__/credentials.cpython-312.pyc,, +google/oauth2/__pycache__/gdch_credentials.cpython-312.pyc,, +google/oauth2/__pycache__/id_token.cpython-312.pyc,, +google/oauth2/__pycache__/reauth.cpython-312.pyc,, +google/oauth2/__pycache__/service_account.cpython-312.pyc,, +google/oauth2/__pycache__/sts.cpython-312.pyc,, +google/oauth2/__pycache__/utils.cpython-312.pyc,, +google/oauth2/__pycache__/webauthn_handler.cpython-312.pyc,, +google/oauth2/__pycache__/webauthn_handler_factory.cpython-312.pyc,, +google/oauth2/__pycache__/webauthn_types.cpython-312.pyc,, +google/oauth2/_client.py,sha256=2bOKX6ET_CF6kpa6bim8XKtFmzkH99KTeDfuhQfyPHU,22710 +google/oauth2/_client_async.py,sha256=nXLgAI5KVPRR6w7-5cjB8jc5V59hd-gpPu9OR7mElCw,10170 +google/oauth2/_credentials_async.py,sha256=hUrucQkcYuYlyCdHMci8tzaVncnjQlFc2sAfNu5Dt8k,4474 +google/oauth2/_id_token_async.py,sha256=ot4_u-rhA22TufWOAR5Mpaua7Q2aAtN4q_kJmj-YnII,10171 +google/oauth2/_reauth_async.py,sha256=XK63x1CCWTBX4TG_mwc1WnRmB3vG0JRZ5F-dNfxodcs,11715 +google/oauth2/_service_account_async.py,sha256=_epalmLbNizOHgHcLZXSoK6_Z5lTk1OiIdEBioBGB5E,5878 +google/oauth2/challenges.py,sha256=Pdy8IpwGJstz890Ctz_xZvhUwIlTvKibSnmHNbRBZd0,10405 +google/oauth2/credentials.py,sha256=BhgwiXwTPy42462fpXp8-A39XkEqLJXV9WCRbzOEUpw,25880 +google/oauth2/gdch_credentials.py,sha256=CY6iPnPuc2OCIe1Zujwg1Mu9QSl1iGJqGOy6TkUleHw,9007 +google/oauth2/id_token.py,sha256=sCPdYw6LxcLgDqUvKygmdcrIGzZFGGXoARRYI-BzZ_U,13710 +google/oauth2/py.typed,sha256=I0muXRRdbdpJoZ_VyheisiTTYcmjTAitQpNvuuh6fMw,76 +google/oauth2/reauth.py,sha256=CkkDpv79_8SsQDromAyI3sa2lS3V_M7bSZqWFZj30nw,12878 +google/oauth2/service_account.py,sha256=51OAO215HosNkPIHNa8HiCf7HNxW6099T0yh3d4HQLY,33572 +google/oauth2/sts.py,sha256=df-lzhe8cBKr2O-tWZTaWdgYonKm1J9HI5Cz_AfW1hU,7728 +google/oauth2/utils.py,sha256=4crAdpKbDtobpQfXJc3uF6Zm6F3IzffvRSo-9h_515w,6315 +google/oauth2/webauthn_handler.py,sha256=lg_5mHowRJ0kx2hluMYhcdiFctHZyi8CFc9fT8CkQwA,2743 +google/oauth2/webauthn_handler_factory.py,sha256=soE5cokZ3pLNbBo1HC6F1N-N-I-ir-DGlD0trGPCpBs,429 +google/oauth2/webauthn_types.py,sha256=IHdqUe-EWOU-CmyLnHLniqc7vGHZ8HpavB0dcjn7Zl8,5386 +google_auth-2.53.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +google_auth-2.53.0.dist-info/METADATA,sha256=g-CiudWTa6LXgbcpO-btaGcArTRVrdYcaOHgd5--bkk,5536 +google_auth-2.53.0.dist-info/RECORD,, +google_auth-2.53.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +google_auth-2.53.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91 +google_auth-2.53.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357 +google_auth-2.53.0.dist-info/top_level.txt,sha256=_1QvSJIhFAGfxb79D6DhB7SUw2X6T4rwnz_LLrbcD3c,7 diff --git a/intelliaide/vendor/google_auth-2.53.0.dist-info/REQUESTED b/intelliaide/vendor/google_auth-2.53.0.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/intelliaide/vendor/google_auth-2.53.0.dist-info/WHEEL b/intelliaide/vendor/google_auth-2.53.0.dist-info/WHEEL new file mode 100644 index 0000000..14a883f --- /dev/null +++ b/intelliaide/vendor/google_auth-2.53.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (82.0.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/intelliaide/vendor/google_auth-2.53.0.dist-info/licenses/LICENSE b/intelliaide/vendor/google_auth-2.53.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/intelliaide/vendor/google_auth-2.53.0.dist-info/licenses/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/intelliaide/vendor/google_auth-2.53.0.dist-info/top_level.txt b/intelliaide/vendor/google_auth-2.53.0.dist-info/top_level.txt new file mode 100644 index 0000000..cb42911 --- /dev/null +++ b/intelliaide/vendor/google_auth-2.53.0.dist-info/top_level.txt @@ -0,0 +1 @@ +google diff --git a/intelliaide/vendor/idna-3.16.dist-info/INSTALLER b/intelliaide/vendor/idna-3.16.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/intelliaide/vendor/idna-3.16.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/intelliaide/vendor/idna-3.16.dist-info/METADATA b/intelliaide/vendor/idna-3.16.dist-info/METADATA new file mode 100644 index 0000000..9a2b49a --- /dev/null +++ b/intelliaide/vendor/idna-3.16.dist-info/METADATA @@ -0,0 +1,164 @@ +Metadata-Version: 2.4 +Name: idna +Version: 3.16 +Summary: Internationalized Domain Names in Applications (IDNA) +Author-email: Kim Davies +Requires-Python: >=3.9 +Description-Content-Type: text/markdown +License-Expression: BSD-3-Clause +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: System Administrators +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Internet :: Name Service (DNS) +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Utilities +License-File: LICENSE.md +Requires-Dist: ruff >= 0.6.2 ; extra == "all" +Requires-Dist: mypy >= 1.11.2 ; extra == "all" +Requires-Dist: pytest >= 8.3.2 ; extra == "all" +Project-URL: Changelog, https://github.com/kjd/idna/blob/master/HISTORY.md +Project-URL: Issue tracker, https://github.com/kjd/idna/issues +Project-URL: Source, https://github.com/kjd/idna +Provides-Extra: all + +# Internationalized Domain Names in Applications (IDNA) + +Support for [Internationalized Domain Names in Applications +(IDNA)](https://tools.ietf.org/html/rfc5891) and [Unicode IDNA +Compatibility Processing](https://unicode.org/reports/tr46/). It +supersedes the standard library's `encodings.idna`, which only +implements the 2003 specification, offering broader script coverage and +limiting domains with known security vulnerabilities. + +## Usage + +Package may be installed from [PyPI](https://pypi.org/project/idna/) via +the typical methods (e.g. `python3 -m pip install idna`) + +For typical usage, the `encode` and `decode` functions will take a +domain name argument and perform a conversion to ASCII-compatible encoding +(known as A-labels), or to Unicode strings (known as U-labels) +respectively. + +```pycon +>>> import idna +>>> idna.encode('ドメイン.テスト') +b'xn--eckwd4c7c.xn--zckzah' +>>> print(idna.decode('xn--eckwd4c7c.xn--zckzah')) +ドメイン.テスト +``` + +Conversions can be applied at a per-label basis using the `ulabel` or +`alabel` functions for specialized use cases. + + +### Compatibility Mapping (UTS #46) + +This library provides support for [Unicode IDNA Compatibility +Processing](https://unicode.org/reports/tr46/) which normalizes input from +different potential ways a user may input a domain prior to performing the IDNA +conversion operations. This functionality, known as a +[mapping](https://tools.ietf.org/html/rfc5895), is considered by the +specification to be a local user-interface issue distinct from IDNA +conversion functionality. + +For example, "Königsgäßchen" is not a permissible label as capital letters +are not allowed. UTS 46 will convert this into lower case prior to applying +the IDNA conversion. + +```pycon +>>> import idna +>>> idna.encode('Königsgäßchen') +... +idna.core.InvalidCodepoint: Codepoint U+004B at position 1 of 'Königsgäßchen' not allowed +>>> idna.encode('Königsgäßchen', uts46=True) +b'xn--knigsgchen-b4a3dun' +>>> idna.decode('xn--knigsgchen-b4a3dun') +'königsgäßchen' +``` + + +## Exceptions + +All errors raised during conversion derive from the `idna.IDNAError` +base class. The more specific exceptions are: + +* `idna.IDNABidiError` — raised when a label contains an illegal + combination of left-to-right and right-to-left characters. +* `idna.InvalidCodepoint` — raised when a label contains a codepoint + that is INVALID for IDNA. +* `idna.InvalidCodepointContext` — raised when a CONTEXTO or CONTEXTJ + codepoint appears in a position whose contextual requirements are + not satisfied. + + +## Command-line tool + +The package supports command-line usage to convert domain names +between their Unicode and ASCII-compatible forms. It can be run either +as a module (`python3 -m idna`) or, once installed (such as with `uv +tool` or `pipx`), via the `idna` script: + +```bash +$ uv tool install idna +$ idna xn--e1afmkfd.xn--p1ai +пример.рф +$ idna пример.рф +xn--e1afmkfd.xn--p1ai +``` + +With no mode flag the direction is chosen automatically: inputs +containing an `xn--` label are decoded, anything else is encoded. Pass +`-e`/`--encode` or `-d`/`--decode` to force a specific direction. + +Multiple domains may be supplied at once, either as positional arguments +or by piping one domain per line on standard input. When more than +one domain is supplied without explicitly asking to encode or decode, +the direction is picked from the first input and that mode is applied +to every remaining input. Use `-e`/`--encode` or `-d`/`--decode` to +override the heuristic if the first input is ambiguous. + +UTS #46 mapping is applied by default, which lets the tool accept +inputs that aren't strictly valid IDNA 2008 by normalising them first: + +```bash +$ idna ΠΑΡΆΔΕΙΓΜΑ.ΕΛ +xn--hxajbheg2az3al.xn--qxam +``` + +Pass `--strict` to disable UTS #46 and apply IDNA 2008 rules verbatim; +the same input will then be rejected. + +Conversion failures are reported on stderr together with the +offending input; processing continues with the remaining domains and +the tool exits with a non-zero status if any conversion failed. + + +## Additional Notes + +* **Version support**. This library supports Python 3.9 and higher. + As this library serves as a low-level toolkit for a variety of + applications, we strive to support all versions of Python that are + not beyond end-of-life. + +* **Emoji**. It is an occasional request to support emoji domains in + this library. Encoding of symbols like emoji is expressly prohibited by + the IDNA technical standard, and emoji domains are broadly phased + out across the domain industry due to associated security risks. + +* **Regenerating lookup tables**. The IDNA and UTS 46 functionality + relies upon pre-calculated lookup tables, generated using the + `idna-data` script in [`tools/`](tools/README.md). + diff --git a/intelliaide/vendor/idna-3.16.dist-info/RECORD b/intelliaide/vendor/idna-3.16.dist-info/RECORD new file mode 100644 index 0000000..91d62f1 --- /dev/null +++ b/intelliaide/vendor/idna-3.16.dist-info/RECORD @@ -0,0 +1,28 @@ +../../bin/idna,sha256=ik39X3QB7MIwNBxROWFOwpABsPUEqsfd8XLV5GMhQaA,219 +idna-3.16.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +idna-3.16.dist-info/METADATA,sha256=sjAPRjXAVVGHZEfKUICACY75c0RMTvuv2vzrDRxQgo4,6369 +idna-3.16.dist-info/RECORD,, +idna-3.16.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82 +idna-3.16.dist-info/entry_points.txt,sha256=7H3nGOHap3jnLE5e7q7Ywr9Vq8axB7WIj5-C_4N2vhw,38 +idna-3.16.dist-info/licenses/LICENSE.md,sha256=GppPDj1HmickDd1ZqRN6ZqtKD539yMphiMwL_YUYfwQ,1541 +idna/__init__.py,sha256=MPqNDLZbXqGaNdXxAFhiqFPKEQXju2jNQhCey6-5eJM,868 +idna/__main__.py,sha256=4JMK66Wj4uLZTKbF-sT3LAxOsr6buig77PmOkJCRRxw,83 +idna/__pycache__/__init__.cpython-312.pyc,, +idna/__pycache__/__main__.cpython-312.pyc,, +idna/__pycache__/cli.cpython-312.pyc,, +idna/__pycache__/codec.cpython-312.pyc,, +idna/__pycache__/compat.cpython-312.pyc,, +idna/__pycache__/core.cpython-312.pyc,, +idna/__pycache__/idnadata.cpython-312.pyc,, +idna/__pycache__/intranges.cpython-312.pyc,, +idna/__pycache__/package_data.cpython-312.pyc,, +idna/__pycache__/uts46data.cpython-312.pyc,, +idna/cli.py,sha256=swqJLMNc8Uzs60KziNpbWnHuqlG3WRQwJSbo4n8xDAo,4139 +idna/codec.py,sha256=JRbo-f7pEkLdWeiH89Z72UR4VBYhvKDFrQBeNX6sRDE,5040 +idna/compat.py,sha256=AepA39ceRHxkfHP41-FvKW5Ki-f4PfUZ90RUMlCNdmo,1353 +idna/core.py,sha256=WSSrhVyOHcFYvw1rXYLayVxORJ0HKF83bORzKhKyu3E,22863 +idna/idnadata.py,sha256=ArBb71aYVqU5sg_tsQJPzAGvxbAhJ9c28laSj-SuLwU,92575 +idna/intranges.py,sha256=g49scLSkqJtAhLmOODa7hVHriSjmb60tiTsEoocJdBI,1851 +idna/package_data.py,sha256=m9fnBBCnbqf9LnD9VKuYBiD4WqYKT7KssKSQyER4MqE,21 +idna/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +idna/uts46data.py,sha256=4J1cPwBRfKPlxWT6tNhzkq-et4eyoF0uoNbfvkAuiJE,202674 diff --git a/intelliaide/vendor/idna-3.16.dist-info/WHEEL b/intelliaide/vendor/idna-3.16.dist-info/WHEEL new file mode 100644 index 0000000..d8b9936 --- /dev/null +++ b/intelliaide/vendor/idna-3.16.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.12.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/intelliaide/vendor/idna-3.16.dist-info/entry_points.txt b/intelliaide/vendor/idna-3.16.dist-info/entry_points.txt new file mode 100644 index 0000000..59ca7ac --- /dev/null +++ b/intelliaide/vendor/idna-3.16.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +idna=idna.cli:main + diff --git a/intelliaide/vendor/idna-3.16.dist-info/licenses/LICENSE.md b/intelliaide/vendor/idna-3.16.dist-info/licenses/LICENSE.md new file mode 100644 index 0000000..f706835 --- /dev/null +++ b/intelliaide/vendor/idna-3.16.dist-info/licenses/LICENSE.md @@ -0,0 +1,31 @@ +BSD 3-Clause License + +Copyright (c) 2013-2026, Kim Davies and contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/intelliaide/vendor/idna/__init__.py b/intelliaide/vendor/idna/__init__.py new file mode 100644 index 0000000..cfdc030 --- /dev/null +++ b/intelliaide/vendor/idna/__init__.py @@ -0,0 +1,45 @@ +from .core import ( + IDNABidiError, + IDNAError, + InvalidCodepoint, + InvalidCodepointContext, + alabel, + check_bidi, + check_hyphen_ok, + check_initial_combiner, + check_label, + check_nfc, + decode, + encode, + ulabel, + uts46_remap, + valid_contextj, + valid_contexto, + valid_label_length, + valid_string_length, +) +from .intranges import intranges_contain +from .package_data import __version__ + +__all__ = [ + "__version__", + "IDNABidiError", + "IDNAError", + "InvalidCodepoint", + "InvalidCodepointContext", + "alabel", + "check_bidi", + "check_hyphen_ok", + "check_initial_combiner", + "check_label", + "check_nfc", + "decode", + "encode", + "intranges_contain", + "ulabel", + "uts46_remap", + "valid_contextj", + "valid_contexto", + "valid_label_length", + "valid_string_length", +] diff --git a/intelliaide/vendor/idna/__main__.py b/intelliaide/vendor/idna/__main__.py new file mode 100644 index 0000000..dbdd066 --- /dev/null +++ b/intelliaide/vendor/idna/__main__.py @@ -0,0 +1,6 @@ +import sys + +from .cli import main + +if __name__ == "__main__": + sys.exit(main()) diff --git a/intelliaide/vendor/idna/cli.py b/intelliaide/vendor/idna/cli.py new file mode 100644 index 0000000..4acda2c --- /dev/null +++ b/intelliaide/vendor/idna/cli.py @@ -0,0 +1,128 @@ +"""Command-line interface for the :mod:`idna` package. + +Invoked via ``python -m idna``. See :func:`main` for the entry point. +""" + +import argparse +import sys +from collections.abc import Iterable +from itertools import chain +from typing import IO, Optional + +from . import IDNAError, decode, encode +from .core import _alabel_prefix, _unicode_dots_re +from .package_data import __version__ + + +def _looks_like_alabel(s: str) -> bool: + """Return True if any label in ``s`` carries the ``xn--`` ACE prefix.""" + prefix = _alabel_prefix.decode("ascii") + return any(label.lower().startswith(prefix) for label in _unicode_dots_re.split(s)) + + +def _build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="python -m idna", + description=( + "Convert a domain name between its Unicode (U-label) and " + "ASCII-compatible (A-label) forms. With no mode flag, the " + "direction is chosen from the first input — if it contains " + "an xn-- label the stream is decoded, otherwise it is " + "encoded — and the same mode is applied to every remaining " + "input. UTS #46 mapping is applied by default; pass " + "--strict to disable it. When no domains are given on the " + "command line and stdin is piped, one domain per line is " + "read from stdin." + ), + ) + mode = parser.add_mutually_exclusive_group() + mode.add_argument( + "-e", + "--encode", + dest="mode", + action="store_const", + const="encode", + help="Encode the input to its ASCII A-label form.", + ) + mode.add_argument( + "-d", + "--decode", + dest="mode", + action="store_const", + const="decode", + help="Decode the input from its ASCII A-label form.", + ) + parser.add_argument( + "--strict", + action="store_true", + help="Disable the default UTS #46 mapping and apply IDNA 2008 rules verbatim.", + ) + parser.add_argument( + "--version", + action="version", + version=f"idna {__version__}", + ) + parser.add_argument( + "domain", + nargs="*", + help="One or more domain names to convert. Omit to read from stdin.", + ) + return parser + + +def _iter_stdin(stream: IO[str]) -> Iterable[str]: + """Yield non-empty stripped lines from ``stream``, ignoring blanks.""" + for line in stream: + stripped = line.strip() + if stripped: + yield stripped + + +def _convert_one(domain: str, mode: str, uts46: bool) -> bool: + """Convert ``domain`` and write the result; return ``False`` on failure.""" + try: + if mode == "decode": + print(decode(domain, uts46=uts46)) + else: + print(encode(domain, uts46=uts46).decode("ascii")) + except IDNAError as err: + print(f"idna: {mode} failed for {domain!r}: {err}", file=sys.stderr) + return False + return True + + +def main(argv: Optional[list[str]] = None) -> int: + """Entry point for ``python -m idna``. + + When more than one domain is supplied (via positional arguments or + piped stdin) and no mode flag is given, the first input determines + the direction and that mode is applied uniformly to the rest. + + :param argv: Argument list excluding the program name. Defaults to + :data:`sys.argv` when ``None``. + :returns: ``0`` on success, ``1`` if any conversion fails. + """ + parser = _build_parser() + args = parser.parse_args(argv) + uts46 = not args.strict + + if args.domain: + domains: Iterable[str] = args.domain + elif not sys.stdin.isatty(): + domains = _iter_stdin(sys.stdin) + else: + parser.error("a domain argument is required when stdin is a terminal") + + iterator = iter(domains) + first = next(iterator, None) + if first is None: + return 0 + + mode = args.mode or ("decode" if _looks_like_alabel(first) else "encode") + + results = [_convert_one(domain, mode, uts46) for domain in chain([first], iterator)] + return 0 if all(results) else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/intelliaide/vendor/idna/codec.py b/intelliaide/vendor/idna/codec.py new file mode 100644 index 0000000..83b42fe --- /dev/null +++ b/intelliaide/vendor/idna/codec.py @@ -0,0 +1,159 @@ +import codecs +from typing import Any, Optional + +from .core import IDNAError, _unicode_dots_re, alabel, decode, encode, ulabel + + +class Codec(codecs.Codec): + """Stateless IDNA 2008 codec. + + Implements the :class:`codecs.Codec` protocol so that the whole-domain + encoder (:func:`idna.encode`) and decoder (:func:`idna.decode`) are + accessible through the standard codec machinery as ``"idna2008"``. + + Only the ``"strict"`` error handler is supported; any other handler + raises :exc:`~idna.IDNAError`. + """ + + def encode(self, data: str, errors: str = "strict") -> tuple[bytes, int]: # ty: ignore[invalid-method-override] + if errors != "strict": + raise IDNAError(f'Unsupported error handling "{errors}"') + + if not data: + return b"", 0 + + return encode(data), len(data) + + def decode(self, data: bytes, errors: str = "strict") -> tuple[str, int]: # ty: ignore[invalid-method-override] + if errors != "strict": + raise IDNAError(f'Unsupported error handling "{errors}"') + + if not data: + return "", 0 + + return decode(data), len(data) + + +class IncrementalEncoder(codecs.BufferedIncrementalEncoder): + """Incremental IDNA 2008 encoder. + + Buffers a partial trailing label across calls until either the next + label separator is seen or ``final=True``, so that streamed input is + encoded one whole label at a time. Any of the four Unicode label + separators (``U+002E``, ``U+3002``, ``U+FF0E``, ``U+FF61``) ends a + label; the result always uses ``U+002E`` as the separator. + + Only the ``"strict"`` error handler is supported. + """ + + def _buffer_encode(self, data: str, errors: str, final: bool) -> tuple[bytes, int]: # ty: ignore[invalid-method-override] + if errors != "strict": + raise IDNAError(f'Unsupported error handling "{errors}"') + + if not data: + return b"", 0 + + labels = _unicode_dots_re.split(data) + trailing_dot = b"" + if labels: + if not labels[-1]: + trailing_dot = b"." + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = b"." + + result = [] + size = 0 + for label in labels: + result.append(alabel(label)) + if size: + size += 1 + size += len(label) + + # Join with U+002E + result_bytes = b".".join(result) + trailing_dot + size += len(trailing_dot) + return result_bytes, size + + +class IncrementalDecoder(codecs.BufferedIncrementalDecoder): + """Incremental IDNA 2008 decoder. + + Buffers a partial trailing label across calls until either the next + label separator is seen or ``final=True``, so that streamed input is + decoded one whole label at a time. + + Only the ``"strict"`` error handler is supported. + """ + + def _buffer_decode(self, data: Any, errors: str, final: bool) -> tuple[str, int]: # ty: ignore[invalid-method-override] + if errors != "strict": + raise IDNAError(f'Unsupported error handling "{errors}"') + + if not data: + return ("", 0) + + if not isinstance(data, str): + data = str(data, "ascii") + + labels = _unicode_dots_re.split(data) + trailing_dot = "" + if labels: + if not labels[-1]: + trailing_dot = "." + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = "." + + result = [] + size = 0 + for label in labels: + result.append(ulabel(label)) + if size: + size += 1 + size += len(label) + + result_str = ".".join(result) + trailing_dot + size += len(trailing_dot) + return (result_str, size) + + +class StreamWriter(Codec, codecs.StreamWriter): + pass + + +class StreamReader(Codec, codecs.StreamReader): + pass + + +def search_function(name: str) -> Optional[codecs.CodecInfo]: + """Codec search function registered with :mod:`codecs`. + + Returns a :class:`codecs.CodecInfo` for the ``"idna2008"`` codec name + so that ``str.encode("idna2008")`` and ``bytes.decode("idna2008")`` + invoke the IDNA 2008 codec defined in this module. + + :param name: The codec name being looked up. + :returns: A :class:`codecs.CodecInfo` instance if ``name`` is + ``"idna2008"``, otherwise ``None``. + """ + if name != "idna2008": + return None + return codecs.CodecInfo( + name=name, + encode=Codec().encode, + decode=Codec().decode, # type: ignore + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamwriter=StreamWriter, + streamreader=StreamReader, + ) + + +codecs.register(search_function) diff --git a/intelliaide/vendor/idna/compat.py b/intelliaide/vendor/idna/compat.py new file mode 100644 index 0000000..1d01e3d --- /dev/null +++ b/intelliaide/vendor/idna/compat.py @@ -0,0 +1,41 @@ +from typing import Any, Union + +from .core import decode, encode + + +def ToASCII(label: str) -> bytes: + """Compatibility shim for :rfc:`3490` ``ToASCII``. + + Delegates to :func:`idna.encode` (IDNA 2008). Provided to ease porting + of code written against the legacy :mod:`encodings.idna` API; new code + should call :func:`idna.encode` directly. + + :param label: The label or domain to encode. + :returns: The encoded form as ASCII :class:`bytes`. + """ + return encode(label) + + +def ToUnicode(label: Union[bytes, bytearray]) -> str: + """Compatibility shim for :rfc:`3490` ``ToUnicode``. + + Delegates to :func:`idna.decode` (IDNA 2008). Provided to ease porting + of code written against the legacy :mod:`encodings.idna` API; new code + should call :func:`idna.decode` directly. + + :param label: The label or domain to decode. + :returns: The decoded Unicode form. + """ + return decode(label) + + +def nameprep(s: Any) -> None: + """Stub for :rfc:`3491` Nameprep, which is not used by IDNA 2008. + + IDNA 2008 (:rfc:`5891`) replaces Nameprep with the per-codepoint + validity classes from :rfc:`5892`; this function exists only to + return a clear error if legacy code attempts to call it. + + :raises NotImplementedError: Always. + """ + raise NotImplementedError("IDNA 2008 does not utilise nameprep protocol") diff --git a/intelliaide/vendor/idna/core.py b/intelliaide/vendor/idna/core.py new file mode 100644 index 0000000..da45b2a --- /dev/null +++ b/intelliaide/vendor/idna/core.py @@ -0,0 +1,605 @@ +import bisect +import re +import unicodedata +import warnings +from typing import Optional, Union + +from . import idnadata +from .intranges import intranges_contain + +_virama_combining_class = 9 +_alabel_prefix = b"xn--" +_unicode_dots_re = re.compile("[\u002e\u3002\uff0e\uff61]") + + +# Bidi category sets from RFC 5893, hoisted out of the per-codepoint loop +_bidi_rtl_first = frozenset({"R", "AL"}) +_bidi_rtl_categories = frozenset({"R", "AL", "AN"}) +_bidi_rtl_allowed = frozenset({"R", "AL", "AN", "EN", "ES", "CS", "ET", "ON", "BN", "NSM"}) +_bidi_rtl_valid_ending = frozenset({"R", "AL", "EN", "AN"}) +_bidi_rtl_numeric = frozenset({"AN", "EN"}) +_bidi_ltr_allowed = frozenset({"L", "EN", "ES", "CS", "ET", "ON", "BN", "NSM"}) +_bidi_ltr_valid_ending = frozenset({"L", "EN"}) +_bidi_joiner_l_or_d = frozenset({ord("L"), ord("D")}) +_bidi_joiner_r_or_d = frozenset({ord("R"), ord("D")}) + + +class IDNAError(UnicodeError): + """Base exception for all IDNA-encoding related problems""" + + +class IDNABidiError(IDNAError): + """Exception when bidirectional requirements are not satisfied""" + + +class InvalidCodepoint(IDNAError): + """Exception when a disallowed or unallocated codepoint is used""" + + +class InvalidCodepointContext(IDNAError): + """Exception when the codepoint is not valid in the context it is used""" + + +def _combining_class(cp: int) -> int: + v = unicodedata.combining(chr(cp)) + if v == 0 and not unicodedata.name(chr(cp)): + raise ValueError("Unknown character in unicodedata") + return v + + +def _is_script(cp: str, script: str) -> bool: + return intranges_contain(ord(cp), idnadata.scripts[script]) + + +def _punycode(s: str) -> bytes: + return s.encode("punycode") + + +def _unot(s: int) -> str: + return f"U+{s:04X}" + + +def valid_label_length(label: Union[bytes, str]) -> bool: + """Check that a label does not exceed the maximum permitted length. + + Per :rfc:`1035` (and :rfc:`5891` §4.2.4) a DNS label must not exceed + 63 octets. The argument may be either a :class:`str` (a U-label, where + length is measured in characters) or :class:`bytes` (an A-label, where + length is measured in octets). + + :param label: The label to check. + :returns: ``True`` if the label is within the length limit, otherwise + ``False``. + """ + return len(label) <= 63 + + +def valid_string_length(domain: Union[bytes, str], trailing_dot: bool) -> bool: + """Check that a full domain name does not exceed the maximum length. + + Per :rfc:`1035`, a domain name is limited to 253 octets when no trailing + dot is present, or 254 octets when one is included. + + :param domain: The full (possibly multi-label) domain name. + :param trailing_dot: ``True`` if ``domain`` includes a trailing ``.``. + :returns: ``True`` if the domain is within the length limit, otherwise + ``False``. + """ + return len(domain) <= (254 if trailing_dot else 253) + + +def check_bidi(label: str, check_ltr: bool = False) -> bool: + """Validate the Bidi Rule from :rfc:`5893` for a single label. + + The Bidi Rule constrains how bidirectional characters (Hebrew, Arabic, + etc.) may appear within a label. By default the check is only applied + when the label contains at least one right-to-left character (Unicode + bidirectional categories ``R``, ``AL``, or ``AN``); set ``check_ltr`` + to ``True`` to apply it to LTR-only labels as well. + + :param label: The label to validate, as a Unicode string. + :param check_ltr: If ``True``, apply the rules even when the label + contains no RTL characters. + :returns: ``True`` if the label satisfies the Bidi Rule. + :raises IDNABidiError: If any of Bidi Rule conditions 1-6 are violated, + or if the directional category of a codepoint cannot be determined. + """ + # Bidi rules should only be applied if string contains RTL characters + bidi_label = False + for idx, cp in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + if direction == "": + # String likely comes from a newer version of Unicode + raise IDNABidiError(f"Unknown directionality in label {label!r} at position {idx}") + if direction in _bidi_rtl_categories: + bidi_label = True + if not bidi_label and not check_ltr: + return True + + # Bidi rule 1 + direction = unicodedata.bidirectional(label[0]) + if direction in _bidi_rtl_first: + rtl = True + elif direction == "L": + rtl = False + else: + raise IDNABidiError(f"First codepoint in label {label!r} must be directionality L, R or AL") + + valid_ending = False + number_type: Optional[str] = None + for idx, cp in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + + if rtl: + # Bidi rule 2 + if direction not in _bidi_rtl_allowed: + raise IDNABidiError(f"Invalid direction for codepoint at position {idx} in a right-to-left label") + # Bidi rule 3 + if direction in _bidi_rtl_valid_ending: + valid_ending = True + elif direction != "NSM": + valid_ending = False + # Bidi rule 4 + if direction in _bidi_rtl_numeric: + if not number_type: + number_type = direction + elif number_type != direction: + raise IDNABidiError("Can not mix numeral types in a right-to-left label") + else: + # Bidi rule 5 + if direction not in _bidi_ltr_allowed: + raise IDNABidiError(f"Invalid direction for codepoint at position {idx} in a left-to-right label") + # Bidi rule 6 + if direction in _bidi_ltr_valid_ending: + valid_ending = True + elif direction != "NSM": + valid_ending = False + + if not valid_ending: + raise IDNABidiError("Label ends with illegal codepoint directionality") + + return True + + +def check_initial_combiner(label: str) -> bool: + """Reject labels that begin with a combining mark. + + Per :rfc:`5891` §4.2.3.2 a label must not start with a character of + Unicode general category ``M`` (Mark). + + :param label: The label to check. + :returns: ``True`` if the first character is not a combining mark. + :raises IDNAError: If the label begins with a combining character. + """ + if unicodedata.category(label[0])[0] == "M": + raise IDNAError("Label begins with an illegal combining character") + return True + + +def check_hyphen_ok(label: str) -> bool: + """Validate the hyphen restrictions for a label. + + Per :rfc:`5891` §4.2.3.1 a label must not start or end with a hyphen + (``U+002D``), and must not have hyphens in both the third and fourth + positions (the prefix reserved for A-labels). + + :param label: The label to check. + :returns: ``True`` if the hyphen restrictions are satisfied. + :raises IDNAError: If any of the hyphen restrictions are violated. + """ + if label[2:4] == "--": + raise IDNAError("Label has disallowed hyphens in 3rd and 4th position") + if label[0] == "-" or label[-1] == "-": + raise IDNAError("Label must not start or end with a hyphen") + return True + + +def check_nfc(label: str) -> None: + """Require that a label is in Unicode Normalization Form C. + + :param label: The label to check. + :raises IDNAError: If ``label`` differs from its NFC normalisation. + """ + if unicodedata.normalize("NFC", label) != label: + raise IDNAError("Label must be in Normalization Form C") + + +def valid_contextj(label: str, pos: int) -> bool: + """Validate the CONTEXTJ rules from :rfc:`5892` Appendix A. + + These rules govern the contextual use of the joiner codepoints + ``U+200C`` (ZERO WIDTH NON-JOINER, Appendix A.1) and ``U+200D`` + (ZERO WIDTH JOINER, Appendix A.2) within a label. + + :param label: The label containing the codepoint. + :param pos: Index of the joiner codepoint within ``label``. + :returns: ``True`` if the codepoint at ``pos`` satisfies its CONTEXTJ + rule, ``False`` otherwise (including when the codepoint at + ``pos`` is not a recognised joiner). + :raises ValueError: If an adjacent codepoint has no Unicode name when + determining its combining class. + """ + cp_value = ord(label[pos]) + + if cp_value == 0x200C: + if pos > 0 and _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + + ok = False + for i in range(pos - 1, -1, -1): + joining_type = idnadata.joining_types().get(ord(label[i])) + if joining_type == ord("T"): + continue + if joining_type in _bidi_joiner_l_or_d: + ok = True + break + break + + if not ok: + return False + + ok = False + for i in range(pos + 1, len(label)): + joining_type = idnadata.joining_types().get(ord(label[i])) + if joining_type == ord("T"): + continue + if joining_type in _bidi_joiner_r_or_d: + ok = True + break + break + return ok + + if cp_value == 0x200D: + return pos > 0 and _combining_class(ord(label[pos - 1])) == _virama_combining_class + + return False + + +def valid_contexto(label: str, pos: int, exception: bool = False) -> bool: + """Validate the CONTEXTO rules from :rfc:`5892` Appendix A. + + Covers the contextual rules for codepoints such as MIDDLE DOT + (``U+00B7``), Greek lower numeral sign, Hebrew punctuation, Katakana + middle dot, and the Arabic-Indic / Extended Arabic-Indic digit ranges. + + :param label: The label containing the codepoint. + :param pos: Index of the codepoint within ``label``. + :param exception: Reserved for forward compatibility; currently unused. + :returns: ``True`` if the codepoint at ``pos`` satisfies its CONTEXTO + rule, ``False`` otherwise (including when the codepoint is not a + recognised CONTEXTO codepoint). + """ + cp_value = ord(label[pos]) + + if cp_value == 0x00B7: + return 0 < pos < len(label) - 1 and ord(label[pos - 1]) == 0x006C and ord(label[pos + 1]) == 0x006C + + if cp_value == 0x0375: + if pos < len(label) - 1 and len(label) > 1: + return _is_script(label[pos + 1], "Greek") + return False + + if cp_value in {0x05F3, 0x05F4}: + if pos > 0: + return _is_script(label[pos - 1], "Hebrew") + return False + + if cp_value == 0x30FB: + for cp in label: + if cp == "\u30fb": + continue + if _is_script(cp, "Hiragana") or _is_script(cp, "Katakana") or _is_script(cp, "Han"): + return True + return False + + if 0x660 <= cp_value <= 0x669: + return not any(0x6F0 <= ord(cp) <= 0x06F9 for cp in label) + + if 0x6F0 <= cp_value <= 0x6F9: + return not any(0x660 <= ord(cp) <= 0x0669 for cp in label) + + return False + + +def check_label(label: Union[str, bytes, bytearray]) -> None: + """Run the full set of IDNA 2008 validity checks on a single label. + + Applies, in order: NFC normalisation (:func:`check_nfc`), hyphen + restrictions (:func:`check_hyphen_ok`), the no-leading-combiner rule + (:func:`check_initial_combiner`), per-codepoint validity (PVALID, + CONTEXTJ, CONTEXTO classes from :rfc:`5892`), and the Bidi Rule + (:func:`check_bidi`). + + :param label: The label to validate. ``bytes`` or ``bytearray`` input + is decoded as UTF-8 first. + :raises IDNAError: If the label is empty or fails a structural rule. + :raises InvalidCodepoint: If the label contains a DISALLOWED or + UNASSIGNED codepoint. + :raises InvalidCodepointContext: If a CONTEXTJ or CONTEXTO codepoint + is not valid in its context. + :raises IDNABidiError: If the Bidi Rule is violated. + """ + if isinstance(label, (bytes, bytearray)): + label = label.decode("utf-8") + if len(label) == 0: + raise IDNAError("Empty Label") + + # Reject on domain length rather than label length so support some UTS 46 + # use cases, still reducing processing of label contextual rules + if not valid_string_length(label, trailing_dot=True): + raise IDNAError("Label too long") + + check_nfc(label) + check_hyphen_ok(label) + check_initial_combiner(label) + + for pos, cp in enumerate(label): + cp_value = ord(cp) + if intranges_contain(cp_value, idnadata.codepoint_classes["PVALID"]): + continue + if intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTJ"]): + try: + if not valid_contextj(label, pos): + raise InvalidCodepointContext(f"Joiner {_unot(cp_value)} not allowed at position {pos + 1} in {label!r}") + except ValueError as err: + raise IDNAError( + f"Unknown codepoint adjacent to joiner {_unot(cp_value)} at position {pos + 1} in {label!r}" + ) from err + elif intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTO"]): + if not valid_contexto(label, pos): + raise InvalidCodepointContext(f"Codepoint {_unot(cp_value)} not allowed at position {pos + 1} in {label!r}") + else: + raise InvalidCodepoint(f"Codepoint {_unot(cp_value)} at position {pos + 1} of {label!r} not allowed") + + check_bidi(label) + + +def alabel(label: str) -> bytes: + """Convert a single U-label into its A-label form. + + The result is the ASCII-Compatible Encoding (ACE) form per :rfc:`5891` + §4: the label is validated, Punycode-encoded, and prefixed with + ``xn--``. Pure ASCII labels that are already valid IDNA labels are + returned unchanged (as :class:`bytes`). + + :param label: The label to convert, as a Unicode string. + :returns: The A-label as ASCII-encoded :class:`bytes`. + :raises IDNAError: If the label is invalid or the resulting A-label + exceeds 63 octets. + """ + try: + label_bytes = label.encode("ascii") + except UnicodeEncodeError: + pass + else: + ulabel(label_bytes) + if not valid_label_length(label_bytes): + raise IDNAError("Label too long") + return label_bytes + + check_label(label) + label_bytes = _alabel_prefix + _punycode(label) + + if not valid_label_length(label_bytes): + raise IDNAError("Label too long") + + return label_bytes + + +def ulabel(label: Union[str, bytes, bytearray]) -> str: + """Convert a single A-label into its U-label form. + + Performs the inverse of :func:`alabel`: an ``xn--``-prefixed label is + Punycode-decoded and validated. Labels that are already Unicode (or + plain ASCII without the ACE prefix) are validated and returned as a + Unicode string. + + :param label: The label to convert. ``bytes`` or ``bytearray`` input + is treated as ASCII. + :returns: The U-label as a Unicode string. + :raises IDNAError: If the label is malformed or fails validation. + """ + if not isinstance(label, (bytes, bytearray)): + try: + label_bytes = label.encode("ascii") + except UnicodeEncodeError: + check_label(label) + return label + else: + label_bytes = bytes(label) + + label_bytes = label_bytes.lower() + if label_bytes.startswith(_alabel_prefix): + label_bytes = label_bytes[len(_alabel_prefix) :] + if not label_bytes: + raise IDNAError("Malformed A-label, no Punycode eligible content found") + if label_bytes.endswith(b"-"): + raise IDNAError("A-label must not end with a hyphen") + else: + check_label(label_bytes) + return label_bytes.decode("ascii") + + try: + label = label_bytes.decode("punycode") + except UnicodeError as err: + raise IDNAError("Invalid A-label") from err + check_label(label) + return label + + +def uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False) -> str: + """Apply the UTS #46 character mapping to a domain string. + + Implements the mapping table from `UTS #46 §4 + `_: each character is kept, + replaced, or rejected based on its status (``V``, ``M``, ``D``, ``3``, + ``I``). The result is returned in Normalisation Form C. + + :param domain: The full domain name to remap. + :param std3_rules: If ``True``, apply the stricter STD3 ASCII rules + (status ``3`` codepoints raise instead of being kept or mapped). + :param transitional: If ``True``, use transitional processing (status + ``D`` codepoints are mapped instead of kept). Transitional + processing has been removed from UTS #46 and this option is + retained only for backwards compatibility. + :returns: The remapped domain, in Normalisation Form C. + :raises InvalidCodepoint: If the domain contains a disallowed + codepoint under the chosen rules. + """ + from .uts46data import uts46data + + output = "" + + for pos, char in enumerate(domain): + code_point = ord(char) + uts46row = uts46data[code_point if code_point < 256 else bisect.bisect_left(uts46data, (code_point, "Z")) - 1] + status = uts46row[1] + replacement: Optional[str] = None + if len(uts46row) == 3: + replacement = uts46row[2] # ty: ignore[index-out-of-bounds] + + # UTS #46 §4: V is always valid, D is deviation (kept unless transitional), + # 3 is disallowed-STD3 (kept unmapped if std3_rules is off and no mapping). + keep_as_is = ( + status == "V" or (status == "D" and not transitional) or (status == "3" and not std3_rules and replacement is None) + ) + # M is mapped, 3-with-replacement and transitional D fall through to the + # same replacement output path. + use_replacement = replacement is not None and ( + status == "M" or (status == "3" and not std3_rules) or (status == "D" and transitional) + ) + + if keep_as_is: + output += char + elif use_replacement: + assert replacement is not None # narrowed by use_replacement + output += replacement + elif status == "I": + continue + else: + raise InvalidCodepoint(f"Codepoint {_unot(code_point)} not allowed at position {pos + 1} in {domain!r}") + + return unicodedata.normalize("NFC", output) + + +def encode( + s: Union[str, bytes, bytearray], + strict: bool = False, + uts46: bool = False, + std3_rules: bool = False, + transitional: bool = False, +) -> bytes: + """Encode a Unicode domain name into its ASCII (A-label) form. + + Splits the input on label separators (only ``U+002E`` if ``strict`` is + set; otherwise also IDEOGRAPHIC FULL STOP ``U+3002``, FULLWIDTH FULL + STOP ``U+FF0E``, and HALFWIDTH IDEOGRAPHIC FULL STOP ``U+FF61``), + encodes each label with :func:`alabel`, and rejoins them with ``.``. + Optionally pre-processes the input through :func:`uts46_remap`. + + :param s: The domain name to encode. + :param strict: If ``True``, only ``U+002E`` is recognised as a label + separator. + :param uts46: If ``True``, apply UTS #46 mapping before encoding. + :param std3_rules: Forwarded to :func:`uts46_remap` when ``uts46`` is + ``True``. + :param transitional: Forwarded to :func:`uts46_remap` when ``uts46`` + is ``True``. Deprecated: emits a :class:`DeprecationWarning` and + will be removed in a future version. + :returns: The encoded domain as ASCII :class:`bytes`. + :raises IDNAError: If the domain is empty, contains an invalid label, + or exceeds the maximum domain length. + """ + if transitional: + warnings.warn( + "Transitional processing has been removed from UTS #46. " + "The transitional argument will be removed in a future version.", + DeprecationWarning, + stacklevel=2, + ) + if not isinstance(s, str): + try: + s = str(s, "ascii") + except (UnicodeDecodeError, TypeError) as err: + raise IDNAError("should pass a unicode string to the function rather than a byte string.") from err + if uts46: + s = uts46_remap(s, std3_rules, transitional) + + # Reject inputs that exceed the maximum DNS domain length up-front + # to avoid expensive computation on long inputs. + if not valid_string_length(s, trailing_dot=True): + raise IDNAError("Domain too long") + + trailing_dot = False + result = [] + labels = s.split(".") if strict else _unicode_dots_re.split(s) + if not labels or labels == [""]: + raise IDNAError("Empty domain") + if labels[-1] == "": + del labels[-1] + trailing_dot = True + for label in labels: + s = alabel(label) + if s: + result.append(s) + else: + raise IDNAError("Empty label") + if trailing_dot: + result.append(b"") + s = b".".join(result) + if not valid_string_length(s, trailing_dot): + raise IDNAError("Domain too long") + return s + + +def decode( + s: Union[str, bytes, bytearray], + strict: bool = False, + uts46: bool = False, + std3_rules: bool = False, +) -> str: + """Decode an A-label-encoded domain name back to Unicode. + + Splits the input on label separators (see :func:`encode` for the + rules), decodes each label with :func:`ulabel`, and rejoins them + with ``.``. Optionally pre-processes the input through + :func:`uts46_remap`. + + :param s: The domain name to decode. + :param strict: If ``True``, only ``U+002E`` is recognised as a label + separator. + :param uts46: If ``True``, apply UTS #46 mapping before decoding. + :param std3_rules: Forwarded to :func:`uts46_remap` when ``uts46`` is + ``True``. + :returns: The decoded domain as a Unicode string. + :raises IDNAError: If the input is not valid ASCII, contains an + invalid label, or is empty. + """ + if not isinstance(s, str): + try: + s = str(s, "ascii") + except (UnicodeDecodeError, TypeError) as err: + raise IDNAError("Invalid ASCII in A-label") from err + if uts46: + s = uts46_remap(s, std3_rules, False) + # Reject inputs that exceed the maximum DNS domain length up-front + # to avoid expensive computation on long inputs. + if not valid_string_length(s, trailing_dot=True): + raise IDNAError("Domain too long") + trailing_dot = False + result = [] + labels = s.split(".") if strict else _unicode_dots_re.split(s) + if not labels or labels == [""]: + raise IDNAError("Empty domain") + if not labels[-1]: + del labels[-1] + trailing_dot = True + for label in labels: + s = ulabel(label) + if s: + result.append(s) + else: + raise IDNAError("Empty label") + if trailing_dot: + result.append("") + return ".".join(result) diff --git a/intelliaide/vendor/idna/idnadata.py b/intelliaide/vendor/idna/idnadata.py new file mode 100644 index 0000000..d768948 --- /dev/null +++ b/intelliaide/vendor/idna/idnadata.py @@ -0,0 +1,4366 @@ +# This file is automatically generated by tools/idna-data + +from functools import cache + +__version__ = "17.0.0" + +scripts = { + "Greek": ( + 0x37000000374, + 0x37500000378, + 0x37A0000037E, + 0x37F00000380, + 0x38400000385, + 0x38600000387, + 0x3880000038B, + 0x38C0000038D, + 0x38E000003A2, + 0x3A3000003E2, + 0x3F000000400, + 0x1D2600001D2B, + 0x1D5D00001D62, + 0x1D6600001D6B, + 0x1DBF00001DC0, + 0x1F0000001F16, + 0x1F1800001F1E, + 0x1F2000001F46, + 0x1F4800001F4E, + 0x1F5000001F58, + 0x1F5900001F5A, + 0x1F5B00001F5C, + 0x1F5D00001F5E, + 0x1F5F00001F7E, + 0x1F8000001FB5, + 0x1FB600001FC5, + 0x1FC600001FD4, + 0x1FD600001FDC, + 0x1FDD00001FF0, + 0x1FF200001FF5, + 0x1FF600001FFF, + 0x212600002127, + 0xAB650000AB66, + 0x101400001018F, + 0x101A0000101A1, + 0x1D2000001D246, + ), + "Han": ( + 0x2E8000002E9A, + 0x2E9B00002EF4, + 0x2F0000002FD6, + 0x300500003006, + 0x300700003008, + 0x30210000302A, + 0x30380000303C, + 0x340000004DC0, + 0x4E000000A000, + 0xF9000000FA6E, + 0xFA700000FADA, + 0x16FE200016FE4, + 0x16FF000016FF7, + 0x200000002A6E0, + 0x2A7000002B81E, + 0x2B8200002CEAE, + 0x2CEB00002EBE1, + 0x2EBF00002EE5E, + 0x2F8000002FA1E, + 0x300000003134B, + 0x313500003347A, + ), + "Hebrew": ( + 0x591000005C8, + 0x5D0000005EB, + 0x5EF000005F5, + 0xFB1D0000FB37, + 0xFB380000FB3D, + 0xFB3E0000FB3F, + 0xFB400000FB42, + 0xFB430000FB45, + 0xFB460000FB50, + ), + "Hiragana": ( + 0x304100003097, + 0x309D000030A0, + 0x1B0010001B120, + 0x1B1320001B133, + 0x1B1500001B153, + 0x1F2000001F201, + ), + "Katakana": ( + 0x30A1000030FB, + 0x30FD00003100, + 0x31F000003200, + 0x32D0000032FF, + 0x330000003358, + 0xFF660000FF70, + 0xFF710000FF9E, + 0x1AFF00001AFF4, + 0x1AFF50001AFFC, + 0x1AFFD0001AFFF, + 0x1B0000001B001, + 0x1B1200001B123, + 0x1B1550001B156, + 0x1B1640001B168, + ), +} + + +@cache +def joining_types() -> dict[int, int]: + return { + 0xAD: 84, + 0x300: 84, + 0x301: 84, + 0x302: 84, + 0x303: 84, + 0x304: 84, + 0x305: 84, + 0x306: 84, + 0x307: 84, + 0x308: 84, + 0x309: 84, + 0x30A: 84, + 0x30B: 84, + 0x30C: 84, + 0x30D: 84, + 0x30E: 84, + 0x30F: 84, + 0x310: 84, + 0x311: 84, + 0x312: 84, + 0x313: 84, + 0x314: 84, + 0x315: 84, + 0x316: 84, + 0x317: 84, + 0x318: 84, + 0x319: 84, + 0x31A: 84, + 0x31B: 84, + 0x31C: 84, + 0x31D: 84, + 0x31E: 84, + 0x31F: 84, + 0x320: 84, + 0x321: 84, + 0x322: 84, + 0x323: 84, + 0x324: 84, + 0x325: 84, + 0x326: 84, + 0x327: 84, + 0x328: 84, + 0x329: 84, + 0x32A: 84, + 0x32B: 84, + 0x32C: 84, + 0x32D: 84, + 0x32E: 84, + 0x32F: 84, + 0x330: 84, + 0x331: 84, + 0x332: 84, + 0x333: 84, + 0x334: 84, + 0x335: 84, + 0x336: 84, + 0x337: 84, + 0x338: 84, + 0x339: 84, + 0x33A: 84, + 0x33B: 84, + 0x33C: 84, + 0x33D: 84, + 0x33E: 84, + 0x33F: 84, + 0x340: 84, + 0x341: 84, + 0x342: 84, + 0x343: 84, + 0x344: 84, + 0x345: 84, + 0x346: 84, + 0x347: 84, + 0x348: 84, + 0x349: 84, + 0x34A: 84, + 0x34B: 84, + 0x34C: 84, + 0x34D: 84, + 0x34E: 84, + 0x34F: 84, + 0x350: 84, + 0x351: 84, + 0x352: 84, + 0x353: 84, + 0x354: 84, + 0x355: 84, + 0x356: 84, + 0x357: 84, + 0x358: 84, + 0x359: 84, + 0x35A: 84, + 0x35B: 84, + 0x35C: 84, + 0x35D: 84, + 0x35E: 84, + 0x35F: 84, + 0x360: 84, + 0x361: 84, + 0x362: 84, + 0x363: 84, + 0x364: 84, + 0x365: 84, + 0x366: 84, + 0x367: 84, + 0x368: 84, + 0x369: 84, + 0x36A: 84, + 0x36B: 84, + 0x36C: 84, + 0x36D: 84, + 0x36E: 84, + 0x36F: 84, + 0x483: 84, + 0x484: 84, + 0x485: 84, + 0x486: 84, + 0x487: 84, + 0x488: 84, + 0x489: 84, + 0x591: 84, + 0x592: 84, + 0x593: 84, + 0x594: 84, + 0x595: 84, + 0x596: 84, + 0x597: 84, + 0x598: 84, + 0x599: 84, + 0x59A: 84, + 0x59B: 84, + 0x59C: 84, + 0x59D: 84, + 0x59E: 84, + 0x59F: 84, + 0x5A0: 84, + 0x5A1: 84, + 0x5A2: 84, + 0x5A3: 84, + 0x5A4: 84, + 0x5A5: 84, + 0x5A6: 84, + 0x5A7: 84, + 0x5A8: 84, + 0x5A9: 84, + 0x5AA: 84, + 0x5AB: 84, + 0x5AC: 84, + 0x5AD: 84, + 0x5AE: 84, + 0x5AF: 84, + 0x5B0: 84, + 0x5B1: 84, + 0x5B2: 84, + 0x5B3: 84, + 0x5B4: 84, + 0x5B5: 84, + 0x5B6: 84, + 0x5B7: 84, + 0x5B8: 84, + 0x5B9: 84, + 0x5BA: 84, + 0x5BB: 84, + 0x5BC: 84, + 0x5BD: 84, + 0x5BF: 84, + 0x5C1: 84, + 0x5C2: 84, + 0x5C4: 84, + 0x5C5: 84, + 0x5C7: 84, + 0x610: 84, + 0x611: 84, + 0x612: 84, + 0x613: 84, + 0x614: 84, + 0x615: 84, + 0x616: 84, + 0x617: 84, + 0x618: 84, + 0x619: 84, + 0x61A: 84, + 0x61C: 84, + 0x620: 68, + 0x622: 82, + 0x623: 82, + 0x624: 82, + 0x625: 82, + 0x626: 68, + 0x627: 82, + 0x628: 68, + 0x629: 82, + 0x62A: 68, + 0x62B: 68, + 0x62C: 68, + 0x62D: 68, + 0x62E: 68, + 0x62F: 82, + 0x630: 82, + 0x631: 82, + 0x632: 82, + 0x633: 68, + 0x634: 68, + 0x635: 68, + 0x636: 68, + 0x637: 68, + 0x638: 68, + 0x639: 68, + 0x63A: 68, + 0x63B: 68, + 0x63C: 68, + 0x63D: 68, + 0x63E: 68, + 0x63F: 68, + 0x640: 67, + 0x641: 68, + 0x642: 68, + 0x643: 68, + 0x644: 68, + 0x645: 68, + 0x646: 68, + 0x647: 68, + 0x648: 82, + 0x649: 68, + 0x64A: 68, + 0x64B: 84, + 0x64C: 84, + 0x64D: 84, + 0x64E: 84, + 0x64F: 84, + 0x650: 84, + 0x651: 84, + 0x652: 84, + 0x653: 84, + 0x654: 84, + 0x655: 84, + 0x656: 84, + 0x657: 84, + 0x658: 84, + 0x659: 84, + 0x65A: 84, + 0x65B: 84, + 0x65C: 84, + 0x65D: 84, + 0x65E: 84, + 0x65F: 84, + 0x66E: 68, + 0x66F: 68, + 0x670: 84, + 0x671: 82, + 0x672: 82, + 0x673: 82, + 0x675: 82, + 0x676: 82, + 0x677: 82, + 0x678: 68, + 0x679: 68, + 0x67A: 68, + 0x67B: 68, + 0x67C: 68, + 0x67D: 68, + 0x67E: 68, + 0x67F: 68, + 0x680: 68, + 0x681: 68, + 0x682: 68, + 0x683: 68, + 0x684: 68, + 0x685: 68, + 0x686: 68, + 0x687: 68, + 0x688: 82, + 0x689: 82, + 0x68A: 82, + 0x68B: 82, + 0x68C: 82, + 0x68D: 82, + 0x68E: 82, + 0x68F: 82, + 0x690: 82, + 0x691: 82, + 0x692: 82, + 0x693: 82, + 0x694: 82, + 0x695: 82, + 0x696: 82, + 0x697: 82, + 0x698: 82, + 0x699: 82, + 0x69A: 68, + 0x69B: 68, + 0x69C: 68, + 0x69D: 68, + 0x69E: 68, + 0x69F: 68, + 0x6A0: 68, + 0x6A1: 68, + 0x6A2: 68, + 0x6A3: 68, + 0x6A4: 68, + 0x6A5: 68, + 0x6A6: 68, + 0x6A7: 68, + 0x6A8: 68, + 0x6A9: 68, + 0x6AA: 68, + 0x6AB: 68, + 0x6AC: 68, + 0x6AD: 68, + 0x6AE: 68, + 0x6AF: 68, + 0x6B0: 68, + 0x6B1: 68, + 0x6B2: 68, + 0x6B3: 68, + 0x6B4: 68, + 0x6B5: 68, + 0x6B6: 68, + 0x6B7: 68, + 0x6B8: 68, + 0x6B9: 68, + 0x6BA: 68, + 0x6BB: 68, + 0x6BC: 68, + 0x6BD: 68, + 0x6BE: 68, + 0x6BF: 68, + 0x6C0: 82, + 0x6C1: 68, + 0x6C2: 68, + 0x6C3: 82, + 0x6C4: 82, + 0x6C5: 82, + 0x6C6: 82, + 0x6C7: 82, + 0x6C8: 82, + 0x6C9: 82, + 0x6CA: 82, + 0x6CB: 82, + 0x6CC: 68, + 0x6CD: 82, + 0x6CE: 68, + 0x6CF: 82, + 0x6D0: 68, + 0x6D1: 68, + 0x6D2: 82, + 0x6D3: 82, + 0x6D5: 82, + 0x6D6: 84, + 0x6D7: 84, + 0x6D8: 84, + 0x6D9: 84, + 0x6DA: 84, + 0x6DB: 84, + 0x6DC: 84, + 0x6DF: 84, + 0x6E0: 84, + 0x6E1: 84, + 0x6E2: 84, + 0x6E3: 84, + 0x6E4: 84, + 0x6E7: 84, + 0x6E8: 84, + 0x6EA: 84, + 0x6EB: 84, + 0x6EC: 84, + 0x6ED: 84, + 0x6EE: 82, + 0x6EF: 82, + 0x6FA: 68, + 0x6FB: 68, + 0x6FC: 68, + 0x6FF: 68, + 0x70F: 84, + 0x710: 82, + 0x711: 84, + 0x712: 68, + 0x713: 68, + 0x714: 68, + 0x715: 82, + 0x716: 82, + 0x717: 82, + 0x718: 82, + 0x719: 82, + 0x71A: 68, + 0x71B: 68, + 0x71C: 68, + 0x71D: 68, + 0x71E: 82, + 0x71F: 68, + 0x720: 68, + 0x721: 68, + 0x722: 68, + 0x723: 68, + 0x724: 68, + 0x725: 68, + 0x726: 68, + 0x727: 68, + 0x728: 82, + 0x729: 68, + 0x72A: 82, + 0x72B: 68, + 0x72C: 82, + 0x72D: 68, + 0x72E: 68, + 0x72F: 82, + 0x730: 84, + 0x731: 84, + 0x732: 84, + 0x733: 84, + 0x734: 84, + 0x735: 84, + 0x736: 84, + 0x737: 84, + 0x738: 84, + 0x739: 84, + 0x73A: 84, + 0x73B: 84, + 0x73C: 84, + 0x73D: 84, + 0x73E: 84, + 0x73F: 84, + 0x740: 84, + 0x741: 84, + 0x742: 84, + 0x743: 84, + 0x744: 84, + 0x745: 84, + 0x746: 84, + 0x747: 84, + 0x748: 84, + 0x749: 84, + 0x74A: 84, + 0x74D: 82, + 0x74E: 68, + 0x74F: 68, + 0x750: 68, + 0x751: 68, + 0x752: 68, + 0x753: 68, + 0x754: 68, + 0x755: 68, + 0x756: 68, + 0x757: 68, + 0x758: 68, + 0x759: 82, + 0x75A: 82, + 0x75B: 82, + 0x75C: 68, + 0x75D: 68, + 0x75E: 68, + 0x75F: 68, + 0x760: 68, + 0x761: 68, + 0x762: 68, + 0x763: 68, + 0x764: 68, + 0x765: 68, + 0x766: 68, + 0x767: 68, + 0x768: 68, + 0x769: 68, + 0x76A: 68, + 0x76B: 82, + 0x76C: 82, + 0x76D: 68, + 0x76E: 68, + 0x76F: 68, + 0x770: 68, + 0x771: 82, + 0x772: 68, + 0x773: 82, + 0x774: 82, + 0x775: 68, + 0x776: 68, + 0x777: 68, + 0x778: 82, + 0x779: 82, + 0x77A: 68, + 0x77B: 68, + 0x77C: 68, + 0x77D: 68, + 0x77E: 68, + 0x77F: 68, + 0x7A6: 84, + 0x7A7: 84, + 0x7A8: 84, + 0x7A9: 84, + 0x7AA: 84, + 0x7AB: 84, + 0x7AC: 84, + 0x7AD: 84, + 0x7AE: 84, + 0x7AF: 84, + 0x7B0: 84, + 0x7CA: 68, + 0x7CB: 68, + 0x7CC: 68, + 0x7CD: 68, + 0x7CE: 68, + 0x7CF: 68, + 0x7D0: 68, + 0x7D1: 68, + 0x7D2: 68, + 0x7D3: 68, + 0x7D4: 68, + 0x7D5: 68, + 0x7D6: 68, + 0x7D7: 68, + 0x7D8: 68, + 0x7D9: 68, + 0x7DA: 68, + 0x7DB: 68, + 0x7DC: 68, + 0x7DD: 68, + 0x7DE: 68, + 0x7DF: 68, + 0x7E0: 68, + 0x7E1: 68, + 0x7E2: 68, + 0x7E3: 68, + 0x7E4: 68, + 0x7E5: 68, + 0x7E6: 68, + 0x7E7: 68, + 0x7E8: 68, + 0x7E9: 68, + 0x7EA: 68, + 0x7EB: 84, + 0x7EC: 84, + 0x7ED: 84, + 0x7EE: 84, + 0x7EF: 84, + 0x7F0: 84, + 0x7F1: 84, + 0x7F2: 84, + 0x7F3: 84, + 0x7FA: 67, + 0x7FD: 84, + 0x816: 84, + 0x817: 84, + 0x818: 84, + 0x819: 84, + 0x81B: 84, + 0x81C: 84, + 0x81D: 84, + 0x81E: 84, + 0x81F: 84, + 0x820: 84, + 0x821: 84, + 0x822: 84, + 0x823: 84, + 0x825: 84, + 0x826: 84, + 0x827: 84, + 0x829: 84, + 0x82A: 84, + 0x82B: 84, + 0x82C: 84, + 0x82D: 84, + 0x840: 82, + 0x841: 68, + 0x842: 68, + 0x843: 68, + 0x844: 68, + 0x845: 68, + 0x846: 82, + 0x847: 82, + 0x848: 68, + 0x849: 82, + 0x84A: 68, + 0x84B: 68, + 0x84C: 68, + 0x84D: 68, + 0x84E: 68, + 0x84F: 68, + 0x850: 68, + 0x851: 68, + 0x852: 68, + 0x853: 68, + 0x854: 82, + 0x855: 68, + 0x856: 82, + 0x857: 82, + 0x858: 82, + 0x859: 84, + 0x85A: 84, + 0x85B: 84, + 0x860: 68, + 0x862: 68, + 0x863: 68, + 0x864: 68, + 0x865: 68, + 0x867: 82, + 0x868: 68, + 0x869: 82, + 0x86A: 82, + 0x870: 82, + 0x871: 82, + 0x872: 82, + 0x873: 82, + 0x874: 82, + 0x875: 82, + 0x876: 82, + 0x877: 82, + 0x878: 82, + 0x879: 82, + 0x87A: 82, + 0x87B: 82, + 0x87C: 82, + 0x87D: 82, + 0x87E: 82, + 0x87F: 82, + 0x880: 82, + 0x881: 82, + 0x882: 82, + 0x883: 67, + 0x884: 67, + 0x885: 67, + 0x886: 68, + 0x889: 68, + 0x88A: 68, + 0x88B: 68, + 0x88C: 68, + 0x88D: 68, + 0x88E: 82, + 0x88F: 68, + 0x897: 84, + 0x898: 84, + 0x899: 84, + 0x89A: 84, + 0x89B: 84, + 0x89C: 84, + 0x89D: 84, + 0x89E: 84, + 0x89F: 84, + 0x8A0: 68, + 0x8A1: 68, + 0x8A2: 68, + 0x8A3: 68, + 0x8A4: 68, + 0x8A5: 68, + 0x8A6: 68, + 0x8A7: 68, + 0x8A8: 68, + 0x8A9: 68, + 0x8AA: 82, + 0x8AB: 82, + 0x8AC: 82, + 0x8AE: 82, + 0x8AF: 68, + 0x8B0: 68, + 0x8B1: 82, + 0x8B2: 82, + 0x8B3: 68, + 0x8B4: 68, + 0x8B5: 68, + 0x8B6: 68, + 0x8B7: 68, + 0x8B8: 68, + 0x8B9: 82, + 0x8BA: 68, + 0x8BB: 68, + 0x8BC: 68, + 0x8BD: 68, + 0x8BE: 68, + 0x8BF: 68, + 0x8C0: 68, + 0x8C1: 68, + 0x8C2: 68, + 0x8C3: 68, + 0x8C4: 68, + 0x8C5: 68, + 0x8C6: 68, + 0x8C7: 68, + 0x8C8: 68, + 0x8CA: 84, + 0x8CB: 84, + 0x8CC: 84, + 0x8CD: 84, + 0x8CE: 84, + 0x8CF: 84, + 0x8D0: 84, + 0x8D1: 84, + 0x8D2: 84, + 0x8D3: 84, + 0x8D4: 84, + 0x8D5: 84, + 0x8D6: 84, + 0x8D7: 84, + 0x8D8: 84, + 0x8D9: 84, + 0x8DA: 84, + 0x8DB: 84, + 0x8DC: 84, + 0x8DD: 84, + 0x8DE: 84, + 0x8DF: 84, + 0x8E0: 84, + 0x8E1: 84, + 0x8E3: 84, + 0x8E4: 84, + 0x8E5: 84, + 0x8E6: 84, + 0x8E7: 84, + 0x8E8: 84, + 0x8E9: 84, + 0x8EA: 84, + 0x8EB: 84, + 0x8EC: 84, + 0x8ED: 84, + 0x8EE: 84, + 0x8EF: 84, + 0x8F0: 84, + 0x8F1: 84, + 0x8F2: 84, + 0x8F3: 84, + 0x8F4: 84, + 0x8F5: 84, + 0x8F6: 84, + 0x8F7: 84, + 0x8F8: 84, + 0x8F9: 84, + 0x8FA: 84, + 0x8FB: 84, + 0x8FC: 84, + 0x8FD: 84, + 0x8FE: 84, + 0x8FF: 84, + 0x900: 84, + 0x901: 84, + 0x902: 84, + 0x93A: 84, + 0x93C: 84, + 0x941: 84, + 0x942: 84, + 0x943: 84, + 0x944: 84, + 0x945: 84, + 0x946: 84, + 0x947: 84, + 0x948: 84, + 0x94D: 84, + 0x951: 84, + 0x952: 84, + 0x953: 84, + 0x954: 84, + 0x955: 84, + 0x956: 84, + 0x957: 84, + 0x962: 84, + 0x963: 84, + 0x981: 84, + 0x9BC: 84, + 0x9C1: 84, + 0x9C2: 84, + 0x9C3: 84, + 0x9C4: 84, + 0x9CD: 84, + 0x9E2: 84, + 0x9E3: 84, + 0x9FE: 84, + 0xA01: 84, + 0xA02: 84, + 0xA3C: 84, + 0xA41: 84, + 0xA42: 84, + 0xA47: 84, + 0xA48: 84, + 0xA4B: 84, + 0xA4C: 84, + 0xA4D: 84, + 0xA51: 84, + 0xA70: 84, + 0xA71: 84, + 0xA75: 84, + 0xA81: 84, + 0xA82: 84, + 0xABC: 84, + 0xAC1: 84, + 0xAC2: 84, + 0xAC3: 84, + 0xAC4: 84, + 0xAC5: 84, + 0xAC7: 84, + 0xAC8: 84, + 0xACD: 84, + 0xAE2: 84, + 0xAE3: 84, + 0xAFA: 84, + 0xAFB: 84, + 0xAFC: 84, + 0xAFD: 84, + 0xAFE: 84, + 0xAFF: 84, + 0xB01: 84, + 0xB3C: 84, + 0xB3F: 84, + 0xB41: 84, + 0xB42: 84, + 0xB43: 84, + 0xB44: 84, + 0xB4D: 84, + 0xB55: 84, + 0xB56: 84, + 0xB62: 84, + 0xB63: 84, + 0xB82: 84, + 0xBC0: 84, + 0xBCD: 84, + 0xC00: 84, + 0xC04: 84, + 0xC3C: 84, + 0xC3E: 84, + 0xC3F: 84, + 0xC40: 84, + 0xC46: 84, + 0xC47: 84, + 0xC48: 84, + 0xC4A: 84, + 0xC4B: 84, + 0xC4C: 84, + 0xC4D: 84, + 0xC55: 84, + 0xC56: 84, + 0xC62: 84, + 0xC63: 84, + 0xC81: 84, + 0xCBC: 84, + 0xCBF: 84, + 0xCC6: 84, + 0xCCC: 84, + 0xCCD: 84, + 0xCE2: 84, + 0xCE3: 84, + 0xD00: 84, + 0xD01: 84, + 0xD3B: 84, + 0xD3C: 84, + 0xD41: 84, + 0xD42: 84, + 0xD43: 84, + 0xD44: 84, + 0xD4D: 84, + 0xD62: 84, + 0xD63: 84, + 0xD81: 84, + 0xDCA: 84, + 0xDD2: 84, + 0xDD3: 84, + 0xDD4: 84, + 0xDD6: 84, + 0xE31: 84, + 0xE34: 84, + 0xE35: 84, + 0xE36: 84, + 0xE37: 84, + 0xE38: 84, + 0xE39: 84, + 0xE3A: 84, + 0xE47: 84, + 0xE48: 84, + 0xE49: 84, + 0xE4A: 84, + 0xE4B: 84, + 0xE4C: 84, + 0xE4D: 84, + 0xE4E: 84, + 0xEB1: 84, + 0xEB4: 84, + 0xEB5: 84, + 0xEB6: 84, + 0xEB7: 84, + 0xEB8: 84, + 0xEB9: 84, + 0xEBA: 84, + 0xEBB: 84, + 0xEBC: 84, + 0xEC8: 84, + 0xEC9: 84, + 0xECA: 84, + 0xECB: 84, + 0xECC: 84, + 0xECD: 84, + 0xECE: 84, + 0xF18: 84, + 0xF19: 84, + 0xF35: 84, + 0xF37: 84, + 0xF39: 84, + 0xF71: 84, + 0xF72: 84, + 0xF73: 84, + 0xF74: 84, + 0xF75: 84, + 0xF76: 84, + 0xF77: 84, + 0xF78: 84, + 0xF79: 84, + 0xF7A: 84, + 0xF7B: 84, + 0xF7C: 84, + 0xF7D: 84, + 0xF7E: 84, + 0xF80: 84, + 0xF81: 84, + 0xF82: 84, + 0xF83: 84, + 0xF84: 84, + 0xF86: 84, + 0xF87: 84, + 0xF8D: 84, + 0xF8E: 84, + 0xF8F: 84, + 0xF90: 84, + 0xF91: 84, + 0xF92: 84, + 0xF93: 84, + 0xF94: 84, + 0xF95: 84, + 0xF96: 84, + 0xF97: 84, + 0xF99: 84, + 0xF9A: 84, + 0xF9B: 84, + 0xF9C: 84, + 0xF9D: 84, + 0xF9E: 84, + 0xF9F: 84, + 0xFA0: 84, + 0xFA1: 84, + 0xFA2: 84, + 0xFA3: 84, + 0xFA4: 84, + 0xFA5: 84, + 0xFA6: 84, + 0xFA7: 84, + 0xFA8: 84, + 0xFA9: 84, + 0xFAA: 84, + 0xFAB: 84, + 0xFAC: 84, + 0xFAD: 84, + 0xFAE: 84, + 0xFAF: 84, + 0xFB0: 84, + 0xFB1: 84, + 0xFB2: 84, + 0xFB3: 84, + 0xFB4: 84, + 0xFB5: 84, + 0xFB6: 84, + 0xFB7: 84, + 0xFB8: 84, + 0xFB9: 84, + 0xFBA: 84, + 0xFBB: 84, + 0xFBC: 84, + 0xFC6: 84, + 0x102D: 84, + 0x102E: 84, + 0x102F: 84, + 0x1030: 84, + 0x1032: 84, + 0x1033: 84, + 0x1034: 84, + 0x1035: 84, + 0x1036: 84, + 0x1037: 84, + 0x1039: 84, + 0x103A: 84, + 0x103D: 84, + 0x103E: 84, + 0x1058: 84, + 0x1059: 84, + 0x105E: 84, + 0x105F: 84, + 0x1060: 84, + 0x1071: 84, + 0x1072: 84, + 0x1073: 84, + 0x1074: 84, + 0x1082: 84, + 0x1085: 84, + 0x1086: 84, + 0x108D: 84, + 0x109D: 84, + 0x135D: 84, + 0x135E: 84, + 0x135F: 84, + 0x1712: 84, + 0x1713: 84, + 0x1714: 84, + 0x1732: 84, + 0x1733: 84, + 0x1752: 84, + 0x1753: 84, + 0x1772: 84, + 0x1773: 84, + 0x17B4: 84, + 0x17B5: 84, + 0x17B7: 84, + 0x17B8: 84, + 0x17B9: 84, + 0x17BA: 84, + 0x17BB: 84, + 0x17BC: 84, + 0x17BD: 84, + 0x17C6: 84, + 0x17C9: 84, + 0x17CA: 84, + 0x17CB: 84, + 0x17CC: 84, + 0x17CD: 84, + 0x17CE: 84, + 0x17CF: 84, + 0x17D0: 84, + 0x17D1: 84, + 0x17D2: 84, + 0x17D3: 84, + 0x17DD: 84, + 0x1807: 68, + 0x180A: 67, + 0x180B: 84, + 0x180C: 84, + 0x180D: 84, + 0x180F: 84, + 0x1820: 68, + 0x1821: 68, + 0x1822: 68, + 0x1823: 68, + 0x1824: 68, + 0x1825: 68, + 0x1826: 68, + 0x1827: 68, + 0x1828: 68, + 0x1829: 68, + 0x182A: 68, + 0x182B: 68, + 0x182C: 68, + 0x182D: 68, + 0x182E: 68, + 0x182F: 68, + 0x1830: 68, + 0x1831: 68, + 0x1832: 68, + 0x1833: 68, + 0x1834: 68, + 0x1835: 68, + 0x1836: 68, + 0x1837: 68, + 0x1838: 68, + 0x1839: 68, + 0x183A: 68, + 0x183B: 68, + 0x183C: 68, + 0x183D: 68, + 0x183E: 68, + 0x183F: 68, + 0x1840: 68, + 0x1841: 68, + 0x1842: 68, + 0x1843: 68, + 0x1844: 68, + 0x1845: 68, + 0x1846: 68, + 0x1847: 68, + 0x1848: 68, + 0x1849: 68, + 0x184A: 68, + 0x184B: 68, + 0x184C: 68, + 0x184D: 68, + 0x184E: 68, + 0x184F: 68, + 0x1850: 68, + 0x1851: 68, + 0x1852: 68, + 0x1853: 68, + 0x1854: 68, + 0x1855: 68, + 0x1856: 68, + 0x1857: 68, + 0x1858: 68, + 0x1859: 68, + 0x185A: 68, + 0x185B: 68, + 0x185C: 68, + 0x185D: 68, + 0x185E: 68, + 0x185F: 68, + 0x1860: 68, + 0x1861: 68, + 0x1862: 68, + 0x1863: 68, + 0x1864: 68, + 0x1865: 68, + 0x1866: 68, + 0x1867: 68, + 0x1868: 68, + 0x1869: 68, + 0x186A: 68, + 0x186B: 68, + 0x186C: 68, + 0x186D: 68, + 0x186E: 68, + 0x186F: 68, + 0x1870: 68, + 0x1871: 68, + 0x1872: 68, + 0x1873: 68, + 0x1874: 68, + 0x1875: 68, + 0x1876: 68, + 0x1877: 68, + 0x1878: 68, + 0x1885: 84, + 0x1886: 84, + 0x1887: 68, + 0x1888: 68, + 0x1889: 68, + 0x188A: 68, + 0x188B: 68, + 0x188C: 68, + 0x188D: 68, + 0x188E: 68, + 0x188F: 68, + 0x1890: 68, + 0x1891: 68, + 0x1892: 68, + 0x1893: 68, + 0x1894: 68, + 0x1895: 68, + 0x1896: 68, + 0x1897: 68, + 0x1898: 68, + 0x1899: 68, + 0x189A: 68, + 0x189B: 68, + 0x189C: 68, + 0x189D: 68, + 0x189E: 68, + 0x189F: 68, + 0x18A0: 68, + 0x18A1: 68, + 0x18A2: 68, + 0x18A3: 68, + 0x18A4: 68, + 0x18A5: 68, + 0x18A6: 68, + 0x18A7: 68, + 0x18A8: 68, + 0x18A9: 84, + 0x18AA: 68, + 0x1920: 84, + 0x1921: 84, + 0x1922: 84, + 0x1927: 84, + 0x1928: 84, + 0x1932: 84, + 0x1939: 84, + 0x193A: 84, + 0x193B: 84, + 0x1A17: 84, + 0x1A18: 84, + 0x1A1B: 84, + 0x1A56: 84, + 0x1A58: 84, + 0x1A59: 84, + 0x1A5A: 84, + 0x1A5B: 84, + 0x1A5C: 84, + 0x1A5D: 84, + 0x1A5E: 84, + 0x1A60: 84, + 0x1A62: 84, + 0x1A65: 84, + 0x1A66: 84, + 0x1A67: 84, + 0x1A68: 84, + 0x1A69: 84, + 0x1A6A: 84, + 0x1A6B: 84, + 0x1A6C: 84, + 0x1A73: 84, + 0x1A74: 84, + 0x1A75: 84, + 0x1A76: 84, + 0x1A77: 84, + 0x1A78: 84, + 0x1A79: 84, + 0x1A7A: 84, + 0x1A7B: 84, + 0x1A7C: 84, + 0x1A7F: 84, + 0x1AB0: 84, + 0x1AB1: 84, + 0x1AB2: 84, + 0x1AB3: 84, + 0x1AB4: 84, + 0x1AB5: 84, + 0x1AB6: 84, + 0x1AB7: 84, + 0x1AB8: 84, + 0x1AB9: 84, + 0x1ABA: 84, + 0x1ABB: 84, + 0x1ABC: 84, + 0x1ABD: 84, + 0x1ABE: 84, + 0x1ABF: 84, + 0x1AC0: 84, + 0x1AC1: 84, + 0x1AC2: 84, + 0x1AC3: 84, + 0x1AC4: 84, + 0x1AC5: 84, + 0x1AC6: 84, + 0x1AC7: 84, + 0x1AC8: 84, + 0x1AC9: 84, + 0x1ACA: 84, + 0x1ACB: 84, + 0x1ACC: 84, + 0x1ACD: 84, + 0x1ACE: 84, + 0x1ACF: 84, + 0x1AD0: 84, + 0x1AD1: 84, + 0x1AD2: 84, + 0x1AD3: 84, + 0x1AD4: 84, + 0x1AD5: 84, + 0x1AD6: 84, + 0x1AD7: 84, + 0x1AD8: 84, + 0x1AD9: 84, + 0x1ADA: 84, + 0x1ADB: 84, + 0x1ADC: 84, + 0x1ADD: 84, + 0x1AE0: 84, + 0x1AE1: 84, + 0x1AE2: 84, + 0x1AE3: 84, + 0x1AE4: 84, + 0x1AE5: 84, + 0x1AE6: 84, + 0x1AE7: 84, + 0x1AE8: 84, + 0x1AE9: 84, + 0x1AEA: 84, + 0x1AEB: 84, + 0x1B00: 84, + 0x1B01: 84, + 0x1B02: 84, + 0x1B03: 84, + 0x1B34: 84, + 0x1B36: 84, + 0x1B37: 84, + 0x1B38: 84, + 0x1B39: 84, + 0x1B3A: 84, + 0x1B3C: 84, + 0x1B42: 84, + 0x1B6B: 84, + 0x1B6C: 84, + 0x1B6D: 84, + 0x1B6E: 84, + 0x1B6F: 84, + 0x1B70: 84, + 0x1B71: 84, + 0x1B72: 84, + 0x1B73: 84, + 0x1B80: 84, + 0x1B81: 84, + 0x1BA2: 84, + 0x1BA3: 84, + 0x1BA4: 84, + 0x1BA5: 84, + 0x1BA8: 84, + 0x1BA9: 84, + 0x1BAB: 84, + 0x1BAC: 84, + 0x1BAD: 84, + 0x1BE6: 84, + 0x1BE8: 84, + 0x1BE9: 84, + 0x1BED: 84, + 0x1BEF: 84, + 0x1BF0: 84, + 0x1BF1: 84, + 0x1C2C: 84, + 0x1C2D: 84, + 0x1C2E: 84, + 0x1C2F: 84, + 0x1C30: 84, + 0x1C31: 84, + 0x1C32: 84, + 0x1C33: 84, + 0x1C36: 84, + 0x1C37: 84, + 0x1CD0: 84, + 0x1CD1: 84, + 0x1CD2: 84, + 0x1CD4: 84, + 0x1CD5: 84, + 0x1CD6: 84, + 0x1CD7: 84, + 0x1CD8: 84, + 0x1CD9: 84, + 0x1CDA: 84, + 0x1CDB: 84, + 0x1CDC: 84, + 0x1CDD: 84, + 0x1CDE: 84, + 0x1CDF: 84, + 0x1CE0: 84, + 0x1CE2: 84, + 0x1CE3: 84, + 0x1CE4: 84, + 0x1CE5: 84, + 0x1CE6: 84, + 0x1CE7: 84, + 0x1CE8: 84, + 0x1CED: 84, + 0x1CF4: 84, + 0x1CF8: 84, + 0x1CF9: 84, + 0x1DC0: 84, + 0x1DC1: 84, + 0x1DC2: 84, + 0x1DC3: 84, + 0x1DC4: 84, + 0x1DC5: 84, + 0x1DC6: 84, + 0x1DC7: 84, + 0x1DC8: 84, + 0x1DC9: 84, + 0x1DCA: 84, + 0x1DCB: 84, + 0x1DCC: 84, + 0x1DCD: 84, + 0x1DCE: 84, + 0x1DCF: 84, + 0x1DD0: 84, + 0x1DD1: 84, + 0x1DD2: 84, + 0x1DD3: 84, + 0x1DD4: 84, + 0x1DD5: 84, + 0x1DD6: 84, + 0x1DD7: 84, + 0x1DD8: 84, + 0x1DD9: 84, + 0x1DDA: 84, + 0x1DDB: 84, + 0x1DDC: 84, + 0x1DDD: 84, + 0x1DDE: 84, + 0x1DDF: 84, + 0x1DE0: 84, + 0x1DE1: 84, + 0x1DE2: 84, + 0x1DE3: 84, + 0x1DE4: 84, + 0x1DE5: 84, + 0x1DE6: 84, + 0x1DE7: 84, + 0x1DE8: 84, + 0x1DE9: 84, + 0x1DEA: 84, + 0x1DEB: 84, + 0x1DEC: 84, + 0x1DED: 84, + 0x1DEE: 84, + 0x1DEF: 84, + 0x1DF0: 84, + 0x1DF1: 84, + 0x1DF2: 84, + 0x1DF3: 84, + 0x1DF4: 84, + 0x1DF5: 84, + 0x1DF6: 84, + 0x1DF7: 84, + 0x1DF8: 84, + 0x1DF9: 84, + 0x1DFA: 84, + 0x1DFB: 84, + 0x1DFC: 84, + 0x1DFD: 84, + 0x1DFE: 84, + 0x1DFF: 84, + 0x200B: 84, + 0x200D: 67, + 0x200E: 84, + 0x200F: 84, + 0x202A: 84, + 0x202B: 84, + 0x202C: 84, + 0x202D: 84, + 0x202E: 84, + 0x2060: 84, + 0x2061: 84, + 0x2062: 84, + 0x2063: 84, + 0x2064: 84, + 0x206A: 84, + 0x206B: 84, + 0x206C: 84, + 0x206D: 84, + 0x206E: 84, + 0x206F: 84, + 0x20D0: 84, + 0x20D1: 84, + 0x20D2: 84, + 0x20D3: 84, + 0x20D4: 84, + 0x20D5: 84, + 0x20D6: 84, + 0x20D7: 84, + 0x20D8: 84, + 0x20D9: 84, + 0x20DA: 84, + 0x20DB: 84, + 0x20DC: 84, + 0x20DD: 84, + 0x20DE: 84, + 0x20DF: 84, + 0x20E0: 84, + 0x20E1: 84, + 0x20E2: 84, + 0x20E3: 84, + 0x20E4: 84, + 0x20E5: 84, + 0x20E6: 84, + 0x20E7: 84, + 0x20E8: 84, + 0x20E9: 84, + 0x20EA: 84, + 0x20EB: 84, + 0x20EC: 84, + 0x20ED: 84, + 0x20EE: 84, + 0x20EF: 84, + 0x20F0: 84, + 0x2CEF: 84, + 0x2CF0: 84, + 0x2CF1: 84, + 0x2D7F: 84, + 0x2DE0: 84, + 0x2DE1: 84, + 0x2DE2: 84, + 0x2DE3: 84, + 0x2DE4: 84, + 0x2DE5: 84, + 0x2DE6: 84, + 0x2DE7: 84, + 0x2DE8: 84, + 0x2DE9: 84, + 0x2DEA: 84, + 0x2DEB: 84, + 0x2DEC: 84, + 0x2DED: 84, + 0x2DEE: 84, + 0x2DEF: 84, + 0x2DF0: 84, + 0x2DF1: 84, + 0x2DF2: 84, + 0x2DF3: 84, + 0x2DF4: 84, + 0x2DF5: 84, + 0x2DF6: 84, + 0x2DF7: 84, + 0x2DF8: 84, + 0x2DF9: 84, + 0x2DFA: 84, + 0x2DFB: 84, + 0x2DFC: 84, + 0x2DFD: 84, + 0x2DFE: 84, + 0x2DFF: 84, + 0x302A: 84, + 0x302B: 84, + 0x302C: 84, + 0x302D: 84, + 0x3099: 84, + 0x309A: 84, + 0xA66F: 84, + 0xA670: 84, + 0xA671: 84, + 0xA672: 84, + 0xA674: 84, + 0xA675: 84, + 0xA676: 84, + 0xA677: 84, + 0xA678: 84, + 0xA679: 84, + 0xA67A: 84, + 0xA67B: 84, + 0xA67C: 84, + 0xA67D: 84, + 0xA69E: 84, + 0xA69F: 84, + 0xA6F0: 84, + 0xA6F1: 84, + 0xA802: 84, + 0xA806: 84, + 0xA80B: 84, + 0xA825: 84, + 0xA826: 84, + 0xA82C: 84, + 0xA840: 68, + 0xA841: 68, + 0xA842: 68, + 0xA843: 68, + 0xA844: 68, + 0xA845: 68, + 0xA846: 68, + 0xA847: 68, + 0xA848: 68, + 0xA849: 68, + 0xA84A: 68, + 0xA84B: 68, + 0xA84C: 68, + 0xA84D: 68, + 0xA84E: 68, + 0xA84F: 68, + 0xA850: 68, + 0xA851: 68, + 0xA852: 68, + 0xA853: 68, + 0xA854: 68, + 0xA855: 68, + 0xA856: 68, + 0xA857: 68, + 0xA858: 68, + 0xA859: 68, + 0xA85A: 68, + 0xA85B: 68, + 0xA85C: 68, + 0xA85D: 68, + 0xA85E: 68, + 0xA85F: 68, + 0xA860: 68, + 0xA861: 68, + 0xA862: 68, + 0xA863: 68, + 0xA864: 68, + 0xA865: 68, + 0xA866: 68, + 0xA867: 68, + 0xA868: 68, + 0xA869: 68, + 0xA86A: 68, + 0xA86B: 68, + 0xA86C: 68, + 0xA86D: 68, + 0xA86E: 68, + 0xA86F: 68, + 0xA870: 68, + 0xA871: 68, + 0xA872: 76, + 0xA8C4: 84, + 0xA8C5: 84, + 0xA8E0: 84, + 0xA8E1: 84, + 0xA8E2: 84, + 0xA8E3: 84, + 0xA8E4: 84, + 0xA8E5: 84, + 0xA8E6: 84, + 0xA8E7: 84, + 0xA8E8: 84, + 0xA8E9: 84, + 0xA8EA: 84, + 0xA8EB: 84, + 0xA8EC: 84, + 0xA8ED: 84, + 0xA8EE: 84, + 0xA8EF: 84, + 0xA8F0: 84, + 0xA8F1: 84, + 0xA8FF: 84, + 0xA926: 84, + 0xA927: 84, + 0xA928: 84, + 0xA929: 84, + 0xA92A: 84, + 0xA92B: 84, + 0xA92C: 84, + 0xA92D: 84, + 0xA947: 84, + 0xA948: 84, + 0xA949: 84, + 0xA94A: 84, + 0xA94B: 84, + 0xA94C: 84, + 0xA94D: 84, + 0xA94E: 84, + 0xA94F: 84, + 0xA950: 84, + 0xA951: 84, + 0xA980: 84, + 0xA981: 84, + 0xA982: 84, + 0xA9B3: 84, + 0xA9B6: 84, + 0xA9B7: 84, + 0xA9B8: 84, + 0xA9B9: 84, + 0xA9BC: 84, + 0xA9BD: 84, + 0xA9E5: 84, + 0xAA29: 84, + 0xAA2A: 84, + 0xAA2B: 84, + 0xAA2C: 84, + 0xAA2D: 84, + 0xAA2E: 84, + 0xAA31: 84, + 0xAA32: 84, + 0xAA35: 84, + 0xAA36: 84, + 0xAA43: 84, + 0xAA4C: 84, + 0xAA7C: 84, + 0xAAB0: 84, + 0xAAB2: 84, + 0xAAB3: 84, + 0xAAB4: 84, + 0xAAB7: 84, + 0xAAB8: 84, + 0xAABE: 84, + 0xAABF: 84, + 0xAAC1: 84, + 0xAAEC: 84, + 0xAAED: 84, + 0xAAF6: 84, + 0xABE5: 84, + 0xABE8: 84, + 0xABED: 84, + 0xFB1E: 84, + 0xFE00: 84, + 0xFE01: 84, + 0xFE02: 84, + 0xFE03: 84, + 0xFE04: 84, + 0xFE05: 84, + 0xFE06: 84, + 0xFE07: 84, + 0xFE08: 84, + 0xFE09: 84, + 0xFE0A: 84, + 0xFE0B: 84, + 0xFE0C: 84, + 0xFE0D: 84, + 0xFE0E: 84, + 0xFE0F: 84, + 0xFE20: 84, + 0xFE21: 84, + 0xFE22: 84, + 0xFE23: 84, + 0xFE24: 84, + 0xFE25: 84, + 0xFE26: 84, + 0xFE27: 84, + 0xFE28: 84, + 0xFE29: 84, + 0xFE2A: 84, + 0xFE2B: 84, + 0xFE2C: 84, + 0xFE2D: 84, + 0xFE2E: 84, + 0xFE2F: 84, + 0xFEFF: 84, + 0xFFF9: 84, + 0xFFFA: 84, + 0xFFFB: 84, + 0x101FD: 84, + 0x102E0: 84, + 0x10376: 84, + 0x10377: 84, + 0x10378: 84, + 0x10379: 84, + 0x1037A: 84, + 0x10A01: 84, + 0x10A02: 84, + 0x10A03: 84, + 0x10A05: 84, + 0x10A06: 84, + 0x10A0C: 84, + 0x10A0D: 84, + 0x10A0E: 84, + 0x10A0F: 84, + 0x10A38: 84, + 0x10A39: 84, + 0x10A3A: 84, + 0x10A3F: 84, + 0x10AC0: 68, + 0x10AC1: 68, + 0x10AC2: 68, + 0x10AC3: 68, + 0x10AC4: 68, + 0x10AC5: 82, + 0x10AC7: 82, + 0x10AC9: 82, + 0x10ACA: 82, + 0x10ACD: 76, + 0x10ACE: 82, + 0x10ACF: 82, + 0x10AD0: 82, + 0x10AD1: 82, + 0x10AD2: 82, + 0x10AD3: 68, + 0x10AD4: 68, + 0x10AD5: 68, + 0x10AD6: 68, + 0x10AD7: 76, + 0x10AD8: 68, + 0x10AD9: 68, + 0x10ADA: 68, + 0x10ADB: 68, + 0x10ADC: 68, + 0x10ADD: 82, + 0x10ADE: 68, + 0x10ADF: 68, + 0x10AE0: 68, + 0x10AE1: 82, + 0x10AE4: 82, + 0x10AE5: 84, + 0x10AE6: 84, + 0x10AEB: 68, + 0x10AEC: 68, + 0x10AED: 68, + 0x10AEE: 68, + 0x10AEF: 82, + 0x10B80: 68, + 0x10B81: 82, + 0x10B82: 68, + 0x10B83: 82, + 0x10B84: 82, + 0x10B85: 82, + 0x10B86: 68, + 0x10B87: 68, + 0x10B88: 68, + 0x10B89: 82, + 0x10B8A: 68, + 0x10B8B: 68, + 0x10B8C: 82, + 0x10B8D: 68, + 0x10B8E: 82, + 0x10B8F: 82, + 0x10B90: 68, + 0x10B91: 82, + 0x10BA9: 82, + 0x10BAA: 82, + 0x10BAB: 82, + 0x10BAC: 82, + 0x10BAD: 68, + 0x10BAE: 68, + 0x10D00: 76, + 0x10D01: 68, + 0x10D02: 68, + 0x10D03: 68, + 0x10D04: 68, + 0x10D05: 68, + 0x10D06: 68, + 0x10D07: 68, + 0x10D08: 68, + 0x10D09: 68, + 0x10D0A: 68, + 0x10D0B: 68, + 0x10D0C: 68, + 0x10D0D: 68, + 0x10D0E: 68, + 0x10D0F: 68, + 0x10D10: 68, + 0x10D11: 68, + 0x10D12: 68, + 0x10D13: 68, + 0x10D14: 68, + 0x10D15: 68, + 0x10D16: 68, + 0x10D17: 68, + 0x10D18: 68, + 0x10D19: 68, + 0x10D1A: 68, + 0x10D1B: 68, + 0x10D1C: 68, + 0x10D1D: 68, + 0x10D1E: 68, + 0x10D1F: 68, + 0x10D20: 68, + 0x10D21: 68, + 0x10D22: 82, + 0x10D23: 68, + 0x10D24: 84, + 0x10D25: 84, + 0x10D26: 84, + 0x10D27: 84, + 0x10D69: 84, + 0x10D6A: 84, + 0x10D6B: 84, + 0x10D6C: 84, + 0x10D6D: 84, + 0x10EAB: 84, + 0x10EAC: 84, + 0x10EC2: 82, + 0x10EC3: 68, + 0x10EC4: 68, + 0x10EC6: 68, + 0x10EC7: 68, + 0x10EFA: 84, + 0x10EFB: 84, + 0x10EFC: 84, + 0x10EFD: 84, + 0x10EFE: 84, + 0x10EFF: 84, + 0x10F30: 68, + 0x10F31: 68, + 0x10F32: 68, + 0x10F33: 82, + 0x10F34: 68, + 0x10F35: 68, + 0x10F36: 68, + 0x10F37: 68, + 0x10F38: 68, + 0x10F39: 68, + 0x10F3A: 68, + 0x10F3B: 68, + 0x10F3C: 68, + 0x10F3D: 68, + 0x10F3E: 68, + 0x10F3F: 68, + 0x10F40: 68, + 0x10F41: 68, + 0x10F42: 68, + 0x10F43: 68, + 0x10F44: 68, + 0x10F46: 84, + 0x10F47: 84, + 0x10F48: 84, + 0x10F49: 84, + 0x10F4A: 84, + 0x10F4B: 84, + 0x10F4C: 84, + 0x10F4D: 84, + 0x10F4E: 84, + 0x10F4F: 84, + 0x10F50: 84, + 0x10F51: 68, + 0x10F52: 68, + 0x10F53: 68, + 0x10F54: 82, + 0x10F70: 68, + 0x10F71: 68, + 0x10F72: 68, + 0x10F73: 68, + 0x10F74: 82, + 0x10F75: 82, + 0x10F76: 68, + 0x10F77: 68, + 0x10F78: 68, + 0x10F79: 68, + 0x10F7A: 68, + 0x10F7B: 68, + 0x10F7C: 68, + 0x10F7D: 68, + 0x10F7E: 68, + 0x10F7F: 68, + 0x10F80: 68, + 0x10F81: 68, + 0x10F82: 84, + 0x10F83: 84, + 0x10F84: 84, + 0x10F85: 84, + 0x10FB0: 68, + 0x10FB2: 68, + 0x10FB3: 68, + 0x10FB4: 82, + 0x10FB5: 82, + 0x10FB6: 82, + 0x10FB8: 68, + 0x10FB9: 82, + 0x10FBA: 82, + 0x10FBB: 68, + 0x10FBC: 68, + 0x10FBD: 82, + 0x10FBE: 68, + 0x10FBF: 68, + 0x10FC1: 68, + 0x10FC2: 82, + 0x10FC3: 82, + 0x10FC4: 68, + 0x10FC9: 82, + 0x10FCA: 68, + 0x10FCB: 76, + 0x11001: 84, + 0x11038: 84, + 0x11039: 84, + 0x1103A: 84, + 0x1103B: 84, + 0x1103C: 84, + 0x1103D: 84, + 0x1103E: 84, + 0x1103F: 84, + 0x11040: 84, + 0x11041: 84, + 0x11042: 84, + 0x11043: 84, + 0x11044: 84, + 0x11045: 84, + 0x11046: 84, + 0x11070: 84, + 0x11073: 84, + 0x11074: 84, + 0x1107F: 84, + 0x11080: 84, + 0x11081: 84, + 0x110B3: 84, + 0x110B4: 84, + 0x110B5: 84, + 0x110B6: 84, + 0x110B9: 84, + 0x110BA: 84, + 0x110C2: 84, + 0x11100: 84, + 0x11101: 84, + 0x11102: 84, + 0x11127: 84, + 0x11128: 84, + 0x11129: 84, + 0x1112A: 84, + 0x1112B: 84, + 0x1112D: 84, + 0x1112E: 84, + 0x1112F: 84, + 0x11130: 84, + 0x11131: 84, + 0x11132: 84, + 0x11133: 84, + 0x11134: 84, + 0x11173: 84, + 0x11180: 84, + 0x11181: 84, + 0x111B6: 84, + 0x111B7: 84, + 0x111B8: 84, + 0x111B9: 84, + 0x111BA: 84, + 0x111BB: 84, + 0x111BC: 84, + 0x111BD: 84, + 0x111BE: 84, + 0x111C9: 84, + 0x111CA: 84, + 0x111CB: 84, + 0x111CC: 84, + 0x111CF: 84, + 0x1122F: 84, + 0x11230: 84, + 0x11231: 84, + 0x11234: 84, + 0x11236: 84, + 0x11237: 84, + 0x1123E: 84, + 0x11241: 84, + 0x112DF: 84, + 0x112E3: 84, + 0x112E4: 84, + 0x112E5: 84, + 0x112E6: 84, + 0x112E7: 84, + 0x112E8: 84, + 0x112E9: 84, + 0x112EA: 84, + 0x11300: 84, + 0x11301: 84, + 0x1133B: 84, + 0x1133C: 84, + 0x11340: 84, + 0x11366: 84, + 0x11367: 84, + 0x11368: 84, + 0x11369: 84, + 0x1136A: 84, + 0x1136B: 84, + 0x1136C: 84, + 0x11370: 84, + 0x11371: 84, + 0x11372: 84, + 0x11373: 84, + 0x11374: 84, + 0x113BB: 84, + 0x113BC: 84, + 0x113BD: 84, + 0x113BE: 84, + 0x113BF: 84, + 0x113C0: 84, + 0x113CE: 84, + 0x113D0: 84, + 0x113D2: 84, + 0x113E1: 84, + 0x113E2: 84, + 0x11438: 84, + 0x11439: 84, + 0x1143A: 84, + 0x1143B: 84, + 0x1143C: 84, + 0x1143D: 84, + 0x1143E: 84, + 0x1143F: 84, + 0x11442: 84, + 0x11443: 84, + 0x11444: 84, + 0x11446: 84, + 0x1145E: 84, + 0x114B3: 84, + 0x114B4: 84, + 0x114B5: 84, + 0x114B6: 84, + 0x114B7: 84, + 0x114B8: 84, + 0x114BA: 84, + 0x114BF: 84, + 0x114C0: 84, + 0x114C2: 84, + 0x114C3: 84, + 0x115B2: 84, + 0x115B3: 84, + 0x115B4: 84, + 0x115B5: 84, + 0x115BC: 84, + 0x115BD: 84, + 0x115BF: 84, + 0x115C0: 84, + 0x115DC: 84, + 0x115DD: 84, + 0x11633: 84, + 0x11634: 84, + 0x11635: 84, + 0x11636: 84, + 0x11637: 84, + 0x11638: 84, + 0x11639: 84, + 0x1163A: 84, + 0x1163D: 84, + 0x1163F: 84, + 0x11640: 84, + 0x116AB: 84, + 0x116AD: 84, + 0x116B0: 84, + 0x116B1: 84, + 0x116B2: 84, + 0x116B3: 84, + 0x116B4: 84, + 0x116B5: 84, + 0x116B7: 84, + 0x1171D: 84, + 0x1171F: 84, + 0x11722: 84, + 0x11723: 84, + 0x11724: 84, + 0x11725: 84, + 0x11727: 84, + 0x11728: 84, + 0x11729: 84, + 0x1172A: 84, + 0x1172B: 84, + 0x1182F: 84, + 0x11830: 84, + 0x11831: 84, + 0x11832: 84, + 0x11833: 84, + 0x11834: 84, + 0x11835: 84, + 0x11836: 84, + 0x11837: 84, + 0x11839: 84, + 0x1183A: 84, + 0x1193B: 84, + 0x1193C: 84, + 0x1193E: 84, + 0x11943: 84, + 0x119D4: 84, + 0x119D5: 84, + 0x119D6: 84, + 0x119D7: 84, + 0x119DA: 84, + 0x119DB: 84, + 0x119E0: 84, + 0x11A01: 84, + 0x11A02: 84, + 0x11A03: 84, + 0x11A04: 84, + 0x11A05: 84, + 0x11A06: 84, + 0x11A07: 84, + 0x11A08: 84, + 0x11A09: 84, + 0x11A0A: 84, + 0x11A33: 84, + 0x11A34: 84, + 0x11A35: 84, + 0x11A36: 84, + 0x11A37: 84, + 0x11A38: 84, + 0x11A3B: 84, + 0x11A3C: 84, + 0x11A3D: 84, + 0x11A3E: 84, + 0x11A47: 84, + 0x11A51: 84, + 0x11A52: 84, + 0x11A53: 84, + 0x11A54: 84, + 0x11A55: 84, + 0x11A56: 84, + 0x11A59: 84, + 0x11A5A: 84, + 0x11A5B: 84, + 0x11A8A: 84, + 0x11A8B: 84, + 0x11A8C: 84, + 0x11A8D: 84, + 0x11A8E: 84, + 0x11A8F: 84, + 0x11A90: 84, + 0x11A91: 84, + 0x11A92: 84, + 0x11A93: 84, + 0x11A94: 84, + 0x11A95: 84, + 0x11A96: 84, + 0x11A98: 84, + 0x11A99: 84, + 0x11B60: 84, + 0x11B62: 84, + 0x11B63: 84, + 0x11B64: 84, + 0x11B66: 84, + 0x11C30: 84, + 0x11C31: 84, + 0x11C32: 84, + 0x11C33: 84, + 0x11C34: 84, + 0x11C35: 84, + 0x11C36: 84, + 0x11C38: 84, + 0x11C39: 84, + 0x11C3A: 84, + 0x11C3B: 84, + 0x11C3C: 84, + 0x11C3D: 84, + 0x11C3F: 84, + 0x11C92: 84, + 0x11C93: 84, + 0x11C94: 84, + 0x11C95: 84, + 0x11C96: 84, + 0x11C97: 84, + 0x11C98: 84, + 0x11C99: 84, + 0x11C9A: 84, + 0x11C9B: 84, + 0x11C9C: 84, + 0x11C9D: 84, + 0x11C9E: 84, + 0x11C9F: 84, + 0x11CA0: 84, + 0x11CA1: 84, + 0x11CA2: 84, + 0x11CA3: 84, + 0x11CA4: 84, + 0x11CA5: 84, + 0x11CA6: 84, + 0x11CA7: 84, + 0x11CAA: 84, + 0x11CAB: 84, + 0x11CAC: 84, + 0x11CAD: 84, + 0x11CAE: 84, + 0x11CAF: 84, + 0x11CB0: 84, + 0x11CB2: 84, + 0x11CB3: 84, + 0x11CB5: 84, + 0x11CB6: 84, + 0x11D31: 84, + 0x11D32: 84, + 0x11D33: 84, + 0x11D34: 84, + 0x11D35: 84, + 0x11D36: 84, + 0x11D3A: 84, + 0x11D3C: 84, + 0x11D3D: 84, + 0x11D3F: 84, + 0x11D40: 84, + 0x11D41: 84, + 0x11D42: 84, + 0x11D43: 84, + 0x11D44: 84, + 0x11D45: 84, + 0x11D47: 84, + 0x11D90: 84, + 0x11D91: 84, + 0x11D95: 84, + 0x11D97: 84, + 0x11EF3: 84, + 0x11EF4: 84, + 0x11F00: 84, + 0x11F01: 84, + 0x11F36: 84, + 0x11F37: 84, + 0x11F38: 84, + 0x11F39: 84, + 0x11F3A: 84, + 0x11F40: 84, + 0x11F42: 84, + 0x11F5A: 84, + 0x13430: 84, + 0x13431: 84, + 0x13432: 84, + 0x13433: 84, + 0x13434: 84, + 0x13435: 84, + 0x13436: 84, + 0x13437: 84, + 0x13438: 84, + 0x13439: 84, + 0x1343A: 84, + 0x1343B: 84, + 0x1343C: 84, + 0x1343D: 84, + 0x1343E: 84, + 0x1343F: 84, + 0x13440: 84, + 0x13447: 84, + 0x13448: 84, + 0x13449: 84, + 0x1344A: 84, + 0x1344B: 84, + 0x1344C: 84, + 0x1344D: 84, + 0x1344E: 84, + 0x1344F: 84, + 0x13450: 84, + 0x13451: 84, + 0x13452: 84, + 0x13453: 84, + 0x13454: 84, + 0x13455: 84, + 0x1611E: 84, + 0x1611F: 84, + 0x16120: 84, + 0x16121: 84, + 0x16122: 84, + 0x16123: 84, + 0x16124: 84, + 0x16125: 84, + 0x16126: 84, + 0x16127: 84, + 0x16128: 84, + 0x16129: 84, + 0x1612D: 84, + 0x1612E: 84, + 0x1612F: 84, + 0x16AF0: 84, + 0x16AF1: 84, + 0x16AF2: 84, + 0x16AF3: 84, + 0x16AF4: 84, + 0x16B30: 84, + 0x16B31: 84, + 0x16B32: 84, + 0x16B33: 84, + 0x16B34: 84, + 0x16B35: 84, + 0x16B36: 84, + 0x16F4F: 84, + 0x16F8F: 84, + 0x16F90: 84, + 0x16F91: 84, + 0x16F92: 84, + 0x16FE4: 84, + 0x1BC9D: 84, + 0x1BC9E: 84, + 0x1BCA0: 84, + 0x1BCA1: 84, + 0x1BCA2: 84, + 0x1BCA3: 84, + 0x1CF00: 84, + 0x1CF01: 84, + 0x1CF02: 84, + 0x1CF03: 84, + 0x1CF04: 84, + 0x1CF05: 84, + 0x1CF06: 84, + 0x1CF07: 84, + 0x1CF08: 84, + 0x1CF09: 84, + 0x1CF0A: 84, + 0x1CF0B: 84, + 0x1CF0C: 84, + 0x1CF0D: 84, + 0x1CF0E: 84, + 0x1CF0F: 84, + 0x1CF10: 84, + 0x1CF11: 84, + 0x1CF12: 84, + 0x1CF13: 84, + 0x1CF14: 84, + 0x1CF15: 84, + 0x1CF16: 84, + 0x1CF17: 84, + 0x1CF18: 84, + 0x1CF19: 84, + 0x1CF1A: 84, + 0x1CF1B: 84, + 0x1CF1C: 84, + 0x1CF1D: 84, + 0x1CF1E: 84, + 0x1CF1F: 84, + 0x1CF20: 84, + 0x1CF21: 84, + 0x1CF22: 84, + 0x1CF23: 84, + 0x1CF24: 84, + 0x1CF25: 84, + 0x1CF26: 84, + 0x1CF27: 84, + 0x1CF28: 84, + 0x1CF29: 84, + 0x1CF2A: 84, + 0x1CF2B: 84, + 0x1CF2C: 84, + 0x1CF2D: 84, + 0x1CF30: 84, + 0x1CF31: 84, + 0x1CF32: 84, + 0x1CF33: 84, + 0x1CF34: 84, + 0x1CF35: 84, + 0x1CF36: 84, + 0x1CF37: 84, + 0x1CF38: 84, + 0x1CF39: 84, + 0x1CF3A: 84, + 0x1CF3B: 84, + 0x1CF3C: 84, + 0x1CF3D: 84, + 0x1CF3E: 84, + 0x1CF3F: 84, + 0x1CF40: 84, + 0x1CF41: 84, + 0x1CF42: 84, + 0x1CF43: 84, + 0x1CF44: 84, + 0x1CF45: 84, + 0x1CF46: 84, + 0x1D167: 84, + 0x1D168: 84, + 0x1D169: 84, + 0x1D173: 84, + 0x1D174: 84, + 0x1D175: 84, + 0x1D176: 84, + 0x1D177: 84, + 0x1D178: 84, + 0x1D179: 84, + 0x1D17A: 84, + 0x1D17B: 84, + 0x1D17C: 84, + 0x1D17D: 84, + 0x1D17E: 84, + 0x1D17F: 84, + 0x1D180: 84, + 0x1D181: 84, + 0x1D182: 84, + 0x1D185: 84, + 0x1D186: 84, + 0x1D187: 84, + 0x1D188: 84, + 0x1D189: 84, + 0x1D18A: 84, + 0x1D18B: 84, + 0x1D1AA: 84, + 0x1D1AB: 84, + 0x1D1AC: 84, + 0x1D1AD: 84, + 0x1D242: 84, + 0x1D243: 84, + 0x1D244: 84, + 0x1DA00: 84, + 0x1DA01: 84, + 0x1DA02: 84, + 0x1DA03: 84, + 0x1DA04: 84, + 0x1DA05: 84, + 0x1DA06: 84, + 0x1DA07: 84, + 0x1DA08: 84, + 0x1DA09: 84, + 0x1DA0A: 84, + 0x1DA0B: 84, + 0x1DA0C: 84, + 0x1DA0D: 84, + 0x1DA0E: 84, + 0x1DA0F: 84, + 0x1DA10: 84, + 0x1DA11: 84, + 0x1DA12: 84, + 0x1DA13: 84, + 0x1DA14: 84, + 0x1DA15: 84, + 0x1DA16: 84, + 0x1DA17: 84, + 0x1DA18: 84, + 0x1DA19: 84, + 0x1DA1A: 84, + 0x1DA1B: 84, + 0x1DA1C: 84, + 0x1DA1D: 84, + 0x1DA1E: 84, + 0x1DA1F: 84, + 0x1DA20: 84, + 0x1DA21: 84, + 0x1DA22: 84, + 0x1DA23: 84, + 0x1DA24: 84, + 0x1DA25: 84, + 0x1DA26: 84, + 0x1DA27: 84, + 0x1DA28: 84, + 0x1DA29: 84, + 0x1DA2A: 84, + 0x1DA2B: 84, + 0x1DA2C: 84, + 0x1DA2D: 84, + 0x1DA2E: 84, + 0x1DA2F: 84, + 0x1DA30: 84, + 0x1DA31: 84, + 0x1DA32: 84, + 0x1DA33: 84, + 0x1DA34: 84, + 0x1DA35: 84, + 0x1DA36: 84, + 0x1DA3B: 84, + 0x1DA3C: 84, + 0x1DA3D: 84, + 0x1DA3E: 84, + 0x1DA3F: 84, + 0x1DA40: 84, + 0x1DA41: 84, + 0x1DA42: 84, + 0x1DA43: 84, + 0x1DA44: 84, + 0x1DA45: 84, + 0x1DA46: 84, + 0x1DA47: 84, + 0x1DA48: 84, + 0x1DA49: 84, + 0x1DA4A: 84, + 0x1DA4B: 84, + 0x1DA4C: 84, + 0x1DA4D: 84, + 0x1DA4E: 84, + 0x1DA4F: 84, + 0x1DA50: 84, + 0x1DA51: 84, + 0x1DA52: 84, + 0x1DA53: 84, + 0x1DA54: 84, + 0x1DA55: 84, + 0x1DA56: 84, + 0x1DA57: 84, + 0x1DA58: 84, + 0x1DA59: 84, + 0x1DA5A: 84, + 0x1DA5B: 84, + 0x1DA5C: 84, + 0x1DA5D: 84, + 0x1DA5E: 84, + 0x1DA5F: 84, + 0x1DA60: 84, + 0x1DA61: 84, + 0x1DA62: 84, + 0x1DA63: 84, + 0x1DA64: 84, + 0x1DA65: 84, + 0x1DA66: 84, + 0x1DA67: 84, + 0x1DA68: 84, + 0x1DA69: 84, + 0x1DA6A: 84, + 0x1DA6B: 84, + 0x1DA6C: 84, + 0x1DA75: 84, + 0x1DA84: 84, + 0x1DA9B: 84, + 0x1DA9C: 84, + 0x1DA9D: 84, + 0x1DA9E: 84, + 0x1DA9F: 84, + 0x1DAA1: 84, + 0x1DAA2: 84, + 0x1DAA3: 84, + 0x1DAA4: 84, + 0x1DAA5: 84, + 0x1DAA6: 84, + 0x1DAA7: 84, + 0x1DAA8: 84, + 0x1DAA9: 84, + 0x1DAAA: 84, + 0x1DAAB: 84, + 0x1DAAC: 84, + 0x1DAAD: 84, + 0x1DAAE: 84, + 0x1DAAF: 84, + 0x1E000: 84, + 0x1E001: 84, + 0x1E002: 84, + 0x1E003: 84, + 0x1E004: 84, + 0x1E005: 84, + 0x1E006: 84, + 0x1E008: 84, + 0x1E009: 84, + 0x1E00A: 84, + 0x1E00B: 84, + 0x1E00C: 84, + 0x1E00D: 84, + 0x1E00E: 84, + 0x1E00F: 84, + 0x1E010: 84, + 0x1E011: 84, + 0x1E012: 84, + 0x1E013: 84, + 0x1E014: 84, + 0x1E015: 84, + 0x1E016: 84, + 0x1E017: 84, + 0x1E018: 84, + 0x1E01B: 84, + 0x1E01C: 84, + 0x1E01D: 84, + 0x1E01E: 84, + 0x1E01F: 84, + 0x1E020: 84, + 0x1E021: 84, + 0x1E023: 84, + 0x1E024: 84, + 0x1E026: 84, + 0x1E027: 84, + 0x1E028: 84, + 0x1E029: 84, + 0x1E02A: 84, + 0x1E08F: 84, + 0x1E130: 84, + 0x1E131: 84, + 0x1E132: 84, + 0x1E133: 84, + 0x1E134: 84, + 0x1E135: 84, + 0x1E136: 84, + 0x1E2AE: 84, + 0x1E2EC: 84, + 0x1E2ED: 84, + 0x1E2EE: 84, + 0x1E2EF: 84, + 0x1E4EC: 84, + 0x1E4ED: 84, + 0x1E4EE: 84, + 0x1E4EF: 84, + 0x1E5EE: 84, + 0x1E5EF: 84, + 0x1E6E3: 84, + 0x1E6E6: 84, + 0x1E6EE: 84, + 0x1E6EF: 84, + 0x1E6F5: 84, + 0x1E8D0: 84, + 0x1E8D1: 84, + 0x1E8D2: 84, + 0x1E8D3: 84, + 0x1E8D4: 84, + 0x1E8D5: 84, + 0x1E8D6: 84, + 0x1E900: 68, + 0x1E901: 68, + 0x1E902: 68, + 0x1E903: 68, + 0x1E904: 68, + 0x1E905: 68, + 0x1E906: 68, + 0x1E907: 68, + 0x1E908: 68, + 0x1E909: 68, + 0x1E90A: 68, + 0x1E90B: 68, + 0x1E90C: 68, + 0x1E90D: 68, + 0x1E90E: 68, + 0x1E90F: 68, + 0x1E910: 68, + 0x1E911: 68, + 0x1E912: 68, + 0x1E913: 68, + 0x1E914: 68, + 0x1E915: 68, + 0x1E916: 68, + 0x1E917: 68, + 0x1E918: 68, + 0x1E919: 68, + 0x1E91A: 68, + 0x1E91B: 68, + 0x1E91C: 68, + 0x1E91D: 68, + 0x1E91E: 68, + 0x1E91F: 68, + 0x1E920: 68, + 0x1E921: 68, + 0x1E922: 68, + 0x1E923: 68, + 0x1E924: 68, + 0x1E925: 68, + 0x1E926: 68, + 0x1E927: 68, + 0x1E928: 68, + 0x1E929: 68, + 0x1E92A: 68, + 0x1E92B: 68, + 0x1E92C: 68, + 0x1E92D: 68, + 0x1E92E: 68, + 0x1E92F: 68, + 0x1E930: 68, + 0x1E931: 68, + 0x1E932: 68, + 0x1E933: 68, + 0x1E934: 68, + 0x1E935: 68, + 0x1E936: 68, + 0x1E937: 68, + 0x1E938: 68, + 0x1E939: 68, + 0x1E93A: 68, + 0x1E93B: 68, + 0x1E93C: 68, + 0x1E93D: 68, + 0x1E93E: 68, + 0x1E93F: 68, + 0x1E940: 68, + 0x1E941: 68, + 0x1E942: 68, + 0x1E943: 68, + 0x1E944: 84, + 0x1E945: 84, + 0x1E946: 84, + 0x1E947: 84, + 0x1E948: 84, + 0x1E949: 84, + 0x1E94A: 84, + 0x1E94B: 84, + 0xE0001: 84, + 0xE0020: 84, + 0xE0021: 84, + 0xE0022: 84, + 0xE0023: 84, + 0xE0024: 84, + 0xE0025: 84, + 0xE0026: 84, + 0xE0027: 84, + 0xE0028: 84, + 0xE0029: 84, + 0xE002A: 84, + 0xE002B: 84, + 0xE002C: 84, + 0xE002D: 84, + 0xE002E: 84, + 0xE002F: 84, + 0xE0030: 84, + 0xE0031: 84, + 0xE0032: 84, + 0xE0033: 84, + 0xE0034: 84, + 0xE0035: 84, + 0xE0036: 84, + 0xE0037: 84, + 0xE0038: 84, + 0xE0039: 84, + 0xE003A: 84, + 0xE003B: 84, + 0xE003C: 84, + 0xE003D: 84, + 0xE003E: 84, + 0xE003F: 84, + 0xE0040: 84, + 0xE0041: 84, + 0xE0042: 84, + 0xE0043: 84, + 0xE0044: 84, + 0xE0045: 84, + 0xE0046: 84, + 0xE0047: 84, + 0xE0048: 84, + 0xE0049: 84, + 0xE004A: 84, + 0xE004B: 84, + 0xE004C: 84, + 0xE004D: 84, + 0xE004E: 84, + 0xE004F: 84, + 0xE0050: 84, + 0xE0051: 84, + 0xE0052: 84, + 0xE0053: 84, + 0xE0054: 84, + 0xE0055: 84, + 0xE0056: 84, + 0xE0057: 84, + 0xE0058: 84, + 0xE0059: 84, + 0xE005A: 84, + 0xE005B: 84, + 0xE005C: 84, + 0xE005D: 84, + 0xE005E: 84, + 0xE005F: 84, + 0xE0060: 84, + 0xE0061: 84, + 0xE0062: 84, + 0xE0063: 84, + 0xE0064: 84, + 0xE0065: 84, + 0xE0066: 84, + 0xE0067: 84, + 0xE0068: 84, + 0xE0069: 84, + 0xE006A: 84, + 0xE006B: 84, + 0xE006C: 84, + 0xE006D: 84, + 0xE006E: 84, + 0xE006F: 84, + 0xE0070: 84, + 0xE0071: 84, + 0xE0072: 84, + 0xE0073: 84, + 0xE0074: 84, + 0xE0075: 84, + 0xE0076: 84, + 0xE0077: 84, + 0xE0078: 84, + 0xE0079: 84, + 0xE007A: 84, + 0xE007B: 84, + 0xE007C: 84, + 0xE007D: 84, + 0xE007E: 84, + 0xE007F: 84, + 0xE0100: 84, + 0xE0101: 84, + 0xE0102: 84, + 0xE0103: 84, + 0xE0104: 84, + 0xE0105: 84, + 0xE0106: 84, + 0xE0107: 84, + 0xE0108: 84, + 0xE0109: 84, + 0xE010A: 84, + 0xE010B: 84, + 0xE010C: 84, + 0xE010D: 84, + 0xE010E: 84, + 0xE010F: 84, + 0xE0110: 84, + 0xE0111: 84, + 0xE0112: 84, + 0xE0113: 84, + 0xE0114: 84, + 0xE0115: 84, + 0xE0116: 84, + 0xE0117: 84, + 0xE0118: 84, + 0xE0119: 84, + 0xE011A: 84, + 0xE011B: 84, + 0xE011C: 84, + 0xE011D: 84, + 0xE011E: 84, + 0xE011F: 84, + 0xE0120: 84, + 0xE0121: 84, + 0xE0122: 84, + 0xE0123: 84, + 0xE0124: 84, + 0xE0125: 84, + 0xE0126: 84, + 0xE0127: 84, + 0xE0128: 84, + 0xE0129: 84, + 0xE012A: 84, + 0xE012B: 84, + 0xE012C: 84, + 0xE012D: 84, + 0xE012E: 84, + 0xE012F: 84, + 0xE0130: 84, + 0xE0131: 84, + 0xE0132: 84, + 0xE0133: 84, + 0xE0134: 84, + 0xE0135: 84, + 0xE0136: 84, + 0xE0137: 84, + 0xE0138: 84, + 0xE0139: 84, + 0xE013A: 84, + 0xE013B: 84, + 0xE013C: 84, + 0xE013D: 84, + 0xE013E: 84, + 0xE013F: 84, + 0xE0140: 84, + 0xE0141: 84, + 0xE0142: 84, + 0xE0143: 84, + 0xE0144: 84, + 0xE0145: 84, + 0xE0146: 84, + 0xE0147: 84, + 0xE0148: 84, + 0xE0149: 84, + 0xE014A: 84, + 0xE014B: 84, + 0xE014C: 84, + 0xE014D: 84, + 0xE014E: 84, + 0xE014F: 84, + 0xE0150: 84, + 0xE0151: 84, + 0xE0152: 84, + 0xE0153: 84, + 0xE0154: 84, + 0xE0155: 84, + 0xE0156: 84, + 0xE0157: 84, + 0xE0158: 84, + 0xE0159: 84, + 0xE015A: 84, + 0xE015B: 84, + 0xE015C: 84, + 0xE015D: 84, + 0xE015E: 84, + 0xE015F: 84, + 0xE0160: 84, + 0xE0161: 84, + 0xE0162: 84, + 0xE0163: 84, + 0xE0164: 84, + 0xE0165: 84, + 0xE0166: 84, + 0xE0167: 84, + 0xE0168: 84, + 0xE0169: 84, + 0xE016A: 84, + 0xE016B: 84, + 0xE016C: 84, + 0xE016D: 84, + 0xE016E: 84, + 0xE016F: 84, + 0xE0170: 84, + 0xE0171: 84, + 0xE0172: 84, + 0xE0173: 84, + 0xE0174: 84, + 0xE0175: 84, + 0xE0176: 84, + 0xE0177: 84, + 0xE0178: 84, + 0xE0179: 84, + 0xE017A: 84, + 0xE017B: 84, + 0xE017C: 84, + 0xE017D: 84, + 0xE017E: 84, + 0xE017F: 84, + 0xE0180: 84, + 0xE0181: 84, + 0xE0182: 84, + 0xE0183: 84, + 0xE0184: 84, + 0xE0185: 84, + 0xE0186: 84, + 0xE0187: 84, + 0xE0188: 84, + 0xE0189: 84, + 0xE018A: 84, + 0xE018B: 84, + 0xE018C: 84, + 0xE018D: 84, + 0xE018E: 84, + 0xE018F: 84, + 0xE0190: 84, + 0xE0191: 84, + 0xE0192: 84, + 0xE0193: 84, + 0xE0194: 84, + 0xE0195: 84, + 0xE0196: 84, + 0xE0197: 84, + 0xE0198: 84, + 0xE0199: 84, + 0xE019A: 84, + 0xE019B: 84, + 0xE019C: 84, + 0xE019D: 84, + 0xE019E: 84, + 0xE019F: 84, + 0xE01A0: 84, + 0xE01A1: 84, + 0xE01A2: 84, + 0xE01A3: 84, + 0xE01A4: 84, + 0xE01A5: 84, + 0xE01A6: 84, + 0xE01A7: 84, + 0xE01A8: 84, + 0xE01A9: 84, + 0xE01AA: 84, + 0xE01AB: 84, + 0xE01AC: 84, + 0xE01AD: 84, + 0xE01AE: 84, + 0xE01AF: 84, + 0xE01B0: 84, + 0xE01B1: 84, + 0xE01B2: 84, + 0xE01B3: 84, + 0xE01B4: 84, + 0xE01B5: 84, + 0xE01B6: 84, + 0xE01B7: 84, + 0xE01B8: 84, + 0xE01B9: 84, + 0xE01BA: 84, + 0xE01BB: 84, + 0xE01BC: 84, + 0xE01BD: 84, + 0xE01BE: 84, + 0xE01BF: 84, + 0xE01C0: 84, + 0xE01C1: 84, + 0xE01C2: 84, + 0xE01C3: 84, + 0xE01C4: 84, + 0xE01C5: 84, + 0xE01C6: 84, + 0xE01C7: 84, + 0xE01C8: 84, + 0xE01C9: 84, + 0xE01CA: 84, + 0xE01CB: 84, + 0xE01CC: 84, + 0xE01CD: 84, + 0xE01CE: 84, + 0xE01CF: 84, + 0xE01D0: 84, + 0xE01D1: 84, + 0xE01D2: 84, + 0xE01D3: 84, + 0xE01D4: 84, + 0xE01D5: 84, + 0xE01D6: 84, + 0xE01D7: 84, + 0xE01D8: 84, + 0xE01D9: 84, + 0xE01DA: 84, + 0xE01DB: 84, + 0xE01DC: 84, + 0xE01DD: 84, + 0xE01DE: 84, + 0xE01DF: 84, + 0xE01E0: 84, + 0xE01E1: 84, + 0xE01E2: 84, + 0xE01E3: 84, + 0xE01E4: 84, + 0xE01E5: 84, + 0xE01E6: 84, + 0xE01E7: 84, + 0xE01E8: 84, + 0xE01E9: 84, + 0xE01EA: 84, + 0xE01EB: 84, + 0xE01EC: 84, + 0xE01ED: 84, + 0xE01EE: 84, + 0xE01EF: 84, + } + + +codepoint_classes = { + "PVALID": ( + 0x2D0000002E, + 0x300000003A, + 0x610000007B, + 0xDF000000F7, + 0xF800000100, + 0x10100000102, + 0x10300000104, + 0x10500000106, + 0x10700000108, + 0x1090000010A, + 0x10B0000010C, + 0x10D0000010E, + 0x10F00000110, + 0x11100000112, + 0x11300000114, + 0x11500000116, + 0x11700000118, + 0x1190000011A, + 0x11B0000011C, + 0x11D0000011E, + 0x11F00000120, + 0x12100000122, + 0x12300000124, + 0x12500000126, + 0x12700000128, + 0x1290000012A, + 0x12B0000012C, + 0x12D0000012E, + 0x12F00000130, + 0x13100000132, + 0x13500000136, + 0x13700000139, + 0x13A0000013B, + 0x13C0000013D, + 0x13E0000013F, + 0x14200000143, + 0x14400000145, + 0x14600000147, + 0x14800000149, + 0x14B0000014C, + 0x14D0000014E, + 0x14F00000150, + 0x15100000152, + 0x15300000154, + 0x15500000156, + 0x15700000158, + 0x1590000015A, + 0x15B0000015C, + 0x15D0000015E, + 0x15F00000160, + 0x16100000162, + 0x16300000164, + 0x16500000166, + 0x16700000168, + 0x1690000016A, + 0x16B0000016C, + 0x16D0000016E, + 0x16F00000170, + 0x17100000172, + 0x17300000174, + 0x17500000176, + 0x17700000178, + 0x17A0000017B, + 0x17C0000017D, + 0x17E0000017F, + 0x18000000181, + 0x18300000184, + 0x18500000186, + 0x18800000189, + 0x18C0000018E, + 0x19200000193, + 0x19500000196, + 0x1990000019C, + 0x19E0000019F, + 0x1A1000001A2, + 0x1A3000001A4, + 0x1A5000001A6, + 0x1A8000001A9, + 0x1AA000001AC, + 0x1AD000001AE, + 0x1B0000001B1, + 0x1B4000001B5, + 0x1B6000001B7, + 0x1B9000001BC, + 0x1BD000001C4, + 0x1CE000001CF, + 0x1D0000001D1, + 0x1D2000001D3, + 0x1D4000001D5, + 0x1D6000001D7, + 0x1D8000001D9, + 0x1DA000001DB, + 0x1DC000001DE, + 0x1DF000001E0, + 0x1E1000001E2, + 0x1E3000001E4, + 0x1E5000001E6, + 0x1E7000001E8, + 0x1E9000001EA, + 0x1EB000001EC, + 0x1ED000001EE, + 0x1EF000001F1, + 0x1F5000001F6, + 0x1F9000001FA, + 0x1FB000001FC, + 0x1FD000001FE, + 0x1FF00000200, + 0x20100000202, + 0x20300000204, + 0x20500000206, + 0x20700000208, + 0x2090000020A, + 0x20B0000020C, + 0x20D0000020E, + 0x20F00000210, + 0x21100000212, + 0x21300000214, + 0x21500000216, + 0x21700000218, + 0x2190000021A, + 0x21B0000021C, + 0x21D0000021E, + 0x21F00000220, + 0x22100000222, + 0x22300000224, + 0x22500000226, + 0x22700000228, + 0x2290000022A, + 0x22B0000022C, + 0x22D0000022E, + 0x22F00000230, + 0x23100000232, + 0x2330000023A, + 0x23C0000023D, + 0x23F00000241, + 0x24200000243, + 0x24700000248, + 0x2490000024A, + 0x24B0000024C, + 0x24D0000024E, + 0x24F000002B0, + 0x2B9000002C2, + 0x2C6000002D2, + 0x2EC000002ED, + 0x2EE000002EF, + 0x30000000340, + 0x34200000343, + 0x3460000034F, + 0x35000000370, + 0x37100000372, + 0x37300000374, + 0x37700000378, + 0x37B0000037E, + 0x39000000391, + 0x3AC000003CF, + 0x3D7000003D8, + 0x3D9000003DA, + 0x3DB000003DC, + 0x3DD000003DE, + 0x3DF000003E0, + 0x3E1000003E2, + 0x3E3000003E4, + 0x3E5000003E6, + 0x3E7000003E8, + 0x3E9000003EA, + 0x3EB000003EC, + 0x3ED000003EE, + 0x3EF000003F0, + 0x3F3000003F4, + 0x3F8000003F9, + 0x3FB000003FD, + 0x43000000460, + 0x46100000462, + 0x46300000464, + 0x46500000466, + 0x46700000468, + 0x4690000046A, + 0x46B0000046C, + 0x46D0000046E, + 0x46F00000470, + 0x47100000472, + 0x47300000474, + 0x47500000476, + 0x47700000478, + 0x4790000047A, + 0x47B0000047C, + 0x47D0000047E, + 0x47F00000480, + 0x48100000482, + 0x48300000488, + 0x48B0000048C, + 0x48D0000048E, + 0x48F00000490, + 0x49100000492, + 0x49300000494, + 0x49500000496, + 0x49700000498, + 0x4990000049A, + 0x49B0000049C, + 0x49D0000049E, + 0x49F000004A0, + 0x4A1000004A2, + 0x4A3000004A4, + 0x4A5000004A6, + 0x4A7000004A8, + 0x4A9000004AA, + 0x4AB000004AC, + 0x4AD000004AE, + 0x4AF000004B0, + 0x4B1000004B2, + 0x4B3000004B4, + 0x4B5000004B6, + 0x4B7000004B8, + 0x4B9000004BA, + 0x4BB000004BC, + 0x4BD000004BE, + 0x4BF000004C0, + 0x4C2000004C3, + 0x4C4000004C5, + 0x4C6000004C7, + 0x4C8000004C9, + 0x4CA000004CB, + 0x4CC000004CD, + 0x4CE000004D0, + 0x4D1000004D2, + 0x4D3000004D4, + 0x4D5000004D6, + 0x4D7000004D8, + 0x4D9000004DA, + 0x4DB000004DC, + 0x4DD000004DE, + 0x4DF000004E0, + 0x4E1000004E2, + 0x4E3000004E4, + 0x4E5000004E6, + 0x4E7000004E8, + 0x4E9000004EA, + 0x4EB000004EC, + 0x4ED000004EE, + 0x4EF000004F0, + 0x4F1000004F2, + 0x4F3000004F4, + 0x4F5000004F6, + 0x4F7000004F8, + 0x4F9000004FA, + 0x4FB000004FC, + 0x4FD000004FE, + 0x4FF00000500, + 0x50100000502, + 0x50300000504, + 0x50500000506, + 0x50700000508, + 0x5090000050A, + 0x50B0000050C, + 0x50D0000050E, + 0x50F00000510, + 0x51100000512, + 0x51300000514, + 0x51500000516, + 0x51700000518, + 0x5190000051A, + 0x51B0000051C, + 0x51D0000051E, + 0x51F00000520, + 0x52100000522, + 0x52300000524, + 0x52500000526, + 0x52700000528, + 0x5290000052A, + 0x52B0000052C, + 0x52D0000052E, + 0x52F00000530, + 0x5590000055A, + 0x56000000587, + 0x58800000589, + 0x591000005BE, + 0x5BF000005C0, + 0x5C1000005C3, + 0x5C4000005C6, + 0x5C7000005C8, + 0x5D0000005EB, + 0x5EF000005F3, + 0x6100000061B, + 0x62000000640, + 0x64100000660, + 0x66E00000675, + 0x679000006D4, + 0x6D5000006DD, + 0x6DF000006E9, + 0x6EA000006F0, + 0x6FA00000700, + 0x7100000074B, + 0x74D000007B2, + 0x7C0000007F6, + 0x7FD000007FE, + 0x8000000082E, + 0x8400000085C, + 0x8600000086B, + 0x87000000888, + 0x88900000890, + 0x897000008E2, + 0x8E300000958, + 0x96000000964, + 0x96600000970, + 0x97100000984, + 0x9850000098D, + 0x98F00000991, + 0x993000009A9, + 0x9AA000009B1, + 0x9B2000009B3, + 0x9B6000009BA, + 0x9BC000009C5, + 0x9C7000009C9, + 0x9CB000009CF, + 0x9D7000009D8, + 0x9E0000009E4, + 0x9E6000009F2, + 0x9FC000009FD, + 0x9FE000009FF, + 0xA0100000A04, + 0xA0500000A0B, + 0xA0F00000A11, + 0xA1300000A29, + 0xA2A00000A31, + 0xA3200000A33, + 0xA3500000A36, + 0xA3800000A3A, + 0xA3C00000A3D, + 0xA3E00000A43, + 0xA4700000A49, + 0xA4B00000A4E, + 0xA5100000A52, + 0xA5C00000A5D, + 0xA6600000A76, + 0xA8100000A84, + 0xA8500000A8E, + 0xA8F00000A92, + 0xA9300000AA9, + 0xAAA00000AB1, + 0xAB200000AB4, + 0xAB500000ABA, + 0xABC00000AC6, + 0xAC700000ACA, + 0xACB00000ACE, + 0xAD000000AD1, + 0xAE000000AE4, + 0xAE600000AF0, + 0xAF900000B00, + 0xB0100000B04, + 0xB0500000B0D, + 0xB0F00000B11, + 0xB1300000B29, + 0xB2A00000B31, + 0xB3200000B34, + 0xB3500000B3A, + 0xB3C00000B45, + 0xB4700000B49, + 0xB4B00000B4E, + 0xB5500000B58, + 0xB5F00000B64, + 0xB6600000B70, + 0xB7100000B72, + 0xB8200000B84, + 0xB8500000B8B, + 0xB8E00000B91, + 0xB9200000B96, + 0xB9900000B9B, + 0xB9C00000B9D, + 0xB9E00000BA0, + 0xBA300000BA5, + 0xBA800000BAB, + 0xBAE00000BBA, + 0xBBE00000BC3, + 0xBC600000BC9, + 0xBCA00000BCE, + 0xBD000000BD1, + 0xBD700000BD8, + 0xBE600000BF0, + 0xC0000000C0D, + 0xC0E00000C11, + 0xC1200000C29, + 0xC2A00000C3A, + 0xC3C00000C45, + 0xC4600000C49, + 0xC4A00000C4E, + 0xC5500000C57, + 0xC5800000C5B, + 0xC5C00000C5E, + 0xC6000000C64, + 0xC6600000C70, + 0xC8000000C84, + 0xC8500000C8D, + 0xC8E00000C91, + 0xC9200000CA9, + 0xCAA00000CB4, + 0xCB500000CBA, + 0xCBC00000CC5, + 0xCC600000CC9, + 0xCCA00000CCE, + 0xCD500000CD7, + 0xCDC00000CDF, + 0xCE000000CE4, + 0xCE600000CF0, + 0xCF100000CF4, + 0xD0000000D0D, + 0xD0E00000D11, + 0xD1200000D45, + 0xD4600000D49, + 0xD4A00000D4F, + 0xD5400000D58, + 0xD5F00000D64, + 0xD6600000D70, + 0xD7A00000D80, + 0xD8100000D84, + 0xD8500000D97, + 0xD9A00000DB2, + 0xDB300000DBC, + 0xDBD00000DBE, + 0xDC000000DC7, + 0xDCA00000DCB, + 0xDCF00000DD5, + 0xDD600000DD7, + 0xDD800000DE0, + 0xDE600000DF0, + 0xDF200000DF4, + 0xE0100000E33, + 0xE3400000E3B, + 0xE4000000E4F, + 0xE5000000E5A, + 0xE8100000E83, + 0xE8400000E85, + 0xE8600000E8B, + 0xE8C00000EA4, + 0xEA500000EA6, + 0xEA700000EB3, + 0xEB400000EBE, + 0xEC000000EC5, + 0xEC600000EC7, + 0xEC800000ECF, + 0xED000000EDA, + 0xEDE00000EE0, + 0xF0000000F01, + 0xF0B00000F0C, + 0xF1800000F1A, + 0xF2000000F2A, + 0xF3500000F36, + 0xF3700000F38, + 0xF3900000F3A, + 0xF3E00000F43, + 0xF4400000F48, + 0xF4900000F4D, + 0xF4E00000F52, + 0xF5300000F57, + 0xF5800000F5C, + 0xF5D00000F69, + 0xF6A00000F6D, + 0xF7100000F73, + 0xF7400000F75, + 0xF7A00000F81, + 0xF8200000F85, + 0xF8600000F93, + 0xF9400000F98, + 0xF9900000F9D, + 0xF9E00000FA2, + 0xFA300000FA7, + 0xFA800000FAC, + 0xFAD00000FB9, + 0xFBA00000FBD, + 0xFC600000FC7, + 0x10000000104A, + 0x10500000109E, + 0x10D0000010FB, + 0x10FD00001100, + 0x120000001249, + 0x124A0000124E, + 0x125000001257, + 0x125800001259, + 0x125A0000125E, + 0x126000001289, + 0x128A0000128E, + 0x1290000012B1, + 0x12B2000012B6, + 0x12B8000012BF, + 0x12C0000012C1, + 0x12C2000012C6, + 0x12C8000012D7, + 0x12D800001311, + 0x131200001316, + 0x13180000135B, + 0x135D00001360, + 0x138000001390, + 0x13A0000013F6, + 0x14010000166D, + 0x166F00001680, + 0x16810000169B, + 0x16A0000016EB, + 0x16F1000016F9, + 0x170000001716, + 0x171F00001735, + 0x174000001754, + 0x17600000176D, + 0x176E00001771, + 0x177200001774, + 0x1780000017B4, + 0x17B6000017D4, + 0x17D7000017D8, + 0x17DC000017DE, + 0x17E0000017EA, + 0x18100000181A, + 0x182000001879, + 0x1880000018AB, + 0x18B0000018F6, + 0x19000000191F, + 0x19200000192C, + 0x19300000193C, + 0x19460000196E, + 0x197000001975, + 0x1980000019AC, + 0x19B0000019CA, + 0x19D0000019DA, + 0x1A0000001A1C, + 0x1A2000001A5F, + 0x1A6000001A7D, + 0x1A7F00001A8A, + 0x1A9000001A9A, + 0x1AA700001AA8, + 0x1AB000001ABE, + 0x1ABF00001ADE, + 0x1AE000001AEC, + 0x1B0000001B4D, + 0x1B5000001B5A, + 0x1B6B00001B74, + 0x1B8000001BF4, + 0x1C0000001C38, + 0x1C4000001C4A, + 0x1C4D00001C7E, + 0x1C8A00001C8B, + 0x1CD000001CD3, + 0x1CD400001CFB, + 0x1D0000001D2C, + 0x1D2F00001D30, + 0x1D3B00001D3C, + 0x1D4E00001D4F, + 0x1D6B00001D78, + 0x1D7900001D9B, + 0x1DC000001E00, + 0x1E0100001E02, + 0x1E0300001E04, + 0x1E0500001E06, + 0x1E0700001E08, + 0x1E0900001E0A, + 0x1E0B00001E0C, + 0x1E0D00001E0E, + 0x1E0F00001E10, + 0x1E1100001E12, + 0x1E1300001E14, + 0x1E1500001E16, + 0x1E1700001E18, + 0x1E1900001E1A, + 0x1E1B00001E1C, + 0x1E1D00001E1E, + 0x1E1F00001E20, + 0x1E2100001E22, + 0x1E2300001E24, + 0x1E2500001E26, + 0x1E2700001E28, + 0x1E2900001E2A, + 0x1E2B00001E2C, + 0x1E2D00001E2E, + 0x1E2F00001E30, + 0x1E3100001E32, + 0x1E3300001E34, + 0x1E3500001E36, + 0x1E3700001E38, + 0x1E3900001E3A, + 0x1E3B00001E3C, + 0x1E3D00001E3E, + 0x1E3F00001E40, + 0x1E4100001E42, + 0x1E4300001E44, + 0x1E4500001E46, + 0x1E4700001E48, + 0x1E4900001E4A, + 0x1E4B00001E4C, + 0x1E4D00001E4E, + 0x1E4F00001E50, + 0x1E5100001E52, + 0x1E5300001E54, + 0x1E5500001E56, + 0x1E5700001E58, + 0x1E5900001E5A, + 0x1E5B00001E5C, + 0x1E5D00001E5E, + 0x1E5F00001E60, + 0x1E6100001E62, + 0x1E6300001E64, + 0x1E6500001E66, + 0x1E6700001E68, + 0x1E6900001E6A, + 0x1E6B00001E6C, + 0x1E6D00001E6E, + 0x1E6F00001E70, + 0x1E7100001E72, + 0x1E7300001E74, + 0x1E7500001E76, + 0x1E7700001E78, + 0x1E7900001E7A, + 0x1E7B00001E7C, + 0x1E7D00001E7E, + 0x1E7F00001E80, + 0x1E8100001E82, + 0x1E8300001E84, + 0x1E8500001E86, + 0x1E8700001E88, + 0x1E8900001E8A, + 0x1E8B00001E8C, + 0x1E8D00001E8E, + 0x1E8F00001E90, + 0x1E9100001E92, + 0x1E9300001E94, + 0x1E9500001E9A, + 0x1E9C00001E9E, + 0x1E9F00001EA0, + 0x1EA100001EA2, + 0x1EA300001EA4, + 0x1EA500001EA6, + 0x1EA700001EA8, + 0x1EA900001EAA, + 0x1EAB00001EAC, + 0x1EAD00001EAE, + 0x1EAF00001EB0, + 0x1EB100001EB2, + 0x1EB300001EB4, + 0x1EB500001EB6, + 0x1EB700001EB8, + 0x1EB900001EBA, + 0x1EBB00001EBC, + 0x1EBD00001EBE, + 0x1EBF00001EC0, + 0x1EC100001EC2, + 0x1EC300001EC4, + 0x1EC500001EC6, + 0x1EC700001EC8, + 0x1EC900001ECA, + 0x1ECB00001ECC, + 0x1ECD00001ECE, + 0x1ECF00001ED0, + 0x1ED100001ED2, + 0x1ED300001ED4, + 0x1ED500001ED6, + 0x1ED700001ED8, + 0x1ED900001EDA, + 0x1EDB00001EDC, + 0x1EDD00001EDE, + 0x1EDF00001EE0, + 0x1EE100001EE2, + 0x1EE300001EE4, + 0x1EE500001EE6, + 0x1EE700001EE8, + 0x1EE900001EEA, + 0x1EEB00001EEC, + 0x1EED00001EEE, + 0x1EEF00001EF0, + 0x1EF100001EF2, + 0x1EF300001EF4, + 0x1EF500001EF6, + 0x1EF700001EF8, + 0x1EF900001EFA, + 0x1EFB00001EFC, + 0x1EFD00001EFE, + 0x1EFF00001F08, + 0x1F1000001F16, + 0x1F2000001F28, + 0x1F3000001F38, + 0x1F4000001F46, + 0x1F5000001F58, + 0x1F6000001F68, + 0x1F7000001F71, + 0x1F7200001F73, + 0x1F7400001F75, + 0x1F7600001F77, + 0x1F7800001F79, + 0x1F7A00001F7B, + 0x1F7C00001F7D, + 0x1FB000001FB2, + 0x1FB600001FB7, + 0x1FC600001FC7, + 0x1FD000001FD3, + 0x1FD600001FD8, + 0x1FE000001FE3, + 0x1FE400001FE8, + 0x1FF600001FF7, + 0x214E0000214F, + 0x218400002185, + 0x2C3000002C60, + 0x2C6100002C62, + 0x2C6500002C67, + 0x2C6800002C69, + 0x2C6A00002C6B, + 0x2C6C00002C6D, + 0x2C7100002C72, + 0x2C7300002C75, + 0x2C7600002C7C, + 0x2C8100002C82, + 0x2C8300002C84, + 0x2C8500002C86, + 0x2C8700002C88, + 0x2C8900002C8A, + 0x2C8B00002C8C, + 0x2C8D00002C8E, + 0x2C8F00002C90, + 0x2C9100002C92, + 0x2C9300002C94, + 0x2C9500002C96, + 0x2C9700002C98, + 0x2C9900002C9A, + 0x2C9B00002C9C, + 0x2C9D00002C9E, + 0x2C9F00002CA0, + 0x2CA100002CA2, + 0x2CA300002CA4, + 0x2CA500002CA6, + 0x2CA700002CA8, + 0x2CA900002CAA, + 0x2CAB00002CAC, + 0x2CAD00002CAE, + 0x2CAF00002CB0, + 0x2CB100002CB2, + 0x2CB300002CB4, + 0x2CB500002CB6, + 0x2CB700002CB8, + 0x2CB900002CBA, + 0x2CBB00002CBC, + 0x2CBD00002CBE, + 0x2CBF00002CC0, + 0x2CC100002CC2, + 0x2CC300002CC4, + 0x2CC500002CC6, + 0x2CC700002CC8, + 0x2CC900002CCA, + 0x2CCB00002CCC, + 0x2CCD00002CCE, + 0x2CCF00002CD0, + 0x2CD100002CD2, + 0x2CD300002CD4, + 0x2CD500002CD6, + 0x2CD700002CD8, + 0x2CD900002CDA, + 0x2CDB00002CDC, + 0x2CDD00002CDE, + 0x2CDF00002CE0, + 0x2CE100002CE2, + 0x2CE300002CE5, + 0x2CEC00002CED, + 0x2CEE00002CF2, + 0x2CF300002CF4, + 0x2D0000002D26, + 0x2D2700002D28, + 0x2D2D00002D2E, + 0x2D3000002D68, + 0x2D7F00002D97, + 0x2DA000002DA7, + 0x2DA800002DAF, + 0x2DB000002DB7, + 0x2DB800002DBF, + 0x2DC000002DC7, + 0x2DC800002DCF, + 0x2DD000002DD7, + 0x2DD800002DDF, + 0x2DE000002E00, + 0x2E2F00002E30, + 0x300500003008, + 0x302A0000302E, + 0x303C0000303D, + 0x304100003097, + 0x30990000309B, + 0x309D0000309F, + 0x30A1000030FB, + 0x30FC000030FF, + 0x310500003130, + 0x31A0000031C0, + 0x31F000003200, + 0x340000004DC0, + 0x4E000000A48D, + 0xA4D00000A4FE, + 0xA5000000A60D, + 0xA6100000A62C, + 0xA6410000A642, + 0xA6430000A644, + 0xA6450000A646, + 0xA6470000A648, + 0xA6490000A64A, + 0xA64B0000A64C, + 0xA64D0000A64E, + 0xA64F0000A650, + 0xA6510000A652, + 0xA6530000A654, + 0xA6550000A656, + 0xA6570000A658, + 0xA6590000A65A, + 0xA65B0000A65C, + 0xA65D0000A65E, + 0xA65F0000A660, + 0xA6610000A662, + 0xA6630000A664, + 0xA6650000A666, + 0xA6670000A668, + 0xA6690000A66A, + 0xA66B0000A66C, + 0xA66D0000A670, + 0xA6740000A67E, + 0xA67F0000A680, + 0xA6810000A682, + 0xA6830000A684, + 0xA6850000A686, + 0xA6870000A688, + 0xA6890000A68A, + 0xA68B0000A68C, + 0xA68D0000A68E, + 0xA68F0000A690, + 0xA6910000A692, + 0xA6930000A694, + 0xA6950000A696, + 0xA6970000A698, + 0xA6990000A69A, + 0xA69B0000A69C, + 0xA69E0000A6E6, + 0xA6F00000A6F2, + 0xA7170000A720, + 0xA7230000A724, + 0xA7250000A726, + 0xA7270000A728, + 0xA7290000A72A, + 0xA72B0000A72C, + 0xA72D0000A72E, + 0xA72F0000A732, + 0xA7330000A734, + 0xA7350000A736, + 0xA7370000A738, + 0xA7390000A73A, + 0xA73B0000A73C, + 0xA73D0000A73E, + 0xA73F0000A740, + 0xA7410000A742, + 0xA7430000A744, + 0xA7450000A746, + 0xA7470000A748, + 0xA7490000A74A, + 0xA74B0000A74C, + 0xA74D0000A74E, + 0xA74F0000A750, + 0xA7510000A752, + 0xA7530000A754, + 0xA7550000A756, + 0xA7570000A758, + 0xA7590000A75A, + 0xA75B0000A75C, + 0xA75D0000A75E, + 0xA75F0000A760, + 0xA7610000A762, + 0xA7630000A764, + 0xA7650000A766, + 0xA7670000A768, + 0xA7690000A76A, + 0xA76B0000A76C, + 0xA76D0000A76E, + 0xA76F0000A770, + 0xA7710000A779, + 0xA77A0000A77B, + 0xA77C0000A77D, + 0xA77F0000A780, + 0xA7810000A782, + 0xA7830000A784, + 0xA7850000A786, + 0xA7870000A789, + 0xA78C0000A78D, + 0xA78E0000A790, + 0xA7910000A792, + 0xA7930000A796, + 0xA7970000A798, + 0xA7990000A79A, + 0xA79B0000A79C, + 0xA79D0000A79E, + 0xA79F0000A7A0, + 0xA7A10000A7A2, + 0xA7A30000A7A4, + 0xA7A50000A7A6, + 0xA7A70000A7A8, + 0xA7A90000A7AA, + 0xA7AF0000A7B0, + 0xA7B50000A7B6, + 0xA7B70000A7B8, + 0xA7B90000A7BA, + 0xA7BB0000A7BC, + 0xA7BD0000A7BE, + 0xA7BF0000A7C0, + 0xA7C10000A7C2, + 0xA7C30000A7C4, + 0xA7C80000A7C9, + 0xA7CA0000A7CB, + 0xA7CD0000A7CE, + 0xA7CF0000A7D0, + 0xA7D10000A7D2, + 0xA7D30000A7D4, + 0xA7D50000A7D6, + 0xA7D70000A7D8, + 0xA7D90000A7DA, + 0xA7DB0000A7DC, + 0xA7F60000A7F8, + 0xA7FA0000A828, + 0xA82C0000A82D, + 0xA8400000A874, + 0xA8800000A8C6, + 0xA8D00000A8DA, + 0xA8E00000A8F8, + 0xA8FB0000A8FC, + 0xA8FD0000A92E, + 0xA9300000A954, + 0xA9800000A9C1, + 0xA9CF0000A9DA, + 0xA9E00000A9FF, + 0xAA000000AA37, + 0xAA400000AA4E, + 0xAA500000AA5A, + 0xAA600000AA77, + 0xAA7A0000AAC3, + 0xAADB0000AADE, + 0xAAE00000AAF0, + 0xAAF20000AAF7, + 0xAB010000AB07, + 0xAB090000AB0F, + 0xAB110000AB17, + 0xAB200000AB27, + 0xAB280000AB2F, + 0xAB300000AB5B, + 0xAB600000AB69, + 0xABC00000ABEB, + 0xABEC0000ABEE, + 0xABF00000ABFA, + 0xAC000000D7A4, + 0xFA0E0000FA10, + 0xFA110000FA12, + 0xFA130000FA15, + 0xFA1F0000FA20, + 0xFA210000FA22, + 0xFA230000FA25, + 0xFA270000FA2A, + 0xFB1E0000FB1F, + 0xFE200000FE30, + 0xFE730000FE74, + 0x100000001000C, + 0x1000D00010027, + 0x100280001003B, + 0x1003C0001003E, + 0x1003F0001004E, + 0x100500001005E, + 0x10080000100FB, + 0x101FD000101FE, + 0x102800001029D, + 0x102A0000102D1, + 0x102E0000102E1, + 0x1030000010320, + 0x1032D00010341, + 0x103420001034A, + 0x103500001037B, + 0x103800001039E, + 0x103A0000103C4, + 0x103C8000103D0, + 0x104280001049E, + 0x104A0000104AA, + 0x104D8000104FC, + 0x1050000010528, + 0x1053000010564, + 0x10597000105A2, + 0x105A3000105B2, + 0x105B3000105BA, + 0x105BB000105BD, + 0x105C0000105F4, + 0x1060000010737, + 0x1074000010756, + 0x1076000010768, + 0x1078000010781, + 0x1080000010806, + 0x1080800010809, + 0x1080A00010836, + 0x1083700010839, + 0x1083C0001083D, + 0x1083F00010856, + 0x1086000010877, + 0x108800001089F, + 0x108E0000108F3, + 0x108F4000108F6, + 0x1090000010916, + 0x109200001093A, + 0x109400001095A, + 0x10980000109B8, + 0x109BE000109C0, + 0x10A0000010A04, + 0x10A0500010A07, + 0x10A0C00010A14, + 0x10A1500010A18, + 0x10A1900010A36, + 0x10A3800010A3B, + 0x10A3F00010A40, + 0x10A6000010A7D, + 0x10A8000010A9D, + 0x10AC000010AC8, + 0x10AC900010AE7, + 0x10B0000010B36, + 0x10B4000010B56, + 0x10B6000010B73, + 0x10B8000010B92, + 0x10C0000010C49, + 0x10CC000010CF3, + 0x10D0000010D28, + 0x10D3000010D3A, + 0x10D4000010D50, + 0x10D6900010D6E, + 0x10D6F00010D86, + 0x10E8000010EAA, + 0x10EAB00010EAD, + 0x10EB000010EB2, + 0x10EC200010EC8, + 0x10EFA00010F1D, + 0x10F2700010F28, + 0x10F3000010F51, + 0x10F7000010F86, + 0x10FB000010FC5, + 0x10FE000010FF7, + 0x1100000011047, + 0x1106600011076, + 0x1107F000110BB, + 0x110C2000110C3, + 0x110D0000110E9, + 0x110F0000110FA, + 0x1110000011135, + 0x1113600011140, + 0x1114400011148, + 0x1115000011174, + 0x1117600011177, + 0x11180000111C5, + 0x111C9000111CD, + 0x111CE000111DB, + 0x111DC000111DD, + 0x1120000011212, + 0x1121300011238, + 0x1123E00011242, + 0x1128000011287, + 0x1128800011289, + 0x1128A0001128E, + 0x1128F0001129E, + 0x1129F000112A9, + 0x112B0000112EB, + 0x112F0000112FA, + 0x1130000011304, + 0x113050001130D, + 0x1130F00011311, + 0x1131300011329, + 0x1132A00011331, + 0x1133200011334, + 0x113350001133A, + 0x1133B00011345, + 0x1134700011349, + 0x1134B0001134E, + 0x1135000011351, + 0x1135700011358, + 0x1135D00011364, + 0x113660001136D, + 0x1137000011375, + 0x113800001138A, + 0x1138B0001138C, + 0x1138E0001138F, + 0x11390000113B6, + 0x113B7000113C1, + 0x113C2000113C3, + 0x113C5000113C6, + 0x113C7000113CB, + 0x113CC000113D4, + 0x113E1000113E3, + 0x114000001144B, + 0x114500001145A, + 0x1145E00011462, + 0x11480000114C6, + 0x114C7000114C8, + 0x114D0000114DA, + 0x11580000115B6, + 0x115B8000115C1, + 0x115D8000115DE, + 0x1160000011641, + 0x1164400011645, + 0x116500001165A, + 0x11680000116B9, + 0x116C0000116CA, + 0x116D0000116E4, + 0x117000001171B, + 0x1171D0001172C, + 0x117300001173A, + 0x1174000011747, + 0x118000001183B, + 0x118C0000118EA, + 0x118FF00011907, + 0x119090001190A, + 0x1190C00011914, + 0x1191500011917, + 0x1191800011936, + 0x1193700011939, + 0x1193B00011944, + 0x119500001195A, + 0x119A0000119A8, + 0x119AA000119D8, + 0x119DA000119E2, + 0x119E3000119E5, + 0x11A0000011A3F, + 0x11A4700011A48, + 0x11A5000011A9A, + 0x11A9D00011A9E, + 0x11AB000011AF9, + 0x11B6000011B68, + 0x11BC000011BE1, + 0x11BF000011BFA, + 0x11C0000011C09, + 0x11C0A00011C37, + 0x11C3800011C41, + 0x11C5000011C5A, + 0x11C7200011C90, + 0x11C9200011CA8, + 0x11CA900011CB7, + 0x11D0000011D07, + 0x11D0800011D0A, + 0x11D0B00011D37, + 0x11D3A00011D3B, + 0x11D3C00011D3E, + 0x11D3F00011D48, + 0x11D5000011D5A, + 0x11D6000011D66, + 0x11D6700011D69, + 0x11D6A00011D8F, + 0x11D9000011D92, + 0x11D9300011D99, + 0x11DA000011DAA, + 0x11DB000011DDC, + 0x11DE000011DEA, + 0x11EE000011EF7, + 0x11F0000011F11, + 0x11F1200011F3B, + 0x11F3E00011F43, + 0x11F5000011F5B, + 0x11FB000011FB1, + 0x120000001239A, + 0x1248000012544, + 0x12F9000012FF1, + 0x1300000013430, + 0x1344000013456, + 0x13460000143FB, + 0x1440000014647, + 0x161000001613A, + 0x1680000016A39, + 0x16A4000016A5F, + 0x16A6000016A6A, + 0x16A7000016ABF, + 0x16AC000016ACA, + 0x16AD000016AEE, + 0x16AF000016AF5, + 0x16B0000016B37, + 0x16B4000016B44, + 0x16B5000016B5A, + 0x16B6300016B78, + 0x16B7D00016B90, + 0x16D4000016D6D, + 0x16D7000016D7A, + 0x16E6000016E80, + 0x16EBB00016ED4, + 0x16F0000016F4B, + 0x16F4F00016F88, + 0x16F8F00016FA0, + 0x16FE000016FE2, + 0x16FE300016FE5, + 0x16FF000016FF4, + 0x1700000018CD6, + 0x18CFF00018D1F, + 0x18D8000018DF3, + 0x1AFF00001AFF4, + 0x1AFF50001AFFC, + 0x1AFFD0001AFFF, + 0x1B0000001B123, + 0x1B1320001B133, + 0x1B1500001B153, + 0x1B1550001B156, + 0x1B1640001B168, + 0x1B1700001B2FC, + 0x1BC000001BC6B, + 0x1BC700001BC7D, + 0x1BC800001BC89, + 0x1BC900001BC9A, + 0x1BC9D0001BC9F, + 0x1CF000001CF2E, + 0x1CF300001CF47, + 0x1DA000001DA37, + 0x1DA3B0001DA6D, + 0x1DA750001DA76, + 0x1DA840001DA85, + 0x1DA9B0001DAA0, + 0x1DAA10001DAB0, + 0x1DF000001DF1F, + 0x1DF250001DF2B, + 0x1E0000001E007, + 0x1E0080001E019, + 0x1E01B0001E022, + 0x1E0230001E025, + 0x1E0260001E02B, + 0x1E08F0001E090, + 0x1E1000001E12D, + 0x1E1300001E13E, + 0x1E1400001E14A, + 0x1E14E0001E14F, + 0x1E2900001E2AF, + 0x1E2C00001E2FA, + 0x1E4D00001E4FA, + 0x1E5D00001E5FB, + 0x1E6C00001E6DF, + 0x1E6E00001E6F6, + 0x1E6FE0001E700, + 0x1E7E00001E7E7, + 0x1E7E80001E7EC, + 0x1E7ED0001E7EF, + 0x1E7F00001E7FF, + 0x1E8000001E8C5, + 0x1E8D00001E8D7, + 0x1E9220001E94C, + 0x1E9500001E95A, + 0x200000002A6E0, + 0x2A7000002B81E, + 0x2B8200002CEAE, + 0x2CEB00002EBE1, + 0x2EBF00002EE5E, + 0x300000003134B, + 0x313500003347A, + ), + "CONTEXTJ": (0x200C0000200E,), + "CONTEXTO": ( + 0xB7000000B8, + 0x37500000376, + 0x5F3000005F5, + 0x6600000066A, + 0x6F0000006FA, + 0x30FB000030FC, + ), +} diff --git a/intelliaide/vendor/idna/intranges.py b/intelliaide/vendor/idna/intranges.py new file mode 100644 index 0000000..19d7781 --- /dev/null +++ b/intelliaide/vendor/idna/intranges.py @@ -0,0 +1,55 @@ +""" +Given a list of integers, made up of (hopefully) a small number of long runs +of consecutive integers, compute a representation of the form +((start1, end1), (start2, end2) ...). Then answer the question "was x present +in the original list?" in time O(log(# runs)). +""" + +import bisect + + +def intranges_from_list(list_: list[int]) -> tuple[int, ...]: + """Represent a list of integers as a sequence of ranges: + ((start_0, end_0), (start_1, end_1), ...), such that the original + integers are exactly those x such that start_i <= x < end_i for some i. + + Ranges are encoded as single integers (start << 32 | end), not as tuples. + """ + + sorted_list = sorted(list_) + ranges = [] + last_write = -1 + for i in range(len(sorted_list)): + if i + 1 < len(sorted_list) and sorted_list[i] == sorted_list[i + 1] - 1: + continue + current_range = sorted_list[last_write + 1 : i + 1] + ranges.append(_encode_range(current_range[0], current_range[-1] + 1)) + last_write = i + + return tuple(ranges) + + +def _encode_range(start: int, end: int) -> int: + return (start << 32) | end + + +def _decode_range(r: int) -> tuple[int, int]: + return (r >> 32), (r & ((1 << 32) - 1)) + + +def intranges_contain(int_: int, ranges: tuple[int, ...]) -> bool: + """Determine if `int_` falls into one of the ranges in `ranges`.""" + tuple_ = _encode_range(int_, 0) + pos = bisect.bisect_left(ranges, tuple_) + # we could be immediately ahead of a tuple (start, end) + # with start < int_ <= end + if pos > 0: + left, right = _decode_range(ranges[pos - 1]) + if left <= int_ < right: + return True + # or we could be immediately behind a tuple (int_, end) + if pos < len(ranges): + left, _ = _decode_range(ranges[pos]) + if left == int_: + return True + return False diff --git a/intelliaide/vendor/idna/package_data.py b/intelliaide/vendor/idna/package_data.py new file mode 100644 index 0000000..560eb22 --- /dev/null +++ b/intelliaide/vendor/idna/package_data.py @@ -0,0 +1 @@ +__version__ = "3.16" diff --git a/intelliaide/vendor/idna/py.typed b/intelliaide/vendor/idna/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/intelliaide/vendor/idna/uts46data.py b/intelliaide/vendor/idna/uts46data.py new file mode 100644 index 0000000..f728fd9 --- /dev/null +++ b/intelliaide/vendor/idna/uts46data.py @@ -0,0 +1,8383 @@ +# This file is automatically generated by tools/idna-data + +from typing import Union + +"""IDNA Mapping Table from UTS46.""" + + +__version__ = "17.0.0" + +uts46data: tuple[Union[tuple[int, str], tuple[int, str, str]], ...] = ( + (0x0, "V"), + (0x1, "V"), + (0x2, "V"), + (0x3, "V"), + (0x4, "V"), + (0x5, "V"), + (0x6, "V"), + (0x7, "V"), + (0x8, "V"), + (0x9, "V"), + (0xA, "V"), + (0xB, "V"), + (0xC, "V"), + (0xD, "V"), + (0xE, "V"), + (0xF, "V"), + (0x10, "V"), + (0x11, "V"), + (0x12, "V"), + (0x13, "V"), + (0x14, "V"), + (0x15, "V"), + (0x16, "V"), + (0x17, "V"), + (0x18, "V"), + (0x19, "V"), + (0x1A, "V"), + (0x1B, "V"), + (0x1C, "V"), + (0x1D, "V"), + (0x1E, "V"), + (0x1F, "V"), + (0x20, "V"), + (0x21, "V"), + (0x22, "V"), + (0x23, "V"), + (0x24, "V"), + (0x25, "V"), + (0x26, "V"), + (0x27, "V"), + (0x28, "V"), + (0x29, "V"), + (0x2A, "V"), + (0x2B, "V"), + (0x2C, "V"), + (0x2D, "V"), + (0x2E, "V"), + (0x2F, "V"), + (0x30, "V"), + (0x31, "V"), + (0x32, "V"), + (0x33, "V"), + (0x34, "V"), + (0x35, "V"), + (0x36, "V"), + (0x37, "V"), + (0x38, "V"), + (0x39, "V"), + (0x3A, "V"), + (0x3B, "V"), + (0x3C, "V"), + (0x3D, "V"), + (0x3E, "V"), + (0x3F, "V"), + (0x40, "V"), + (0x41, "M", "a"), + (0x42, "M", "b"), + (0x43, "M", "c"), + (0x44, "M", "d"), + (0x45, "M", "e"), + (0x46, "M", "f"), + (0x47, "M", "g"), + (0x48, "M", "h"), + (0x49, "M", "i"), + (0x4A, "M", "j"), + (0x4B, "M", "k"), + (0x4C, "M", "l"), + (0x4D, "M", "m"), + (0x4E, "M", "n"), + (0x4F, "M", "o"), + (0x50, "M", "p"), + (0x51, "M", "q"), + (0x52, "M", "r"), + (0x53, "M", "s"), + (0x54, "M", "t"), + (0x55, "M", "u"), + (0x56, "M", "v"), + (0x57, "M", "w"), + (0x58, "M", "x"), + (0x59, "M", "y"), + (0x5A, "M", "z"), + (0x5B, "V"), + (0x5C, "V"), + (0x5D, "V"), + (0x5E, "V"), + (0x5F, "V"), + (0x60, "V"), + (0x61, "V"), + (0x62, "V"), + (0x63, "V"), + (0x64, "V"), + (0x65, "V"), + (0x66, "V"), + (0x67, "V"), + (0x68, "V"), + (0x69, "V"), + (0x6A, "V"), + (0x6B, "V"), + (0x6C, "V"), + (0x6D, "V"), + (0x6E, "V"), + (0x6F, "V"), + (0x70, "V"), + (0x71, "V"), + (0x72, "V"), + (0x73, "V"), + (0x74, "V"), + (0x75, "V"), + (0x76, "V"), + (0x77, "V"), + (0x78, "V"), + (0x79, "V"), + (0x7A, "V"), + (0x7B, "V"), + (0x7C, "V"), + (0x7D, "V"), + (0x7E, "V"), + (0x7F, "V"), + (0x80, "X"), + (0x81, "X"), + (0x82, "X"), + (0x83, "X"), + (0x84, "X"), + (0x85, "X"), + (0x86, "X"), + (0x87, "X"), + (0x88, "X"), + (0x89, "X"), + (0x8A, "X"), + (0x8B, "X"), + (0x8C, "X"), + (0x8D, "X"), + (0x8E, "X"), + (0x8F, "X"), + (0x90, "X"), + (0x91, "X"), + (0x92, "X"), + (0x93, "X"), + (0x94, "X"), + (0x95, "X"), + (0x96, "X"), + (0x97, "X"), + (0x98, "X"), + (0x99, "X"), + (0x9A, "X"), + (0x9B, "X"), + (0x9C, "X"), + (0x9D, "X"), + (0x9E, "X"), + (0x9F, "X"), + (0xA0, "M", " "), + (0xA1, "V"), + (0xA2, "V"), + (0xA3, "V"), + (0xA4, "V"), + (0xA5, "V"), + (0xA6, "V"), + (0xA7, "V"), + (0xA8, "M", " ̈"), + (0xA9, "V"), + (0xAA, "M", "a"), + (0xAB, "V"), + (0xAC, "V"), + (0xAD, "I"), + (0xAE, "V"), + (0xAF, "M", " ̄"), + (0xB0, "V"), + (0xB1, "V"), + (0xB2, "M", "2"), + (0xB3, "M", "3"), + (0xB4, "M", " ́"), + (0xB5, "M", "μ"), + (0xB6, "V"), + (0xB7, "V"), + (0xB8, "M", " ̧"), + (0xB9, "M", "1"), + (0xBA, "M", "o"), + (0xBB, "V"), + (0xBC, "M", "1⁄4"), + (0xBD, "M", "1⁄2"), + (0xBE, "M", "3⁄4"), + (0xBF, "V"), + (0xC0, "M", "à"), + (0xC1, "M", "á"), + (0xC2, "M", "â"), + (0xC3, "M", "ã"), + (0xC4, "M", "ä"), + (0xC5, "M", "å"), + (0xC6, "M", "æ"), + (0xC7, "M", "ç"), + (0xC8, "M", "è"), + (0xC9, "M", "é"), + (0xCA, "M", "ê"), + (0xCB, "M", "ë"), + (0xCC, "M", "ì"), + (0xCD, "M", "í"), + (0xCE, "M", "î"), + (0xCF, "M", "ï"), + (0xD0, "M", "ð"), + (0xD1, "M", "ñ"), + (0xD2, "M", "ò"), + (0xD3, "M", "ó"), + (0xD4, "M", "ô"), + (0xD5, "M", "õ"), + (0xD6, "M", "ö"), + (0xD7, "V"), + (0xD8, "M", "ø"), + (0xD9, "M", "ù"), + (0xDA, "M", "ú"), + (0xDB, "M", "û"), + (0xDC, "M", "ü"), + (0xDD, "M", "ý"), + (0xDE, "M", "þ"), + (0xDF, "D", "ss"), + (0xE0, "V"), + (0xE1, "V"), + (0xE2, "V"), + (0xE3, "V"), + (0xE4, "V"), + (0xE5, "V"), + (0xE6, "V"), + (0xE7, "V"), + (0xE8, "V"), + (0xE9, "V"), + (0xEA, "V"), + (0xEB, "V"), + (0xEC, "V"), + (0xED, "V"), + (0xEE, "V"), + (0xEF, "V"), + (0xF0, "V"), + (0xF1, "V"), + (0xF2, "V"), + (0xF3, "V"), + (0xF4, "V"), + (0xF5, "V"), + (0xF6, "V"), + (0xF7, "V"), + (0xF8, "V"), + (0xF9, "V"), + (0xFA, "V"), + (0xFB, "V"), + (0xFC, "V"), + (0xFD, "V"), + (0xFE, "V"), + (0xFF, "V"), + (0x100, "M", "ā"), + (0x101, "V"), + (0x102, "M", "ă"), + (0x103, "V"), + (0x104, "M", "ą"), + (0x105, "V"), + (0x106, "M", "ć"), + (0x107, "V"), + (0x108, "M", "ĉ"), + (0x109, "V"), + (0x10A, "M", "ċ"), + (0x10B, "V"), + (0x10C, "M", "č"), + (0x10D, "V"), + (0x10E, "M", "ď"), + (0x10F, "V"), + (0x110, "M", "đ"), + (0x111, "V"), + (0x112, "M", "ē"), + (0x113, "V"), + (0x114, "M", "ĕ"), + (0x115, "V"), + (0x116, "M", "ė"), + (0x117, "V"), + (0x118, "M", "ę"), + (0x119, "V"), + (0x11A, "M", "ě"), + (0x11B, "V"), + (0x11C, "M", "ĝ"), + (0x11D, "V"), + (0x11E, "M", "ğ"), + (0x11F, "V"), + (0x120, "M", "ġ"), + (0x121, "V"), + (0x122, "M", "ģ"), + (0x123, "V"), + (0x124, "M", "ĥ"), + (0x125, "V"), + (0x126, "M", "ħ"), + (0x127, "V"), + (0x128, "M", "ĩ"), + (0x129, "V"), + (0x12A, "M", "ī"), + (0x12B, "V"), + (0x12C, "M", "ĭ"), + (0x12D, "V"), + (0x12E, "M", "į"), + (0x12F, "V"), + (0x130, "M", "i̇"), + (0x131, "V"), + (0x132, "M", "ij"), + (0x134, "M", "ĵ"), + (0x135, "V"), + (0x136, "M", "ķ"), + (0x137, "V"), + (0x139, "M", "ĺ"), + (0x13A, "V"), + (0x13B, "M", "ļ"), + (0x13C, "V"), + (0x13D, "M", "ľ"), + (0x13E, "V"), + (0x13F, "M", "l·"), + (0x141, "M", "ł"), + (0x142, "V"), + (0x143, "M", "ń"), + (0x144, "V"), + (0x145, "M", "ņ"), + (0x146, "V"), + (0x147, "M", "ň"), + (0x148, "V"), + (0x149, "M", "ʼn"), + (0x14A, "M", "ŋ"), + (0x14B, "V"), + (0x14C, "M", "ō"), + (0x14D, "V"), + (0x14E, "M", "ŏ"), + (0x14F, "V"), + (0x150, "M", "ő"), + (0x151, "V"), + (0x152, "M", "œ"), + (0x153, "V"), + (0x154, "M", "ŕ"), + (0x155, "V"), + (0x156, "M", "ŗ"), + (0x157, "V"), + (0x158, "M", "ř"), + (0x159, "V"), + (0x15A, "M", "ś"), + (0x15B, "V"), + (0x15C, "M", "ŝ"), + (0x15D, "V"), + (0x15E, "M", "ş"), + (0x15F, "V"), + (0x160, "M", "š"), + (0x161, "V"), + (0x162, "M", "ţ"), + (0x163, "V"), + (0x164, "M", "ť"), + (0x165, "V"), + (0x166, "M", "ŧ"), + (0x167, "V"), + (0x168, "M", "ũ"), + (0x169, "V"), + (0x16A, "M", "ū"), + (0x16B, "V"), + (0x16C, "M", "ŭ"), + (0x16D, "V"), + (0x16E, "M", "ů"), + (0x16F, "V"), + (0x170, "M", "ű"), + (0x171, "V"), + (0x172, "M", "ų"), + (0x173, "V"), + (0x174, "M", "ŵ"), + (0x175, "V"), + (0x176, "M", "ŷ"), + (0x177, "V"), + (0x178, "M", "ÿ"), + (0x179, "M", "ź"), + (0x17A, "V"), + (0x17B, "M", "ż"), + (0x17C, "V"), + (0x17D, "M", "ž"), + (0x17E, "V"), + (0x17F, "M", "s"), + (0x180, "V"), + (0x181, "M", "ɓ"), + (0x182, "M", "ƃ"), + (0x183, "V"), + (0x184, "M", "ƅ"), + (0x185, "V"), + (0x186, "M", "ɔ"), + (0x187, "M", "ƈ"), + (0x188, "V"), + (0x189, "M", "ɖ"), + (0x18A, "M", "ɗ"), + (0x18B, "M", "ƌ"), + (0x18C, "V"), + (0x18E, "M", "ǝ"), + (0x18F, "M", "ə"), + (0x190, "M", "ɛ"), + (0x191, "M", "ƒ"), + (0x192, "V"), + (0x193, "M", "ɠ"), + (0x194, "M", "ɣ"), + (0x195, "V"), + (0x196, "M", "ɩ"), + (0x197, "M", "ɨ"), + (0x198, "M", "ƙ"), + (0x199, "V"), + (0x19C, "M", "ɯ"), + (0x19D, "M", "ɲ"), + (0x19E, "V"), + (0x19F, "M", "ɵ"), + (0x1A0, "M", "ơ"), + (0x1A1, "V"), + (0x1A2, "M", "ƣ"), + (0x1A3, "V"), + (0x1A4, "M", "ƥ"), + (0x1A5, "V"), + (0x1A6, "M", "ʀ"), + (0x1A7, "M", "ƨ"), + (0x1A8, "V"), + (0x1A9, "M", "ʃ"), + (0x1AA, "V"), + (0x1AC, "M", "ƭ"), + (0x1AD, "V"), + (0x1AE, "M", "ʈ"), + (0x1AF, "M", "ư"), + (0x1B0, "V"), + (0x1B1, "M", "ʊ"), + (0x1B2, "M", "ʋ"), + (0x1B3, "M", "ƴ"), + (0x1B4, "V"), + (0x1B5, "M", "ƶ"), + (0x1B6, "V"), + (0x1B7, "M", "ʒ"), + (0x1B8, "M", "ƹ"), + (0x1B9, "V"), + (0x1BC, "M", "ƽ"), + (0x1BD, "V"), + (0x1C4, "M", "dž"), + (0x1C7, "M", "lj"), + (0x1CA, "M", "nj"), + (0x1CD, "M", "ǎ"), + (0x1CE, "V"), + (0x1CF, "M", "ǐ"), + (0x1D0, "V"), + (0x1D1, "M", "ǒ"), + (0x1D2, "V"), + (0x1D3, "M", "ǔ"), + (0x1D4, "V"), + (0x1D5, "M", "ǖ"), + (0x1D6, "V"), + (0x1D7, "M", "ǘ"), + (0x1D8, "V"), + (0x1D9, "M", "ǚ"), + (0x1DA, "V"), + (0x1DB, "M", "ǜ"), + (0x1DC, "V"), + (0x1DE, "M", "ǟ"), + (0x1DF, "V"), + (0x1E0, "M", "ǡ"), + (0x1E1, "V"), + (0x1E2, "M", "ǣ"), + (0x1E3, "V"), + (0x1E4, "M", "ǥ"), + (0x1E5, "V"), + (0x1E6, "M", "ǧ"), + (0x1E7, "V"), + (0x1E8, "M", "ǩ"), + (0x1E9, "V"), + (0x1EA, "M", "ǫ"), + (0x1EB, "V"), + (0x1EC, "M", "ǭ"), + (0x1ED, "V"), + (0x1EE, "M", "ǯ"), + (0x1EF, "V"), + (0x1F1, "M", "dz"), + (0x1F4, "M", "ǵ"), + (0x1F5, "V"), + (0x1F6, "M", "ƕ"), + (0x1F7, "M", "ƿ"), + (0x1F8, "M", "ǹ"), + (0x1F9, "V"), + (0x1FA, "M", "ǻ"), + (0x1FB, "V"), + (0x1FC, "M", "ǽ"), + (0x1FD, "V"), + (0x1FE, "M", "ǿ"), + (0x1FF, "V"), + (0x200, "M", "ȁ"), + (0x201, "V"), + (0x202, "M", "ȃ"), + (0x203, "V"), + (0x204, "M", "ȅ"), + (0x205, "V"), + (0x206, "M", "ȇ"), + (0x207, "V"), + (0x208, "M", "ȉ"), + (0x209, "V"), + (0x20A, "M", "ȋ"), + (0x20B, "V"), + (0x20C, "M", "ȍ"), + (0x20D, "V"), + (0x20E, "M", "ȏ"), + (0x20F, "V"), + (0x210, "M", "ȑ"), + (0x211, "V"), + (0x212, "M", "ȓ"), + (0x213, "V"), + (0x214, "M", "ȕ"), + (0x215, "V"), + (0x216, "M", "ȗ"), + (0x217, "V"), + (0x218, "M", "ș"), + (0x219, "V"), + (0x21A, "M", "ț"), + (0x21B, "V"), + (0x21C, "M", "ȝ"), + (0x21D, "V"), + (0x21E, "M", "ȟ"), + (0x21F, "V"), + (0x220, "M", "ƞ"), + (0x221, "V"), + (0x222, "M", "ȣ"), + (0x223, "V"), + (0x224, "M", "ȥ"), + (0x225, "V"), + (0x226, "M", "ȧ"), + (0x227, "V"), + (0x228, "M", "ȩ"), + (0x229, "V"), + (0x22A, "M", "ȫ"), + (0x22B, "V"), + (0x22C, "M", "ȭ"), + (0x22D, "V"), + (0x22E, "M", "ȯ"), + (0x22F, "V"), + (0x230, "M", "ȱ"), + (0x231, "V"), + (0x232, "M", "ȳ"), + (0x233, "V"), + (0x23A, "M", "ⱥ"), + (0x23B, "M", "ȼ"), + (0x23C, "V"), + (0x23D, "M", "ƚ"), + (0x23E, "M", "ⱦ"), + (0x23F, "V"), + (0x241, "M", "ɂ"), + (0x242, "V"), + (0x243, "M", "ƀ"), + (0x244, "M", "ʉ"), + (0x245, "M", "ʌ"), + (0x246, "M", "ɇ"), + (0x247, "V"), + (0x248, "M", "ɉ"), + (0x249, "V"), + (0x24A, "M", "ɋ"), + (0x24B, "V"), + (0x24C, "M", "ɍ"), + (0x24D, "V"), + (0x24E, "M", "ɏ"), + (0x24F, "V"), + (0x2B0, "M", "h"), + (0x2B1, "M", "ɦ"), + (0x2B2, "M", "j"), + (0x2B3, "M", "r"), + (0x2B4, "M", "ɹ"), + (0x2B5, "M", "ɻ"), + (0x2B6, "M", "ʁ"), + (0x2B7, "M", "w"), + (0x2B8, "M", "y"), + (0x2B9, "V"), + (0x2D8, "M", " ̆"), + (0x2D9, "M", " ̇"), + (0x2DA, "M", " ̊"), + (0x2DB, "M", " ̨"), + (0x2DC, "M", " ̃"), + (0x2DD, "M", " ̋"), + (0x2DE, "V"), + (0x2E0, "M", "ɣ"), + (0x2E1, "M", "l"), + (0x2E2, "M", "s"), + (0x2E3, "M", "x"), + (0x2E4, "M", "ʕ"), + (0x2E5, "V"), + (0x340, "M", "̀"), + (0x341, "M", "́"), + (0x342, "V"), + (0x343, "M", "̓"), + (0x344, "M", "̈́"), + (0x345, "M", "ι"), + (0x346, "V"), + (0x34F, "I"), + (0x350, "V"), + (0x370, "M", "ͱ"), + (0x371, "V"), + (0x372, "M", "ͳ"), + (0x373, "V"), + (0x374, "M", "ʹ"), + (0x375, "V"), + (0x376, "M", "ͷ"), + (0x377, "V"), + (0x378, "X"), + (0x37A, "M", " ι"), + (0x37B, "V"), + (0x37E, "M", ";"), + (0x37F, "M", "ϳ"), + (0x380, "X"), + (0x384, "M", " ́"), + (0x385, "M", " ̈́"), + (0x386, "M", "ά"), + (0x387, "M", "·"), + (0x388, "M", "έ"), + (0x389, "M", "ή"), + (0x38A, "M", "ί"), + (0x38B, "X"), + (0x38C, "M", "ό"), + (0x38D, "X"), + (0x38E, "M", "ύ"), + (0x38F, "M", "ώ"), + (0x390, "V"), + (0x391, "M", "α"), + (0x392, "M", "β"), + (0x393, "M", "γ"), + (0x394, "M", "δ"), + (0x395, "M", "ε"), + (0x396, "M", "ζ"), + (0x397, "M", "η"), + (0x398, "M", "θ"), + (0x399, "M", "ι"), + (0x39A, "M", "κ"), + (0x39B, "M", "λ"), + (0x39C, "M", "μ"), + (0x39D, "M", "ν"), + (0x39E, "M", "ξ"), + (0x39F, "M", "ο"), + (0x3A0, "M", "π"), + (0x3A1, "M", "ρ"), + (0x3A2, "X"), + (0x3A3, "M", "σ"), + (0x3A4, "M", "τ"), + (0x3A5, "M", "υ"), + (0x3A6, "M", "φ"), + (0x3A7, "M", "χ"), + (0x3A8, "M", "ψ"), + (0x3A9, "M", "ω"), + (0x3AA, "M", "ϊ"), + (0x3AB, "M", "ϋ"), + (0x3AC, "V"), + (0x3C2, "D", "σ"), + (0x3C3, "V"), + (0x3CF, "M", "ϗ"), + (0x3D0, "M", "β"), + (0x3D1, "M", "θ"), + (0x3D2, "M", "υ"), + (0x3D3, "M", "ύ"), + (0x3D4, "M", "ϋ"), + (0x3D5, "M", "φ"), + (0x3D6, "M", "π"), + (0x3D7, "V"), + (0x3D8, "M", "ϙ"), + (0x3D9, "V"), + (0x3DA, "M", "ϛ"), + (0x3DB, "V"), + (0x3DC, "M", "ϝ"), + (0x3DD, "V"), + (0x3DE, "M", "ϟ"), + (0x3DF, "V"), + (0x3E0, "M", "ϡ"), + (0x3E1, "V"), + (0x3E2, "M", "ϣ"), + (0x3E3, "V"), + (0x3E4, "M", "ϥ"), + (0x3E5, "V"), + (0x3E6, "M", "ϧ"), + (0x3E7, "V"), + (0x3E8, "M", "ϩ"), + (0x3E9, "V"), + (0x3EA, "M", "ϫ"), + (0x3EB, "V"), + (0x3EC, "M", "ϭ"), + (0x3ED, "V"), + (0x3EE, "M", "ϯ"), + (0x3EF, "V"), + (0x3F0, "M", "κ"), + (0x3F1, "M", "ρ"), + (0x3F2, "M", "σ"), + (0x3F3, "V"), + (0x3F4, "M", "θ"), + (0x3F5, "M", "ε"), + (0x3F6, "V"), + (0x3F7, "M", "ϸ"), + (0x3F8, "V"), + (0x3F9, "M", "σ"), + (0x3FA, "M", "ϻ"), + (0x3FB, "V"), + (0x3FD, "M", "ͻ"), + (0x3FE, "M", "ͼ"), + (0x3FF, "M", "ͽ"), + (0x400, "M", "ѐ"), + (0x401, "M", "ё"), + (0x402, "M", "ђ"), + (0x403, "M", "ѓ"), + (0x404, "M", "є"), + (0x405, "M", "ѕ"), + (0x406, "M", "і"), + (0x407, "M", "ї"), + (0x408, "M", "ј"), + (0x409, "M", "љ"), + (0x40A, "M", "њ"), + (0x40B, "M", "ћ"), + (0x40C, "M", "ќ"), + (0x40D, "M", "ѝ"), + (0x40E, "M", "ў"), + (0x40F, "M", "џ"), + (0x410, "M", "а"), + (0x411, "M", "б"), + (0x412, "M", "в"), + (0x413, "M", "г"), + (0x414, "M", "д"), + (0x415, "M", "е"), + (0x416, "M", "ж"), + (0x417, "M", "з"), + (0x418, "M", "и"), + (0x419, "M", "й"), + (0x41A, "M", "к"), + (0x41B, "M", "л"), + (0x41C, "M", "м"), + (0x41D, "M", "н"), + (0x41E, "M", "о"), + (0x41F, "M", "п"), + (0x420, "M", "р"), + (0x421, "M", "с"), + (0x422, "M", "т"), + (0x423, "M", "у"), + (0x424, "M", "ф"), + (0x425, "M", "х"), + (0x426, "M", "ц"), + (0x427, "M", "ч"), + (0x428, "M", "ш"), + (0x429, "M", "щ"), + (0x42A, "M", "ъ"), + (0x42B, "M", "ы"), + (0x42C, "M", "ь"), + (0x42D, "M", "э"), + (0x42E, "M", "ю"), + (0x42F, "M", "я"), + (0x430, "V"), + (0x460, "M", "ѡ"), + (0x461, "V"), + (0x462, "M", "ѣ"), + (0x463, "V"), + (0x464, "M", "ѥ"), + (0x465, "V"), + (0x466, "M", "ѧ"), + (0x467, "V"), + (0x468, "M", "ѩ"), + (0x469, "V"), + (0x46A, "M", "ѫ"), + (0x46B, "V"), + (0x46C, "M", "ѭ"), + (0x46D, "V"), + (0x46E, "M", "ѯ"), + (0x46F, "V"), + (0x470, "M", "ѱ"), + (0x471, "V"), + (0x472, "M", "ѳ"), + (0x473, "V"), + (0x474, "M", "ѵ"), + (0x475, "V"), + (0x476, "M", "ѷ"), + (0x477, "V"), + (0x478, "M", "ѹ"), + (0x479, "V"), + (0x47A, "M", "ѻ"), + (0x47B, "V"), + (0x47C, "M", "ѽ"), + (0x47D, "V"), + (0x47E, "M", "ѿ"), + (0x47F, "V"), + (0x480, "M", "ҁ"), + (0x481, "V"), + (0x48A, "M", "ҋ"), + (0x48B, "V"), + (0x48C, "M", "ҍ"), + (0x48D, "V"), + (0x48E, "M", "ҏ"), + (0x48F, "V"), + (0x490, "M", "ґ"), + (0x491, "V"), + (0x492, "M", "ғ"), + (0x493, "V"), + (0x494, "M", "ҕ"), + (0x495, "V"), + (0x496, "M", "җ"), + (0x497, "V"), + (0x498, "M", "ҙ"), + (0x499, "V"), + (0x49A, "M", "қ"), + (0x49B, "V"), + (0x49C, "M", "ҝ"), + (0x49D, "V"), + (0x49E, "M", "ҟ"), + (0x49F, "V"), + (0x4A0, "M", "ҡ"), + (0x4A1, "V"), + (0x4A2, "M", "ң"), + (0x4A3, "V"), + (0x4A4, "M", "ҥ"), + (0x4A5, "V"), + (0x4A6, "M", "ҧ"), + (0x4A7, "V"), + (0x4A8, "M", "ҩ"), + (0x4A9, "V"), + (0x4AA, "M", "ҫ"), + (0x4AB, "V"), + (0x4AC, "M", "ҭ"), + (0x4AD, "V"), + (0x4AE, "M", "ү"), + (0x4AF, "V"), + (0x4B0, "M", "ұ"), + (0x4B1, "V"), + (0x4B2, "M", "ҳ"), + (0x4B3, "V"), + (0x4B4, "M", "ҵ"), + (0x4B5, "V"), + (0x4B6, "M", "ҷ"), + (0x4B7, "V"), + (0x4B8, "M", "ҹ"), + (0x4B9, "V"), + (0x4BA, "M", "һ"), + (0x4BB, "V"), + (0x4BC, "M", "ҽ"), + (0x4BD, "V"), + (0x4BE, "M", "ҿ"), + (0x4BF, "V"), + (0x4C0, "M", "ӏ"), + (0x4C1, "M", "ӂ"), + (0x4C2, "V"), + (0x4C3, "M", "ӄ"), + (0x4C4, "V"), + (0x4C5, "M", "ӆ"), + (0x4C6, "V"), + (0x4C7, "M", "ӈ"), + (0x4C8, "V"), + (0x4C9, "M", "ӊ"), + (0x4CA, "V"), + (0x4CB, "M", "ӌ"), + (0x4CC, "V"), + (0x4CD, "M", "ӎ"), + (0x4CE, "V"), + (0x4D0, "M", "ӑ"), + (0x4D1, "V"), + (0x4D2, "M", "ӓ"), + (0x4D3, "V"), + (0x4D4, "M", "ӕ"), + (0x4D5, "V"), + (0x4D6, "M", "ӗ"), + (0x4D7, "V"), + (0x4D8, "M", "ә"), + (0x4D9, "V"), + (0x4DA, "M", "ӛ"), + (0x4DB, "V"), + (0x4DC, "M", "ӝ"), + (0x4DD, "V"), + (0x4DE, "M", "ӟ"), + (0x4DF, "V"), + (0x4E0, "M", "ӡ"), + (0x4E1, "V"), + (0x4E2, "M", "ӣ"), + (0x4E3, "V"), + (0x4E4, "M", "ӥ"), + (0x4E5, "V"), + (0x4E6, "M", "ӧ"), + (0x4E7, "V"), + (0x4E8, "M", "ө"), + (0x4E9, "V"), + (0x4EA, "M", "ӫ"), + (0x4EB, "V"), + (0x4EC, "M", "ӭ"), + (0x4ED, "V"), + (0x4EE, "M", "ӯ"), + (0x4EF, "V"), + (0x4F0, "M", "ӱ"), + (0x4F1, "V"), + (0x4F2, "M", "ӳ"), + (0x4F3, "V"), + (0x4F4, "M", "ӵ"), + (0x4F5, "V"), + (0x4F6, "M", "ӷ"), + (0x4F7, "V"), + (0x4F8, "M", "ӹ"), + (0x4F9, "V"), + (0x4FA, "M", "ӻ"), + (0x4FB, "V"), + (0x4FC, "M", "ӽ"), + (0x4FD, "V"), + (0x4FE, "M", "ӿ"), + (0x4FF, "V"), + (0x500, "M", "ԁ"), + (0x501, "V"), + (0x502, "M", "ԃ"), + (0x503, "V"), + (0x504, "M", "ԅ"), + (0x505, "V"), + (0x506, "M", "ԇ"), + (0x507, "V"), + (0x508, "M", "ԉ"), + (0x509, "V"), + (0x50A, "M", "ԋ"), + (0x50B, "V"), + (0x50C, "M", "ԍ"), + (0x50D, "V"), + (0x50E, "M", "ԏ"), + (0x50F, "V"), + (0x510, "M", "ԑ"), + (0x511, "V"), + (0x512, "M", "ԓ"), + (0x513, "V"), + (0x514, "M", "ԕ"), + (0x515, "V"), + (0x516, "M", "ԗ"), + (0x517, "V"), + (0x518, "M", "ԙ"), + (0x519, "V"), + (0x51A, "M", "ԛ"), + (0x51B, "V"), + (0x51C, "M", "ԝ"), + (0x51D, "V"), + (0x51E, "M", "ԟ"), + (0x51F, "V"), + (0x520, "M", "ԡ"), + (0x521, "V"), + (0x522, "M", "ԣ"), + (0x523, "V"), + (0x524, "M", "ԥ"), + (0x525, "V"), + (0x526, "M", "ԧ"), + (0x527, "V"), + (0x528, "M", "ԩ"), + (0x529, "V"), + (0x52A, "M", "ԫ"), + (0x52B, "V"), + (0x52C, "M", "ԭ"), + (0x52D, "V"), + (0x52E, "M", "ԯ"), + (0x52F, "V"), + (0x530, "X"), + (0x531, "M", "ա"), + (0x532, "M", "բ"), + (0x533, "M", "գ"), + (0x534, "M", "դ"), + (0x535, "M", "ե"), + (0x536, "M", "զ"), + (0x537, "M", "է"), + (0x538, "M", "ը"), + (0x539, "M", "թ"), + (0x53A, "M", "ժ"), + (0x53B, "M", "ի"), + (0x53C, "M", "լ"), + (0x53D, "M", "խ"), + (0x53E, "M", "ծ"), + (0x53F, "M", "կ"), + (0x540, "M", "հ"), + (0x541, "M", "ձ"), + (0x542, "M", "ղ"), + (0x543, "M", "ճ"), + (0x544, "M", "մ"), + (0x545, "M", "յ"), + (0x546, "M", "ն"), + (0x547, "M", "շ"), + (0x548, "M", "ո"), + (0x549, "M", "չ"), + (0x54A, "M", "պ"), + (0x54B, "M", "ջ"), + (0x54C, "M", "ռ"), + (0x54D, "M", "ս"), + (0x54E, "M", "վ"), + (0x54F, "M", "տ"), + (0x550, "M", "ր"), + (0x551, "M", "ց"), + (0x552, "M", "ւ"), + (0x553, "M", "փ"), + (0x554, "M", "ք"), + (0x555, "M", "օ"), + (0x556, "M", "ֆ"), + (0x557, "X"), + (0x559, "V"), + (0x587, "M", "եւ"), + (0x588, "V"), + (0x58B, "X"), + (0x58D, "V"), + (0x590, "X"), + (0x591, "V"), + (0x5C8, "X"), + (0x5D0, "V"), + (0x5EB, "X"), + (0x5EF, "V"), + (0x5F5, "X"), + (0x606, "V"), + (0x61C, "X"), + (0x61D, "V"), + (0x675, "M", "اٴ"), + (0x676, "M", "وٴ"), + (0x677, "M", "ۇٴ"), + (0x678, "M", "يٴ"), + (0x679, "V"), + (0x6DD, "X"), + (0x6DE, "V"), + (0x70E, "X"), + (0x710, "V"), + (0x74B, "X"), + (0x74D, "V"), + (0x7B2, "X"), + (0x7C0, "V"), + (0x7FB, "X"), + (0x7FD, "V"), + (0x82E, "X"), + (0x830, "V"), + (0x83F, "X"), + (0x840, "V"), + (0x85C, "X"), + (0x85E, "V"), + (0x85F, "X"), + (0x860, "V"), + (0x86B, "X"), + (0x870, "V"), + (0x890, "X"), + (0x897, "V"), + (0x8E2, "X"), + (0x8E3, "V"), + (0x958, "M", "क़"), + (0x959, "M", "ख़"), + (0x95A, "M", "ग़"), + (0x95B, "M", "ज़"), + (0x95C, "M", "ड़"), + (0x95D, "M", "ढ़"), + (0x95E, "M", "फ़"), + (0x95F, "M", "य़"), + (0x960, "V"), + (0x984, "X"), + (0x985, "V"), + (0x98D, "X"), + (0x98F, "V"), + (0x991, "X"), + (0x993, "V"), + (0x9A9, "X"), + (0x9AA, "V"), + (0x9B1, "X"), + (0x9B2, "V"), + (0x9B3, "X"), + (0x9B6, "V"), + (0x9BA, "X"), + (0x9BC, "V"), + (0x9C5, "X"), + (0x9C7, "V"), + (0x9C9, "X"), + (0x9CB, "V"), + (0x9CF, "X"), + (0x9D7, "V"), + (0x9D8, "X"), + (0x9DC, "M", "ড়"), + (0x9DD, "M", "ঢ়"), + (0x9DE, "X"), + (0x9DF, "M", "য়"), + (0x9E0, "V"), + (0x9E4, "X"), + (0x9E6, "V"), + (0x9FF, "X"), + (0xA01, "V"), + (0xA04, "X"), + (0xA05, "V"), + (0xA0B, "X"), + (0xA0F, "V"), + (0xA11, "X"), + (0xA13, "V"), + (0xA29, "X"), + (0xA2A, "V"), + (0xA31, "X"), + (0xA32, "V"), + (0xA33, "M", "ਲ਼"), + (0xA34, "X"), + (0xA35, "V"), + (0xA36, "M", "ਸ਼"), + (0xA37, "X"), + (0xA38, "V"), + (0xA3A, "X"), + (0xA3C, "V"), + (0xA3D, "X"), + (0xA3E, "V"), + (0xA43, "X"), + (0xA47, "V"), + (0xA49, "X"), + (0xA4B, "V"), + (0xA4E, "X"), + (0xA51, "V"), + (0xA52, "X"), + (0xA59, "M", "ਖ਼"), + (0xA5A, "M", "ਗ਼"), + (0xA5B, "M", "ਜ਼"), + (0xA5C, "V"), + (0xA5D, "X"), + (0xA5E, "M", "ਫ਼"), + (0xA5F, "X"), + (0xA66, "V"), + (0xA77, "X"), + (0xA81, "V"), + (0xA84, "X"), + (0xA85, "V"), + (0xA8E, "X"), + (0xA8F, "V"), + (0xA92, "X"), + (0xA93, "V"), + (0xAA9, "X"), + (0xAAA, "V"), + (0xAB1, "X"), + (0xAB2, "V"), + (0xAB4, "X"), + (0xAB5, "V"), + (0xABA, "X"), + (0xABC, "V"), + (0xAC6, "X"), + (0xAC7, "V"), + (0xACA, "X"), + (0xACB, "V"), + (0xACE, "X"), + (0xAD0, "V"), + (0xAD1, "X"), + (0xAE0, "V"), + (0xAE4, "X"), + (0xAE6, "V"), + (0xAF2, "X"), + (0xAF9, "V"), + (0xB00, "X"), + (0xB01, "V"), + (0xB04, "X"), + (0xB05, "V"), + (0xB0D, "X"), + (0xB0F, "V"), + (0xB11, "X"), + (0xB13, "V"), + (0xB29, "X"), + (0xB2A, "V"), + (0xB31, "X"), + (0xB32, "V"), + (0xB34, "X"), + (0xB35, "V"), + (0xB3A, "X"), + (0xB3C, "V"), + (0xB45, "X"), + (0xB47, "V"), + (0xB49, "X"), + (0xB4B, "V"), + (0xB4E, "X"), + (0xB55, "V"), + (0xB58, "X"), + (0xB5C, "M", "ଡ଼"), + (0xB5D, "M", "ଢ଼"), + (0xB5E, "X"), + (0xB5F, "V"), + (0xB64, "X"), + (0xB66, "V"), + (0xB78, "X"), + (0xB82, "V"), + (0xB84, "X"), + (0xB85, "V"), + (0xB8B, "X"), + (0xB8E, "V"), + (0xB91, "X"), + (0xB92, "V"), + (0xB96, "X"), + (0xB99, "V"), + (0xB9B, "X"), + (0xB9C, "V"), + (0xB9D, "X"), + (0xB9E, "V"), + (0xBA0, "X"), + (0xBA3, "V"), + (0xBA5, "X"), + (0xBA8, "V"), + (0xBAB, "X"), + (0xBAE, "V"), + (0xBBA, "X"), + (0xBBE, "V"), + (0xBC3, "X"), + (0xBC6, "V"), + (0xBC9, "X"), + (0xBCA, "V"), + (0xBCE, "X"), + (0xBD0, "V"), + (0xBD1, "X"), + (0xBD7, "V"), + (0xBD8, "X"), + (0xBE6, "V"), + (0xBFB, "X"), + (0xC00, "V"), + (0xC0D, "X"), + (0xC0E, "V"), + (0xC11, "X"), + (0xC12, "V"), + (0xC29, "X"), + (0xC2A, "V"), + (0xC3A, "X"), + (0xC3C, "V"), + (0xC45, "X"), + (0xC46, "V"), + (0xC49, "X"), + (0xC4A, "V"), + (0xC4E, "X"), + (0xC55, "V"), + (0xC57, "X"), + (0xC58, "V"), + (0xC5B, "X"), + (0xC5C, "V"), + (0xC5E, "X"), + (0xC60, "V"), + (0xC64, "X"), + (0xC66, "V"), + (0xC70, "X"), + (0xC77, "V"), + (0xC8D, "X"), + (0xC8E, "V"), + (0xC91, "X"), + (0xC92, "V"), + (0xCA9, "X"), + (0xCAA, "V"), + (0xCB4, "X"), + (0xCB5, "V"), + (0xCBA, "X"), + (0xCBC, "V"), + (0xCC5, "X"), + (0xCC6, "V"), + (0xCC9, "X"), + (0xCCA, "V"), + (0xCCE, "X"), + (0xCD5, "V"), + (0xCD7, "X"), + (0xCDC, "V"), + (0xCDF, "X"), + (0xCE0, "V"), + (0xCE4, "X"), + (0xCE6, "V"), + (0xCF0, "X"), + (0xCF1, "V"), + (0xCF4, "X"), + (0xD00, "V"), + (0xD0D, "X"), + (0xD0E, "V"), + (0xD11, "X"), + (0xD12, "V"), + (0xD45, "X"), + (0xD46, "V"), + (0xD49, "X"), + (0xD4A, "V"), + (0xD50, "X"), + (0xD54, "V"), + (0xD64, "X"), + (0xD66, "V"), + (0xD80, "X"), + (0xD81, "V"), + (0xD84, "X"), + (0xD85, "V"), + (0xD97, "X"), + (0xD9A, "V"), + (0xDB2, "X"), + (0xDB3, "V"), + (0xDBC, "X"), + (0xDBD, "V"), + (0xDBE, "X"), + (0xDC0, "V"), + (0xDC7, "X"), + (0xDCA, "V"), + (0xDCB, "X"), + (0xDCF, "V"), + (0xDD5, "X"), + (0xDD6, "V"), + (0xDD7, "X"), + (0xDD8, "V"), + (0xDE0, "X"), + (0xDE6, "V"), + (0xDF0, "X"), + (0xDF2, "V"), + (0xDF5, "X"), + (0xE01, "V"), + (0xE33, "M", "ํา"), + (0xE34, "V"), + (0xE3B, "X"), + (0xE3F, "V"), + (0xE5C, "X"), + (0xE81, "V"), + (0xE83, "X"), + (0xE84, "V"), + (0xE85, "X"), + (0xE86, "V"), + (0xE8B, "X"), + (0xE8C, "V"), + (0xEA4, "X"), + (0xEA5, "V"), + (0xEA6, "X"), + (0xEA7, "V"), + (0xEB3, "M", "ໍາ"), + (0xEB4, "V"), + (0xEBE, "X"), + (0xEC0, "V"), + (0xEC5, "X"), + (0xEC6, "V"), + (0xEC7, "X"), + (0xEC8, "V"), + (0xECF, "X"), + (0xED0, "V"), + (0xEDA, "X"), + (0xEDC, "M", "ຫນ"), + (0xEDD, "M", "ຫມ"), + (0xEDE, "V"), + (0xEE0, "X"), + (0xF00, "V"), + (0xF0C, "M", "་"), + (0xF0D, "V"), + (0xF43, "M", "གྷ"), + (0xF44, "V"), + (0xF48, "X"), + (0xF49, "V"), + (0xF4D, "M", "ཌྷ"), + (0xF4E, "V"), + (0xF52, "M", "དྷ"), + (0xF53, "V"), + (0xF57, "M", "བྷ"), + (0xF58, "V"), + (0xF5C, "M", "ཛྷ"), + (0xF5D, "V"), + (0xF69, "M", "ཀྵ"), + (0xF6A, "V"), + (0xF6D, "X"), + (0xF71, "V"), + (0xF73, "M", "ཱི"), + (0xF74, "V"), + (0xF75, "M", "ཱུ"), + (0xF76, "M", "ྲྀ"), + (0xF77, "M", "ྲཱྀ"), + (0xF78, "M", "ླྀ"), + (0xF79, "M", "ླཱྀ"), + (0xF7A, "V"), + (0xF81, "M", "ཱྀ"), + (0xF82, "V"), + (0xF93, "M", "ྒྷ"), + (0xF94, "V"), + (0xF98, "X"), + (0xF99, "V"), + (0xF9D, "M", "ྜྷ"), + (0xF9E, "V"), + (0xFA2, "M", "ྡྷ"), + (0xFA3, "V"), + (0xFA7, "M", "ྦྷ"), + (0xFA8, "V"), + (0xFAC, "M", "ྫྷ"), + (0xFAD, "V"), + (0xFB9, "M", "ྐྵ"), + (0xFBA, "V"), + (0xFBD, "X"), + (0xFBE, "V"), + (0xFCD, "X"), + (0xFCE, "V"), + (0xFDB, "X"), + (0x1000, "V"), + (0x10A0, "M", "ⴀ"), + (0x10A1, "M", "ⴁ"), + (0x10A2, "M", "ⴂ"), + (0x10A3, "M", "ⴃ"), + (0x10A4, "M", "ⴄ"), + (0x10A5, "M", "ⴅ"), + (0x10A6, "M", "ⴆ"), + (0x10A7, "M", "ⴇ"), + (0x10A8, "M", "ⴈ"), + (0x10A9, "M", "ⴉ"), + (0x10AA, "M", "ⴊ"), + (0x10AB, "M", "ⴋ"), + (0x10AC, "M", "ⴌ"), + (0x10AD, "M", "ⴍ"), + (0x10AE, "M", "ⴎ"), + (0x10AF, "M", "ⴏ"), + (0x10B0, "M", "ⴐ"), + (0x10B1, "M", "ⴑ"), + (0x10B2, "M", "ⴒ"), + (0x10B3, "M", "ⴓ"), + (0x10B4, "M", "ⴔ"), + (0x10B5, "M", "ⴕ"), + (0x10B6, "M", "ⴖ"), + (0x10B7, "M", "ⴗ"), + (0x10B8, "M", "ⴘ"), + (0x10B9, "M", "ⴙ"), + (0x10BA, "M", "ⴚ"), + (0x10BB, "M", "ⴛ"), + (0x10BC, "M", "ⴜ"), + (0x10BD, "M", "ⴝ"), + (0x10BE, "M", "ⴞ"), + (0x10BF, "M", "ⴟ"), + (0x10C0, "M", "ⴠ"), + (0x10C1, "M", "ⴡ"), + (0x10C2, "M", "ⴢ"), + (0x10C3, "M", "ⴣ"), + (0x10C4, "M", "ⴤ"), + (0x10C5, "M", "ⴥ"), + (0x10C6, "X"), + (0x10C7, "M", "ⴧ"), + (0x10C8, "X"), + (0x10CD, "M", "ⴭ"), + (0x10CE, "X"), + (0x10D0, "V"), + (0x10FC, "M", "ნ"), + (0x10FD, "V"), + (0x115F, "I"), + (0x1161, "V"), + (0x1249, "X"), + (0x124A, "V"), + (0x124E, "X"), + (0x1250, "V"), + (0x1257, "X"), + (0x1258, "V"), + (0x1259, "X"), + (0x125A, "V"), + (0x125E, "X"), + (0x1260, "V"), + (0x1289, "X"), + (0x128A, "V"), + (0x128E, "X"), + (0x1290, "V"), + (0x12B1, "X"), + (0x12B2, "V"), + (0x12B6, "X"), + (0x12B8, "V"), + (0x12BF, "X"), + (0x12C0, "V"), + (0x12C1, "X"), + (0x12C2, "V"), + (0x12C6, "X"), + (0x12C8, "V"), + (0x12D7, "X"), + (0x12D8, "V"), + (0x1311, "X"), + (0x1312, "V"), + (0x1316, "X"), + (0x1318, "V"), + (0x135B, "X"), + (0x135D, "V"), + (0x137D, "X"), + (0x1380, "V"), + (0x139A, "X"), + (0x13A0, "V"), + (0x13F6, "X"), + (0x13F8, "M", "Ᏸ"), + (0x13F9, "M", "Ᏹ"), + (0x13FA, "M", "Ᏺ"), + (0x13FB, "M", "Ᏻ"), + (0x13FC, "M", "Ᏼ"), + (0x13FD, "M", "Ᏽ"), + (0x13FE, "X"), + (0x1400, "V"), + (0x1680, "X"), + (0x1681, "V"), + (0x169D, "X"), + (0x16A0, "V"), + (0x16F9, "X"), + (0x1700, "V"), + (0x1716, "X"), + (0x171F, "V"), + (0x1737, "X"), + (0x1740, "V"), + (0x1754, "X"), + (0x1760, "V"), + (0x176D, "X"), + (0x176E, "V"), + (0x1771, "X"), + (0x1772, "V"), + (0x1774, "X"), + (0x1780, "V"), + (0x17B4, "I"), + (0x17B6, "V"), + (0x17DE, "X"), + (0x17E0, "V"), + (0x17EA, "X"), + (0x17F0, "V"), + (0x17FA, "X"), + (0x1800, "V"), + (0x180B, "I"), + (0x1810, "V"), + (0x181A, "X"), + (0x1820, "V"), + (0x1879, "X"), + (0x1880, "V"), + (0x18AB, "X"), + (0x18B0, "V"), + (0x18F6, "X"), + (0x1900, "V"), + (0x191F, "X"), + (0x1920, "V"), + (0x192C, "X"), + (0x1930, "V"), + (0x193C, "X"), + (0x1940, "V"), + (0x1941, "X"), + (0x1944, "V"), + (0x196E, "X"), + (0x1970, "V"), + (0x1975, "X"), + (0x1980, "V"), + (0x19AC, "X"), + (0x19B0, "V"), + (0x19CA, "X"), + (0x19D0, "V"), + (0x19DB, "X"), + (0x19DE, "V"), + (0x1A1C, "X"), + (0x1A1E, "V"), + (0x1A5F, "X"), + (0x1A60, "V"), + (0x1A7D, "X"), + (0x1A7F, "V"), + (0x1A8A, "X"), + (0x1A90, "V"), + (0x1A9A, "X"), + (0x1AA0, "V"), + (0x1AAE, "X"), + (0x1AB0, "V"), + (0x1ADE, "X"), + (0x1AE0, "V"), + (0x1AEC, "X"), + (0x1B00, "V"), + (0x1B4D, "X"), + (0x1B4E, "V"), + (0x1BF4, "X"), + (0x1BFC, "V"), + (0x1C38, "X"), + (0x1C3B, "V"), + (0x1C4A, "X"), + (0x1C4D, "V"), + (0x1C80, "M", "в"), + (0x1C81, "M", "д"), + (0x1C82, "M", "о"), + (0x1C83, "M", "с"), + (0x1C84, "M", "т"), + (0x1C86, "M", "ъ"), + (0x1C87, "M", "ѣ"), + (0x1C88, "M", "ꙋ"), + (0x1C89, "M", "ᲊ"), + (0x1C8A, "V"), + (0x1C8B, "X"), + (0x1C90, "M", "ა"), + (0x1C91, "M", "ბ"), + (0x1C92, "M", "გ"), + (0x1C93, "M", "დ"), + (0x1C94, "M", "ე"), + (0x1C95, "M", "ვ"), + (0x1C96, "M", "ზ"), + (0x1C97, "M", "თ"), + (0x1C98, "M", "ი"), + (0x1C99, "M", "კ"), + (0x1C9A, "M", "ლ"), + (0x1C9B, "M", "მ"), + (0x1C9C, "M", "ნ"), + (0x1C9D, "M", "ო"), + (0x1C9E, "M", "პ"), + (0x1C9F, "M", "ჟ"), + (0x1CA0, "M", "რ"), + (0x1CA1, "M", "ს"), + (0x1CA2, "M", "ტ"), + (0x1CA3, "M", "უ"), + (0x1CA4, "M", "ფ"), + (0x1CA5, "M", "ქ"), + (0x1CA6, "M", "ღ"), + (0x1CA7, "M", "ყ"), + (0x1CA8, "M", "შ"), + (0x1CA9, "M", "ჩ"), + (0x1CAA, "M", "ც"), + (0x1CAB, "M", "ძ"), + (0x1CAC, "M", "წ"), + (0x1CAD, "M", "ჭ"), + (0x1CAE, "M", "ხ"), + (0x1CAF, "M", "ჯ"), + (0x1CB0, "M", "ჰ"), + (0x1CB1, "M", "ჱ"), + (0x1CB2, "M", "ჲ"), + (0x1CB3, "M", "ჳ"), + (0x1CB4, "M", "ჴ"), + (0x1CB5, "M", "ჵ"), + (0x1CB6, "M", "ჶ"), + (0x1CB7, "M", "ჷ"), + (0x1CB8, "M", "ჸ"), + (0x1CB9, "M", "ჹ"), + (0x1CBA, "M", "ჺ"), + (0x1CBB, "X"), + (0x1CBD, "M", "ჽ"), + (0x1CBE, "M", "ჾ"), + (0x1CBF, "M", "ჿ"), + (0x1CC0, "V"), + (0x1CC8, "X"), + (0x1CD0, "V"), + (0x1CFB, "X"), + (0x1D00, "V"), + (0x1D2C, "M", "a"), + (0x1D2D, "M", "æ"), + (0x1D2E, "M", "b"), + (0x1D2F, "V"), + (0x1D30, "M", "d"), + (0x1D31, "M", "e"), + (0x1D32, "M", "ǝ"), + (0x1D33, "M", "g"), + (0x1D34, "M", "h"), + (0x1D35, "M", "i"), + (0x1D36, "M", "j"), + (0x1D37, "M", "k"), + (0x1D38, "M", "l"), + (0x1D39, "M", "m"), + (0x1D3A, "M", "n"), + (0x1D3B, "V"), + (0x1D3C, "M", "o"), + (0x1D3D, "M", "ȣ"), + (0x1D3E, "M", "p"), + (0x1D3F, "M", "r"), + (0x1D40, "M", "t"), + (0x1D41, "M", "u"), + (0x1D42, "M", "w"), + (0x1D43, "M", "a"), + (0x1D44, "M", "ɐ"), + (0x1D45, "M", "ɑ"), + (0x1D46, "M", "ᴂ"), + (0x1D47, "M", "b"), + (0x1D48, "M", "d"), + (0x1D49, "M", "e"), + (0x1D4A, "M", "ə"), + (0x1D4B, "M", "ɛ"), + (0x1D4C, "M", "ɜ"), + (0x1D4D, "M", "g"), + (0x1D4E, "V"), + (0x1D4F, "M", "k"), + (0x1D50, "M", "m"), + (0x1D51, "M", "ŋ"), + (0x1D52, "M", "o"), + (0x1D53, "M", "ɔ"), + (0x1D54, "M", "ᴖ"), + (0x1D55, "M", "ᴗ"), + (0x1D56, "M", "p"), + (0x1D57, "M", "t"), + (0x1D58, "M", "u"), + (0x1D59, "M", "ᴝ"), + (0x1D5A, "M", "ɯ"), + (0x1D5B, "M", "v"), + (0x1D5C, "M", "ᴥ"), + (0x1D5D, "M", "β"), + (0x1D5E, "M", "γ"), + (0x1D5F, "M", "δ"), + (0x1D60, "M", "φ"), + (0x1D61, "M", "χ"), + (0x1D62, "M", "i"), + (0x1D63, "M", "r"), + (0x1D64, "M", "u"), + (0x1D65, "M", "v"), + (0x1D66, "M", "β"), + (0x1D67, "M", "γ"), + (0x1D68, "M", "ρ"), + (0x1D69, "M", "φ"), + (0x1D6A, "M", "χ"), + (0x1D6B, "V"), + (0x1D78, "M", "н"), + (0x1D79, "V"), + (0x1D9B, "M", "ɒ"), + (0x1D9C, "M", "c"), + (0x1D9D, "M", "ɕ"), + (0x1D9E, "M", "ð"), + (0x1D9F, "M", "ɜ"), + (0x1DA0, "M", "f"), + (0x1DA1, "M", "ɟ"), + (0x1DA2, "M", "ɡ"), + (0x1DA3, "M", "ɥ"), + (0x1DA4, "M", "ɨ"), + (0x1DA5, "M", "ɩ"), + (0x1DA6, "M", "ɪ"), + (0x1DA7, "M", "ᵻ"), + (0x1DA8, "M", "ʝ"), + (0x1DA9, "M", "ɭ"), + (0x1DAA, "M", "ᶅ"), + (0x1DAB, "M", "ʟ"), + (0x1DAC, "M", "ɱ"), + (0x1DAD, "M", "ɰ"), + (0x1DAE, "M", "ɲ"), + (0x1DAF, "M", "ɳ"), + (0x1DB0, "M", "ɴ"), + (0x1DB1, "M", "ɵ"), + (0x1DB2, "M", "ɸ"), + (0x1DB3, "M", "ʂ"), + (0x1DB4, "M", "ʃ"), + (0x1DB5, "M", "ƫ"), + (0x1DB6, "M", "ʉ"), + (0x1DB7, "M", "ʊ"), + (0x1DB8, "M", "ᴜ"), + (0x1DB9, "M", "ʋ"), + (0x1DBA, "M", "ʌ"), + (0x1DBB, "M", "z"), + (0x1DBC, "M", "ʐ"), + (0x1DBD, "M", "ʑ"), + (0x1DBE, "M", "ʒ"), + (0x1DBF, "M", "θ"), + (0x1DC0, "V"), + (0x1E00, "M", "ḁ"), + (0x1E01, "V"), + (0x1E02, "M", "ḃ"), + (0x1E03, "V"), + (0x1E04, "M", "ḅ"), + (0x1E05, "V"), + (0x1E06, "M", "ḇ"), + (0x1E07, "V"), + (0x1E08, "M", "ḉ"), + (0x1E09, "V"), + (0x1E0A, "M", "ḋ"), + (0x1E0B, "V"), + (0x1E0C, "M", "ḍ"), + (0x1E0D, "V"), + (0x1E0E, "M", "ḏ"), + (0x1E0F, "V"), + (0x1E10, "M", "ḑ"), + (0x1E11, "V"), + (0x1E12, "M", "ḓ"), + (0x1E13, "V"), + (0x1E14, "M", "ḕ"), + (0x1E15, "V"), + (0x1E16, "M", "ḗ"), + (0x1E17, "V"), + (0x1E18, "M", "ḙ"), + (0x1E19, "V"), + (0x1E1A, "M", "ḛ"), + (0x1E1B, "V"), + (0x1E1C, "M", "ḝ"), + (0x1E1D, "V"), + (0x1E1E, "M", "ḟ"), + (0x1E1F, "V"), + (0x1E20, "M", "ḡ"), + (0x1E21, "V"), + (0x1E22, "M", "ḣ"), + (0x1E23, "V"), + (0x1E24, "M", "ḥ"), + (0x1E25, "V"), + (0x1E26, "M", "ḧ"), + (0x1E27, "V"), + (0x1E28, "M", "ḩ"), + (0x1E29, "V"), + (0x1E2A, "M", "ḫ"), + (0x1E2B, "V"), + (0x1E2C, "M", "ḭ"), + (0x1E2D, "V"), + (0x1E2E, "M", "ḯ"), + (0x1E2F, "V"), + (0x1E30, "M", "ḱ"), + (0x1E31, "V"), + (0x1E32, "M", "ḳ"), + (0x1E33, "V"), + (0x1E34, "M", "ḵ"), + (0x1E35, "V"), + (0x1E36, "M", "ḷ"), + (0x1E37, "V"), + (0x1E38, "M", "ḹ"), + (0x1E39, "V"), + (0x1E3A, "M", "ḻ"), + (0x1E3B, "V"), + (0x1E3C, "M", "ḽ"), + (0x1E3D, "V"), + (0x1E3E, "M", "ḿ"), + (0x1E3F, "V"), + (0x1E40, "M", "ṁ"), + (0x1E41, "V"), + (0x1E42, "M", "ṃ"), + (0x1E43, "V"), + (0x1E44, "M", "ṅ"), + (0x1E45, "V"), + (0x1E46, "M", "ṇ"), + (0x1E47, "V"), + (0x1E48, "M", "ṉ"), + (0x1E49, "V"), + (0x1E4A, "M", "ṋ"), + (0x1E4B, "V"), + (0x1E4C, "M", "ṍ"), + (0x1E4D, "V"), + (0x1E4E, "M", "ṏ"), + (0x1E4F, "V"), + (0x1E50, "M", "ṑ"), + (0x1E51, "V"), + (0x1E52, "M", "ṓ"), + (0x1E53, "V"), + (0x1E54, "M", "ṕ"), + (0x1E55, "V"), + (0x1E56, "M", "ṗ"), + (0x1E57, "V"), + (0x1E58, "M", "ṙ"), + (0x1E59, "V"), + (0x1E5A, "M", "ṛ"), + (0x1E5B, "V"), + (0x1E5C, "M", "ṝ"), + (0x1E5D, "V"), + (0x1E5E, "M", "ṟ"), + (0x1E5F, "V"), + (0x1E60, "M", "ṡ"), + (0x1E61, "V"), + (0x1E62, "M", "ṣ"), + (0x1E63, "V"), + (0x1E64, "M", "ṥ"), + (0x1E65, "V"), + (0x1E66, "M", "ṧ"), + (0x1E67, "V"), + (0x1E68, "M", "ṩ"), + (0x1E69, "V"), + (0x1E6A, "M", "ṫ"), + (0x1E6B, "V"), + (0x1E6C, "M", "ṭ"), + (0x1E6D, "V"), + (0x1E6E, "M", "ṯ"), + (0x1E6F, "V"), + (0x1E70, "M", "ṱ"), + (0x1E71, "V"), + (0x1E72, "M", "ṳ"), + (0x1E73, "V"), + (0x1E74, "M", "ṵ"), + (0x1E75, "V"), + (0x1E76, "M", "ṷ"), + (0x1E77, "V"), + (0x1E78, "M", "ṹ"), + (0x1E79, "V"), + (0x1E7A, "M", "ṻ"), + (0x1E7B, "V"), + (0x1E7C, "M", "ṽ"), + (0x1E7D, "V"), + (0x1E7E, "M", "ṿ"), + (0x1E7F, "V"), + (0x1E80, "M", "ẁ"), + (0x1E81, "V"), + (0x1E82, "M", "ẃ"), + (0x1E83, "V"), + (0x1E84, "M", "ẅ"), + (0x1E85, "V"), + (0x1E86, "M", "ẇ"), + (0x1E87, "V"), + (0x1E88, "M", "ẉ"), + (0x1E89, "V"), + (0x1E8A, "M", "ẋ"), + (0x1E8B, "V"), + (0x1E8C, "M", "ẍ"), + (0x1E8D, "V"), + (0x1E8E, "M", "ẏ"), + (0x1E8F, "V"), + (0x1E90, "M", "ẑ"), + (0x1E91, "V"), + (0x1E92, "M", "ẓ"), + (0x1E93, "V"), + (0x1E94, "M", "ẕ"), + (0x1E95, "V"), + (0x1E9A, "M", "aʾ"), + (0x1E9B, "M", "ṡ"), + (0x1E9C, "V"), + (0x1E9E, "M", "ß"), + (0x1E9F, "V"), + (0x1EA0, "M", "ạ"), + (0x1EA1, "V"), + (0x1EA2, "M", "ả"), + (0x1EA3, "V"), + (0x1EA4, "M", "ấ"), + (0x1EA5, "V"), + (0x1EA6, "M", "ầ"), + (0x1EA7, "V"), + (0x1EA8, "M", "ẩ"), + (0x1EA9, "V"), + (0x1EAA, "M", "ẫ"), + (0x1EAB, "V"), + (0x1EAC, "M", "ậ"), + (0x1EAD, "V"), + (0x1EAE, "M", "ắ"), + (0x1EAF, "V"), + (0x1EB0, "M", "ằ"), + (0x1EB1, "V"), + (0x1EB2, "M", "ẳ"), + (0x1EB3, "V"), + (0x1EB4, "M", "ẵ"), + (0x1EB5, "V"), + (0x1EB6, "M", "ặ"), + (0x1EB7, "V"), + (0x1EB8, "M", "ẹ"), + (0x1EB9, "V"), + (0x1EBA, "M", "ẻ"), + (0x1EBB, "V"), + (0x1EBC, "M", "ẽ"), + (0x1EBD, "V"), + (0x1EBE, "M", "ế"), + (0x1EBF, "V"), + (0x1EC0, "M", "ề"), + (0x1EC1, "V"), + (0x1EC2, "M", "ể"), + (0x1EC3, "V"), + (0x1EC4, "M", "ễ"), + (0x1EC5, "V"), + (0x1EC6, "M", "ệ"), + (0x1EC7, "V"), + (0x1EC8, "M", "ỉ"), + (0x1EC9, "V"), + (0x1ECA, "M", "ị"), + (0x1ECB, "V"), + (0x1ECC, "M", "ọ"), + (0x1ECD, "V"), + (0x1ECE, "M", "ỏ"), + (0x1ECF, "V"), + (0x1ED0, "M", "ố"), + (0x1ED1, "V"), + (0x1ED2, "M", "ồ"), + (0x1ED3, "V"), + (0x1ED4, "M", "ổ"), + (0x1ED5, "V"), + (0x1ED6, "M", "ỗ"), + (0x1ED7, "V"), + (0x1ED8, "M", "ộ"), + (0x1ED9, "V"), + (0x1EDA, "M", "ớ"), + (0x1EDB, "V"), + (0x1EDC, "M", "ờ"), + (0x1EDD, "V"), + (0x1EDE, "M", "ở"), + (0x1EDF, "V"), + (0x1EE0, "M", "ỡ"), + (0x1EE1, "V"), + (0x1EE2, "M", "ợ"), + (0x1EE3, "V"), + (0x1EE4, "M", "ụ"), + (0x1EE5, "V"), + (0x1EE6, "M", "ủ"), + (0x1EE7, "V"), + (0x1EE8, "M", "ứ"), + (0x1EE9, "V"), + (0x1EEA, "M", "ừ"), + (0x1EEB, "V"), + (0x1EEC, "M", "ử"), + (0x1EED, "V"), + (0x1EEE, "M", "ữ"), + (0x1EEF, "V"), + (0x1EF0, "M", "ự"), + (0x1EF1, "V"), + (0x1EF2, "M", "ỳ"), + (0x1EF3, "V"), + (0x1EF4, "M", "ỵ"), + (0x1EF5, "V"), + (0x1EF6, "M", "ỷ"), + (0x1EF7, "V"), + (0x1EF8, "M", "ỹ"), + (0x1EF9, "V"), + (0x1EFA, "M", "ỻ"), + (0x1EFB, "V"), + (0x1EFC, "M", "ỽ"), + (0x1EFD, "V"), + (0x1EFE, "M", "ỿ"), + (0x1EFF, "V"), + (0x1F08, "M", "ἀ"), + (0x1F09, "M", "ἁ"), + (0x1F0A, "M", "ἂ"), + (0x1F0B, "M", "ἃ"), + (0x1F0C, "M", "ἄ"), + (0x1F0D, "M", "ἅ"), + (0x1F0E, "M", "ἆ"), + (0x1F0F, "M", "ἇ"), + (0x1F10, "V"), + (0x1F16, "X"), + (0x1F18, "M", "ἐ"), + (0x1F19, "M", "ἑ"), + (0x1F1A, "M", "ἒ"), + (0x1F1B, "M", "ἓ"), + (0x1F1C, "M", "ἔ"), + (0x1F1D, "M", "ἕ"), + (0x1F1E, "X"), + (0x1F20, "V"), + (0x1F28, "M", "ἠ"), + (0x1F29, "M", "ἡ"), + (0x1F2A, "M", "ἢ"), + (0x1F2B, "M", "ἣ"), + (0x1F2C, "M", "ἤ"), + (0x1F2D, "M", "ἥ"), + (0x1F2E, "M", "ἦ"), + (0x1F2F, "M", "ἧ"), + (0x1F30, "V"), + (0x1F38, "M", "ἰ"), + (0x1F39, "M", "ἱ"), + (0x1F3A, "M", "ἲ"), + (0x1F3B, "M", "ἳ"), + (0x1F3C, "M", "ἴ"), + (0x1F3D, "M", "ἵ"), + (0x1F3E, "M", "ἶ"), + (0x1F3F, "M", "ἷ"), + (0x1F40, "V"), + (0x1F46, "X"), + (0x1F48, "M", "ὀ"), + (0x1F49, "M", "ὁ"), + (0x1F4A, "M", "ὂ"), + (0x1F4B, "M", "ὃ"), + (0x1F4C, "M", "ὄ"), + (0x1F4D, "M", "ὅ"), + (0x1F4E, "X"), + (0x1F50, "V"), + (0x1F58, "X"), + (0x1F59, "M", "ὑ"), + (0x1F5A, "X"), + (0x1F5B, "M", "ὓ"), + (0x1F5C, "X"), + (0x1F5D, "M", "ὕ"), + (0x1F5E, "X"), + (0x1F5F, "M", "ὗ"), + (0x1F60, "V"), + (0x1F68, "M", "ὠ"), + (0x1F69, "M", "ὡ"), + (0x1F6A, "M", "ὢ"), + (0x1F6B, "M", "ὣ"), + (0x1F6C, "M", "ὤ"), + (0x1F6D, "M", "ὥ"), + (0x1F6E, "M", "ὦ"), + (0x1F6F, "M", "ὧ"), + (0x1F70, "V"), + (0x1F71, "M", "ά"), + (0x1F72, "V"), + (0x1F73, "M", "έ"), + (0x1F74, "V"), + (0x1F75, "M", "ή"), + (0x1F76, "V"), + (0x1F77, "M", "ί"), + (0x1F78, "V"), + (0x1F79, "M", "ό"), + (0x1F7A, "V"), + (0x1F7B, "M", "ύ"), + (0x1F7C, "V"), + (0x1F7D, "M", "ώ"), + (0x1F7E, "X"), + (0x1F80, "M", "ἀι"), + (0x1F81, "M", "ἁι"), + (0x1F82, "M", "ἂι"), + (0x1F83, "M", "ἃι"), + (0x1F84, "M", "ἄι"), + (0x1F85, "M", "ἅι"), + (0x1F86, "M", "ἆι"), + (0x1F87, "M", "ἇι"), + (0x1F88, "M", "ἀι"), + (0x1F89, "M", "ἁι"), + (0x1F8A, "M", "ἂι"), + (0x1F8B, "M", "ἃι"), + (0x1F8C, "M", "ἄι"), + (0x1F8D, "M", "ἅι"), + (0x1F8E, "M", "ἆι"), + (0x1F8F, "M", "ἇι"), + (0x1F90, "M", "ἠι"), + (0x1F91, "M", "ἡι"), + (0x1F92, "M", "ἢι"), + (0x1F93, "M", "ἣι"), + (0x1F94, "M", "ἤι"), + (0x1F95, "M", "ἥι"), + (0x1F96, "M", "ἦι"), + (0x1F97, "M", "ἧι"), + (0x1F98, "M", "ἠι"), + (0x1F99, "M", "ἡι"), + (0x1F9A, "M", "ἢι"), + (0x1F9B, "M", "ἣι"), + (0x1F9C, "M", "ἤι"), + (0x1F9D, "M", "ἥι"), + (0x1F9E, "M", "ἦι"), + (0x1F9F, "M", "ἧι"), + (0x1FA0, "M", "ὠι"), + (0x1FA1, "M", "ὡι"), + (0x1FA2, "M", "ὢι"), + (0x1FA3, "M", "ὣι"), + (0x1FA4, "M", "ὤι"), + (0x1FA5, "M", "ὥι"), + (0x1FA6, "M", "ὦι"), + (0x1FA7, "M", "ὧι"), + (0x1FA8, "M", "ὠι"), + (0x1FA9, "M", "ὡι"), + (0x1FAA, "M", "ὢι"), + (0x1FAB, "M", "ὣι"), + (0x1FAC, "M", "ὤι"), + (0x1FAD, "M", "ὥι"), + (0x1FAE, "M", "ὦι"), + (0x1FAF, "M", "ὧι"), + (0x1FB0, "V"), + (0x1FB2, "M", "ὰι"), + (0x1FB3, "M", "αι"), + (0x1FB4, "M", "άι"), + (0x1FB5, "X"), + (0x1FB6, "V"), + (0x1FB7, "M", "ᾶι"), + (0x1FB8, "M", "ᾰ"), + (0x1FB9, "M", "ᾱ"), + (0x1FBA, "M", "ὰ"), + (0x1FBB, "M", "ά"), + (0x1FBC, "M", "αι"), + (0x1FBD, "M", " ̓"), + (0x1FBE, "M", "ι"), + (0x1FBF, "M", " ̓"), + (0x1FC0, "M", " ͂"), + (0x1FC1, "M", " ̈͂"), + (0x1FC2, "M", "ὴι"), + (0x1FC3, "M", "ηι"), + (0x1FC4, "M", "ήι"), + (0x1FC5, "X"), + (0x1FC6, "V"), + (0x1FC7, "M", "ῆι"), + (0x1FC8, "M", "ὲ"), + (0x1FC9, "M", "έ"), + (0x1FCA, "M", "ὴ"), + (0x1FCB, "M", "ή"), + (0x1FCC, "M", "ηι"), + (0x1FCD, "M", " ̓̀"), + (0x1FCE, "M", " ̓́"), + (0x1FCF, "M", " ̓͂"), + (0x1FD0, "V"), + (0x1FD3, "M", "ΐ"), + (0x1FD4, "X"), + (0x1FD6, "V"), + (0x1FD8, "M", "ῐ"), + (0x1FD9, "M", "ῑ"), + (0x1FDA, "M", "ὶ"), + (0x1FDB, "M", "ί"), + (0x1FDC, "X"), + (0x1FDD, "M", " ̔̀"), + (0x1FDE, "M", " ̔́"), + (0x1FDF, "M", " ̔͂"), + (0x1FE0, "V"), + (0x1FE3, "M", "ΰ"), + (0x1FE4, "V"), + (0x1FE8, "M", "ῠ"), + (0x1FE9, "M", "ῡ"), + (0x1FEA, "M", "ὺ"), + (0x1FEB, "M", "ύ"), + (0x1FEC, "M", "ῥ"), + (0x1FED, "M", " ̈̀"), + (0x1FEE, "M", " ̈́"), + (0x1FEF, "M", "`"), + (0x1FF0, "X"), + (0x1FF2, "M", "ὼι"), + (0x1FF3, "M", "ωι"), + (0x1FF4, "M", "ώι"), + (0x1FF5, "X"), + (0x1FF6, "V"), + (0x1FF7, "M", "ῶι"), + (0x1FF8, "M", "ὸ"), + (0x1FF9, "M", "ό"), + (0x1FFA, "M", "ὼ"), + (0x1FFB, "M", "ώ"), + (0x1FFC, "M", "ωι"), + (0x1FFD, "M", " ́"), + (0x1FFE, "M", " ̔"), + (0x1FFF, "X"), + (0x2000, "M", " "), + (0x200B, "I"), + (0x200C, "D", ""), + (0x200E, "X"), + (0x2010, "V"), + (0x2011, "M", "‐"), + (0x2012, "V"), + (0x2017, "M", " ̳"), + (0x2018, "V"), + (0x2024, "X"), + (0x2027, "V"), + (0x2028, "X"), + (0x202F, "M", " "), + (0x2030, "V"), + (0x2033, "M", "′′"), + (0x2034, "M", "′′′"), + (0x2035, "V"), + (0x2036, "M", "‵‵"), + (0x2037, "M", "‵‵‵"), + (0x2038, "V"), + (0x203C, "M", "!!"), + (0x203D, "V"), + (0x203E, "M", " ̅"), + (0x203F, "V"), + (0x2047, "M", "??"), + (0x2048, "M", "?!"), + (0x2049, "M", "!?"), + (0x204A, "V"), + (0x2057, "M", "′′′′"), + (0x2058, "V"), + (0x205F, "M", " "), + (0x2060, "I"), + (0x2065, "X"), + (0x206A, "I"), + (0x2070, "M", "0"), + (0x2071, "M", "i"), + (0x2072, "X"), + (0x2074, "M", "4"), + (0x2075, "M", "5"), + (0x2076, "M", "6"), + (0x2077, "M", "7"), + (0x2078, "M", "8"), + (0x2079, "M", "9"), + (0x207A, "M", "+"), + (0x207B, "M", "−"), + (0x207C, "M", "="), + (0x207D, "M", "("), + (0x207E, "M", ")"), + (0x207F, "M", "n"), + (0x2080, "M", "0"), + (0x2081, "M", "1"), + (0x2082, "M", "2"), + (0x2083, "M", "3"), + (0x2084, "M", "4"), + (0x2085, "M", "5"), + (0x2086, "M", "6"), + (0x2087, "M", "7"), + (0x2088, "M", "8"), + (0x2089, "M", "9"), + (0x208A, "M", "+"), + (0x208B, "M", "−"), + (0x208C, "M", "="), + (0x208D, "M", "("), + (0x208E, "M", ")"), + (0x208F, "X"), + (0x2090, "M", "a"), + (0x2091, "M", "e"), + (0x2092, "M", "o"), + (0x2093, "M", "x"), + (0x2094, "M", "ə"), + (0x2095, "M", "h"), + (0x2096, "M", "k"), + (0x2097, "M", "l"), + (0x2098, "M", "m"), + (0x2099, "M", "n"), + (0x209A, "M", "p"), + (0x209B, "M", "s"), + (0x209C, "M", "t"), + (0x209D, "X"), + (0x20A0, "V"), + (0x20A8, "M", "rs"), + (0x20A9, "V"), + (0x20C2, "X"), + (0x20D0, "V"), + (0x20F1, "X"), + (0x2100, "M", "a/c"), + (0x2101, "M", "a/s"), + (0x2102, "M", "c"), + (0x2103, "M", "°c"), + (0x2104, "V"), + (0x2105, "M", "c/o"), + (0x2106, "M", "c/u"), + (0x2107, "M", "ɛ"), + (0x2108, "V"), + (0x2109, "M", "°f"), + (0x210A, "M", "g"), + (0x210B, "M", "h"), + (0x210F, "M", "ħ"), + (0x2110, "M", "i"), + (0x2112, "M", "l"), + (0x2114, "V"), + (0x2115, "M", "n"), + (0x2116, "M", "no"), + (0x2117, "V"), + (0x2119, "M", "p"), + (0x211A, "M", "q"), + (0x211B, "M", "r"), + (0x211E, "V"), + (0x2120, "M", "sm"), + (0x2121, "M", "tel"), + (0x2122, "M", "tm"), + (0x2123, "V"), + (0x2124, "M", "z"), + (0x2125, "V"), + (0x2126, "M", "ω"), + (0x2127, "V"), + (0x2128, "M", "z"), + (0x2129, "V"), + (0x212A, "M", "k"), + (0x212B, "M", "å"), + (0x212C, "M", "b"), + (0x212D, "M", "c"), + (0x212E, "V"), + (0x212F, "M", "e"), + (0x2131, "M", "f"), + (0x2132, "M", "ⅎ"), + (0x2133, "M", "m"), + (0x2134, "M", "o"), + (0x2135, "M", "א"), + (0x2136, "M", "ב"), + (0x2137, "M", "ג"), + (0x2138, "M", "ד"), + (0x2139, "M", "i"), + (0x213A, "V"), + (0x213B, "M", "fax"), + (0x213C, "M", "π"), + (0x213D, "M", "γ"), + (0x213F, "M", "π"), + (0x2140, "M", "∑"), + (0x2141, "V"), + (0x2145, "M", "d"), + (0x2147, "M", "e"), + (0x2148, "M", "i"), + (0x2149, "M", "j"), + (0x214A, "V"), + (0x2150, "M", "1⁄7"), + (0x2151, "M", "1⁄9"), + (0x2152, "M", "1⁄10"), + (0x2153, "M", "1⁄3"), + (0x2154, "M", "2⁄3"), + (0x2155, "M", "1⁄5"), + (0x2156, "M", "2⁄5"), + (0x2157, "M", "3⁄5"), + (0x2158, "M", "4⁄5"), + (0x2159, "M", "1⁄6"), + (0x215A, "M", "5⁄6"), + (0x215B, "M", "1⁄8"), + (0x215C, "M", "3⁄8"), + (0x215D, "M", "5⁄8"), + (0x215E, "M", "7⁄8"), + (0x215F, "M", "1⁄"), + (0x2160, "M", "i"), + (0x2161, "M", "ii"), + (0x2162, "M", "iii"), + (0x2163, "M", "iv"), + (0x2164, "M", "v"), + (0x2165, "M", "vi"), + (0x2166, "M", "vii"), + (0x2167, "M", "viii"), + (0x2168, "M", "ix"), + (0x2169, "M", "x"), + (0x216A, "M", "xi"), + (0x216B, "M", "xii"), + (0x216C, "M", "l"), + (0x216D, "M", "c"), + (0x216E, "M", "d"), + (0x216F, "M", "m"), + (0x2170, "M", "i"), + (0x2171, "M", "ii"), + (0x2172, "M", "iii"), + (0x2173, "M", "iv"), + (0x2174, "M", "v"), + (0x2175, "M", "vi"), + (0x2176, "M", "vii"), + (0x2177, "M", "viii"), + (0x2178, "M", "ix"), + (0x2179, "M", "x"), + (0x217A, "M", "xi"), + (0x217B, "M", "xii"), + (0x217C, "M", "l"), + (0x217D, "M", "c"), + (0x217E, "M", "d"), + (0x217F, "M", "m"), + (0x2180, "V"), + (0x2183, "M", "ↄ"), + (0x2184, "V"), + (0x2189, "M", "0⁄3"), + (0x218A, "V"), + (0x218C, "X"), + (0x2190, "V"), + (0x222C, "M", "∫∫"), + (0x222D, "M", "∫∫∫"), + (0x222E, "V"), + (0x222F, "M", "∮∮"), + (0x2230, "M", "∮∮∮"), + (0x2231, "V"), + (0x2329, "M", "〈"), + (0x232A, "M", "〉"), + (0x232B, "V"), + (0x242A, "X"), + (0x2440, "V"), + (0x244B, "X"), + (0x2460, "M", "1"), + (0x2461, "M", "2"), + (0x2462, "M", "3"), + (0x2463, "M", "4"), + (0x2464, "M", "5"), + (0x2465, "M", "6"), + (0x2466, "M", "7"), + (0x2467, "M", "8"), + (0x2468, "M", "9"), + (0x2469, "M", "10"), + (0x246A, "M", "11"), + (0x246B, "M", "12"), + (0x246C, "M", "13"), + (0x246D, "M", "14"), + (0x246E, "M", "15"), + (0x246F, "M", "16"), + (0x2470, "M", "17"), + (0x2471, "M", "18"), + (0x2472, "M", "19"), + (0x2473, "M", "20"), + (0x2474, "M", "(1)"), + (0x2475, "M", "(2)"), + (0x2476, "M", "(3)"), + (0x2477, "M", "(4)"), + (0x2478, "M", "(5)"), + (0x2479, "M", "(6)"), + (0x247A, "M", "(7)"), + (0x247B, "M", "(8)"), + (0x247C, "M", "(9)"), + (0x247D, "M", "(10)"), + (0x247E, "M", "(11)"), + (0x247F, "M", "(12)"), + (0x2480, "M", "(13)"), + (0x2481, "M", "(14)"), + (0x2482, "M", "(15)"), + (0x2483, "M", "(16)"), + (0x2484, "M", "(17)"), + (0x2485, "M", "(18)"), + (0x2486, "M", "(19)"), + (0x2487, "M", "(20)"), + (0x2488, "X"), + (0x249C, "M", "(a)"), + (0x249D, "M", "(b)"), + (0x249E, "M", "(c)"), + (0x249F, "M", "(d)"), + (0x24A0, "M", "(e)"), + (0x24A1, "M", "(f)"), + (0x24A2, "M", "(g)"), + (0x24A3, "M", "(h)"), + (0x24A4, "M", "(i)"), + (0x24A5, "M", "(j)"), + (0x24A6, "M", "(k)"), + (0x24A7, "M", "(l)"), + (0x24A8, "M", "(m)"), + (0x24A9, "M", "(n)"), + (0x24AA, "M", "(o)"), + (0x24AB, "M", "(p)"), + (0x24AC, "M", "(q)"), + (0x24AD, "M", "(r)"), + (0x24AE, "M", "(s)"), + (0x24AF, "M", "(t)"), + (0x24B0, "M", "(u)"), + (0x24B1, "M", "(v)"), + (0x24B2, "M", "(w)"), + (0x24B3, "M", "(x)"), + (0x24B4, "M", "(y)"), + (0x24B5, "M", "(z)"), + (0x24B6, "M", "a"), + (0x24B7, "M", "b"), + (0x24B8, "M", "c"), + (0x24B9, "M", "d"), + (0x24BA, "M", "e"), + (0x24BB, "M", "f"), + (0x24BC, "M", "g"), + (0x24BD, "M", "h"), + (0x24BE, "M", "i"), + (0x24BF, "M", "j"), + (0x24C0, "M", "k"), + (0x24C1, "M", "l"), + (0x24C2, "M", "m"), + (0x24C3, "M", "n"), + (0x24C4, "M", "o"), + (0x24C5, "M", "p"), + (0x24C6, "M", "q"), + (0x24C7, "M", "r"), + (0x24C8, "M", "s"), + (0x24C9, "M", "t"), + (0x24CA, "M", "u"), + (0x24CB, "M", "v"), + (0x24CC, "M", "w"), + (0x24CD, "M", "x"), + (0x24CE, "M", "y"), + (0x24CF, "M", "z"), + (0x24D0, "M", "a"), + (0x24D1, "M", "b"), + (0x24D2, "M", "c"), + (0x24D3, "M", "d"), + (0x24D4, "M", "e"), + (0x24D5, "M", "f"), + (0x24D6, "M", "g"), + (0x24D7, "M", "h"), + (0x24D8, "M", "i"), + (0x24D9, "M", "j"), + (0x24DA, "M", "k"), + (0x24DB, "M", "l"), + (0x24DC, "M", "m"), + (0x24DD, "M", "n"), + (0x24DE, "M", "o"), + (0x24DF, "M", "p"), + (0x24E0, "M", "q"), + (0x24E1, "M", "r"), + (0x24E2, "M", "s"), + (0x24E3, "M", "t"), + (0x24E4, "M", "u"), + (0x24E5, "M", "v"), + (0x24E6, "M", "w"), + (0x24E7, "M", "x"), + (0x24E8, "M", "y"), + (0x24E9, "M", "z"), + (0x24EA, "M", "0"), + (0x24EB, "V"), + (0x2A0C, "M", "∫∫∫∫"), + (0x2A0D, "V"), + (0x2A74, "M", "::="), + (0x2A75, "M", "=="), + (0x2A76, "M", "==="), + (0x2A77, "V"), + (0x2ADC, "M", "⫝̸"), + (0x2ADD, "V"), + (0x2B74, "X"), + (0x2B76, "V"), + (0x2C00, "M", "ⰰ"), + (0x2C01, "M", "ⰱ"), + (0x2C02, "M", "ⰲ"), + (0x2C03, "M", "ⰳ"), + (0x2C04, "M", "ⰴ"), + (0x2C05, "M", "ⰵ"), + (0x2C06, "M", "ⰶ"), + (0x2C07, "M", "ⰷ"), + (0x2C08, "M", "ⰸ"), + (0x2C09, "M", "ⰹ"), + (0x2C0A, "M", "ⰺ"), + (0x2C0B, "M", "ⰻ"), + (0x2C0C, "M", "ⰼ"), + (0x2C0D, "M", "ⰽ"), + (0x2C0E, "M", "ⰾ"), + (0x2C0F, "M", "ⰿ"), + (0x2C10, "M", "ⱀ"), + (0x2C11, "M", "ⱁ"), + (0x2C12, "M", "ⱂ"), + (0x2C13, "M", "ⱃ"), + (0x2C14, "M", "ⱄ"), + (0x2C15, "M", "ⱅ"), + (0x2C16, "M", "ⱆ"), + (0x2C17, "M", "ⱇ"), + (0x2C18, "M", "ⱈ"), + (0x2C19, "M", "ⱉ"), + (0x2C1A, "M", "ⱊ"), + (0x2C1B, "M", "ⱋ"), + (0x2C1C, "M", "ⱌ"), + (0x2C1D, "M", "ⱍ"), + (0x2C1E, "M", "ⱎ"), + (0x2C1F, "M", "ⱏ"), + (0x2C20, "M", "ⱐ"), + (0x2C21, "M", "ⱑ"), + (0x2C22, "M", "ⱒ"), + (0x2C23, "M", "ⱓ"), + (0x2C24, "M", "ⱔ"), + (0x2C25, "M", "ⱕ"), + (0x2C26, "M", "ⱖ"), + (0x2C27, "M", "ⱗ"), + (0x2C28, "M", "ⱘ"), + (0x2C29, "M", "ⱙ"), + (0x2C2A, "M", "ⱚ"), + (0x2C2B, "M", "ⱛ"), + (0x2C2C, "M", "ⱜ"), + (0x2C2D, "M", "ⱝ"), + (0x2C2E, "M", "ⱞ"), + (0x2C2F, "M", "ⱟ"), + (0x2C30, "V"), + (0x2C60, "M", "ⱡ"), + (0x2C61, "V"), + (0x2C62, "M", "ɫ"), + (0x2C63, "M", "ᵽ"), + (0x2C64, "M", "ɽ"), + (0x2C65, "V"), + (0x2C67, "M", "ⱨ"), + (0x2C68, "V"), + (0x2C69, "M", "ⱪ"), + (0x2C6A, "V"), + (0x2C6B, "M", "ⱬ"), + (0x2C6C, "V"), + (0x2C6D, "M", "ɑ"), + (0x2C6E, "M", "ɱ"), + (0x2C6F, "M", "ɐ"), + (0x2C70, "M", "ɒ"), + (0x2C71, "V"), + (0x2C72, "M", "ⱳ"), + (0x2C73, "V"), + (0x2C75, "M", "ⱶ"), + (0x2C76, "V"), + (0x2C7C, "M", "j"), + (0x2C7D, "M", "v"), + (0x2C7E, "M", "ȿ"), + (0x2C7F, "M", "ɀ"), + (0x2C80, "M", "ⲁ"), + (0x2C81, "V"), + (0x2C82, "M", "ⲃ"), + (0x2C83, "V"), + (0x2C84, "M", "ⲅ"), + (0x2C85, "V"), + (0x2C86, "M", "ⲇ"), + (0x2C87, "V"), + (0x2C88, "M", "ⲉ"), + (0x2C89, "V"), + (0x2C8A, "M", "ⲋ"), + (0x2C8B, "V"), + (0x2C8C, "M", "ⲍ"), + (0x2C8D, "V"), + (0x2C8E, "M", "ⲏ"), + (0x2C8F, "V"), + (0x2C90, "M", "ⲑ"), + (0x2C91, "V"), + (0x2C92, "M", "ⲓ"), + (0x2C93, "V"), + (0x2C94, "M", "ⲕ"), + (0x2C95, "V"), + (0x2C96, "M", "ⲗ"), + (0x2C97, "V"), + (0x2C98, "M", "ⲙ"), + (0x2C99, "V"), + (0x2C9A, "M", "ⲛ"), + (0x2C9B, "V"), + (0x2C9C, "M", "ⲝ"), + (0x2C9D, "V"), + (0x2C9E, "M", "ⲟ"), + (0x2C9F, "V"), + (0x2CA0, "M", "ⲡ"), + (0x2CA1, "V"), + (0x2CA2, "M", "ⲣ"), + (0x2CA3, "V"), + (0x2CA4, "M", "ⲥ"), + (0x2CA5, "V"), + (0x2CA6, "M", "ⲧ"), + (0x2CA7, "V"), + (0x2CA8, "M", "ⲩ"), + (0x2CA9, "V"), + (0x2CAA, "M", "ⲫ"), + (0x2CAB, "V"), + (0x2CAC, "M", "ⲭ"), + (0x2CAD, "V"), + (0x2CAE, "M", "ⲯ"), + (0x2CAF, "V"), + (0x2CB0, "M", "ⲱ"), + (0x2CB1, "V"), + (0x2CB2, "M", "ⲳ"), + (0x2CB3, "V"), + (0x2CB4, "M", "ⲵ"), + (0x2CB5, "V"), + (0x2CB6, "M", "ⲷ"), + (0x2CB7, "V"), + (0x2CB8, "M", "ⲹ"), + (0x2CB9, "V"), + (0x2CBA, "M", "ⲻ"), + (0x2CBB, "V"), + (0x2CBC, "M", "ⲽ"), + (0x2CBD, "V"), + (0x2CBE, "M", "ⲿ"), + (0x2CBF, "V"), + (0x2CC0, "M", "ⳁ"), + (0x2CC1, "V"), + (0x2CC2, "M", "ⳃ"), + (0x2CC3, "V"), + (0x2CC4, "M", "ⳅ"), + (0x2CC5, "V"), + (0x2CC6, "M", "ⳇ"), + (0x2CC7, "V"), + (0x2CC8, "M", "ⳉ"), + (0x2CC9, "V"), + (0x2CCA, "M", "ⳋ"), + (0x2CCB, "V"), + (0x2CCC, "M", "ⳍ"), + (0x2CCD, "V"), + (0x2CCE, "M", "ⳏ"), + (0x2CCF, "V"), + (0x2CD0, "M", "ⳑ"), + (0x2CD1, "V"), + (0x2CD2, "M", "ⳓ"), + (0x2CD3, "V"), + (0x2CD4, "M", "ⳕ"), + (0x2CD5, "V"), + (0x2CD6, "M", "ⳗ"), + (0x2CD7, "V"), + (0x2CD8, "M", "ⳙ"), + (0x2CD9, "V"), + (0x2CDA, "M", "ⳛ"), + (0x2CDB, "V"), + (0x2CDC, "M", "ⳝ"), + (0x2CDD, "V"), + (0x2CDE, "M", "ⳟ"), + (0x2CDF, "V"), + (0x2CE0, "M", "ⳡ"), + (0x2CE1, "V"), + (0x2CE2, "M", "ⳣ"), + (0x2CE3, "V"), + (0x2CEB, "M", "ⳬ"), + (0x2CEC, "V"), + (0x2CED, "M", "ⳮ"), + (0x2CEE, "V"), + (0x2CF2, "M", "ⳳ"), + (0x2CF3, "V"), + (0x2CF4, "X"), + (0x2CF9, "V"), + (0x2D26, "X"), + (0x2D27, "V"), + (0x2D28, "X"), + (0x2D2D, "V"), + (0x2D2E, "X"), + (0x2D30, "V"), + (0x2D68, "X"), + (0x2D6F, "M", "ⵡ"), + (0x2D70, "V"), + (0x2D71, "X"), + (0x2D7F, "V"), + (0x2D97, "X"), + (0x2DA0, "V"), + (0x2DA7, "X"), + (0x2DA8, "V"), + (0x2DAF, "X"), + (0x2DB0, "V"), + (0x2DB7, "X"), + (0x2DB8, "V"), + (0x2DBF, "X"), + (0x2DC0, "V"), + (0x2DC7, "X"), + (0x2DC8, "V"), + (0x2DCF, "X"), + (0x2DD0, "V"), + (0x2DD7, "X"), + (0x2DD8, "V"), + (0x2DDF, "X"), + (0x2DE0, "V"), + (0x2E5E, "X"), + (0x2E80, "V"), + (0x2E9A, "X"), + (0x2E9B, "V"), + (0x2E9F, "M", "母"), + (0x2EA0, "V"), + (0x2EF3, "M", "龟"), + (0x2EF4, "X"), + (0x2F00, "M", "一"), + (0x2F01, "M", "丨"), + (0x2F02, "M", "丶"), + (0x2F03, "M", "丿"), + (0x2F04, "M", "乙"), + (0x2F05, "M", "亅"), + (0x2F06, "M", "二"), + (0x2F07, "M", "亠"), + (0x2F08, "M", "人"), + (0x2F09, "M", "儿"), + (0x2F0A, "M", "入"), + (0x2F0B, "M", "八"), + (0x2F0C, "M", "冂"), + (0x2F0D, "M", "冖"), + (0x2F0E, "M", "冫"), + (0x2F0F, "M", "几"), + (0x2F10, "M", "凵"), + (0x2F11, "M", "刀"), + (0x2F12, "M", "力"), + (0x2F13, "M", "勹"), + (0x2F14, "M", "匕"), + (0x2F15, "M", "匚"), + (0x2F16, "M", "匸"), + (0x2F17, "M", "十"), + (0x2F18, "M", "卜"), + (0x2F19, "M", "卩"), + (0x2F1A, "M", "厂"), + (0x2F1B, "M", "厶"), + (0x2F1C, "M", "又"), + (0x2F1D, "M", "口"), + (0x2F1E, "M", "囗"), + (0x2F1F, "M", "土"), + (0x2F20, "M", "士"), + (0x2F21, "M", "夂"), + (0x2F22, "M", "夊"), + (0x2F23, "M", "夕"), + (0x2F24, "M", "大"), + (0x2F25, "M", "女"), + (0x2F26, "M", "子"), + (0x2F27, "M", "宀"), + (0x2F28, "M", "寸"), + (0x2F29, "M", "小"), + (0x2F2A, "M", "尢"), + (0x2F2B, "M", "尸"), + (0x2F2C, "M", "屮"), + (0x2F2D, "M", "山"), + (0x2F2E, "M", "巛"), + (0x2F2F, "M", "工"), + (0x2F30, "M", "己"), + (0x2F31, "M", "巾"), + (0x2F32, "M", "干"), + (0x2F33, "M", "幺"), + (0x2F34, "M", "广"), + (0x2F35, "M", "廴"), + (0x2F36, "M", "廾"), + (0x2F37, "M", "弋"), + (0x2F38, "M", "弓"), + (0x2F39, "M", "彐"), + (0x2F3A, "M", "彡"), + (0x2F3B, "M", "彳"), + (0x2F3C, "M", "心"), + (0x2F3D, "M", "戈"), + (0x2F3E, "M", "戶"), + (0x2F3F, "M", "手"), + (0x2F40, "M", "支"), + (0x2F41, "M", "攴"), + (0x2F42, "M", "文"), + (0x2F43, "M", "斗"), + (0x2F44, "M", "斤"), + (0x2F45, "M", "方"), + (0x2F46, "M", "无"), + (0x2F47, "M", "日"), + (0x2F48, "M", "曰"), + (0x2F49, "M", "月"), + (0x2F4A, "M", "木"), + (0x2F4B, "M", "欠"), + (0x2F4C, "M", "止"), + (0x2F4D, "M", "歹"), + (0x2F4E, "M", "殳"), + (0x2F4F, "M", "毋"), + (0x2F50, "M", "比"), + (0x2F51, "M", "毛"), + (0x2F52, "M", "氏"), + (0x2F53, "M", "气"), + (0x2F54, "M", "水"), + (0x2F55, "M", "火"), + (0x2F56, "M", "爪"), + (0x2F57, "M", "父"), + (0x2F58, "M", "爻"), + (0x2F59, "M", "爿"), + (0x2F5A, "M", "片"), + (0x2F5B, "M", "牙"), + (0x2F5C, "M", "牛"), + (0x2F5D, "M", "犬"), + (0x2F5E, "M", "玄"), + (0x2F5F, "M", "玉"), + (0x2F60, "M", "瓜"), + (0x2F61, "M", "瓦"), + (0x2F62, "M", "甘"), + (0x2F63, "M", "生"), + (0x2F64, "M", "用"), + (0x2F65, "M", "田"), + (0x2F66, "M", "疋"), + (0x2F67, "M", "疒"), + (0x2F68, "M", "癶"), + (0x2F69, "M", "白"), + (0x2F6A, "M", "皮"), + (0x2F6B, "M", "皿"), + (0x2F6C, "M", "目"), + (0x2F6D, "M", "矛"), + (0x2F6E, "M", "矢"), + (0x2F6F, "M", "石"), + (0x2F70, "M", "示"), + (0x2F71, "M", "禸"), + (0x2F72, "M", "禾"), + (0x2F73, "M", "穴"), + (0x2F74, "M", "立"), + (0x2F75, "M", "竹"), + (0x2F76, "M", "米"), + (0x2F77, "M", "糸"), + (0x2F78, "M", "缶"), + (0x2F79, "M", "网"), + (0x2F7A, "M", "羊"), + (0x2F7B, "M", "羽"), + (0x2F7C, "M", "老"), + (0x2F7D, "M", "而"), + (0x2F7E, "M", "耒"), + (0x2F7F, "M", "耳"), + (0x2F80, "M", "聿"), + (0x2F81, "M", "肉"), + (0x2F82, "M", "臣"), + (0x2F83, "M", "自"), + (0x2F84, "M", "至"), + (0x2F85, "M", "臼"), + (0x2F86, "M", "舌"), + (0x2F87, "M", "舛"), + (0x2F88, "M", "舟"), + (0x2F89, "M", "艮"), + (0x2F8A, "M", "色"), + (0x2F8B, "M", "艸"), + (0x2F8C, "M", "虍"), + (0x2F8D, "M", "虫"), + (0x2F8E, "M", "血"), + (0x2F8F, "M", "行"), + (0x2F90, "M", "衣"), + (0x2F91, "M", "襾"), + (0x2F92, "M", "見"), + (0x2F93, "M", "角"), + (0x2F94, "M", "言"), + (0x2F95, "M", "谷"), + (0x2F96, "M", "豆"), + (0x2F97, "M", "豕"), + (0x2F98, "M", "豸"), + (0x2F99, "M", "貝"), + (0x2F9A, "M", "赤"), + (0x2F9B, "M", "走"), + (0x2F9C, "M", "足"), + (0x2F9D, "M", "身"), + (0x2F9E, "M", "車"), + (0x2F9F, "M", "辛"), + (0x2FA0, "M", "辰"), + (0x2FA1, "M", "辵"), + (0x2FA2, "M", "邑"), + (0x2FA3, "M", "酉"), + (0x2FA4, "M", "釆"), + (0x2FA5, "M", "里"), + (0x2FA6, "M", "金"), + (0x2FA7, "M", "長"), + (0x2FA8, "M", "門"), + (0x2FA9, "M", "阜"), + (0x2FAA, "M", "隶"), + (0x2FAB, "M", "隹"), + (0x2FAC, "M", "雨"), + (0x2FAD, "M", "靑"), + (0x2FAE, "M", "非"), + (0x2FAF, "M", "面"), + (0x2FB0, "M", "革"), + (0x2FB1, "M", "韋"), + (0x2FB2, "M", "韭"), + (0x2FB3, "M", "音"), + (0x2FB4, "M", "頁"), + (0x2FB5, "M", "風"), + (0x2FB6, "M", "飛"), + (0x2FB7, "M", "食"), + (0x2FB8, "M", "首"), + (0x2FB9, "M", "香"), + (0x2FBA, "M", "馬"), + (0x2FBB, "M", "骨"), + (0x2FBC, "M", "高"), + (0x2FBD, "M", "髟"), + (0x2FBE, "M", "鬥"), + (0x2FBF, "M", "鬯"), + (0x2FC0, "M", "鬲"), + (0x2FC1, "M", "鬼"), + (0x2FC2, "M", "魚"), + (0x2FC3, "M", "鳥"), + (0x2FC4, "M", "鹵"), + (0x2FC5, "M", "鹿"), + (0x2FC6, "M", "麥"), + (0x2FC7, "M", "麻"), + (0x2FC8, "M", "黃"), + (0x2FC9, "M", "黍"), + (0x2FCA, "M", "黑"), + (0x2FCB, "M", "黹"), + (0x2FCC, "M", "黽"), + (0x2FCD, "M", "鼎"), + (0x2FCE, "M", "鼓"), + (0x2FCF, "M", "鼠"), + (0x2FD0, "M", "鼻"), + (0x2FD1, "M", "齊"), + (0x2FD2, "M", "齒"), + (0x2FD3, "M", "龍"), + (0x2FD4, "M", "龜"), + (0x2FD5, "M", "龠"), + (0x2FD6, "X"), + (0x3000, "M", " "), + (0x3001, "V"), + (0x3002, "M", "."), + (0x3003, "V"), + (0x3036, "M", "〒"), + (0x3037, "V"), + (0x3038, "M", "十"), + (0x3039, "M", "卄"), + (0x303A, "M", "卅"), + (0x303B, "V"), + (0x3040, "X"), + (0x3041, "V"), + (0x3097, "X"), + (0x3099, "V"), + (0x309B, "M", " ゙"), + (0x309C, "M", " ゚"), + (0x309D, "V"), + (0x309F, "M", "より"), + (0x30A0, "V"), + (0x30FF, "M", "コト"), + (0x3100, "X"), + (0x3105, "V"), + (0x3130, "X"), + (0x3131, "M", "ᄀ"), + (0x3132, "M", "ᄁ"), + (0x3133, "M", "ᆪ"), + (0x3134, "M", "ᄂ"), + (0x3135, "M", "ᆬ"), + (0x3136, "M", "ᆭ"), + (0x3137, "M", "ᄃ"), + (0x3138, "M", "ᄄ"), + (0x3139, "M", "ᄅ"), + (0x313A, "M", "ᆰ"), + (0x313B, "M", "ᆱ"), + (0x313C, "M", "ᆲ"), + (0x313D, "M", "ᆳ"), + (0x313E, "M", "ᆴ"), + (0x313F, "M", "ᆵ"), + (0x3140, "M", "ᄚ"), + (0x3141, "M", "ᄆ"), + (0x3142, "M", "ᄇ"), + (0x3143, "M", "ᄈ"), + (0x3144, "M", "ᄡ"), + (0x3145, "M", "ᄉ"), + (0x3146, "M", "ᄊ"), + (0x3147, "M", "ᄋ"), + (0x3148, "M", "ᄌ"), + (0x3149, "M", "ᄍ"), + (0x314A, "M", "ᄎ"), + (0x314B, "M", "ᄏ"), + (0x314C, "M", "ᄐ"), + (0x314D, "M", "ᄑ"), + (0x314E, "M", "ᄒ"), + (0x314F, "M", "ᅡ"), + (0x3150, "M", "ᅢ"), + (0x3151, "M", "ᅣ"), + (0x3152, "M", "ᅤ"), + (0x3153, "M", "ᅥ"), + (0x3154, "M", "ᅦ"), + (0x3155, "M", "ᅧ"), + (0x3156, "M", "ᅨ"), + (0x3157, "M", "ᅩ"), + (0x3158, "M", "ᅪ"), + (0x3159, "M", "ᅫ"), + (0x315A, "M", "ᅬ"), + (0x315B, "M", "ᅭ"), + (0x315C, "M", "ᅮ"), + (0x315D, "M", "ᅯ"), + (0x315E, "M", "ᅰ"), + (0x315F, "M", "ᅱ"), + (0x3160, "M", "ᅲ"), + (0x3161, "M", "ᅳ"), + (0x3162, "M", "ᅴ"), + (0x3163, "M", "ᅵ"), + (0x3164, "I"), + (0x3165, "M", "ᄔ"), + (0x3166, "M", "ᄕ"), + (0x3167, "M", "ᇇ"), + (0x3168, "M", "ᇈ"), + (0x3169, "M", "ᇌ"), + (0x316A, "M", "ᇎ"), + (0x316B, "M", "ᇓ"), + (0x316C, "M", "ᇗ"), + (0x316D, "M", "ᇙ"), + (0x316E, "M", "ᄜ"), + (0x316F, "M", "ᇝ"), + (0x3170, "M", "ᇟ"), + (0x3171, "M", "ᄝ"), + (0x3172, "M", "ᄞ"), + (0x3173, "M", "ᄠ"), + (0x3174, "M", "ᄢ"), + (0x3175, "M", "ᄣ"), + (0x3176, "M", "ᄧ"), + (0x3177, "M", "ᄩ"), + (0x3178, "M", "ᄫ"), + (0x3179, "M", "ᄬ"), + (0x317A, "M", "ᄭ"), + (0x317B, "M", "ᄮ"), + (0x317C, "M", "ᄯ"), + (0x317D, "M", "ᄲ"), + (0x317E, "M", "ᄶ"), + (0x317F, "M", "ᅀ"), + (0x3180, "M", "ᅇ"), + (0x3181, "M", "ᅌ"), + (0x3182, "M", "ᇱ"), + (0x3183, "M", "ᇲ"), + (0x3184, "M", "ᅗ"), + (0x3185, "M", "ᅘ"), + (0x3186, "M", "ᅙ"), + (0x3187, "M", "ᆄ"), + (0x3188, "M", "ᆅ"), + (0x3189, "M", "ᆈ"), + (0x318A, "M", "ᆑ"), + (0x318B, "M", "ᆒ"), + (0x318C, "M", "ᆔ"), + (0x318D, "M", "ᆞ"), + (0x318E, "M", "ᆡ"), + (0x318F, "X"), + (0x3190, "V"), + (0x3192, "M", "一"), + (0x3193, "M", "二"), + (0x3194, "M", "三"), + (0x3195, "M", "四"), + (0x3196, "M", "上"), + (0x3197, "M", "中"), + (0x3198, "M", "下"), + (0x3199, "M", "甲"), + (0x319A, "M", "乙"), + (0x319B, "M", "丙"), + (0x319C, "M", "丁"), + (0x319D, "M", "天"), + (0x319E, "M", "地"), + (0x319F, "M", "人"), + (0x31A0, "V"), + (0x31E6, "X"), + (0x31F0, "V"), + (0x3200, "M", "(ᄀ)"), + (0x3201, "M", "(ᄂ)"), + (0x3202, "M", "(ᄃ)"), + (0x3203, "M", "(ᄅ)"), + (0x3204, "M", "(ᄆ)"), + (0x3205, "M", "(ᄇ)"), + (0x3206, "M", "(ᄉ)"), + (0x3207, "M", "(ᄋ)"), + (0x3208, "M", "(ᄌ)"), + (0x3209, "M", "(ᄎ)"), + (0x320A, "M", "(ᄏ)"), + (0x320B, "M", "(ᄐ)"), + (0x320C, "M", "(ᄑ)"), + (0x320D, "M", "(ᄒ)"), + (0x320E, "M", "(가)"), + (0x320F, "M", "(나)"), + (0x3210, "M", "(다)"), + (0x3211, "M", "(라)"), + (0x3212, "M", "(마)"), + (0x3213, "M", "(바)"), + (0x3214, "M", "(사)"), + (0x3215, "M", "(아)"), + (0x3216, "M", "(자)"), + (0x3217, "M", "(차)"), + (0x3218, "M", "(카)"), + (0x3219, "M", "(타)"), + (0x321A, "M", "(파)"), + (0x321B, "M", "(하)"), + (0x321C, "M", "(주)"), + (0x321D, "M", "(오전)"), + (0x321E, "M", "(오후)"), + (0x321F, "X"), + (0x3220, "M", "(一)"), + (0x3221, "M", "(二)"), + (0x3222, "M", "(三)"), + (0x3223, "M", "(四)"), + (0x3224, "M", "(五)"), + (0x3225, "M", "(六)"), + (0x3226, "M", "(七)"), + (0x3227, "M", "(八)"), + (0x3228, "M", "(九)"), + (0x3229, "M", "(十)"), + (0x322A, "M", "(月)"), + (0x322B, "M", "(火)"), + (0x322C, "M", "(水)"), + (0x322D, "M", "(木)"), + (0x322E, "M", "(金)"), + (0x322F, "M", "(土)"), + (0x3230, "M", "(日)"), + (0x3231, "M", "(株)"), + (0x3232, "M", "(有)"), + (0x3233, "M", "(社)"), + (0x3234, "M", "(名)"), + (0x3235, "M", "(特)"), + (0x3236, "M", "(財)"), + (0x3237, "M", "(祝)"), + (0x3238, "M", "(労)"), + (0x3239, "M", "(代)"), + (0x323A, "M", "(呼)"), + (0x323B, "M", "(学)"), + (0x323C, "M", "(監)"), + (0x323D, "M", "(企)"), + (0x323E, "M", "(資)"), + (0x323F, "M", "(協)"), + (0x3240, "M", "(祭)"), + (0x3241, "M", "(休)"), + (0x3242, "M", "(自)"), + (0x3243, "M", "(至)"), + (0x3244, "M", "問"), + (0x3245, "M", "幼"), + (0x3246, "M", "文"), + (0x3247, "M", "箏"), + (0x3248, "V"), + (0x3250, "M", "pte"), + (0x3251, "M", "21"), + (0x3252, "M", "22"), + (0x3253, "M", "23"), + (0x3254, "M", "24"), + (0x3255, "M", "25"), + (0x3256, "M", "26"), + (0x3257, "M", "27"), + (0x3258, "M", "28"), + (0x3259, "M", "29"), + (0x325A, "M", "30"), + (0x325B, "M", "31"), + (0x325C, "M", "32"), + (0x325D, "M", "33"), + (0x325E, "M", "34"), + (0x325F, "M", "35"), + (0x3260, "M", "ᄀ"), + (0x3261, "M", "ᄂ"), + (0x3262, "M", "ᄃ"), + (0x3263, "M", "ᄅ"), + (0x3264, "M", "ᄆ"), + (0x3265, "M", "ᄇ"), + (0x3266, "M", "ᄉ"), + (0x3267, "M", "ᄋ"), + (0x3268, "M", "ᄌ"), + (0x3269, "M", "ᄎ"), + (0x326A, "M", "ᄏ"), + (0x326B, "M", "ᄐ"), + (0x326C, "M", "ᄑ"), + (0x326D, "M", "ᄒ"), + (0x326E, "M", "가"), + (0x326F, "M", "나"), + (0x3270, "M", "다"), + (0x3271, "M", "라"), + (0x3272, "M", "마"), + (0x3273, "M", "바"), + (0x3274, "M", "사"), + (0x3275, "M", "아"), + (0x3276, "M", "자"), + (0x3277, "M", "차"), + (0x3278, "M", "카"), + (0x3279, "M", "타"), + (0x327A, "M", "파"), + (0x327B, "M", "하"), + (0x327C, "M", "참고"), + (0x327D, "M", "주의"), + (0x327E, "M", "우"), + (0x327F, "V"), + (0x3280, "M", "一"), + (0x3281, "M", "二"), + (0x3282, "M", "三"), + (0x3283, "M", "四"), + (0x3284, "M", "五"), + (0x3285, "M", "六"), + (0x3286, "M", "七"), + (0x3287, "M", "八"), + (0x3288, "M", "九"), + (0x3289, "M", "十"), + (0x328A, "M", "月"), + (0x328B, "M", "火"), + (0x328C, "M", "水"), + (0x328D, "M", "木"), + (0x328E, "M", "金"), + (0x328F, "M", "土"), + (0x3290, "M", "日"), + (0x3291, "M", "株"), + (0x3292, "M", "有"), + (0x3293, "M", "社"), + (0x3294, "M", "名"), + (0x3295, "M", "特"), + (0x3296, "M", "財"), + (0x3297, "M", "祝"), + (0x3298, "M", "労"), + (0x3299, "M", "秘"), + (0x329A, "M", "男"), + (0x329B, "M", "女"), + (0x329C, "M", "適"), + (0x329D, "M", "優"), + (0x329E, "M", "印"), + (0x329F, "M", "注"), + (0x32A0, "M", "項"), + (0x32A1, "M", "休"), + (0x32A2, "M", "写"), + (0x32A3, "M", "正"), + (0x32A4, "M", "上"), + (0x32A5, "M", "中"), + (0x32A6, "M", "下"), + (0x32A7, "M", "左"), + (0x32A8, "M", "右"), + (0x32A9, "M", "医"), + (0x32AA, "M", "宗"), + (0x32AB, "M", "学"), + (0x32AC, "M", "監"), + (0x32AD, "M", "企"), + (0x32AE, "M", "資"), + (0x32AF, "M", "協"), + (0x32B0, "M", "夜"), + (0x32B1, "M", "36"), + (0x32B2, "M", "37"), + (0x32B3, "M", "38"), + (0x32B4, "M", "39"), + (0x32B5, "M", "40"), + (0x32B6, "M", "41"), + (0x32B7, "M", "42"), + (0x32B8, "M", "43"), + (0x32B9, "M", "44"), + (0x32BA, "M", "45"), + (0x32BB, "M", "46"), + (0x32BC, "M", "47"), + (0x32BD, "M", "48"), + (0x32BE, "M", "49"), + (0x32BF, "M", "50"), + (0x32C0, "M", "1月"), + (0x32C1, "M", "2月"), + (0x32C2, "M", "3月"), + (0x32C3, "M", "4月"), + (0x32C4, "M", "5月"), + (0x32C5, "M", "6月"), + (0x32C6, "M", "7月"), + (0x32C7, "M", "8月"), + (0x32C8, "M", "9月"), + (0x32C9, "M", "10月"), + (0x32CA, "M", "11月"), + (0x32CB, "M", "12月"), + (0x32CC, "M", "hg"), + (0x32CD, "M", "erg"), + (0x32CE, "M", "ev"), + (0x32CF, "M", "ltd"), + (0x32D0, "M", "ア"), + (0x32D1, "M", "イ"), + (0x32D2, "M", "ウ"), + (0x32D3, "M", "エ"), + (0x32D4, "M", "オ"), + (0x32D5, "M", "カ"), + (0x32D6, "M", "キ"), + (0x32D7, "M", "ク"), + (0x32D8, "M", "ケ"), + (0x32D9, "M", "コ"), + (0x32DA, "M", "サ"), + (0x32DB, "M", "シ"), + (0x32DC, "M", "ス"), + (0x32DD, "M", "セ"), + (0x32DE, "M", "ソ"), + (0x32DF, "M", "タ"), + (0x32E0, "M", "チ"), + (0x32E1, "M", "ツ"), + (0x32E2, "M", "テ"), + (0x32E3, "M", "ト"), + (0x32E4, "M", "ナ"), + (0x32E5, "M", "ニ"), + (0x32E6, "M", "ヌ"), + (0x32E7, "M", "ネ"), + (0x32E8, "M", "ノ"), + (0x32E9, "M", "ハ"), + (0x32EA, "M", "ヒ"), + (0x32EB, "M", "フ"), + (0x32EC, "M", "ヘ"), + (0x32ED, "M", "ホ"), + (0x32EE, "M", "マ"), + (0x32EF, "M", "ミ"), + (0x32F0, "M", "ム"), + (0x32F1, "M", "メ"), + (0x32F2, "M", "モ"), + (0x32F3, "M", "ヤ"), + (0x32F4, "M", "ユ"), + (0x32F5, "M", "ヨ"), + (0x32F6, "M", "ラ"), + (0x32F7, "M", "リ"), + (0x32F8, "M", "ル"), + (0x32F9, "M", "レ"), + (0x32FA, "M", "ロ"), + (0x32FB, "M", "ワ"), + (0x32FC, "M", "ヰ"), + (0x32FD, "M", "ヱ"), + (0x32FE, "M", "ヲ"), + (0x32FF, "M", "令和"), + (0x3300, "M", "アパート"), + (0x3301, "M", "アルファ"), + (0x3302, "M", "アンペア"), + (0x3303, "M", "アール"), + (0x3304, "M", "イニング"), + (0x3305, "M", "インチ"), + (0x3306, "M", "ウォン"), + (0x3307, "M", "エスクード"), + (0x3308, "M", "エーカー"), + (0x3309, "M", "オンス"), + (0x330A, "M", "オーム"), + (0x330B, "M", "カイリ"), + (0x330C, "M", "カラット"), + (0x330D, "M", "カロリー"), + (0x330E, "M", "ガロン"), + (0x330F, "M", "ガンマ"), + (0x3310, "M", "ギガ"), + (0x3311, "M", "ギニー"), + (0x3312, "M", "キュリー"), + (0x3313, "M", "ギルダー"), + (0x3314, "M", "キロ"), + (0x3315, "M", "キログラム"), + (0x3316, "M", "キロメートル"), + (0x3317, "M", "キロワット"), + (0x3318, "M", "グラム"), + (0x3319, "M", "グラムトン"), + (0x331A, "M", "クルゼイロ"), + (0x331B, "M", "クローネ"), + (0x331C, "M", "ケース"), + (0x331D, "M", "コルナ"), + (0x331E, "M", "コーポ"), + (0x331F, "M", "サイクル"), + (0x3320, "M", "サンチーム"), + (0x3321, "M", "シリング"), + (0x3322, "M", "センチ"), + (0x3323, "M", "セント"), + (0x3324, "M", "ダース"), + (0x3325, "M", "デシ"), + (0x3326, "M", "ドル"), + (0x3327, "M", "トン"), + (0x3328, "M", "ナノ"), + (0x3329, "M", "ノット"), + (0x332A, "M", "ハイツ"), + (0x332B, "M", "パーセント"), + (0x332C, "M", "パーツ"), + (0x332D, "M", "バーレル"), + (0x332E, "M", "ピアストル"), + (0x332F, "M", "ピクル"), + (0x3330, "M", "ピコ"), + (0x3331, "M", "ビル"), + (0x3332, "M", "ファラッド"), + (0x3333, "M", "フィート"), + (0x3334, "M", "ブッシェル"), + (0x3335, "M", "フラン"), + (0x3336, "M", "ヘクタール"), + (0x3337, "M", "ペソ"), + (0x3338, "M", "ペニヒ"), + (0x3339, "M", "ヘルツ"), + (0x333A, "M", "ペンス"), + (0x333B, "M", "ページ"), + (0x333C, "M", "ベータ"), + (0x333D, "M", "ポイント"), + (0x333E, "M", "ボルト"), + (0x333F, "M", "ホン"), + (0x3340, "M", "ポンド"), + (0x3341, "M", "ホール"), + (0x3342, "M", "ホーン"), + (0x3343, "M", "マイクロ"), + (0x3344, "M", "マイル"), + (0x3345, "M", "マッハ"), + (0x3346, "M", "マルク"), + (0x3347, "M", "マンション"), + (0x3348, "M", "ミクロン"), + (0x3349, "M", "ミリ"), + (0x334A, "M", "ミリバール"), + (0x334B, "M", "メガ"), + (0x334C, "M", "メガトン"), + (0x334D, "M", "メートル"), + (0x334E, "M", "ヤード"), + (0x334F, "M", "ヤール"), + (0x3350, "M", "ユアン"), + (0x3351, "M", "リットル"), + (0x3352, "M", "リラ"), + (0x3353, "M", "ルピー"), + (0x3354, "M", "ルーブル"), + (0x3355, "M", "レム"), + (0x3356, "M", "レントゲン"), + (0x3357, "M", "ワット"), + (0x3358, "M", "0点"), + (0x3359, "M", "1点"), + (0x335A, "M", "2点"), + (0x335B, "M", "3点"), + (0x335C, "M", "4点"), + (0x335D, "M", "5点"), + (0x335E, "M", "6点"), + (0x335F, "M", "7点"), + (0x3360, "M", "8点"), + (0x3361, "M", "9点"), + (0x3362, "M", "10点"), + (0x3363, "M", "11点"), + (0x3364, "M", "12点"), + (0x3365, "M", "13点"), + (0x3366, "M", "14点"), + (0x3367, "M", "15点"), + (0x3368, "M", "16点"), + (0x3369, "M", "17点"), + (0x336A, "M", "18点"), + (0x336B, "M", "19点"), + (0x336C, "M", "20点"), + (0x336D, "M", "21点"), + (0x336E, "M", "22点"), + (0x336F, "M", "23点"), + (0x3370, "M", "24点"), + (0x3371, "M", "hpa"), + (0x3372, "M", "da"), + (0x3373, "M", "au"), + (0x3374, "M", "bar"), + (0x3375, "M", "ov"), + (0x3376, "M", "pc"), + (0x3377, "M", "dm"), + (0x3378, "M", "dm2"), + (0x3379, "M", "dm3"), + (0x337A, "M", "iu"), + (0x337B, "M", "平成"), + (0x337C, "M", "昭和"), + (0x337D, "M", "大正"), + (0x337E, "M", "明治"), + (0x337F, "M", "株式会社"), + (0x3380, "M", "pa"), + (0x3381, "M", "na"), + (0x3382, "M", "μa"), + (0x3383, "M", "ma"), + (0x3384, "M", "ka"), + (0x3385, "M", "kb"), + (0x3386, "M", "mb"), + (0x3387, "M", "gb"), + (0x3388, "M", "cal"), + (0x3389, "M", "kcal"), + (0x338A, "M", "pf"), + (0x338B, "M", "nf"), + (0x338C, "M", "μf"), + (0x338D, "M", "μg"), + (0x338E, "M", "mg"), + (0x338F, "M", "kg"), + (0x3390, "M", "hz"), + (0x3391, "M", "khz"), + (0x3392, "M", "mhz"), + (0x3393, "M", "ghz"), + (0x3394, "M", "thz"), + (0x3395, "M", "μl"), + (0x3396, "M", "ml"), + (0x3397, "M", "dl"), + (0x3398, "M", "kl"), + (0x3399, "M", "fm"), + (0x339A, "M", "nm"), + (0x339B, "M", "μm"), + (0x339C, "M", "mm"), + (0x339D, "M", "cm"), + (0x339E, "M", "km"), + (0x339F, "M", "mm2"), + (0x33A0, "M", "cm2"), + (0x33A1, "M", "m2"), + (0x33A2, "M", "km2"), + (0x33A3, "M", "mm3"), + (0x33A4, "M", "cm3"), + (0x33A5, "M", "m3"), + (0x33A6, "M", "km3"), + (0x33A7, "M", "m∕s"), + (0x33A8, "M", "m∕s2"), + (0x33A9, "M", "pa"), + (0x33AA, "M", "kpa"), + (0x33AB, "M", "mpa"), + (0x33AC, "M", "gpa"), + (0x33AD, "M", "rad"), + (0x33AE, "M", "rad∕s"), + (0x33AF, "M", "rad∕s2"), + (0x33B0, "M", "ps"), + (0x33B1, "M", "ns"), + (0x33B2, "M", "μs"), + (0x33B3, "M", "ms"), + (0x33B4, "M", "pv"), + (0x33B5, "M", "nv"), + (0x33B6, "M", "μv"), + (0x33B7, "M", "mv"), + (0x33B8, "M", "kv"), + (0x33B9, "M", "mv"), + (0x33BA, "M", "pw"), + (0x33BB, "M", "nw"), + (0x33BC, "M", "μw"), + (0x33BD, "M", "mw"), + (0x33BE, "M", "kw"), + (0x33BF, "M", "mw"), + (0x33C0, "M", "kω"), + (0x33C1, "M", "mω"), + (0x33C2, "X"), + (0x33C3, "M", "bq"), + (0x33C4, "M", "cc"), + (0x33C5, "M", "cd"), + (0x33C6, "M", "c∕kg"), + (0x33C7, "X"), + (0x33C8, "M", "db"), + (0x33C9, "M", "gy"), + (0x33CA, "M", "ha"), + (0x33CB, "M", "hp"), + (0x33CC, "M", "in"), + (0x33CD, "M", "kk"), + (0x33CE, "M", "km"), + (0x33CF, "M", "kt"), + (0x33D0, "M", "lm"), + (0x33D1, "M", "ln"), + (0x33D2, "M", "log"), + (0x33D3, "M", "lx"), + (0x33D4, "M", "mb"), + (0x33D5, "M", "mil"), + (0x33D6, "M", "mol"), + (0x33D7, "M", "ph"), + (0x33D8, "X"), + (0x33D9, "M", "ppm"), + (0x33DA, "M", "pr"), + (0x33DB, "M", "sr"), + (0x33DC, "M", "sv"), + (0x33DD, "M", "wb"), + (0x33DE, "M", "v∕m"), + (0x33DF, "M", "a∕m"), + (0x33E0, "M", "1日"), + (0x33E1, "M", "2日"), + (0x33E2, "M", "3日"), + (0x33E3, "M", "4日"), + (0x33E4, "M", "5日"), + (0x33E5, "M", "6日"), + (0x33E6, "M", "7日"), + (0x33E7, "M", "8日"), + (0x33E8, "M", "9日"), + (0x33E9, "M", "10日"), + (0x33EA, "M", "11日"), + (0x33EB, "M", "12日"), + (0x33EC, "M", "13日"), + (0x33ED, "M", "14日"), + (0x33EE, "M", "15日"), + (0x33EF, "M", "16日"), + (0x33F0, "M", "17日"), + (0x33F1, "M", "18日"), + (0x33F2, "M", "19日"), + (0x33F3, "M", "20日"), + (0x33F4, "M", "21日"), + (0x33F5, "M", "22日"), + (0x33F6, "M", "23日"), + (0x33F7, "M", "24日"), + (0x33F8, "M", "25日"), + (0x33F9, "M", "26日"), + (0x33FA, "M", "27日"), + (0x33FB, "M", "28日"), + (0x33FC, "M", "29日"), + (0x33FD, "M", "30日"), + (0x33FE, "M", "31日"), + (0x33FF, "M", "gal"), + (0x3400, "V"), + (0xA48D, "X"), + (0xA490, "V"), + (0xA4C7, "X"), + (0xA4D0, "V"), + (0xA62C, "X"), + (0xA640, "M", "ꙁ"), + (0xA641, "V"), + (0xA642, "M", "ꙃ"), + (0xA643, "V"), + (0xA644, "M", "ꙅ"), + (0xA645, "V"), + (0xA646, "M", "ꙇ"), + (0xA647, "V"), + (0xA648, "M", "ꙉ"), + (0xA649, "V"), + (0xA64A, "M", "ꙋ"), + (0xA64B, "V"), + (0xA64C, "M", "ꙍ"), + (0xA64D, "V"), + (0xA64E, "M", "ꙏ"), + (0xA64F, "V"), + (0xA650, "M", "ꙑ"), + (0xA651, "V"), + (0xA652, "M", "ꙓ"), + (0xA653, "V"), + (0xA654, "M", "ꙕ"), + (0xA655, "V"), + (0xA656, "M", "ꙗ"), + (0xA657, "V"), + (0xA658, "M", "ꙙ"), + (0xA659, "V"), + (0xA65A, "M", "ꙛ"), + (0xA65B, "V"), + (0xA65C, "M", "ꙝ"), + (0xA65D, "V"), + (0xA65E, "M", "ꙟ"), + (0xA65F, "V"), + (0xA660, "M", "ꙡ"), + (0xA661, "V"), + (0xA662, "M", "ꙣ"), + (0xA663, "V"), + (0xA664, "M", "ꙥ"), + (0xA665, "V"), + (0xA666, "M", "ꙧ"), + (0xA667, "V"), + (0xA668, "M", "ꙩ"), + (0xA669, "V"), + (0xA66A, "M", "ꙫ"), + (0xA66B, "V"), + (0xA66C, "M", "ꙭ"), + (0xA66D, "V"), + (0xA680, "M", "ꚁ"), + (0xA681, "V"), + (0xA682, "M", "ꚃ"), + (0xA683, "V"), + (0xA684, "M", "ꚅ"), + (0xA685, "V"), + (0xA686, "M", "ꚇ"), + (0xA687, "V"), + (0xA688, "M", "ꚉ"), + (0xA689, "V"), + (0xA68A, "M", "ꚋ"), + (0xA68B, "V"), + (0xA68C, "M", "ꚍ"), + (0xA68D, "V"), + (0xA68E, "M", "ꚏ"), + (0xA68F, "V"), + (0xA690, "M", "ꚑ"), + (0xA691, "V"), + (0xA692, "M", "ꚓ"), + (0xA693, "V"), + (0xA694, "M", "ꚕ"), + (0xA695, "V"), + (0xA696, "M", "ꚗ"), + (0xA697, "V"), + (0xA698, "M", "ꚙ"), + (0xA699, "V"), + (0xA69A, "M", "ꚛ"), + (0xA69B, "V"), + (0xA69C, "M", "ъ"), + (0xA69D, "M", "ь"), + (0xA69E, "V"), + (0xA6F8, "X"), + (0xA700, "V"), + (0xA722, "M", "ꜣ"), + (0xA723, "V"), + (0xA724, "M", "ꜥ"), + (0xA725, "V"), + (0xA726, "M", "ꜧ"), + (0xA727, "V"), + (0xA728, "M", "ꜩ"), + (0xA729, "V"), + (0xA72A, "M", "ꜫ"), + (0xA72B, "V"), + (0xA72C, "M", "ꜭ"), + (0xA72D, "V"), + (0xA72E, "M", "ꜯ"), + (0xA72F, "V"), + (0xA732, "M", "ꜳ"), + (0xA733, "V"), + (0xA734, "M", "ꜵ"), + (0xA735, "V"), + (0xA736, "M", "ꜷ"), + (0xA737, "V"), + (0xA738, "M", "ꜹ"), + (0xA739, "V"), + (0xA73A, "M", "ꜻ"), + (0xA73B, "V"), + (0xA73C, "M", "ꜽ"), + (0xA73D, "V"), + (0xA73E, "M", "ꜿ"), + (0xA73F, "V"), + (0xA740, "M", "ꝁ"), + (0xA741, "V"), + (0xA742, "M", "ꝃ"), + (0xA743, "V"), + (0xA744, "M", "ꝅ"), + (0xA745, "V"), + (0xA746, "M", "ꝇ"), + (0xA747, "V"), + (0xA748, "M", "ꝉ"), + (0xA749, "V"), + (0xA74A, "M", "ꝋ"), + (0xA74B, "V"), + (0xA74C, "M", "ꝍ"), + (0xA74D, "V"), + (0xA74E, "M", "ꝏ"), + (0xA74F, "V"), + (0xA750, "M", "ꝑ"), + (0xA751, "V"), + (0xA752, "M", "ꝓ"), + (0xA753, "V"), + (0xA754, "M", "ꝕ"), + (0xA755, "V"), + (0xA756, "M", "ꝗ"), + (0xA757, "V"), + (0xA758, "M", "ꝙ"), + (0xA759, "V"), + (0xA75A, "M", "ꝛ"), + (0xA75B, "V"), + (0xA75C, "M", "ꝝ"), + (0xA75D, "V"), + (0xA75E, "M", "ꝟ"), + (0xA75F, "V"), + (0xA760, "M", "ꝡ"), + (0xA761, "V"), + (0xA762, "M", "ꝣ"), + (0xA763, "V"), + (0xA764, "M", "ꝥ"), + (0xA765, "V"), + (0xA766, "M", "ꝧ"), + (0xA767, "V"), + (0xA768, "M", "ꝩ"), + (0xA769, "V"), + (0xA76A, "M", "ꝫ"), + (0xA76B, "V"), + (0xA76C, "M", "ꝭ"), + (0xA76D, "V"), + (0xA76E, "M", "ꝯ"), + (0xA76F, "V"), + (0xA770, "M", "ꝯ"), + (0xA771, "V"), + (0xA779, "M", "ꝺ"), + (0xA77A, "V"), + (0xA77B, "M", "ꝼ"), + (0xA77C, "V"), + (0xA77D, "M", "ᵹ"), + (0xA77E, "M", "ꝿ"), + (0xA77F, "V"), + (0xA780, "M", "ꞁ"), + (0xA781, "V"), + (0xA782, "M", "ꞃ"), + (0xA783, "V"), + (0xA784, "M", "ꞅ"), + (0xA785, "V"), + (0xA786, "M", "ꞇ"), + (0xA787, "V"), + (0xA78B, "M", "ꞌ"), + (0xA78C, "V"), + (0xA78D, "M", "ɥ"), + (0xA78E, "V"), + (0xA790, "M", "ꞑ"), + (0xA791, "V"), + (0xA792, "M", "ꞓ"), + (0xA793, "V"), + (0xA796, "M", "ꞗ"), + (0xA797, "V"), + (0xA798, "M", "ꞙ"), + (0xA799, "V"), + (0xA79A, "M", "ꞛ"), + (0xA79B, "V"), + (0xA79C, "M", "ꞝ"), + (0xA79D, "V"), + (0xA79E, "M", "ꞟ"), + (0xA79F, "V"), + (0xA7A0, "M", "ꞡ"), + (0xA7A1, "V"), + (0xA7A2, "M", "ꞣ"), + (0xA7A3, "V"), + (0xA7A4, "M", "ꞥ"), + (0xA7A5, "V"), + (0xA7A6, "M", "ꞧ"), + (0xA7A7, "V"), + (0xA7A8, "M", "ꞩ"), + (0xA7A9, "V"), + (0xA7AA, "M", "ɦ"), + (0xA7AB, "M", "ɜ"), + (0xA7AC, "M", "ɡ"), + (0xA7AD, "M", "ɬ"), + (0xA7AE, "M", "ɪ"), + (0xA7AF, "V"), + (0xA7B0, "M", "ʞ"), + (0xA7B1, "M", "ʇ"), + (0xA7B2, "M", "ʝ"), + (0xA7B3, "M", "ꭓ"), + (0xA7B4, "M", "ꞵ"), + (0xA7B5, "V"), + (0xA7B6, "M", "ꞷ"), + (0xA7B7, "V"), + (0xA7B8, "M", "ꞹ"), + (0xA7B9, "V"), + (0xA7BA, "M", "ꞻ"), + (0xA7BB, "V"), + (0xA7BC, "M", "ꞽ"), + (0xA7BD, "V"), + (0xA7BE, "M", "ꞿ"), + (0xA7BF, "V"), + (0xA7C0, "M", "ꟁ"), + (0xA7C1, "V"), + (0xA7C2, "M", "ꟃ"), + (0xA7C3, "V"), + (0xA7C4, "M", "ꞔ"), + (0xA7C5, "M", "ʂ"), + (0xA7C6, "M", "ᶎ"), + (0xA7C7, "M", "ꟈ"), + (0xA7C8, "V"), + (0xA7C9, "M", "ꟊ"), + (0xA7CA, "V"), + (0xA7CB, "M", "ɤ"), + (0xA7CC, "M", "ꟍ"), + (0xA7CD, "V"), + (0xA7CE, "M", "꟏"), + (0xA7CF, "V"), + (0xA7D0, "M", "ꟑ"), + (0xA7D1, "V"), + (0xA7D2, "M", "ꟓ"), + (0xA7D3, "V"), + (0xA7D4, "M", "ꟕ"), + (0xA7D5, "V"), + (0xA7D6, "M", "ꟗ"), + (0xA7D7, "V"), + (0xA7D8, "M", "ꟙ"), + (0xA7D9, "V"), + (0xA7DA, "M", "ꟛ"), + (0xA7DB, "V"), + (0xA7DC, "M", "ƛ"), + (0xA7DD, "X"), + (0xA7F1, "M", "s"), + (0xA7F2, "M", "c"), + (0xA7F3, "M", "f"), + (0xA7F4, "M", "q"), + (0xA7F5, "M", "ꟶ"), + (0xA7F6, "V"), + (0xA7F8, "M", "ħ"), + (0xA7F9, "M", "œ"), + (0xA7FA, "V"), + (0xA82D, "X"), + (0xA830, "V"), + (0xA83A, "X"), + (0xA840, "V"), + (0xA878, "X"), + (0xA880, "V"), + (0xA8C6, "X"), + (0xA8CE, "V"), + (0xA8DA, "X"), + (0xA8E0, "V"), + (0xA954, "X"), + (0xA95F, "V"), + (0xA97D, "X"), + (0xA980, "V"), + (0xA9CE, "X"), + (0xA9CF, "V"), + (0xA9DA, "X"), + (0xA9DE, "V"), + (0xA9FF, "X"), + (0xAA00, "V"), + (0xAA37, "X"), + (0xAA40, "V"), + (0xAA4E, "X"), + (0xAA50, "V"), + (0xAA5A, "X"), + (0xAA5C, "V"), + (0xAAC3, "X"), + (0xAADB, "V"), + (0xAAF7, "X"), + (0xAB01, "V"), + (0xAB07, "X"), + (0xAB09, "V"), + (0xAB0F, "X"), + (0xAB11, "V"), + (0xAB17, "X"), + (0xAB20, "V"), + (0xAB27, "X"), + (0xAB28, "V"), + (0xAB2F, "X"), + (0xAB30, "V"), + (0xAB5C, "M", "ꜧ"), + (0xAB5D, "M", "ꬷ"), + (0xAB5E, "M", "ɫ"), + (0xAB5F, "M", "ꭒ"), + (0xAB60, "V"), + (0xAB69, "M", "ʍ"), + (0xAB6A, "V"), + (0xAB6C, "X"), + (0xAB70, "M", "Ꭰ"), + (0xAB71, "M", "Ꭱ"), + (0xAB72, "M", "Ꭲ"), + (0xAB73, "M", "Ꭳ"), + (0xAB74, "M", "Ꭴ"), + (0xAB75, "M", "Ꭵ"), + (0xAB76, "M", "Ꭶ"), + (0xAB77, "M", "Ꭷ"), + (0xAB78, "M", "Ꭸ"), + (0xAB79, "M", "Ꭹ"), + (0xAB7A, "M", "Ꭺ"), + (0xAB7B, "M", "Ꭻ"), + (0xAB7C, "M", "Ꭼ"), + (0xAB7D, "M", "Ꭽ"), + (0xAB7E, "M", "Ꭾ"), + (0xAB7F, "M", "Ꭿ"), + (0xAB80, "M", "Ꮀ"), + (0xAB81, "M", "Ꮁ"), + (0xAB82, "M", "Ꮂ"), + (0xAB83, "M", "Ꮃ"), + (0xAB84, "M", "Ꮄ"), + (0xAB85, "M", "Ꮅ"), + (0xAB86, "M", "Ꮆ"), + (0xAB87, "M", "Ꮇ"), + (0xAB88, "M", "Ꮈ"), + (0xAB89, "M", "Ꮉ"), + (0xAB8A, "M", "Ꮊ"), + (0xAB8B, "M", "Ꮋ"), + (0xAB8C, "M", "Ꮌ"), + (0xAB8D, "M", "Ꮍ"), + (0xAB8E, "M", "Ꮎ"), + (0xAB8F, "M", "Ꮏ"), + (0xAB90, "M", "Ꮐ"), + (0xAB91, "M", "Ꮑ"), + (0xAB92, "M", "Ꮒ"), + (0xAB93, "M", "Ꮓ"), + (0xAB94, "M", "Ꮔ"), + (0xAB95, "M", "Ꮕ"), + (0xAB96, "M", "Ꮖ"), + (0xAB97, "M", "Ꮗ"), + (0xAB98, "M", "Ꮘ"), + (0xAB99, "M", "Ꮙ"), + (0xAB9A, "M", "Ꮚ"), + (0xAB9B, "M", "Ꮛ"), + (0xAB9C, "M", "Ꮜ"), + (0xAB9D, "M", "Ꮝ"), + (0xAB9E, "M", "Ꮞ"), + (0xAB9F, "M", "Ꮟ"), + (0xABA0, "M", "Ꮠ"), + (0xABA1, "M", "Ꮡ"), + (0xABA2, "M", "Ꮢ"), + (0xABA3, "M", "Ꮣ"), + (0xABA4, "M", "Ꮤ"), + (0xABA5, "M", "Ꮥ"), + (0xABA6, "M", "Ꮦ"), + (0xABA7, "M", "Ꮧ"), + (0xABA8, "M", "Ꮨ"), + (0xABA9, "M", "Ꮩ"), + (0xABAA, "M", "Ꮪ"), + (0xABAB, "M", "Ꮫ"), + (0xABAC, "M", "Ꮬ"), + (0xABAD, "M", "Ꮭ"), + (0xABAE, "M", "Ꮮ"), + (0xABAF, "M", "Ꮯ"), + (0xABB0, "M", "Ꮰ"), + (0xABB1, "M", "Ꮱ"), + (0xABB2, "M", "Ꮲ"), + (0xABB3, "M", "Ꮳ"), + (0xABB4, "M", "Ꮴ"), + (0xABB5, "M", "Ꮵ"), + (0xABB6, "M", "Ꮶ"), + (0xABB7, "M", "Ꮷ"), + (0xABB8, "M", "Ꮸ"), + (0xABB9, "M", "Ꮹ"), + (0xABBA, "M", "Ꮺ"), + (0xABBB, "M", "Ꮻ"), + (0xABBC, "M", "Ꮼ"), + (0xABBD, "M", "Ꮽ"), + (0xABBE, "M", "Ꮾ"), + (0xABBF, "M", "Ꮿ"), + (0xABC0, "V"), + (0xABEE, "X"), + (0xABF0, "V"), + (0xABFA, "X"), + (0xAC00, "V"), + (0xD7A4, "X"), + (0xD7B0, "V"), + (0xD7C7, "X"), + (0xD7CB, "V"), + (0xD7FC, "X"), + (0xF900, "M", "豈"), + (0xF901, "M", "更"), + (0xF902, "M", "車"), + (0xF903, "M", "賈"), + (0xF904, "M", "滑"), + (0xF905, "M", "串"), + (0xF906, "M", "句"), + (0xF907, "M", "龜"), + (0xF909, "M", "契"), + (0xF90A, "M", "金"), + (0xF90B, "M", "喇"), + (0xF90C, "M", "奈"), + (0xF90D, "M", "懶"), + (0xF90E, "M", "癩"), + (0xF90F, "M", "羅"), + (0xF910, "M", "蘿"), + (0xF911, "M", "螺"), + (0xF912, "M", "裸"), + (0xF913, "M", "邏"), + (0xF914, "M", "樂"), + (0xF915, "M", "洛"), + (0xF916, "M", "烙"), + (0xF917, "M", "珞"), + (0xF918, "M", "落"), + (0xF919, "M", "酪"), + (0xF91A, "M", "駱"), + (0xF91B, "M", "亂"), + (0xF91C, "M", "卵"), + (0xF91D, "M", "欄"), + (0xF91E, "M", "爛"), + (0xF91F, "M", "蘭"), + (0xF920, "M", "鸞"), + (0xF921, "M", "嵐"), + (0xF922, "M", "濫"), + (0xF923, "M", "藍"), + (0xF924, "M", "襤"), + (0xF925, "M", "拉"), + (0xF926, "M", "臘"), + (0xF927, "M", "蠟"), + (0xF928, "M", "廊"), + (0xF929, "M", "朗"), + (0xF92A, "M", "浪"), + (0xF92B, "M", "狼"), + (0xF92C, "M", "郎"), + (0xF92D, "M", "來"), + (0xF92E, "M", "冷"), + (0xF92F, "M", "勞"), + (0xF930, "M", "擄"), + (0xF931, "M", "櫓"), + (0xF932, "M", "爐"), + (0xF933, "M", "盧"), + (0xF934, "M", "老"), + (0xF935, "M", "蘆"), + (0xF936, "M", "虜"), + (0xF937, "M", "路"), + (0xF938, "M", "露"), + (0xF939, "M", "魯"), + (0xF93A, "M", "鷺"), + (0xF93B, "M", "碌"), + (0xF93C, "M", "祿"), + (0xF93D, "M", "綠"), + (0xF93E, "M", "菉"), + (0xF93F, "M", "錄"), + (0xF940, "M", "鹿"), + (0xF941, "M", "論"), + (0xF942, "M", "壟"), + (0xF943, "M", "弄"), + (0xF944, "M", "籠"), + (0xF945, "M", "聾"), + (0xF946, "M", "牢"), + (0xF947, "M", "磊"), + (0xF948, "M", "賂"), + (0xF949, "M", "雷"), + (0xF94A, "M", "壘"), + (0xF94B, "M", "屢"), + (0xF94C, "M", "樓"), + (0xF94D, "M", "淚"), + (0xF94E, "M", "漏"), + (0xF94F, "M", "累"), + (0xF950, "M", "縷"), + (0xF951, "M", "陋"), + (0xF952, "M", "勒"), + (0xF953, "M", "肋"), + (0xF954, "M", "凜"), + (0xF955, "M", "凌"), + (0xF956, "M", "稜"), + (0xF957, "M", "綾"), + (0xF958, "M", "菱"), + (0xF959, "M", "陵"), + (0xF95A, "M", "讀"), + (0xF95B, "M", "拏"), + (0xF95C, "M", "樂"), + (0xF95D, "M", "諾"), + (0xF95E, "M", "丹"), + (0xF95F, "M", "寧"), + (0xF960, "M", "怒"), + (0xF961, "M", "率"), + (0xF962, "M", "異"), + (0xF963, "M", "北"), + (0xF964, "M", "磻"), + (0xF965, "M", "便"), + (0xF966, "M", "復"), + (0xF967, "M", "不"), + (0xF968, "M", "泌"), + (0xF969, "M", "數"), + (0xF96A, "M", "索"), + (0xF96B, "M", "參"), + (0xF96C, "M", "塞"), + (0xF96D, "M", "省"), + (0xF96E, "M", "葉"), + (0xF96F, "M", "說"), + (0xF970, "M", "殺"), + (0xF971, "M", "辰"), + (0xF972, "M", "沈"), + (0xF973, "M", "拾"), + (0xF974, "M", "若"), + (0xF975, "M", "掠"), + (0xF976, "M", "略"), + (0xF977, "M", "亮"), + (0xF978, "M", "兩"), + (0xF979, "M", "凉"), + (0xF97A, "M", "梁"), + (0xF97B, "M", "糧"), + (0xF97C, "M", "良"), + (0xF97D, "M", "諒"), + (0xF97E, "M", "量"), + (0xF97F, "M", "勵"), + (0xF980, "M", "呂"), + (0xF981, "M", "女"), + (0xF982, "M", "廬"), + (0xF983, "M", "旅"), + (0xF984, "M", "濾"), + (0xF985, "M", "礪"), + (0xF986, "M", "閭"), + (0xF987, "M", "驪"), + (0xF988, "M", "麗"), + (0xF989, "M", "黎"), + (0xF98A, "M", "力"), + (0xF98B, "M", "曆"), + (0xF98C, "M", "歷"), + (0xF98D, "M", "轢"), + (0xF98E, "M", "年"), + (0xF98F, "M", "憐"), + (0xF990, "M", "戀"), + (0xF991, "M", "撚"), + (0xF992, "M", "漣"), + (0xF993, "M", "煉"), + (0xF994, "M", "璉"), + (0xF995, "M", "秊"), + (0xF996, "M", "練"), + (0xF997, "M", "聯"), + (0xF998, "M", "輦"), + (0xF999, "M", "蓮"), + (0xF99A, "M", "連"), + (0xF99B, "M", "鍊"), + (0xF99C, "M", "列"), + (0xF99D, "M", "劣"), + (0xF99E, "M", "咽"), + (0xF99F, "M", "烈"), + (0xF9A0, "M", "裂"), + (0xF9A1, "M", "說"), + (0xF9A2, "M", "廉"), + (0xF9A3, "M", "念"), + (0xF9A4, "M", "捻"), + (0xF9A5, "M", "殮"), + (0xF9A6, "M", "簾"), + (0xF9A7, "M", "獵"), + (0xF9A8, "M", "令"), + (0xF9A9, "M", "囹"), + (0xF9AA, "M", "寧"), + (0xF9AB, "M", "嶺"), + (0xF9AC, "M", "怜"), + (0xF9AD, "M", "玲"), + (0xF9AE, "M", "瑩"), + (0xF9AF, "M", "羚"), + (0xF9B0, "M", "聆"), + (0xF9B1, "M", "鈴"), + (0xF9B2, "M", "零"), + (0xF9B3, "M", "靈"), + (0xF9B4, "M", "領"), + (0xF9B5, "M", "例"), + (0xF9B6, "M", "禮"), + (0xF9B7, "M", "醴"), + (0xF9B8, "M", "隸"), + (0xF9B9, "M", "惡"), + (0xF9BA, "M", "了"), + (0xF9BB, "M", "僚"), + (0xF9BC, "M", "寮"), + (0xF9BD, "M", "尿"), + (0xF9BE, "M", "料"), + (0xF9BF, "M", "樂"), + (0xF9C0, "M", "燎"), + (0xF9C1, "M", "療"), + (0xF9C2, "M", "蓼"), + (0xF9C3, "M", "遼"), + (0xF9C4, "M", "龍"), + (0xF9C5, "M", "暈"), + (0xF9C6, "M", "阮"), + (0xF9C7, "M", "劉"), + (0xF9C8, "M", "杻"), + (0xF9C9, "M", "柳"), + (0xF9CA, "M", "流"), + (0xF9CB, "M", "溜"), + (0xF9CC, "M", "琉"), + (0xF9CD, "M", "留"), + (0xF9CE, "M", "硫"), + (0xF9CF, "M", "紐"), + (0xF9D0, "M", "類"), + (0xF9D1, "M", "六"), + (0xF9D2, "M", "戮"), + (0xF9D3, "M", "陸"), + (0xF9D4, "M", "倫"), + (0xF9D5, "M", "崙"), + (0xF9D6, "M", "淪"), + (0xF9D7, "M", "輪"), + (0xF9D8, "M", "律"), + (0xF9D9, "M", "慄"), + (0xF9DA, "M", "栗"), + (0xF9DB, "M", "率"), + (0xF9DC, "M", "隆"), + (0xF9DD, "M", "利"), + (0xF9DE, "M", "吏"), + (0xF9DF, "M", "履"), + (0xF9E0, "M", "易"), + (0xF9E1, "M", "李"), + (0xF9E2, "M", "梨"), + (0xF9E3, "M", "泥"), + (0xF9E4, "M", "理"), + (0xF9E5, "M", "痢"), + (0xF9E6, "M", "罹"), + (0xF9E7, "M", "裏"), + (0xF9E8, "M", "裡"), + (0xF9E9, "M", "里"), + (0xF9EA, "M", "離"), + (0xF9EB, "M", "匿"), + (0xF9EC, "M", "溺"), + (0xF9ED, "M", "吝"), + (0xF9EE, "M", "燐"), + (0xF9EF, "M", "璘"), + (0xF9F0, "M", "藺"), + (0xF9F1, "M", "隣"), + (0xF9F2, "M", "鱗"), + (0xF9F3, "M", "麟"), + (0xF9F4, "M", "林"), + (0xF9F5, "M", "淋"), + (0xF9F6, "M", "臨"), + (0xF9F7, "M", "立"), + (0xF9F8, "M", "笠"), + (0xF9F9, "M", "粒"), + (0xF9FA, "M", "狀"), + (0xF9FB, "M", "炙"), + (0xF9FC, "M", "識"), + (0xF9FD, "M", "什"), + (0xF9FE, "M", "茶"), + (0xF9FF, "M", "刺"), + (0xFA00, "M", "切"), + (0xFA01, "M", "度"), + (0xFA02, "M", "拓"), + (0xFA03, "M", "糖"), + (0xFA04, "M", "宅"), + (0xFA05, "M", "洞"), + (0xFA06, "M", "暴"), + (0xFA07, "M", "輻"), + (0xFA08, "M", "行"), + (0xFA09, "M", "降"), + (0xFA0A, "M", "見"), + (0xFA0B, "M", "廓"), + (0xFA0C, "M", "兀"), + (0xFA0D, "M", "嗀"), + (0xFA0E, "V"), + (0xFA10, "M", "塚"), + (0xFA11, "V"), + (0xFA12, "M", "晴"), + (0xFA13, "V"), + (0xFA15, "M", "凞"), + (0xFA16, "M", "猪"), + (0xFA17, "M", "益"), + (0xFA18, "M", "礼"), + (0xFA19, "M", "神"), + (0xFA1A, "M", "祥"), + (0xFA1B, "M", "福"), + (0xFA1C, "M", "靖"), + (0xFA1D, "M", "精"), + (0xFA1E, "M", "羽"), + (0xFA1F, "V"), + (0xFA20, "M", "蘒"), + (0xFA21, "V"), + (0xFA22, "M", "諸"), + (0xFA23, "V"), + (0xFA25, "M", "逸"), + (0xFA26, "M", "都"), + (0xFA27, "V"), + (0xFA2A, "M", "飯"), + (0xFA2B, "M", "飼"), + (0xFA2C, "M", "館"), + (0xFA2D, "M", "鶴"), + (0xFA2E, "M", "郞"), + (0xFA2F, "M", "隷"), + (0xFA30, "M", "侮"), + (0xFA31, "M", "僧"), + (0xFA32, "M", "免"), + (0xFA33, "M", "勉"), + (0xFA34, "M", "勤"), + (0xFA35, "M", "卑"), + (0xFA36, "M", "喝"), + (0xFA37, "M", "嘆"), + (0xFA38, "M", "器"), + (0xFA39, "M", "塀"), + (0xFA3A, "M", "墨"), + (0xFA3B, "M", "層"), + (0xFA3C, "M", "屮"), + (0xFA3D, "M", "悔"), + (0xFA3E, "M", "慨"), + (0xFA3F, "M", "憎"), + (0xFA40, "M", "懲"), + (0xFA41, "M", "敏"), + (0xFA42, "M", "既"), + (0xFA43, "M", "暑"), + (0xFA44, "M", "梅"), + (0xFA45, "M", "海"), + (0xFA46, "M", "渚"), + (0xFA47, "M", "漢"), + (0xFA48, "M", "煮"), + (0xFA49, "M", "爫"), + (0xFA4A, "M", "琢"), + (0xFA4B, "M", "碑"), + (0xFA4C, "M", "社"), + (0xFA4D, "M", "祉"), + (0xFA4E, "M", "祈"), + (0xFA4F, "M", "祐"), + (0xFA50, "M", "祖"), + (0xFA51, "M", "祝"), + (0xFA52, "M", "禍"), + (0xFA53, "M", "禎"), + (0xFA54, "M", "穀"), + (0xFA55, "M", "突"), + (0xFA56, "M", "節"), + (0xFA57, "M", "練"), + (0xFA58, "M", "縉"), + (0xFA59, "M", "繁"), + (0xFA5A, "M", "署"), + (0xFA5B, "M", "者"), + (0xFA5C, "M", "臭"), + (0xFA5D, "M", "艹"), + (0xFA5F, "M", "著"), + (0xFA60, "M", "褐"), + (0xFA61, "M", "視"), + (0xFA62, "M", "謁"), + (0xFA63, "M", "謹"), + (0xFA64, "M", "賓"), + (0xFA65, "M", "贈"), + (0xFA66, "M", "辶"), + (0xFA67, "M", "逸"), + (0xFA68, "M", "難"), + (0xFA69, "M", "響"), + (0xFA6A, "M", "頻"), + (0xFA6B, "M", "恵"), + (0xFA6C, "M", "𤋮"), + (0xFA6D, "M", "舘"), + (0xFA6E, "X"), + (0xFA70, "M", "並"), + (0xFA71, "M", "况"), + (0xFA72, "M", "全"), + (0xFA73, "M", "侀"), + (0xFA74, "M", "充"), + (0xFA75, "M", "冀"), + (0xFA76, "M", "勇"), + (0xFA77, "M", "勺"), + (0xFA78, "M", "喝"), + (0xFA79, "M", "啕"), + (0xFA7A, "M", "喙"), + (0xFA7B, "M", "嗢"), + (0xFA7C, "M", "塚"), + (0xFA7D, "M", "墳"), + (0xFA7E, "M", "奄"), + (0xFA7F, "M", "奔"), + (0xFA80, "M", "婢"), + (0xFA81, "M", "嬨"), + (0xFA82, "M", "廒"), + (0xFA83, "M", "廙"), + (0xFA84, "M", "彩"), + (0xFA85, "M", "徭"), + (0xFA86, "M", "惘"), + (0xFA87, "M", "慎"), + (0xFA88, "M", "愈"), + (0xFA89, "M", "憎"), + (0xFA8A, "M", "慠"), + (0xFA8B, "M", "懲"), + (0xFA8C, "M", "戴"), + (0xFA8D, "M", "揄"), + (0xFA8E, "M", "搜"), + (0xFA8F, "M", "摒"), + (0xFA90, "M", "敖"), + (0xFA91, "M", "晴"), + (0xFA92, "M", "朗"), + (0xFA93, "M", "望"), + (0xFA94, "M", "杖"), + (0xFA95, "M", "歹"), + (0xFA96, "M", "殺"), + (0xFA97, "M", "流"), + (0xFA98, "M", "滛"), + (0xFA99, "M", "滋"), + (0xFA9A, "M", "漢"), + (0xFA9B, "M", "瀞"), + (0xFA9C, "M", "煮"), + (0xFA9D, "M", "瞧"), + (0xFA9E, "M", "爵"), + (0xFA9F, "M", "犯"), + (0xFAA0, "M", "猪"), + (0xFAA1, "M", "瑱"), + (0xFAA2, "M", "甆"), + (0xFAA3, "M", "画"), + (0xFAA4, "M", "瘝"), + (0xFAA5, "M", "瘟"), + (0xFAA6, "M", "益"), + (0xFAA7, "M", "盛"), + (0xFAA8, "M", "直"), + (0xFAA9, "M", "睊"), + (0xFAAA, "M", "着"), + (0xFAAB, "M", "磌"), + (0xFAAC, "M", "窱"), + (0xFAAD, "M", "節"), + (0xFAAE, "M", "类"), + (0xFAAF, "M", "絛"), + (0xFAB0, "M", "練"), + (0xFAB1, "M", "缾"), + (0xFAB2, "M", "者"), + (0xFAB3, "M", "荒"), + (0xFAB4, "M", "華"), + (0xFAB5, "M", "蝹"), + (0xFAB6, "M", "襁"), + (0xFAB7, "M", "覆"), + (0xFAB8, "M", "視"), + (0xFAB9, "M", "調"), + (0xFABA, "M", "諸"), + (0xFABB, "M", "請"), + (0xFABC, "M", "謁"), + (0xFABD, "M", "諾"), + (0xFABE, "M", "諭"), + (0xFABF, "M", "謹"), + (0xFAC0, "M", "變"), + (0xFAC1, "M", "贈"), + (0xFAC2, "M", "輸"), + (0xFAC3, "M", "遲"), + (0xFAC4, "M", "醙"), + (0xFAC5, "M", "鉶"), + (0xFAC6, "M", "陼"), + (0xFAC7, "M", "難"), + (0xFAC8, "M", "靖"), + (0xFAC9, "M", "韛"), + (0xFACA, "M", "響"), + (0xFACB, "M", "頋"), + (0xFACC, "M", "頻"), + (0xFACD, "M", "鬒"), + (0xFACE, "M", "龜"), + (0xFACF, "M", "𢡊"), + (0xFAD0, "M", "𢡄"), + (0xFAD1, "M", "𣏕"), + (0xFAD2, "M", "㮝"), + (0xFAD3, "M", "䀘"), + (0xFAD4, "M", "䀹"), + (0xFAD5, "M", "𥉉"), + (0xFAD6, "M", "𥳐"), + (0xFAD7, "M", "𧻓"), + (0xFAD8, "M", "齃"), + (0xFAD9, "M", "龎"), + (0xFADA, "X"), + (0xFB00, "M", "ff"), + (0xFB01, "M", "fi"), + (0xFB02, "M", "fl"), + (0xFB03, "M", "ffi"), + (0xFB04, "M", "ffl"), + (0xFB05, "M", "st"), + (0xFB07, "X"), + (0xFB13, "M", "մն"), + (0xFB14, "M", "մե"), + (0xFB15, "M", "մի"), + (0xFB16, "M", "վն"), + (0xFB17, "M", "մխ"), + (0xFB18, "X"), + (0xFB1D, "M", "יִ"), + (0xFB1E, "V"), + (0xFB1F, "M", "ײַ"), + (0xFB20, "M", "ע"), + (0xFB21, "M", "א"), + (0xFB22, "M", "ד"), + (0xFB23, "M", "ה"), + (0xFB24, "M", "כ"), + (0xFB25, "M", "ל"), + (0xFB26, "M", "ם"), + (0xFB27, "M", "ר"), + (0xFB28, "M", "ת"), + (0xFB29, "M", "+"), + (0xFB2A, "M", "שׁ"), + (0xFB2B, "M", "שׂ"), + (0xFB2C, "M", "שּׁ"), + (0xFB2D, "M", "שּׂ"), + (0xFB2E, "M", "אַ"), + (0xFB2F, "M", "אָ"), + (0xFB30, "M", "אּ"), + (0xFB31, "M", "בּ"), + (0xFB32, "M", "גּ"), + (0xFB33, "M", "דּ"), + (0xFB34, "M", "הּ"), + (0xFB35, "M", "וּ"), + (0xFB36, "M", "זּ"), + (0xFB37, "X"), + (0xFB38, "M", "טּ"), + (0xFB39, "M", "יּ"), + (0xFB3A, "M", "ךּ"), + (0xFB3B, "M", "כּ"), + (0xFB3C, "M", "לּ"), + (0xFB3D, "X"), + (0xFB3E, "M", "מּ"), + (0xFB3F, "X"), + (0xFB40, "M", "נּ"), + (0xFB41, "M", "סּ"), + (0xFB42, "X"), + (0xFB43, "M", "ףּ"), + (0xFB44, "M", "פּ"), + (0xFB45, "X"), + (0xFB46, "M", "צּ"), + (0xFB47, "M", "קּ"), + (0xFB48, "M", "רּ"), + (0xFB49, "M", "שּ"), + (0xFB4A, "M", "תּ"), + (0xFB4B, "M", "וֹ"), + (0xFB4C, "M", "בֿ"), + (0xFB4D, "M", "כֿ"), + (0xFB4E, "M", "פֿ"), + (0xFB4F, "M", "אל"), + (0xFB50, "M", "ٱ"), + (0xFB52, "M", "ٻ"), + (0xFB56, "M", "پ"), + (0xFB5A, "M", "ڀ"), + (0xFB5E, "M", "ٺ"), + (0xFB62, "M", "ٿ"), + (0xFB66, "M", "ٹ"), + (0xFB6A, "M", "ڤ"), + (0xFB6E, "M", "ڦ"), + (0xFB72, "M", "ڄ"), + (0xFB76, "M", "ڃ"), + (0xFB7A, "M", "چ"), + (0xFB7E, "M", "ڇ"), + (0xFB82, "M", "ڍ"), + (0xFB84, "M", "ڌ"), + (0xFB86, "M", "ڎ"), + (0xFB88, "M", "ڈ"), + (0xFB8A, "M", "ژ"), + (0xFB8C, "M", "ڑ"), + (0xFB8E, "M", "ک"), + (0xFB92, "M", "گ"), + (0xFB96, "M", "ڳ"), + (0xFB9A, "M", "ڱ"), + (0xFB9E, "M", "ں"), + (0xFBA0, "M", "ڻ"), + (0xFBA4, "M", "ۀ"), + (0xFBA6, "M", "ہ"), + (0xFBAA, "M", "ھ"), + (0xFBAE, "M", "ے"), + (0xFBB0, "M", "ۓ"), + (0xFBB2, "V"), + (0xFBD3, "M", "ڭ"), + (0xFBD7, "M", "ۇ"), + (0xFBD9, "M", "ۆ"), + (0xFBDB, "M", "ۈ"), + (0xFBDD, "M", "ۇٴ"), + (0xFBDE, "M", "ۋ"), + (0xFBE0, "M", "ۅ"), + (0xFBE2, "M", "ۉ"), + (0xFBE4, "M", "ې"), + (0xFBE8, "M", "ى"), + (0xFBEA, "M", "ئا"), + (0xFBEC, "M", "ئە"), + (0xFBEE, "M", "ئو"), + (0xFBF0, "M", "ئۇ"), + (0xFBF2, "M", "ئۆ"), + (0xFBF4, "M", "ئۈ"), + (0xFBF6, "M", "ئې"), + (0xFBF9, "M", "ئى"), + (0xFBFC, "M", "ی"), + (0xFC00, "M", "ئج"), + (0xFC01, "M", "ئح"), + (0xFC02, "M", "ئم"), + (0xFC03, "M", "ئى"), + (0xFC04, "M", "ئي"), + (0xFC05, "M", "بج"), + (0xFC06, "M", "بح"), + (0xFC07, "M", "بخ"), + (0xFC08, "M", "بم"), + (0xFC09, "M", "بى"), + (0xFC0A, "M", "بي"), + (0xFC0B, "M", "تج"), + (0xFC0C, "M", "تح"), + (0xFC0D, "M", "تخ"), + (0xFC0E, "M", "تم"), + (0xFC0F, "M", "تى"), + (0xFC10, "M", "تي"), + (0xFC11, "M", "ثج"), + (0xFC12, "M", "ثم"), + (0xFC13, "M", "ثى"), + (0xFC14, "M", "ثي"), + (0xFC15, "M", "جح"), + (0xFC16, "M", "جم"), + (0xFC17, "M", "حج"), + (0xFC18, "M", "حم"), + (0xFC19, "M", "خج"), + (0xFC1A, "M", "خح"), + (0xFC1B, "M", "خم"), + (0xFC1C, "M", "سج"), + (0xFC1D, "M", "سح"), + (0xFC1E, "M", "سخ"), + (0xFC1F, "M", "سم"), + (0xFC20, "M", "صح"), + (0xFC21, "M", "صم"), + (0xFC22, "M", "ضج"), + (0xFC23, "M", "ضح"), + (0xFC24, "M", "ضخ"), + (0xFC25, "M", "ضم"), + (0xFC26, "M", "طح"), + (0xFC27, "M", "طم"), + (0xFC28, "M", "ظم"), + (0xFC29, "M", "عج"), + (0xFC2A, "M", "عم"), + (0xFC2B, "M", "غج"), + (0xFC2C, "M", "غم"), + (0xFC2D, "M", "فج"), + (0xFC2E, "M", "فح"), + (0xFC2F, "M", "فخ"), + (0xFC30, "M", "فم"), + (0xFC31, "M", "فى"), + (0xFC32, "M", "في"), + (0xFC33, "M", "قح"), + (0xFC34, "M", "قم"), + (0xFC35, "M", "قى"), + (0xFC36, "M", "قي"), + (0xFC37, "M", "كا"), + (0xFC38, "M", "كج"), + (0xFC39, "M", "كح"), + (0xFC3A, "M", "كخ"), + (0xFC3B, "M", "كل"), + (0xFC3C, "M", "كم"), + (0xFC3D, "M", "كى"), + (0xFC3E, "M", "كي"), + (0xFC3F, "M", "لج"), + (0xFC40, "M", "لح"), + (0xFC41, "M", "لخ"), + (0xFC42, "M", "لم"), + (0xFC43, "M", "لى"), + (0xFC44, "M", "لي"), + (0xFC45, "M", "مج"), + (0xFC46, "M", "مح"), + (0xFC47, "M", "مخ"), + (0xFC48, "M", "مم"), + (0xFC49, "M", "مى"), + (0xFC4A, "M", "مي"), + (0xFC4B, "M", "نج"), + (0xFC4C, "M", "نح"), + (0xFC4D, "M", "نخ"), + (0xFC4E, "M", "نم"), + (0xFC4F, "M", "نى"), + (0xFC50, "M", "ني"), + (0xFC51, "M", "هج"), + (0xFC52, "M", "هم"), + (0xFC53, "M", "هى"), + (0xFC54, "M", "هي"), + (0xFC55, "M", "يج"), + (0xFC56, "M", "يح"), + (0xFC57, "M", "يخ"), + (0xFC58, "M", "يم"), + (0xFC59, "M", "يى"), + (0xFC5A, "M", "يي"), + (0xFC5B, "M", "ذٰ"), + (0xFC5C, "M", "رٰ"), + (0xFC5D, "M", "ىٰ"), + (0xFC5E, "M", " ٌّ"), + (0xFC5F, "M", " ٍّ"), + (0xFC60, "M", " َّ"), + (0xFC61, "M", " ُّ"), + (0xFC62, "M", " ِّ"), + (0xFC63, "M", " ّٰ"), + (0xFC64, "M", "ئر"), + (0xFC65, "M", "ئز"), + (0xFC66, "M", "ئم"), + (0xFC67, "M", "ئن"), + (0xFC68, "M", "ئى"), + (0xFC69, "M", "ئي"), + (0xFC6A, "M", "بر"), + (0xFC6B, "M", "بز"), + (0xFC6C, "M", "بم"), + (0xFC6D, "M", "بن"), + (0xFC6E, "M", "بى"), + (0xFC6F, "M", "بي"), + (0xFC70, "M", "تر"), + (0xFC71, "M", "تز"), + (0xFC72, "M", "تم"), + (0xFC73, "M", "تن"), + (0xFC74, "M", "تى"), + (0xFC75, "M", "تي"), + (0xFC76, "M", "ثر"), + (0xFC77, "M", "ثز"), + (0xFC78, "M", "ثم"), + (0xFC79, "M", "ثن"), + (0xFC7A, "M", "ثى"), + (0xFC7B, "M", "ثي"), + (0xFC7C, "M", "فى"), + (0xFC7D, "M", "في"), + (0xFC7E, "M", "قى"), + (0xFC7F, "M", "قي"), + (0xFC80, "M", "كا"), + (0xFC81, "M", "كل"), + (0xFC82, "M", "كم"), + (0xFC83, "M", "كى"), + (0xFC84, "M", "كي"), + (0xFC85, "M", "لم"), + (0xFC86, "M", "لى"), + (0xFC87, "M", "لي"), + (0xFC88, "M", "ما"), + (0xFC89, "M", "مم"), + (0xFC8A, "M", "نر"), + (0xFC8B, "M", "نز"), + (0xFC8C, "M", "نم"), + (0xFC8D, "M", "نن"), + (0xFC8E, "M", "نى"), + (0xFC8F, "M", "ني"), + (0xFC90, "M", "ىٰ"), + (0xFC91, "M", "ير"), + (0xFC92, "M", "يز"), + (0xFC93, "M", "يم"), + (0xFC94, "M", "ين"), + (0xFC95, "M", "يى"), + (0xFC96, "M", "يي"), + (0xFC97, "M", "ئج"), + (0xFC98, "M", "ئح"), + (0xFC99, "M", "ئخ"), + (0xFC9A, "M", "ئم"), + (0xFC9B, "M", "ئه"), + (0xFC9C, "M", "بج"), + (0xFC9D, "M", "بح"), + (0xFC9E, "M", "بخ"), + (0xFC9F, "M", "بم"), + (0xFCA0, "M", "به"), + (0xFCA1, "M", "تج"), + (0xFCA2, "M", "تح"), + (0xFCA3, "M", "تخ"), + (0xFCA4, "M", "تم"), + (0xFCA5, "M", "ته"), + (0xFCA6, "M", "ثم"), + (0xFCA7, "M", "جح"), + (0xFCA8, "M", "جم"), + (0xFCA9, "M", "حج"), + (0xFCAA, "M", "حم"), + (0xFCAB, "M", "خج"), + (0xFCAC, "M", "خم"), + (0xFCAD, "M", "سج"), + (0xFCAE, "M", "سح"), + (0xFCAF, "M", "سخ"), + (0xFCB0, "M", "سم"), + (0xFCB1, "M", "صح"), + (0xFCB2, "M", "صخ"), + (0xFCB3, "M", "صم"), + (0xFCB4, "M", "ضج"), + (0xFCB5, "M", "ضح"), + (0xFCB6, "M", "ضخ"), + (0xFCB7, "M", "ضم"), + (0xFCB8, "M", "طح"), + (0xFCB9, "M", "ظم"), + (0xFCBA, "M", "عج"), + (0xFCBB, "M", "عم"), + (0xFCBC, "M", "غج"), + (0xFCBD, "M", "غم"), + (0xFCBE, "M", "فج"), + (0xFCBF, "M", "فح"), + (0xFCC0, "M", "فخ"), + (0xFCC1, "M", "فم"), + (0xFCC2, "M", "قح"), + (0xFCC3, "M", "قم"), + (0xFCC4, "M", "كج"), + (0xFCC5, "M", "كح"), + (0xFCC6, "M", "كخ"), + (0xFCC7, "M", "كل"), + (0xFCC8, "M", "كم"), + (0xFCC9, "M", "لج"), + (0xFCCA, "M", "لح"), + (0xFCCB, "M", "لخ"), + (0xFCCC, "M", "لم"), + (0xFCCD, "M", "له"), + (0xFCCE, "M", "مج"), + (0xFCCF, "M", "مح"), + (0xFCD0, "M", "مخ"), + (0xFCD1, "M", "مم"), + (0xFCD2, "M", "نج"), + (0xFCD3, "M", "نح"), + (0xFCD4, "M", "نخ"), + (0xFCD5, "M", "نم"), + (0xFCD6, "M", "نه"), + (0xFCD7, "M", "هج"), + (0xFCD8, "M", "هم"), + (0xFCD9, "M", "هٰ"), + (0xFCDA, "M", "يج"), + (0xFCDB, "M", "يح"), + (0xFCDC, "M", "يخ"), + (0xFCDD, "M", "يم"), + (0xFCDE, "M", "يه"), + (0xFCDF, "M", "ئم"), + (0xFCE0, "M", "ئه"), + (0xFCE1, "M", "بم"), + (0xFCE2, "M", "به"), + (0xFCE3, "M", "تم"), + (0xFCE4, "M", "ته"), + (0xFCE5, "M", "ثم"), + (0xFCE6, "M", "ثه"), + (0xFCE7, "M", "سم"), + (0xFCE8, "M", "سه"), + (0xFCE9, "M", "شم"), + (0xFCEA, "M", "شه"), + (0xFCEB, "M", "كل"), + (0xFCEC, "M", "كم"), + (0xFCED, "M", "لم"), + (0xFCEE, "M", "نم"), + (0xFCEF, "M", "نه"), + (0xFCF0, "M", "يم"), + (0xFCF1, "M", "يه"), + (0xFCF2, "M", "ـَّ"), + (0xFCF3, "M", "ـُّ"), + (0xFCF4, "M", "ـِّ"), + (0xFCF5, "M", "طى"), + (0xFCF6, "M", "طي"), + (0xFCF7, "M", "عى"), + (0xFCF8, "M", "عي"), + (0xFCF9, "M", "غى"), + (0xFCFA, "M", "غي"), + (0xFCFB, "M", "سى"), + (0xFCFC, "M", "سي"), + (0xFCFD, "M", "شى"), + (0xFCFE, "M", "شي"), + (0xFCFF, "M", "حى"), + (0xFD00, "M", "حي"), + (0xFD01, "M", "جى"), + (0xFD02, "M", "جي"), + (0xFD03, "M", "خى"), + (0xFD04, "M", "خي"), + (0xFD05, "M", "صى"), + (0xFD06, "M", "صي"), + (0xFD07, "M", "ضى"), + (0xFD08, "M", "ضي"), + (0xFD09, "M", "شج"), + (0xFD0A, "M", "شح"), + (0xFD0B, "M", "شخ"), + (0xFD0C, "M", "شم"), + (0xFD0D, "M", "شر"), + (0xFD0E, "M", "سر"), + (0xFD0F, "M", "صر"), + (0xFD10, "M", "ضر"), + (0xFD11, "M", "طى"), + (0xFD12, "M", "طي"), + (0xFD13, "M", "عى"), + (0xFD14, "M", "عي"), + (0xFD15, "M", "غى"), + (0xFD16, "M", "غي"), + (0xFD17, "M", "سى"), + (0xFD18, "M", "سي"), + (0xFD19, "M", "شى"), + (0xFD1A, "M", "شي"), + (0xFD1B, "M", "حى"), + (0xFD1C, "M", "حي"), + (0xFD1D, "M", "جى"), + (0xFD1E, "M", "جي"), + (0xFD1F, "M", "خى"), + (0xFD20, "M", "خي"), + (0xFD21, "M", "صى"), + (0xFD22, "M", "صي"), + (0xFD23, "M", "ضى"), + (0xFD24, "M", "ضي"), + (0xFD25, "M", "شج"), + (0xFD26, "M", "شح"), + (0xFD27, "M", "شخ"), + (0xFD28, "M", "شم"), + (0xFD29, "M", "شر"), + (0xFD2A, "M", "سر"), + (0xFD2B, "M", "صر"), + (0xFD2C, "M", "ضر"), + (0xFD2D, "M", "شج"), + (0xFD2E, "M", "شح"), + (0xFD2F, "M", "شخ"), + (0xFD30, "M", "شم"), + (0xFD31, "M", "سه"), + (0xFD32, "M", "شه"), + (0xFD33, "M", "طم"), + (0xFD34, "M", "سج"), + (0xFD35, "M", "سح"), + (0xFD36, "M", "سخ"), + (0xFD37, "M", "شج"), + (0xFD38, "M", "شح"), + (0xFD39, "M", "شخ"), + (0xFD3A, "M", "طم"), + (0xFD3B, "M", "ظم"), + (0xFD3C, "M", "اً"), + (0xFD3E, "V"), + (0xFD50, "M", "تجم"), + (0xFD51, "M", "تحج"), + (0xFD53, "M", "تحم"), + (0xFD54, "M", "تخم"), + (0xFD55, "M", "تمج"), + (0xFD56, "M", "تمح"), + (0xFD57, "M", "تمخ"), + (0xFD58, "M", "جمح"), + (0xFD5A, "M", "حمي"), + (0xFD5B, "M", "حمى"), + (0xFD5C, "M", "سحج"), + (0xFD5D, "M", "سجح"), + (0xFD5E, "M", "سجى"), + (0xFD5F, "M", "سمح"), + (0xFD61, "M", "سمج"), + (0xFD62, "M", "سمم"), + (0xFD64, "M", "صحح"), + (0xFD66, "M", "صمم"), + (0xFD67, "M", "شحم"), + (0xFD69, "M", "شجي"), + (0xFD6A, "M", "شمخ"), + (0xFD6C, "M", "شمم"), + (0xFD6E, "M", "ضحى"), + (0xFD6F, "M", "ضخم"), + (0xFD71, "M", "طمح"), + (0xFD73, "M", "طمم"), + (0xFD74, "M", "طمي"), + (0xFD75, "M", "عجم"), + (0xFD76, "M", "عمم"), + (0xFD78, "M", "عمى"), + (0xFD79, "M", "غمم"), + (0xFD7A, "M", "غمي"), + (0xFD7B, "M", "غمى"), + (0xFD7C, "M", "فخم"), + (0xFD7E, "M", "قمح"), + (0xFD7F, "M", "قمم"), + (0xFD80, "M", "لحم"), + (0xFD81, "M", "لحي"), + (0xFD82, "M", "لحى"), + (0xFD83, "M", "لجج"), + (0xFD85, "M", "لخم"), + (0xFD87, "M", "لمح"), + (0xFD89, "M", "محج"), + (0xFD8A, "M", "محم"), + (0xFD8B, "M", "محي"), + (0xFD8C, "M", "مجح"), + (0xFD8D, "M", "مجم"), + (0xFD8E, "M", "مخج"), + (0xFD8F, "M", "مخم"), + (0xFD90, "V"), + (0xFD92, "M", "مجخ"), + (0xFD93, "M", "همج"), + (0xFD94, "M", "همم"), + (0xFD95, "M", "نحم"), + (0xFD96, "M", "نحى"), + (0xFD97, "M", "نجم"), + (0xFD99, "M", "نجى"), + (0xFD9A, "M", "نمي"), + (0xFD9B, "M", "نمى"), + (0xFD9C, "M", "يمم"), + (0xFD9E, "M", "بخي"), + (0xFD9F, "M", "تجي"), + (0xFDA0, "M", "تجى"), + (0xFDA1, "M", "تخي"), + (0xFDA2, "M", "تخى"), + (0xFDA3, "M", "تمي"), + (0xFDA4, "M", "تمى"), + (0xFDA5, "M", "جمي"), + (0xFDA6, "M", "جحى"), + (0xFDA7, "M", "جمى"), + (0xFDA8, "M", "سخى"), + (0xFDA9, "M", "صحي"), + (0xFDAA, "M", "شحي"), + (0xFDAB, "M", "ضحي"), + (0xFDAC, "M", "لجي"), + (0xFDAD, "M", "لمي"), + (0xFDAE, "M", "يحي"), + (0xFDAF, "M", "يجي"), + (0xFDB0, "M", "يمي"), + (0xFDB1, "M", "ممي"), + (0xFDB2, "M", "قمي"), + (0xFDB3, "M", "نحي"), + (0xFDB4, "M", "قمح"), + (0xFDB5, "M", "لحم"), + (0xFDB6, "M", "عمي"), + (0xFDB7, "M", "كمي"), + (0xFDB8, "M", "نجح"), + (0xFDB9, "M", "مخي"), + (0xFDBA, "M", "لجم"), + (0xFDBB, "M", "كمم"), + (0xFDBC, "M", "لجم"), + (0xFDBD, "M", "نجح"), + (0xFDBE, "M", "جحي"), + (0xFDBF, "M", "حجي"), + (0xFDC0, "M", "مجي"), + (0xFDC1, "M", "فمي"), + (0xFDC2, "M", "بحي"), + (0xFDC3, "M", "كمم"), + (0xFDC4, "M", "عجم"), + (0xFDC5, "M", "صمم"), + (0xFDC6, "M", "سخي"), + (0xFDC7, "M", "نجي"), + (0xFDC8, "V"), + (0xFDD0, "X"), + (0xFDF0, "M", "صلے"), + (0xFDF1, "M", "قلے"), + (0xFDF2, "M", "الله"), + (0xFDF3, "M", "اكبر"), + (0xFDF4, "M", "محمد"), + (0xFDF5, "M", "صلعم"), + (0xFDF6, "M", "رسول"), + (0xFDF7, "M", "عليه"), + (0xFDF8, "M", "وسلم"), + (0xFDF9, "M", "صلى"), + (0xFDFA, "M", "صلى الله عليه وسلم"), + (0xFDFB, "M", "جل جلاله"), + (0xFDFC, "M", "ریال"), + (0xFDFD, "V"), + (0xFE00, "I"), + (0xFE10, "M", ","), + (0xFE11, "M", "、"), + (0xFE12, "X"), + (0xFE13, "M", ":"), + (0xFE14, "M", ";"), + (0xFE15, "M", "!"), + (0xFE16, "M", "?"), + (0xFE17, "M", "〖"), + (0xFE18, "M", "〗"), + (0xFE19, "X"), + (0xFE20, "V"), + (0xFE30, "X"), + (0xFE31, "M", "—"), + (0xFE32, "M", "–"), + (0xFE33, "M", "_"), + (0xFE35, "M", "("), + (0xFE36, "M", ")"), + (0xFE37, "M", "{"), + (0xFE38, "M", "}"), + (0xFE39, "M", "〔"), + (0xFE3A, "M", "〕"), + (0xFE3B, "M", "【"), + (0xFE3C, "M", "】"), + (0xFE3D, "M", "《"), + (0xFE3E, "M", "》"), + (0xFE3F, "M", "〈"), + (0xFE40, "M", "〉"), + (0xFE41, "M", "「"), + (0xFE42, "M", "」"), + (0xFE43, "M", "『"), + (0xFE44, "M", "』"), + (0xFE45, "V"), + (0xFE47, "M", "["), + (0xFE48, "M", "]"), + (0xFE49, "M", " ̅"), + (0xFE4D, "M", "_"), + (0xFE50, "M", ","), + (0xFE51, "M", "、"), + (0xFE52, "X"), + (0xFE54, "M", ";"), + (0xFE55, "M", ":"), + (0xFE56, "M", "?"), + (0xFE57, "M", "!"), + (0xFE58, "M", "—"), + (0xFE59, "M", "("), + (0xFE5A, "M", ")"), + (0xFE5B, "M", "{"), + (0xFE5C, "M", "}"), + (0xFE5D, "M", "〔"), + (0xFE5E, "M", "〕"), + (0xFE5F, "M", "#"), + (0xFE60, "M", "&"), + (0xFE61, "M", "*"), + (0xFE62, "M", "+"), + (0xFE63, "M", "-"), + (0xFE64, "M", "<"), + (0xFE65, "M", ">"), + (0xFE66, "M", "="), + (0xFE67, "X"), + (0xFE68, "M", "\\"), + (0xFE69, "M", "$"), + (0xFE6A, "M", "%"), + (0xFE6B, "M", "@"), + (0xFE6C, "X"), + (0xFE70, "M", " ً"), + (0xFE71, "M", "ـً"), + (0xFE72, "M", " ٌ"), + (0xFE73, "V"), + (0xFE74, "M", " ٍ"), + (0xFE75, "X"), + (0xFE76, "M", " َ"), + (0xFE77, "M", "ـَ"), + (0xFE78, "M", " ُ"), + (0xFE79, "M", "ـُ"), + (0xFE7A, "M", " ِ"), + (0xFE7B, "M", "ـِ"), + (0xFE7C, "M", " ّ"), + (0xFE7D, "M", "ـّ"), + (0xFE7E, "M", " ْ"), + (0xFE7F, "M", "ـْ"), + (0xFE80, "M", "ء"), + (0xFE81, "M", "آ"), + (0xFE83, "M", "أ"), + (0xFE85, "M", "ؤ"), + (0xFE87, "M", "إ"), + (0xFE89, "M", "ئ"), + (0xFE8D, "M", "ا"), + (0xFE8F, "M", "ب"), + (0xFE93, "M", "ة"), + (0xFE95, "M", "ت"), + (0xFE99, "M", "ث"), + (0xFE9D, "M", "ج"), + (0xFEA1, "M", "ح"), + (0xFEA5, "M", "خ"), + (0xFEA9, "M", "د"), + (0xFEAB, "M", "ذ"), + (0xFEAD, "M", "ر"), + (0xFEAF, "M", "ز"), + (0xFEB1, "M", "س"), + (0xFEB5, "M", "ش"), + (0xFEB9, "M", "ص"), + (0xFEBD, "M", "ض"), + (0xFEC1, "M", "ط"), + (0xFEC5, "M", "ظ"), + (0xFEC9, "M", "ع"), + (0xFECD, "M", "غ"), + (0xFED1, "M", "ف"), + (0xFED5, "M", "ق"), + (0xFED9, "M", "ك"), + (0xFEDD, "M", "ل"), + (0xFEE1, "M", "م"), + (0xFEE5, "M", "ن"), + (0xFEE9, "M", "ه"), + (0xFEED, "M", "و"), + (0xFEEF, "M", "ى"), + (0xFEF1, "M", "ي"), + (0xFEF5, "M", "لآ"), + (0xFEF7, "M", "لأ"), + (0xFEF9, "M", "لإ"), + (0xFEFB, "M", "لا"), + (0xFEFD, "X"), + (0xFEFF, "I"), + (0xFF00, "X"), + (0xFF01, "M", "!"), + (0xFF02, "M", '"'), + (0xFF03, "M", "#"), + (0xFF04, "M", "$"), + (0xFF05, "M", "%"), + (0xFF06, "M", "&"), + (0xFF07, "M", "'"), + (0xFF08, "M", "("), + (0xFF09, "M", ")"), + (0xFF0A, "M", "*"), + (0xFF0B, "M", "+"), + (0xFF0C, "M", ","), + (0xFF0D, "M", "-"), + (0xFF0E, "M", "."), + (0xFF0F, "M", "/"), + (0xFF10, "M", "0"), + (0xFF11, "M", "1"), + (0xFF12, "M", "2"), + (0xFF13, "M", "3"), + (0xFF14, "M", "4"), + (0xFF15, "M", "5"), + (0xFF16, "M", "6"), + (0xFF17, "M", "7"), + (0xFF18, "M", "8"), + (0xFF19, "M", "9"), + (0xFF1A, "M", ":"), + (0xFF1B, "M", ";"), + (0xFF1C, "M", "<"), + (0xFF1D, "M", "="), + (0xFF1E, "M", ">"), + (0xFF1F, "M", "?"), + (0xFF20, "M", "@"), + (0xFF21, "M", "a"), + (0xFF22, "M", "b"), + (0xFF23, "M", "c"), + (0xFF24, "M", "d"), + (0xFF25, "M", "e"), + (0xFF26, "M", "f"), + (0xFF27, "M", "g"), + (0xFF28, "M", "h"), + (0xFF29, "M", "i"), + (0xFF2A, "M", "j"), + (0xFF2B, "M", "k"), + (0xFF2C, "M", "l"), + (0xFF2D, "M", "m"), + (0xFF2E, "M", "n"), + (0xFF2F, "M", "o"), + (0xFF30, "M", "p"), + (0xFF31, "M", "q"), + (0xFF32, "M", "r"), + (0xFF33, "M", "s"), + (0xFF34, "M", "t"), + (0xFF35, "M", "u"), + (0xFF36, "M", "v"), + (0xFF37, "M", "w"), + (0xFF38, "M", "x"), + (0xFF39, "M", "y"), + (0xFF3A, "M", "z"), + (0xFF3B, "M", "["), + (0xFF3C, "M", "\\"), + (0xFF3D, "M", "]"), + (0xFF3E, "M", "^"), + (0xFF3F, "M", "_"), + (0xFF40, "M", "`"), + (0xFF41, "M", "a"), + (0xFF42, "M", "b"), + (0xFF43, "M", "c"), + (0xFF44, "M", "d"), + (0xFF45, "M", "e"), + (0xFF46, "M", "f"), + (0xFF47, "M", "g"), + (0xFF48, "M", "h"), + (0xFF49, "M", "i"), + (0xFF4A, "M", "j"), + (0xFF4B, "M", "k"), + (0xFF4C, "M", "l"), + (0xFF4D, "M", "m"), + (0xFF4E, "M", "n"), + (0xFF4F, "M", "o"), + (0xFF50, "M", "p"), + (0xFF51, "M", "q"), + (0xFF52, "M", "r"), + (0xFF53, "M", "s"), + (0xFF54, "M", "t"), + (0xFF55, "M", "u"), + (0xFF56, "M", "v"), + (0xFF57, "M", "w"), + (0xFF58, "M", "x"), + (0xFF59, "M", "y"), + (0xFF5A, "M", "z"), + (0xFF5B, "M", "{"), + (0xFF5C, "M", "|"), + (0xFF5D, "M", "}"), + (0xFF5E, "M", "~"), + (0xFF5F, "M", "⦅"), + (0xFF60, "M", "⦆"), + (0xFF61, "M", "."), + (0xFF62, "M", "「"), + (0xFF63, "M", "」"), + (0xFF64, "M", "、"), + (0xFF65, "M", "・"), + (0xFF66, "M", "ヲ"), + (0xFF67, "M", "ァ"), + (0xFF68, "M", "ィ"), + (0xFF69, "M", "ゥ"), + (0xFF6A, "M", "ェ"), + (0xFF6B, "M", "ォ"), + (0xFF6C, "M", "ャ"), + (0xFF6D, "M", "ュ"), + (0xFF6E, "M", "ョ"), + (0xFF6F, "M", "ッ"), + (0xFF70, "M", "ー"), + (0xFF71, "M", "ア"), + (0xFF72, "M", "イ"), + (0xFF73, "M", "ウ"), + (0xFF74, "M", "エ"), + (0xFF75, "M", "オ"), + (0xFF76, "M", "カ"), + (0xFF77, "M", "キ"), + (0xFF78, "M", "ク"), + (0xFF79, "M", "ケ"), + (0xFF7A, "M", "コ"), + (0xFF7B, "M", "サ"), + (0xFF7C, "M", "シ"), + (0xFF7D, "M", "ス"), + (0xFF7E, "M", "セ"), + (0xFF7F, "M", "ソ"), + (0xFF80, "M", "タ"), + (0xFF81, "M", "チ"), + (0xFF82, "M", "ツ"), + (0xFF83, "M", "テ"), + (0xFF84, "M", "ト"), + (0xFF85, "M", "ナ"), + (0xFF86, "M", "ニ"), + (0xFF87, "M", "ヌ"), + (0xFF88, "M", "ネ"), + (0xFF89, "M", "ノ"), + (0xFF8A, "M", "ハ"), + (0xFF8B, "M", "ヒ"), + (0xFF8C, "M", "フ"), + (0xFF8D, "M", "ヘ"), + (0xFF8E, "M", "ホ"), + (0xFF8F, "M", "マ"), + (0xFF90, "M", "ミ"), + (0xFF91, "M", "ム"), + (0xFF92, "M", "メ"), + (0xFF93, "M", "モ"), + (0xFF94, "M", "ヤ"), + (0xFF95, "M", "ユ"), + (0xFF96, "M", "ヨ"), + (0xFF97, "M", "ラ"), + (0xFF98, "M", "リ"), + (0xFF99, "M", "ル"), + (0xFF9A, "M", "レ"), + (0xFF9B, "M", "ロ"), + (0xFF9C, "M", "ワ"), + (0xFF9D, "M", "ン"), + (0xFF9E, "M", "゙"), + (0xFF9F, "M", "゚"), + (0xFFA0, "I"), + (0xFFA1, "M", "ᄀ"), + (0xFFA2, "M", "ᄁ"), + (0xFFA3, "M", "ᆪ"), + (0xFFA4, "M", "ᄂ"), + (0xFFA5, "M", "ᆬ"), + (0xFFA6, "M", "ᆭ"), + (0xFFA7, "M", "ᄃ"), + (0xFFA8, "M", "ᄄ"), + (0xFFA9, "M", "ᄅ"), + (0xFFAA, "M", "ᆰ"), + (0xFFAB, "M", "ᆱ"), + (0xFFAC, "M", "ᆲ"), + (0xFFAD, "M", "ᆳ"), + (0xFFAE, "M", "ᆴ"), + (0xFFAF, "M", "ᆵ"), + (0xFFB0, "M", "ᄚ"), + (0xFFB1, "M", "ᄆ"), + (0xFFB2, "M", "ᄇ"), + (0xFFB3, "M", "ᄈ"), + (0xFFB4, "M", "ᄡ"), + (0xFFB5, "M", "ᄉ"), + (0xFFB6, "M", "ᄊ"), + (0xFFB7, "M", "ᄋ"), + (0xFFB8, "M", "ᄌ"), + (0xFFB9, "M", "ᄍ"), + (0xFFBA, "M", "ᄎ"), + (0xFFBB, "M", "ᄏ"), + (0xFFBC, "M", "ᄐ"), + (0xFFBD, "M", "ᄑ"), + (0xFFBE, "M", "ᄒ"), + (0xFFBF, "X"), + (0xFFC2, "M", "ᅡ"), + (0xFFC3, "M", "ᅢ"), + (0xFFC4, "M", "ᅣ"), + (0xFFC5, "M", "ᅤ"), + (0xFFC6, "M", "ᅥ"), + (0xFFC7, "M", "ᅦ"), + (0xFFC8, "X"), + (0xFFCA, "M", "ᅧ"), + (0xFFCB, "M", "ᅨ"), + (0xFFCC, "M", "ᅩ"), + (0xFFCD, "M", "ᅪ"), + (0xFFCE, "M", "ᅫ"), + (0xFFCF, "M", "ᅬ"), + (0xFFD0, "X"), + (0xFFD2, "M", "ᅭ"), + (0xFFD3, "M", "ᅮ"), + (0xFFD4, "M", "ᅯ"), + (0xFFD5, "M", "ᅰ"), + (0xFFD6, "M", "ᅱ"), + (0xFFD7, "M", "ᅲ"), + (0xFFD8, "X"), + (0xFFDA, "M", "ᅳ"), + (0xFFDB, "M", "ᅴ"), + (0xFFDC, "M", "ᅵ"), + (0xFFDD, "X"), + (0xFFE0, "M", "¢"), + (0xFFE1, "M", "£"), + (0xFFE2, "M", "¬"), + (0xFFE3, "M", " ̄"), + (0xFFE4, "M", "¦"), + (0xFFE5, "M", "¥"), + (0xFFE6, "M", "₩"), + (0xFFE7, "X"), + (0xFFE8, "M", "│"), + (0xFFE9, "M", "←"), + (0xFFEA, "M", "↑"), + (0xFFEB, "M", "→"), + (0xFFEC, "M", "↓"), + (0xFFED, "M", "■"), + (0xFFEE, "M", "○"), + (0xFFEF, "X"), + (0x10000, "V"), + (0x1000C, "X"), + (0x1000D, "V"), + (0x10027, "X"), + (0x10028, "V"), + (0x1003B, "X"), + (0x1003C, "V"), + (0x1003E, "X"), + (0x1003F, "V"), + (0x1004E, "X"), + (0x10050, "V"), + (0x1005E, "X"), + (0x10080, "V"), + (0x100FB, "X"), + (0x10100, "V"), + (0x10103, "X"), + (0x10107, "V"), + (0x10134, "X"), + (0x10137, "V"), + (0x1018F, "X"), + (0x10190, "V"), + (0x1019D, "X"), + (0x101A0, "V"), + (0x101A1, "X"), + (0x101D0, "V"), + (0x101FE, "X"), + (0x10280, "V"), + (0x1029D, "X"), + (0x102A0, "V"), + (0x102D1, "X"), + (0x102E0, "V"), + (0x102FC, "X"), + (0x10300, "V"), + (0x10324, "X"), + (0x1032D, "V"), + (0x1034B, "X"), + (0x10350, "V"), + (0x1037B, "X"), + (0x10380, "V"), + (0x1039E, "X"), + (0x1039F, "V"), + (0x103C4, "X"), + (0x103C8, "V"), + (0x103D6, "X"), + (0x10400, "M", "𐐨"), + (0x10401, "M", "𐐩"), + (0x10402, "M", "𐐪"), + (0x10403, "M", "𐐫"), + (0x10404, "M", "𐐬"), + (0x10405, "M", "𐐭"), + (0x10406, "M", "𐐮"), + (0x10407, "M", "𐐯"), + (0x10408, "M", "𐐰"), + (0x10409, "M", "𐐱"), + (0x1040A, "M", "𐐲"), + (0x1040B, "M", "𐐳"), + (0x1040C, "M", "𐐴"), + (0x1040D, "M", "𐐵"), + (0x1040E, "M", "𐐶"), + (0x1040F, "M", "𐐷"), + (0x10410, "M", "𐐸"), + (0x10411, "M", "𐐹"), + (0x10412, "M", "𐐺"), + (0x10413, "M", "𐐻"), + (0x10414, "M", "𐐼"), + (0x10415, "M", "𐐽"), + (0x10416, "M", "𐐾"), + (0x10417, "M", "𐐿"), + (0x10418, "M", "𐑀"), + (0x10419, "M", "𐑁"), + (0x1041A, "M", "𐑂"), + (0x1041B, "M", "𐑃"), + (0x1041C, "M", "𐑄"), + (0x1041D, "M", "𐑅"), + (0x1041E, "M", "𐑆"), + (0x1041F, "M", "𐑇"), + (0x10420, "M", "𐑈"), + (0x10421, "M", "𐑉"), + (0x10422, "M", "𐑊"), + (0x10423, "M", "𐑋"), + (0x10424, "M", "𐑌"), + (0x10425, "M", "𐑍"), + (0x10426, "M", "𐑎"), + (0x10427, "M", "𐑏"), + (0x10428, "V"), + (0x1049E, "X"), + (0x104A0, "V"), + (0x104AA, "X"), + (0x104B0, "M", "𐓘"), + (0x104B1, "M", "𐓙"), + (0x104B2, "M", "𐓚"), + (0x104B3, "M", "𐓛"), + (0x104B4, "M", "𐓜"), + (0x104B5, "M", "𐓝"), + (0x104B6, "M", "𐓞"), + (0x104B7, "M", "𐓟"), + (0x104B8, "M", "𐓠"), + (0x104B9, "M", "𐓡"), + (0x104BA, "M", "𐓢"), + (0x104BB, "M", "𐓣"), + (0x104BC, "M", "𐓤"), + (0x104BD, "M", "𐓥"), + (0x104BE, "M", "𐓦"), + (0x104BF, "M", "𐓧"), + (0x104C0, "M", "𐓨"), + (0x104C1, "M", "𐓩"), + (0x104C2, "M", "𐓪"), + (0x104C3, "M", "𐓫"), + (0x104C4, "M", "𐓬"), + (0x104C5, "M", "𐓭"), + (0x104C6, "M", "𐓮"), + (0x104C7, "M", "𐓯"), + (0x104C8, "M", "𐓰"), + (0x104C9, "M", "𐓱"), + (0x104CA, "M", "𐓲"), + (0x104CB, "M", "𐓳"), + (0x104CC, "M", "𐓴"), + (0x104CD, "M", "𐓵"), + (0x104CE, "M", "𐓶"), + (0x104CF, "M", "𐓷"), + (0x104D0, "M", "𐓸"), + (0x104D1, "M", "𐓹"), + (0x104D2, "M", "𐓺"), + (0x104D3, "M", "𐓻"), + (0x104D4, "X"), + (0x104D8, "V"), + (0x104FC, "X"), + (0x10500, "V"), + (0x10528, "X"), + (0x10530, "V"), + (0x10564, "X"), + (0x1056F, "V"), + (0x10570, "M", "𐖗"), + (0x10571, "M", "𐖘"), + (0x10572, "M", "𐖙"), + (0x10573, "M", "𐖚"), + (0x10574, "M", "𐖛"), + (0x10575, "M", "𐖜"), + (0x10576, "M", "𐖝"), + (0x10577, "M", "𐖞"), + (0x10578, "M", "𐖟"), + (0x10579, "M", "𐖠"), + (0x1057A, "M", "𐖡"), + (0x1057B, "X"), + (0x1057C, "M", "𐖣"), + (0x1057D, "M", "𐖤"), + (0x1057E, "M", "𐖥"), + (0x1057F, "M", "𐖦"), + (0x10580, "M", "𐖧"), + (0x10581, "M", "𐖨"), + (0x10582, "M", "𐖩"), + (0x10583, "M", "𐖪"), + (0x10584, "M", "𐖫"), + (0x10585, "M", "𐖬"), + (0x10586, "M", "𐖭"), + (0x10587, "M", "𐖮"), + (0x10588, "M", "𐖯"), + (0x10589, "M", "𐖰"), + (0x1058A, "M", "𐖱"), + (0x1058B, "X"), + (0x1058C, "M", "𐖳"), + (0x1058D, "M", "𐖴"), + (0x1058E, "M", "𐖵"), + (0x1058F, "M", "𐖶"), + (0x10590, "M", "𐖷"), + (0x10591, "M", "𐖸"), + (0x10592, "M", "𐖹"), + (0x10593, "X"), + (0x10594, "M", "𐖻"), + (0x10595, "M", "𐖼"), + (0x10596, "X"), + (0x10597, "V"), + (0x105A2, "X"), + (0x105A3, "V"), + (0x105B2, "X"), + (0x105B3, "V"), + (0x105BA, "X"), + (0x105BB, "V"), + (0x105BD, "X"), + (0x105C0, "V"), + (0x105F4, "X"), + (0x10600, "V"), + (0x10737, "X"), + (0x10740, "V"), + (0x10756, "X"), + (0x10760, "V"), + (0x10768, "X"), + (0x10780, "V"), + (0x10781, "M", "ː"), + (0x10782, "M", "ˑ"), + (0x10783, "M", "æ"), + (0x10784, "M", "ʙ"), + (0x10785, "M", "ɓ"), + (0x10786, "X"), + (0x10787, "M", "ʣ"), + (0x10788, "M", "ꭦ"), + (0x10789, "M", "ʥ"), + (0x1078A, "M", "ʤ"), + (0x1078B, "M", "ɖ"), + (0x1078C, "M", "ɗ"), + (0x1078D, "M", "ᶑ"), + (0x1078E, "M", "ɘ"), + (0x1078F, "M", "ɞ"), + (0x10790, "M", "ʩ"), + (0x10791, "M", "ɤ"), + (0x10792, "M", "ɢ"), + (0x10793, "M", "ɠ"), + (0x10794, "M", "ʛ"), + (0x10795, "M", "ħ"), + (0x10796, "M", "ʜ"), + (0x10797, "M", "ɧ"), + (0x10798, "M", "ʄ"), + (0x10799, "M", "ʪ"), + (0x1079A, "M", "ʫ"), + (0x1079B, "M", "ɬ"), + (0x1079C, "M", "𝼄"), + (0x1079D, "M", "ꞎ"), + (0x1079E, "M", "ɮ"), + (0x1079F, "M", "𝼅"), + (0x107A0, "M", "ʎ"), + (0x107A1, "M", "𝼆"), + (0x107A2, "M", "ø"), + (0x107A3, "M", "ɶ"), + (0x107A4, "M", "ɷ"), + (0x107A5, "M", "q"), + (0x107A6, "M", "ɺ"), + (0x107A7, "M", "𝼈"), + (0x107A8, "M", "ɽ"), + (0x107A9, "M", "ɾ"), + (0x107AA, "M", "ʀ"), + (0x107AB, "M", "ʨ"), + (0x107AC, "M", "ʦ"), + (0x107AD, "M", "ꭧ"), + (0x107AE, "M", "ʧ"), + (0x107AF, "M", "ʈ"), + (0x107B0, "M", "ⱱ"), + (0x107B1, "X"), + (0x107B2, "M", "ʏ"), + (0x107B3, "M", "ʡ"), + (0x107B4, "M", "ʢ"), + (0x107B5, "M", "ʘ"), + (0x107B6, "M", "ǀ"), + (0x107B7, "M", "ǁ"), + (0x107B8, "M", "ǂ"), + (0x107B9, "M", "𝼊"), + (0x107BA, "M", "𝼞"), + (0x107BB, "X"), + (0x10800, "V"), + (0x10806, "X"), + (0x10808, "V"), + (0x10809, "X"), + (0x1080A, "V"), + (0x10836, "X"), + (0x10837, "V"), + (0x10839, "X"), + (0x1083C, "V"), + (0x1083D, "X"), + (0x1083F, "V"), + (0x10856, "X"), + (0x10857, "V"), + (0x1089F, "X"), + (0x108A7, "V"), + (0x108B0, "X"), + (0x108E0, "V"), + (0x108F3, "X"), + (0x108F4, "V"), + (0x108F6, "X"), + (0x108FB, "V"), + (0x1091C, "X"), + (0x1091F, "V"), + (0x1093A, "X"), + (0x1093F, "V"), + (0x1095A, "X"), + (0x10980, "V"), + (0x109B8, "X"), + (0x109BC, "V"), + (0x109D0, "X"), + (0x109D2, "V"), + (0x10A04, "X"), + (0x10A05, "V"), + (0x10A07, "X"), + (0x10A0C, "V"), + (0x10A14, "X"), + (0x10A15, "V"), + (0x10A18, "X"), + (0x10A19, "V"), + (0x10A36, "X"), + (0x10A38, "V"), + (0x10A3B, "X"), + (0x10A3F, "V"), + (0x10A49, "X"), + (0x10A50, "V"), + (0x10A59, "X"), + (0x10A60, "V"), + (0x10AA0, "X"), + (0x10AC0, "V"), + (0x10AE7, "X"), + (0x10AEB, "V"), + (0x10AF7, "X"), + (0x10B00, "V"), + (0x10B36, "X"), + (0x10B39, "V"), + (0x10B56, "X"), + (0x10B58, "V"), + (0x10B73, "X"), + (0x10B78, "V"), + (0x10B92, "X"), + (0x10B99, "V"), + (0x10B9D, "X"), + (0x10BA9, "V"), + (0x10BB0, "X"), + (0x10C00, "V"), + (0x10C49, "X"), + (0x10C80, "M", "𐳀"), + (0x10C81, "M", "𐳁"), + (0x10C82, "M", "𐳂"), + (0x10C83, "M", "𐳃"), + (0x10C84, "M", "𐳄"), + (0x10C85, "M", "𐳅"), + (0x10C86, "M", "𐳆"), + (0x10C87, "M", "𐳇"), + (0x10C88, "M", "𐳈"), + (0x10C89, "M", "𐳉"), + (0x10C8A, "M", "𐳊"), + (0x10C8B, "M", "𐳋"), + (0x10C8C, "M", "𐳌"), + (0x10C8D, "M", "𐳍"), + (0x10C8E, "M", "𐳎"), + (0x10C8F, "M", "𐳏"), + (0x10C90, "M", "𐳐"), + (0x10C91, "M", "𐳑"), + (0x10C92, "M", "𐳒"), + (0x10C93, "M", "𐳓"), + (0x10C94, "M", "𐳔"), + (0x10C95, "M", "𐳕"), + (0x10C96, "M", "𐳖"), + (0x10C97, "M", "𐳗"), + (0x10C98, "M", "𐳘"), + (0x10C99, "M", "𐳙"), + (0x10C9A, "M", "𐳚"), + (0x10C9B, "M", "𐳛"), + (0x10C9C, "M", "𐳜"), + (0x10C9D, "M", "𐳝"), + (0x10C9E, "M", "𐳞"), + (0x10C9F, "M", "𐳟"), + (0x10CA0, "M", "𐳠"), + (0x10CA1, "M", "𐳡"), + (0x10CA2, "M", "𐳢"), + (0x10CA3, "M", "𐳣"), + (0x10CA4, "M", "𐳤"), + (0x10CA5, "M", "𐳥"), + (0x10CA6, "M", "𐳦"), + (0x10CA7, "M", "𐳧"), + (0x10CA8, "M", "𐳨"), + (0x10CA9, "M", "𐳩"), + (0x10CAA, "M", "𐳪"), + (0x10CAB, "M", "𐳫"), + (0x10CAC, "M", "𐳬"), + (0x10CAD, "M", "𐳭"), + (0x10CAE, "M", "𐳮"), + (0x10CAF, "M", "𐳯"), + (0x10CB0, "M", "𐳰"), + (0x10CB1, "M", "𐳱"), + (0x10CB2, "M", "𐳲"), + (0x10CB3, "X"), + (0x10CC0, "V"), + (0x10CF3, "X"), + (0x10CFA, "V"), + (0x10D28, "X"), + (0x10D30, "V"), + (0x10D3A, "X"), + (0x10D40, "V"), + (0x10D50, "M", "𐵰"), + (0x10D51, "M", "𐵱"), + (0x10D52, "M", "𐵲"), + (0x10D53, "M", "𐵳"), + (0x10D54, "M", "𐵴"), + (0x10D55, "M", "𐵵"), + (0x10D56, "M", "𐵶"), + (0x10D57, "M", "𐵷"), + (0x10D58, "M", "𐵸"), + (0x10D59, "M", "𐵹"), + (0x10D5A, "M", "𐵺"), + (0x10D5B, "M", "𐵻"), + (0x10D5C, "M", "𐵼"), + (0x10D5D, "M", "𐵽"), + (0x10D5E, "M", "𐵾"), + (0x10D5F, "M", "𐵿"), + (0x10D60, "M", "𐶀"), + (0x10D61, "M", "𐶁"), + (0x10D62, "M", "𐶂"), + (0x10D63, "M", "𐶃"), + (0x10D64, "M", "𐶄"), + (0x10D65, "M", "𐶅"), + (0x10D66, "X"), + (0x10D69, "V"), + (0x10D86, "X"), + (0x10D8E, "V"), + (0x10D90, "X"), + (0x10E60, "V"), + (0x10E7F, "X"), + (0x10E80, "V"), + (0x10EAA, "X"), + (0x10EAB, "V"), + (0x10EAE, "X"), + (0x10EB0, "V"), + (0x10EB2, "X"), + (0x10EC2, "V"), + (0x10EC8, "X"), + (0x10ED0, "V"), + (0x10ED9, "X"), + (0x10EFA, "V"), + (0x10F28, "X"), + (0x10F30, "V"), + (0x10F5A, "X"), + (0x10F70, "V"), + (0x10F8A, "X"), + (0x10FB0, "V"), + (0x10FCC, "X"), + (0x10FE0, "V"), + (0x10FF7, "X"), + (0x11000, "V"), + (0x1104E, "X"), + (0x11052, "V"), + (0x11076, "X"), + (0x1107F, "V"), + (0x110BD, "X"), + (0x110BE, "V"), + (0x110C3, "X"), + (0x110D0, "V"), + (0x110E9, "X"), + (0x110F0, "V"), + (0x110FA, "X"), + (0x11100, "V"), + (0x11135, "X"), + (0x11136, "V"), + (0x11148, "X"), + (0x11150, "V"), + (0x11177, "X"), + (0x11180, "V"), + (0x111E0, "X"), + (0x111E1, "V"), + (0x111F5, "X"), + (0x11200, "V"), + (0x11212, "X"), + (0x11213, "V"), + (0x11242, "X"), + (0x11280, "V"), + (0x11287, "X"), + (0x11288, "V"), + (0x11289, "X"), + (0x1128A, "V"), + (0x1128E, "X"), + (0x1128F, "V"), + (0x1129E, "X"), + (0x1129F, "V"), + (0x112AA, "X"), + (0x112B0, "V"), + (0x112EB, "X"), + (0x112F0, "V"), + (0x112FA, "X"), + (0x11300, "V"), + (0x11304, "X"), + (0x11305, "V"), + (0x1130D, "X"), + (0x1130F, "V"), + (0x11311, "X"), + (0x11313, "V"), + (0x11329, "X"), + (0x1132A, "V"), + (0x11331, "X"), + (0x11332, "V"), + (0x11334, "X"), + (0x11335, "V"), + (0x1133A, "X"), + (0x1133B, "V"), + (0x11345, "X"), + (0x11347, "V"), + (0x11349, "X"), + (0x1134B, "V"), + (0x1134E, "X"), + (0x11350, "V"), + (0x11351, "X"), + (0x11357, "V"), + (0x11358, "X"), + (0x1135D, "V"), + (0x11364, "X"), + (0x11366, "V"), + (0x1136D, "X"), + (0x11370, "V"), + (0x11375, "X"), + (0x11380, "V"), + (0x1138A, "X"), + (0x1138B, "V"), + (0x1138C, "X"), + (0x1138E, "V"), + (0x1138F, "X"), + (0x11390, "V"), + (0x113B6, "X"), + (0x113B7, "V"), + (0x113C1, "X"), + (0x113C2, "V"), + (0x113C3, "X"), + (0x113C5, "V"), + (0x113C6, "X"), + (0x113C7, "V"), + (0x113CB, "X"), + (0x113CC, "V"), + (0x113D6, "X"), + (0x113D7, "V"), + (0x113D9, "X"), + (0x113E1, "V"), + (0x113E3, "X"), + (0x11400, "V"), + (0x1145C, "X"), + (0x1145D, "V"), + (0x11462, "X"), + (0x11480, "V"), + (0x114C8, "X"), + (0x114D0, "V"), + (0x114DA, "X"), + (0x11580, "V"), + (0x115B6, "X"), + (0x115B8, "V"), + (0x115DE, "X"), + (0x11600, "V"), + (0x11645, "X"), + (0x11650, "V"), + (0x1165A, "X"), + (0x11660, "V"), + (0x1166D, "X"), + (0x11680, "V"), + (0x116BA, "X"), + (0x116C0, "V"), + (0x116CA, "X"), + (0x116D0, "V"), + (0x116E4, "X"), + (0x11700, "V"), + (0x1171B, "X"), + (0x1171D, "V"), + (0x1172C, "X"), + (0x11730, "V"), + (0x11747, "X"), + (0x11800, "V"), + (0x1183C, "X"), + (0x118A0, "M", "𑣀"), + (0x118A1, "M", "𑣁"), + (0x118A2, "M", "𑣂"), + (0x118A3, "M", "𑣃"), + (0x118A4, "M", "𑣄"), + (0x118A5, "M", "𑣅"), + (0x118A6, "M", "𑣆"), + (0x118A7, "M", "𑣇"), + (0x118A8, "M", "𑣈"), + (0x118A9, "M", "𑣉"), + (0x118AA, "M", "𑣊"), + (0x118AB, "M", "𑣋"), + (0x118AC, "M", "𑣌"), + (0x118AD, "M", "𑣍"), + (0x118AE, "M", "𑣎"), + (0x118AF, "M", "𑣏"), + (0x118B0, "M", "𑣐"), + (0x118B1, "M", "𑣑"), + (0x118B2, "M", "𑣒"), + (0x118B3, "M", "𑣓"), + (0x118B4, "M", "𑣔"), + (0x118B5, "M", "𑣕"), + (0x118B6, "M", "𑣖"), + (0x118B7, "M", "𑣗"), + (0x118B8, "M", "𑣘"), + (0x118B9, "M", "𑣙"), + (0x118BA, "M", "𑣚"), + (0x118BB, "M", "𑣛"), + (0x118BC, "M", "𑣜"), + (0x118BD, "M", "𑣝"), + (0x118BE, "M", "𑣞"), + (0x118BF, "M", "𑣟"), + (0x118C0, "V"), + (0x118F3, "X"), + (0x118FF, "V"), + (0x11907, "X"), + (0x11909, "V"), + (0x1190A, "X"), + (0x1190C, "V"), + (0x11914, "X"), + (0x11915, "V"), + (0x11917, "X"), + (0x11918, "V"), + (0x11936, "X"), + (0x11937, "V"), + (0x11939, "X"), + (0x1193B, "V"), + (0x11947, "X"), + (0x11950, "V"), + (0x1195A, "X"), + (0x119A0, "V"), + (0x119A8, "X"), + (0x119AA, "V"), + (0x119D8, "X"), + (0x119DA, "V"), + (0x119E5, "X"), + (0x11A00, "V"), + (0x11A48, "X"), + (0x11A50, "V"), + (0x11AA3, "X"), + (0x11AB0, "V"), + (0x11AF9, "X"), + (0x11B00, "V"), + (0x11B0A, "X"), + (0x11B60, "V"), + (0x11B68, "X"), + (0x11BC0, "V"), + (0x11BE2, "X"), + (0x11BF0, "V"), + (0x11BFA, "X"), + (0x11C00, "V"), + (0x11C09, "X"), + (0x11C0A, "V"), + (0x11C37, "X"), + (0x11C38, "V"), + (0x11C46, "X"), + (0x11C50, "V"), + (0x11C6D, "X"), + (0x11C70, "V"), + (0x11C90, "X"), + (0x11C92, "V"), + (0x11CA8, "X"), + (0x11CA9, "V"), + (0x11CB7, "X"), + (0x11D00, "V"), + (0x11D07, "X"), + (0x11D08, "V"), + (0x11D0A, "X"), + (0x11D0B, "V"), + (0x11D37, "X"), + (0x11D3A, "V"), + (0x11D3B, "X"), + (0x11D3C, "V"), + (0x11D3E, "X"), + (0x11D3F, "V"), + (0x11D48, "X"), + (0x11D50, "V"), + (0x11D5A, "X"), + (0x11D60, "V"), + (0x11D66, "X"), + (0x11D67, "V"), + (0x11D69, "X"), + (0x11D6A, "V"), + (0x11D8F, "X"), + (0x11D90, "V"), + (0x11D92, "X"), + (0x11D93, "V"), + (0x11D99, "X"), + (0x11DA0, "V"), + (0x11DAA, "X"), + (0x11DB0, "V"), + (0x11DDC, "X"), + (0x11DE0, "V"), + (0x11DEA, "X"), + (0x11EE0, "V"), + (0x11EF9, "X"), + (0x11F00, "V"), + (0x11F11, "X"), + (0x11F12, "V"), + (0x11F3B, "X"), + (0x11F3E, "V"), + (0x11F5B, "X"), + (0x11FB0, "V"), + (0x11FB1, "X"), + (0x11FC0, "V"), + (0x11FF2, "X"), + (0x11FFF, "V"), + (0x1239A, "X"), + (0x12400, "V"), + (0x1246F, "X"), + (0x12470, "V"), + (0x12475, "X"), + (0x12480, "V"), + (0x12544, "X"), + (0x12F90, "V"), + (0x12FF3, "X"), + (0x13000, "V"), + (0x13430, "X"), + (0x13440, "V"), + (0x13456, "X"), + (0x13460, "V"), + (0x143FB, "X"), + (0x14400, "V"), + (0x14647, "X"), + (0x16100, "V"), + (0x1613A, "X"), + (0x16800, "V"), + (0x16A39, "X"), + (0x16A40, "V"), + (0x16A5F, "X"), + (0x16A60, "V"), + (0x16A6A, "X"), + (0x16A6E, "V"), + (0x16ABF, "X"), + (0x16AC0, "V"), + (0x16ACA, "X"), + (0x16AD0, "V"), + (0x16AEE, "X"), + (0x16AF0, "V"), + (0x16AF6, "X"), + (0x16B00, "V"), + (0x16B46, "X"), + (0x16B50, "V"), + (0x16B5A, "X"), + (0x16B5B, "V"), + (0x16B62, "X"), + (0x16B63, "V"), + (0x16B78, "X"), + (0x16B7D, "V"), + (0x16B90, "X"), + (0x16D40, "V"), + (0x16D7A, "X"), + (0x16E40, "M", "𖹠"), + (0x16E41, "M", "𖹡"), + (0x16E42, "M", "𖹢"), + (0x16E43, "M", "𖹣"), + (0x16E44, "M", "𖹤"), + (0x16E45, "M", "𖹥"), + (0x16E46, "M", "𖹦"), + (0x16E47, "M", "𖹧"), + (0x16E48, "M", "𖹨"), + (0x16E49, "M", "𖹩"), + (0x16E4A, "M", "𖹪"), + (0x16E4B, "M", "𖹫"), + (0x16E4C, "M", "𖹬"), + (0x16E4D, "M", "𖹭"), + (0x16E4E, "M", "𖹮"), + (0x16E4F, "M", "𖹯"), + (0x16E50, "M", "𖹰"), + (0x16E51, "M", "𖹱"), + (0x16E52, "M", "𖹲"), + (0x16E53, "M", "𖹳"), + (0x16E54, "M", "𖹴"), + (0x16E55, "M", "𖹵"), + (0x16E56, "M", "𖹶"), + (0x16E57, "M", "𖹷"), + (0x16E58, "M", "𖹸"), + (0x16E59, "M", "𖹹"), + (0x16E5A, "M", "𖹺"), + (0x16E5B, "M", "𖹻"), + (0x16E5C, "M", "𖹼"), + (0x16E5D, "M", "𖹽"), + (0x16E5E, "M", "𖹾"), + (0x16E5F, "M", "𖹿"), + (0x16E60, "V"), + (0x16E9B, "X"), + (0x16EA0, "M", "𖺻"), + (0x16EA1, "M", "𖺼"), + (0x16EA2, "M", "𖺽"), + (0x16EA3, "M", "𖺾"), + (0x16EA4, "M", "𖺿"), + (0x16EA5, "M", "𖻀"), + (0x16EA6, "M", "𖻁"), + (0x16EA7, "M", "𖻂"), + (0x16EA8, "M", "𖻃"), + (0x16EA9, "M", "𖻄"), + (0x16EAA, "M", "𖻅"), + (0x16EAB, "M", "𖻆"), + (0x16EAC, "M", "𖻇"), + (0x16EAD, "M", "𖻈"), + (0x16EAE, "M", "𖻉"), + (0x16EAF, "M", "𖻊"), + (0x16EB0, "M", "𖻋"), + (0x16EB1, "M", "𖻌"), + (0x16EB2, "M", "𖻍"), + (0x16EB3, "M", "𖻎"), + (0x16EB4, "M", "𖻏"), + (0x16EB5, "M", "𖻐"), + (0x16EB6, "M", "𖻑"), + (0x16EB7, "M", "𖻒"), + (0x16EB8, "M", "𖻓"), + (0x16EB9, "X"), + (0x16EBB, "V"), + (0x16ED4, "X"), + (0x16F00, "V"), + (0x16F4B, "X"), + (0x16F4F, "V"), + (0x16F88, "X"), + (0x16F8F, "V"), + (0x16FA0, "X"), + (0x16FE0, "V"), + (0x16FE5, "X"), + (0x16FF0, "V"), + (0x16FF7, "X"), + (0x17000, "V"), + (0x18CD6, "X"), + (0x18CFF, "V"), + (0x18D1F, "X"), + (0x18D80, "V"), + (0x18DF3, "X"), + (0x1AFF0, "V"), + (0x1AFF4, "X"), + (0x1AFF5, "V"), + (0x1AFFC, "X"), + (0x1AFFD, "V"), + (0x1AFFF, "X"), + (0x1B000, "V"), + (0x1B123, "X"), + (0x1B132, "V"), + (0x1B133, "X"), + (0x1B150, "V"), + (0x1B153, "X"), + (0x1B155, "V"), + (0x1B156, "X"), + (0x1B164, "V"), + (0x1B168, "X"), + (0x1B170, "V"), + (0x1B2FC, "X"), + (0x1BC00, "V"), + (0x1BC6B, "X"), + (0x1BC70, "V"), + (0x1BC7D, "X"), + (0x1BC80, "V"), + (0x1BC89, "X"), + (0x1BC90, "V"), + (0x1BC9A, "X"), + (0x1BC9C, "V"), + (0x1BCA0, "I"), + (0x1BCA4, "X"), + (0x1CC00, "V"), + (0x1CCD6, "M", "a"), + (0x1CCD7, "M", "b"), + (0x1CCD8, "M", "c"), + (0x1CCD9, "M", "d"), + (0x1CCDA, "M", "e"), + (0x1CCDB, "M", "f"), + (0x1CCDC, "M", "g"), + (0x1CCDD, "M", "h"), + (0x1CCDE, "M", "i"), + (0x1CCDF, "M", "j"), + (0x1CCE0, "M", "k"), + (0x1CCE1, "M", "l"), + (0x1CCE2, "M", "m"), + (0x1CCE3, "M", "n"), + (0x1CCE4, "M", "o"), + (0x1CCE5, "M", "p"), + (0x1CCE6, "M", "q"), + (0x1CCE7, "M", "r"), + (0x1CCE8, "M", "s"), + (0x1CCE9, "M", "t"), + (0x1CCEA, "M", "u"), + (0x1CCEB, "M", "v"), + (0x1CCEC, "M", "w"), + (0x1CCED, "M", "x"), + (0x1CCEE, "M", "y"), + (0x1CCEF, "M", "z"), + (0x1CCF0, "M", "0"), + (0x1CCF1, "M", "1"), + (0x1CCF2, "M", "2"), + (0x1CCF3, "M", "3"), + (0x1CCF4, "M", "4"), + (0x1CCF5, "M", "5"), + (0x1CCF6, "M", "6"), + (0x1CCF7, "M", "7"), + (0x1CCF8, "M", "8"), + (0x1CCF9, "M", "9"), + (0x1CCFA, "V"), + (0x1CCFD, "X"), + (0x1CD00, "V"), + (0x1CEB4, "X"), + (0x1CEBA, "V"), + (0x1CED1, "X"), + (0x1CEE0, "V"), + (0x1CEF1, "X"), + (0x1CF00, "V"), + (0x1CF2E, "X"), + (0x1CF30, "V"), + (0x1CF47, "X"), + (0x1CF50, "V"), + (0x1CFC4, "X"), + (0x1D000, "V"), + (0x1D0F6, "X"), + (0x1D100, "V"), + (0x1D127, "X"), + (0x1D129, "V"), + (0x1D15E, "M", "𝅗𝅥"), + (0x1D15F, "M", "𝅘𝅥"), + (0x1D160, "M", "𝅘𝅥𝅮"), + (0x1D161, "M", "𝅘𝅥𝅯"), + (0x1D162, "M", "𝅘𝅥𝅰"), + (0x1D163, "M", "𝅘𝅥𝅱"), + (0x1D164, "M", "𝅘𝅥𝅲"), + (0x1D165, "V"), + (0x1D173, "I"), + (0x1D17B, "V"), + (0x1D1BB, "M", "𝆹𝅥"), + (0x1D1BC, "M", "𝆺𝅥"), + (0x1D1BD, "M", "𝆹𝅥𝅮"), + (0x1D1BE, "M", "𝆺𝅥𝅮"), + (0x1D1BF, "M", "𝆹𝅥𝅯"), + (0x1D1C0, "M", "𝆺𝅥𝅯"), + (0x1D1C1, "V"), + (0x1D1EB, "X"), + (0x1D200, "V"), + (0x1D246, "X"), + (0x1D2C0, "V"), + (0x1D2D4, "X"), + (0x1D2E0, "V"), + (0x1D2F4, "X"), + (0x1D300, "V"), + (0x1D357, "X"), + (0x1D360, "V"), + (0x1D379, "X"), + (0x1D400, "M", "a"), + (0x1D401, "M", "b"), + (0x1D402, "M", "c"), + (0x1D403, "M", "d"), + (0x1D404, "M", "e"), + (0x1D405, "M", "f"), + (0x1D406, "M", "g"), + (0x1D407, "M", "h"), + (0x1D408, "M", "i"), + (0x1D409, "M", "j"), + (0x1D40A, "M", "k"), + (0x1D40B, "M", "l"), + (0x1D40C, "M", "m"), + (0x1D40D, "M", "n"), + (0x1D40E, "M", "o"), + (0x1D40F, "M", "p"), + (0x1D410, "M", "q"), + (0x1D411, "M", "r"), + (0x1D412, "M", "s"), + (0x1D413, "M", "t"), + (0x1D414, "M", "u"), + (0x1D415, "M", "v"), + (0x1D416, "M", "w"), + (0x1D417, "M", "x"), + (0x1D418, "M", "y"), + (0x1D419, "M", "z"), + (0x1D41A, "M", "a"), + (0x1D41B, "M", "b"), + (0x1D41C, "M", "c"), + (0x1D41D, "M", "d"), + (0x1D41E, "M", "e"), + (0x1D41F, "M", "f"), + (0x1D420, "M", "g"), + (0x1D421, "M", "h"), + (0x1D422, "M", "i"), + (0x1D423, "M", "j"), + (0x1D424, "M", "k"), + (0x1D425, "M", "l"), + (0x1D426, "M", "m"), + (0x1D427, "M", "n"), + (0x1D428, "M", "o"), + (0x1D429, "M", "p"), + (0x1D42A, "M", "q"), + (0x1D42B, "M", "r"), + (0x1D42C, "M", "s"), + (0x1D42D, "M", "t"), + (0x1D42E, "M", "u"), + (0x1D42F, "M", "v"), + (0x1D430, "M", "w"), + (0x1D431, "M", "x"), + (0x1D432, "M", "y"), + (0x1D433, "M", "z"), + (0x1D434, "M", "a"), + (0x1D435, "M", "b"), + (0x1D436, "M", "c"), + (0x1D437, "M", "d"), + (0x1D438, "M", "e"), + (0x1D439, "M", "f"), + (0x1D43A, "M", "g"), + (0x1D43B, "M", "h"), + (0x1D43C, "M", "i"), + (0x1D43D, "M", "j"), + (0x1D43E, "M", "k"), + (0x1D43F, "M", "l"), + (0x1D440, "M", "m"), + (0x1D441, "M", "n"), + (0x1D442, "M", "o"), + (0x1D443, "M", "p"), + (0x1D444, "M", "q"), + (0x1D445, "M", "r"), + (0x1D446, "M", "s"), + (0x1D447, "M", "t"), + (0x1D448, "M", "u"), + (0x1D449, "M", "v"), + (0x1D44A, "M", "w"), + (0x1D44B, "M", "x"), + (0x1D44C, "M", "y"), + (0x1D44D, "M", "z"), + (0x1D44E, "M", "a"), + (0x1D44F, "M", "b"), + (0x1D450, "M", "c"), + (0x1D451, "M", "d"), + (0x1D452, "M", "e"), + (0x1D453, "M", "f"), + (0x1D454, "M", "g"), + (0x1D455, "X"), + (0x1D456, "M", "i"), + (0x1D457, "M", "j"), + (0x1D458, "M", "k"), + (0x1D459, "M", "l"), + (0x1D45A, "M", "m"), + (0x1D45B, "M", "n"), + (0x1D45C, "M", "o"), + (0x1D45D, "M", "p"), + (0x1D45E, "M", "q"), + (0x1D45F, "M", "r"), + (0x1D460, "M", "s"), + (0x1D461, "M", "t"), + (0x1D462, "M", "u"), + (0x1D463, "M", "v"), + (0x1D464, "M", "w"), + (0x1D465, "M", "x"), + (0x1D466, "M", "y"), + (0x1D467, "M", "z"), + (0x1D468, "M", "a"), + (0x1D469, "M", "b"), + (0x1D46A, "M", "c"), + (0x1D46B, "M", "d"), + (0x1D46C, "M", "e"), + (0x1D46D, "M", "f"), + (0x1D46E, "M", "g"), + (0x1D46F, "M", "h"), + (0x1D470, "M", "i"), + (0x1D471, "M", "j"), + (0x1D472, "M", "k"), + (0x1D473, "M", "l"), + (0x1D474, "M", "m"), + (0x1D475, "M", "n"), + (0x1D476, "M", "o"), + (0x1D477, "M", "p"), + (0x1D478, "M", "q"), + (0x1D479, "M", "r"), + (0x1D47A, "M", "s"), + (0x1D47B, "M", "t"), + (0x1D47C, "M", "u"), + (0x1D47D, "M", "v"), + (0x1D47E, "M", "w"), + (0x1D47F, "M", "x"), + (0x1D480, "M", "y"), + (0x1D481, "M", "z"), + (0x1D482, "M", "a"), + (0x1D483, "M", "b"), + (0x1D484, "M", "c"), + (0x1D485, "M", "d"), + (0x1D486, "M", "e"), + (0x1D487, "M", "f"), + (0x1D488, "M", "g"), + (0x1D489, "M", "h"), + (0x1D48A, "M", "i"), + (0x1D48B, "M", "j"), + (0x1D48C, "M", "k"), + (0x1D48D, "M", "l"), + (0x1D48E, "M", "m"), + (0x1D48F, "M", "n"), + (0x1D490, "M", "o"), + (0x1D491, "M", "p"), + (0x1D492, "M", "q"), + (0x1D493, "M", "r"), + (0x1D494, "M", "s"), + (0x1D495, "M", "t"), + (0x1D496, "M", "u"), + (0x1D497, "M", "v"), + (0x1D498, "M", "w"), + (0x1D499, "M", "x"), + (0x1D49A, "M", "y"), + (0x1D49B, "M", "z"), + (0x1D49C, "M", "a"), + (0x1D49D, "X"), + (0x1D49E, "M", "c"), + (0x1D49F, "M", "d"), + (0x1D4A0, "X"), + (0x1D4A2, "M", "g"), + (0x1D4A3, "X"), + (0x1D4A5, "M", "j"), + (0x1D4A6, "M", "k"), + (0x1D4A7, "X"), + (0x1D4A9, "M", "n"), + (0x1D4AA, "M", "o"), + (0x1D4AB, "M", "p"), + (0x1D4AC, "M", "q"), + (0x1D4AD, "X"), + (0x1D4AE, "M", "s"), + (0x1D4AF, "M", "t"), + (0x1D4B0, "M", "u"), + (0x1D4B1, "M", "v"), + (0x1D4B2, "M", "w"), + (0x1D4B3, "M", "x"), + (0x1D4B4, "M", "y"), + (0x1D4B5, "M", "z"), + (0x1D4B6, "M", "a"), + (0x1D4B7, "M", "b"), + (0x1D4B8, "M", "c"), + (0x1D4B9, "M", "d"), + (0x1D4BA, "X"), + (0x1D4BB, "M", "f"), + (0x1D4BC, "X"), + (0x1D4BD, "M", "h"), + (0x1D4BE, "M", "i"), + (0x1D4BF, "M", "j"), + (0x1D4C0, "M", "k"), + (0x1D4C1, "M", "l"), + (0x1D4C2, "M", "m"), + (0x1D4C3, "M", "n"), + (0x1D4C4, "X"), + (0x1D4C5, "M", "p"), + (0x1D4C6, "M", "q"), + (0x1D4C7, "M", "r"), + (0x1D4C8, "M", "s"), + (0x1D4C9, "M", "t"), + (0x1D4CA, "M", "u"), + (0x1D4CB, "M", "v"), + (0x1D4CC, "M", "w"), + (0x1D4CD, "M", "x"), + (0x1D4CE, "M", "y"), + (0x1D4CF, "M", "z"), + (0x1D4D0, "M", "a"), + (0x1D4D1, "M", "b"), + (0x1D4D2, "M", "c"), + (0x1D4D3, "M", "d"), + (0x1D4D4, "M", "e"), + (0x1D4D5, "M", "f"), + (0x1D4D6, "M", "g"), + (0x1D4D7, "M", "h"), + (0x1D4D8, "M", "i"), + (0x1D4D9, "M", "j"), + (0x1D4DA, "M", "k"), + (0x1D4DB, "M", "l"), + (0x1D4DC, "M", "m"), + (0x1D4DD, "M", "n"), + (0x1D4DE, "M", "o"), + (0x1D4DF, "M", "p"), + (0x1D4E0, "M", "q"), + (0x1D4E1, "M", "r"), + (0x1D4E2, "M", "s"), + (0x1D4E3, "M", "t"), + (0x1D4E4, "M", "u"), + (0x1D4E5, "M", "v"), + (0x1D4E6, "M", "w"), + (0x1D4E7, "M", "x"), + (0x1D4E8, "M", "y"), + (0x1D4E9, "M", "z"), + (0x1D4EA, "M", "a"), + (0x1D4EB, "M", "b"), + (0x1D4EC, "M", "c"), + (0x1D4ED, "M", "d"), + (0x1D4EE, "M", "e"), + (0x1D4EF, "M", "f"), + (0x1D4F0, "M", "g"), + (0x1D4F1, "M", "h"), + (0x1D4F2, "M", "i"), + (0x1D4F3, "M", "j"), + (0x1D4F4, "M", "k"), + (0x1D4F5, "M", "l"), + (0x1D4F6, "M", "m"), + (0x1D4F7, "M", "n"), + (0x1D4F8, "M", "o"), + (0x1D4F9, "M", "p"), + (0x1D4FA, "M", "q"), + (0x1D4FB, "M", "r"), + (0x1D4FC, "M", "s"), + (0x1D4FD, "M", "t"), + (0x1D4FE, "M", "u"), + (0x1D4FF, "M", "v"), + (0x1D500, "M", "w"), + (0x1D501, "M", "x"), + (0x1D502, "M", "y"), + (0x1D503, "M", "z"), + (0x1D504, "M", "a"), + (0x1D505, "M", "b"), + (0x1D506, "X"), + (0x1D507, "M", "d"), + (0x1D508, "M", "e"), + (0x1D509, "M", "f"), + (0x1D50A, "M", "g"), + (0x1D50B, "X"), + (0x1D50D, "M", "j"), + (0x1D50E, "M", "k"), + (0x1D50F, "M", "l"), + (0x1D510, "M", "m"), + (0x1D511, "M", "n"), + (0x1D512, "M", "o"), + (0x1D513, "M", "p"), + (0x1D514, "M", "q"), + (0x1D515, "X"), + (0x1D516, "M", "s"), + (0x1D517, "M", "t"), + (0x1D518, "M", "u"), + (0x1D519, "M", "v"), + (0x1D51A, "M", "w"), + (0x1D51B, "M", "x"), + (0x1D51C, "M", "y"), + (0x1D51D, "X"), + (0x1D51E, "M", "a"), + (0x1D51F, "M", "b"), + (0x1D520, "M", "c"), + (0x1D521, "M", "d"), + (0x1D522, "M", "e"), + (0x1D523, "M", "f"), + (0x1D524, "M", "g"), + (0x1D525, "M", "h"), + (0x1D526, "M", "i"), + (0x1D527, "M", "j"), + (0x1D528, "M", "k"), + (0x1D529, "M", "l"), + (0x1D52A, "M", "m"), + (0x1D52B, "M", "n"), + (0x1D52C, "M", "o"), + (0x1D52D, "M", "p"), + (0x1D52E, "M", "q"), + (0x1D52F, "M", "r"), + (0x1D530, "M", "s"), + (0x1D531, "M", "t"), + (0x1D532, "M", "u"), + (0x1D533, "M", "v"), + (0x1D534, "M", "w"), + (0x1D535, "M", "x"), + (0x1D536, "M", "y"), + (0x1D537, "M", "z"), + (0x1D538, "M", "a"), + (0x1D539, "M", "b"), + (0x1D53A, "X"), + (0x1D53B, "M", "d"), + (0x1D53C, "M", "e"), + (0x1D53D, "M", "f"), + (0x1D53E, "M", "g"), + (0x1D53F, "X"), + (0x1D540, "M", "i"), + (0x1D541, "M", "j"), + (0x1D542, "M", "k"), + (0x1D543, "M", "l"), + (0x1D544, "M", "m"), + (0x1D545, "X"), + (0x1D546, "M", "o"), + (0x1D547, "X"), + (0x1D54A, "M", "s"), + (0x1D54B, "M", "t"), + (0x1D54C, "M", "u"), + (0x1D54D, "M", "v"), + (0x1D54E, "M", "w"), + (0x1D54F, "M", "x"), + (0x1D550, "M", "y"), + (0x1D551, "X"), + (0x1D552, "M", "a"), + (0x1D553, "M", "b"), + (0x1D554, "M", "c"), + (0x1D555, "M", "d"), + (0x1D556, "M", "e"), + (0x1D557, "M", "f"), + (0x1D558, "M", "g"), + (0x1D559, "M", "h"), + (0x1D55A, "M", "i"), + (0x1D55B, "M", "j"), + (0x1D55C, "M", "k"), + (0x1D55D, "M", "l"), + (0x1D55E, "M", "m"), + (0x1D55F, "M", "n"), + (0x1D560, "M", "o"), + (0x1D561, "M", "p"), + (0x1D562, "M", "q"), + (0x1D563, "M", "r"), + (0x1D564, "M", "s"), + (0x1D565, "M", "t"), + (0x1D566, "M", "u"), + (0x1D567, "M", "v"), + (0x1D568, "M", "w"), + (0x1D569, "M", "x"), + (0x1D56A, "M", "y"), + (0x1D56B, "M", "z"), + (0x1D56C, "M", "a"), + (0x1D56D, "M", "b"), + (0x1D56E, "M", "c"), + (0x1D56F, "M", "d"), + (0x1D570, "M", "e"), + (0x1D571, "M", "f"), + (0x1D572, "M", "g"), + (0x1D573, "M", "h"), + (0x1D574, "M", "i"), + (0x1D575, "M", "j"), + (0x1D576, "M", "k"), + (0x1D577, "M", "l"), + (0x1D578, "M", "m"), + (0x1D579, "M", "n"), + (0x1D57A, "M", "o"), + (0x1D57B, "M", "p"), + (0x1D57C, "M", "q"), + (0x1D57D, "M", "r"), + (0x1D57E, "M", "s"), + (0x1D57F, "M", "t"), + (0x1D580, "M", "u"), + (0x1D581, "M", "v"), + (0x1D582, "M", "w"), + (0x1D583, "M", "x"), + (0x1D584, "M", "y"), + (0x1D585, "M", "z"), + (0x1D586, "M", "a"), + (0x1D587, "M", "b"), + (0x1D588, "M", "c"), + (0x1D589, "M", "d"), + (0x1D58A, "M", "e"), + (0x1D58B, "M", "f"), + (0x1D58C, "M", "g"), + (0x1D58D, "M", "h"), + (0x1D58E, "M", "i"), + (0x1D58F, "M", "j"), + (0x1D590, "M", "k"), + (0x1D591, "M", "l"), + (0x1D592, "M", "m"), + (0x1D593, "M", "n"), + (0x1D594, "M", "o"), + (0x1D595, "M", "p"), + (0x1D596, "M", "q"), + (0x1D597, "M", "r"), + (0x1D598, "M", "s"), + (0x1D599, "M", "t"), + (0x1D59A, "M", "u"), + (0x1D59B, "M", "v"), + (0x1D59C, "M", "w"), + (0x1D59D, "M", "x"), + (0x1D59E, "M", "y"), + (0x1D59F, "M", "z"), + (0x1D5A0, "M", "a"), + (0x1D5A1, "M", "b"), + (0x1D5A2, "M", "c"), + (0x1D5A3, "M", "d"), + (0x1D5A4, "M", "e"), + (0x1D5A5, "M", "f"), + (0x1D5A6, "M", "g"), + (0x1D5A7, "M", "h"), + (0x1D5A8, "M", "i"), + (0x1D5A9, "M", "j"), + (0x1D5AA, "M", "k"), + (0x1D5AB, "M", "l"), + (0x1D5AC, "M", "m"), + (0x1D5AD, "M", "n"), + (0x1D5AE, "M", "o"), + (0x1D5AF, "M", "p"), + (0x1D5B0, "M", "q"), + (0x1D5B1, "M", "r"), + (0x1D5B2, "M", "s"), + (0x1D5B3, "M", "t"), + (0x1D5B4, "M", "u"), + (0x1D5B5, "M", "v"), + (0x1D5B6, "M", "w"), + (0x1D5B7, "M", "x"), + (0x1D5B8, "M", "y"), + (0x1D5B9, "M", "z"), + (0x1D5BA, "M", "a"), + (0x1D5BB, "M", "b"), + (0x1D5BC, "M", "c"), + (0x1D5BD, "M", "d"), + (0x1D5BE, "M", "e"), + (0x1D5BF, "M", "f"), + (0x1D5C0, "M", "g"), + (0x1D5C1, "M", "h"), + (0x1D5C2, "M", "i"), + (0x1D5C3, "M", "j"), + (0x1D5C4, "M", "k"), + (0x1D5C5, "M", "l"), + (0x1D5C6, "M", "m"), + (0x1D5C7, "M", "n"), + (0x1D5C8, "M", "o"), + (0x1D5C9, "M", "p"), + (0x1D5CA, "M", "q"), + (0x1D5CB, "M", "r"), + (0x1D5CC, "M", "s"), + (0x1D5CD, "M", "t"), + (0x1D5CE, "M", "u"), + (0x1D5CF, "M", "v"), + (0x1D5D0, "M", "w"), + (0x1D5D1, "M", "x"), + (0x1D5D2, "M", "y"), + (0x1D5D3, "M", "z"), + (0x1D5D4, "M", "a"), + (0x1D5D5, "M", "b"), + (0x1D5D6, "M", "c"), + (0x1D5D7, "M", "d"), + (0x1D5D8, "M", "e"), + (0x1D5D9, "M", "f"), + (0x1D5DA, "M", "g"), + (0x1D5DB, "M", "h"), + (0x1D5DC, "M", "i"), + (0x1D5DD, "M", "j"), + (0x1D5DE, "M", "k"), + (0x1D5DF, "M", "l"), + (0x1D5E0, "M", "m"), + (0x1D5E1, "M", "n"), + (0x1D5E2, "M", "o"), + (0x1D5E3, "M", "p"), + (0x1D5E4, "M", "q"), + (0x1D5E5, "M", "r"), + (0x1D5E6, "M", "s"), + (0x1D5E7, "M", "t"), + (0x1D5E8, "M", "u"), + (0x1D5E9, "M", "v"), + (0x1D5EA, "M", "w"), + (0x1D5EB, "M", "x"), + (0x1D5EC, "M", "y"), + (0x1D5ED, "M", "z"), + (0x1D5EE, "M", "a"), + (0x1D5EF, "M", "b"), + (0x1D5F0, "M", "c"), + (0x1D5F1, "M", "d"), + (0x1D5F2, "M", "e"), + (0x1D5F3, "M", "f"), + (0x1D5F4, "M", "g"), + (0x1D5F5, "M", "h"), + (0x1D5F6, "M", "i"), + (0x1D5F7, "M", "j"), + (0x1D5F8, "M", "k"), + (0x1D5F9, "M", "l"), + (0x1D5FA, "M", "m"), + (0x1D5FB, "M", "n"), + (0x1D5FC, "M", "o"), + (0x1D5FD, "M", "p"), + (0x1D5FE, "M", "q"), + (0x1D5FF, "M", "r"), + (0x1D600, "M", "s"), + (0x1D601, "M", "t"), + (0x1D602, "M", "u"), + (0x1D603, "M", "v"), + (0x1D604, "M", "w"), + (0x1D605, "M", "x"), + (0x1D606, "M", "y"), + (0x1D607, "M", "z"), + (0x1D608, "M", "a"), + (0x1D609, "M", "b"), + (0x1D60A, "M", "c"), + (0x1D60B, "M", "d"), + (0x1D60C, "M", "e"), + (0x1D60D, "M", "f"), + (0x1D60E, "M", "g"), + (0x1D60F, "M", "h"), + (0x1D610, "M", "i"), + (0x1D611, "M", "j"), + (0x1D612, "M", "k"), + (0x1D613, "M", "l"), + (0x1D614, "M", "m"), + (0x1D615, "M", "n"), + (0x1D616, "M", "o"), + (0x1D617, "M", "p"), + (0x1D618, "M", "q"), + (0x1D619, "M", "r"), + (0x1D61A, "M", "s"), + (0x1D61B, "M", "t"), + (0x1D61C, "M", "u"), + (0x1D61D, "M", "v"), + (0x1D61E, "M", "w"), + (0x1D61F, "M", "x"), + (0x1D620, "M", "y"), + (0x1D621, "M", "z"), + (0x1D622, "M", "a"), + (0x1D623, "M", "b"), + (0x1D624, "M", "c"), + (0x1D625, "M", "d"), + (0x1D626, "M", "e"), + (0x1D627, "M", "f"), + (0x1D628, "M", "g"), + (0x1D629, "M", "h"), + (0x1D62A, "M", "i"), + (0x1D62B, "M", "j"), + (0x1D62C, "M", "k"), + (0x1D62D, "M", "l"), + (0x1D62E, "M", "m"), + (0x1D62F, "M", "n"), + (0x1D630, "M", "o"), + (0x1D631, "M", "p"), + (0x1D632, "M", "q"), + (0x1D633, "M", "r"), + (0x1D634, "M", "s"), + (0x1D635, "M", "t"), + (0x1D636, "M", "u"), + (0x1D637, "M", "v"), + (0x1D638, "M", "w"), + (0x1D639, "M", "x"), + (0x1D63A, "M", "y"), + (0x1D63B, "M", "z"), + (0x1D63C, "M", "a"), + (0x1D63D, "M", "b"), + (0x1D63E, "M", "c"), + (0x1D63F, "M", "d"), + (0x1D640, "M", "e"), + (0x1D641, "M", "f"), + (0x1D642, "M", "g"), + (0x1D643, "M", "h"), + (0x1D644, "M", "i"), + (0x1D645, "M", "j"), + (0x1D646, "M", "k"), + (0x1D647, "M", "l"), + (0x1D648, "M", "m"), + (0x1D649, "M", "n"), + (0x1D64A, "M", "o"), + (0x1D64B, "M", "p"), + (0x1D64C, "M", "q"), + (0x1D64D, "M", "r"), + (0x1D64E, "M", "s"), + (0x1D64F, "M", "t"), + (0x1D650, "M", "u"), + (0x1D651, "M", "v"), + (0x1D652, "M", "w"), + (0x1D653, "M", "x"), + (0x1D654, "M", "y"), + (0x1D655, "M", "z"), + (0x1D656, "M", "a"), + (0x1D657, "M", "b"), + (0x1D658, "M", "c"), + (0x1D659, "M", "d"), + (0x1D65A, "M", "e"), + (0x1D65B, "M", "f"), + (0x1D65C, "M", "g"), + (0x1D65D, "M", "h"), + (0x1D65E, "M", "i"), + (0x1D65F, "M", "j"), + (0x1D660, "M", "k"), + (0x1D661, "M", "l"), + (0x1D662, "M", "m"), + (0x1D663, "M", "n"), + (0x1D664, "M", "o"), + (0x1D665, "M", "p"), + (0x1D666, "M", "q"), + (0x1D667, "M", "r"), + (0x1D668, "M", "s"), + (0x1D669, "M", "t"), + (0x1D66A, "M", "u"), + (0x1D66B, "M", "v"), + (0x1D66C, "M", "w"), + (0x1D66D, "M", "x"), + (0x1D66E, "M", "y"), + (0x1D66F, "M", "z"), + (0x1D670, "M", "a"), + (0x1D671, "M", "b"), + (0x1D672, "M", "c"), + (0x1D673, "M", "d"), + (0x1D674, "M", "e"), + (0x1D675, "M", "f"), + (0x1D676, "M", "g"), + (0x1D677, "M", "h"), + (0x1D678, "M", "i"), + (0x1D679, "M", "j"), + (0x1D67A, "M", "k"), + (0x1D67B, "M", "l"), + (0x1D67C, "M", "m"), + (0x1D67D, "M", "n"), + (0x1D67E, "M", "o"), + (0x1D67F, "M", "p"), + (0x1D680, "M", "q"), + (0x1D681, "M", "r"), + (0x1D682, "M", "s"), + (0x1D683, "M", "t"), + (0x1D684, "M", "u"), + (0x1D685, "M", "v"), + (0x1D686, "M", "w"), + (0x1D687, "M", "x"), + (0x1D688, "M", "y"), + (0x1D689, "M", "z"), + (0x1D68A, "M", "a"), + (0x1D68B, "M", "b"), + (0x1D68C, "M", "c"), + (0x1D68D, "M", "d"), + (0x1D68E, "M", "e"), + (0x1D68F, "M", "f"), + (0x1D690, "M", "g"), + (0x1D691, "M", "h"), + (0x1D692, "M", "i"), + (0x1D693, "M", "j"), + (0x1D694, "M", "k"), + (0x1D695, "M", "l"), + (0x1D696, "M", "m"), + (0x1D697, "M", "n"), + (0x1D698, "M", "o"), + (0x1D699, "M", "p"), + (0x1D69A, "M", "q"), + (0x1D69B, "M", "r"), + (0x1D69C, "M", "s"), + (0x1D69D, "M", "t"), + (0x1D69E, "M", "u"), + (0x1D69F, "M", "v"), + (0x1D6A0, "M", "w"), + (0x1D6A1, "M", "x"), + (0x1D6A2, "M", "y"), + (0x1D6A3, "M", "z"), + (0x1D6A4, "M", "ı"), + (0x1D6A5, "M", "ȷ"), + (0x1D6A6, "X"), + (0x1D6A8, "M", "α"), + (0x1D6A9, "M", "β"), + (0x1D6AA, "M", "γ"), + (0x1D6AB, "M", "δ"), + (0x1D6AC, "M", "ε"), + (0x1D6AD, "M", "ζ"), + (0x1D6AE, "M", "η"), + (0x1D6AF, "M", "θ"), + (0x1D6B0, "M", "ι"), + (0x1D6B1, "M", "κ"), + (0x1D6B2, "M", "λ"), + (0x1D6B3, "M", "μ"), + (0x1D6B4, "M", "ν"), + (0x1D6B5, "M", "ξ"), + (0x1D6B6, "M", "ο"), + (0x1D6B7, "M", "π"), + (0x1D6B8, "M", "ρ"), + (0x1D6B9, "M", "θ"), + (0x1D6BA, "M", "σ"), + (0x1D6BB, "M", "τ"), + (0x1D6BC, "M", "υ"), + (0x1D6BD, "M", "φ"), + (0x1D6BE, "M", "χ"), + (0x1D6BF, "M", "ψ"), + (0x1D6C0, "M", "ω"), + (0x1D6C1, "M", "∇"), + (0x1D6C2, "M", "α"), + (0x1D6C3, "M", "β"), + (0x1D6C4, "M", "γ"), + (0x1D6C5, "M", "δ"), + (0x1D6C6, "M", "ε"), + (0x1D6C7, "M", "ζ"), + (0x1D6C8, "M", "η"), + (0x1D6C9, "M", "θ"), + (0x1D6CA, "M", "ι"), + (0x1D6CB, "M", "κ"), + (0x1D6CC, "M", "λ"), + (0x1D6CD, "M", "μ"), + (0x1D6CE, "M", "ν"), + (0x1D6CF, "M", "ξ"), + (0x1D6D0, "M", "ο"), + (0x1D6D1, "M", "π"), + (0x1D6D2, "M", "ρ"), + (0x1D6D3, "M", "σ"), + (0x1D6D5, "M", "τ"), + (0x1D6D6, "M", "υ"), + (0x1D6D7, "M", "φ"), + (0x1D6D8, "M", "χ"), + (0x1D6D9, "M", "ψ"), + (0x1D6DA, "M", "ω"), + (0x1D6DB, "M", "∂"), + (0x1D6DC, "M", "ε"), + (0x1D6DD, "M", "θ"), + (0x1D6DE, "M", "κ"), + (0x1D6DF, "M", "φ"), + (0x1D6E0, "M", "ρ"), + (0x1D6E1, "M", "π"), + (0x1D6E2, "M", "α"), + (0x1D6E3, "M", "β"), + (0x1D6E4, "M", "γ"), + (0x1D6E5, "M", "δ"), + (0x1D6E6, "M", "ε"), + (0x1D6E7, "M", "ζ"), + (0x1D6E8, "M", "η"), + (0x1D6E9, "M", "θ"), + (0x1D6EA, "M", "ι"), + (0x1D6EB, "M", "κ"), + (0x1D6EC, "M", "λ"), + (0x1D6ED, "M", "μ"), + (0x1D6EE, "M", "ν"), + (0x1D6EF, "M", "ξ"), + (0x1D6F0, "M", "ο"), + (0x1D6F1, "M", "π"), + (0x1D6F2, "M", "ρ"), + (0x1D6F3, "M", "θ"), + (0x1D6F4, "M", "σ"), + (0x1D6F5, "M", "τ"), + (0x1D6F6, "M", "υ"), + (0x1D6F7, "M", "φ"), + (0x1D6F8, "M", "χ"), + (0x1D6F9, "M", "ψ"), + (0x1D6FA, "M", "ω"), + (0x1D6FB, "M", "∇"), + (0x1D6FC, "M", "α"), + (0x1D6FD, "M", "β"), + (0x1D6FE, "M", "γ"), + (0x1D6FF, "M", "δ"), + (0x1D700, "M", "ε"), + (0x1D701, "M", "ζ"), + (0x1D702, "M", "η"), + (0x1D703, "M", "θ"), + (0x1D704, "M", "ι"), + (0x1D705, "M", "κ"), + (0x1D706, "M", "λ"), + (0x1D707, "M", "μ"), + (0x1D708, "M", "ν"), + (0x1D709, "M", "ξ"), + (0x1D70A, "M", "ο"), + (0x1D70B, "M", "π"), + (0x1D70C, "M", "ρ"), + (0x1D70D, "M", "σ"), + (0x1D70F, "M", "τ"), + (0x1D710, "M", "υ"), + (0x1D711, "M", "φ"), + (0x1D712, "M", "χ"), + (0x1D713, "M", "ψ"), + (0x1D714, "M", "ω"), + (0x1D715, "M", "∂"), + (0x1D716, "M", "ε"), + (0x1D717, "M", "θ"), + (0x1D718, "M", "κ"), + (0x1D719, "M", "φ"), + (0x1D71A, "M", "ρ"), + (0x1D71B, "M", "π"), + (0x1D71C, "M", "α"), + (0x1D71D, "M", "β"), + (0x1D71E, "M", "γ"), + (0x1D71F, "M", "δ"), + (0x1D720, "M", "ε"), + (0x1D721, "M", "ζ"), + (0x1D722, "M", "η"), + (0x1D723, "M", "θ"), + (0x1D724, "M", "ι"), + (0x1D725, "M", "κ"), + (0x1D726, "M", "λ"), + (0x1D727, "M", "μ"), + (0x1D728, "M", "ν"), + (0x1D729, "M", "ξ"), + (0x1D72A, "M", "ο"), + (0x1D72B, "M", "π"), + (0x1D72C, "M", "ρ"), + (0x1D72D, "M", "θ"), + (0x1D72E, "M", "σ"), + (0x1D72F, "M", "τ"), + (0x1D730, "M", "υ"), + (0x1D731, "M", "φ"), + (0x1D732, "M", "χ"), + (0x1D733, "M", "ψ"), + (0x1D734, "M", "ω"), + (0x1D735, "M", "∇"), + (0x1D736, "M", "α"), + (0x1D737, "M", "β"), + (0x1D738, "M", "γ"), + (0x1D739, "M", "δ"), + (0x1D73A, "M", "ε"), + (0x1D73B, "M", "ζ"), + (0x1D73C, "M", "η"), + (0x1D73D, "M", "θ"), + (0x1D73E, "M", "ι"), + (0x1D73F, "M", "κ"), + (0x1D740, "M", "λ"), + (0x1D741, "M", "μ"), + (0x1D742, "M", "ν"), + (0x1D743, "M", "ξ"), + (0x1D744, "M", "ο"), + (0x1D745, "M", "π"), + (0x1D746, "M", "ρ"), + (0x1D747, "M", "σ"), + (0x1D749, "M", "τ"), + (0x1D74A, "M", "υ"), + (0x1D74B, "M", "φ"), + (0x1D74C, "M", "χ"), + (0x1D74D, "M", "ψ"), + (0x1D74E, "M", "ω"), + (0x1D74F, "M", "∂"), + (0x1D750, "M", "ε"), + (0x1D751, "M", "θ"), + (0x1D752, "M", "κ"), + (0x1D753, "M", "φ"), + (0x1D754, "M", "ρ"), + (0x1D755, "M", "π"), + (0x1D756, "M", "α"), + (0x1D757, "M", "β"), + (0x1D758, "M", "γ"), + (0x1D759, "M", "δ"), + (0x1D75A, "M", "ε"), + (0x1D75B, "M", "ζ"), + (0x1D75C, "M", "η"), + (0x1D75D, "M", "θ"), + (0x1D75E, "M", "ι"), + (0x1D75F, "M", "κ"), + (0x1D760, "M", "λ"), + (0x1D761, "M", "μ"), + (0x1D762, "M", "ν"), + (0x1D763, "M", "ξ"), + (0x1D764, "M", "ο"), + (0x1D765, "M", "π"), + (0x1D766, "M", "ρ"), + (0x1D767, "M", "θ"), + (0x1D768, "M", "σ"), + (0x1D769, "M", "τ"), + (0x1D76A, "M", "υ"), + (0x1D76B, "M", "φ"), + (0x1D76C, "M", "χ"), + (0x1D76D, "M", "ψ"), + (0x1D76E, "M", "ω"), + (0x1D76F, "M", "∇"), + (0x1D770, "M", "α"), + (0x1D771, "M", "β"), + (0x1D772, "M", "γ"), + (0x1D773, "M", "δ"), + (0x1D774, "M", "ε"), + (0x1D775, "M", "ζ"), + (0x1D776, "M", "η"), + (0x1D777, "M", "θ"), + (0x1D778, "M", "ι"), + (0x1D779, "M", "κ"), + (0x1D77A, "M", "λ"), + (0x1D77B, "M", "μ"), + (0x1D77C, "M", "ν"), + (0x1D77D, "M", "ξ"), + (0x1D77E, "M", "ο"), + (0x1D77F, "M", "π"), + (0x1D780, "M", "ρ"), + (0x1D781, "M", "σ"), + (0x1D783, "M", "τ"), + (0x1D784, "M", "υ"), + (0x1D785, "M", "φ"), + (0x1D786, "M", "χ"), + (0x1D787, "M", "ψ"), + (0x1D788, "M", "ω"), + (0x1D789, "M", "∂"), + (0x1D78A, "M", "ε"), + (0x1D78B, "M", "θ"), + (0x1D78C, "M", "κ"), + (0x1D78D, "M", "φ"), + (0x1D78E, "M", "ρ"), + (0x1D78F, "M", "π"), + (0x1D790, "M", "α"), + (0x1D791, "M", "β"), + (0x1D792, "M", "γ"), + (0x1D793, "M", "δ"), + (0x1D794, "M", "ε"), + (0x1D795, "M", "ζ"), + (0x1D796, "M", "η"), + (0x1D797, "M", "θ"), + (0x1D798, "M", "ι"), + (0x1D799, "M", "κ"), + (0x1D79A, "M", "λ"), + (0x1D79B, "M", "μ"), + (0x1D79C, "M", "ν"), + (0x1D79D, "M", "ξ"), + (0x1D79E, "M", "ο"), + (0x1D79F, "M", "π"), + (0x1D7A0, "M", "ρ"), + (0x1D7A1, "M", "θ"), + (0x1D7A2, "M", "σ"), + (0x1D7A3, "M", "τ"), + (0x1D7A4, "M", "υ"), + (0x1D7A5, "M", "φ"), + (0x1D7A6, "M", "χ"), + (0x1D7A7, "M", "ψ"), + (0x1D7A8, "M", "ω"), + (0x1D7A9, "M", "∇"), + (0x1D7AA, "M", "α"), + (0x1D7AB, "M", "β"), + (0x1D7AC, "M", "γ"), + (0x1D7AD, "M", "δ"), + (0x1D7AE, "M", "ε"), + (0x1D7AF, "M", "ζ"), + (0x1D7B0, "M", "η"), + (0x1D7B1, "M", "θ"), + (0x1D7B2, "M", "ι"), + (0x1D7B3, "M", "κ"), + (0x1D7B4, "M", "λ"), + (0x1D7B5, "M", "μ"), + (0x1D7B6, "M", "ν"), + (0x1D7B7, "M", "ξ"), + (0x1D7B8, "M", "ο"), + (0x1D7B9, "M", "π"), + (0x1D7BA, "M", "ρ"), + (0x1D7BB, "M", "σ"), + (0x1D7BD, "M", "τ"), + (0x1D7BE, "M", "υ"), + (0x1D7BF, "M", "φ"), + (0x1D7C0, "M", "χ"), + (0x1D7C1, "M", "ψ"), + (0x1D7C2, "M", "ω"), + (0x1D7C3, "M", "∂"), + (0x1D7C4, "M", "ε"), + (0x1D7C5, "M", "θ"), + (0x1D7C6, "M", "κ"), + (0x1D7C7, "M", "φ"), + (0x1D7C8, "M", "ρ"), + (0x1D7C9, "M", "π"), + (0x1D7CA, "M", "ϝ"), + (0x1D7CC, "X"), + (0x1D7CE, "M", "0"), + (0x1D7CF, "M", "1"), + (0x1D7D0, "M", "2"), + (0x1D7D1, "M", "3"), + (0x1D7D2, "M", "4"), + (0x1D7D3, "M", "5"), + (0x1D7D4, "M", "6"), + (0x1D7D5, "M", "7"), + (0x1D7D6, "M", "8"), + (0x1D7D7, "M", "9"), + (0x1D7D8, "M", "0"), + (0x1D7D9, "M", "1"), + (0x1D7DA, "M", "2"), + (0x1D7DB, "M", "3"), + (0x1D7DC, "M", "4"), + (0x1D7DD, "M", "5"), + (0x1D7DE, "M", "6"), + (0x1D7DF, "M", "7"), + (0x1D7E0, "M", "8"), + (0x1D7E1, "M", "9"), + (0x1D7E2, "M", "0"), + (0x1D7E3, "M", "1"), + (0x1D7E4, "M", "2"), + (0x1D7E5, "M", "3"), + (0x1D7E6, "M", "4"), + (0x1D7E7, "M", "5"), + (0x1D7E8, "M", "6"), + (0x1D7E9, "M", "7"), + (0x1D7EA, "M", "8"), + (0x1D7EB, "M", "9"), + (0x1D7EC, "M", "0"), + (0x1D7ED, "M", "1"), + (0x1D7EE, "M", "2"), + (0x1D7EF, "M", "3"), + (0x1D7F0, "M", "4"), + (0x1D7F1, "M", "5"), + (0x1D7F2, "M", "6"), + (0x1D7F3, "M", "7"), + (0x1D7F4, "M", "8"), + (0x1D7F5, "M", "9"), + (0x1D7F6, "M", "0"), + (0x1D7F7, "M", "1"), + (0x1D7F8, "M", "2"), + (0x1D7F9, "M", "3"), + (0x1D7FA, "M", "4"), + (0x1D7FB, "M", "5"), + (0x1D7FC, "M", "6"), + (0x1D7FD, "M", "7"), + (0x1D7FE, "M", "8"), + (0x1D7FF, "M", "9"), + (0x1D800, "V"), + (0x1DA8C, "X"), + (0x1DA9B, "V"), + (0x1DAA0, "X"), + (0x1DAA1, "V"), + (0x1DAB0, "X"), + (0x1DF00, "V"), + (0x1DF1F, "X"), + (0x1DF25, "V"), + (0x1DF2B, "X"), + (0x1E000, "V"), + (0x1E007, "X"), + (0x1E008, "V"), + (0x1E019, "X"), + (0x1E01B, "V"), + (0x1E022, "X"), + (0x1E023, "V"), + (0x1E025, "X"), + (0x1E026, "V"), + (0x1E02B, "X"), + (0x1E030, "M", "а"), + (0x1E031, "M", "б"), + (0x1E032, "M", "в"), + (0x1E033, "M", "г"), + (0x1E034, "M", "д"), + (0x1E035, "M", "е"), + (0x1E036, "M", "ж"), + (0x1E037, "M", "з"), + (0x1E038, "M", "и"), + (0x1E039, "M", "к"), + (0x1E03A, "M", "л"), + (0x1E03B, "M", "м"), + (0x1E03C, "M", "о"), + (0x1E03D, "M", "п"), + (0x1E03E, "M", "р"), + (0x1E03F, "M", "с"), + (0x1E040, "M", "т"), + (0x1E041, "M", "у"), + (0x1E042, "M", "ф"), + (0x1E043, "M", "х"), + (0x1E044, "M", "ц"), + (0x1E045, "M", "ч"), + (0x1E046, "M", "ш"), + (0x1E047, "M", "ы"), + (0x1E048, "M", "э"), + (0x1E049, "M", "ю"), + (0x1E04A, "M", "ꚉ"), + (0x1E04B, "M", "ә"), + (0x1E04C, "M", "і"), + (0x1E04D, "M", "ј"), + (0x1E04E, "M", "ө"), + (0x1E04F, "M", "ү"), + (0x1E050, "M", "ӏ"), + (0x1E051, "M", "а"), + (0x1E052, "M", "б"), + (0x1E053, "M", "в"), + (0x1E054, "M", "г"), + (0x1E055, "M", "д"), + (0x1E056, "M", "е"), + (0x1E057, "M", "ж"), + (0x1E058, "M", "з"), + (0x1E059, "M", "и"), + (0x1E05A, "M", "к"), + (0x1E05B, "M", "л"), + (0x1E05C, "M", "о"), + (0x1E05D, "M", "п"), + (0x1E05E, "M", "с"), + (0x1E05F, "M", "у"), + (0x1E060, "M", "ф"), + (0x1E061, "M", "х"), + (0x1E062, "M", "ц"), + (0x1E063, "M", "ч"), + (0x1E064, "M", "ш"), + (0x1E065, "M", "ъ"), + (0x1E066, "M", "ы"), + (0x1E067, "M", "ґ"), + (0x1E068, "M", "і"), + (0x1E069, "M", "ѕ"), + (0x1E06A, "M", "џ"), + (0x1E06B, "M", "ҫ"), + (0x1E06C, "M", "ꙑ"), + (0x1E06D, "M", "ұ"), + (0x1E06E, "X"), + (0x1E08F, "V"), + (0x1E090, "X"), + (0x1E100, "V"), + (0x1E12D, "X"), + (0x1E130, "V"), + (0x1E13E, "X"), + (0x1E140, "V"), + (0x1E14A, "X"), + (0x1E14E, "V"), + (0x1E150, "X"), + (0x1E290, "V"), + (0x1E2AF, "X"), + (0x1E2C0, "V"), + (0x1E2FA, "X"), + (0x1E2FF, "V"), + (0x1E300, "X"), + (0x1E4D0, "V"), + (0x1E4FA, "X"), + (0x1E5D0, "V"), + (0x1E5FB, "X"), + (0x1E5FF, "V"), + (0x1E600, "X"), + (0x1E6C0, "V"), + (0x1E6DF, "X"), + (0x1E6E0, "V"), + (0x1E6F6, "X"), + (0x1E6FE, "V"), + (0x1E700, "X"), + (0x1E7E0, "V"), + (0x1E7E7, "X"), + (0x1E7E8, "V"), + (0x1E7EC, "X"), + (0x1E7ED, "V"), + (0x1E7EF, "X"), + (0x1E7F0, "V"), + (0x1E7FF, "X"), + (0x1E800, "V"), + (0x1E8C5, "X"), + (0x1E8C7, "V"), + (0x1E8D7, "X"), + (0x1E900, "M", "𞤢"), + (0x1E901, "M", "𞤣"), + (0x1E902, "M", "𞤤"), + (0x1E903, "M", "𞤥"), + (0x1E904, "M", "𞤦"), + (0x1E905, "M", "𞤧"), + (0x1E906, "M", "𞤨"), + (0x1E907, "M", "𞤩"), + (0x1E908, "M", "𞤪"), + (0x1E909, "M", "𞤫"), + (0x1E90A, "M", "𞤬"), + (0x1E90B, "M", "𞤭"), + (0x1E90C, "M", "𞤮"), + (0x1E90D, "M", "𞤯"), + (0x1E90E, "M", "𞤰"), + (0x1E90F, "M", "𞤱"), + (0x1E910, "M", "𞤲"), + (0x1E911, "M", "𞤳"), + (0x1E912, "M", "𞤴"), + (0x1E913, "M", "𞤵"), + (0x1E914, "M", "𞤶"), + (0x1E915, "M", "𞤷"), + (0x1E916, "M", "𞤸"), + (0x1E917, "M", "𞤹"), + (0x1E918, "M", "𞤺"), + (0x1E919, "M", "𞤻"), + (0x1E91A, "M", "𞤼"), + (0x1E91B, "M", "𞤽"), + (0x1E91C, "M", "𞤾"), + (0x1E91D, "M", "𞤿"), + (0x1E91E, "M", "𞥀"), + (0x1E91F, "M", "𞥁"), + (0x1E920, "M", "𞥂"), + (0x1E921, "M", "𞥃"), + (0x1E922, "V"), + (0x1E94C, "X"), + (0x1E950, "V"), + (0x1E95A, "X"), + (0x1E95E, "V"), + (0x1E960, "X"), + (0x1EC71, "V"), + (0x1ECB5, "X"), + (0x1ED01, "V"), + (0x1ED3E, "X"), + (0x1EE00, "M", "ا"), + (0x1EE01, "M", "ب"), + (0x1EE02, "M", "ج"), + (0x1EE03, "M", "د"), + (0x1EE04, "X"), + (0x1EE05, "M", "و"), + (0x1EE06, "M", "ز"), + (0x1EE07, "M", "ح"), + (0x1EE08, "M", "ط"), + (0x1EE09, "M", "ي"), + (0x1EE0A, "M", "ك"), + (0x1EE0B, "M", "ل"), + (0x1EE0C, "M", "م"), + (0x1EE0D, "M", "ن"), + (0x1EE0E, "M", "س"), + (0x1EE0F, "M", "ع"), + (0x1EE10, "M", "ف"), + (0x1EE11, "M", "ص"), + (0x1EE12, "M", "ق"), + (0x1EE13, "M", "ر"), + (0x1EE14, "M", "ش"), + (0x1EE15, "M", "ت"), + (0x1EE16, "M", "ث"), + (0x1EE17, "M", "خ"), + (0x1EE18, "M", "ذ"), + (0x1EE19, "M", "ض"), + (0x1EE1A, "M", "ظ"), + (0x1EE1B, "M", "غ"), + (0x1EE1C, "M", "ٮ"), + (0x1EE1D, "M", "ں"), + (0x1EE1E, "M", "ڡ"), + (0x1EE1F, "M", "ٯ"), + (0x1EE20, "X"), + (0x1EE21, "M", "ب"), + (0x1EE22, "M", "ج"), + (0x1EE23, "X"), + (0x1EE24, "M", "ه"), + (0x1EE25, "X"), + (0x1EE27, "M", "ح"), + (0x1EE28, "X"), + (0x1EE29, "M", "ي"), + (0x1EE2A, "M", "ك"), + (0x1EE2B, "M", "ل"), + (0x1EE2C, "M", "م"), + (0x1EE2D, "M", "ن"), + (0x1EE2E, "M", "س"), + (0x1EE2F, "M", "ع"), + (0x1EE30, "M", "ف"), + (0x1EE31, "M", "ص"), + (0x1EE32, "M", "ق"), + (0x1EE33, "X"), + (0x1EE34, "M", "ش"), + (0x1EE35, "M", "ت"), + (0x1EE36, "M", "ث"), + (0x1EE37, "M", "خ"), + (0x1EE38, "X"), + (0x1EE39, "M", "ض"), + (0x1EE3A, "X"), + (0x1EE3B, "M", "غ"), + (0x1EE3C, "X"), + (0x1EE42, "M", "ج"), + (0x1EE43, "X"), + (0x1EE47, "M", "ح"), + (0x1EE48, "X"), + (0x1EE49, "M", "ي"), + (0x1EE4A, "X"), + (0x1EE4B, "M", "ل"), + (0x1EE4C, "X"), + (0x1EE4D, "M", "ن"), + (0x1EE4E, "M", "س"), + (0x1EE4F, "M", "ع"), + (0x1EE50, "X"), + (0x1EE51, "M", "ص"), + (0x1EE52, "M", "ق"), + (0x1EE53, "X"), + (0x1EE54, "M", "ش"), + (0x1EE55, "X"), + (0x1EE57, "M", "خ"), + (0x1EE58, "X"), + (0x1EE59, "M", "ض"), + (0x1EE5A, "X"), + (0x1EE5B, "M", "غ"), + (0x1EE5C, "X"), + (0x1EE5D, "M", "ں"), + (0x1EE5E, "X"), + (0x1EE5F, "M", "ٯ"), + (0x1EE60, "X"), + (0x1EE61, "M", "ب"), + (0x1EE62, "M", "ج"), + (0x1EE63, "X"), + (0x1EE64, "M", "ه"), + (0x1EE65, "X"), + (0x1EE67, "M", "ح"), + (0x1EE68, "M", "ط"), + (0x1EE69, "M", "ي"), + (0x1EE6A, "M", "ك"), + (0x1EE6B, "X"), + (0x1EE6C, "M", "م"), + (0x1EE6D, "M", "ن"), + (0x1EE6E, "M", "س"), + (0x1EE6F, "M", "ع"), + (0x1EE70, "M", "ف"), + (0x1EE71, "M", "ص"), + (0x1EE72, "M", "ق"), + (0x1EE73, "X"), + (0x1EE74, "M", "ش"), + (0x1EE75, "M", "ت"), + (0x1EE76, "M", "ث"), + (0x1EE77, "M", "خ"), + (0x1EE78, "X"), + (0x1EE79, "M", "ض"), + (0x1EE7A, "M", "ظ"), + (0x1EE7B, "M", "غ"), + (0x1EE7C, "M", "ٮ"), + (0x1EE7D, "X"), + (0x1EE7E, "M", "ڡ"), + (0x1EE7F, "X"), + (0x1EE80, "M", "ا"), + (0x1EE81, "M", "ب"), + (0x1EE82, "M", "ج"), + (0x1EE83, "M", "د"), + (0x1EE84, "M", "ه"), + (0x1EE85, "M", "و"), + (0x1EE86, "M", "ز"), + (0x1EE87, "M", "ح"), + (0x1EE88, "M", "ط"), + (0x1EE89, "M", "ي"), + (0x1EE8A, "X"), + (0x1EE8B, "M", "ل"), + (0x1EE8C, "M", "م"), + (0x1EE8D, "M", "ن"), + (0x1EE8E, "M", "س"), + (0x1EE8F, "M", "ع"), + (0x1EE90, "M", "ف"), + (0x1EE91, "M", "ص"), + (0x1EE92, "M", "ق"), + (0x1EE93, "M", "ر"), + (0x1EE94, "M", "ش"), + (0x1EE95, "M", "ت"), + (0x1EE96, "M", "ث"), + (0x1EE97, "M", "خ"), + (0x1EE98, "M", "ذ"), + (0x1EE99, "M", "ض"), + (0x1EE9A, "M", "ظ"), + (0x1EE9B, "M", "غ"), + (0x1EE9C, "X"), + (0x1EEA1, "M", "ب"), + (0x1EEA2, "M", "ج"), + (0x1EEA3, "M", "د"), + (0x1EEA4, "X"), + (0x1EEA5, "M", "و"), + (0x1EEA6, "M", "ز"), + (0x1EEA7, "M", "ح"), + (0x1EEA8, "M", "ط"), + (0x1EEA9, "M", "ي"), + (0x1EEAA, "X"), + (0x1EEAB, "M", "ل"), + (0x1EEAC, "M", "م"), + (0x1EEAD, "M", "ن"), + (0x1EEAE, "M", "س"), + (0x1EEAF, "M", "ع"), + (0x1EEB0, "M", "ف"), + (0x1EEB1, "M", "ص"), + (0x1EEB2, "M", "ق"), + (0x1EEB3, "M", "ر"), + (0x1EEB4, "M", "ش"), + (0x1EEB5, "M", "ت"), + (0x1EEB6, "M", "ث"), + (0x1EEB7, "M", "خ"), + (0x1EEB8, "M", "ذ"), + (0x1EEB9, "M", "ض"), + (0x1EEBA, "M", "ظ"), + (0x1EEBB, "M", "غ"), + (0x1EEBC, "X"), + (0x1EEF0, "V"), + (0x1EEF2, "X"), + (0x1F000, "V"), + (0x1F02C, "X"), + (0x1F030, "V"), + (0x1F094, "X"), + (0x1F0A0, "V"), + (0x1F0AF, "X"), + (0x1F0B1, "V"), + (0x1F0C0, "X"), + (0x1F0C1, "V"), + (0x1F0D0, "X"), + (0x1F0D1, "V"), + (0x1F0F6, "X"), + (0x1F101, "M", "0,"), + (0x1F102, "M", "1,"), + (0x1F103, "M", "2,"), + (0x1F104, "M", "3,"), + (0x1F105, "M", "4,"), + (0x1F106, "M", "5,"), + (0x1F107, "M", "6,"), + (0x1F108, "M", "7,"), + (0x1F109, "M", "8,"), + (0x1F10A, "M", "9,"), + (0x1F10B, "V"), + (0x1F110, "M", "(a)"), + (0x1F111, "M", "(b)"), + (0x1F112, "M", "(c)"), + (0x1F113, "M", "(d)"), + (0x1F114, "M", "(e)"), + (0x1F115, "M", "(f)"), + (0x1F116, "M", "(g)"), + (0x1F117, "M", "(h)"), + (0x1F118, "M", "(i)"), + (0x1F119, "M", "(j)"), + (0x1F11A, "M", "(k)"), + (0x1F11B, "M", "(l)"), + (0x1F11C, "M", "(m)"), + (0x1F11D, "M", "(n)"), + (0x1F11E, "M", "(o)"), + (0x1F11F, "M", "(p)"), + (0x1F120, "M", "(q)"), + (0x1F121, "M", "(r)"), + (0x1F122, "M", "(s)"), + (0x1F123, "M", "(t)"), + (0x1F124, "M", "(u)"), + (0x1F125, "M", "(v)"), + (0x1F126, "M", "(w)"), + (0x1F127, "M", "(x)"), + (0x1F128, "M", "(y)"), + (0x1F129, "M", "(z)"), + (0x1F12A, "M", "〔s〕"), + (0x1F12B, "M", "c"), + (0x1F12C, "M", "r"), + (0x1F12D, "M", "cd"), + (0x1F12E, "M", "wz"), + (0x1F12F, "V"), + (0x1F130, "M", "a"), + (0x1F131, "M", "b"), + (0x1F132, "M", "c"), + (0x1F133, "M", "d"), + (0x1F134, "M", "e"), + (0x1F135, "M", "f"), + (0x1F136, "M", "g"), + (0x1F137, "M", "h"), + (0x1F138, "M", "i"), + (0x1F139, "M", "j"), + (0x1F13A, "M", "k"), + (0x1F13B, "M", "l"), + (0x1F13C, "M", "m"), + (0x1F13D, "M", "n"), + (0x1F13E, "M", "o"), + (0x1F13F, "M", "p"), + (0x1F140, "M", "q"), + (0x1F141, "M", "r"), + (0x1F142, "M", "s"), + (0x1F143, "M", "t"), + (0x1F144, "M", "u"), + (0x1F145, "M", "v"), + (0x1F146, "M", "w"), + (0x1F147, "M", "x"), + (0x1F148, "M", "y"), + (0x1F149, "M", "z"), + (0x1F14A, "M", "hv"), + (0x1F14B, "M", "mv"), + (0x1F14C, "M", "sd"), + (0x1F14D, "M", "ss"), + (0x1F14E, "M", "ppv"), + (0x1F14F, "M", "wc"), + (0x1F150, "V"), + (0x1F16A, "M", "mc"), + (0x1F16B, "M", "md"), + (0x1F16C, "M", "mr"), + (0x1F16D, "V"), + (0x1F190, "M", "dj"), + (0x1F191, "V"), + (0x1F1AE, "X"), + (0x1F1E6, "V"), + (0x1F200, "M", "ほか"), + (0x1F201, "M", "ココ"), + (0x1F202, "M", "サ"), + (0x1F203, "X"), + (0x1F210, "M", "手"), + (0x1F211, "M", "字"), + (0x1F212, "M", "双"), + (0x1F213, "M", "デ"), + (0x1F214, "M", "二"), + (0x1F215, "M", "多"), + (0x1F216, "M", "解"), + (0x1F217, "M", "天"), + (0x1F218, "M", "交"), + (0x1F219, "M", "映"), + (0x1F21A, "M", "無"), + (0x1F21B, "M", "料"), + (0x1F21C, "M", "前"), + (0x1F21D, "M", "後"), + (0x1F21E, "M", "再"), + (0x1F21F, "M", "新"), + (0x1F220, "M", "初"), + (0x1F221, "M", "終"), + (0x1F222, "M", "生"), + (0x1F223, "M", "販"), + (0x1F224, "M", "声"), + (0x1F225, "M", "吹"), + (0x1F226, "M", "演"), + (0x1F227, "M", "投"), + (0x1F228, "M", "捕"), + (0x1F229, "M", "一"), + (0x1F22A, "M", "三"), + (0x1F22B, "M", "遊"), + (0x1F22C, "M", "左"), + (0x1F22D, "M", "中"), + (0x1F22E, "M", "右"), + (0x1F22F, "M", "指"), + (0x1F230, "M", "走"), + (0x1F231, "M", "打"), + (0x1F232, "M", "禁"), + (0x1F233, "M", "空"), + (0x1F234, "M", "合"), + (0x1F235, "M", "満"), + (0x1F236, "M", "有"), + (0x1F237, "M", "月"), + (0x1F238, "M", "申"), + (0x1F239, "M", "割"), + (0x1F23A, "M", "営"), + (0x1F23B, "M", "配"), + (0x1F23C, "X"), + (0x1F240, "M", "〔本〕"), + (0x1F241, "M", "〔三〕"), + (0x1F242, "M", "〔二〕"), + (0x1F243, "M", "〔安〕"), + (0x1F244, "M", "〔点〕"), + (0x1F245, "M", "〔打〕"), + (0x1F246, "M", "〔盗〕"), + (0x1F247, "M", "〔勝〕"), + (0x1F248, "M", "〔敗〕"), + (0x1F249, "X"), + (0x1F250, "M", "得"), + (0x1F251, "M", "可"), + (0x1F252, "X"), + (0x1F260, "V"), + (0x1F266, "X"), + (0x1F300, "V"), + (0x1F6D9, "X"), + (0x1F6DC, "V"), + (0x1F6ED, "X"), + (0x1F6F0, "V"), + (0x1F6FD, "X"), + (0x1F700, "V"), + (0x1F7DA, "X"), + (0x1F7E0, "V"), + (0x1F7EC, "X"), + (0x1F7F0, "V"), + (0x1F7F1, "X"), + (0x1F800, "V"), + (0x1F80C, "X"), + (0x1F810, "V"), + (0x1F848, "X"), + (0x1F850, "V"), + (0x1F85A, "X"), + (0x1F860, "V"), + (0x1F888, "X"), + (0x1F890, "V"), + (0x1F8AE, "X"), + (0x1F8B0, "V"), + (0x1F8BC, "X"), + (0x1F8C0, "V"), + (0x1F8C2, "X"), + (0x1F8D0, "V"), + (0x1F8D9, "X"), + (0x1F900, "V"), + (0x1FA58, "X"), + (0x1FA60, "V"), + (0x1FA6E, "X"), + (0x1FA70, "V"), + (0x1FA7D, "X"), + (0x1FA80, "V"), + (0x1FA8B, "X"), + (0x1FA8E, "V"), + (0x1FAC7, "X"), + (0x1FAC8, "V"), + (0x1FAC9, "X"), + (0x1FACD, "V"), + (0x1FADD, "X"), + (0x1FADF, "V"), + (0x1FAEB, "X"), + (0x1FAEF, "V"), + (0x1FAF9, "X"), + (0x1FB00, "V"), + (0x1FB93, "X"), + (0x1FB94, "V"), + (0x1FBF0, "M", "0"), + (0x1FBF1, "M", "1"), + (0x1FBF2, "M", "2"), + (0x1FBF3, "M", "3"), + (0x1FBF4, "M", "4"), + (0x1FBF5, "M", "5"), + (0x1FBF6, "M", "6"), + (0x1FBF7, "M", "7"), + (0x1FBF8, "M", "8"), + (0x1FBF9, "M", "9"), + (0x1FBFA, "V"), + (0x1FBFB, "X"), + (0x20000, "V"), + (0x2A6E0, "X"), + (0x2A700, "V"), + (0x2B81E, "X"), + (0x2B820, "V"), + (0x2CEAE, "X"), + (0x2CEB0, "V"), + (0x2EBE1, "X"), + (0x2EBF0, "V"), + (0x2EE5E, "X"), + (0x2F800, "M", "丽"), + (0x2F801, "M", "丸"), + (0x2F802, "M", "乁"), + (0x2F803, "M", "𠄢"), + (0x2F804, "M", "你"), + (0x2F805, "M", "侮"), + (0x2F806, "M", "侻"), + (0x2F807, "M", "倂"), + (0x2F808, "M", "偺"), + (0x2F809, "M", "備"), + (0x2F80A, "M", "僧"), + (0x2F80B, "M", "像"), + (0x2F80C, "M", "㒞"), + (0x2F80D, "M", "𠘺"), + (0x2F80E, "M", "免"), + (0x2F80F, "M", "兔"), + (0x2F810, "M", "兤"), + (0x2F811, "M", "具"), + (0x2F812, "M", "𠔜"), + (0x2F813, "M", "㒹"), + (0x2F814, "M", "內"), + (0x2F815, "M", "再"), + (0x2F816, "M", "𠕋"), + (0x2F817, "M", "冗"), + (0x2F818, "M", "冤"), + (0x2F819, "M", "仌"), + (0x2F81A, "M", "冬"), + (0x2F81B, "M", "况"), + (0x2F81C, "M", "𩇟"), + (0x2F81D, "M", "凵"), + (0x2F81E, "M", "刃"), + (0x2F81F, "M", "㓟"), + (0x2F820, "M", "刻"), + (0x2F821, "M", "剆"), + (0x2F822, "M", "割"), + (0x2F823, "M", "剷"), + (0x2F824, "M", "㔕"), + (0x2F825, "M", "勇"), + (0x2F826, "M", "勉"), + (0x2F827, "M", "勤"), + (0x2F828, "M", "勺"), + (0x2F829, "M", "包"), + (0x2F82A, "M", "匆"), + (0x2F82B, "M", "北"), + (0x2F82C, "M", "卉"), + (0x2F82D, "M", "卑"), + (0x2F82E, "M", "博"), + (0x2F82F, "M", "即"), + (0x2F830, "M", "卽"), + (0x2F831, "M", "卿"), + (0x2F834, "M", "𠨬"), + (0x2F835, "M", "灰"), + (0x2F836, "M", "及"), + (0x2F837, "M", "叟"), + (0x2F838, "M", "𠭣"), + (0x2F839, "M", "叫"), + (0x2F83A, "M", "叱"), + (0x2F83B, "M", "吆"), + (0x2F83C, "M", "咞"), + (0x2F83D, "M", "吸"), + (0x2F83E, "M", "呈"), + (0x2F83F, "M", "周"), + (0x2F840, "M", "咢"), + (0x2F841, "M", "哶"), + (0x2F842, "M", "唐"), + (0x2F843, "M", "啓"), + (0x2F844, "M", "啣"), + (0x2F845, "M", "善"), + (0x2F847, "M", "喙"), + (0x2F848, "M", "喫"), + (0x2F849, "M", "喳"), + (0x2F84A, "M", "嗂"), + (0x2F84B, "M", "圖"), + (0x2F84C, "M", "嘆"), + (0x2F84D, "M", "圗"), + (0x2F84E, "M", "噑"), + (0x2F84F, "M", "噴"), + (0x2F850, "M", "切"), + (0x2F851, "M", "壮"), + (0x2F852, "M", "城"), + (0x2F853, "M", "埴"), + (0x2F854, "M", "堍"), + (0x2F855, "M", "型"), + (0x2F856, "M", "堲"), + (0x2F857, "M", "報"), + (0x2F858, "M", "墬"), + (0x2F859, "M", "𡓤"), + (0x2F85A, "M", "売"), + (0x2F85B, "M", "壷"), + (0x2F85C, "M", "夆"), + (0x2F85D, "M", "多"), + (0x2F85E, "M", "夢"), + (0x2F85F, "M", "奢"), + (0x2F860, "M", "𡚨"), + (0x2F861, "M", "𡛪"), + (0x2F862, "M", "姬"), + (0x2F863, "M", "娛"), + (0x2F864, "M", "娧"), + (0x2F865, "M", "姘"), + (0x2F866, "M", "婦"), + (0x2F867, "M", "㛮"), + (0x2F868, "M", "㛼"), + (0x2F869, "M", "嬈"), + (0x2F86A, "M", "嬾"), + (0x2F86C, "M", "𡧈"), + (0x2F86D, "M", "寃"), + (0x2F86E, "M", "寘"), + (0x2F86F, "M", "寧"), + (0x2F870, "M", "寳"), + (0x2F871, "M", "𡬘"), + (0x2F872, "M", "寿"), + (0x2F873, "M", "将"), + (0x2F874, "M", "当"), + (0x2F875, "M", "尢"), + (0x2F876, "M", "㞁"), + (0x2F877, "M", "屠"), + (0x2F878, "M", "屮"), + (0x2F879, "M", "峀"), + (0x2F87A, "M", "岍"), + (0x2F87B, "M", "𡷤"), + (0x2F87C, "M", "嵃"), + (0x2F87D, "M", "𡷦"), + (0x2F87E, "M", "嵮"), + (0x2F87F, "M", "嵫"), + (0x2F880, "M", "嵼"), + (0x2F881, "M", "巡"), + (0x2F882, "M", "巢"), + (0x2F883, "M", "㠯"), + (0x2F884, "M", "巽"), + (0x2F885, "M", "帨"), + (0x2F886, "M", "帽"), + (0x2F887, "M", "幩"), + (0x2F888, "M", "㡢"), + (0x2F889, "M", "𢆃"), + (0x2F88A, "M", "㡼"), + (0x2F88B, "M", "庰"), + (0x2F88C, "M", "庳"), + (0x2F88D, "M", "庶"), + (0x2F88E, "M", "廊"), + (0x2F88F, "M", "𪎒"), + (0x2F890, "M", "廾"), + (0x2F891, "M", "𢌱"), + (0x2F893, "M", "舁"), + (0x2F894, "M", "弢"), + (0x2F896, "M", "㣇"), + (0x2F897, "M", "𣊸"), + (0x2F898, "M", "𦇚"), + (0x2F899, "M", "形"), + (0x2F89A, "M", "彫"), + (0x2F89B, "M", "㣣"), + (0x2F89C, "M", "徚"), + (0x2F89D, "M", "忍"), + (0x2F89E, "M", "志"), + (0x2F89F, "M", "忹"), + (0x2F8A0, "M", "悁"), + (0x2F8A1, "M", "㤺"), + (0x2F8A2, "M", "㤜"), + (0x2F8A3, "M", "悔"), + (0x2F8A4, "M", "𢛔"), + (0x2F8A5, "M", "惇"), + (0x2F8A6, "M", "慈"), + (0x2F8A7, "M", "慌"), + (0x2F8A8, "M", "慎"), + (0x2F8A9, "M", "慌"), + (0x2F8AA, "M", "慺"), + (0x2F8AB, "M", "憎"), + (0x2F8AC, "M", "憲"), + (0x2F8AD, "M", "憤"), + (0x2F8AE, "M", "憯"), + (0x2F8AF, "M", "懞"), + (0x2F8B0, "M", "懲"), + (0x2F8B1, "M", "懶"), + (0x2F8B2, "M", "成"), + (0x2F8B3, "M", "戛"), + (0x2F8B4, "M", "扝"), + (0x2F8B5, "M", "抱"), + (0x2F8B6, "M", "拔"), + (0x2F8B7, "M", "捐"), + (0x2F8B8, "M", "𢬌"), + (0x2F8B9, "M", "挽"), + (0x2F8BA, "M", "拼"), + (0x2F8BB, "M", "捨"), + (0x2F8BC, "M", "掃"), + (0x2F8BD, "M", "揤"), + (0x2F8BE, "M", "𢯱"), + (0x2F8BF, "M", "搢"), + (0x2F8C0, "M", "揅"), + (0x2F8C1, "M", "掩"), + (0x2F8C2, "M", "㨮"), + (0x2F8C3, "M", "摩"), + (0x2F8C4, "M", "摾"), + (0x2F8C5, "M", "撝"), + (0x2F8C6, "M", "摷"), + (0x2F8C7, "M", "㩬"), + (0x2F8C8, "M", "敏"), + (0x2F8C9, "M", "敬"), + (0x2F8CA, "M", "𣀊"), + (0x2F8CB, "M", "旣"), + (0x2F8CC, "M", "書"), + (0x2F8CD, "M", "晉"), + (0x2F8CE, "M", "㬙"), + (0x2F8CF, "M", "暑"), + (0x2F8D0, "M", "㬈"), + (0x2F8D1, "M", "㫤"), + (0x2F8D2, "M", "冒"), + (0x2F8D3, "M", "冕"), + (0x2F8D4, "M", "最"), + (0x2F8D5, "M", "暜"), + (0x2F8D6, "M", "肭"), + (0x2F8D7, "M", "䏙"), + (0x2F8D8, "M", "朗"), + (0x2F8D9, "M", "望"), + (0x2F8DA, "M", "朡"), + (0x2F8DB, "M", "杞"), + (0x2F8DC, "M", "杓"), + (0x2F8DD, "M", "𣏃"), + (0x2F8DE, "M", "㭉"), + (0x2F8DF, "M", "柺"), + (0x2F8E0, "M", "枅"), + (0x2F8E1, "M", "桒"), + (0x2F8E2, "M", "梅"), + (0x2F8E3, "M", "𣑭"), + (0x2F8E4, "M", "梎"), + (0x2F8E5, "M", "栟"), + (0x2F8E6, "M", "椔"), + (0x2F8E7, "M", "㮝"), + (0x2F8E8, "M", "楂"), + (0x2F8E9, "M", "榣"), + (0x2F8EA, "M", "槪"), + (0x2F8EB, "M", "檨"), + (0x2F8EC, "M", "𣚣"), + (0x2F8ED, "M", "櫛"), + (0x2F8EE, "M", "㰘"), + (0x2F8EF, "M", "次"), + (0x2F8F0, "M", "𣢧"), + (0x2F8F1, "M", "歔"), + (0x2F8F2, "M", "㱎"), + (0x2F8F3, "M", "歲"), + (0x2F8F4, "M", "殟"), + (0x2F8F5, "M", "殺"), + (0x2F8F6, "M", "殻"), + (0x2F8F7, "M", "𣪍"), + (0x2F8F8, "M", "𡴋"), + (0x2F8F9, "M", "𣫺"), + (0x2F8FA, "M", "汎"), + (0x2F8FB, "M", "𣲼"), + (0x2F8FC, "M", "沿"), + (0x2F8FD, "M", "泍"), + (0x2F8FE, "M", "汧"), + (0x2F8FF, "M", "洖"), + (0x2F900, "M", "派"), + (0x2F901, "M", "海"), + (0x2F902, "M", "流"), + (0x2F903, "M", "浩"), + (0x2F904, "M", "浸"), + (0x2F905, "M", "涅"), + (0x2F906, "M", "𣴞"), + (0x2F907, "M", "洴"), + (0x2F908, "M", "港"), + (0x2F909, "M", "湮"), + (0x2F90A, "M", "㴳"), + (0x2F90B, "M", "滋"), + (0x2F90C, "M", "滇"), + (0x2F90D, "M", "𣻑"), + (0x2F90E, "M", "淹"), + (0x2F90F, "M", "潮"), + (0x2F910, "M", "𣽞"), + (0x2F911, "M", "𣾎"), + (0x2F912, "M", "濆"), + (0x2F913, "M", "瀹"), + (0x2F914, "M", "瀞"), + (0x2F915, "M", "瀛"), + (0x2F916, "M", "㶖"), + (0x2F917, "M", "灊"), + (0x2F918, "M", "災"), + (0x2F919, "M", "灷"), + (0x2F91A, "M", "炭"), + (0x2F91B, "M", "𠔥"), + (0x2F91C, "M", "煅"), + (0x2F91D, "M", "𤉣"), + (0x2F91E, "M", "熜"), + (0x2F91F, "M", "𤎫"), + (0x2F920, "M", "爨"), + (0x2F921, "M", "爵"), + (0x2F922, "M", "牐"), + (0x2F923, "M", "𤘈"), + (0x2F924, "M", "犀"), + (0x2F925, "M", "犕"), + (0x2F926, "M", "𤜵"), + (0x2F927, "M", "𤠔"), + (0x2F928, "M", "獺"), + (0x2F929, "M", "王"), + (0x2F92A, "M", "㺬"), + (0x2F92B, "M", "玥"), + (0x2F92C, "M", "㺸"), + (0x2F92E, "M", "瑇"), + (0x2F92F, "M", "瑜"), + (0x2F930, "M", "瑱"), + (0x2F931, "M", "璅"), + (0x2F932, "M", "瓊"), + (0x2F933, "M", "㼛"), + (0x2F934, "M", "甤"), + (0x2F935, "M", "𤰶"), + (0x2F936, "M", "甾"), + (0x2F937, "M", "𤲒"), + (0x2F938, "M", "異"), + (0x2F939, "M", "𢆟"), + (0x2F93A, "M", "瘐"), + (0x2F93B, "M", "𤾡"), + (0x2F93C, "M", "𤾸"), + (0x2F93D, "M", "𥁄"), + (0x2F93E, "M", "㿼"), + (0x2F93F, "M", "䀈"), + (0x2F940, "M", "直"), + (0x2F941, "M", "𥃳"), + (0x2F942, "M", "𥃲"), + (0x2F943, "M", "𥄙"), + (0x2F944, "M", "𥄳"), + (0x2F945, "M", "眞"), + (0x2F946, "M", "真"), + (0x2F948, "M", "睊"), + (0x2F949, "M", "䀹"), + (0x2F94A, "M", "瞋"), + (0x2F94B, "M", "䁆"), + (0x2F94C, "M", "䂖"), + (0x2F94D, "M", "𥐝"), + (0x2F94E, "M", "硎"), + (0x2F94F, "M", "碌"), + (0x2F950, "M", "磌"), + (0x2F951, "M", "䃣"), + (0x2F952, "M", "𥘦"), + (0x2F953, "M", "祖"), + (0x2F954, "M", "𥚚"), + (0x2F955, "M", "𥛅"), + (0x2F956, "M", "福"), + (0x2F957, "M", "秫"), + (0x2F958, "M", "䄯"), + (0x2F959, "M", "穀"), + (0x2F95A, "M", "穊"), + (0x2F95B, "M", "穏"), + (0x2F95C, "M", "𥥼"), + (0x2F95D, "M", "𥪧"), + (0x2F95F, "M", "竮"), + (0x2F960, "M", "䈂"), + (0x2F961, "M", "𥮫"), + (0x2F962, "M", "篆"), + (0x2F963, "M", "築"), + (0x2F964, "M", "䈧"), + (0x2F965, "M", "𥲀"), + (0x2F966, "M", "糒"), + (0x2F967, "M", "䊠"), + (0x2F968, "M", "糨"), + (0x2F969, "M", "糣"), + (0x2F96A, "M", "紀"), + (0x2F96B, "M", "𥾆"), + (0x2F96C, "M", "絣"), + (0x2F96D, "M", "䌁"), + (0x2F96E, "M", "緇"), + (0x2F96F, "M", "縂"), + (0x2F970, "M", "繅"), + (0x2F971, "M", "䌴"), + (0x2F972, "M", "𦈨"), + (0x2F973, "M", "𦉇"), + (0x2F974, "M", "䍙"), + (0x2F975, "M", "𦋙"), + (0x2F976, "M", "罺"), + (0x2F977, "M", "𦌾"), + (0x2F978, "M", "羕"), + (0x2F979, "M", "翺"), + (0x2F97A, "M", "者"), + (0x2F97B, "M", "𦓚"), + (0x2F97C, "M", "𦔣"), + (0x2F97D, "M", "聠"), + (0x2F97E, "M", "𦖨"), + (0x2F97F, "M", "聰"), + (0x2F980, "M", "𣍟"), + (0x2F981, "M", "䏕"), + (0x2F982, "M", "育"), + (0x2F983, "M", "脃"), + (0x2F984, "M", "䐋"), + (0x2F985, "M", "脾"), + (0x2F986, "M", "媵"), + (0x2F987, "M", "𦞧"), + (0x2F988, "M", "𦞵"), + (0x2F989, "M", "𣎓"), + (0x2F98A, "M", "𣎜"), + (0x2F98B, "M", "舁"), + (0x2F98C, "M", "舄"), + (0x2F98D, "M", "辞"), + (0x2F98E, "M", "䑫"), + (0x2F98F, "M", "芑"), + (0x2F990, "M", "芋"), + (0x2F991, "M", "芝"), + (0x2F992, "M", "劳"), + (0x2F993, "M", "花"), + (0x2F994, "M", "芳"), + (0x2F995, "M", "芽"), + (0x2F996, "M", "苦"), + (0x2F997, "M", "𦬼"), + (0x2F998, "M", "若"), + (0x2F999, "M", "茝"), + (0x2F99A, "M", "荣"), + (0x2F99B, "M", "莭"), + (0x2F99C, "M", "茣"), + (0x2F99D, "M", "莽"), + (0x2F99E, "M", "菧"), + (0x2F99F, "M", "著"), + (0x2F9A0, "M", "荓"), + (0x2F9A1, "M", "菊"), + (0x2F9A2, "M", "菌"), + (0x2F9A3, "M", "菜"), + (0x2F9A4, "M", "𦰶"), + (0x2F9A5, "M", "𦵫"), + (0x2F9A6, "M", "𦳕"), + (0x2F9A7, "M", "䔫"), + (0x2F9A8, "M", "蓱"), + (0x2F9A9, "M", "蓳"), + (0x2F9AA, "M", "蔖"), + (0x2F9AB, "M", "𧏊"), + (0x2F9AC, "M", "蕤"), + (0x2F9AD, "M", "𦼬"), + (0x2F9AE, "M", "䕝"), + (0x2F9AF, "M", "䕡"), + (0x2F9B0, "M", "𦾱"), + (0x2F9B1, "M", "𧃒"), + (0x2F9B2, "M", "䕫"), + (0x2F9B3, "M", "虐"), + (0x2F9B4, "M", "虜"), + (0x2F9B5, "M", "虧"), + (0x2F9B6, "M", "虩"), + (0x2F9B7, "M", "蚩"), + (0x2F9B8, "M", "蚈"), + (0x2F9B9, "M", "蜎"), + (0x2F9BA, "M", "蛢"), + (0x2F9BB, "M", "蝹"), + (0x2F9BC, "M", "蜨"), + (0x2F9BD, "M", "蝫"), + (0x2F9BE, "M", "螆"), + (0x2F9BF, "M", "䗗"), + (0x2F9C0, "M", "蟡"), + (0x2F9C1, "M", "蠁"), + (0x2F9C2, "M", "䗹"), + (0x2F9C3, "M", "衠"), + (0x2F9C4, "M", "衣"), + (0x2F9C5, "M", "𧙧"), + (0x2F9C6, "M", "裗"), + (0x2F9C7, "M", "裞"), + (0x2F9C8, "M", "䘵"), + (0x2F9C9, "M", "裺"), + (0x2F9CA, "M", "㒻"), + (0x2F9CB, "M", "𧢮"), + (0x2F9CC, "M", "𧥦"), + (0x2F9CD, "M", "䚾"), + (0x2F9CE, "M", "䛇"), + (0x2F9CF, "M", "誠"), + (0x2F9D0, "M", "諭"), + (0x2F9D1, "M", "變"), + (0x2F9D2, "M", "豕"), + (0x2F9D3, "M", "𧲨"), + (0x2F9D4, "M", "貫"), + (0x2F9D5, "M", "賁"), + (0x2F9D6, "M", "贛"), + (0x2F9D7, "M", "起"), + (0x2F9D8, "M", "𧼯"), + (0x2F9D9, "M", "𠠄"), + (0x2F9DA, "M", "跋"), + (0x2F9DB, "M", "趼"), + (0x2F9DC, "M", "跰"), + (0x2F9DD, "M", "𠣞"), + (0x2F9DE, "M", "軔"), + (0x2F9DF, "M", "輸"), + (0x2F9E0, "M", "𨗒"), + (0x2F9E1, "M", "𨗭"), + (0x2F9E2, "M", "邔"), + (0x2F9E3, "M", "郱"), + (0x2F9E4, "M", "鄑"), + (0x2F9E5, "M", "𨜮"), + (0x2F9E6, "M", "鄛"), + (0x2F9E7, "M", "鈸"), + (0x2F9E8, "M", "鋗"), + (0x2F9E9, "M", "鋘"), + (0x2F9EA, "M", "鉼"), + (0x2F9EB, "M", "鏹"), + (0x2F9EC, "M", "鐕"), + (0x2F9ED, "M", "𨯺"), + (0x2F9EE, "M", "開"), + (0x2F9EF, "M", "䦕"), + (0x2F9F0, "M", "閷"), + (0x2F9F1, "M", "𨵷"), + (0x2F9F2, "M", "䧦"), + (0x2F9F3, "M", "雃"), + (0x2F9F4, "M", "嶲"), + (0x2F9F5, "M", "霣"), + (0x2F9F6, "M", "𩅅"), + (0x2F9F7, "M", "𩈚"), + (0x2F9F8, "M", "䩮"), + (0x2F9F9, "M", "䩶"), + (0x2F9FA, "M", "韠"), + (0x2F9FB, "M", "𩐊"), + (0x2F9FC, "M", "䪲"), + (0x2F9FD, "M", "𩒖"), + (0x2F9FE, "M", "頋"), + (0x2FA00, "M", "頩"), + (0x2FA01, "M", "𩖶"), + (0x2FA02, "M", "飢"), + (0x2FA03, "M", "䬳"), + (0x2FA04, "M", "餩"), + (0x2FA05, "M", "馧"), + (0x2FA06, "M", "駂"), + (0x2FA07, "M", "駾"), + (0x2FA08, "M", "䯎"), + (0x2FA09, "M", "𩬰"), + (0x2FA0A, "M", "鬒"), + (0x2FA0B, "M", "鱀"), + (0x2FA0C, "M", "鳽"), + (0x2FA0D, "M", "䳎"), + (0x2FA0E, "M", "䳭"), + (0x2FA0F, "M", "鵧"), + (0x2FA10, "M", "𪃎"), + (0x2FA11, "M", "䳸"), + (0x2FA12, "M", "𪄅"), + (0x2FA13, "M", "𪈎"), + (0x2FA14, "M", "𪊑"), + (0x2FA15, "M", "麻"), + (0x2FA16, "M", "䵖"), + (0x2FA17, "M", "黹"), + (0x2FA18, "M", "黾"), + (0x2FA19, "M", "鼅"), + (0x2FA1A, "M", "鼏"), + (0x2FA1B, "M", "鼖"), + (0x2FA1C, "M", "鼻"), + (0x2FA1D, "M", "𪘀"), + (0x2FA1E, "X"), + (0x30000, "V"), + (0x3134B, "X"), + (0x31350, "V"), + (0x3347A, "X"), + (0xE0100, "I"), + (0xE01F0, "X"), +) diff --git a/intelliaide/vendor/jsonpickle-1.5.1.dist-info/INSTALLER b/intelliaide/vendor/jsonpickle-1.5.1.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/intelliaide/vendor/jsonpickle-1.5.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/intelliaide/vendor/jsonpickle-1.5.1.dist-info/LICENSE b/intelliaide/vendor/jsonpickle-1.5.1.dist-info/LICENSE new file mode 100644 index 0000000..3b8dc58 --- /dev/null +++ b/intelliaide/vendor/jsonpickle-1.5.1.dist-info/LICENSE @@ -0,0 +1,29 @@ +Copyright (C) 2008 John Paulett (john -at- paulett.org) +Copyright (C) 2009-2018 David Aguilar (davvid -at- gmail.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + 3. The name of the author may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/intelliaide/vendor/jsonpickle-1.5.1.dist-info/METADATA b/intelliaide/vendor/jsonpickle-1.5.1.dist-info/METADATA new file mode 100644 index 0000000..a4fb79d --- /dev/null +++ b/intelliaide/vendor/jsonpickle-1.5.1.dist-info/METADATA @@ -0,0 +1,179 @@ +Metadata-Version: 2.1 +Name: jsonpickle +Version: 1.5.1 +Summary: Python library for serializing any arbitrary object graph into JSON +Home-page: https://github.com/jsonpickle/jsonpickle +Author: David Aguilar +Author-email: davvid@gmail.com +License: UNKNOWN +Keywords: json pickle,json,pickle,marshal,serialization,JavaScript Object Notation +Platform: POSIX +Platform: Windows +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: JavaScript +Classifier: Operating System :: OS Independent +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=2.7 +Requires-Dist: importlib-metadata ; python_version < "3.8" +Provides-Extra: docs +Requires-Dist: sphinx ; extra == 'docs' +Requires-Dist: jaraco.packaging (>=3.2) ; extra == 'docs' +Requires-Dist: rst.linker (>=1.9) ; extra == 'docs' +Provides-Extra: testing +Requires-Dist: coverage (<5) ; extra == 'testing' +Requires-Dist: pytest (!=3.7.3,>=3.5) ; extra == 'testing' +Requires-Dist: pytest-checkdocs (>=1.2.3) ; extra == 'testing' +Requires-Dist: pytest-flake8 ; extra == 'testing' +Requires-Dist: pytest-black-multipy ; extra == 'testing' +Requires-Dist: pytest-cov ; extra == 'testing' +Requires-Dist: ecdsa ; extra == 'testing' +Requires-Dist: feedparser ; extra == 'testing' +Requires-Dist: numpy ; extra == 'testing' +Requires-Dist: pandas ; extra == 'testing' +Requires-Dist: pymongo ; extra == 'testing' +Requires-Dist: sklearn ; extra == 'testing' +Requires-Dist: sqlalchemy ; extra == 'testing' +Provides-Extra: testing.libs +Requires-Dist: demjson ; extra == 'testing.libs' +Requires-Dist: simplejson ; extra == 'testing.libs' +Requires-Dist: ujson ; extra == 'testing.libs' +Requires-Dist: yajl ; extra == 'testing.libs' +Requires-Dist: enum34 ; (python_version == "2.7") and extra == 'testing' +Requires-Dist: jsonlib ; (python_version == "2.7") and extra == 'testing' + +.. image:: https://img.shields.io/pypi/v/jsonpickle.svg + :target: `PyPI link`_ + +.. image:: https://img.shields.io/pypi/pyversions/jsonpickle.svg + :target: `PyPI link`_ + +.. _PyPI link: https://pypi.org/project/jsonpickle + +.. image:: https://dev.azure.com/jaraco/jsonpickle/_apis/build/status/jaraco.jsonpickle?branchName=master + :target: https://dev.azure.com/jaraco/jsonpickle/_build/latest?definitionId=1&branchName=master + +.. image:: https://readthedocs.org/projects/jsonpickle/badge/?version=latest + :target: https://jsonpickle.readthedocs.io/en/latest/?badge=latest + +.. image:: https://travis-ci.org/jsonpickle/jsonpickle.svg?branch=master + :target: https://travis-ci.org/jsonpickle/jsonpickle + :alt: travis + +.. image:: https://img.shields.io/badge/License-BSD%203--Clause-blue.svg + :target: https://github.com/jsonpickle/jsonpickle/blob/master/COPYING + :alt: BSD + + +jsonpickle +========== +jsonpickle is a library for the two-way conversion of complex Python objects +and `JSON `_. jsonpickle builds upon the existing JSON +encoders, such as simplejson, json, and demjson. + +For complete documentation, please visit the +`jsonpickle documentation `_. + +Bug reports and merge requests are encouraged at the +`jsonpickle repository on github `_. + +jsonpickle supports Python 2.7 and Python 3.4 or greater. + + **WARNING**: + jsonpickle can execute arbitrary Python code. Do not load jsonpickles from untrusted / unauthenticated sources. + +Why jsonpickle? +=============== +Data serialized with python's pickle (or cPickle or dill) is not easily readable outside of python. Using the json format, jsonpickle allows simple data types to be stored in a human-readable format, and more complex data types such as numpy arrays and pandas dataframes, to be machine-readable on any platform that supports json. E.g., unlike pickled data, jsonpickled data stored in an Amazon S3 bucket is indexible by Amazon's Athena. + +Install +======= + +Install from pip for the latest stable release: + +:: + + pip install jsonpickle + +Install from github for the latest changes: + +:: + + pip install git+https://github.com/jsonpickle/jsonpickle.git + +If you have the files checked out for development: + +:: + + git clone https://github.com/jsonpickle/jsonpickle.git + cd jsonpickle + python setup.py develop + + +Numpy Support +============= +jsonpickle includes a built-in numpy extension. If would like to encode +sklearn models, numpy arrays, and other numpy-based data then you must +enable the numpy extension by registering its handlers:: + + >>> import jsonpickle.ext.numpy as jsonpickle_numpy + >>> jsonpickle_numpy.register_handlers() + +Pandas Support +============== +jsonpickle includes a built-in pandas extension. If would like to encode +pandas DataFrame or Series objects then you must enable the pandas extension +by registering its handlers:: + + >>> import jsonpickle.ext.pandas as jsonpickle_pandas + >>> jsonpickle_pandas.register_handlers() + +jsonpickleJS +============ +`jsonpickleJS `_ +is a javascript implementation of jsonpickle by Michael Scott Cuthbert. +jsonpickleJS can be extremely useful for projects that have parallel data +structures between Python and Javascript. + +License +======= +Licensed under the BSD License. See COPYING for details. +See jsonpickleJS/LICENSE for details about the jsonpickleJS license. + +Development +=========== + +Use `make` to run the unit tests:: + + make test + +`pytest` is used to run unit tests internally. + +A `tox` target is provided to run tests using tox. +Setting ``multi=1`` tests using all installed and supported Python versions:: + + make tox + make tox multi=1 + +`jsonpickle` itself has no dependencies beyond the Python stdlib. +`tox` is required for testing when using the `tox` test runner only. + +The testing requirements are specified in `requirements-dev.txt`. +It is recommended to create a virtualenv and run tests from within the +virtualenv, or use a tool such as `vx `_ +to activate the virtualenv without polluting the shell environment:: + + python3 -mvenv env3x + vx env3x pip install --requirement requirements-dev.txt + vx env3x make test + +`jsonpickle` supports multiple Python versions, so using a combination of +multiple virtualenvs and `tox` is useful in order to catch compatibility +issues when developing. + + diff --git a/intelliaide/vendor/jsonpickle-1.5.1.dist-info/RECORD b/intelliaide/vendor/jsonpickle-1.5.1.dist-info/RECORD new file mode 100644 index 0000000..c44afba --- /dev/null +++ b/intelliaide/vendor/jsonpickle-1.5.1.dist-info/RECORD @@ -0,0 +1,30 @@ +jsonpickle-1.5.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +jsonpickle-1.5.1.dist-info/LICENSE,sha256=x0J1XN545Tr9tDrZCzeskCyA81D41CGSSYCKl2WclqY,1493 +jsonpickle-1.5.1.dist-info/METADATA,sha256=zQsd_8r3eVlFC9njLPazwnlFjMiPYr1QIevaMLIrsis,6632 +jsonpickle-1.5.1.dist-info/RECORD,, +jsonpickle-1.5.1.dist-info/WHEEL,sha256=Z-nyYpwrcSqxfdux5Mbn_DQ525iP7J2DG3JgGvOYyTQ,110 +jsonpickle-1.5.1.dist-info/top_level.txt,sha256=cdRDYlLc4XOT2KzzPurieMn-XW_3YxFzopwdeDqPFVs,11 +jsonpickle/__init__.py,sha256=1Az0ZGQbumVkaxKs0VC51yK0lwspN9N-v4menGQeRes,2585 +jsonpickle/__pycache__/__init__.cpython-312.pyc,, +jsonpickle/__pycache__/backend.cpython-312.pyc,, +jsonpickle/__pycache__/compat.cpython-312.pyc,, +jsonpickle/__pycache__/handlers.cpython-312.pyc,, +jsonpickle/__pycache__/pickler.cpython-312.pyc,, +jsonpickle/__pycache__/tags.cpython-312.pyc,, +jsonpickle/__pycache__/unpickler.cpython-312.pyc,, +jsonpickle/__pycache__/util.cpython-312.pyc,, +jsonpickle/__pycache__/version.cpython-312.pyc,, +jsonpickle/backend.py,sha256=_OdeFpbnbqOkb6C6MIe95ohNHeDpLBIswVuNTHCmZJE,10312 +jsonpickle/compat.py,sha256=5rkux_slQiyqI6dODJUK7VdDM_x5ksfJbgz4A4OS5_Q,1025 +jsonpickle/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +jsonpickle/ext/__pycache__/__init__.cpython-312.pyc,, +jsonpickle/ext/__pycache__/numpy.cpython-312.pyc,, +jsonpickle/ext/__pycache__/pandas.cpython-312.pyc,, +jsonpickle/ext/numpy.py,sha256=w2YgZqR3FSE6u3hPz9ririDBkYIAtrInjeGlDWHJ3qw,12339 +jsonpickle/ext/pandas.py,sha256=GULGS0Da5prD9Tdo5Mhvq_EBzFWcVfZqT0gmqa0PRXM,7231 +jsonpickle/handlers.py,sha256=yvAI7unS9dcRPYvOws9YqUNiRA9BwbNjX2Vx-gB768c,8460 +jsonpickle/pickler.py,sha256=baIG2IeX7miKtGMJl-Q5HpYLILrnzDGWpHKc--eV4Ww,26702 +jsonpickle/tags.py,sha256=QSrEwfvGvqPJxy6KkTKskDKqstm76RulfX8uBrbfn70,986 +jsonpickle/unpickler.py,sha256=EdxyuluNiOwy7g_BGpTD2AWQmYUVccHF1-RIsh5O_4c,25825 +jsonpickle/util.py,sha256=2HGA8CCBwgwiSrXV7PY2XP1vun2RtpvJRSkgouCSs1I,14515 +jsonpickle/version.py,sha256=blvoKVdqIhtMALqv8aFBVj56EYIXUJJMAUkZTec9Ttg,448 diff --git a/intelliaide/vendor/jsonpickle-1.5.1.dist-info/WHEEL b/intelliaide/vendor/jsonpickle-1.5.1.dist-info/WHEEL new file mode 100644 index 0000000..01b8fc7 --- /dev/null +++ b/intelliaide/vendor/jsonpickle-1.5.1.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.36.2) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/intelliaide/vendor/jsonpickle-1.5.1.dist-info/top_level.txt b/intelliaide/vendor/jsonpickle-1.5.1.dist-info/top_level.txt new file mode 100644 index 0000000..dc15bbd --- /dev/null +++ b/intelliaide/vendor/jsonpickle-1.5.1.dist-info/top_level.txt @@ -0,0 +1 @@ +jsonpickle diff --git a/intelliaide/vendor/jsonpickle/__init__.py b/intelliaide/vendor/jsonpickle/__init__.py new file mode 100644 index 0000000..bf92575 --- /dev/null +++ b/intelliaide/vendor/jsonpickle/__init__.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2008 John Paulett (john -at- paulett.org) +# Copyright (C) 2009, 2011, 2013 David Aguilar (davvid -at- gmail.com) +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. + +"""Python library for serializing any arbitrary object graph into JSON. + +.. warning:: + + jsonpickle can execute arbitrary Python code. Do not load jsonpickles from + untrusted / unauthenticated sources. + +jsonpickle can take almost any Python object and turn the object into JSON. +Additionally, it can reconstitute the object back into Python. + +The object must be accessible globally via a module and must +inherit from object (AKA new-style classes). + +Create an object:: + + class Thing(object): + def __init__(self, name): + self.name = name + + obj = Thing('Awesome') + +Use jsonpickle to transform the object into a JSON string:: + + import jsonpickle + frozen = jsonpickle.encode(obj) + +Use jsonpickle to recreate a Python object from a JSON string:: + + thawed = jsonpickle.decode(frozen) + +The new object has the same type and data, but essentially is now a copy of +the original. + +.. code-block:: python + + assert obj.name == thawed.name + +If you will never need to load (regenerate the Python class from JSON), you can +pass in the keyword unpicklable=False to prevent extra information from being +added to JSON:: + + oneway = jsonpickle.encode(obj, unpicklable=False) + result = jsonpickle.decode(oneway) + assert obj.name == result['name'] == 'Awesome' + +""" +from __future__ import absolute_import, division, unicode_literals + +from .backend import json +from .pickler import encode +from .unpickler import decode + +# Export other names not in __all__ +from .backend import JSONBackend # noqa: F401 +from .version import __version__ # noqa: F401 +from .handlers import register # noqa: F401 +from .handlers import unregister # noqa: F401 +from .pickler import Pickler # noqa: F401 +from .unpickler import Unpickler # noqa: F401 + +__all__ = ('encode', 'decode') + +# register built-in handlers +__import__('jsonpickle.handlers', level=0) + +# Export specific JSONPluginMgr methods into the jsonpickle namespace +set_preferred_backend = json.set_preferred_backend +set_decoder_options = json.set_decoder_options +set_encoder_options = json.set_encoder_options +load_backend = json.load_backend +remove_backend = json.remove_backend +enable_fallthrough = json.enable_fallthrough + +# json.load(), loads(), dump(), dumps() compatibility +dumps = encode +loads = decode diff --git a/intelliaide/vendor/jsonpickle/backend.py b/intelliaide/vendor/jsonpickle/backend.py new file mode 100644 index 0000000..e547dae --- /dev/null +++ b/intelliaide/vendor/jsonpickle/backend.py @@ -0,0 +1,294 @@ +from __future__ import absolute_import, division, unicode_literals + +from .compat import string_types +from .compat import PY3_ORDERED_DICT + + +class JSONBackend(object): + """Manages encoding and decoding using various backends. + + It tries these modules in this order: + simplejson, json, demjson + + simplejson is a fast and popular backend and is tried first. + json comes with Python and is tried second. + demjson is the most permissive backend and is tried last. + + """ + + def __init__(self, fallthrough=True): + # Whether we should fallthrough to the next backend + self._fallthrough = fallthrough + # The names of backends that have been successfully imported + self._backend_names = [] + + # A dictionary mapping backend names to encode/decode functions + self._encoders = {} + self._decoders = {} + + # Options to pass to specific encoders + self._encoder_options = {} + + # Options to pass to specific decoders + self._decoder_options = {} + + # The exception class that is thrown when a decoding error occurs + self._decoder_exceptions = {} + + # Whether we've loaded any backends successfully + self._verified = False + + self.load_backend('simplejson') + self.load_backend('json') + self.load_backend('demjson', 'encode', 'decode', 'JSONDecodeError') + self.load_backend('jsonlib', 'write', 'read', 'ReadError') + self.load_backend('yajl') + self.load_backend('ujson') + + # Defaults for various encoders + sort = not PY3_ORDERED_DICT + json_opts = ((), {'sort_keys': sort}) + self._encoder_options = { + 'ujson': ((), {'sort_keys': sort, 'escape_forward_slashes': False}), + 'json': json_opts, + 'simplejson': json_opts, + 'django.util.simplejson': json_opts, + } + + def _verify(self): + """Ensures that we've loaded at least one JSON backend.""" + if self._verified: + return + raise AssertionError( + 'jsonpickle requires at least one of the ' + 'following:\n' + ' python2.6, simplejson, or demjson' + ) + + def enable_fallthrough(self, enable): + """ + Disable jsonpickle's fallthrough-on-error behavior + + By default, jsonpickle tries the next backend when decoding or + encoding using a backend fails. + + This can make it difficult to force jsonpickle to use a specific + backend, and catch errors, because the error will be suppressed and + may not be raised by the subsequent backend. + + Calling `enable_backend(False)` will make jsonpickle immediately + re-raise any exceptions raised by the backends. + + """ + self._fallthrough = enable + + def load_backend(self, name, dumps='dumps', loads='loads', loads_exc=ValueError): + + """Load a JSON backend by name. + + This method loads a backend and sets up references to that + backend's loads/dumps functions and exception classes. + + :param dumps: is the name of the backend's encode method. + The method should take an object and return a string. + Defaults to 'dumps'. + :param loads: names the backend's method for the reverse + operation -- returning a Python object from a string. + :param loads_exc: can be either the name of the exception class + used to denote decoding errors, or it can be a direct reference + to the appropriate exception class itself. If it is a name, + then the assumption is that an exception class of that name + can be found in the backend module's namespace. + :param load: names the backend's 'load' method. + :param dump: names the backend's 'dump' method. + :rtype bool: True on success, False if the backend could not be loaded. + + """ + try: + # Load the JSON backend + mod = __import__(name) + except ImportError: + return False + + # Handle submodules, e.g. django.utils.simplejson + try: + for attr in name.split('.')[1:]: + mod = getattr(mod, attr) + except AttributeError: + return False + + if not self._store(self._encoders, name, mod, dumps) or not self._store( + self._decoders, name, mod, loads + ): + return False + + if isinstance(loads_exc, string_types): + # This backend's decoder exception is part of the backend + if not self._store(self._decoder_exceptions, name, mod, loads_exc): + return False + else: + # simplejson uses ValueError + self._decoder_exceptions[name] = loads_exc + + # Setup the default args and kwargs for this encoder/decoder + self._encoder_options.setdefault(name, ([], {})) + self._decoder_options.setdefault(name, ([], {})) + + # Add this backend to the list of candidate backends + self._backend_names.append(name) + + # Indicate that we successfully loaded a JSON backend + self._verified = True + return True + + def remove_backend(self, name): + """Remove all entries for a particular backend.""" + self._encoders.pop(name, None) + self._decoders.pop(name, None) + self._decoder_exceptions.pop(name, None) + self._decoder_options.pop(name, None) + self._encoder_options.pop(name, None) + if name in self._backend_names: + self._backend_names.remove(name) + self._verified = bool(self._backend_names) + + def encode(self, obj, indent=None, separators=None): + """ + Attempt to encode an object into JSON. + + This tries the loaded backends in order and passes along the last + exception if no backend is able to encode the object. + + """ + self._verify() + + if not self._fallthrough: + name = self._backend_names[0] + return self.backend_encode(name, obj, indent=indent, separators=separators) + + for idx, name in enumerate(self._backend_names): + try: + return self.backend_encode( + name, obj, indent=indent, separators=separators + ) + except Exception as e: + if idx == len(self._backend_names) - 1: + raise e + + # def dumps + dumps = encode + + def backend_encode(self, name, obj, indent=None, separators=None): + optargs, optkwargs = self._encoder_options.get(name, ([], {})) + encoder_kwargs = optkwargs.copy() + if indent is not None: + encoder_kwargs['indent'] = indent + if separators is not None: + encoder_kwargs['separators'] = separators + encoder_args = (obj,) + tuple(optargs) + return self._encoders[name](*encoder_args, **encoder_kwargs) + + def decode(self, string): + """ + Attempt to decode an object from a JSON string. + + This tries the loaded backends in order and passes along the last + exception if no backends are able to decode the string. + + """ + self._verify() + + if not self._fallthrough: + name = self._backend_names[0] + return self.backend_decode(name, string) + + for idx, name in enumerate(self._backend_names): + try: + return self.backend_decode(name, string) + except self._decoder_exceptions[name] as e: + if idx == len(self._backend_names) - 1: + raise e + else: + pass # and try a more forgiving encoder, e.g. demjson + + # def loads + loads = decode + + def backend_decode(self, name, string): + optargs, optkwargs = self._decoder_options.get(name, ((), {})) + decoder_kwargs = optkwargs.copy() + return self._decoders[name](string, *optargs, **decoder_kwargs) + + def set_preferred_backend(self, name): + """ + Set the preferred json backend. + + If a preferred backend is set then jsonpickle tries to use it + before any other backend. + + For example:: + + set_preferred_backend('simplejson') + + If the backend is not one of the built-in jsonpickle backends + (json/simplejson, or demjson) then you must load the backend + prior to calling set_preferred_backend. + + AssertionError is raised if the backend has not been loaded. + + """ + if name in self._backend_names: + self._backend_names.remove(name) + self._backend_names.insert(0, name) + else: + errmsg = 'The "%s" backend has not been loaded.' % name + raise AssertionError(errmsg) + + def set_encoder_options(self, name, *args, **kwargs): + """ + Associate encoder-specific options with an encoder. + + After calling set_encoder_options, any calls to jsonpickle's + encode method will pass the supplied args and kwargs along to + the appropriate backend's encode method. + + For example:: + + set_encoder_options('simplejson', sort_keys=True, indent=4) + set_encoder_options('demjson', compactly=False) + + See the appropriate encoder's documentation for details about + the supported arguments and keyword arguments. + + """ + self._encoder_options[name] = (args, kwargs) + + def set_decoder_options(self, name, *args, **kwargs): + """ + Associate decoder-specific options with a decoder. + + After calling set_decoder_options, any calls to jsonpickle's + decode method will pass the supplied args and kwargs along to + the appropriate backend's decode method. + + For example:: + + set_decoder_options('simplejson', encoding='utf8', cls=JSONDecoder) + set_decoder_options('demjson', strict=True) + + See the appropriate decoder's documentation for details about + the supported arguments and keyword arguments. + + """ + self._decoder_options[name] = (args, kwargs) + + def _store(self, dct, backend, obj, name): + try: + dct[backend] = getattr(obj, name) + except AttributeError: + self.remove_backend(backend) + return False + return True + + +json = JSONBackend() diff --git a/intelliaide/vendor/jsonpickle/compat.py b/intelliaide/vendor/jsonpickle/compat.py new file mode 100644 index 0000000..bf96712 --- /dev/null +++ b/intelliaide/vendor/jsonpickle/compat.py @@ -0,0 +1,39 @@ +from __future__ import absolute_import, division, unicode_literals +import sys +import types +import base64 + +PY_MAJOR = sys.version_info[0] +PY2 = PY_MAJOR == 2 +PY3 = PY_MAJOR == 3 +PY3_ORDERED_DICT = PY3 and sys.version_info[1] >= 6 # Python 3.6+ + +class_types = (type,) +iterator_types = (type(iter('')),) + +if PY3: + import builtins + import queue + from base64 import encodebytes, decodebytes + from collections.abc import Iterator as abc_iterator + + string_types = (str,) + numeric_types = (int, float) + ustr = str +else: + from collections import Iterator as abc_iterator # noqa + + builtins = __import__('__builtin__') + class_types += (types.ClassType,) + encodebytes = base64.encodestring + decodebytes = base64.decodestring + string_types = (builtins.basestring,) + numeric_types = (int, float, builtins.long) + queue = __import__('Queue') + ustr = builtins.unicode + + +def iterator(class_): + if PY2 and hasattr(class_, '__next__'): + class_.next = class_.__next__ + return class_ diff --git a/intelliaide/vendor/jsonpickle/ext/__init__.py b/intelliaide/vendor/jsonpickle/ext/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/intelliaide/vendor/jsonpickle/ext/numpy.py b/intelliaide/vendor/jsonpickle/ext/numpy.py new file mode 100644 index 0000000..d456839 --- /dev/null +++ b/intelliaide/vendor/jsonpickle/ext/numpy.py @@ -0,0 +1,337 @@ +from __future__ import absolute_import +import ast +import sys +import zlib +import warnings +import json + +import numpy as np + +from ..handlers import BaseHandler, register, unregister +from ..compat import numeric_types +from ..util import b64decode, b64encode +from .. import compat + + +__all__ = ['register_handlers', 'unregister_handlers'] + +native_byteorder = '<' if sys.byteorder == 'little' else '>' + + +def get_byteorder(arr): + """translate equals sign to native order""" + byteorder = arr.dtype.byteorder + return native_byteorder if byteorder == '=' else byteorder + + +class NumpyBaseHandler(BaseHandler): + def flatten_dtype(self, dtype, data): + if hasattr(dtype, 'tostring'): + data['dtype'] = dtype.tostring() + else: + dtype = compat.ustr(dtype) + prefix = '(numpy.record, ' + if dtype.startswith(prefix): + dtype = dtype[len(prefix) : -1] + data['dtype'] = dtype + + def restore_dtype(self, data): + dtype = data['dtype'] + if dtype.startswith(('{', '[')): + dtype = ast.literal_eval(dtype) + return np.dtype(dtype) + + +class NumpyDTypeHandler(NumpyBaseHandler): + def flatten(self, obj, data): + self.flatten_dtype(obj, data) + return data + + def restore(self, data): + return self.restore_dtype(data) + + +class NumpyGenericHandler(NumpyBaseHandler): + def flatten(self, obj, data): + self.flatten_dtype(obj.dtype.newbyteorder('N'), data) + data['value'] = self.context.flatten(obj.tolist(), reset=False) + return data + + def restore(self, data): + value = self.context.restore(data['value'], reset=False) + return self.restore_dtype(data).type(value) + + +class NumpyNDArrayHandler(NumpyBaseHandler): + """Stores arrays as text representation, without regard for views""" + + def flatten_flags(self, obj, data): + if obj.flags.writeable is False: + data['writeable'] = False + + def restore_flags(self, data, arr): + if not data.get('writeable', True): + arr.flags.writeable = False + + def flatten(self, obj, data): + self.flatten_dtype(obj.dtype.newbyteorder('N'), data) + self.flatten_flags(obj, data) + data['values'] = self.context.flatten(obj.tolist(), reset=False) + if 0 in obj.shape: + # add shape information explicitly as it cannot be + # inferred from an empty list + data['shape'] = obj.shape + return data + + def restore(self, data): + values = self.context.restore(data['values'], reset=False) + arr = np.array( + values, dtype=self.restore_dtype(data), order=data.get('order', 'C') + ) + shape = data.get('shape', None) + if shape is not None: + arr = arr.reshape(shape) + + self.restore_flags(data, arr) + return arr + + +class NumpyNDArrayHandlerBinary(NumpyNDArrayHandler): + """stores arrays with size greater than 'size_threshold' as + (optionally) compressed base64 + + Notes + ----- + This would be easier to implement using np.save/np.load, but + that would be less language-agnostic + """ + + def __init__(self, size_threshold=16, compression=zlib): + """ + :param size_threshold: nonnegative int or None + valid values for 'size_threshold' are all nonnegative + integers and None + if size_threshold is None, values are always stored as nested lists + :param compression: a compression module or None + valid values for 'compression' are {zlib, bz2, None} + if compresion is None, no compression is applied + """ + self.size_threshold = size_threshold + self.compression = compression + + def flatten_byteorder(self, obj, data): + byteorder = obj.dtype.byteorder + if byteorder != '|': + data['byteorder'] = get_byteorder(obj) + + def restore_byteorder(self, data, arr): + byteorder = data.get('byteorder', None) + if byteorder: + arr.dtype = arr.dtype.newbyteorder(byteorder) + + def flatten(self, obj, data): + """encode numpy to json""" + if self.size_threshold is None or self.size_threshold >= obj.size: + # encode as text + data = super(NumpyNDArrayHandlerBinary, self).flatten(obj, data) + else: + # encode as binary + if obj.dtype == np.object: + # There's a bug deep in the bowels of numpy that causes a + # segfault when round-tripping an ndarray of dtype object. + # E.g., the following will result in a segfault: + # import numpy as np + # arr = np.array([str(i) for i in range(3)], + # dtype=np.object) + # dtype = arr.dtype + # shape = arr.shape + # buf = arr.tobytes() + # del arr + # arr = np.ndarray(buffer=buf, dtype=dtype, + # shape=shape).copy() + # So, save as a binary-encoded list in this case + buf = json.dumps(obj.tolist()).encode() + elif hasattr(obj, 'tobytes'): + # numpy docstring is lacking as of 1.11.2, + # but this is the option we need + buf = obj.tobytes(order='a') + else: + # numpy < 1.9 compatibility + buf = obj.tostring(order='a') + if self.compression: + buf = self.compression.compress(buf) + data['values'] = b64encode(buf) + data['shape'] = obj.shape + self.flatten_dtype(obj.dtype.newbyteorder('N'), data) + self.flatten_byteorder(obj, data) + self.flatten_flags(obj, data) + + if not obj.flags.c_contiguous: + data['order'] = 'F' + + return data + + def restore(self, data): + """decode numpy from json""" + values = data['values'] + if isinstance(values, list): + # decode text representation + arr = super(NumpyNDArrayHandlerBinary, self).restore(data) + elif isinstance(values, numeric_types): + # single-value array + arr = np.array([values], dtype=self.restore_dtype(data)) + else: + # decode binary representation + dtype = self.restore_dtype(data) + buf = b64decode(values) + if self.compression: + buf = self.compression.decompress(buf) + # See note above about segfault bug for numpy dtype object. Those + # are saved as a list to work around that. + if dtype == np.object: + values = json.loads(buf.decode()) + arr = np.array(values, dtype=dtype, order=data.get('order', 'C')) + shape = data.get('shape', None) + if shape is not None: + arr = arr.reshape(shape) + else: + arr = np.ndarray( + buffer=buf, + dtype=dtype, + shape=data.get('shape'), + order=data.get('order', 'C'), + ).copy() # make a copy, to force the result to own the data + self.restore_byteorder(data, arr) + self.restore_flags(data, arr) + + return arr + + +class NumpyNDArrayHandlerView(NumpyNDArrayHandlerBinary): + """Pickles references inside ndarrays, or array-views + + Notes + ----- + The current implementation has some restrictions. + + 'base' arrays, or arrays which are viewed by other arrays, + must be f-or-c-contiguous. + This is not such a large restriction in practice, because all + numpy array creation is c-contiguous by default. + Relaxing this restriction would be nice though; especially if + it can be done without bloating the design too much. + + Furthermore, ndarrays which are views of array-like objects + implementing __array_interface__, + but which are not themselves nd-arrays, are deepcopied with + a warning (by default), + as we cannot guarantee whatever custom logic such classes + implement is correctly reproduced. + """ + + def __init__(self, mode='warn', size_threshold=16, compression=zlib): + """ + :param mode: {'warn', 'raise', 'ignore'} + How to react when encountering array-like objects whos + references we cannot safely serialize + :param size_threshold: nonnegative int or None + valid values for 'size_threshold' are all nonnegative + integers and None + if size_threshold is None, values are always stored as nested lists + :param compression: a compression module or None + valid values for 'compression' are {zlib, bz2, None} + if compresion is None, no compression is applied + """ + super(NumpyNDArrayHandlerView, self).__init__(size_threshold, compression) + self.mode = mode + + def flatten(self, obj, data): + """encode numpy to json""" + base = obj.base + if base is None and obj.flags.forc: + # store by value + data = super(NumpyNDArrayHandlerView, self).flatten(obj, data) + # ensure that views on arrays stored as text + # are interpreted correctly + if not obj.flags.c_contiguous: + data['order'] = 'F' + elif isinstance(base, np.ndarray) and base.flags.forc: + # store by reference + data['base'] = self.context.flatten(base, reset=False) + + offset = obj.ctypes.data - base.ctypes.data + if offset: + data['offset'] = offset + + if not obj.flags.c_contiguous: + data['strides'] = obj.strides + + data['shape'] = obj.shape + self.flatten_dtype(obj.dtype.newbyteorder('N'), data) + self.flatten_flags(obj, data) + + if get_byteorder(obj) != '|': + byteorder = 'S' if get_byteorder(obj) != get_byteorder(base) else None + if byteorder: + data['byteorder'] = byteorder + + if self.size_threshold is None or self.size_threshold >= obj.size: + # not used in restore since base is present, but + # include values for human-readability + super(NumpyNDArrayHandlerBinary, self).flatten(obj, data) + else: + # store a deepcopy or fail + if self.mode == 'warn': + msg = ( + "ndarray is defined by reference to an object " + "we do not know how to serialize. " + "A deep copy is serialized instead, breaking " + "memory aliasing." + ) + warnings.warn(msg) + elif self.mode == 'raise': + msg = ( + "ndarray is defined by reference to an object we do " + "not know how to serialize." + ) + raise ValueError(msg) + data = super(NumpyNDArrayHandlerView, self).flatten(obj.copy(), data) + + return data + + def restore(self, data): + """decode numpy from json""" + base = data.get('base', None) + if base is None: + # decode array with owndata=True + arr = super(NumpyNDArrayHandlerView, self).restore(data) + else: + # decode array view, which references the data of another array + base = self.context.restore(base, reset=False) + assert ( + base.flags.forc + ), "Current implementation assumes base is C or F contiguous" + + arr = np.ndarray( + buffer=base.data, + dtype=self.restore_dtype(data).newbyteorder(data.get('byteorder', '|')), + shape=data.get('shape'), + offset=data.get('offset', 0), + strides=data.get('strides', None), + ) + + self.restore_flags(data, arr) + + return arr + + +def register_handlers(): + register(np.dtype, NumpyDTypeHandler, base=True) + register(np.generic, NumpyGenericHandler, base=True) + register(np.ndarray, NumpyNDArrayHandlerView(), base=True) + + +def unregister_handlers(): + unregister(np.dtype) + unregister(np.generic) + unregister(np.ndarray) diff --git a/intelliaide/vendor/jsonpickle/ext/pandas.py b/intelliaide/vendor/jsonpickle/ext/pandas.py new file mode 100644 index 0000000..985b5a0 --- /dev/null +++ b/intelliaide/vendor/jsonpickle/ext/pandas.py @@ -0,0 +1,235 @@ +from __future__ import absolute_import + +import pandas as pd +from io import StringIO +import zlib + +from .. import encode, decode +from ..handlers import BaseHandler, register, unregister +from ..util import b64decode, b64encode +from .numpy import register_handlers as register_numpy_handlers +from .numpy import unregister_handlers as unregister_numpy_handlers + +__all__ = ['register_handlers', 'unregister_handlers'] + + +class PandasProcessor(object): + def __init__(self, size_threshold=500, compression=zlib): + """ + :param size_threshold: nonnegative int or None + valid values for 'size_threshold' are all nonnegative + integers and None. If size_threshold is None, + dataframes are always stored as csv strings + :param compression: a compression module or None + valid values for 'compression' are {zlib, bz2, None} + if compresion is None, no compression is applied + """ + self.size_threshold = size_threshold + self.compression = compression + + def flatten_pandas(self, buf, data, meta=None): + if self.size_threshold is not None and len(buf) > self.size_threshold: + if self.compression: + buf = self.compression.compress(buf.encode()) + data['comp'] = True + data['values'] = b64encode(buf) + data['txt'] = False + else: + data['values'] = buf + data['txt'] = True + + data['meta'] = meta + return data + + def restore_pandas(self, data): + if data.get('txt', True): + # It's just text... + buf = data['values'] + else: + buf = b64decode(data['values']) + if data.get('comp', False): + buf = self.compression.decompress(buf).decode() + meta = data.get('meta', {}) + return (buf, meta) + + +def make_read_csv_params(meta): + meta_dtypes = meta.get('dtypes', {}) + + parse_dates = [] + converters = {} + dtype = {} + timedeltas = [] + for k, v in meta_dtypes.items(): + if v.startswith('datetime'): + parse_dates.append(k) + elif v.startswith('complex'): + converters[k] = complex + elif v.startswith('timedelta'): + timedeltas.append(k) + dtype[k] = 'object' + else: + dtype[k] = v + + return dict(dtype=dtype, parse_dates=parse_dates, converters=converters), timedeltas + + +class PandasDfHandler(BaseHandler): + pp = PandasProcessor() + + def flatten(self, obj, data): + dtype = obj.dtypes.to_dict() + + meta = {'dtypes': {k: str(dtype[k]) for k in dtype}, 'index': encode(obj.index)} + + data = self.pp.flatten_pandas( + obj.reset_index(drop=True).to_csv(index=False), data, meta + ) + return data + + def restore(self, data): + csv, meta = self.pp.restore_pandas(data) + params, timedeltas = make_read_csv_params(meta) + df = ( + pd.read_csv(StringIO(csv), **params) + if data['values'].strip() + else pd.DataFrame() + ) + for col in timedeltas: + df[col] = pd.to_timedelta(df[col]) + + df.set_index(decode(meta['index']), inplace=True) + return df + + +class PandasSeriesHandler(BaseHandler): + pp = PandasProcessor() + + def flatten(self, obj, data): + """Flatten the index and values for reconstruction""" + data['name'] = obj.name + # This relies on the numpy handlers for the inner guts. + data['index'] = self.context.flatten(obj.index, reset=False) + data['values'] = self.context.flatten(obj.values, reset=False) + return data + + def restore(self, data): + """Restore the flattened data""" + name = data['name'] + index = self.context.restore(data['index'], reset=False) + values = self.context.restore(data['values'], reset=False) + return pd.Series(values, index=index, name=name) + + +class PandasIndexHandler(BaseHandler): + + pp = PandasProcessor() + index_constructor = pd.Index + + def name_bundler(self, obj): + return {'name': obj.name} + + def flatten(self, obj, data): + name_bundle = self.name_bundler(obj) + meta = dict(dtype=str(obj.dtype), **name_bundle) + buf = encode(obj.tolist()) + data = self.pp.flatten_pandas(buf, data, meta) + return data + + def restore(self, data): + buf, meta = self.pp.restore_pandas(data) + dtype = meta.get('dtype', None) + name_bundle = {k: v for k, v in meta.items() if k in {'name', 'names'}} + idx = self.index_constructor(decode(buf), dtype=dtype, **name_bundle) + return idx + + +class PandasPeriodIndexHandler(PandasIndexHandler): + index_constructor = pd.PeriodIndex + + +class PandasMultiIndexHandler(PandasIndexHandler): + def name_bundler(self, obj): + return {'names': obj.names} + + +class PandasTimestampHandler(BaseHandler): + pp = PandasProcessor() + + def flatten(self, obj, data): + meta = {'isoformat': obj.isoformat()} + buf = '' + data = self.pp.flatten_pandas(buf, data, meta) + return data + + def restore(self, data): + _, meta = self.pp.restore_pandas(data) + isoformat = meta['isoformat'] + obj = pd.Timestamp(isoformat) + return obj + + +class PandasPeriodHandler(BaseHandler): + pp = PandasProcessor() + + def flatten(self, obj, data): + meta = { + 'start_time': encode(obj.start_time), + 'freqstr': obj.freqstr, + } + buf = '' + data = self.pp.flatten_pandas(buf, data, meta) + return data + + def restore(self, data): + _, meta = self.pp.restore_pandas(data) + start_time = decode(meta['start_time']) + freqstr = meta['freqstr'] + obj = pd.Period(start_time, freqstr) + return obj + + +class PandasIntervalHandler(BaseHandler): + pp = PandasProcessor() + + def flatten(self, obj, data): + meta = { + 'left': encode(obj.left), + 'right': encode(obj.right), + 'closed': obj.closed, + } + buf = '' + data = self.pp.flatten_pandas(buf, data, meta) + return data + + def restore(self, data): + _, meta = self.pp.restore_pandas(data) + left = decode(meta['left']) + right = decode(meta['right']) + closed = str(meta['closed']) + obj = pd.Interval(left, right, closed=closed) + return obj + + +def register_handlers(): + register_numpy_handlers() + register(pd.DataFrame, PandasDfHandler, base=True) + register(pd.Series, PandasSeriesHandler, base=True) + register(pd.Index, PandasIndexHandler, base=True) + register(pd.PeriodIndex, PandasPeriodIndexHandler, base=True) + register(pd.MultiIndex, PandasMultiIndexHandler, base=True) + register(pd.Timestamp, PandasTimestampHandler, base=True) + register(pd.Period, PandasPeriodHandler, base=True) + register(pd.Interval, PandasIntervalHandler, base=True) + + +def unregister_handlers(): + unregister_numpy_handlers() + unregister(pd.DataFrame) + unregister(pd.Series) + unregister(pd.Index) + unregister(pd.PeriodIndex) + unregister(pd.MultiIndex) + unregister(pd.Timestamp) + unregister(pd.Period) + unregister(pd.Interval) diff --git a/intelliaide/vendor/jsonpickle/handlers.py b/intelliaide/vendor/jsonpickle/handlers.py new file mode 100644 index 0000000..551c802 --- /dev/null +++ b/intelliaide/vendor/jsonpickle/handlers.py @@ -0,0 +1,294 @@ +""" +Custom handlers may be created to handle other objects. Each custom handler +must derive from :class:`jsonpickle.handlers.BaseHandler` and +implement ``flatten`` and ``restore``. + +A handler can be bound to other types by calling +:func:`jsonpickle.handlers.register`. + +""" +from __future__ import absolute_import, division, unicode_literals +import array +import copy +import datetime +import io +import re +import sys +import threading +import uuid + +from . import compat +from . import util + + +class Registry(object): + def __init__(self): + self._handlers = {} + self._base_handlers = {} + + def get(self, cls_or_name, default=None): + """ + :param cls_or_name: the type or its fully qualified name + :param default: default value, if a matching handler is not found + + Looks up a handler by type reference or its fully + qualified name. If a direct match + is not found, the search is performed over all + handlers registered with base=True. + """ + handler = self._handlers.get(cls_or_name) + # attempt to find a base class + if handler is None and util.is_type(cls_or_name): + for cls, base_handler in self._base_handlers.items(): + if issubclass(cls_or_name, cls): + return base_handler + return default if handler is None else handler + + def register(self, cls, handler=None, base=False): + """Register the a custom handler for a class + + :param cls: The custom object class to handle + :param handler: The custom handler class (if + None, a decorator wrapper is returned) + :param base: Indicates whether the handler should + be registered for all subclasses + + This function can be also used as a decorator + by omitting the `handler` argument:: + + @jsonpickle.handlers.register(Foo, base=True) + class FooHandler(jsonpickle.handlers.BaseHandler): + pass + + """ + if handler is None: + + def _register(handler_cls): + self.register(cls, handler=handler_cls, base=base) + return handler_cls + + return _register + if not util.is_type(cls): + raise TypeError('{!r} is not a class/type'.format(cls)) + # store both the name and the actual type for the ugly cases like + # _sre.SRE_Pattern that cannot be loaded back directly + self._handlers[util.importable_name(cls)] = self._handlers[cls] = handler + if base: + # only store the actual type for subclass checking + self._base_handlers[cls] = handler + + def unregister(self, cls): + self._handlers.pop(cls, None) + self._handlers.pop(util.importable_name(cls), None) + self._base_handlers.pop(cls, None) + + +registry = Registry() +register = registry.register +unregister = registry.unregister +get = registry.get + + +class BaseHandler(object): + def __init__(self, context): + """ + Initialize a new handler to handle a registered type. + + :Parameters: + - `context`: reference to pickler/unpickler + + """ + self.context = context + + def __call__(self, context): + """This permits registering either Handler instances or classes + + :Parameters: + - `context`: reference to pickler/unpickler + """ + self.context = context + return self + + def flatten(self, obj, data): + """ + Flatten `obj` into a json-friendly form and write result to `data`. + + :param object obj: The object to be serialized. + :param dict data: A partially filled dictionary which will contain the + json-friendly representation of `obj` once this method has + finished. + """ + raise NotImplementedError('You must implement flatten() in %s' % self.__class__) + + def restore(self, obj): + """ + Restore an object of the registered type from the json-friendly + representation `obj` and return it. + """ + raise NotImplementedError('You must implement restore() in %s' % self.__class__) + + @classmethod + def handles(self, cls): + """ + Register this handler for the given class. Suitable as a decorator, + e.g.:: + + @MyCustomHandler.handles + class MyCustomClass: + def __reduce__(self): + ... + """ + registry.register(cls, self) + return cls + + +class ArrayHandler(BaseHandler): + """Flatten and restore array.array objects""" + + def flatten(self, obj, data): + data['typecode'] = obj.typecode + data['values'] = self.context.flatten(obj.tolist(), reset=False) + return data + + def restore(self, data): + typecode = data['typecode'] + values = self.context.restore(data['values'], reset=False) + if typecode == 'c': + values = [bytes(x) for x in values] + return array.array(typecode, values) + + +ArrayHandler.handles(array.array) + + +class DatetimeHandler(BaseHandler): + + """Custom handler for datetime objects + + Datetime objects use __reduce__, and they generate binary strings encoding + the payload. This handler encodes that payload to reconstruct the + object. + + """ + + def flatten(self, obj, data): + pickler = self.context + if not pickler.unpicklable: + if hasattr(obj, 'isoformat'): + result = obj.isoformat() + else: + result = compat.ustr(obj) + return result + cls, args = obj.__reduce__() + flatten = pickler.flatten + payload = util.b64encode(args[0]) + args = [payload] + [flatten(i, reset=False) for i in args[1:]] + data['__reduce__'] = (flatten(cls, reset=False), args) + return data + + def restore(self, data): + cls, args = data['__reduce__'] + unpickler = self.context + restore = unpickler.restore + cls = restore(cls, reset=False) + value = util.b64decode(args[0]) + params = (value,) + tuple([restore(i, reset=False) for i in args[1:]]) + return cls.__new__(cls, *params) + + +DatetimeHandler.handles(datetime.datetime) +DatetimeHandler.handles(datetime.date) +DatetimeHandler.handles(datetime.time) + + +class RegexHandler(BaseHandler): + """Flatten _sre.SRE_Pattern (compiled regex) objects""" + + def flatten(self, obj, data): + data['pattern'] = obj.pattern + return data + + def restore(self, data): + return re.compile(data['pattern']) + + +RegexHandler.handles(type(re.compile(''))) + + +class QueueHandler(BaseHandler): + """Opaquely serializes Queue objects + + Queues contains mutex and condition variables which cannot be serialized. + Construct a new Queue instance when restoring. + + """ + + def flatten(self, obj, data): + return data + + def restore(self, data): + return compat.queue.Queue() + + +QueueHandler.handles(compat.queue.Queue) + + +class CloneFactory(object): + """Serialization proxy for collections.defaultdict's default_factory""" + + def __init__(self, exemplar): + self.exemplar = exemplar + + def __call__(self, clone=copy.copy): + """Create new instances by making copies of the provided exemplar""" + return clone(self.exemplar) + + def __repr__(self): + return ''.format(id(self), self.exemplar) + + +class UUIDHandler(BaseHandler): + """Serialize uuid.UUID objects""" + + def flatten(self, obj, data): + data['hex'] = obj.hex + return data + + def restore(self, data): + return uuid.UUID(data['hex']) + + +UUIDHandler.handles(uuid.UUID) + + +class LockHandler(BaseHandler): + """Serialize threading.Lock objects""" + + def flatten(self, obj, data): + data['locked'] = obj.locked() + return data + + def restore(self, data): + lock = threading.Lock() + if data.get('locked', False): + lock.acquire() + return lock + + +_lock = threading.Lock() +LockHandler.handles(_lock.__class__) + + +class TextIOHandler(BaseHandler): + """Serialize file descriptors as None because we cannot roundtrip""" + + def flatten(self, obj, data): + return None + + def restore(self, data): + """Restore should never get called because flatten() returns None""" + raise AssertionError('Restoring IO.TextIOHandler is not supported') + + +if sys.version_info >= (3, 8): + TextIOHandler.handles(io.TextIOWrapper) diff --git a/intelliaide/vendor/jsonpickle/pickler.py b/intelliaide/vendor/jsonpickle/pickler.py new file mode 100644 index 0000000..a61980f --- /dev/null +++ b/intelliaide/vendor/jsonpickle/pickler.py @@ -0,0 +1,758 @@ +# Copyright (C) 2008 John Paulett (john -at- paulett.org) +# Copyright (C) 2009-2018 David Aguilar (davvid -at- gmail.com) +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. +from __future__ import absolute_import, division, unicode_literals +import decimal +import warnings +import sys +import types +from itertools import chain, islice + +from . import compat +from . import util +from . import tags +from . import handlers +from .backend import json +from .compat import numeric_types, string_types, PY3, PY2 + + +def encode( + value, + unpicklable=True, + make_refs=True, + keys=False, + max_depth=None, + reset=True, + backend=None, + warn=False, + context=None, + max_iter=None, + use_decimal=False, + numeric_keys=False, + use_base85=False, + fail_safe=None, + indent=None, + separators=None, +): + """Return a JSON formatted representation of value, a Python object. + + :param unpicklable: If set to False then the output will not contain the + information necessary to turn the JSON data back into Python objects, + but a simpler JSON stream is produced. + :param max_depth: If set to a non-negative integer then jsonpickle will + not recurse deeper than 'max_depth' steps into the object. Anything + deeper than 'max_depth' is represented using a Python repr() of the + object. + :param make_refs: If set to False jsonpickle's referencing support is + disabled. Objects that are id()-identical won't be preserved across + encode()/decode(), but the resulting JSON stream will be conceptually + simpler. jsonpickle detects cyclical objects and will break the cycle + by calling repr() instead of recursing when make_refs is set False. + :param keys: If set to True then jsonpickle will encode non-string + dictionary keys instead of coercing them into strings via `repr()`. + This is typically what you want if you need to support Integer or + objects as dictionary keys. + :param numeric_keys: Only use this option if the backend supports integer + dict keys natively. This flag tells jsonpickle to leave numeric keys + as-is rather than conforming them to json-friendly strings. + Using ``keys=True`` is the typical solution for integer keys, so only + use this if you have a specific use case where you want to allow the + backend to handle serialization of numeric dict keys. + :param warn: If set to True then jsonpickle will warn when it + returns None for an object which it cannot pickle + (e.g. file descriptors). + :param max_iter: If set to a non-negative integer then jsonpickle will + consume at most `max_iter` items when pickling iterators. + :param use_decimal: If set to True jsonpickle will allow Decimal + instances to pass-through, with the assumption that the simplejson + backend will be used in `use_decimal` mode. In order to use this mode + you will need to configure simplejson:: + + jsonpickle.set_encoder_options('simplejson', + use_decimal=True, sort_keys=True) + jsonpickle.set_decoder_options('simplejson', + use_decimal=True) + jsonpickle.set_preferred_backend('simplejson') + + NOTE: A side-effect of the above settings is that float values will be + converted to Decimal when converting to json. + :param use_base85: + If possible, use base85 to encode binary data. Base85 bloats binary data + by 1/4 as opposed to base64, which expands it by 1/3. This argument is + ignored on Python 2 because it doesn't support it. + :param fail_safe: If set to a function exceptions are ignored when pickling + and if a exception happens the function is called and the return value + is used as the value for the object that caused the error + :param indent: When `indent` is a non-negative integer, then JSON array + elements and object members will be pretty-printed with that indent + level. An indent level of 0 will only insert newlines. ``None`` is + the most compact representation. Since the default item separator is + ``(', ', ': ')``, the output might include trailing whitespace when + ``indent`` is specified. You can use ``separators=(',', ': ')`` to + avoid this. This value is passed directly to the active JSON backend + library and not used by jsonpickle directly. + :param separators: + If ``separators`` is an ``(item_separator, dict_separator)`` tuple + then it will be used instead of the default ``(', ', ': ')`` + separators. ``(',', ':')`` is the most compact JSON representation. + This value is passed directly to the active JSON backend library and + not used by jsonpickle directly. + + >>> encode('my string') == '"my string"' + True + >>> encode(36) == '36' + True + >>> encode({'foo': True}) == '{"foo": true}' + True + >>> encode({'foo': [1, 2, [3, 4]]}, max_depth=1) + '{"foo": "[1, 2, [3, 4]]"}' + + """ + backend = backend or json + context = context or Pickler( + unpicklable=unpicklable, + make_refs=make_refs, + keys=keys, + backend=backend, + max_depth=max_depth, + warn=warn, + max_iter=max_iter, + numeric_keys=numeric_keys, + use_decimal=use_decimal, + use_base85=use_base85, + fail_safe=fail_safe, + ) + return backend.encode( + context.flatten(value, reset=reset), indent=indent, separators=separators + ) + + +class Pickler(object): + def __init__( + self, + unpicklable=True, + make_refs=True, + max_depth=None, + backend=None, + keys=False, + warn=False, + max_iter=None, + numeric_keys=False, + use_decimal=False, + use_base85=False, + fail_safe=None, + ): + self.unpicklable = unpicklable + self.make_refs = make_refs + self.backend = backend or json + self.keys = keys + self.warn = warn + self.numeric_keys = numeric_keys + self.use_base85 = use_base85 and (not PY2) + # The current recursion depth + self._depth = -1 + # The maximal recursion depth + self._max_depth = max_depth + # Maps id(obj) to reference IDs + self._objs = {} + # Avoids garbage collection + self._seen = [] + # maximum amount of items to take from a pickled iterator + self._max_iter = max_iter + # Whether to allow decimals to pass-through + self._use_decimal = use_decimal + # A cache of objects that have already been flattened. + self._flattened = {} + + if self.use_base85: + self._bytes_tag = tags.B85 + self._bytes_encoder = util.b85encode + else: + self._bytes_tag = tags.B64 + self._bytes_encoder = util.b64encode + + # ignore exceptions + self.fail_safe = fail_safe + + def reset(self): + self._objs = {} + self._depth = -1 + self._seen = [] + self._flattened = {} + + def _push(self): + """Steps down one level in the namespace.""" + self._depth += 1 + + def _pop(self, value): + """Step up one level in the namespace and return the value. + If we're at the root, reset the pickler's state. + """ + self._depth -= 1 + if self._depth == -1: + self.reset() + return value + + def _log_ref(self, obj): + """ + Log a reference to an in-memory object. + Return True if this object is new and was assigned + a new ID. Otherwise return False. + """ + objid = id(obj) + is_new = objid not in self._objs + if is_new: + new_id = len(self._objs) + self._objs[objid] = new_id + return is_new + + def _mkref(self, obj): + """ + Log a reference to an in-memory object, and return + if that object should be considered newly logged. + """ + is_new = self._log_ref(obj) + # Pretend the object is new + pretend_new = not self.unpicklable or not self.make_refs + return pretend_new or is_new + + def _getref(self, obj): + return {tags.ID: self._objs.get(id(obj))} + + def flatten(self, obj, reset=True): + """Takes an object and returns a JSON-safe representation of it. + + Simply returns any of the basic builtin datatypes + + >>> p = Pickler() + >>> p.flatten('hello world') == 'hello world' + True + >>> p.flatten(49) + 49 + >>> p.flatten(350.0) + 350.0 + >>> p.flatten(True) + True + >>> p.flatten(False) + False + >>> r = p.flatten(None) + >>> r is None + True + >>> p.flatten(False) + False + >>> p.flatten([1, 2, 3, 4]) + [1, 2, 3, 4] + >>> p.flatten((1,2,))[tags.TUPLE] + [1, 2] + >>> p.flatten({'key': 'value'}) == {'key': 'value'} + True + """ + if reset: + self.reset() + return self._flatten(obj) + + def _flatten(self, obj): + if self.unpicklable and self.make_refs: + result = self._flatten_impl(obj) + else: + try: + result = self._flattened[id(obj)] + except KeyError: + result = self._flattened[id(obj)] = self._flatten_impl(obj) + return result + + def _flatten_impl(self, obj): + ######################################### + # if obj is nonrecursive return immediately + # for performance reasons we don't want to do recursive checks + if PY2 and isinstance(obj, types.FileType): + return self._flatten_file(obj) + + if type(obj) is bytes: + return self._flatten_bytestring(obj) + + # Decimal is a primitive when use_decimal is True + if type(obj) in util.PRIMITIVES or ( + self._use_decimal and isinstance(obj, decimal.Decimal) + ): + return obj + ######################################### + + self._push() + return self._pop(self._flatten_obj(obj)) + + def _max_reached(self): + return self._depth == self._max_depth + + def _flatten_obj(self, obj): + self._seen.append(obj) + + max_reached = self._max_reached() + + try: + + in_cycle = _in_cycle(obj, self._objs, max_reached, self.make_refs) + if in_cycle: + # break the cycle + flatten_func = repr + else: + flatten_func = self._get_flattener(obj) + + if flatten_func is None: + self._pickle_warning(obj) + return None + + return flatten_func(obj) + + except (KeyboardInterrupt, SystemExit) as e: + raise e + except Exception as e: + if self.fail_safe is None: + raise e + else: + return self.fail_safe(e) + + def _list_recurse(self, obj): + return [self._flatten(v) for v in obj] + + def _get_flattener(self, obj): + if type(obj) is list: + if self._mkref(obj): + return self._list_recurse + else: + self._push() + return self._getref + + elif type(obj) is dict: + if self._mkref(obj): + return self._flatten_dict_obj + else: + self._push() + return self._getref + + # We handle tuples and sets by encoding them in a "(tuple|set)dict" + elif type(obj) is tuple: + if not self.unpicklable: + return self._list_recurse + return lambda obj: {tags.TUPLE: [self._flatten(v) for v in obj]} + + elif type(obj) is set: + if not self.unpicklable: + return self._list_recurse + return lambda obj: {tags.SET: [self._flatten(v) for v in obj]} + + elif util.is_object(obj): + return self._ref_obj_instance + + elif util.is_type(obj): + return _mktyperef + + elif util.is_module_function(obj): + return self._flatten_function + + # instance methods, lambdas, old style classes... + self._pickle_warning(obj) + return None + + def _ref_obj_instance(self, obj): + """Reference an existing object or flatten if new""" + if self.unpicklable: + if self._mkref(obj): + # We've never seen this object so return its + # json representation. + return self._flatten_obj_instance(obj) + # We've seen this object before so place an object + # reference tag in the data. This avoids infinite recursion + # when processing cyclical objects. + return self._getref(obj) + else: + max_reached = self._max_reached() + in_cycle = _in_cycle(obj, self._objs, max_reached, False) + if in_cycle: + # A circular becomes None. + return None + + self._mkref(obj) + return self._flatten_obj_instance(obj) + + def _flatten_file(self, obj): + """ + Special case file objects + """ + assert not PY3 and isinstance(obj, types.FileType) + return None + + def _flatten_bytestring(self, obj): + if PY2: + try: + return obj.decode('utf-8') + except UnicodeDecodeError: + pass + return {self._bytes_tag: self._bytes_encoder(obj)} + + def _flatten_obj_instance(self, obj): + """Recursively flatten an instance and return a json-friendly dict""" + data = {} + has_class = hasattr(obj, '__class__') + has_dict = hasattr(obj, '__dict__') + has_slots = not has_dict and hasattr(obj, '__slots__') + has_getnewargs = util.has_method(obj, '__getnewargs__') + has_getnewargs_ex = util.has_method(obj, '__getnewargs_ex__') + has_getinitargs = util.has_method(obj, '__getinitargs__') + has_reduce, has_reduce_ex = util.has_reduce(obj) + + # Support objects with __getstate__(); this ensures that + # both __setstate__() and __getstate__() are implemented + has_getstate = hasattr(obj, '__getstate__') + # not using has_method since __getstate__() is handled separately below + + if has_class: + cls = obj.__class__ + else: + cls = type(obj) + + # Check for a custom handler + class_name = util.importable_name(cls) + handler = handlers.get(cls, handlers.get(class_name)) + if handler is not None: + if self.unpicklable: + data[tags.OBJECT] = class_name + return handler(self).flatten(obj, data) + + reduce_val = None + + if self.unpicklable: + if has_reduce and not has_reduce_ex: + try: + reduce_val = obj.__reduce__() + except TypeError: + # A lot of builtin types have a reduce which + # just raises a TypeError + # we ignore those + pass + + # test for a reduce implementation, and redirect before + # doing anything else if that is what reduce requests + elif has_reduce_ex: + try: + # we're implementing protocol 2 + reduce_val = obj.__reduce_ex__(2) + except TypeError: + # A lot of builtin types have a reduce which + # just raises a TypeError + # we ignore those + pass + + if reduce_val and isinstance(reduce_val, string_types): + try: + varpath = iter(reduce_val.split('.')) + # curmod will be transformed by the + # loop into the value to pickle + curmod = sys.modules[next(varpath)] + for modname in varpath: + curmod = getattr(curmod, modname) + # replace obj with value retrieved + return self._flatten(curmod) + except KeyError: + # well, we can't do anything with that, so we ignore it + pass + + elif reduce_val: + # at this point, reduce_val should be some kind of iterable + # pad out to len 5 + rv_as_list = list(reduce_val) + insufficiency = 5 - len(rv_as_list) + if insufficiency: + rv_as_list += [None] * insufficiency + + if getattr(rv_as_list[0], '__name__', '') == '__newobj__': + rv_as_list[0] = tags.NEWOBJ + + f, args, state, listitems, dictitems = rv_as_list + + # check that getstate/setstate is sane + if not ( + state + and hasattr(obj, '__getstate__') + and not hasattr(obj, '__setstate__') + and not isinstance(obj, dict) + ): + # turn iterators to iterables for convenient serialization + if rv_as_list[3]: + rv_as_list[3] = tuple(rv_as_list[3]) + + if rv_as_list[4]: + rv_as_list[4] = tuple(rv_as_list[4]) + + reduce_args = list(map(self._flatten, rv_as_list)) + last_index = len(reduce_args) - 1 + while last_index >= 2 and reduce_args[last_index] is None: + last_index -= 1 + data[tags.REDUCE] = reduce_args[: last_index + 1] + + return data + + if has_class and not util.is_module(obj): + if self.unpicklable: + data[tags.OBJECT] = class_name + + if has_getnewargs_ex: + data[tags.NEWARGSEX] = list(map(self._flatten, obj.__getnewargs_ex__())) + + if has_getnewargs and not has_getnewargs_ex: + data[tags.NEWARGS] = self._flatten(obj.__getnewargs__()) + + if has_getinitargs: + data[tags.INITARGS] = self._flatten(obj.__getinitargs__()) + + if has_getstate: + try: + state = obj.__getstate__() + except TypeError: + # Has getstate but it cannot be called, e.g. file descriptors + # in Python3 + self._pickle_warning(obj) + return None + else: + return self._getstate(state, data) + + if util.is_module(obj): + if self.unpicklable: + data[tags.REPR] = '{name}/{name}'.format(name=obj.__name__) + else: + data = compat.ustr(obj) + return data + + if util.is_dictionary_subclass(obj): + self._flatten_dict_obj(obj, data) + return data + + if util.is_sequence_subclass(obj): + return self._flatten_sequence_obj(obj, data) + + if util.is_iterator(obj): + # force list in python 3 + data[tags.ITERATOR] = list(map(self._flatten, islice(obj, self._max_iter))) + return data + + if has_dict: + # Support objects that subclasses list and set + if util.is_sequence_subclass(obj): + return self._flatten_sequence_obj(obj, data) + + # hack for zope persistent objects; this unghostifies the object + getattr(obj, '_', None) + return self._flatten_dict_obj(obj.__dict__, data) + + if has_slots: + return self._flatten_newstyle_with_slots(obj, data) + + # catchall return for data created above without a return + # (e.g. __getnewargs__ is not supposed to be the end of the story) + if data: + return data + + self._pickle_warning(obj) + return None + + def _flatten_function(self, obj): + if self.unpicklable: + data = {tags.FUNCTION: util.importable_name(obj)} + else: + data = None + + return data + + def _flatten_dict_obj(self, obj, data=None): + """Recursively call flatten() and return json-friendly dict""" + if data is None: + data = obj.__class__() + + # If we allow non-string keys then we have to do a two-phase + # encoding to ensure that the reference IDs are deterministic. + if self.keys: + # Phase 1: serialize regular objects, ignore fancy keys. + flatten = self._flatten_string_key_value_pair + for k, v in util.items(obj): + flatten(k, v, data) + + # Phase 2: serialize non-string keys. + flatten = self._flatten_non_string_key_value_pair + for k, v in util.items(obj): + flatten(k, v, data) + else: + # If we have string keys only then we only need a single pass. + flatten = self._flatten_key_value_pair + for k, v in util.items(obj): + flatten(k, v, data) + + # the collections.defaultdict protocol + if hasattr(obj, 'default_factory') and callable(obj.default_factory): + factory = obj.default_factory + if util.is_type(factory): + # Reference the class/type + value = _mktyperef(factory) + else: + # The factory is not a type and could reference e.g. functions + # or even the object instance itself, which creates a cycle. + if self._mkref(factory): + # We've never seen this object before so pickle it in-place. + # Create an instance from the factory and assume that the + # resulting instance is a suitable examplar. + value = self._flatten_obj_instance(handlers.CloneFactory(factory())) + else: + # We've seen this object before. + # Break the cycle by emitting a reference. + value = self._getref(factory) + data['default_factory'] = value + + # Sub-classes of dict + if hasattr(obj, '__dict__') and self.unpicklable: + dict_data = {} + self._flatten_dict_obj(obj.__dict__, dict_data) + data['__dict__'] = dict_data + + return data + + def _flatten_obj_attrs(self, obj, attrs, data): + flatten = self._flatten_key_value_pair + ok = False + for k in attrs: + try: + value = getattr(obj, k) + flatten(k, value, data) + except AttributeError: + # The attribute may have been deleted + continue + ok = True + return ok + + def _flatten_newstyle_with_slots(self, obj, data): + """Return a json-friendly dict for new-style objects with __slots__.""" + allslots = [ + _wrap_string_slot(getattr(cls, '__slots__', tuple())) + for cls in obj.__class__.mro() + ] + + if not self._flatten_obj_attrs(obj, chain(*allslots), data): + attrs = [ + x for x in dir(obj) if not x.startswith('__') and not x.endswith('__') + ] + self._flatten_obj_attrs(obj, attrs, data) + + return data + + def _flatten_key_value_pair(self, k, v, data): + """Flatten a key/value pair into the passed-in dictionary.""" + if not util.is_picklable(k, v): + return data + + if k is None: + k = 'null' # for compatibility with common json encoders + + if self.numeric_keys and isinstance(k, numeric_types): + pass + elif not isinstance(k, string_types): + try: + k = repr(k) + except Exception: + k = compat.ustr(k) + + data[k] = self._flatten(v) + return data + + def _flatten_non_string_key_value_pair(self, k, v, data): + """Flatten only non-string key/value pairs""" + if not util.is_picklable(k, v): + return data + if self.keys and not isinstance(k, string_types): + k = self._escape_key(k) + data[k] = self._flatten(v) + return data + + def _flatten_string_key_value_pair(self, k, v, data): + """Flatten string key/value pairs only.""" + if not util.is_picklable(k, v): + return data + if self.keys: + if not isinstance(k, string_types): + return data + elif k.startswith(tags.JSON_KEY): + k = self._escape_key(k) + else: + if k is None: + k = 'null' # for compatibility with common json encoders + + if self.numeric_keys and isinstance(k, numeric_types): + pass + elif not isinstance(k, string_types): + try: + k = repr(k) + except Exception: + k = compat.ustr(k) + + data[k] = self._flatten(v) + return data + + def _flatten_sequence_obj(self, obj, data): + """Return a json-friendly dict for a sequence subclass.""" + if hasattr(obj, '__dict__'): + self._flatten_dict_obj(obj.__dict__, data) + value = [self._flatten(v) for v in obj] + if self.unpicklable: + data[tags.SEQ] = value + else: + return value + return data + + def _escape_key(self, k): + return tags.JSON_KEY + encode( + k, + reset=False, + keys=True, + context=self, + backend=self.backend, + make_refs=self.make_refs, + ) + + def _getstate(self, obj, data): + state = self._flatten(obj) + if self.unpicklable: + data[tags.STATE] = state + else: + data = state + return data + + def _pickle_warning(self, obj): + if self.warn: + msg = 'jsonpickle cannot pickle %r: replaced with None' % obj + warnings.warn(msg) + + +def _in_cycle(obj, objs, max_reached, make_refs): + """Detect cyclic structures that would lead to infinite recursion""" + return ( + (max_reached or (not make_refs and id(obj) in objs)) + and not util.is_primitive(obj) + and not util.is_enum(obj) + ) + + +def _mktyperef(obj): + """Return a typeref dictionary + + >>> _mktyperef(AssertionError) == {'py/type': 'builtins.AssertionError'} + True + + """ + return {tags.TYPE: util.importable_name(obj)} + + +def _wrap_string_slot(string): + """Converts __slots__ = 'a' into __slots__ = ('a',)""" + if isinstance(string, string_types): + return (string,) + return string diff --git a/intelliaide/vendor/jsonpickle/tags.py b/intelliaide/vendor/jsonpickle/tags.py new file mode 100644 index 0000000..6c02638 --- /dev/null +++ b/intelliaide/vendor/jsonpickle/tags.py @@ -0,0 +1,52 @@ +"""The jsonpickle.tags module provides the custom tags +used for pickling and unpickling Python objects. + +These tags are keys into the flattened dictionaries +created by the Pickler class. The Unpickler uses +these custom key names to identify dictionaries +that need to be specially handled. +""" +from __future__ import absolute_import, division, unicode_literals + + +BYTES = 'py/bytes' +B64 = 'py/b64' +B85 = 'py/b85' +FUNCTION = 'py/function' +ID = 'py/id' +INITARGS = 'py/initargs' +ITERATOR = 'py/iterator' +JSON_KEY = 'json://' +NEWARGS = 'py/newargs' +NEWARGSEX = 'py/newargsex' +NEWOBJ = 'py/newobj' +OBJECT = 'py/object' +REDUCE = 'py/reduce' +REF = 'py/ref' +REPR = 'py/repr' +SEQ = 'py/seq' +SET = 'py/set' +STATE = 'py/state' +TUPLE = 'py/tuple' +TYPE = 'py/type' + +# All reserved tag names +RESERVED = { + BYTES, + FUNCTION, + ID, + INITARGS, + ITERATOR, + NEWARGS, + NEWARGSEX, + NEWOBJ, + OBJECT, + REDUCE, + REF, + REPR, + SEQ, + SET, + STATE, + TUPLE, + TYPE, +} diff --git a/intelliaide/vendor/jsonpickle/unpickler.py b/intelliaide/vendor/jsonpickle/unpickler.py new file mode 100644 index 0000000..b3e1326 --- /dev/null +++ b/intelliaide/vendor/jsonpickle/unpickler.py @@ -0,0 +1,782 @@ +# Copyright (C) 2008 John Paulett (john -at- paulett.org) +# Copyright (C) 2009-2018 David Aguilar (davvid -at- gmail.com) +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. +from __future__ import absolute_import, division, unicode_literals +import quopri +import sys + +from . import compat +from . import util +from . import tags +from . import handlers +from .compat import numeric_types +from .backend import json + + +def decode( + string, backend=None, context=None, keys=False, reset=True, safe=False, classes=None +): + """Convert a JSON string into a Python object. + + The keyword argument 'keys' defaults to False. + If set to True then jsonpickle will decode non-string dictionary keys + into python objects via the jsonpickle protocol. + + The keyword argument 'classes' defaults to None. + If set to a single class, or a sequence (list, set, tuple) of classes, + then the classes will be made available when constructing objects. This + can be used to give jsonpickle access to local classes that are not + available through the global module import scope. + + The keyword argument 'safe' defaults to False. + If set to True, eval() is avoided, but backwards-compatible + (pre-0.7.0) deserialization of repr-serialized objects is disabled. + + The keyword argument 'backend' defaults to None. + If set to an instance of jsonpickle.backend.JSONBackend, jsonpickle + will use that backend for deserialization. + + >>> decode('"my string"') == 'my string' + True + >>> decode('36') + 36 + """ + backend = backend or json + context = context or Unpickler(keys=keys, backend=backend, safe=safe) + data = backend.decode(string) + return context.restore(data, reset=reset, classes=classes) + + +def _safe_hasattr(obj, attr): + """Workaround unreliable hasattr() availability on sqlalchemy objects""" + try: + object.__getattribute__(obj, attr) + return True + except AttributeError: + return False + + +def _is_json_key(key): + """Has this key a special object that has been encoded to JSON?""" + return isinstance(key, compat.string_types) and key.startswith(tags.JSON_KEY) + + +class _Proxy(object): + """Proxies are dummy objects that are later replaced by real instances + + The `restore()` function has to solve a tricky problem when pickling + objects with cyclical references -- the parent instance does not yet + exist. + + The problem is that `__getnewargs__()`, `__getstate__()`, custom handlers, + and cyclical objects graphs are allowed to reference the yet-to-be-created + object via the referencing machinery. + + In other words, objects are allowed to depend on themselves for + construction! + + We solve this problem by placing dummy Proxy objects into the referencing + machinery so that we can construct the child objects before constructing + the parent. Objects are initially created with Proxy attribute values + instead of real references. + + We collect all objects that contain references to proxies and run + a final sweep over them to swap in the real instance. This is done + at the very end of the top-level `restore()`. + + The `instance` attribute below is replaced with the real instance + after `__new__()` has been used to construct the object and is used + when swapping proxies with real instances. + + """ + + def __init__(self): + self.instance = None + + def get(self): + return self.instance + + def reset(self, instance): + self.instance = instance + + +class _IDProxy(_Proxy): + def __init__(self, objs, index): + self._index = index + self._objs = objs + + def get(self): + return self._objs[self._index] + + +def _obj_setattr(obj, attr, proxy): + setattr(obj, attr, proxy.get()) + + +def _obj_setvalue(obj, idx, proxy): + obj[idx] = proxy.get() + + +class Unpickler(object): + def __init__(self, backend=None, keys=False, safe=False): + self.backend = backend or json + self.keys = keys + self.safe = safe + + self.reset() + + def reset(self): + """Resets the object's internal state.""" + # Map reference names to object instances + self._namedict = {} + + # The stack of names traversed for child objects + self._namestack = [] + + # Map of objects to their index in the _objs list + self._obj_to_idx = {} + self._objs = [] + self._proxies = [] + + # Extra local classes not accessible globally + self._classes = {} + + def restore(self, obj, reset=True, classes=None): + """Restores a flattened object to its original python state. + + Simply returns any of the basic builtin types + + >>> u = Unpickler() + >>> u.restore('hello world') == 'hello world' + True + >>> u.restore({'key': 'value'}) == {'key': 'value'} + True + + """ + if reset: + self.reset() + if classes: + self.register_classes(classes) + value = self._restore(obj) + if reset: + self._swap_proxies() + return value + + def register_classes(self, classes): + """Register one or more classes + + :param classes: sequence of classes or a single class to register + + """ + if isinstance(classes, (list, tuple, set)): + for cls in classes: + self.register_classes(cls) + else: + self._classes[util.importable_name(classes)] = classes + + def _swap_proxies(self): + """Replace proxies with their corresponding instances""" + for (obj, attr, proxy, method) in self._proxies: + method(obj, attr, proxy) + self._proxies = [] + + def _restore(self, obj): + # if obj isn't in these types, neither it nor nothing in it can have a tag + # don't change the tuple of types to a set, it won't work with isinstance + if not isinstance(obj, (str, list, dict, set, tuple)): + + def restore(x): + return x + + else: + restore = self._restore_tags(obj) + return restore(obj) + + def _restore_tags(self, obj): + try: + if not tags.RESERVED <= set(obj) and not type(obj) in (list, dict): + + def restore(x): + return x + + return restore + except TypeError: + pass + if has_tag(obj, tags.B64): + restore = self._restore_base64 + elif has_tag(obj, tags.B85): + restore = self._restore_base85 + elif has_tag(obj, tags.ID): + restore = self._restore_id + elif has_tag(obj, tags.ITERATOR): + restore = self._restore_iterator + elif has_tag(obj, tags.TYPE): + restore = self._restore_type + elif has_tag(obj, tags.REDUCE): + restore = self._restore_reduce + elif has_tag(obj, tags.OBJECT): + restore = self._restore_object + elif has_tag(obj, tags.FUNCTION): + restore = self._restore_function + elif has_tag(obj, tags.BYTES): # Backwards compatibility + restore = self._restore_quopri + elif has_tag(obj, tags.REF): # Backwards compatibility + restore = self._restore_ref + elif has_tag(obj, tags.REPR): # Backwards compatibility + restore = self._restore_repr + elif util.is_list(obj): + restore = self._restore_list + elif has_tag(obj, tags.TUPLE): + restore = self._restore_tuple + elif has_tag(obj, tags.SET): + restore = self._restore_set + elif util.is_dictionary(obj): + restore = self._restore_dict + else: + + def restore(x): + return x + + return restore + + def _restore_base64(self, obj): + return util.b64decode(obj[tags.B64].encode('utf-8')) + + def _restore_base85(self, obj): + return util.b85decode(obj[tags.B85].encode('utf-8')) + + #: For backwards compatibility with bytes data produced by older versions + def _restore_quopri(self, obj): + return quopri.decodestring(obj[tags.BYTES].encode('utf-8')) + + def _restore_iterator(self, obj): + return iter(self._restore_list(obj[tags.ITERATOR])) + + def _restore_reduce(self, obj): + """ + Supports restoring with all elements of __reduce__ as per pep 307. + Assumes that iterator items (the last two) are represented as lists + as per pickler implementation. + """ + proxy = _Proxy() + self._mkref(proxy) + reduce_val = list(map(self._restore, obj[tags.REDUCE])) + if len(reduce_val) < 5: + reduce_val.extend([None] * (5 - len(reduce_val))) + f, args, state, listitems, dictitems = reduce_val + + if f == tags.NEWOBJ or getattr(f, '__name__', '') == '__newobj__': + # mandated special case + cls = args[0] + if not isinstance(cls, type): + cls = self._restore(cls) + stage1 = cls.__new__(cls, *args[1:]) + else: + stage1 = f(*args) + + if state: + try: + stage1.__setstate__(state) + except AttributeError: + # it's fine - we'll try the prescribed default methods + try: + # we can't do a straight update here because we + # need object identity of the state dict to be + # preserved so that _swap_proxies works out + for k, v in stage1.__dict__.items(): + state.setdefault(k, v) + stage1.__dict__ = state + except AttributeError: + # next prescribed default + try: + for k, v in state.items(): + setattr(stage1, k, v) + except Exception: + dict_state, slots_state = state + if dict_state: + stage1.__dict__.update(dict_state) + if slots_state: + for k, v in slots_state.items(): + setattr(stage1, k, v) + + if listitems: + # should be lists if not None + try: + stage1.extend(listitems) + except AttributeError: + for x in listitems: + stage1.append(x) + + if dictitems: + for k, v in dictitems: + stage1.__setitem__(k, v) + + proxy.reset(stage1) + self._swapref(proxy, stage1) + return stage1 + + def _restore_id(self, obj): + try: + idx = obj[tags.ID] + return self._objs[idx] + except IndexError: + return _IDProxy(self._objs, idx) + + def _restore_ref(self, obj): + return self._namedict.get(obj[tags.REF]) + + def _restore_type(self, obj): + typeref = loadclass(obj[tags.TYPE], classes=self._classes) + if typeref is None: + return obj + return typeref + + def _restore_repr(self, obj): + if self.safe: + # eval() is not allowed in safe mode + return None + obj = loadrepr(obj[tags.REPR]) + return self._mkref(obj) + + def _restore_object(self, obj): + class_name = obj[tags.OBJECT] + cls = loadclass(class_name, classes=self._classes) + handler = handlers.get(cls, handlers.get(class_name)) + if handler is not None: # custom handler + proxy = _Proxy() + self._mkref(proxy) + instance = handler(self).restore(obj) + proxy.reset(instance) + self._swapref(proxy, instance) + return instance + + if cls is None: + return self._mkref(obj) + + return self._restore_object_instance(obj, cls) + + def _restore_function(self, obj): + return loadclass(obj[tags.FUNCTION], classes=self._classes) + + def _loadfactory(self, obj): + try: + default_factory = obj['default_factory'] + except KeyError: + return None + del obj['default_factory'] + return self._restore(default_factory) + + def _restore_object_instance(self, obj, cls): + # This is a placeholder proxy object which allows child objects to + # reference the parent object before it has been instantiated. + proxy = _Proxy() + self._mkref(proxy) + + # An object can install itself as its own factory, so load the factory + # after the instance is available for referencing. + factory = self._loadfactory(obj) + + if has_tag(obj, tags.NEWARGSEX): + args, kwargs = obj[tags.NEWARGSEX] + else: + args = getargs(obj, classes=self._classes) + kwargs = {} + if args: + args = self._restore(args) + if kwargs: + kwargs = self._restore(kwargs) + + is_oldstyle = not (isinstance(cls, type) or getattr(cls, '__meta__', None)) + try: + if (not is_oldstyle) and hasattr(cls, '__new__'): + # new style classes + if factory: + instance = cls.__new__(cls, factory, *args, **kwargs) + instance.default_factory = factory + else: + instance = cls.__new__(cls, *args, **kwargs) + else: + instance = object.__new__(cls) + except TypeError: # old-style classes + is_oldstyle = True + + if is_oldstyle: + try: + instance = cls(*args) + except TypeError: # fail gracefully + try: + instance = make_blank_classic(cls) + except Exception: # fail gracefully + return self._mkref(obj) + + proxy.reset(instance) + self._swapref(proxy, instance) + + if isinstance(instance, tuple): + return instance + + instance = self._restore_object_instance_variables(obj, instance) + + if _safe_hasattr(instance, 'default_factory') and isinstance( + instance.default_factory, _Proxy + ): + instance.default_factory = instance.default_factory.get() + + return instance + + def _restore_from_dict(self, obj, instance, ignorereserved=True): + restore_key = self._restore_key_fn() + method = _obj_setattr + deferred = {} + + for k, v in util.items(obj): + # ignore the reserved attribute + if ignorereserved and k in tags.RESERVED: + continue + if isinstance(k, numeric_types): + str_k = k.__str__() + else: + str_k = k + self._namestack.append(str_k) + k = restore_key(k) + # step into the namespace + value = self._restore(v) + if util.is_noncomplex(instance) or util.is_dictionary_subclass(instance): + try: + if k == '__dict__': + setattr(instance, k, value) + else: + instance[k] = value + except TypeError: + # Immutable object, must be constructed in one shot + if k != '__dict__': + deferred[k] = value + self._namestack.pop() + continue + else: + setattr(instance, k, value) + + # This instance has an instance variable named `k` that is + # currently a proxy and must be replaced + if isinstance(value, _Proxy): + self._proxies.append((instance, k, value, method)) + + # step out + self._namestack.pop() + + if deferred: + # SQLAlchemy Immutable mappings must be constructed in one shot + instance = instance.__class__(deferred) + + return instance + + def _restore_object_instance_variables(self, obj, instance): + instance = self._restore_from_dict(obj, instance) + + # Handle list and set subclasses + if has_tag(obj, tags.SEQ): + if hasattr(instance, 'append'): + for v in obj[tags.SEQ]: + instance.append(self._restore(v)) + elif hasattr(instance, 'add'): + for v in obj[tags.SEQ]: + instance.add(self._restore(v)) + + if has_tag(obj, tags.STATE): + instance = self._restore_state(obj, instance) + + return instance + + def _restore_state(self, obj, instance): + state = self._restore(obj[tags.STATE]) + has_slots = ( + isinstance(state, tuple) and len(state) == 2 and isinstance(state[1], dict) + ) + has_slots_and_dict = has_slots and isinstance(state[0], dict) + if hasattr(instance, '__setstate__'): + instance.__setstate__(state) + elif isinstance(state, dict): + # implements described default handling + # of state for object with instance dict + # and no slots + instance = self._restore_from_dict(state, instance, ignorereserved=False) + elif has_slots: + instance = self._restore_from_dict(state[1], instance, ignorereserved=False) + if has_slots_and_dict: + instance = self._restore_from_dict( + state[0], instance, ignorereserved=False + ) + elif not hasattr(instance, '__getnewargs__') and not hasattr( + instance, '__getnewargs_ex__' + ): + # __setstate__ is not implemented so that means that the best + # we can do is return the result of __getstate__() rather than + # return an empty shell of an object. + # However, if there were newargs, it's not an empty shell + instance = state + return instance + + def _restore_list(self, obj): + parent = [] + self._mkref(parent) + children = [self._restore(v) for v in obj] + parent.extend(children) + method = _obj_setvalue + proxies = [ + (parent, idx, value, method) + for idx, value in enumerate(parent) + if isinstance(value, _Proxy) + ] + self._proxies.extend(proxies) + return parent + + def _restore_tuple(self, obj): + return tuple([self._restore(v) for v in obj[tags.TUPLE]]) + + def _restore_set(self, obj): + return {self._restore(v) for v in obj[tags.SET]} + + def _restore_dict(self, obj): + data = {} + self._mkref(data) + + # If we are decoding dicts that can have non-string keys then we + # need to do a two-phase decode where the non-string keys are + # processed last. This ensures a deterministic order when + # assigning object IDs for references. + if self.keys: + # Phase 1: regular non-special keys. + for k, v in util.items(obj): + if _is_json_key(k): + continue + if isinstance(k, numeric_types): + str_k = k.__str__() + else: + str_k = k + self._namestack.append(str_k) + data[k] = self._restore(v) + + self._namestack.pop() + + # Phase 2: object keys only. + for k, v in util.items(obj): + if not _is_json_key(k): + continue + self._namestack.append(k) + + k = self._restore_pickled_key(k) + data[k] = result = self._restore(v) + # k is currently a proxy and must be replaced + if isinstance(result, _Proxy): + self._proxies.append((data, k, result, _obj_setvalue)) + + self._namestack.pop() + else: + # No special keys, thus we don't need to restore the keys either. + for k, v in util.items(obj): + if isinstance(k, numeric_types): + str_k = k.__str__() + else: + str_k = k + self._namestack.append(str_k) + data[k] = self._restore(v) + self._namestack.pop() + return data + + def _restore_key_fn(self): + """Return a callable that restores keys + + This function is responsible for restoring non-string keys + when we are decoding with `keys=True`. + + """ + # This function is called before entering a tight loop + # where the returned function will be called. + # We return a specific function after checking self.keys + # instead of doing so in the body of the function to + # avoid conditional branching inside a tight loop. + if self.keys: + restore_key = self._restore_pickled_key + else: + + def restore_key(key): + return key + + return restore_key + + def _restore_pickled_key(self, key): + """Restore a possibly pickled key""" + if _is_json_key(key): + key = decode( + key[len(tags.JSON_KEY) :], + backend=self.backend, + context=self, + keys=True, + reset=False, + ) + return key + + def _refname(self): + """Calculates the name of the current location in the JSON stack. + + This is called as jsonpickle traverses the object structure to + create references to previously-traversed objects. This allows + cyclical data structures such as doubly-linked lists. + jsonpickle ensures that duplicate python references to the same + object results in only a single JSON object definition and + special reference tags to represent each reference. + + >>> u = Unpickler() + >>> u._namestack = [] + >>> u._refname() == '/' + True + >>> u._namestack = ['a'] + >>> u._refname() == '/a' + True + >>> u._namestack = ['a', 'b'] + >>> u._refname() == '/a/b' + True + + """ + return '/' + '/'.join(self._namestack) + + def _mkref(self, obj): + obj_id = id(obj) + try: + self._obj_to_idx[obj_id] + except KeyError: + self._obj_to_idx[obj_id] = len(self._objs) + self._objs.append(obj) + # Backwards compatibility: old versions of jsonpickle + # produced "py/ref" references. + self._namedict[self._refname()] = obj + return obj + + def _swapref(self, proxy, instance): + proxy_id = id(proxy) + instance_id = id(instance) + + instance_index = self._obj_to_idx[proxy_id] + self._obj_to_idx[instance_id] = instance_index + del self._obj_to_idx[proxy_id] + + self._objs[instance_index] = instance + self._namedict[self._refname()] = instance + + +def loadclass(module_and_name, classes=None): + """Loads the module and returns the class. + + >>> cls = loadclass('datetime.datetime') + >>> cls.__name__ + 'datetime' + + >>> loadclass('does.not.exist') + + >>> loadclass('builtins.int')() + 0 + + """ + # Check if the class exists in a caller-provided scope + if classes: + try: + return classes[module_and_name] + except KeyError: + pass + # Otherwise, load classes from globally-accessible imports + names = module_and_name.split('.') + # First assume that everything up to the last dot is the module name, + # then try other splits to handle classes that are defined within + # classes + for up_to in range(len(names) - 1, 0, -1): + module = util.untranslate_module_name('.'.join(names[:up_to])) + try: + __import__(module) + obj = sys.modules[module] + for class_name in names[up_to:]: + obj = getattr(obj, class_name) + return obj + except (AttributeError, ImportError, ValueError): + continue + return None + + +def getargs(obj, classes=None): + """Return arguments suitable for __new__()""" + # Let saved newargs take precedence over everything + if has_tag(obj, tags.NEWARGSEX): + raise ValueError("__newargs_ex__ returns both args and kwargs") + + if has_tag(obj, tags.NEWARGS): + return obj[tags.NEWARGS] + + if has_tag(obj, tags.INITARGS): + return obj[tags.INITARGS] + + try: + seq_list = obj[tags.SEQ] + obj_dict = obj[tags.OBJECT] + except KeyError: + return [] + typeref = loadclass(obj_dict, classes=classes) + if not typeref: + return [] + if hasattr(typeref, '_fields'): + if len(typeref._fields) == len(seq_list): + return seq_list + return [] + + +class _trivialclassic: + """ + A trivial class that can be instantiated with no args + """ + + +def make_blank_classic(cls): + """ + Implement the mandated strategy for dealing with classic classes + which cannot be instantiated without __getinitargs__ because they + take parameters + """ + instance = _trivialclassic() + instance.__class__ = cls + return instance + + +def loadrepr(reprstr): + """Returns an instance of the object from the object's repr() string. + It involves the dynamic specification of code. + + >>> obj = loadrepr('datetime/datetime.datetime.now()') + >>> obj.__class__.__name__ + 'datetime' + + """ + module, evalstr = reprstr.split('/') + mylocals = locals() + localname = module + if '.' in localname: + localname = module.split('.', 1)[0] + mylocals[localname] = __import__(module) + return eval(evalstr) + + +def has_tag(obj, tag): + """Helper class that tests to see if the obj is a dictionary + and contains a particular key/tag. + + >>> obj = {'test': 1} + >>> has_tag(obj, 'test') + True + >>> has_tag(obj, 'fail') + False + + >>> has_tag(42, 'fail') + False + + """ + return type(obj) is dict and tag in obj diff --git a/intelliaide/vendor/jsonpickle/util.py b/intelliaide/vendor/jsonpickle/util.py new file mode 100644 index 0000000..c674255 --- /dev/null +++ b/intelliaide/vendor/jsonpickle/util.py @@ -0,0 +1,585 @@ +# Copyright (C) 2008 John Paulett (john -at- paulett.org) +# Copyright (C) 2009-2018 David Aguilar (davvid -at- gmail.com) +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. + +"""Helper functions for pickling and unpickling. Most functions assist in +determining the type of an object. +""" +from __future__ import absolute_import, division, unicode_literals +import base64 +import collections +import io +import operator +import sys +import time +import types +import inspect + +from . import tags +from . import compat +from .compat import ( + abc_iterator, + class_types, + iterator_types, + numeric_types, + PY2, + PY3, + PY3_ORDERED_DICT, +) + +if PY2: + import __builtin__ + +SEQUENCES = (list, set, tuple) +SEQUENCES_SET = {list, set, tuple} +PRIMITIVES = {compat.ustr, bool, type(None)} | set(numeric_types) +FUNCTION_TYPES = { + types.FunctionType, + types.MethodType, + types.LambdaType, + types.BuiltinFunctionType, + types.BuiltinMethodType, +} +NON_REDUCIBLE_TYPES = ( + { + int, + float, + list, + dict, + set, + tuple, + object, + bytes, + } + | PRIMITIVES + | FUNCTION_TYPES +) + + +def is_type(obj): + """Returns True is obj is a reference to a type. + + >>> is_type(1) + False + + >>> is_type(object) + True + + >>> class Klass: pass + >>> is_type(Klass) + True + """ + # use "isinstance" and not "is" to allow for metaclasses + return isinstance(obj, class_types) + + +def has_method(obj, name): + # false if attribute doesn't exist + if not hasattr(obj, name): + return False + func = getattr(obj, name) + + # builtin descriptors like __getnewargs__ + if isinstance(func, types.BuiltinMethodType): + return True + + # note that FunctionType has a different meaning in py2/py3 + if not isinstance(func, (types.MethodType, types.FunctionType)): + return False + + # need to go through __dict__'s since in py3 + # methods are essentially descriptors + + # __class__ for old-style classes + base_type = obj if is_type(obj) else obj.__class__ + original = None + # there is no .mro() for old-style classes + for subtype in inspect.getmro(base_type): + original = vars(subtype).get(name) + if original is not None: + break + + # name not found in the mro + if original is None: + return False + + # static methods are always fine + if isinstance(original, staticmethod): + return True + + # at this point, the method has to be an instancemthod or a classmethod + self_attr = '__self__' if PY3 else 'im_self' + if not hasattr(func, self_attr): + return False + bound_to = getattr(func, self_attr) + + # class methods + if isinstance(original, classmethod): + return issubclass(base_type, bound_to) + + # bound methods + return isinstance(obj, type(bound_to)) + + +def is_object(obj): + """Returns True is obj is a reference to an object instance. + + >>> is_object(1) + True + + >>> is_object(object()) + True + + >>> is_object(lambda x: 1) + False + """ + return isinstance(obj, object) and not isinstance( + obj, (type, types.FunctionType, types.BuiltinFunctionType) + ) + + +def is_primitive(obj): + """Helper method to see if the object is a basic data type. Unicode strings, + integers, longs, floats, booleans, and None are considered primitive + and will return True when passed into *is_primitive()* + + >>> is_primitive(3) + True + >>> is_primitive([4,4]) + False + """ + return type(obj) in PRIMITIVES + + +def is_enum(obj): + """Is the object an enum?""" + return 'enum' in sys.modules and isinstance(obj, sys.modules['enum'].Enum) + + +def is_dictionary(obj): + """Helper method for testing if the object is a dictionary. + + >>> is_dictionary({'key':'value'}) + True + + """ + return type(obj) is dict + + +def is_sequence(obj): + """Helper method to see if the object is a sequence (list, set, or tuple). + + >>> is_sequence([4]) + True + + """ + return type(obj) in SEQUENCES_SET + + +def is_list(obj): + """Helper method to see if the object is a Python list. + + >>> is_list([4]) + True + """ + return type(obj) is list + + +def is_set(obj): + """Helper method to see if the object is a Python set. + + >>> is_set(set()) + True + """ + return type(obj) is set + + +def is_bytes(obj): + """Helper method to see if the object is a bytestring. + + >>> is_bytes(b'foo') + True + """ + return type(obj) is bytes + + +def is_unicode(obj): + """Helper method to see if the object is a unicode string""" + return type(obj) is compat.ustr + + +def is_tuple(obj): + """Helper method to see if the object is a Python tuple. + + >>> is_tuple((1,)) + True + """ + return type(obj) is tuple + + +def is_dictionary_subclass(obj): + """Returns True if *obj* is a subclass of the dict type. *obj* must be + a subclass and not the actual builtin dict. + + >>> class Temp(dict): pass + >>> is_dictionary_subclass(Temp()) + True + """ + # TODO: add UserDict + return ( + hasattr(obj, '__class__') + and issubclass(obj.__class__, dict) + and type(obj) is not dict + ) + + +def is_sequence_subclass(obj): + """Returns True if *obj* is a subclass of list, set or tuple. + + *obj* must be a subclass and not the actual builtin, such + as list, set, tuple, etc.. + + >>> class Temp(list): pass + >>> is_sequence_subclass(Temp()) + True + """ + return ( + hasattr(obj, '__class__') + and (issubclass(obj.__class__, SEQUENCES) or is_list_like(obj)) + and not is_sequence(obj) + ) + + +def is_noncomplex(obj): + """Returns True if *obj* is a special (weird) class, that is more complex + than primitive data types, but is not a full object. Including: + + * :class:`~time.struct_time` + """ + if type(obj) is time.struct_time: + return True + return False + + +def is_function(obj): + """Returns true if passed a function + + >>> is_function(lambda x: 1) + True + + >>> is_function(locals) + True + + >>> def method(): pass + >>> is_function(method) + True + + >>> is_function(1) + False + """ + return type(obj) in FUNCTION_TYPES + + +def is_module_function(obj): + """Return True if `obj` is a module-global function + + >>> import os + >>> is_module_function(os.path.exists) + True + + >>> is_module_function(lambda: None) + False + + """ + + return ( + hasattr(obj, '__class__') + and isinstance(obj, (types.FunctionType, types.BuiltinFunctionType)) + and hasattr(obj, '__module__') + and hasattr(obj, '__name__') + and obj.__name__ != '' + ) + + +def is_module(obj): + """Returns True if passed a module + + >>> import os + >>> is_module(os) + True + + """ + return isinstance(obj, types.ModuleType) + + +def is_picklable(name, value): + """Return True if an object can be pickled + + >>> import os + >>> is_picklable('os', os) + True + + >>> def foo(): pass + >>> is_picklable('foo', foo) + True + + >>> is_picklable('foo', lambda: None) + False + + """ + if name in tags.RESERVED: + return False + return is_module_function(value) or not is_function(value) + + +def is_installed(module): + """Tests to see if ``module`` is available on the sys.path + + >>> is_installed('sys') + True + >>> is_installed('hopefullythisisnotarealmodule') + False + + """ + try: + __import__(module) + return True + except ImportError: + return False + + +def is_list_like(obj): + return hasattr(obj, '__getitem__') and hasattr(obj, 'append') + + +def is_iterator(obj): + is_file = PY2 and isinstance(obj, __builtin__.file) + return ( + isinstance(obj, abc_iterator) and not isinstance(obj, io.IOBase) and not is_file + ) + + +def is_collections(obj): + try: + return type(obj).__module__ == 'collections' + except Exception: + return False + + +def is_reducible_sequence_subclass(obj): + return hasattr(obj, '__class__') and issubclass(obj.__class__, SEQUENCES) + + +def is_reducible(obj): + """ + Returns false if of a type which have special casing, + and should not have their __reduce__ methods used + """ + # defaultdicts may contain functions which we cannot serialise + if is_collections(obj) and not isinstance(obj, collections.defaultdict): + return True + # sets are slightly slower in this case + if type(obj) in NON_REDUCIBLE_TYPES: + return False + elif obj is object: + return False + elif is_list_like(obj): + return False + elif isinstance(obj, types.ModuleType): + return False + elif is_dictionary_subclass(obj): + return False + elif is_reducible_sequence_subclass(obj): + return False + elif isinstance(getattr(obj, '__slots__', None), iterator_types): + return False + elif is_type(obj) and obj.__module__ == 'datetime': + return False + return True + + +def in_dict(obj, key, default=False): + """ + Returns true if key exists in obj.__dict__; false if not in. + If obj.__dict__ is absent, return default + """ + return (key in obj.__dict__) if getattr(obj, '__dict__', None) else default + + +def in_slots(obj, key, default=False): + """ + Returns true if key exists in obj.__slots__; false if not in. + If obj.__slots__ is absent, return default + """ + return (key in obj.__slots__) if getattr(obj, '__slots__', None) else default + + +def has_reduce(obj): + """ + Tests if __reduce__ or __reduce_ex__ exists in the object dict or + in the class dicts of every class in the MRO *except object*. + + Returns a tuple of booleans (has_reduce, has_reduce_ex) + """ + + if not is_reducible(obj) or is_type(obj): + return (False, False) + + # in this case, reduce works and is desired + # notwithstanding depending on default object + # reduce + if is_noncomplex(obj): + return (False, True) + + has_reduce = False + has_reduce_ex = False + + REDUCE = '__reduce__' + REDUCE_EX = '__reduce_ex__' + + # For object instance + has_reduce = in_dict(obj, REDUCE) or in_slots(obj, REDUCE) + has_reduce_ex = in_dict(obj, REDUCE_EX) or in_slots(obj, REDUCE_EX) + + # turn to the MRO + for base in type(obj).__mro__: + if is_reducible(base): + has_reduce = has_reduce or in_dict(base, REDUCE) + has_reduce_ex = has_reduce_ex or in_dict(base, REDUCE_EX) + if has_reduce and has_reduce_ex: + return (has_reduce, has_reduce_ex) + + # for things that don't have a proper dict but can be + # getattred (rare, but includes some builtins) + cls = type(obj) + object_reduce = getattr(object, REDUCE) + object_reduce_ex = getattr(object, REDUCE_EX) + if not has_reduce: + has_reduce_cls = getattr(cls, REDUCE, False) + if has_reduce_cls is not object_reduce: + has_reduce = has_reduce_cls + + if not has_reduce_ex: + has_reduce_ex_cls = getattr(cls, REDUCE_EX, False) + if has_reduce_ex_cls is not object_reduce_ex: + has_reduce_ex = has_reduce_ex_cls + + return (has_reduce, has_reduce_ex) + + +def translate_module_name(module): + """Rename builtin modules to a consistent module name. + + Prefer the more modern naming. + + This is used so that references to Python's `builtins` module can + be loaded in both Python 2 and 3. We remap to the "__builtin__" + name and unmap it when importing. + + Map the Python2 `exceptions` module to `builtins` because + `builtins` is a superset and contains everything that is + available in `exceptions`, which makes the translation simpler. + + See untranslate_module_name() for the reverse operation. + """ + lookup = dict(__builtin__='builtins', exceptions='builtins') + return lookup.get(module, module) + + +def untranslate_module_name(module): + """Rename module names mention in JSON to names that we can import + + This reverses the translation applied by translate_module_name() to + a module name available to the current version of Python. + + """ + module = _0_9_6_compat_untranslate(module) + lookup = dict(builtins='__builtin__') if PY2 else {} + return lookup.get(module, module) + + +def _0_9_6_compat_untranslate(module): + """Provide compatibility for pickles created with jsonpickle 0.9.6 and + earlier, remapping `exceptions` and `__builtin__` to `builtins`. + """ + lookup = dict(__builtin__='builtins', exceptions='builtins') + return lookup.get(module, module) + + +def importable_name(cls): + """ + >>> class Example(object): + ... pass + + >>> ex = Example() + >>> importable_name(ex.__class__) == 'jsonpickle.util.Example' + True + >>> importable_name(type(25)) == 'builtins.int' + True + >>> importable_name(None.__class__) == 'builtins.NoneType' + True + >>> importable_name(False.__class__) == 'builtins.bool' + True + >>> importable_name(AttributeError) == 'builtins.AttributeError' + True + + """ + # Use the fully-qualified name if available (Python >= 3.3) + name = getattr(cls, '__qualname__', cls.__name__) + module = translate_module_name(cls.__module__) + return '{}.{}'.format(module, name) + + +def b64encode(data): + """ + Encode binary data to ascii text in base64. Data must be bytes. + """ + return base64.b64encode(data).decode('ascii') + + +def b64decode(payload): + """ + Decode payload - must be ascii text. + """ + return base64.b64decode(payload) + + +def b85encode(data): + """ + Encode binary data to ascii text in base85. Data must be bytes. + """ + if PY2: + raise NotImplementedError("Python 2 can't encode data in base85.") + return base64.b85encode(data).decode('ascii') + + +def b85decode(payload): + """ + Decode payload - must be ascii text. + """ + if PY2: + raise NotImplementedError("Python 2 can't decode base85-encoded data.") + return base64.b85decode(payload) + + +def itemgetter(obj, getter=operator.itemgetter(0)): + return compat.ustr(getter(obj)) + + +def items(obj): + """Iterate over dicts in a deterministic order + + Python2 does not guarantee dict ordering, so this function + papers over the difference in behavior. Python3 does guarantee + dict order, without use of OrderedDict, so no sorting is needed there. + + """ + if PY3_ORDERED_DICT: + for k, v in obj.items(): + yield k, v + else: + for k, v in sorted(obj.items(), key=itemgetter): + yield k, v diff --git a/intelliaide/vendor/jsonpickle/version.py b/intelliaide/vendor/jsonpickle/version.py new file mode 100644 index 0000000..92b44b2 --- /dev/null +++ b/intelliaide/vendor/jsonpickle/version.py @@ -0,0 +1,21 @@ +import sys + +try: + if sys.version_info < (3, 8): + import importlib_metadata as metadata + else: + from importlib import metadata +except (ImportError, OSError): + metadata = None + + +def _get_version(): + default_version = '0.0.0-alpha' + try: + version = metadata.version('jsonpickle') + except (AttributeError, ImportError, OSError): + version = default_version + return version + + +__version__ = _get_version() diff --git a/intelliaide/vendor/lxml-6.1.1.dist-info/INSTALLER b/intelliaide/vendor/lxml-6.1.1.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/intelliaide/vendor/lxml-6.1.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/intelliaide/vendor/lxml-6.1.1.dist-info/METADATA b/intelliaide/vendor/lxml-6.1.1.dist-info/METADATA new file mode 100644 index 0000000..d30675f --- /dev/null +++ b/intelliaide/vendor/lxml-6.1.1.dist-info/METADATA @@ -0,0 +1,92 @@ +Metadata-Version: 2.4 +Name: lxml +Version: 6.1.1 +Summary: Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API. +Home-page: https://lxml.de/ +Author: lxml dev team +Author-email: lxml@lxml.de +Maintainer: lxml dev team +Maintainer-email: lxml@lxml.de +License: BSD-3-Clause +Project-URL: Source, https://github.com/lxml/lxml +Project-URL: Bug Tracker, https://bugs.launchpad.net/lxml +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Information Technology +Classifier: Programming Language :: Cython +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: C +Classifier: Operating System :: OS Independent +Classifier: Topic :: Text Processing :: Markup :: HTML +Classifier: Topic :: Text Processing :: Markup :: XML +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=3.8 +License-File: LICENSE.txt +License-File: LICENSES.txt +Provides-Extra: source +Provides-Extra: cssselect +Requires-Dist: cssselect>=0.7; extra == "cssselect" +Provides-Extra: html5 +Requires-Dist: html5lib; extra == "html5" +Provides-Extra: htmlsoup +Requires-Dist: BeautifulSoup4; extra == "htmlsoup" +Provides-Extra: html-clean +Requires-Dist: lxml_html_clean; extra == "html-clean" +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: home-page +Dynamic: license +Dynamic: license-file +Dynamic: maintainer +Dynamic: maintainer-email +Dynamic: project-url +Dynamic: provides-extra +Dynamic: requires-python +Dynamic: summary + +lxml is a Pythonic, mature binding for the libxml2 and libxslt libraries. +It provides safe and convenient access to these libraries using the +ElementTree API. + +It extends the ElementTree API significantly to offer support for XPath, +RelaxNG, XML Schema, XSLT, C14N and much more. + +To contact the project, go to the `project home page `_ +or see our bug tracker at https://launchpad.net/lxml + +In case you want to use the current in-development version of lxml, +you can get it from the github repository at +https://github.com/lxml/lxml . Note that this requires Cython to +build the sources, see the build instructions on the project home page. + + +After an official release of a new stable series, bug fixes may become available at +https://github.com/lxml/lxml/tree/lxml-6.1 . +Running ``pip install https://github.com/lxml/lxml/archive/refs/heads/lxml-6.1.tar.gz`` +will install the unreleased branch state as soon as a maintenance branch has been established. +Note that this requires Cython to be installed at an appropriate version for the build. + +6.1.1 (2026-05-18) +================== + +Bugs fixed +---------- + +* The known link attributes in ``lxml.html.defs.link_attrs`` were missing ``xlink:href``, + which can be used for URL bypass attacks in embedded SVG/MathML/etc. content. + https://github.com/fedora-python/lxml_html_clean/security/advisories/GHSA-4jhm-jv67-739f + +* The Linux wheels use a patched libxslt 1.1.43, fixing CVE-2025-7424 and CVE-2025-11731. + +* The Windows wheels use libxslt 1.1.45, fixing CVE-2025-7424 and CVE-2025-11731. + + diff --git a/intelliaide/vendor/lxml-6.1.1.dist-info/RECORD b/intelliaide/vendor/lxml-6.1.1.dist-info/RECORD new file mode 100644 index 0000000..8f94e50 --- /dev/null +++ b/intelliaide/vendor/lxml-6.1.1.dist-info/RECORD @@ -0,0 +1,204 @@ +lxml-6.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +lxml-6.1.1.dist-info/METADATA,sha256=qbvABHCIHPEzcHkgBEmV_9YGNyPiA8po1l4vKiWIa8c,3518 +lxml-6.1.1.dist-info/RECORD,, +lxml-6.1.1.dist-info/WHEEL,sha256=rSHxYvtox9WA2CLg-qPNRHTwscksbjj7CEInt0-zzg4,152 +lxml-6.1.1.dist-info/licenses/LICENSE.txt,sha256=j8K1aBM1FuRoRdIUeRet7uFkjnCumrXtbFQXr-9M6FU,1507 +lxml-6.1.1.dist-info/licenses/LICENSES.txt,sha256=QdSd1AaqDhVIptXyGjDWv2OLPNlutyid00jYPtLkA5I,1514 +lxml-6.1.1.dist-info/top_level.txt,sha256=NjD988wqaKq512nshNdLt-uDxsjkp4Bh51m6N-dhUrk,5 +lxml/ElementInclude.py,sha256=PSLeZFvCa76WHJulPLxcZXJtCI2-4dK2CtqPRiYOAQg,8560 +lxml/__init__.py,sha256=OKLD7sP5BpJ5x3k2-I1dl5ozoGx3xVvO3ml2ISuEZQg,574 +lxml/__pycache__/ElementInclude.cpython-312.pyc,, +lxml/__pycache__/__init__.cpython-312.pyc,, +lxml/__pycache__/_elementpath.cpython-312.pyc,, +lxml/__pycache__/builder.cpython-312.pyc,, +lxml/__pycache__/cssselect.cpython-312.pyc,, +lxml/__pycache__/doctestcompare.cpython-312.pyc,, +lxml/__pycache__/pyclasslookup.cpython-312.pyc,, +lxml/__pycache__/sax.cpython-312.pyc,, +lxml/__pycache__/usedoctest.cpython-312.pyc,, +lxml/_elementpath.cpython-312-x86_64-linux-gnu.so,sha256=Gngj3IsCTtH8jsFJTJKW0x4oAbFMPSQIFgVbMUZ-PvU,209096 +lxml/_elementpath.py,sha256=b80hM3ndAkTtRX6v54za3LkkAqCcd0700BbMPZHnTBU,10959 +lxml/apihelpers.pxi,sha256=CugqfQgyn6aFtwp7eH144RUpV2v1ItxoalhJcFbORTQ,64604 +lxml/builder.cpython-312-x86_64-linux-gnu.so,sha256=MSiyw-7TTv91-yEn-dq2qc5QIFmTW2knqs64u7GoUvs,120880 +lxml/builder.py,sha256=sL7iKT8NXVmpO_p2p2nHTjShjZOGMFLgFzgbhUFl68g,8487 +lxml/classlookup.pxi,sha256=Tax8Vhbm5C6UCjgmRFsYjW0pFHxIuTthH1MOgASDLgc,22435 +lxml/cleanup.pxi,sha256=ZNEpbv7qx_ICPzsxhCaMUHCOfiznOoZ_u3jlYXHAuh4,8454 +lxml/cssselect.py,sha256=_wZdX-B9p5MeIYABmENIYRWEkwXwX-7jO8Dkf-1rUZU,3306 +lxml/debug.pxi,sha256=KTcpR8-slUYvmIPbE35GoHDNTb-gjTEvD7bw6LltM4c,1125 +lxml/docloader.pxi,sha256=fcoVd_mwTFZhjItCv_TfwXpgVek7JlvFObSfRxmVqs0,5790 +lxml/doctestcompare.py,sha256=40EDnkwpcvW86qNa86990OXF42xdHaosSZoiBsEjkzU,17731 +lxml/dtd.pxi,sha256=IAKkmA4ZoC68sqAWcTqoS8jEGYcPQrVMCZgn4iLBYko,15281 +lxml/etree.cpython-312-x86_64-linux-gnu.so,sha256=11l6D-wiV2QWAoI4_yCmX5DZerb5b2X-OmIHgIVa3Ug,5276528 +lxml/etree.h,sha256=hWgHumv7DeTpH0nHhbsCIe693cIcimtLabExxwBxR-c,9792 +lxml/etree.pyx,sha256=zgobCeGw6lw902OA0FWuwlOpqA_0eZeLM2baWi1pzio,138216 +lxml/etree_api.h,sha256=UG16a1ThLt259FSG3vUy6ka6OIat-6LLJyVBLz6d3OM,17710 +lxml/extensions.pxi,sha256=saxxc1mZyA8WdwIIL90B4lQb351j8-G7ttLPI5k-8Hw,32248 +lxml/html/ElementSoup.py,sha256=s_dLobLMuKn2DhexR-iDXdZrMFg1RjLy1feHsIeZMpw,320 +lxml/html/__init__.py,sha256=CC5WdsvSptZhr9MZya1qsL6JKVbviYdrHOhXrGhmORg,64425 +lxml/html/__pycache__/ElementSoup.cpython-312.pyc,, +lxml/html/__pycache__/__init__.cpython-312.pyc,, +lxml/html/__pycache__/_diffcommand.cpython-312.pyc,, +lxml/html/__pycache__/_difflib.cpython-312.pyc,, +lxml/html/__pycache__/_html5builder.cpython-312.pyc,, +lxml/html/__pycache__/_setmixin.cpython-312.pyc,, +lxml/html/__pycache__/builder.cpython-312.pyc,, +lxml/html/__pycache__/clean.cpython-312.pyc,, +lxml/html/__pycache__/defs.cpython-312.pyc,, +lxml/html/__pycache__/diff.cpython-312.pyc,, +lxml/html/__pycache__/formfill.cpython-312.pyc,, +lxml/html/__pycache__/html5parser.cpython-312.pyc,, +lxml/html/__pycache__/soupparser.cpython-312.pyc,, +lxml/html/__pycache__/usedoctest.cpython-312.pyc,, +lxml/html/_diffcommand.py,sha256=kz_7EP9PmYWuczlZcGiw74_rG0eTKvQ2lrO0rkiwlYE,2081 +lxml/html/_difflib.cpython-312-x86_64-linux-gnu.so,sha256=0Za0L59Q9ZDfTG86x9enqvkdutV0Z29YDEjG4nftBLQ,533400 +lxml/html/_difflib.py,sha256=GgH_jVrZQC8tI8WV_lFZQsXFJ3mOTAPup1zjBJNvkPo,84954 +lxml/html/_html5builder.py,sha256=NLaT-Ev-aBgJpeQl-6ZbJChLZK5GV-znDkHOJD5VQC4,3230 +lxml/html/_setmixin.py,sha256=8IFIOLmVz0G-XzsD2tCEkSFWO-dgPBHgvHufC8ni67s,1188 +lxml/html/builder.py,sha256=Uz3r5uiuCdoN0UPa7ngoLMwAadVIhslzGvlRPGigY_M,6187 +lxml/html/clean.py,sha256=WU0KfXAyjDFn2ojE7T1dfk4cl_FduBu5ZbFSTc9Up_o,503 +lxml/html/defs.py,sha256=6z6B1ws_2l9zgw3DL7ML62wwBYe6m5reJp6m3uTKptQ,5398 +lxml/html/diff.cpython-312-x86_64-linux-gnu.so,sha256=EoLnmB8DWTm1fvtjq5IQR6j-bXa-h3nnNRt4E_F-LzA,361400 +lxml/html/diff.py,sha256=Za0By-yeYlQEjUu7m7xKB288kKiy8VBS5gT0RPOaFY0,32989 +lxml/html/formfill.py,sha256=umgk0BbkAI1W6q9musFbL-cDnI_aap2NsLBJqk0UmVI,9681 +lxml/html/html5parser.py,sha256=dnyC4cqHxywjZSzk0mu2L7THTZjxhg4yF4pncjusa_w,8634 +lxml/html/soupparser.py,sha256=xo8VvNeOEb-SChuXLKCRECh8J7HBiJLE9sAbEskoUUQ,10197 +lxml/html/usedoctest.py,sha256=tPlmVz4KK1GRKV5DJLrdVECeqsT9PlDzSqqTodVi5s0,249 +lxml/includes/__init__.pxd,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +lxml/includes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +lxml/includes/__pycache__/__init__.cpython-312.pyc,, +lxml/includes/c14n.pxd,sha256=DBQcOJ0c_YS245ohMb8fmuEC1kFyv1LrNY_8Mf-syZg,1110 +lxml/includes/config.pxd,sha256=H6Mrl8It21hzRI2hzMId9W48QqkYYkoLT4dniLNmdTw,96 +lxml/includes/dtdvalid.pxd,sha256=Nv0OykjYehv2lO-Zj--q6jS3TAC_dvQVPSgPMuse1NM,689 +lxml/includes/etree_defs.h,sha256=h_UjJTmNUqPyKNNrWB9hxmt6v4CF7_83XVY8dOfxqW0,14524 +lxml/includes/etreepublic.pxd,sha256=Bn4d3JkWPqXputXqI-eJ0xmPrwNFPTfDCa7axgjB7FM,10184 +lxml/includes/extlibs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +lxml/includes/extlibs/__pycache__/__init__.cpython-312.pyc,, +lxml/includes/extlibs/libcharset.h,sha256=GA0FumrbNI4VDGlzq3lf5CLaCwXgn4unw2l0btGQFwI,1510 +lxml/includes/extlibs/localcharset.h,sha256=Z_AagaQeq0aDE7NPsVOqEf4nO4KcUp46ggo4d0ONIOQ,6338 +lxml/includes/extlibs/zconf.h,sha256=BxihG-sylbNF-ymmO0S2VLKC2CxM0FE_YiVYfyspuLs,16921 +lxml/includes/extlibs/zlib.h,sha256=gYZn1qtqN_50acsGp_DLLCyy8slIoD5azPGkp0vzAgo,103848 +lxml/includes/htmlparser.pxd,sha256=9uASkP5dU7OE2lCOLT-z2e01qSbFlp4ehgwdostF_qk,2802 +lxml/includes/libexslt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +lxml/includes/libexslt/__pycache__/__init__.cpython-312.pyc,, +lxml/includes/libexslt/exslt.h,sha256=eSW5tMJAewSUANLqk7AGEiU8b2BbCNRyauHnez7nKSU,3114 +lxml/includes/libexslt/exsltconfig.h,sha256=QHxzEbRlv_h0USBvpr0Zrl0Muzlc71VCrvgR6lqnLEY,1172 +lxml/includes/libexslt/exsltexports.h,sha256=1Jm9KTXm2FUUJIZ6V6-Uw55yG0BMULX3_goyxDd2LL8,1077 +lxml/includes/libxml/HTMLparser.h,sha256=sU4xGqj-vBtEvzlxA3hBPWJboifvkc4F1hynKXmsl3k,9569 +lxml/includes/libxml/HTMLtree.h,sha256=Q7UBKFbQ8fx4d_dMnmR6ay8JmfOhopFkDp2B63YkLDU,3517 +lxml/includes/libxml/SAX.h,sha256=SFnG27EFrYGUB9HDL_xSIGBwEns5pl07rApXWThFZFM,386 +lxml/includes/libxml/SAX2.h,sha256=RfFP5o3le-Rg8bnA2GW7L7L9_pfXCs3TieODcv1DTWY,4240 +lxml/includes/libxml/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +lxml/includes/libxml/__pycache__/__init__.cpython-312.pyc,, +lxml/includes/libxml/c14n.h,sha256=BSBXw6nIZutC8mWvbRrLLmoWjw3wRt-nM93vjXGMCm8,2742 +lxml/includes/libxml/catalog.h,sha256=H9ssTCaBjtDqc-AZqCk1R7h8F2iD9szqLjJyHpaczXg,4633 +lxml/includes/libxml/chvalid.h,sha256=TZcceNp6Cw0QlYwIqK9GxyYqL5UiAjpQyjt_yrZGTQE,5087 +lxml/includes/libxml/debugXML.h,sha256=XXRNI39gJW7bGRC4SzE4ad-SJ906BsUGz3AwOtkKuS4,1667 +lxml/includes/libxml/dict.h,sha256=SweaPGMtTTf4je6dNTIoEzcfEvpsAT9_PhR7FC0K-rQ,1770 +lxml/includes/libxml/encoding.h,sha256=haL7ratww2wkIERGmtwUqU2BbTVe52FZFU7MmrOpsPk,9623 +lxml/includes/libxml/entities.h,sha256=LEOCA826-0f8dhRJzC_2hvUVsSH7lKQjrea9hSTdBbo,4419 +lxml/includes/libxml/globals.h,sha256=NH8zyRI5cXJJGp5k2aLxOm-reJEGOFX6LYP82GBXRlY,583 +lxml/includes/libxml/hash.h,sha256=KIIpAYKBfGUU3ydWhGehUyfuauZz_Ps0gyambzQo_rc,7017 +lxml/includes/libxml/list.h,sha256=oh7iJNQajRA_cHsNk9CcFPYkaW2smf4J_MpedPPjC4k,3128 +lxml/includes/libxml/nanoftp.h,sha256=22PBtWhJueYLFvwukt4oFooRct_xJA83hbluHRBNXUM,302 +lxml/includes/libxml/nanohttp.h,sha256=bLbzYjAyAKmP3ComMOPH6XaUImu6bNAESF1HrVtRve0,2124 +lxml/includes/libxml/parser.h,sha256=Uq7-ce55UUAsvo4n6CiBlNQpmowewvWhOsQtgGM1UQ8,48498 +lxml/includes/libxml/parserInternals.h,sha256=8_Wr6UgRzm8BRn1RPLxyBkw6BagAdDvVqMA_e181_EI,14539 +lxml/includes/libxml/relaxng.h,sha256=VXZ74r5Yja06KqypdBHc8neDwPxQ2aMrsWHSdRt5oi4,5991 +lxml/includes/libxml/schemasInternals.h,sha256=V8M4In3zf24EX55Yt4dcfxwp7NpHGYViKnLKwtyrPJ4,26233 +lxml/includes/libxml/schematron.h,sha256=8EhPDhvtlMxl9e0C5rSbEruOvzJS5BC_OOFbq9RXZnY,4255 +lxml/includes/libxml/threads.h,sha256=mT3CgK4lXK7-NDnUOFXqYuCK6fyY70S3BsHF-TnT45k,1619 +lxml/includes/libxml/tree.h,sha256=zTRLt6h5x6ApyeXgs90CKQZSAl2hKm7b5NxtPKUQFAE,36106 +lxml/includes/libxml/uri.h,sha256=J9teJHme5z883c4twF5oImEYY-E3xSvhdSGpyRVtvIg,2855 +lxml/includes/libxml/valid.h,sha256=By61IbPvk_eLux7a8x0mOaly7oclFaSGaFE8b2xZcUE,13226 +lxml/includes/libxml/xinclude.h,sha256=K3I5jhw2zAMj26LuRNZc15Bwv2JE2hWxwVn4TCqv2b4,3258 +lxml/includes/libxml/xlink.h,sha256=TVLOkISrcKDelo9n_XIUyPiStDYa8NxuF2dz70aBFCI,5062 +lxml/includes/libxml/xmlIO.h,sha256=FvbuMYTy1-S5PScabE03wz0oWKf626pmXvOPZNuLm-w,11948 +lxml/includes/libxml/xmlautomata.h,sha256=7Sc3YgPz1ZIBKCHPSxs5oAwJEZWQ1RT2kyUw85pUtmU,4004 +lxml/includes/libxml/xmlerror.h,sha256=mMfltMxUza6kiSBfP2QfnY3UlMP_rEXKfX0wruBLl4A,37561 +lxml/includes/libxml/xmlexports.h,sha256=IyV3AMeQVbOl0wkjlnNX4B8WUZ-5GNKQmxZc6-maWUU,2025 +lxml/includes/libxml/xmlmemory.h,sha256=m7wGvVMxNzZiuOAo3vkjxaVWstc8aQLzb6obbjPsebE,4658 +lxml/includes/libxml/xmlmodule.h,sha256=ERUHUmDdZRmh6NjLYWUpse51rLWR8qNjPHOtdgmlLF0,1198 +lxml/includes/libxml/xmlreader.h,sha256=BAHinlSOTXX3DEax9BniaIIPAXJyLGfzym9R-27LCcU,12387 +lxml/includes/libxml/xmlregexp.h,sha256=_q6C1XRy8DS3kSmLbEKpvkKQciTgjTJgGc_zUQ6m22M,2632 +lxml/includes/libxml/xmlsave.h,sha256=zcEQr9sO5CsFrnoOLshhdsqMEr8k4AeFhbkYyNfO9Fs,2934 +lxml/includes/libxml/xmlschemas.h,sha256=5AfLnYUcfmxHRzg0dVpdHig--4ui1-XDwDgpKGDKCiU,7067 +lxml/includes/libxml/xmlschemastypes.h,sha256=MYwlGmoKAo3lHRaaKgnCXiLmPT9KRjdxyCJ7TEyZ6jM,4583 +lxml/includes/libxml/xmlstring.h,sha256=d5PpqxP1I1sfmCUHvVJtjoC9h7hLHcAAQ5ok_Rtf50I,5271 +lxml/includes/libxml/xmlunicode.h,sha256=8sq3wEW2AiyTCuc3ZceOEkce7lfrI7VnkRfwEQgc6pU,278 +lxml/includes/libxml/xmlversion.h,sha256=oVpaE_xbttaeZNFKSuSfcLOceWz7LQgKP71Z1msXZNo,5112 +lxml/includes/libxml/xmlwriter.h,sha256=BEUwYNKx3xymDE9vepksEK7yVq9SXYm1d2pQnzlPy90,20688 +lxml/includes/libxml/xpath.h,sha256=CQv6X_pRhuXoCVpqoDXYB7FfusLK7AuPxCNigwhNYAA,16156 +lxml/includes/libxml/xpathInternals.h,sha256=mc9B5tdpfssyz_NPUzww6dKuWCtBybBiBRJkTe4AE4U,18504 +lxml/includes/libxml/xpointer.h,sha256=DAxMsfPp2SSZgXFrPbxBA84RwTMRf35Qg_LBbUzPQhA,1026 +lxml/includes/libxslt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +lxml/includes/libxslt/__pycache__/__init__.cpython-312.pyc,, +lxml/includes/libxslt/attributes.h,sha256=qKwzfGf7r89esLC65s96iYJWRA-s-Ezss2_V6Mmo1hk,957 +lxml/includes/libxslt/documents.h,sha256=kBihgH5pqRvFalhm_fOFHtJTFhTpBcm681yT5dxgwfw,2704 +lxml/includes/libxslt/extensions.h,sha256=W5UMyJqUP_1zt6sXZ0mgc0gAIwDJrZ8gjByhyrWqvd8,6899 +lxml/includes/libxslt/extra.h,sha256=6X3Wu3NdPtrlqz-Koo7dB-rccnnszi6j3zg599gTByg,1640 +lxml/includes/libxslt/functions.h,sha256=fc4CZj-9KeBHzO9-WWU_bNqmaEZAz3n7NNwClIBXk14,1972 +lxml/includes/libxslt/imports.h,sha256=18kIjoGqdFXR63Ce3ZtzxsTiYV3XGKpchYakMUPDuUI,1840 +lxml/includes/libxslt/keys.h,sha256=16v25VEluS7jYhgg6gYFwVxgGMn-1ctnlhhWWT4RcBY,1155 +lxml/includes/libxslt/namespaces.h,sha256=VofSn2Kkn-a5JyRKCmY3jPp7amQy3n09vzy0KUQt4q0,1666 +lxml/includes/libxslt/numbersInternals.h,sha256=Eg5gYZ5p3h0_e5wyI61S-0E6_ArVJzv0yr63j6BU2fc,2019 +lxml/includes/libxslt/pattern.h,sha256=tJ-BPfs9UYgiZMMoQZbhij3g7xVppYq7TrrOu25eR7Q,2110 +lxml/includes/libxslt/preproc.h,sha256=D_LjEdHhsdyBnEAvflnwFgoR4hGUb72kgEhXkkmPRsw,896 +lxml/includes/libxslt/security.h,sha256=fUD1cy_WxFCTvTNAF0WOQIU4p5CNWn1LHFyZJd-Fx5U,2652 +lxml/includes/libxslt/templates.h,sha256=bnt6Jqui6KU5pNUdMNPbQZkZ5d-VTWqC0TMGkOlVoIo,2268 +lxml/includes/libxslt/transform.h,sha256=ICT7meUV0OTAx27WaKVrKj-aUmR9LSpTNaOAJd2UStg,6311 +lxml/includes/libxslt/variables.h,sha256=cQAgPe4QCcK2uKbWg7Iz-9peM9xWGm7m3M6jQm0sjIA,3143 +lxml/includes/libxslt/xslt.h,sha256=wmFx2Q31Pd8Iq2phAQpY9J3QQatb8lWg3gABtqKFgEw,1964 +lxml/includes/libxslt/xsltInternals.h,sha256=2EbEKYmnYZq0HjGnUMAlpqnqZJurRXzjlgk5Js1WYaY,57949 +lxml/includes/libxslt/xsltconfig.h,sha256=cV5scdRK6xmOHeOg3OCw6hBfcQ_nrtNs_tKefX67304,2910 +lxml/includes/libxslt/xsltexports.h,sha256=1-luH-0bCIgBAlKAXhV-dqHBfwOAQNDamiYbxIlTf0k,1124 +lxml/includes/libxslt/xsltlocale.h,sha256=ppxGEmJfZIJgwRQzCM0_77p9WNekEWq1NrdYZrQl4IE,942 +lxml/includes/libxslt/xsltutils.h,sha256=1eguYgR9-jeNOVlBUktHboaq-VLX6JXraO80TfbARKM,9085 +lxml/includes/lxml-version.h,sha256=QCE72dSV-W29efxzYz7IG_wH5leX_x0mZdFbQmH2C4w,71 +lxml/includes/relaxng.pxd,sha256=HzHlQ6mCcf_tj_JZ9NAVJTVAv8ScCkE8Ifq15y3bS0c,2615 +lxml/includes/schematron.pxd,sha256=Hob7xh-K-MKqp7WiG8thMagf5EkQzmgfi4ds0EF91JA,1604 +lxml/includes/tree.pxd,sha256=XApzMRy_LSqCtQ-OTS-vNSW7CT_OWstybfIT2H84LsA,20179 +lxml/includes/uri.pxd,sha256=3vOXw6AbSPxAM9uo71T1qnfx-wd9ezXLDQtWsb2zX0I,145 +lxml/includes/xinclude.pxd,sha256=CuO_XZNB6E2JK1qXXWn11APrjFQV5kA6SMyb77WZn0A,804 +lxml/includes/xmlerror.pxd,sha256=OQqayytkV0NigAPbsQCCcvmy7luRe0XhVzpTdzJjP3g,58837 +lxml/includes/xmlparser.pxd,sha256=eDGyU5kZyNVksK0dUhMIi7rnE-LSevXsqyl72v99Ess,13730 +lxml/includes/xmlschema.pxd,sha256=OLZPd2WDJyopiXJJyo-dAyyYHaeSYFiMAI4tqIiv-Ik,1702 +lxml/includes/xpath.pxd,sha256=e8-ZYUbRG7N1mHETAlknJ_QqAteOosrYLRgpH-OsTkg,5603 +lxml/includes/xslt.pxd,sha256=4yl3pOu7pAvsx5Tc-W4IWCoB8wgtSSR62HI1jqu6jko,8241 +lxml/isoschematron/__init__.py,sha256=uauerYeKTlWFCJSqieIHhF5l6rYV2myeEJ0Imd1LzRc,13274 +lxml/isoschematron/__pycache__/__init__.cpython-312.pyc,, +lxml/isoschematron/resources/rng/iso-schematron.rng,sha256=VsWxPyi3iViJDDbjJJw0wWkEHkLrz9zoCA8zJLor9N4,18337 +lxml/isoschematron/resources/xsl/RNG2Schtrn.xsl,sha256=ObebsB8Wt-d3uIA_U5NU85TpnQ3PxPX38TdOAqosMac,3172 +lxml/isoschematron/resources/xsl/XSD2Schtrn.xsl,sha256=QweRrIIM-zFcgg98GXA2CaWfIbgVE0XKEeYSfvv67A0,4563 +lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_abstract_expand.xsl,sha256=xSZ_Ekq_I-62ZpiE5AqYYHwFW_qh855zt9V4_s7rbkY,11703 +lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_dsdl_include.xsl,sha256=x42QJ-dxQ1waPzydsCoQnp2Xj15y53nW43O7BuoDRHk,39957 +lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_schematron_message.xsl,sha256=Tr9BnO6pzjVWwhqJfm10UlvAy95EgfSCz2iMlrVGT6Q,2015 +lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_schematron_skeleton_for_xslt1.xsl,sha256=ue8q_88X4e_jsJizo31GRNBxNhdxkEE9fY20oq0Iqwk,71764 +lxml/isoschematron/resources/xsl/iso-schematron-xslt1/iso_svrl_for_xslt1.xsl,sha256=BBAdsVSi5zAzeGepuN6gS1saQINDqITXKplmmj4dTWg,20382 +lxml/isoschematron/resources/xsl/iso-schematron-xslt1/readme.txt,sha256=OGLiFswuLJEW5EPYKOeoauuCJFEtVa6jyzBE1OcJI98,3310 +lxml/iterparse.pxi,sha256=rERnJ9D0Cvn87YDMSq8V880DKV2WthXs5BXSSY2Baj4,16845 +lxml/lxml.etree.h,sha256=hWgHumv7DeTpH0nHhbsCIe693cIcimtLabExxwBxR-c,9792 +lxml/lxml.etree_api.h,sha256=O_s_RIicKl7_h5U0aKu9dKSzJCTYSDJI6jlX7b-2v0Q,17715 +lxml/nsclasses.pxi,sha256=5pzNBhBtlqObPdThL9QIGRs1Dxj1qnr0PyXuTCURqTg,9129 +lxml/objectify.cpython-312-x86_64-linux-gnu.so,sha256=iTDWB90lJBk_EVv0x5DwNpvB4cbOEqPDMKg-511TN5w,2916760 +lxml/objectify.pyx,sha256=8lIjgklL3LJ3PYAcWZ5hPLuLFa7oLqEspco4bSz4EI4,75850 +lxml/objectpath.pxi,sha256=s5TNG2-EbaWWKLFAiX303B95zK_Ui8ausB__3QvFFGw,11450 +lxml/parser.pxi,sha256=wv8ODQRXZk0r0ZVHY4zsF2vLbL_SFGwYIFSKcWAVCdQ,85821 +lxml/parsertarget.pxi,sha256=v1PidxRaG5giwXcTDkpBI7PDFmsZuOcK0y9LdkQaY8M,6326 +lxml/proxy.pxi,sha256=LnqqSZVUsJ5MnuVrhYrLwMbAKWuv1eOn-XYslut6CLM,24015 +lxml/public-api.pxi,sha256=yVPuZV2LwN5Ki73mD85V9hnUXxOs5VMeFMLtQMNf0Qk,6756 +lxml/pyclasslookup.py,sha256=gLD1HM2HtITYYiGzjEOewSwbB7XkVx_NZv_quCt79Oc,92 +lxml/readonlytree.pxi,sha256=n2MRjz81EADWBXJx0miosN8PJ4i5y3guQJT_RZF6qKg,19008 +lxml/relaxng.pxi,sha256=3OQ-fZMzP-KF5vM6HTozT_9ee3J0DJnpj9RcHC8LoMw,6339 +lxml/sax.cpython-312-x86_64-linux-gnu.so,sha256=_is26aCl78oYdPU_iyAE_CxAfev44Uir8RK03edgTVk,182096 +lxml/sax.py,sha256=5ryEnOMIMeLpLel-PFpszMHq99mWNmsnGrMvLWIm1UM,9708 +lxml/saxparser.pxi,sha256=yV67FWD60DfmFi7hbV_eLyJIMu7X2MOfV2g4X99iUR4,33538 +lxml/schematron.pxi,sha256=F2OHKZUl57-byBk_wWtPTnHZ1fwlj0FtwG3VuGtG-UY,6064 +lxml/serializer.pxi,sha256=iIXfechFHfvFs2sTk7wMIy3sDJxmaMPbNO33mkLLBUE,68063 +lxml/usedoctest.py,sha256=qRgZKQVcAZcl-zN0AIXVJnOsETUXz2nPXkxuzs1lGgk,230 +lxml/xinclude.pxi,sha256=7eBrI_OK47mmrHQ0ixbixRI8pKqQ1nwkMV-OmKUVlD4,2456 +lxml/xmlerror.pxi,sha256=MAQv92NXFoaMOzT8WeeM0L3YyksFoo48tpz3KZHTqcY,50234 +lxml/xmlid.pxi,sha256=5zf9oR6bsCtavGiOmilNyHqYwgG_bnrIabSd2SURtm0,6073 +lxml/xmlschema.pxi,sha256=mumNoHni5S3BQPtcmOHRd61KRaVWu4eOie2wQeB0e6E,8490 +lxml/xpath.pxi,sha256=aqW24V817dUxps4Gnc8h7Tm3QVlITKvxU5_9WgJUIFg,19132 +lxml/xslt.pxi,sha256=lLVwvlhMKL970rVqThP6Jnn79L3MK9UG0DGnXnNy7YI,36356 +lxml/xsltext.pxi,sha256=TImDiAPlAezC07P7RY1N9YChA7AuKFH-G53hXdel9yc,11088 diff --git a/intelliaide/vendor/lxml-6.1.1.dist-info/WHEEL b/intelliaide/vendor/lxml-6.1.1.dist-info/WHEEL new file mode 100644 index 0000000..43257e7 --- /dev/null +++ b/intelliaide/vendor/lxml-6.1.1.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: setuptools (82.0.1) +Root-Is-Purelib: false +Tag: cp312-cp312-manylinux_2_26_x86_64 +Tag: cp312-cp312-manylinux_2_28_x86_64 + diff --git a/intelliaide/vendor/lxml-6.1.1.dist-info/licenses/LICENSE.txt b/intelliaide/vendor/lxml-6.1.1.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000..0bdf039 --- /dev/null +++ b/intelliaide/vendor/lxml-6.1.1.dist-info/licenses/LICENSE.txt @@ -0,0 +1,31 @@ +BSD 3-Clause License + +Copyright (c) 2004 Infrae. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + 3. Neither the name of Infrae nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INFRAE OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/intelliaide/vendor/lxml-6.1.1.dist-info/licenses/LICENSES.txt b/intelliaide/vendor/lxml-6.1.1.dist-info/licenses/LICENSES.txt new file mode 100644 index 0000000..9f97c18 --- /dev/null +++ b/intelliaide/vendor/lxml-6.1.1.dist-info/licenses/LICENSES.txt @@ -0,0 +1,29 @@ +lxml is copyright Infrae and distributed under the BSD license (see +doc/licenses/BSD.txt), with the following exceptions: + +Some code, such a selftest.py, selftest2.py and +src/lxml/_elementpath.py are derived from ElementTree and +cElementTree. See doc/licenses/elementtree.txt for the license text. + +lxml.cssselect and lxml.html are copyright Ian Bicking and distributed +under the BSD license (see doc/licenses/BSD.txt). + +test.py, the test-runner script, is GPL and copyright Shuttleworth +Foundation. See doc/licenses/GPL.txt. It is believed the unchanged +inclusion of test.py to run the unit test suite falls under the +"aggregation" clause of the GPL and thus does not affect the license +of the rest of the package. + +The isoschematron implementation uses several XSL and RelaxNG resources: + * The (XML syntax) RelaxNG schema for schematron, copyright International + Organization for Standardization (see + src/lxml/isoschematron/resources/rng/iso-schematron.rng for the license + text) + * The skeleton iso-schematron-xlt1 pure-xslt schematron implementation + xsl stylesheets, copyright Rick Jelliffe and Academia Sinica Computing + Center, Taiwan (see the xsl files here for the license text: + src/lxml/isoschematron/resources/xsl/iso-schematron-xslt1/) + * The xsd/rng schema schematron extraction xsl transformations are unlicensed + and copyright the respective authors as noted (see + src/lxml/isoschematron/resources/xsl/RNG2Schtrn.xsl and + src/lxml/isoschematron/resources/xsl/XSD2Schtrn.xsl) diff --git a/intelliaide/vendor/lxml-6.1.1.dist-info/top_level.txt b/intelliaide/vendor/lxml-6.1.1.dist-info/top_level.txt new file mode 100644 index 0000000..ab90481 --- /dev/null +++ b/intelliaide/vendor/lxml-6.1.1.dist-info/top_level.txt @@ -0,0 +1 @@ +lxml diff --git a/intelliaide/vendor/lxml/ElementInclude.py b/intelliaide/vendor/lxml/ElementInclude.py new file mode 100644 index 0000000..2188433 --- /dev/null +++ b/intelliaide/vendor/lxml/ElementInclude.py @@ -0,0 +1,244 @@ +# +# ElementTree +# $Id: ElementInclude.py 1862 2004-06-18 07:31:02Z Fredrik $ +# +# limited xinclude support for element trees +# +# history: +# 2003-08-15 fl created +# 2003-11-14 fl fixed default loader +# +# Copyright (c) 2003-2004 by Fredrik Lundh. All rights reserved. +# +# fredrik@pythonware.com +# http://www.pythonware.com +# +# -------------------------------------------------------------------- +# The ElementTree toolkit is +# +# Copyright (c) 1999-2004 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# -------------------------------------------------------------------- + +""" +Limited XInclude support for the ElementTree package. + +While lxml.etree has full support for XInclude (see +`etree.ElementTree.xinclude()`), this module provides a simpler, pure +Python, ElementTree compatible implementation that supports a simple +form of custom URL resolvers. +""" + +from lxml import etree +try: + from urlparse import urljoin + from urllib2 import urlopen +except ImportError: + # Python 3 + from urllib.parse import urljoin + from urllib.request import urlopen + +XINCLUDE = "{http://www.w3.org/2001/XInclude}" + +XINCLUDE_INCLUDE = XINCLUDE + "include" +XINCLUDE_FALLBACK = XINCLUDE + "fallback" +XINCLUDE_ITER_TAG = XINCLUDE + "*" + +# For security reasons, the inclusion depth is limited to this read-only value by default. +DEFAULT_MAX_INCLUSION_DEPTH = 6 + + +## +# Fatal include error. + +class FatalIncludeError(etree.LxmlSyntaxError): + pass + + +class LimitedRecursiveIncludeError(FatalIncludeError): + pass + + +## +# ET compatible default loader. +# This loader reads an included resource from disk. +# +# @param href Resource reference. +# @param parse Parse mode. Either "xml" or "text". +# @param encoding Optional text encoding. +# @return The expanded resource. If the parse mode is "xml", this +# is an ElementTree instance. If the parse mode is "text", this +# is a Unicode string. If the loader fails, it can return None +# or raise an IOError exception. +# @throws IOError If the loader fails to load the resource. + +def default_loader(href, parse, encoding=None): + file = open(href, 'rb') + if parse == "xml": + data = etree.parse(file).getroot() + else: + data = file.read() + if not encoding: + encoding = 'utf-8' + data = data.decode(encoding) + file.close() + return data + + +## +# Default loader used by lxml.etree - handles custom resolvers properly +# + +def _lxml_default_loader(href, parse, encoding=None, parser=None): + if parse == "xml": + data = etree.parse(href, parser).getroot() + else: + if "://" in href: + f = urlopen(href) + else: + f = open(href, 'rb') + data = f.read() + f.close() + if not encoding: + encoding = 'utf-8' + data = data.decode(encoding) + return data + + +## +# Wrapper for ET compatibility - drops the parser + +def _wrap_et_loader(loader): + def load(href, parse, encoding=None, parser=None): + return loader(href, parse, encoding) + return load + + +## +# Expand XInclude directives. +# +# @param elem Root element. +# @param loader Optional resource loader. If omitted, it defaults +# to {@link default_loader}. If given, it should be a callable +# that implements the same interface as default_loader. +# @param base_url The base URL of the original file, to resolve +# relative include file references. +# @param max_depth The maximum number of recursive inclusions. +# Limited to reduce the risk of malicious content explosion. +# Pass None to disable the limitation. +# @throws LimitedRecursiveIncludeError If the {@link max_depth} was exceeded. +# @throws FatalIncludeError If the function fails to include a given +# resource, or if the tree contains malformed XInclude elements. +# @throws IOError If the function fails to load a given resource. +# @returns the node or its replacement if it was an XInclude node + +def include(elem, loader=None, base_url=None, + max_depth=DEFAULT_MAX_INCLUSION_DEPTH): + if max_depth is None: + max_depth = -1 + elif max_depth < 0: + raise ValueError("expected non-negative depth or None for 'max_depth', got %r" % max_depth) + + if base_url is None: + if hasattr(elem, 'getroot'): + tree = elem + elem = elem.getroot() + else: + tree = elem.getroottree() + if hasattr(tree, 'docinfo'): + base_url = tree.docinfo.URL + elif hasattr(elem, 'getroot'): + elem = elem.getroot() + _include(elem, loader, base_url, max_depth) + + +def _include(elem, loader=None, base_url=None, + max_depth=DEFAULT_MAX_INCLUSION_DEPTH, _parent_hrefs=None): + if loader is not None: + load_include = _wrap_et_loader(loader) + else: + load_include = _lxml_default_loader + + if _parent_hrefs is None: + _parent_hrefs = set() + + parser = elem.getroottree().parser + + include_elements = list( + elem.iter(XINCLUDE_ITER_TAG)) + + for e in include_elements: + if e.tag == XINCLUDE_INCLUDE: + # process xinclude directive + href = urljoin(base_url, e.get("href")) + parse = e.get("parse", "xml") + parent = e.getparent() + if parse == "xml": + if href in _parent_hrefs: + raise FatalIncludeError( + "recursive include of %r detected" % href + ) + if max_depth == 0: + raise LimitedRecursiveIncludeError( + "maximum xinclude depth reached when including file %s" % href) + node = load_include(href, parse, parser=parser) + if node is None: + raise FatalIncludeError( + "cannot load %r as %r" % (href, parse) + ) + node = _include(node, loader, href, max_depth - 1, {href} | _parent_hrefs) + if e.tail: + node.tail = (node.tail or "") + e.tail + if parent is None: + return node # replaced the root node! + parent.replace(e, node) + elif parse == "text": + text = load_include(href, parse, encoding=e.get("encoding")) + if text is None: + raise FatalIncludeError( + "cannot load %r as %r" % (href, parse) + ) + predecessor = e.getprevious() + if predecessor is not None: + predecessor.tail = (predecessor.tail or "") + text + elif parent is None: + return text # replaced the root node! + else: + parent.text = (parent.text or "") + text + (e.tail or "") + parent.remove(e) + else: + raise FatalIncludeError( + "unknown parse type in xi:include tag (%r)" % parse + ) + elif e.tag == XINCLUDE_FALLBACK: + parent = e.getparent() + if parent is not None and parent.tag != XINCLUDE_INCLUDE: + raise FatalIncludeError( + "xi:fallback tag must be child of xi:include (%r)" % e.tag + ) + else: + raise FatalIncludeError( + "Invalid element found in XInclude namespace (%r)" % e.tag + ) + return elem diff --git a/intelliaide/vendor/lxml/__init__.py b/intelliaide/vendor/lxml/__init__.py new file mode 100644 index 0000000..6c821ac --- /dev/null +++ b/intelliaide/vendor/lxml/__init__.py @@ -0,0 +1,22 @@ +# this is a package + +__version__ = "6.1.1" + + +def get_include(): + """ + Returns a list of header include paths (for lxml itself, libxml2 + and libxslt) needed to compile C code against lxml if it was built + with statically linked libraries. + """ + import os + lxml_path = __path__[0] + include_path = os.path.join(lxml_path, 'includes') + includes = [include_path, lxml_path] + + for name in os.listdir(include_path): + path = os.path.join(include_path, name) + if os.path.isdir(path): + includes.append(path) + + return includes diff --git a/intelliaide/vendor/lxml/_elementpath.cpython-312-x86_64-linux-gnu.so b/intelliaide/vendor/lxml/_elementpath.cpython-312-x86_64-linux-gnu.so new file mode 100755 index 0000000..3e602bb Binary files /dev/null and b/intelliaide/vendor/lxml/_elementpath.cpython-312-x86_64-linux-gnu.so differ diff --git a/intelliaide/vendor/lxml/_elementpath.py b/intelliaide/vendor/lxml/_elementpath.py new file mode 100644 index 0000000..760a1e0 --- /dev/null +++ b/intelliaide/vendor/lxml/_elementpath.py @@ -0,0 +1,343 @@ +# cython: language_level=3 + +# +# ElementTree +# $Id: ElementPath.py 3375 2008-02-13 08:05:08Z fredrik $ +# +# limited xpath support for element trees +# +# history: +# 2003-05-23 fl created +# 2003-05-28 fl added support for // etc +# 2003-08-27 fl fixed parsing of periods in element names +# 2007-09-10 fl new selection engine +# 2007-09-12 fl fixed parent selector +# 2007-09-13 fl added iterfind; changed findall to return a list +# 2007-11-30 fl added namespaces support +# 2009-10-30 fl added child element value filter +# +# Copyright (c) 2003-2009 by Fredrik Lundh. All rights reserved. +# +# fredrik@pythonware.com +# http://www.pythonware.com +# +# -------------------------------------------------------------------- +# The ElementTree toolkit is +# +# Copyright (c) 1999-2009 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# -------------------------------------------------------------------- + +## +# Implementation module for XPath support. There's usually no reason +# to import this module directly; the ElementTree does this for +# you, if needed. +## + + +import re + +xpath_tokenizer_re = re.compile( + "(" + "'[^']*'|\"[^\"]*\"|" + "::|" + "//?|" + r"\.\.|" + r"\(\)|" + r"[/.*:\[\]\(\)@=])|" + r"((?:\{[^}]+\})?[^/\[\]\(\)@=\s]+)|" + r"\s+" + ) + +def xpath_tokenizer(pattern, namespaces=None, with_prefixes=True): + # ElementTree uses '', lxml used None originally. + default_namespace = (namespaces.get(None) or namespaces.get('')) if namespaces else None + parsing_attribute = False + for token in xpath_tokenizer_re.findall(pattern): + ttype, tag = token + if tag and tag[0] != "{": + if ":" in tag and with_prefixes: + prefix, uri = tag.split(":", 1) + try: + if not namespaces: + raise KeyError + yield ttype, "{%s}%s" % (namespaces[prefix], uri) + except KeyError: + raise SyntaxError("prefix %r not found in prefix map" % prefix) + elif tag.isdecimal(): + yield token # index + elif default_namespace and not parsing_attribute: + yield ttype, "{%s}%s" % (default_namespace, tag) + else: + yield token + parsing_attribute = False + else: + yield token + parsing_attribute = ttype == '@' + + +def prepare_child(next, token): + tag = token[1] + def select(result): + for elem in result: + yield from elem.iterchildren(tag) + return select + +def prepare_star(next, token): + def select(result): + for elem in result: + yield from elem.iterchildren('*') + return select + +def prepare_self(next, token): + def select(result): + return result + return select + +def prepare_descendant(next, token): + token = next() + if token[0] == "*": + tag = "*" + elif not token[0]: + tag = token[1] + else: + raise SyntaxError("invalid descendant") + def select(result): + for elem in result: + yield from elem.iterdescendants(tag) + return select + +def prepare_parent(next, token): + def select(result): + for elem in result: + parent = elem.getparent() + if parent is not None: + yield parent + return select + +def prepare_predicate(next, token): + # FIXME: replace with real parser!!! refs: + # http://effbot.org/zone/simple-iterator-parser.htm + # http://javascript.crockford.com/tdop/tdop.html + signature = '' + predicate = [] + while 1: + token = next() + if token[0] == "]": + break + if token == ('', ''): + # ignore whitespace + continue + if token[0] and token[0][:1] in "'\"": + token = "'", token[0][1:-1] + signature += token[0] or "-" + predicate.append(token[1]) + + # use signature to determine predicate type + if signature == "@-": + # [@attribute] predicate + key = predicate[1] + def select(result): + for elem in result: + if elem.get(key) is not None: + yield elem + return select + if signature == "@-='": + # [@attribute='value'] + key = predicate[1] + value = predicate[-1] + def select(result): + for elem in result: + if elem.get(key) == value: + yield elem + return select + if signature == "-" and not re.match(r"-?\d+$", predicate[0]): + # [tag] + tag = predicate[0] + def select(result): + for elem in result: + for _ in elem.iterchildren(tag): + yield elem + break + return select + if signature == ".='" or (signature == "-='" and not re.match(r"-?\d+$", predicate[0])): + # [.='value'] or [tag='value'] + tag = predicate[0] + value = predicate[-1] + if tag: + def select(result): + for elem in result: + for e in elem.iterchildren(tag): + if "".join(e.itertext()) == value: + yield elem + break + else: + def select(result): + for elem in result: + if "".join(elem.itertext()) == value: + yield elem + return select + if signature == "-" or signature == "-()" or signature == "-()-": + # [index] or [last()] or [last()-index] + if signature == "-": + # [index] + index = int(predicate[0]) - 1 + if index < 0: + if index == -1: + raise SyntaxError( + "indices in path predicates are 1-based, not 0-based") + else: + raise SyntaxError("path index >= 1 expected") + else: + if predicate[0] != "last": + raise SyntaxError("unsupported function") + if signature == "-()-": + try: + index = int(predicate[2]) - 1 + except ValueError: + raise SyntaxError("unsupported expression") + else: + index = -1 + def select(result): + for elem in result: + parent = elem.getparent() + if parent is None: + continue + try: + # FIXME: what if the selector is "*" ? + elems = list(parent.iterchildren(elem.tag)) + if elems[index] is elem: + yield elem + except IndexError: + pass + return select + raise SyntaxError("invalid predicate") + +ops = { + "": prepare_child, + "*": prepare_star, + ".": prepare_self, + "..": prepare_parent, + "//": prepare_descendant, + "[": prepare_predicate, +} + + +# -------------------------------------------------------------------- + +_cache = {} + + +def _build_path_iterator(path, namespaces, with_prefixes=True): + """compile selector pattern""" + if path[-1:] == "/": + path += "*" # implicit all (FIXME: keep this?) + + cache_key = (path,) + if namespaces: + # lxml originally used None for the default namespace but ElementTree uses the + # more convenient (all-strings-dict) empty string, so we support both here, + # preferring the more convenient '', as long as they aren't ambiguous. + if None in namespaces: + if '' in namespaces and namespaces[None] != namespaces['']: + raise ValueError("Ambiguous default namespace provided: %r versus %r" % ( + namespaces[None], namespaces[''])) + cache_key += (namespaces[None],) + tuple(sorted( + item for item in namespaces.items() if item[0] is not None)) + else: + cache_key += tuple(sorted(namespaces.items())) + + try: + return _cache[cache_key] + except KeyError: + pass + if len(_cache) > 100: + _cache.clear() + + if path[:1] == "/": + raise SyntaxError("cannot use absolute path on element") + stream = iter(xpath_tokenizer(path, namespaces, with_prefixes=with_prefixes)) + try: + _next = stream.next + except AttributeError: + # Python 3 + _next = stream.__next__ + try: + token = _next() + except StopIteration: + raise SyntaxError("empty path expression") + selector = [] + while 1: + try: + selector.append(ops[token[0]](_next, token)) + except StopIteration: + raise SyntaxError("invalid path") + try: + token = _next() + if token[0] == "/": + token = _next() + except StopIteration: + break + _cache[cache_key] = selector + return selector + + +## +# Iterate over the matching nodes + +def iterfind(elem, path, namespaces=None, with_prefixes=True): + selector = _build_path_iterator(path, namespaces, with_prefixes=with_prefixes) + result = iter((elem,)) + for select in selector: + result = select(result) + return result + + +## +# Find first matching object. + +def find(elem, path, namespaces=None, with_prefixes=True): + it = iterfind(elem, path, namespaces, with_prefixes=with_prefixes) + try: + return next(it) + except StopIteration: + return None + + +## +# Find all matching objects. + +def findall(elem, path, namespaces=None, with_prefixes=True): + return list(iterfind(elem, path, namespaces)) + + +## +# Find text for first matching object. + +def findtext(elem, path, default=None, namespaces=None, with_prefixes=True): + el = find(elem, path, namespaces, with_prefixes=with_prefixes) + if el is None: + return default + else: + return el.text or '' diff --git a/intelliaide/vendor/lxml/apihelpers.pxi b/intelliaide/vendor/lxml/apihelpers.pxi new file mode 100644 index 0000000..87a27d9 --- /dev/null +++ b/intelliaide/vendor/lxml/apihelpers.pxi @@ -0,0 +1,1819 @@ +# Private/public helper functions for API functions + +from lxml.includes cimport uri + + +cdef void displayNode(xmlNode* c_node, indent) noexcept: + # to help with debugging + cdef xmlNode* c_child + try: + print(indent * ' ', c_node) + c_child = c_node.children + while c_child is not NULL: + displayNode(c_child, indent + 1) + c_child = c_child.next + finally: + return # swallow any exceptions + +cdef inline bint _isHtmlDocument(_Element element) except -1: + cdef xmlNode* c_node = element._c_node + return ( + c_node is not NULL and c_node.doc is not NULL and + c_node.doc.properties & tree.XML_DOC_HTML != 0 + ) + +cdef inline int _assertValidNode(_Element element) except -1: + assert element._c_node is not NULL, "invalid Element proxy at %s" % id(element) + +cdef inline int _assertValidDoc(_Document doc) except -1: + assert doc._c_doc is not NULL, "invalid Document proxy at %s" % id(doc) + +cdef _Document _documentOrRaise(object input): + """Call this to get the document of a _Document, _ElementTree or _Element + object, or to raise an exception if it can't be determined. + + Should be used in all API functions for consistency. + """ + cdef _Document doc + if isinstance(input, _ElementTree): + if (<_ElementTree>input)._context_node is not None: + doc = (<_ElementTree>input)._context_node._doc + else: + doc = None + elif isinstance(input, _Element): + doc = (<_Element>input)._doc + elif isinstance(input, _Document): + doc = <_Document>input + else: + raise TypeError, f"Invalid input object: {python._fqtypename(input).decode('utf8')}" + if doc is None: + raise ValueError, f"Input object has no document: {python._fqtypename(input).decode('utf8')}" + _assertValidDoc(doc) + return doc + +cdef _Element _rootNodeOrRaise(object input): + """Call this to get the root node of a _Document, _ElementTree or + _Element object, or to raise an exception if it can't be determined. + + Should be used in all API functions for consistency. + """ + cdef _Element node + if isinstance(input, _ElementTree): + node = (<_ElementTree>input)._context_node + elif isinstance(input, _Element): + node = <_Element>input + elif isinstance(input, _Document): + node = (<_Document>input).getroot() + else: + raise TypeError, f"Invalid input object: {python._fqtypename(input).decode('utf8')}" + if (node is None or not node._c_node or + node._c_node.type != tree.XML_ELEMENT_NODE): + raise ValueError, f"Input object is not an XML element: {python._fqtypename(input).decode('utf8')}" + _assertValidNode(node) + return node + +cdef bint _isAncestorOrSame(xmlNode* c_ancestor, xmlNode* c_node) noexcept: + while c_node: + if c_node is c_ancestor: + return True + c_node = c_node.parent + return False + +cdef _Element _makeElement(tag, xmlDoc* c_doc, _Document doc, + _BaseParser parser, text, tail, attrib, nsmap, + dict extra_attrs): + """Create a new element and initialize text content, namespaces and + attributes. + + This helper function will reuse as much of the existing document as + possible: + + If 'parser' is None, the parser will be inherited from 'doc' or the + default parser will be used. + + If 'doc' is None, 'c_doc' is used to create a new _Document and the new + element is made its root node. + + If 'c_doc' is also NULL, a new xmlDoc will be created. + """ + cdef xmlNode* c_node + if doc is not None: + c_doc = doc._c_doc + ns_utf, name_utf = _getNsTag(tag) + if parser is not None and parser._for_html: + _htmlTagValidOrRaise(name_utf) + if c_doc is NULL: + c_doc = _newHTMLDoc() + else: + _tagValidOrRaise(name_utf) + if c_doc is NULL: + c_doc = _newXMLDoc() + c_node = _createElement(c_doc, name_utf) + if c_node is NULL: + if doc is None and c_doc is not NULL: + tree.xmlFreeDoc(c_doc) + raise MemoryError() + try: + if doc is None: + tree.xmlDocSetRootElement(c_doc, c_node) + doc = _documentFactory(c_doc, parser) + if text is not None: + _setNodeText(c_node, text) + if tail is not None: + _setTailText(c_node, tail) + # add namespaces to node if necessary + _setNodeNamespaces(c_node, doc, ns_utf, nsmap) + _initNodeAttributes(c_node, doc, attrib, extra_attrs) + return _elementFactory(doc, c_node) + except: + # free allocated c_node/c_doc unless Python does it for us + if c_node.doc is not c_doc: + # node not yet in document => will not be freed by document + if tail is not None: + _removeText(c_node.next) # tail + tree.xmlFreeNode(c_node) + if doc is None: + # c_doc will not be freed by doc + tree.xmlFreeDoc(c_doc) + raise + +cdef int _initNewElement(_Element element, bint is_html, name_utf, ns_utf, + _BaseParser parser, attrib, nsmap, dict extra_attrs) except -1: + """Initialise a new Element object. + + This is used when users instantiate a Python Element subclass + directly, without it being mapped to an existing XML node. + """ + cdef xmlDoc* c_doc + cdef xmlNode* c_node + cdef _Document doc + if is_html: + _htmlTagValidOrRaise(name_utf) + c_doc = _newHTMLDoc() + else: + _tagValidOrRaise(name_utf) + c_doc = _newXMLDoc() + c_node = _createElement(c_doc, name_utf) + if c_node is NULL: + if c_doc is not NULL: + tree.xmlFreeDoc(c_doc) + raise MemoryError() + tree.xmlDocSetRootElement(c_doc, c_node) + doc = _documentFactory(c_doc, parser) + # add namespaces to node if necessary + _setNodeNamespaces(c_node, doc, ns_utf, nsmap) + _initNodeAttributes(c_node, doc, attrib, extra_attrs) + _registerProxy(element, doc, c_node) + element._init() + return 0 + +cdef _Element _makeSubElement(_Element parent, tag, text, tail, + attrib, nsmap, dict extra_attrs): + """Create a new child element and initialize text content, namespaces and + attributes. + """ + cdef xmlNode* c_node + cdef xmlDoc* c_doc + if parent is None or parent._doc is None: + return None + _assertValidNode(parent) + ns_utf, name_utf = _getNsTag(tag) + c_doc = parent._doc._c_doc + + if parent._doc._parser is not None and parent._doc._parser._for_html: + _htmlTagValidOrRaise(name_utf) + else: + _tagValidOrRaise(name_utf) + + c_node = _createElement(c_doc, name_utf) + if c_node is NULL: + raise MemoryError() + tree.xmlAddChild(parent._c_node, c_node) + + try: + if text is not None: + _setNodeText(c_node, text) + if tail is not None: + _setTailText(c_node, tail) + + # add namespaces to node if necessary + _setNodeNamespaces(c_node, parent._doc, ns_utf, nsmap) + _initNodeAttributes(c_node, parent._doc, attrib, extra_attrs) + return _elementFactory(parent._doc, c_node) + except: + # make sure we clean up in case of an error + _removeNode(parent._doc, c_node) + raise + + +cdef int _setNodeNamespaces(xmlNode* c_node, _Document doc, + object node_ns_utf, object nsmap) except -1: + """Lookup current namespace prefixes, then set namespace structure for + node (if 'node_ns_utf' was provided) and register new ns-prefix mappings. + + 'node_ns_utf' should only be passed for a newly created node. + """ + cdef xmlNs* c_ns + cdef list nsdefs + + if nsmap: + for prefix, href in _iter_nsmap(nsmap): + href_utf = _utf8(href) + _uriValidOrRaise(href_utf) + c_href = _xcstr(href_utf) + if prefix is not None: + prefix_utf = _utf8(prefix) + _prefixValidOrRaise(prefix_utf) + c_prefix = _xcstr(prefix_utf) + else: + c_prefix = NULL + # add namespace with prefix if it is not already known + c_ns = tree.xmlSearchNs(doc._c_doc, c_node, c_prefix) + if c_ns is NULL or \ + c_ns.href is NULL or \ + tree.xmlStrcmp(c_ns.href, c_href) != 0: + c_ns = tree.xmlNewNs(c_node, c_href, c_prefix) + if c_ns is NULL: + # libxml2 has two error conditions: "out of memory" and "prefix exists already". + # We ignore the latter for compatibility reasons. It currently only appears + # during namespace cleanup. + c_ns = c_node.nsDef + while c_ns is not NULL: + if c_prefix is NULL: + if c_ns.prefix is NULL: + break + elif tree.xmlStrcmp(c_ns.prefix, c_prefix) == 0: + break + c_ns = c_ns.next + else: + raise MemoryError() + if href_utf == node_ns_utf: + tree.xmlSetNs(c_node, c_ns) + node_ns_utf = None + + if node_ns_utf is not None: + _uriValidOrRaise(node_ns_utf) + doc._setNodeNs(c_node, _xcstr(node_ns_utf)) + return 0 + + +cdef dict _build_nsmap(xmlNode* c_node): + """ + Namespace prefix->URI mapping known in the context of this Element. + This includes all namespace declarations of the parents. + """ + cdef xmlNs* c_ns + nsmap = {} + while c_node is not NULL and c_node.type == tree.XML_ELEMENT_NODE: + c_ns = c_node.nsDef + while c_ns is not NULL: + if c_ns.prefix or c_ns.href: + prefix = funicodeOrNone(c_ns.prefix) + if prefix not in nsmap: + nsmap[prefix] = funicodeOrNone(c_ns.href) + c_ns = c_ns.next + c_node = c_node.parent + return nsmap + + +cdef _iter_nsmap(nsmap): + """ + Create a reproducibly ordered iterable from an nsmap mapping. + Tries to preserve an existing order and sorts if it assumes no order. + + The difference to _iter_attrib() is that None doesn't sort with strings + in Py3.x. + """ + if isinstance(nsmap, dict): + # dicts are insertion-ordered in Py3.6+ => keep the user provided order. + return nsmap.items() + if len(nsmap) <= 1: + return nsmap.items() + if isinstance(nsmap, OrderedDict): + return nsmap.items() # keep existing order + if None not in nsmap: + return sorted(nsmap.items()) + + # Move the default namespace to the end. This makes sure libxml2 + # prefers a prefix if the ns is defined redundantly on the same + # element. That way, users can work around a problem themselves + # where default namespace attributes on non-default namespaced + # elements serialise without prefix (i.e. into the non-default + # namespace). + default_ns = nsmap[None] + nsdefs = [(k, v) for k, v in nsmap.items() if k is not None] + nsdefs.sort() + nsdefs.append((None, default_ns)) + return nsdefs + + +cdef _iter_attrib(attrib): + """ + Create a reproducibly ordered iterable from an attrib mapping. + Tries to preserve an existing order and sorts if it assumes no order. + """ + # dicts are insertion-ordered in Py3.6+ => keep the user provided order. + if isinstance(attrib, (dict, _Attrib, OrderedDict)): + return attrib.items() + # assume it's an unordered mapping of some kind + return sorted(attrib.items()) + + +cdef _initNodeAttributes(xmlNode* c_node, _Document doc, attrib, dict extra): + """Initialise the attributes of an element node. + """ + cdef bint is_html + cdef xmlNs* c_ns + if attrib is not None and not hasattr(attrib, 'items'): + raise TypeError, f"Invalid attribute dictionary: {python._fqtypename(attrib).decode('utf8')}" + if not attrib and not extra: + return # nothing to do + is_html = doc._parser._for_html + seen = set() + if extra: + for name, value in extra.items(): + _addAttributeToNode(c_node, doc, is_html, name, value, seen) + if attrib: + for name, value in _iter_attrib(attrib): + _addAttributeToNode(c_node, doc, is_html, name, value, seen) + + +cdef int _addAttributeToNode(xmlNode* c_node, _Document doc, bint is_html, + name, value, set seen_tags) except -1: + ns_utf, name_utf = tag = _getNsTag(name) + if tag in seen_tags: + return 0 + seen_tags.add(tag) + if not is_html: + _attributeValidOrRaise(name_utf) + value_utf = _utf8(value) + if ns_utf is None: + new_attr = tree.xmlNewProp(c_node, _xcstr(name_utf), _xcstr(value_utf)) + else: + _uriValidOrRaise(ns_utf) + c_ns = doc._findOrBuildNodeNs(c_node, _xcstr(ns_utf), NULL, 1) + new_attr = tree.xmlNewNsProp(c_node, c_ns, _xcstr(name_utf), _xcstr(value_utf)) + if new_attr is NULL: + raise MemoryError() + return 0 + + +ctypedef struct _ns_node_ref: + xmlNs* ns + xmlNode* node + + +cdef int _collectNsDefs(xmlNode* c_element, _ns_node_ref **_c_ns_list, + size_t *_c_ns_list_len, size_t *_c_ns_list_size) except -1: + c_ns_list = _c_ns_list[0] + cdef size_t c_ns_list_len = _c_ns_list_len[0] + cdef size_t c_ns_list_size = _c_ns_list_size[0] + + c_nsdef = c_element.nsDef + while c_nsdef is not NULL: + if c_ns_list_len >= c_ns_list_size: + if c_ns_list is NULL: + c_ns_list_size = 20 + else: + c_ns_list_size *= 2 + c_nsref_ptr = <_ns_node_ref*> python.lxml_realloc( + c_ns_list, c_ns_list_size, sizeof(_ns_node_ref)) + if c_nsref_ptr is NULL: + if c_ns_list is not NULL: + python.lxml_free(c_ns_list) + _c_ns_list[0] = NULL + raise MemoryError() + c_ns_list = c_nsref_ptr + + c_ns_list[c_ns_list_len] = _ns_node_ref(c_nsdef, c_element) + c_ns_list_len += 1 + c_nsdef = c_nsdef.next + + _c_ns_list_size[0] = c_ns_list_size + _c_ns_list_len[0] = c_ns_list_len + _c_ns_list[0] = c_ns_list + + +cdef int _removeUnusedNamespaceDeclarations(xmlNode* c_element, set prefixes_to_keep) except -1: + """Remove any namespace declarations from a subtree that are not used by + any of its elements (or attributes). + + If a 'prefixes_to_keep' is provided, it must be a set of prefixes. + Any corresponding namespace mappings will not be removed as part of the cleanup. + """ + cdef xmlNode* c_node + cdef _ns_node_ref* c_ns_list = NULL + cdef size_t c_ns_list_size = 0 + cdef size_t c_ns_list_len = 0 + cdef size_t i + + if c_element.parent and c_element.parent.type == tree.XML_DOCUMENT_NODE: + # include declarations on the document node + _collectNsDefs(c_element.parent, &c_ns_list, &c_ns_list_len, &c_ns_list_size) + + tree.BEGIN_FOR_EACH_ELEMENT_FROM(c_element, c_element, 1) + # collect all new namespace declarations into the ns list + if c_element.nsDef: + _collectNsDefs(c_element, &c_ns_list, &c_ns_list_len, &c_ns_list_size) + + # remove all namespace declarations from the list that are referenced + if c_ns_list_len and c_element.type == tree.XML_ELEMENT_NODE: + c_node = c_element + while c_node and c_ns_list_len: + if c_node.ns: + for i in range(c_ns_list_len): + if c_node.ns is c_ns_list[i].ns: + c_ns_list_len -= 1 + c_ns_list[i] = c_ns_list[c_ns_list_len] + #c_ns_list[c_ns_list_len] = _ns_node_ref(NULL, NULL) + break + if c_node is c_element: + # continue with attributes + c_node = c_element.properties + else: + c_node = c_node.next + tree.END_FOR_EACH_ELEMENT_FROM(c_element) + + if c_ns_list is NULL: + return 0 + + # free all namespace declarations that remained in the list, + # except for those we should keep explicitly + cdef xmlNs* c_nsdef + for i in range(c_ns_list_len): + if prefixes_to_keep is not None: + if c_ns_list[i].ns.prefix and c_ns_list[i].ns.prefix in prefixes_to_keep: + continue + c_node = c_ns_list[i].node + c_nsdef = c_node.nsDef + if c_nsdef is c_ns_list[i].ns: + c_node.nsDef = c_node.nsDef.next + else: + while c_nsdef.next is not c_ns_list[i].ns: + c_nsdef = c_nsdef.next + c_nsdef.next = c_nsdef.next.next + tree.xmlFreeNs(c_ns_list[i].ns) + + if c_ns_list is not NULL: + python.lxml_free(c_ns_list) + return 0 + +cdef xmlNs* _searchNsByHref(xmlNode* c_node, const_xmlChar* c_href, bint is_attribute) noexcept: + """Search a namespace declaration that covers a node (element or + attribute). + + For attributes, try to find a prefixed namespace declaration + instead of the default namespaces. This helps in supporting + round-trips for attributes on elements with a different namespace. + """ + cdef xmlNs* c_ns + cdef xmlNs* c_default_ns = NULL + cdef xmlNode* c_element + if c_href is NULL or c_node is NULL or c_node.type == tree.XML_ENTITY_REF_NODE: + return NULL + if tree.xmlStrcmp(c_href, tree.XML_XML_NAMESPACE) == 0: + # no special cases here, let libxml2 handle this + return tree.xmlSearchNsByHref(c_node.doc, c_node, c_href) + if c_node.type == tree.XML_ATTRIBUTE_NODE: + is_attribute = 1 + while c_node is not NULL and c_node.type != tree.XML_ELEMENT_NODE: + c_node = c_node.parent + c_element = c_node + while c_node is not NULL: + if c_node.type == tree.XML_ELEMENT_NODE: + c_ns = c_node.nsDef + while c_ns is not NULL: + if c_ns.href is not NULL and tree.xmlStrcmp(c_href, c_ns.href) == 0: + if c_ns.prefix is NULL and is_attribute: + # for attributes, continue searching a named + # prefix, but keep the first default namespace + # declaration that we found + if c_default_ns is NULL: + c_default_ns = c_ns + elif tree.xmlSearchNs( + c_element.doc, c_element, c_ns.prefix) is c_ns: + # start node is in namespace scope => found! + return c_ns + c_ns = c_ns.next + if c_node is not c_element and c_node.ns is not NULL: + # optimise: the node may have the namespace itself + c_ns = c_node.ns + if c_ns.href is not NULL and tree.xmlStrcmp(c_href, c_ns.href) == 0: + if c_ns.prefix is NULL and is_attribute: + # for attributes, continue searching a named + # prefix, but keep the first default namespace + # declaration that we found + if c_default_ns is NULL: + c_default_ns = c_ns + elif tree.xmlSearchNs( + c_element.doc, c_element, c_ns.prefix) is c_ns: + # start node is in namespace scope => found! + return c_ns + c_node = c_node.parent + # nothing found => use a matching default namespace or fail + if c_default_ns is not NULL: + if tree.xmlSearchNs(c_element.doc, c_element, NULL) is c_default_ns: + return c_default_ns + return NULL + +cdef int _replaceNodeByChildren(_Document doc, xmlNode* c_node) except -1: + # NOTE: this does not deallocate the node, just unlink it! + cdef xmlNode* c_parent + cdef xmlNode* c_child + if c_node.children is NULL: + tree.xmlUnlinkNode(c_node) + return 0 + + c_parent = c_node.parent + # fix parent links of children + c_child = c_node.children + while c_child is not NULL: + c_child.parent = c_parent + c_child = c_child.next + + # fix namespace references of children if their parent's namespace + # declarations get lost + if c_node.nsDef is not NULL: + c_child = c_node.children + while c_child is not NULL: + moveNodeToDocument(doc, doc._c_doc, c_child) + c_child = c_child.next + + # fix sibling links to/from child slice + if c_node.prev is NULL: + c_parent.children = c_node.children + else: + c_node.prev.next = c_node.children + c_node.children.prev = c_node.prev + if c_node.next is NULL: + c_parent.last = c_node.last + else: + c_node.next.prev = c_node.last + c_node.last.next = c_node.next + + # unlink c_node + c_node.children = c_node.last = NULL + c_node.parent = c_node.next = c_node.prev = NULL + return 0 + +cdef unicode _attributeValue(xmlNode* c_element, xmlAttr* c_attrib_node): + c_href = _getNs(c_attrib_node) + value = tree.xmlGetNsProp(c_element, c_attrib_node.name, c_href) + try: + result = funicode(value) + finally: + tree.xmlFree(value) + return result + +cdef unicode _attributeValueFromNsName(xmlNode* c_element, + const_xmlChar* c_href, const_xmlChar* c_name): + c_result = tree.xmlGetNsProp(c_element, c_name, c_href) + if c_result is NULL: + return None + try: + result = funicode(c_result) + finally: + tree.xmlFree(c_result) + return result + +cdef object _getNodeAttributeValue(xmlNode* c_node, key, default): + ns, tag = _getNsTag(key) + c_href = NULL if ns is None else _xcstr(ns) + c_result = tree.xmlGetNsProp(c_node, _xcstr(tag), c_href) + if c_result is NULL: + # XXX free namespace that is not in use..? + return default + try: + result = funicode(c_result) + finally: + tree.xmlFree(c_result) + return result + +cdef inline object _getAttributeValue(_Element element, key, default): + return _getNodeAttributeValue(element._c_node, key, default) + +cdef int _setAttributeValue(_Element element, key, value) except -1: + cdef const_xmlChar* c_value + cdef xmlNs* c_ns + ns, tag = _getNsTag(key) + is_html = element._doc._parser._for_html + if not is_html: + _attributeValidOrRaise(tag) + c_tag = _xcstr(tag) + if value is None and is_html: + c_value = NULL + else: + if isinstance(value, QName): + value = _resolveQNameText(element, value) + else: + value = _utf8(value) + c_value = _xcstr(value) + if ns is None: + c_ns = NULL + else: + c_ns = element._doc._findOrBuildNodeNs(element._c_node, _xcstr(ns), NULL, 1) + tree.xmlSetNsProp(element._c_node, c_ns, c_tag, c_value) + return 0 + +cdef int _delAttribute(_Element element, key) except -1: + ns, tag = _getNsTag(key) + c_href = NULL if ns is None else _xcstr(ns) + if _delAttributeFromNsName(element._c_node, c_href, _xcstr(tag)): + raise KeyError, key + return 0 + +cdef int _delAttributeFromNsName(xmlNode* c_node, const_xmlChar* c_href, const_xmlChar* c_name) noexcept: + c_attr = tree.xmlHasNsProp(c_node, c_name, c_href) + if c_attr is NULL: + # XXX free namespace that is not in use..? + return -1 + tree.xmlRemoveProp(c_attr) + return 0 + +cdef list _collectAttributes(xmlNode* c_node, int collecttype): + """Collect all attributes of a node in a list. Depending on collecttype, + it collects either the name (1), the value (2) or the name-value tuples. + """ + cdef Py_ssize_t count + c_attr = c_node.properties + count = 0 + while c_attr is not NULL: + if c_attr.type == tree.XML_ATTRIBUTE_NODE: + count += 1 + c_attr = c_attr.next + + if not count: + return [] + + attributes = [None] * count + c_attr = c_node.properties + count = 0 + while c_attr is not NULL: + if c_attr.type == tree.XML_ATTRIBUTE_NODE: + if collecttype == 1: + item = _namespacedName(c_attr) + elif collecttype == 2: + item = _attributeValue(c_node, c_attr) + else: + item = (_namespacedName(c_attr), + _attributeValue(c_node, c_attr)) + attributes[count] = item + count += 1 + c_attr = c_attr.next + return attributes + +cdef object __RE_XML_ENCODING = re.compile( + r'^(<\?xml[^>]+)\s+encoding\s*=\s*["\'][^"\']*["\'](\s*\?>|)', re.U) + +cdef object __REPLACE_XML_ENCODING = __RE_XML_ENCODING.sub +cdef object __HAS_XML_ENCODING = __RE_XML_ENCODING.match + +cdef object _stripEncodingDeclaration(object xml_string): + # this is a hack to remove the XML encoding declaration from unicode + return __REPLACE_XML_ENCODING(r'\g<1>\g<2>', xml_string) + +cdef bint _hasEncodingDeclaration(object xml_string) except -1: + # check if a (unicode) string has an XML encoding declaration + return __HAS_XML_ENCODING(xml_string) is not None + +cdef inline bint _hasText(xmlNode* c_node) noexcept: + return c_node is not NULL and _textNodeOrSkip(c_node.children) is not NULL + +cdef inline bint _hasTail(xmlNode* c_node) noexcept: + return c_node is not NULL and _textNodeOrSkip(c_node.next) is not NULL + +cdef inline bint _hasNonWhitespaceTail(xmlNode* c_node) except -1: + return _hasNonWhitespaceText(c_node, tail=True) + +cdef bint _hasNonWhitespaceText(xmlNode* c_node, bint tail=False) except -1: + c_text_node = c_node and _textNodeOrSkip(c_node.next if tail else c_node.children) + if c_text_node is NULL: + return False + while c_text_node is not NULL: + if c_text_node.content[0] != c'\0' and not _collectText(c_text_node).isspace(): + return True + c_text_node = _textNodeOrSkip(c_text_node.next) + return False + +cdef unicode _collectText(xmlNode* c_node): + """Collect all text nodes and return them as a unicode string. + + Start collecting at c_node. + + If there was no text to collect, return None + """ + cdef Py_ssize_t scount + cdef xmlChar* c_text + cdef xmlNode* c_node_cur + # check for multiple text nodes + scount = 0 + c_text = NULL + c_node_cur = c_node = _textNodeOrSkip(c_node) + while c_node_cur is not NULL: + if c_node_cur.content[0] != c'\0': + c_text = c_node_cur.content + scount += 1 + c_node_cur = _textNodeOrSkip(c_node_cur.next) + + # handle two most common cases first + if c_text is NULL: + return '' if scount > 0 else None + if scount == 1: + return funicode(c_text) + + # the rest is not performance critical anymore + result = b'' + while c_node is not NULL: + result += c_node.content + c_node = _textNodeOrSkip(c_node.next) + return funicode(result) + +cdef void _removeText(xmlNode* c_node) noexcept: + """Remove all text nodes. + + Start removing at c_node. + """ + cdef xmlNode* c_next + c_node = _textNodeOrSkip(c_node) + while c_node is not NULL: + c_next = _textNodeOrSkip(c_node.next) + tree.xmlUnlinkNode(c_node) + tree.xmlFreeNode(c_node) + c_node = c_next + +cdef xmlNode* _createTextNode(xmlDoc* doc, value) except NULL: + cdef xmlNode* c_text_node + if isinstance(value, CDATA): + c_text_node = tree.xmlNewCDataBlock( + doc, _xcstr((value)._utf8_data), + python.PyBytes_GET_SIZE((value)._utf8_data)) + else: + text = _utf8(value) + c_text_node = tree.xmlNewDocText(doc, _xcstr(text)) + if not c_text_node: + raise MemoryError() + return c_text_node + +cdef int _setNodeText(xmlNode* c_node, value) except -1: + # remove all text nodes at the start first + _removeText(c_node.children) + if value is None: + return 0 + # now add new text node with value at start + c_text_node = _createTextNode(c_node.doc, value) + if c_node.children is NULL: + tree.xmlAddChild(c_node, c_text_node) + else: + tree.xmlAddPrevSibling(c_node.children, c_text_node) + return 0 + +cdef int _setTailText(xmlNode* c_node, value) except -1: + # remove all text nodes at the start first + _removeText(c_node.next) + if value is None: + return 0 + # now append new text node with value + c_text_node = _createTextNode(c_node.doc, value) + tree.xmlAddNextSibling(c_node, c_text_node) + return 0 + +cdef bytes _resolveQNameText(_Element element, value): + cdef xmlNs* c_ns + ns, tag = _getNsTag(value) + if ns is None: + return tag + else: + c_ns = element._doc._findOrBuildNodeNs( + element._c_node, _xcstr(ns), NULL, 0) + return python.PyBytes_FromFormat('%s:%s', c_ns.prefix, _cstr(tag)) + +cdef inline bint _hasChild(xmlNode* c_node) noexcept: + return c_node is not NULL and _findChildForwards(c_node, 0) is not NULL + +cdef inline Py_ssize_t _countElements(xmlNode* c_node) noexcept: + "Counts the elements within the following siblings and the node itself." + cdef Py_ssize_t count + count = 0 + while c_node is not NULL: + if _isElement(c_node): + count += 1 + c_node = c_node.next + return count + + +cdef int _findChildSlice( + slice sliceobject, xmlNode* c_parent, + xmlNode** c_start_node, Py_ssize_t* c_step, Py_ssize_t* c_length) except -1: + """Resolve a children slice. + + Returns the start node, step size and the slice length in the + pointer arguments. + """ + cdef Py_ssize_t start = 0, stop = 0, childcount + childcount = _countElements(c_parent.children) + if childcount == 0: + c_start_node[0] = NULL + c_length[0] = 0 + if sliceobject.step is None: + c_step[0] = 1 + else: + python._PyEval_SliceIndex(sliceobject.step, c_step) + return 0 + + python.PySlice_GetIndicesEx( + sliceobject, childcount, &start, &stop, c_step, c_length) + + if start > childcount // 2: + c_start_node[0] = _findChildBackwards(c_parent, childcount - start - 1) + else: + c_start_node[0] = _findChild(c_parent, start) + return 0 + + +cdef bint _isFullSlice(slice sliceobject) except -1: + """Conservative guess if this slice is a full slice as in ``s[:]``. + """ + cdef Py_ssize_t step = 0 + if sliceobject is None: + return 0 + if sliceobject.start is None and \ + sliceobject.stop is None: + if sliceobject.step is None: + return 1 + python._PyEval_SliceIndex(sliceobject.step, &step) + if step == 1: + return 1 + return 0 + return 0 + +cdef _collectChildren(_Element element): + cdef xmlNode* c_node + cdef list result = [] + c_node = element._c_node.children + if c_node is not NULL: + if not _isElement(c_node): + c_node = _nextElement(c_node) + while c_node is not NULL: + result.append(_elementFactory(element._doc, c_node)) + c_node = _nextElement(c_node) + return result + +cdef inline xmlNode* _findChild(xmlNode* c_node, Py_ssize_t index) noexcept: + if index < 0: + return _findChildBackwards(c_node, -index - 1) + else: + return _findChildForwards(c_node, index) + +cdef inline xmlNode* _findChildForwards(xmlNode* c_node, Py_ssize_t index) noexcept: + """Return child element of c_node with index, or return NULL if not found. + """ + cdef xmlNode* c_child + cdef Py_ssize_t c + c_child = c_node.children + c = 0 + while c_child is not NULL: + if _isElement(c_child): + if c == index: + return c_child + c += 1 + c_child = c_child.next + return NULL + +cdef inline xmlNode* _findChildBackwards(xmlNode* c_node, Py_ssize_t index) noexcept: + """Return child element of c_node with index, or return NULL if not found. + Search from the end. + """ + cdef xmlNode* c_child + cdef Py_ssize_t c + c_child = c_node.last + c = 0 + while c_child is not NULL: + if _isElement(c_child): + if c == index: + return c_child + c += 1 + c_child = c_child.prev + return NULL + +cdef inline xmlNode* _textNodeOrSkip(xmlNode* c_node) noexcept nogil: + """Return the node if it's a text node. Skip over ignorable nodes in a + series of text nodes. Return NULL if a non-ignorable node is found. + + This is used to skip over XInclude nodes when collecting adjacent text + nodes. + """ + while c_node is not NULL: + if c_node.type == tree.XML_TEXT_NODE or \ + c_node.type == tree.XML_CDATA_SECTION_NODE: + return c_node + elif c_node.type == tree.XML_XINCLUDE_START or \ + c_node.type == tree.XML_XINCLUDE_END: + c_node = c_node.next + else: + return NULL + return NULL + +cdef inline xmlNode* _nextElement(xmlNode* c_node) noexcept: + """Given a node, find the next sibling that is an element. + """ + if c_node is NULL: + return NULL + c_node = c_node.next + while c_node is not NULL: + if _isElement(c_node): + return c_node + c_node = c_node.next + return NULL + +cdef inline xmlNode* _previousElement(xmlNode* c_node) noexcept: + """Given a node, find the next sibling that is an element. + """ + if c_node is NULL: + return NULL + c_node = c_node.prev + while c_node is not NULL: + if _isElement(c_node): + return c_node + c_node = c_node.prev + return NULL + +cdef inline xmlNode* _parentElement(xmlNode* c_node) noexcept: + "Given a node, find the parent element." + if c_node is NULL or not _isElement(c_node): + return NULL + c_node = c_node.parent + if c_node is NULL or not _isElement(c_node): + return NULL + return c_node + +cdef inline bint _tagMatches(xmlNode* c_node, const_xmlChar* c_href, const_xmlChar* c_name) noexcept: + """Tests if the node matches namespace URI and tag name. + + A node matches if it matches both c_href and c_name. + + A node matches c_href if any of the following is true: + * c_href is NULL + * its namespace is NULL and c_href is the empty string + * its namespace string equals the c_href string + + A node matches c_name if any of the following is true: + * c_name is NULL + * its name string equals the c_name string + """ + if c_node is NULL: + return 0 + if c_node.type != tree.XML_ELEMENT_NODE: + # not an element, only succeed if we match everything + return c_name is NULL and c_href is NULL + if c_name is NULL: + if c_href is NULL: + # always match + return 1 + else: + c_node_href = _getNs(c_node) + if c_node_href is NULL: + return c_href[0] == c'\0' + else: + return tree.xmlStrcmp(c_node_href, c_href) == 0 + elif c_href is NULL: + if _getNs(c_node) is not NULL: + return 0 + return c_node.name == c_name or tree.xmlStrcmp(c_node.name, c_name) == 0 + elif c_node.name == c_name or tree.xmlStrcmp(c_node.name, c_name) == 0: + c_node_href = _getNs(c_node) + if c_node_href is NULL: + return c_href[0] == c'\0' + else: + return tree.xmlStrcmp(c_node_href, c_href) == 0 + else: + return 0 + +cdef inline bint _tagMatchesExactly(xmlNode* c_node, qname* c_qname) noexcept: + """Tests if the node matches namespace URI and tag name. + + This differs from _tagMatches() in that it does not consider a + NULL value in qname.href a wildcard, and that it expects the c_name + to be taken from the doc dict, i.e. it only compares the names by + address. + + A node matches if it matches both href and c_name of the qname. + + A node matches c_href if any of the following is true: + * its namespace is NULL and c_href is the empty string + * its namespace string equals the c_href string + + A node matches c_name if any of the following is true: + * c_name is NULL + * its name string points to the same address (!) as c_name + """ + return _nsTagMatchesExactly(_getNs(c_node), c_node.name, c_qname) + +cdef inline bint _nsTagMatchesExactly(const_xmlChar* c_node_href, + const_xmlChar* c_node_name, + qname* c_qname) noexcept: + """Tests if name and namespace URI match those of c_qname. + + This differs from _tagMatches() in that it does not consider a + NULL value in qname.href a wildcard, and that it expects the c_name + to be taken from the doc dict, i.e. it only compares the names by + address. + + A node matches if it matches both href and c_name of the qname. + + A node matches c_href if any of the following is true: + * its namespace is NULL and c_href is the empty string + * its namespace string equals the c_href string + + A node matches c_name if any of the following is true: + * c_name is NULL + * its name string points to the same address (!) as c_name + """ + cdef char* c_href + if c_qname.c_name is not NULL and c_qname.c_name is not c_node_name: + return 0 + if c_qname.href is NULL: + return 1 + c_href = python.__cstr(c_qname.href) + if c_href[0] == b'\0': + return c_node_href is NULL or c_node_href[0] == b'\0' + elif c_node_href is NULL: + return 0 + else: + return tree.xmlStrcmp(c_href, c_node_href) == 0 + +cdef Py_ssize_t _mapTagsToQnameMatchArray(xmlDoc* c_doc, list ns_tags, + qname* c_ns_tags, bint force_into_dict) except -1: + """Map a sequence of (name, namespace) pairs to a qname array for efficient + matching with _tagMatchesExactly() above. + + Note that each qname struct in the array owns its href byte string object + if it is not NULL. + """ + cdef Py_ssize_t count = 0, i, c_tag_len + cdef bytes ns, tag + cdef const_xmlChar* c_tag + + for ns, tag in ns_tags: + if tag is None: + c_tag = NULL + else: + c_tag_len = len(tag) + if c_tag_len > limits.INT_MAX: + # too long, not in the dict => not in the document + continue + elif force_into_dict: + c_tag = tree.xmlDictLookup(c_doc.dict, _xcstr(tag), c_tag_len) + if c_tag is NULL: + # clean up before raising the error + for i in xrange(count): + cpython.ref.Py_XDECREF(c_ns_tags[i].href) + raise MemoryError() + else: + c_tag = tree.xmlDictExists(c_doc.dict, _xcstr(tag), c_tag_len) + if c_tag is NULL: + # not in the dict => not in the document + continue + + c_ns_tags[count].c_name = c_tag + if ns is None: + c_ns_tags[count].href = NULL + else: + cpython.ref.Py_INCREF(ns) # keep an owned reference! + c_ns_tags[count].href = ns + count += 1 + return count + +cdef int _removeNode(_Document doc, xmlNode* c_node) except -1: + """Unlink and free a node and subnodes if possible. Otherwise, make sure + it's self-contained. + """ + cdef xmlNode* c_next + c_next = c_node.next + tree.xmlUnlinkNode(c_node) + _moveTail(c_next, c_node) + if not attemptDeallocation(c_node): + # make namespaces absolute + moveNodeToDocument(doc, c_node.doc, c_node) + return 0 + +cdef int _removeSiblings(xmlNode* c_element, tree.xmlElementType node_type, bint with_tail) except -1: + cdef xmlNode* c_node + cdef xmlNode* c_next + c_node = c_element.next + while c_node is not NULL: + c_next = _nextElement(c_node) + if c_node.type == node_type: + if with_tail: + _removeText(c_node.next) + tree.xmlUnlinkNode(c_node) + attemptDeallocation(c_node) + c_node = c_next + c_node = c_element.prev + while c_node is not NULL: + c_next = _previousElement(c_node) + if c_node.type == node_type: + if with_tail: + _removeText(c_node.next) + tree.xmlUnlinkNode(c_node) + attemptDeallocation(c_node) + c_node = c_next + return 0 + +cdef void _moveTail(xmlNode* c_tail, xmlNode* c_target) noexcept: + cdef xmlNode* c_next + # tail support: look for any text nodes trailing this node and + # move them too + c_tail = _textNodeOrSkip(c_tail) + while c_tail is not NULL: + c_next = _textNodeOrSkip(c_tail.next) + c_target = tree.xmlAddNextSibling(c_target, c_tail) + c_tail = c_next + +cdef int _copyTail(xmlNode* c_tail, xmlNode* c_target) except -1: + cdef xmlNode* c_new_tail + # tail copying support: look for any text nodes trailing this node and + # copy it to the target node + c_tail = _textNodeOrSkip(c_tail) + while c_tail is not NULL: + if c_target.doc is not c_tail.doc: + c_new_tail = tree.xmlDocCopyNode(c_tail, c_target.doc, 0) + else: + c_new_tail = tree.xmlCopyNode(c_tail, 0) + if c_new_tail is NULL: + raise MemoryError() + c_target = tree.xmlAddNextSibling(c_target, c_new_tail) + c_tail = _textNodeOrSkip(c_tail.next) + return 0 + +cdef int _copyNonElementSiblings(xmlNode* c_node, xmlNode* c_target) except -1: + cdef xmlNode* c_copy + cdef xmlNode* c_sibling = c_node + while c_sibling.prev != NULL and \ + (c_sibling.prev.type == tree.XML_PI_NODE or + c_sibling.prev.type == tree.XML_COMMENT_NODE or + c_sibling.prev.type == tree.XML_DTD_NODE): + c_sibling = c_sibling.prev + while c_sibling != c_node: + if c_sibling.type == tree.XML_DTD_NODE: + c_copy = _copyDtd(c_sibling) + if c_sibling == c_node.doc.intSubset: + c_target.doc.intSubset = c_copy + else: # c_sibling == c_node.doc.extSubset + c_target.doc.extSubset = c_copy + else: + c_copy = tree.xmlDocCopyNode(c_sibling, c_target.doc, 1) + if c_copy is NULL: + raise MemoryError() + tree.xmlAddPrevSibling(c_target, c_copy) + c_sibling = c_sibling.next + while c_sibling.next != NULL and \ + (c_sibling.next.type == tree.XML_PI_NODE or + c_sibling.next.type == tree.XML_COMMENT_NODE): + c_sibling = c_sibling.next + c_copy = tree.xmlDocCopyNode(c_sibling, c_target.doc, 1) + if c_copy is NULL: + raise MemoryError() + tree.xmlAddNextSibling(c_target, c_copy) + +cdef int _deleteSlice(_Document doc, xmlNode* c_node, + Py_ssize_t count, Py_ssize_t step) except -1: + """Delete slice, ``count`` items starting with ``c_node`` with a step + width of ``step``. + """ + cdef xmlNode* c_next + cdef Py_ssize_t c, i + cdef _node_to_node_function next_element + if c_node is NULL: + return 0 + if step > 0: + next_element = _nextElement + else: + step = -step if step != python.PY_SSIZE_T_MIN else python.PY_SSIZE_T_MAX + next_element = _previousElement + # now start deleting nodes + c = 0 + c_next = c_node + while c_node is not NULL and c < count: + for i in range(step): + c_next = next_element(c_next) + if c_next is NULL: + break + _removeNode(doc, c_node) + c += 1 + c_node = c_next + return 0 + +cdef int _replaceSlice(_Element parent, xmlNode* c_node, + Py_ssize_t slicelength, Py_ssize_t step, + bint left_to_right, elements) except -1: + """Replace the slice of ``count`` elements starting at ``c_node`` with + positive step width ``step`` by the Elements in ``elements``. The + direction is given by the boolean argument ``left_to_right``. + + ``c_node`` may be NULL to indicate the end of the children list. + """ + cdef xmlNode* c_orig_neighbour + cdef xmlNode* c_next + cdef xmlDoc* c_source_doc + cdef _Element element + cdef Py_ssize_t seqlength, i, c + cdef _node_to_node_function next_element + assert step > 0, step + if left_to_right: + next_element = _nextElement + else: + next_element = _previousElement + + if not isinstance(elements, (list, tuple)): + elements = list(elements) + + if step != 1 or not left_to_right: + # *replacing* children stepwise with list => check size! + seqlength = len(elements) + if seqlength != slicelength: + raise ValueError, f"attempt to assign sequence of size {seqlength} " \ + f"to extended slice of size {slicelength}" + + if c_node is NULL: + # no children yet => add all elements straight away + if left_to_right: + for element in elements: + assert element is not None, "Node must not be None" + _appendChild(parent, element) + else: + for element in elements: + assert element is not None, "Node must not be None" + _prependChild(parent, element) + return 0 + + # remove the elements first as some might be re-added + if left_to_right: + # L->R, remember left neighbour + c_orig_neighbour = _previousElement(c_node) + else: + # R->L, remember right neighbour + c_orig_neighbour = _nextElement(c_node) + + # We remove the original slice elements one by one. Since we hold + # a Python reference to all elements that we will insert, it is + # safe to let _removeNode() try (and fail) to free them even if + # the element itself or one of its descendents will be reinserted. + c = 0 + c_next = c_node + while c_node is not NULL and c < slicelength: + for i in range(step): + c_next = next_element(c_next) + if c_next is NULL: + break + _removeNode(parent._doc, c_node) + c += 1 + c_node = c_next + + # make sure each element is inserted only once + elements = iter(elements) + + # find the first node right of the new insertion point + if left_to_right: + if c_orig_neighbour is not NULL: + c_node = next_element(c_orig_neighbour) + else: + # before the first element + c_node = _findChildForwards(parent._c_node, 0) + elif c_orig_neighbour is NULL: + # at the end, but reversed stepping + # append one element and go to the next insertion point + for element in elements: + assert element is not None, "Node must not be None" + _appendChild(parent, element) + c_node = element._c_node + if slicelength > 0: + slicelength -= 1 + for i in range(1, step): + c_node = next_element(c_node) + if c_node is NULL: + break + break + else: + c_node = c_orig_neighbour + + if left_to_right: + # adjust step size after removing slice as we are not stepping + # over the newly inserted elements + step -= 1 + + # now insert elements where we removed them + if c_node is not NULL: + for element in elements: + assert element is not None, "Node must not be None" + _assertValidNode(element) + # move element and tail over + c_source_doc = element._c_node.doc + c_next = element._c_node.next + tree.xmlAddPrevSibling(c_node, element._c_node) + _moveTail(c_next, element._c_node) + + # integrate element into new document + moveNodeToDocument(parent._doc, c_source_doc, element._c_node) + + # stop at the end of the slice + if slicelength > 0: + slicelength -= 1 + for i in range(step): + c_node = next_element(c_node) + if c_node is NULL: + break + if c_node is NULL: + break + else: + # everything inserted + return 0 + + # append the remaining elements at the respective end + if left_to_right: + for element in elements: + assert element is not None, "Node must not be None" + _assertValidNode(element) + _appendChild(parent, element) + else: + for element in elements: + assert element is not None, "Node must not be None" + _assertValidNode(element) + _prependChild(parent, element) + + return 0 + + +cdef int _linkChild(xmlNode* c_parent, xmlNode* c_node) except -1: + """Adaptation of 'xmlAddChild()' that deep-fix the document links iteratively. + """ + assert _isElement(c_node) + c_node.parent = c_parent + if c_parent.children is NULL: + c_parent.children = c_parent.last = c_node + else: + c_node.prev = c_parent.last + c_parent.last.next = c_node + c_parent.last = c_node + + _setTreeDoc(c_node, c_parent.doc) + return 0 + + +cdef int _appendChild(_Element parent, _Element child) except -1: + """Append a new child to a parent element. + """ + c_node = child._c_node + c_source_doc = c_node.doc + # prevent cycles + if _isAncestorOrSame(c_node, parent._c_node): + raise ValueError("cannot append parent to itself") + # store possible text node + c_next = c_node.next + # move node itself + tree.xmlUnlinkNode(c_node) + # do not call xmlAddChild() here since it would deep-traverse the tree + _linkChild(parent._c_node, c_node) + _moveTail(c_next, c_node) + # uh oh, elements may be pointing to different doc when + # parent element has moved; change them too.. + moveNodeToDocument(parent._doc, c_source_doc, c_node) + return 0 + +cdef int _prependChild(_Element parent, _Element child) except -1: + """Prepend a new child to a parent element. + """ + c_node = child._c_node + c_source_doc = c_node.doc + # prevent cycles + if _isAncestorOrSame(c_node, parent._c_node): + raise ValueError("cannot append parent to itself") + # store possible text node + c_next = c_node.next + # move node itself + c_child = _findChildForwards(parent._c_node, 0) + if c_child is NULL: + tree.xmlUnlinkNode(c_node) + # do not call xmlAddChild() here since it would deep-traverse the tree + _linkChild(parent._c_node, c_node) + else: + tree.xmlAddPrevSibling(c_child, c_node) + _moveTail(c_next, c_node) + # uh oh, elements may be pointing to different doc when + # parent element has moved; change them too.. + moveNodeToDocument(parent._doc, c_source_doc, c_node) + return 0 + +cdef int _appendSibling(_Element element, _Element sibling) except -1: + """Add a new sibling behind an element. + """ + return _addSibling(element, sibling, as_next=True) + +cdef int _prependSibling(_Element element, _Element sibling) except -1: + """Add a new sibling before an element. + """ + return _addSibling(element, sibling, as_next=False) + +cdef int _addSibling(_Element element, _Element sibling, bint as_next) except -1: + c_node = sibling._c_node + c_source_doc = c_node.doc + # prevent cycles + if _isAncestorOrSame(c_node, element._c_node): + if element._c_node is c_node: + return 0 # nothing to do + raise ValueError("cannot add ancestor as sibling, please break cycle first") + # store possible text node + c_next = c_node.next + # move node itself + if as_next: + # must insert after any tail text + c_next_node = _nextElement(element._c_node) + if c_next_node is NULL: + c_next_node = element._c_node + while c_next_node.next: + c_next_node = c_next_node.next + tree.xmlAddNextSibling(c_next_node, c_node) + else: + tree.xmlAddPrevSibling(c_next_node, c_node) + else: + tree.xmlAddPrevSibling(element._c_node, c_node) + _moveTail(c_next, c_node) + # uh oh, elements may be pointing to different doc when + # parent element has moved; change them too.. + moveNodeToDocument(element._doc, c_source_doc, c_node) + return 0 + +cdef inline bint isutf8(const_xmlChar* s) noexcept: + cdef xmlChar c = s[0] + while c != c'\0': + if c & 0x80: + return True + s += 1 + c = s[0] + return False + +cdef bint isutf8l(const_xmlChar* s, size_t length) noexcept: + """ + Search for non-ASCII characters in the string, knowing its length in advance. + """ + cdef unsigned int i + cdef unsigned long non_ascii_mask + cdef const unsigned long *lptr = s + + cdef const unsigned long *end = lptr + length // sizeof(unsigned long) + if length >= sizeof(non_ascii_mask): + # Build constant 0x80808080... mask (and let the C compiler fold it). + non_ascii_mask = 0 + for i in range(sizeof(non_ascii_mask) // 2): + non_ascii_mask = (non_ascii_mask << 16) | 0x8080 + + # Advance to long-aligned character before we start reading longs. + while (s) % sizeof(unsigned long) and s < end: + if s[0] & 0x80: + return True + s += 1 + + # Read one long at a time + lptr = s + while lptr < end: + if lptr[0] & non_ascii_mask: + return True + lptr += 1 + s = lptr + + while s < (end + length % sizeof(unsigned long)): + if s[0] & 0x80: + return True + s += 1 + + return False + +cdef int _is_valid_xml_ascii(bytes pystring) except -1: + """Check if a string is XML ascii content.""" + cdef signed char ch + # When ch is a *signed* char, non-ascii characters are negative integers + # and xmlIsChar_ch does not accept them. + for ch in pystring: + if not tree.xmlIsChar_ch(ch): + return 0 + return 1 + +cdef bint _is_valid_xml_utf8(bytes pystring) except -1: + """Check if a string is like valid UTF-8 XML content.""" + cdef const_xmlChar* s = _xcstr(pystring) + cdef const_xmlChar* c_end = s + len(pystring) + cdef unsigned long next3 = 0 + if s < c_end - 2: + next3 = (s[0] << 8) | (s[1]) + + while s < c_end - 2: + next3 = 0x00ffffff & ((next3 << 8) | s[2]) + if s[0] & 0x80: + # 0xefbfbe and 0xefbfbf are utf-8 encodings of + # forbidden characters \ufffe and \uffff + if next3 == 0x00efbfbe or next3 == 0x00efbfbf: + return 0 + # 0xeda080 and 0xedbfbf are utf-8 encodings of + # \ud800 and \udfff. Anything between them (inclusive) + # is forbidden, because they are surrogate blocks in utf-16. + if 0x00eda080 <= next3 <= 0x00edbfbf: + return 0 + elif not tree.xmlIsChar_ch(s[0]): + return 0 # invalid ascii char + s += 1 + + while s < c_end: + if not s[0] & 0x80 and not tree.xmlIsChar_ch(s[0]): + return 0 # invalid ascii char + s += 1 + + return 1 + +cdef inline unicode funicodeOrNone(const_xmlChar* s): + return funicode(s) if s is not NULL else None + +cdef inline unicode funicodeOrEmpty(const_xmlChar* s): + return funicode(s) if s is not NULL else '' + +cdef unicode funicode(const_xmlChar* s): + return s.decode('UTF-8') + +cdef bytes _utf8(object s): + """Test if a string is valid user input and encode it to UTF-8. + Reject all bytes/unicode input that contains non-XML characters. + Reject all bytes input that contains non-ASCII characters. + """ + cdef int valid + cdef bytes utf8_string + if isinstance(s, unicode): + utf8_string = (s).encode('utf8') + valid = _is_valid_xml_utf8(utf8_string) + elif isinstance(s, (bytes, bytearray)): + utf8_string = s if type(s) is bytes else bytes(s) + valid = _is_valid_xml_ascii(utf8_string) + else: + raise TypeError("Argument must be bytes or unicode, got '%.200s'" % type(s).__name__) + if not valid: + raise ValueError( + "All strings must be XML compatible: Unicode or ASCII, no NULL bytes or control characters") + return utf8_string + + +cdef bytes _utf8orNone(object s): + return _utf8(s) if s is not None else None + + +cdef enum: + NO_FILE_PATH = 0 + ABS_UNIX_FILE_PATH = 1 + ABS_WIN_FILE_PATH = 2 + REL_FILE_PATH = 3 + + +cdef bint _isFilePath(const_xmlChar* c_path) noexcept: + "simple heuristic to see if a path is a filename" + cdef xmlChar c + # test if it looks like an absolute Unix path or a Windows network path + if c_path[0] == c'/': + return ABS_UNIX_FILE_PATH + + # test if it looks like an absolute Windows path or URL + if c'a' <= c_path[0] <= c'z' or c'A' <= c_path[0] <= c'Z': + c_path += 1 + if c_path[0] == c':' and c_path[1] in b'\0\\': + return ABS_WIN_FILE_PATH # C: or C:\... + + # test if it looks like a URL with scheme:// + while c'a' <= c_path[0] <= c'z' or c'A' <= c_path[0] <= c'Z': + c_path += 1 + if c_path[0] == c':' and c_path[1] == c'/' and c_path[2] == c'/': + return NO_FILE_PATH + + # assume it's a relative path + return REL_FILE_PATH + + +cdef object _getFSPathOrObject(object obj): + """ + Get the __fspath__ attribute of an object if it exists. + Otherwise, the original object is returned. + """ + if _isString(obj): + return obj + try: + return python.PyOS_FSPath(obj) + except TypeError: + return obj + + +cdef object _encodeFilename(object filename): + """Make sure a filename is 8-bit encoded (or None). + """ + if filename is None: + return None + elif isinstance(filename, bytes): + return filename + elif isinstance(filename, unicode): + filename8 = (filename).encode('utf8') + if _isFilePath(filename8): + try: + return python.PyUnicode_AsEncodedString( + filename, _C_FILENAME_ENCODING, NULL) + except UnicodeEncodeError: + pass + return filename8 + else: + raise TypeError("Argument must be string or unicode.") + +cdef object _decodeFilename(const_xmlChar* c_path): + """Make the filename a unicode string if we are in Py3. + """ + return _decodeFilenameWithLength(c_path, tree.xmlStrlen(c_path)) + +cdef object _decodeFilenameWithLength(const_xmlChar* c_path, size_t c_len): + """Make the filename a unicode string if we are in Py3. + """ + if _isFilePath(c_path): + try: + return python.PyUnicode_Decode( + c_path, c_len, _C_FILENAME_ENCODING, NULL) + except UnicodeDecodeError: + pass + try: + return (c_path)[:c_len].decode('UTF-8') + except UnicodeDecodeError: + # this is a stupid fallback, but it might still work... + return (c_path)[:c_len].decode('latin-1', 'replace') + +cdef object _encodeFilenameUTF8(object filename): + """Recode filename as UTF-8. Tries ASCII, local filesystem encoding and + UTF-8 as source encoding. + """ + cdef char* c_filename + if filename is None: + return None + elif isinstance(filename, bytes): + if not isutf8l(filename, len(filename)): + # plain ASCII! + return filename + c_filename = _cstr(filename) + try: + # try to decode with default encoding + filename = python.PyUnicode_Decode( + c_filename, len(filename), + _C_FILENAME_ENCODING, NULL) + except UnicodeDecodeError as decode_exc: + try: + # try if it's proper UTF-8 + (filename).decode('utf8') + return filename + except UnicodeDecodeError: + raise decode_exc # otherwise re-raise original exception + if isinstance(filename, unicode): + return (filename).encode('utf8') + else: + raise TypeError("Argument must be string or unicode.") + +cdef tuple _getNsTag(tag): + """Given a tag, find namespace URI and tag name. + Return None for NS uri if no namespace URI provided. + """ + return __getNsTag(tag, 0) + +cdef tuple _getNsTagWithEmptyNs(tag): + """Given a tag, find namespace URI and tag name. Return None for NS uri + if no namespace URI provided, or the empty string if namespace + part is '{}'. + """ + return __getNsTag(tag, 1) + +cdef tuple __getNsTag(tag, bint empty_ns): + cdef char* c_tag + cdef char* c_ns_end + cdef Py_ssize_t taglen + cdef Py_ssize_t nslen + cdef bytes ns = None + # _isString() is much faster than isinstance() + if not _isString(tag) and isinstance(tag, QName): + tag = (tag).text + tag = _utf8(tag) + c_tag = _cstr(tag) + if c_tag[0] == c'{': + c_tag += 1 + c_ns_end = cstring_h.strchr(c_tag, c'}') + if c_ns_end is NULL: + raise ValueError, "Invalid tag name" + nslen = c_ns_end - c_tag + taglen = python.PyBytes_GET_SIZE(tag) - nslen - 2 + if taglen == 0: + raise ValueError, "Empty tag name" + if nslen > 0: + ns = c_tag[:nslen] + elif empty_ns: + ns = b'' + tag = c_ns_end[1:taglen+1] + elif python.PyBytes_GET_SIZE(tag) == 0: + raise ValueError, "Empty tag name" + return ns, tag + +cdef inline int _pyXmlNameIsValid(name_utf8): + return _xmlNameIsValid(_xcstr(name_utf8)) and b':' not in name_utf8 + +cdef inline int _pyHtmlNameIsValid(name_utf8): + return _htmlNameIsValid(_xcstr(name_utf8)) + +cdef inline int _xmlNameIsValid(const_xmlChar* c_name) noexcept: + return tree.xmlValidateNameValue(c_name) + +cdef int _htmlNameIsValid(const_xmlChar* c_name) noexcept: + if c_name is NULL or c_name[0] == c'\0': + return 0 + while c_name[0] != c'\0': + if c_name[0] in b'&<>/"\'\t\n\x0B\x0C\r ': + return 0 + c_name += 1 + return 1 + +cdef bint _characterReferenceIsValid(const_xmlChar* c_name) noexcept: + cdef bint is_hex + if c_name[0] == c'x': + c_name += 1 + is_hex = 1 + else: + is_hex = 0 + if c_name[0] == c'\0': + return 0 + while c_name[0] != c'\0': + if c_name[0] < c'0' or c_name[0] > c'9': + if not is_hex: + return 0 + if not (c'a' <= c_name[0] <= c'f'): + if not (c'A' <= c_name[0] <= c'F'): + return 0 + c_name += 1 + return 1 + +cdef int _tagValidOrRaise(tag_utf) except -1: + if not _pyXmlNameIsValid(tag_utf): + raise ValueError(f"Invalid tag name {(tag_utf).decode('utf8')!r}") + return 0 + +cdef int _htmlTagValidOrRaise(tag_utf) except -1: + if not _pyHtmlNameIsValid(tag_utf): + raise ValueError(f"Invalid HTML tag name {(tag_utf).decode('utf8')!r}") + return 0 + +cdef int _attributeValidOrRaise(name_utf) except -1: + if not _pyXmlNameIsValid(name_utf): + raise ValueError(f"Invalid attribute name {(name_utf).decode('utf8')!r}") + return 0 + +cdef int _prefixValidOrRaise(tag_utf) except -1: + if not _pyXmlNameIsValid(tag_utf): + raise ValueError(f"Invalid namespace prefix {(tag_utf).decode('utf8')!r}") + return 0 + +cdef int _uriValidOrRaise(uri_utf) except -1: + cdef uri.xmlURI* c_uri = uri.xmlParseURI(_cstr(uri_utf)) + if c_uri is NULL: + raise ValueError(f"Invalid namespace URI {(uri_utf).decode('utf8')!r}") + uri.xmlFreeURI(c_uri) + return 0 + +cdef inline unicode _namespacedName(xmlNode* c_node): + return _namespacedNameFromNsName(_getNs(c_node), c_node.name) + + +cdef unicode _namespacedNameFromNsName(const_xmlChar* c_href, const_xmlChar* c_name): + name = funicode(c_name) + if c_href is NULL: + return name + href = funicode(c_href) + return f"{{{href}}}{name}" + + +cdef _getFilenameForFile(source): + """Given a Python File or Gzip object, give filename back. + + Returns None if not a file object. + """ + # urllib2 provides a geturl() method + try: + return source.geturl() + except: + pass + # file instances have a name attribute + try: + filename = source.name + if _isString(filename): + return os_path_abspath(filename) + except: + pass + # gzip file instances have a filename attribute (before Py3k) + try: + filename = source.filename + if _isString(filename): + return os_path_abspath(filename) + except: + pass + # can't determine filename + return None diff --git a/intelliaide/vendor/lxml/builder.cpython-312-x86_64-linux-gnu.so b/intelliaide/vendor/lxml/builder.cpython-312-x86_64-linux-gnu.so new file mode 100755 index 0000000..9dd5bf7 Binary files /dev/null and b/intelliaide/vendor/lxml/builder.cpython-312-x86_64-linux-gnu.so differ diff --git a/intelliaide/vendor/lxml/builder.py b/intelliaide/vendor/lxml/builder.py new file mode 100644 index 0000000..55d9e0d --- /dev/null +++ b/intelliaide/vendor/lxml/builder.py @@ -0,0 +1,243 @@ +# cython: language_level=2 + +# +# Element generator factory by Fredrik Lundh. +# +# Source: +# http://online.effbot.org/2006_11_01_archive.htm#et-builder +# http://effbot.python-hosting.com/file/stuff/sandbox/elementlib/builder.py +# +# -------------------------------------------------------------------- +# The ElementTree toolkit is +# +# Copyright (c) 1999-2004 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# -------------------------------------------------------------------- + +""" +The ``E`` Element factory for generating XML documents. +""" + + +import lxml.etree as ET +_QName = ET.QName + +from functools import partial + +try: + from types import GenericAlias as _GenericAlias +except ImportError: + # Python 3.8 - we only need this as return value from "__class_getitem__" + def _GenericAlias(cls, item): + return f"{cls.__name__}[{item.__name__}]" + +try: + basestring +except NameError: + basestring = str + +try: + unicode +except NameError: + unicode = str + + +class ElementMaker: + """Element generator factory. + + Unlike the ordinary Element factory, the E factory allows you to pass in + more than just a tag and some optional attributes; you can also pass in + text and other elements. The text is added as either text or tail + attributes, and elements are inserted at the right spot. Some small + examples:: + + >>> from lxml import etree as ET + >>> from lxml.builder import E + + >>> ET.tostring(E("tag")) + '' + >>> ET.tostring(E("tag", "text")) + 'text' + >>> ET.tostring(E("tag", "text", key="value")) + 'text' + >>> ET.tostring(E("tag", E("subtag", "text"), "tail")) + 'texttail' + + For simple tags, the factory also allows you to write ``E.tag(...)`` instead + of ``E('tag', ...)``:: + + >>> ET.tostring(E.tag()) + '' + >>> ET.tostring(E.tag("text")) + 'text' + >>> ET.tostring(E.tag(E.subtag("text"), "tail")) + 'texttail' + + Here's a somewhat larger example; this shows how to generate HTML + documents, using a mix of prepared factory functions for inline elements, + nested ``E.tag`` calls, and embedded XHTML fragments:: + + # some common inline elements + A = E.a + I = E.i + B = E.b + + def CLASS(v): + # helper function, 'class' is a reserved word + return {'class': v} + + page = ( + E.html( + E.head( + E.title("This is a sample document") + ), + E.body( + E.h1("Hello!", CLASS("title")), + E.p("This is a paragraph with ", B("bold"), " text in it!"), + E.p("This is another paragraph, with a ", + A("link", href="http://www.python.org"), "."), + E.p("Here are some reserved characters: ."), + ET.XML("

And finally, here is an embedded XHTML fragment.

"), + ) + ) + ) + + print ET.tostring(page) + + Here's a prettyprinted version of the output from the above script:: + + + + This is a sample document + + +

Hello!

+

This is a paragraph with bold text in it!

+

This is another paragraph, with link.

+

Here are some reserved characters: <spam&egg>.

+

And finally, here is an embedded XHTML fragment.

+ + + + For namespace support, you can pass a namespace map (``nsmap``) + and/or a specific target ``namespace`` to the ElementMaker class:: + + >>> E = ElementMaker(namespace="http://my.ns/") + >>> print(ET.tostring( E.test )) + + + >>> E = ElementMaker(namespace="http://my.ns/", nsmap={'p':'http://my.ns/'}) + >>> print(ET.tostring( E.test )) + + """ + + def __init__(self, typemap=None, + namespace=None, nsmap=None, makeelement=None): + self._namespace = '{' + namespace + '}' if namespace is not None else None + self._nsmap = dict(nsmap) if nsmap else None + + assert makeelement is None or callable(makeelement) + self._makeelement = makeelement if makeelement is not None else ET.Element + + # initialize the default type map functions for this element factory + typemap = dict(typemap) if typemap else {} + + def add_text(elem, item): + try: + last_child = elem[-1] + except IndexError: + elem.text = (elem.text or "") + item + else: + last_child.tail = (last_child.tail or "") + item + + def add_cdata(elem, cdata): + if elem.text: + raise ValueError("Can't add a CDATA section. Element already has some text: %r" % elem.text) + elem.text = cdata + + if str not in typemap: + typemap[str] = add_text + if unicode not in typemap: + typemap[unicode] = add_text + if ET.CDATA not in typemap: + typemap[ET.CDATA] = add_cdata + + def add_dict(elem, item): + attrib = elem.attrib + for k, v in item.items(): + if isinstance(v, basestring): + attrib[k] = v + else: + attrib[k] = typemap[type(v)](None, v) + + if dict not in typemap: + typemap[dict] = add_dict + + self._typemap = typemap + + def __call__(self, tag, *children, **attrib): + typemap = self._typemap + + # We'll usually get a 'str', and the compiled type check is very fast. + if not isinstance(tag, str) and isinstance(tag, _QName): + # A QName is explicitly qualified, do not look at self._namespace. + tag = tag.text + elif self._namespace is not None and tag[0] != '{': + tag = self._namespace + tag + elem = self._makeelement(tag, nsmap=self._nsmap) + if attrib: + typemap[dict](elem, attrib) + + for item in children: + if callable(item): + item = item() + t = typemap.get(type(item)) + if t is None: + if ET.iselement(item): + elem.append(item) + continue + for basetype in type(item).__mro__: + # See if the typemap knows of any of this type's bases. + t = typemap.get(basetype) + if t is not None: + break + else: + raise TypeError("bad argument type: %s(%r)" % + (type(item).__name__, item)) + v = t(elem, item) + if v: + typemap.get(type(v))(elem, v) + + return elem + + def __getattr__(self, tag): + return partial(self, tag) + + # Allow subscripting ElementMaker in type annotations (PEP 560) + def __class_getitem__(cls, item): + return _GenericAlias(cls, item) + + +# create factory object +E = ElementMaker() diff --git a/intelliaide/vendor/lxml/classlookup.pxi b/intelliaide/vendor/lxml/classlookup.pxi new file mode 100644 index 0000000..92d1d47 --- /dev/null +++ b/intelliaide/vendor/lxml/classlookup.pxi @@ -0,0 +1,580 @@ +# Configurable Element class lookup + +################################################################################ +# Custom Element classes + +cdef public class ElementBase(_Element) [ type LxmlElementBaseType, + object LxmlElementBase ]: + """ElementBase(*children, attrib=None, nsmap=None, **_extra) + + The public Element class. All custom Element classes must inherit + from this one. To create an Element, use the `Element()` factory. + + BIG FAT WARNING: Subclasses *must not* override __init__ or + __new__ as it is absolutely undefined when these objects will be + created or destroyed. All persistent state of Elements must be + stored in the underlying XML. If you really need to initialize + the object after creation, you can implement an ``_init(self)`` + method that will be called directly after object creation. + + Subclasses of this class can be instantiated to create a new + Element. By default, the tag name will be the class name and the + namespace will be empty. You can modify this with the following + class attributes: + + * TAG - the tag name, possibly containing a namespace in Clark + notation + + * NAMESPACE - the default namespace URI, unless provided as part + of the TAG attribute. + + * HTML - flag if the class is an HTML tag, as opposed to an XML + tag. This only applies to un-namespaced tags and defaults to + false (i.e. XML). + + * PARSER - the parser that provides the configuration for the + newly created document. Providing an HTML parser here will + default to creating an HTML element. + + In user code, the latter three are commonly inherited in class + hierarchies that implement a common namespace. + """ + def __init__(self, *children, attrib=None, nsmap=None, **_extra): + """ElementBase(*children, attrib=None, nsmap=None, **_extra) + """ + cdef bint is_html = 0 + cdef _BaseParser parser + cdef _Element last_child + # don't use normal attribute access as it might be overridden + _getattr = object.__getattribute__ + try: + namespace = _utf8(_getattr(self, 'NAMESPACE')) + except AttributeError: + namespace = None + try: + ns, tag = _getNsTag(_getattr(self, 'TAG')) + if ns is not None: + namespace = ns + except AttributeError: + tag = _utf8(_getattr(_getattr(self, '__class__'), '__name__')) + if b'.' in tag: + tag = tag.split(b'.')[-1] + try: + parser = _getattr(self, 'PARSER') + except AttributeError: + parser = None + for child in children: + if isinstance(child, _Element): + parser = (<_Element>child)._doc._parser + break + if isinstance(parser, HTMLParser): + is_html = 1 + if namespace is None: + try: + is_html = _getattr(self, 'HTML') + except AttributeError: + pass + _initNewElement(self, is_html, tag, namespace, parser, + attrib, nsmap, _extra) + last_child = None + for child in children: + if _isString(child): + if last_child is None: + _setNodeText(self._c_node, + (_collectText(self._c_node.children) or '') + child) + else: + _setTailText(last_child._c_node, + (_collectText(last_child._c_node.next) or '') + child) + elif isinstance(child, _Element): + last_child = child + _appendChild(self, last_child) + elif isinstance(child, type) and issubclass(child, ElementBase): + last_child = child() + _appendChild(self, last_child) + else: + raise TypeError, f"Invalid child type: {type(child)!r}" + +cdef class CommentBase(_Comment): + """All custom Comment classes must inherit from this one. + + To create an XML Comment instance, use the ``Comment()`` factory. + + Subclasses *must not* override __init__ or __new__ as it is + absolutely undefined when these objects will be created or + destroyed. All persistent state of Comments must be stored in the + underlying XML. If you really need to initialize the object after + creation, you can implement an ``_init(self)`` method that will be + called after object creation. + """ + def __init__(self, text): + # copied from Comment() factory + cdef _Document doc + cdef xmlDoc* c_doc + if text is None: + text = b'' + else: + text = _utf8(text) + c_doc = _newXMLDoc() + doc = _documentFactory(c_doc, None) + self._c_node = _createComment(c_doc, _xcstr(text)) + if self._c_node is NULL: + raise MemoryError() + tree.xmlAddChild(c_doc, self._c_node) + _registerProxy(self, doc, self._c_node) + self._init() + +cdef class PIBase(_ProcessingInstruction): + """All custom Processing Instruction classes must inherit from this one. + + To create an XML ProcessingInstruction instance, use the ``PI()`` + factory. + + Subclasses *must not* override __init__ or __new__ as it is + absolutely undefined when these objects will be created or + destroyed. All persistent state of PIs must be stored in the + underlying XML. If you really need to initialize the object after + creation, you can implement an ``_init(self)`` method that will be + called after object creation. + """ + def __init__(self, target, text=None): + # copied from PI() factory + cdef _Document doc + cdef xmlDoc* c_doc + target = _utf8(target) + if text is None: + text = b'' + else: + text = _utf8(text) + c_doc = _newXMLDoc() + doc = _documentFactory(c_doc, None) + self._c_node = _createPI(c_doc, _xcstr(target), _xcstr(text)) + if self._c_node is NULL: + raise MemoryError() + tree.xmlAddChild(c_doc, self._c_node) + _registerProxy(self, doc, self._c_node) + self._init() + +cdef class EntityBase(_Entity): + """All custom Entity classes must inherit from this one. + + To create an XML Entity instance, use the ``Entity()`` factory. + + Subclasses *must not* override __init__ or __new__ as it is + absolutely undefined when these objects will be created or + destroyed. All persistent state of Entities must be stored in the + underlying XML. If you really need to initialize the object after + creation, you can implement an ``_init(self)`` method that will be + called after object creation. + """ + def __init__(self, name): + cdef _Document doc + cdef xmlDoc* c_doc + name_utf = _utf8(name) + c_name = _xcstr(name_utf) + if c_name[0] == c'#': + if not _characterReferenceIsValid(c_name + 1): + raise ValueError, f"Invalid character reference: '{name}'" + elif not _xmlNameIsValid(c_name): + raise ValueError, f"Invalid entity reference: '{name}'" + c_doc = _newXMLDoc() + doc = _documentFactory(c_doc, None) + self._c_node = _createEntity(c_doc, c_name) + if self._c_node is NULL: + raise MemoryError() + tree.xmlAddChild(c_doc, self._c_node) + _registerProxy(self, doc, self._c_node) + self._init() + + +cdef int _validateNodeClass(xmlNode* c_node, cls) except -1: + if c_node.type == tree.XML_ELEMENT_NODE: + expected = ElementBase + elif c_node.type == tree.XML_COMMENT_NODE: + expected = CommentBase + elif c_node.type == tree.XML_ENTITY_REF_NODE: + expected = EntityBase + elif c_node.type == tree.XML_PI_NODE: + expected = PIBase + else: + assert False, f"Unknown node type: {c_node.type}" + + if not (isinstance(cls, type) and issubclass(cls, expected)): + raise TypeError( + f"result of class lookup must be subclass of {type(expected)}, got {type(cls)}") + return 0 + + +################################################################################ +# Element class lookup + +ctypedef public object (*_element_class_lookup_function)(object, _Document, xmlNode*) + +# class to store element class lookup functions +cdef public class ElementClassLookup [ type LxmlElementClassLookupType, + object LxmlElementClassLookup ]: + """ElementClassLookup(self) + Superclass of Element class lookups. + """ + cdef _element_class_lookup_function _lookup_function + + +cdef public class FallbackElementClassLookup(ElementClassLookup) \ + [ type LxmlFallbackElementClassLookupType, + object LxmlFallbackElementClassLookup ]: + """FallbackElementClassLookup(self, fallback=None) + + Superclass of Element class lookups with additional fallback. + """ + cdef readonly ElementClassLookup fallback + cdef _element_class_lookup_function _fallback_function + def __cinit__(self): + # fall back to default lookup + self._fallback_function = _lookupDefaultElementClass + + def __init__(self, ElementClassLookup fallback=None): + if fallback is not None: + self._setFallback(fallback) + else: + self._fallback_function = _lookupDefaultElementClass + + cdef void _setFallback(self, ElementClassLookup lookup): + """Sets the fallback scheme for this lookup method. + """ + self.fallback = lookup + self._fallback_function = lookup._lookup_function + if self._fallback_function is NULL: + self._fallback_function = _lookupDefaultElementClass + + def set_fallback(self, ElementClassLookup lookup not None): + """set_fallback(self, lookup) + + Sets the fallback scheme for this lookup method. + """ + self._setFallback(lookup) + +cdef inline object _callLookupFallback(FallbackElementClassLookup lookup, + _Document doc, xmlNode* c_node): + return lookup._fallback_function(lookup.fallback, doc, c_node) + + +################################################################################ +# default lookup scheme + +cdef class ElementDefaultClassLookup(ElementClassLookup): + """ElementDefaultClassLookup(self, element=None, comment=None, pi=None, entity=None) + Element class lookup scheme that always returns the default Element + class. + + The keyword arguments ``element``, ``comment``, ``pi`` and ``entity`` + accept the respective Element classes. + """ + cdef readonly object element_class + cdef readonly object comment_class + cdef readonly object pi_class + cdef readonly object entity_class + def __cinit__(self): + self._lookup_function = _lookupDefaultElementClass + + def __init__(self, element=None, comment=None, pi=None, entity=None): + if element is None: + self.element_class = _Element + elif issubclass(element, ElementBase): + self.element_class = element + else: + raise TypeError, "element class must be subclass of ElementBase" + + if comment is None: + self.comment_class = _Comment + elif issubclass(comment, CommentBase): + self.comment_class = comment + else: + raise TypeError, "comment class must be subclass of CommentBase" + + if entity is None: + self.entity_class = _Entity + elif issubclass(entity, EntityBase): + self.entity_class = entity + else: + raise TypeError, "Entity class must be subclass of EntityBase" + + if pi is None: + self.pi_class = None # special case, see below + elif issubclass(pi, PIBase): + self.pi_class = pi + else: + raise TypeError, "PI class must be subclass of PIBase" + +cdef object _lookupDefaultElementClass(state, _Document _doc, xmlNode* c_node): + "Trivial class lookup function that always returns the default class." + if c_node.type == tree.XML_ELEMENT_NODE: + if state is not None: + return (state).element_class + else: + return _Element + elif c_node.type == tree.XML_COMMENT_NODE: + if state is not None: + return (state).comment_class + else: + return _Comment + elif c_node.type == tree.XML_ENTITY_REF_NODE: + if state is not None: + return (state).entity_class + else: + return _Entity + elif c_node.type == tree.XML_PI_NODE: + if state is None or (state).pi_class is None: + # special case XSLT-PI + if c_node.name is not NULL and c_node.content is not NULL: + if tree.xmlStrcmp(c_node.name, "xml-stylesheet") == 0: + if tree.xmlStrstr(c_node.content, "text/xsl") is not NULL or \ + tree.xmlStrstr(c_node.content, "text/xml") is not NULL: + return _XSLTProcessingInstruction + return _ProcessingInstruction + else: + return (state).pi_class + else: + assert False, f"Unknown node type: {c_node.type}" + + +################################################################################ +# attribute based lookup scheme + +cdef class AttributeBasedElementClassLookup(FallbackElementClassLookup): + """AttributeBasedElementClassLookup(self, attribute_name, class_mapping, fallback=None) + Checks an attribute of an Element and looks up the value in a + class dictionary. + + Arguments: + - attribute name - '{ns}name' style string + - class mapping - Python dict mapping attribute values to Element classes + - fallback - optional fallback lookup mechanism + + A None key in the class mapping will be checked if the attribute is + missing. + """ + cdef object _class_mapping + cdef tuple _pytag + cdef const_xmlChar* _c_ns + cdef const_xmlChar* _c_name + def __cinit__(self): + self._lookup_function = _attribute_class_lookup + + def __init__(self, attribute_name, class_mapping, + ElementClassLookup fallback=None): + self._pytag = _getNsTag(attribute_name) + ns, name = self._pytag + if ns is None: + self._c_ns = NULL + else: + self._c_ns = _xcstr(ns) + self._c_name = _xcstr(name) + self._class_mapping = dict(class_mapping) + + FallbackElementClassLookup.__init__(self, fallback) + +cdef object _attribute_class_lookup(state, _Document doc, xmlNode* c_node): + cdef AttributeBasedElementClassLookup lookup + cdef python.PyObject* dict_result + + lookup = state + if c_node.type == tree.XML_ELEMENT_NODE: + value = _attributeValueFromNsName( + c_node, lookup._c_ns, lookup._c_name) + dict_result = python.PyDict_GetItem(lookup._class_mapping, value) + if dict_result is not NULL: + cls = dict_result + _validateNodeClass(c_node, cls) + return cls + return _callLookupFallback(lookup, doc, c_node) + + +################################################################################ +# per-parser lookup scheme + +cdef class ParserBasedElementClassLookup(FallbackElementClassLookup): + """ParserBasedElementClassLookup(self, fallback=None) + Element class lookup based on the XML parser. + """ + def __cinit__(self): + self._lookup_function = _parser_class_lookup + +cdef object _parser_class_lookup(state, _Document doc, xmlNode* c_node): + if doc._parser._class_lookup is not None: + return doc._parser._class_lookup._lookup_function( + doc._parser._class_lookup, doc, c_node) + return _callLookupFallback(state, doc, c_node) + + +################################################################################ +# custom class lookup based on node type, namespace, name + +cdef class CustomElementClassLookup(FallbackElementClassLookup): + """CustomElementClassLookup(self, fallback=None) + Element class lookup based on a subclass method. + + You can inherit from this class and override the method:: + + lookup(self, type, doc, namespace, name) + + to lookup the element class for a node. Arguments of the method: + * type: one of 'element', 'comment', 'PI', 'entity' + * doc: document that the node is in + * namespace: namespace URI of the node (or None for comments/PIs/entities) + * name: name of the element/entity, None for comments, target for PIs + + If you return None from this method, the fallback will be called. + """ + def __cinit__(self): + self._lookup_function = _custom_class_lookup + + def lookup(self, type, doc, namespace, name): + "lookup(self, type, doc, namespace, name)" + return None + +cdef object _custom_class_lookup(state, _Document doc, xmlNode* c_node): + cdef CustomElementClassLookup lookup + + lookup = state + + if c_node.type == tree.XML_ELEMENT_NODE: + element_type = "element" + elif c_node.type == tree.XML_COMMENT_NODE: + element_type = "comment" + elif c_node.type == tree.XML_PI_NODE: + element_type = "PI" + elif c_node.type == tree.XML_ENTITY_REF_NODE: + element_type = "entity" + else: + element_type = "element" + if c_node.name is NULL: + name = None + else: + name = funicode(c_node.name) + c_str = tree._getNs(c_node) + ns = funicode(c_str) if c_str is not NULL else None + + cls = lookup.lookup(element_type, doc, ns, name) + if cls is not None: + _validateNodeClass(c_node, cls) + return cls + return _callLookupFallback(lookup, doc, c_node) + + +################################################################################ +# read-only tree based class lookup + +cdef class PythonElementClassLookup(FallbackElementClassLookup): + """PythonElementClassLookup(self, fallback=None) + Element class lookup based on a subclass method. + + This class lookup scheme allows access to the entire XML tree in + read-only mode. To use it, re-implement the ``lookup(self, doc, + root)`` method in a subclass:: + + from lxml import etree, pyclasslookup + + class MyElementClass(etree.ElementBase): + honkey = True + + class MyLookup(pyclasslookup.PythonElementClassLookup): + def lookup(self, doc, root): + if root.tag == "sometag": + return MyElementClass + else: + for child in root: + if child.tag == "someothertag": + return MyElementClass + # delegate to default + return None + + If you return None from this method, the fallback will be called. + + The first argument is the opaque document instance that contains + the Element. The second argument is a lightweight Element proxy + implementation that is only valid during the lookup. Do not try + to keep a reference to it. Once the lookup is done, the proxy + will be invalid. + + Also, you cannot wrap such a read-only Element in an ElementTree, + and you must take care not to keep a reference to them outside of + the `lookup()` method. + + Note that the API of the Element objects is not complete. It is + purely read-only and does not support all features of the normal + `lxml.etree` API (such as XPath, extended slicing or some + iteration methods). + + See https://lxml.de/element_classes.html + """ + def __cinit__(self): + self._lookup_function = _python_class_lookup + + def lookup(self, doc, element): + """lookup(self, doc, element) + + Override this method to implement your own lookup scheme. + """ + return None + +cdef object _python_class_lookup(state, _Document doc, tree.xmlNode* c_node): + cdef PythonElementClassLookup lookup + cdef _ReadOnlyProxy proxy + lookup = state + + proxy = _newReadOnlyProxy(None, c_node) + cls = lookup.lookup(doc, proxy) + _freeReadOnlyProxies(proxy) + + if cls is not None: + _validateNodeClass(c_node, cls) + return cls + return _callLookupFallback(lookup, doc, c_node) + +################################################################################ +# Global setup + +cdef _element_class_lookup_function LOOKUP_ELEMENT_CLASS +cdef object ELEMENT_CLASS_LOOKUP_STATE + +cdef void _setElementClassLookupFunction( + _element_class_lookup_function function, object state): + global LOOKUP_ELEMENT_CLASS, ELEMENT_CLASS_LOOKUP_STATE + if function is NULL: + state = DEFAULT_ELEMENT_CLASS_LOOKUP + function = DEFAULT_ELEMENT_CLASS_LOOKUP._lookup_function + + ELEMENT_CLASS_LOOKUP_STATE = state + LOOKUP_ELEMENT_CLASS = function + +def set_element_class_lookup(ElementClassLookup lookup = None): + """set_element_class_lookup(lookup = None) + + Set the global element class lookup method. + + This defines the main entry point for looking up element implementations. + The standard implementation uses the :class:`ParserBasedElementClassLookup` + to delegate to different lookup schemes for each parser. + + .. warning:: + + This should only be changed by applications, not by library packages. + In most cases, parser specific lookups should be preferred, + which can be configured via + :meth:`~lxml.etree.XMLParser.set_element_class_lookup` + (and the same for HTML parsers). + + Globally replacing the element class lookup by something other than a + :class:`ParserBasedElementClassLookup` will prevent parser specific lookup + schemes from working. Several tools rely on parser specific lookups, + including :mod:`lxml.html` and :mod:`lxml.objectify`. + """ + if lookup is None or lookup._lookup_function is NULL: + _setElementClassLookupFunction(NULL, None) + else: + _setElementClassLookupFunction(lookup._lookup_function, lookup) + +# default setup: parser delegation +cdef ParserBasedElementClassLookup DEFAULT_ELEMENT_CLASS_LOOKUP +DEFAULT_ELEMENT_CLASS_LOOKUP = ParserBasedElementClassLookup() + +set_element_class_lookup(DEFAULT_ELEMENT_CLASS_LOOKUP) diff --git a/intelliaide/vendor/lxml/cleanup.pxi b/intelliaide/vendor/lxml/cleanup.pxi new file mode 100644 index 0000000..8e266b3 --- /dev/null +++ b/intelliaide/vendor/lxml/cleanup.pxi @@ -0,0 +1,215 @@ +# functions for tree cleanup and removing elements from subtrees + +def cleanup_namespaces(tree_or_element, top_nsmap=None, keep_ns_prefixes=None): + """cleanup_namespaces(tree_or_element, top_nsmap=None, keep_ns_prefixes=None) + + Remove all namespace declarations from a subtree that are not used + by any of the elements or attributes in that tree. + + If a 'top_nsmap' is provided, it must be a mapping from prefixes + to namespace URIs. These namespaces will be declared on the top + element of the subtree before running the cleanup, which allows + moving namespace declarations to the top of the tree. + + If a 'keep_ns_prefixes' is provided, it must be a list of prefixes. + These prefixes will not be removed as part of the cleanup. + """ + element = _rootNodeOrRaise(tree_or_element) + c_element = element._c_node + + if top_nsmap: + doc = element._doc + # declare namespaces from nsmap, then apply them to the subtree + _setNodeNamespaces(c_element, doc, None, top_nsmap) + moveNodeToDocument(doc, c_element.doc, c_element) + + keep_ns_prefixes = ( + set([_utf8(prefix) for prefix in keep_ns_prefixes]) + if keep_ns_prefixes else None) + + _removeUnusedNamespaceDeclarations(c_element, keep_ns_prefixes) + + +def strip_attributes(tree_or_element, *attribute_names): + """strip_attributes(tree_or_element, *attribute_names) + + Delete all attributes with the provided attribute names from an + Element (or ElementTree) and its descendants. + + Attribute names can contain wildcards as in `_Element.iter`. + + Example usage:: + + strip_attributes(root_element, + 'simpleattr', + '{http://some/ns}attrname', + '{http://other/ns}*') + """ + cdef _MultiTagMatcher matcher + element = _rootNodeOrRaise(tree_or_element) + if not attribute_names: + return + + matcher = _MultiTagMatcher.__new__(_MultiTagMatcher, attribute_names) + matcher.cacheTags(element._doc) + if matcher.rejectsAllAttributes(): + return + _strip_attributes(element._c_node, matcher) + + +cdef _strip_attributes(xmlNode* c_node, _MultiTagMatcher matcher): + cdef xmlAttr* c_attr + cdef xmlAttr* c_next_attr + tree.BEGIN_FOR_EACH_ELEMENT_FROM(c_node, c_node, 1) + if c_node.type == tree.XML_ELEMENT_NODE: + c_attr = c_node.properties + while c_attr is not NULL: + c_next_attr = c_attr.next + if matcher.matchesAttribute(c_attr): + tree.xmlRemoveProp(c_attr) + c_attr = c_next_attr + tree.END_FOR_EACH_ELEMENT_FROM(c_node) + + +def strip_elements(tree_or_element, *tag_names, bint with_tail=True): + """strip_elements(tree_or_element, *tag_names, with_tail=True) + + Delete all elements with the provided tag names from a tree or + subtree. This will remove the elements and their entire subtree, + including all their attributes, text content and descendants. It + will also remove the tail text of the element unless you + explicitly set the ``with_tail`` keyword argument option to False. + + Tag names can contain wildcards as in `_Element.iter`. + + Note that this will not delete the element (or ElementTree root + element) that you passed even if it matches. It will only treat + its descendants. If you want to include the root element, check + its tag name directly before even calling this function. + + Example usage:: + + strip_elements(some_element, + 'simpletagname', # non-namespaced tag + '{http://some/ns}tagname', # namespaced tag + '{http://some/other/ns}*' # any tag from a namespace + lxml.etree.Comment # comments + ) + """ + cdef _MultiTagMatcher matcher + doc = _documentOrRaise(tree_or_element) + element = _rootNodeOrRaise(tree_or_element) + if not tag_names: + return + + matcher = _MultiTagMatcher.__new__(_MultiTagMatcher, tag_names) + matcher.cacheTags(doc) + if matcher.rejectsAll(): + return + + if isinstance(tree_or_element, _ElementTree): + # include PIs and comments next to the root node + if matcher.matchesType(tree.XML_COMMENT_NODE): + _removeSiblings(element._c_node, tree.XML_COMMENT_NODE, with_tail) + if matcher.matchesType(tree.XML_PI_NODE): + _removeSiblings(element._c_node, tree.XML_PI_NODE, with_tail) + _strip_elements(doc, element._c_node, matcher, with_tail) + +cdef _strip_elements(_Document doc, xmlNode* c_node, _MultiTagMatcher matcher, + bint with_tail): + cdef xmlNode* c_child + cdef xmlNode* c_next + + tree.BEGIN_FOR_EACH_ELEMENT_FROM(c_node, c_node, 1) + if c_node.type == tree.XML_ELEMENT_NODE: + # we run through the children here to prevent any problems + # with the tree iteration which would occur if we unlinked the + # c_node itself + c_child = _findChildForwards(c_node, 0) + while c_child is not NULL: + c_next = _nextElement(c_child) + if matcher.matches(c_child): + if c_child.type == tree.XML_ELEMENT_NODE: + if not with_tail: + tree.xmlUnlinkNode(c_child) + _removeNode(doc, c_child) + else: + if with_tail: + _removeText(c_child.next) + tree.xmlUnlinkNode(c_child) + attemptDeallocation(c_child) + c_child = c_next + tree.END_FOR_EACH_ELEMENT_FROM(c_node) + + +def strip_tags(tree_or_element, *tag_names): + """strip_tags(tree_or_element, *tag_names) + + Delete all elements with the provided tag names from a tree or + subtree. This will remove the elements and their attributes, but + *not* their text/tail content or descendants. Instead, it will + merge the text content and children of the element into its + parent. + + Tag names can contain wildcards as in `_Element.iter`. + + Note that this will not delete the element (or ElementTree root + element) that you passed even if it matches. It will only treat + its descendants. + + Example usage:: + + strip_tags(some_element, + 'simpletagname', # non-namespaced tag + '{http://some/ns}tagname', # namespaced tag + '{http://some/other/ns}*' # any tag from a namespace + Comment # comments (including their text!) + ) + """ + cdef _MultiTagMatcher matcher + doc = _documentOrRaise(tree_or_element) + element = _rootNodeOrRaise(tree_or_element) + if not tag_names: + return + + matcher = _MultiTagMatcher.__new__(_MultiTagMatcher, tag_names) + matcher.cacheTags(doc) + if matcher.rejectsAll(): + return + + if isinstance(tree_or_element, _ElementTree): + # include PIs and comments next to the root node + if matcher.matchesType(tree.XML_COMMENT_NODE): + _removeSiblings(element._c_node, tree.XML_COMMENT_NODE, 0) + if matcher.matchesType(tree.XML_PI_NODE): + _removeSiblings(element._c_node, tree.XML_PI_NODE, 0) + _strip_tags(doc, element._c_node, matcher) + +cdef _strip_tags(_Document doc, xmlNode* c_node, _MultiTagMatcher matcher): + cdef xmlNode* c_child + cdef xmlNode* c_next + + tree.BEGIN_FOR_EACH_ELEMENT_FROM(c_node, c_node, 1) + if c_node.type == tree.XML_ELEMENT_NODE: + # we run through the children here to prevent any problems + # with the tree iteration which would occur if we unlinked the + # c_node itself + c_child = _findChildForwards(c_node, 0) + while c_child is not NULL: + if not matcher.matches(c_child): + c_child = _nextElement(c_child) + continue + if c_child.type == tree.XML_ELEMENT_NODE: + c_next = _findChildForwards(c_child, 0) or _nextElement(c_child) + _replaceNodeByChildren(doc, c_child) + if not attemptDeallocation(c_child): + if c_child.nsDef is not NULL: + # make namespaces absolute + moveNodeToDocument(doc, doc._c_doc, c_child) + c_child = c_next + else: + c_next = _nextElement(c_child) + tree.xmlUnlinkNode(c_child) + attemptDeallocation(c_child) + c_child = c_next + tree.END_FOR_EACH_ELEMENT_FROM(c_node) diff --git a/intelliaide/vendor/lxml/cssselect.py b/intelliaide/vendor/lxml/cssselect.py new file mode 100644 index 0000000..54cd75a --- /dev/null +++ b/intelliaide/vendor/lxml/cssselect.py @@ -0,0 +1,101 @@ +"""CSS Selectors based on XPath. + +This module supports selecting XML/HTML tags based on CSS selectors. +See the `CSSSelector` class for details. + +This is a thin wrapper around cssselect 0.7 or later. +""" + + +from . import etree +try: + import cssselect as external_cssselect +except ImportError: + raise ImportError( + 'cssselect does not seem to be installed. ' + 'See https://pypi.org/project/cssselect/') + + +SelectorSyntaxError = external_cssselect.SelectorSyntaxError +ExpressionError = external_cssselect.ExpressionError +SelectorError = external_cssselect.SelectorError + + +__all__ = ['SelectorSyntaxError', 'ExpressionError', 'SelectorError', + 'CSSSelector'] + + +class LxmlTranslator(external_cssselect.GenericTranslator): + """ + A custom CSS selector to XPath translator with lxml-specific extensions. + """ + def xpath_contains_function(self, xpath, function): + # Defined there, removed in later drafts: + # http://www.w3.org/TR/2001/CR-css3-selectors-20011113/#content-selectors + if function.argument_types() not in (['STRING'], ['IDENT']): + raise ExpressionError( + "Expected a single string or ident for :contains(), got %r" + % function.arguments) + value = function.arguments[0].value + return xpath.add_condition( + 'contains(__lxml_internal_css:lower-case(string(.)), %s)' + % self.xpath_literal(value.lower())) + + +class LxmlHTMLTranslator(LxmlTranslator, external_cssselect.HTMLTranslator): + """ + lxml extensions + HTML support. + """ + + +def _make_lower_case(context, s): + return s.lower() + +ns = etree.FunctionNamespace('http://codespeak.net/lxml/css/') +ns.prefix = '__lxml_internal_css' +ns['lower-case'] = _make_lower_case + + +class CSSSelector(etree.XPath): + """A CSS selector. + + Usage:: + + >>> from lxml import etree, cssselect + >>> select = cssselect.CSSSelector("a tag > child") + + >>> root = etree.XML("TEXT") + >>> [ el.tag for el in select(root) ] + ['child'] + + To use CSS namespaces, you need to pass a prefix-to-namespace + mapping as ``namespaces`` keyword argument:: + + >>> rdfns = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' + >>> select_ns = cssselect.CSSSelector('root > rdf|Description', + ... namespaces={'rdf': rdfns}) + + >>> rdf = etree.XML(( + ... '' + ... 'blah' + ... '') % rdfns) + >>> [(el.tag, el.text) for el in select_ns(rdf)] + [('{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description', 'blah')] + + """ + def __init__(self, css, namespaces=None, translator='xml'): + if translator == 'xml': + translator = LxmlTranslator() + elif translator == 'html': + translator = LxmlHTMLTranslator() + elif translator == 'xhtml': + translator = LxmlHTMLTranslator(xhtml=True) + path = translator.css_to_xpath(css) + super().__init__(path, namespaces=namespaces) + self.css = css + + def __repr__(self): + return '<%s %x for %r>' % ( + self.__class__.__name__, + abs(id(self)), + self.css) diff --git a/intelliaide/vendor/lxml/debug.pxi b/intelliaide/vendor/lxml/debug.pxi new file mode 100644 index 0000000..d728e84 --- /dev/null +++ b/intelliaide/vendor/lxml/debug.pxi @@ -0,0 +1,36 @@ +@cython.final +@cython.internal +cdef class _MemDebug: + """Debugging support for the memory allocation in libxml2. + """ + def bytes_used(self): + """bytes_used(self) + + Returns the total amount of memory (in bytes) currently used by libxml2. + Note that libxml2 constrains this value to a C int, which limits + the accuracy on 64 bit systems. + """ + return tree.xmlMemUsed() + + def blocks_used(self): + """blocks_used(self) + + Returns the total number of memory blocks currently allocated by libxml2. + Note that libxml2 constrains this value to a C int, which limits + the accuracy on 64 bit systems. + """ + return tree.xmlMemBlocks() + + def dict_size(self): + """dict_size(self) + + Returns the current size of the global name dictionary used by libxml2 + for the current thread. Each thread has its own dictionary. + """ + c_dict = __GLOBAL_PARSER_CONTEXT._getThreadDict(NULL) + if c_dict is NULL: + raise MemoryError() + return tree.xmlDictSize(c_dict) + + +memory_debugger = _MemDebug() diff --git a/intelliaide/vendor/lxml/docloader.pxi b/intelliaide/vendor/lxml/docloader.pxi new file mode 100644 index 0000000..07e0cd7 --- /dev/null +++ b/intelliaide/vendor/lxml/docloader.pxi @@ -0,0 +1,179 @@ +# Custom resolver API + +ctypedef enum _InputDocumentDataType: + PARSER_DATA_INVALID + PARSER_DATA_EMPTY + PARSER_DATA_STRING + PARSER_DATA_FILENAME + PARSER_DATA_FILE + +@cython.final +@cython.internal +cdef class _InputDocument: + cdef _InputDocumentDataType _type + cdef bytes _data_bytes + cdef object _filename + cdef object _file + cdef bint _close_file + + def __cinit__(self): + self._type = PARSER_DATA_INVALID + + +cdef class Resolver: + "This is the base class of all resolvers." + def resolve(self, system_url, public_id, context): + """resolve(self, system_url, public_id, context) + + Override this method to resolve an external source by + ``system_url`` and ``public_id``. The third argument is an + opaque context object. + + Return the result of one of the ``resolve_*()`` methods. + """ + return None + + def resolve_empty(self, context): + """resolve_empty(self, context) + + Return an empty input document. + + Pass context as parameter. + """ + cdef _InputDocument doc_ref + doc_ref = _InputDocument() + doc_ref._type = PARSER_DATA_EMPTY + return doc_ref + + def resolve_string(self, string, context, *, base_url=None): + """resolve_string(self, string, context, base_url=None) + + Return a parsable string as input document. + + Pass data string and context as parameters. You can pass the + source URL or filename through the ``base_url`` keyword + argument. + """ + cdef _InputDocument doc_ref + if isinstance(string, unicode): + string = (string).encode('utf8') + elif not isinstance(string, bytes): + raise TypeError, "argument must be a byte string or unicode string" + doc_ref = _InputDocument() + doc_ref._type = PARSER_DATA_STRING + doc_ref._data_bytes = string + if base_url is not None: + doc_ref._filename = _encodeFilename(base_url) + return doc_ref + + def resolve_filename(self, filename, context): + """resolve_filename(self, filename, context) + + Return the name of a parsable file as input document. + + Pass filename and context as parameters. You can also pass a + URL with an HTTP, FTP or file target. + """ + cdef _InputDocument doc_ref + doc_ref = _InputDocument() + doc_ref._type = PARSER_DATA_FILENAME + doc_ref._filename = _encodeFilename(filename) + return doc_ref + + def resolve_file(self, f, context, *, base_url=None, bint close=True): + """resolve_file(self, f, context, base_url=None, close=True) + + Return an open file-like object as input document. + + Pass open file and context as parameters. You can pass the + base URL or filename of the file through the ``base_url`` + keyword argument. If the ``close`` flag is True (the + default), the file will be closed after reading. + + Note that using ``.resolve_filename()`` is more efficient, + especially in threaded environments. + """ + cdef _InputDocument doc_ref + try: + f.read + except AttributeError: + raise TypeError, "Argument is not a file-like object" + doc_ref = _InputDocument() + doc_ref._type = PARSER_DATA_FILE + if base_url is not None: + doc_ref._filename = _encodeFilename(base_url) + else: + doc_ref._filename = _getFilenameForFile(f) + doc_ref._close_file = close + doc_ref._file = f + return doc_ref + +@cython.final +@cython.internal +cdef class _ResolverRegistry: + cdef object _resolvers + cdef Resolver _default_resolver + def __cinit__(self, Resolver default_resolver=None): + self._resolvers = set() + self._default_resolver = default_resolver + + def add(self, Resolver resolver not None): + """add(self, resolver) + + Register a resolver. + + For each requested entity, the 'resolve' method of the resolver will + be called and the result will be passed to the parser. If this method + returns None, the request will be delegated to other resolvers or the + default resolver. The resolvers will be tested in an arbitrary order + until the first match is found. + """ + self._resolvers.add(resolver) + + def remove(self, resolver): + "remove(self, resolver)" + self._resolvers.discard(resolver) + + cdef _ResolverRegistry _copy(self): + cdef _ResolverRegistry registry + registry = _ResolverRegistry(self._default_resolver) + registry._resolvers = self._resolvers.copy() + return registry + + def copy(self): + "copy(self)" + return self._copy() + + def resolve(self, system_url, public_id, context): + "resolve(self, system_url, public_id, context)" + for resolver in self._resolvers: + result = resolver.resolve(system_url, public_id, context) + if result is not None: + return result + if self._default_resolver is None: + return None + return self._default_resolver.resolve(system_url, public_id, context) + + def __repr__(self): + return repr(self._resolvers) + + +@cython.internal +cdef class _ResolverContext(_ExceptionContext): + cdef _ResolverRegistry _resolvers + cdef _TempStore _storage + + @cython.final + cdef int clear(self) except -1: + _ExceptionContext.clear(self) + self._storage.clear() + return 0 + + +cdef _initResolverContext(_ResolverContext context, + _ResolverRegistry resolvers): + if resolvers is None: + context._resolvers = _ResolverRegistry() + else: + context._resolvers = resolvers + context._storage = _TempStore() diff --git a/intelliaide/vendor/lxml/doctestcompare.py b/intelliaide/vendor/lxml/doctestcompare.py new file mode 100644 index 0000000..8099771 --- /dev/null +++ b/intelliaide/vendor/lxml/doctestcompare.py @@ -0,0 +1,488 @@ +""" +lxml-based doctest output comparison. + +Note: normally, you should just import the `lxml.usedoctest` and +`lxml.html.usedoctest` modules from within a doctest, instead of this +one:: + + >>> import lxml.usedoctest # for XML output + + >>> import lxml.html.usedoctest # for HTML output + +To use this module directly, you must call ``lxmldoctest.install()``, +which will cause doctest to use this in all subsequent calls. + +This changes the way output is checked and comparisons are made for +XML or HTML-like content. + +XML or HTML content is noticed because the example starts with ``<`` +(it's HTML if it starts with ```` or include an ``any`` +attribute in the tag. An ``any`` tag matches any tag, while the +attribute matches any and all attributes. + +When a match fails, the reformatted example and gotten text is +displayed (indented), and a rough diff-like output is given. Anything +marked with ``+`` is in the output but wasn't supposed to be, and +similarly ``-`` means its in the example but wasn't in the output. + +You can disable parsing on one line with ``# doctest:+NOPARSE_MARKUP`` +""" + +from lxml import etree +import sys +import re +import doctest +try: + from html import escape as html_escape +except ImportError: + from cgi import escape as html_escape + +__all__ = ['PARSE_HTML', 'PARSE_XML', 'NOPARSE_MARKUP', 'LXMLOutputChecker', + 'LHTMLOutputChecker', 'install', 'temp_install'] + +PARSE_HTML = doctest.register_optionflag('PARSE_HTML') +PARSE_XML = doctest.register_optionflag('PARSE_XML') +NOPARSE_MARKUP = doctest.register_optionflag('NOPARSE_MARKUP') + +OutputChecker = doctest.OutputChecker + +def strip(v): + if v is None: + return None + else: + return v.strip() + +def norm_whitespace(v): + return _norm_whitespace_re.sub(' ', v) + +_html_parser = etree.HTMLParser(recover=False, remove_blank_text=True) + +def html_fromstring(html): + return etree.fromstring(html, _html_parser) + +# We use this to distinguish repr()s from elements: +_repr_re = re.compile(r'^<[^>]+ (at|object) ') +_norm_whitespace_re = re.compile(r'[ \t\n][ \t\n]+') + +class LXMLOutputChecker(OutputChecker): + + empty_tags = ( + 'param', 'img', 'area', 'br', 'basefont', 'input', + 'base', 'meta', 'link', 'col') + + def get_default_parser(self): + return etree.XML + + def check_output(self, want, got, optionflags): + alt_self = getattr(self, '_temp_override_self', None) + if alt_self is not None: + super_method = self._temp_call_super_check_output + self = alt_self + else: + super_method = OutputChecker.check_output + parser = self.get_parser(want, got, optionflags) + if not parser: + return super_method( + self, want, got, optionflags) + try: + want_doc = parser(want) + except etree.XMLSyntaxError: + return False + try: + got_doc = parser(got) + except etree.XMLSyntaxError: + return False + return self.compare_docs(want_doc, got_doc) + + def get_parser(self, want, got, optionflags): + parser = None + if NOPARSE_MARKUP & optionflags: + return None + if PARSE_HTML & optionflags: + parser = html_fromstring + elif PARSE_XML & optionflags: + parser = etree.XML + elif (want.strip().lower().startswith('' % el.tag + return '<%s %s>' % (el.tag, ' '.join(attrs)) + + def format_end_tag(self, el): + if isinstance(el, etree.CommentBase): + # FIXME: probably PIs should be handled specially too? + return '-->' + return '' % el.tag + + def collect_diff(self, want, got, html, indent): + parts = [] + if not len(want) and not len(got): + parts.append(' '*indent) + parts.append(self.collect_diff_tag(want, got)) + if not self.html_empty_tag(got, html): + parts.append(self.collect_diff_text(want.text, got.text)) + parts.append(self.collect_diff_end_tag(want, got)) + parts.append(self.collect_diff_text(want.tail, got.tail)) + parts.append('\n') + return ''.join(parts) + parts.append(' '*indent) + parts.append(self.collect_diff_tag(want, got)) + parts.append('\n') + if strip(want.text) or strip(got.text): + parts.append(' '*indent) + parts.append(self.collect_diff_text(want.text, got.text)) + parts.append('\n') + want_children = list(want) + got_children = list(got) + while want_children or got_children: + if not want_children: + parts.append(self.format_doc(got_children.pop(0), html, indent+2, '+')) + continue + if not got_children: + parts.append(self.format_doc(want_children.pop(0), html, indent+2, '-')) + continue + parts.append(self.collect_diff( + want_children.pop(0), got_children.pop(0), html, indent+2)) + parts.append(' '*indent) + parts.append(self.collect_diff_end_tag(want, got)) + parts.append('\n') + if strip(want.tail) or strip(got.tail): + parts.append(' '*indent) + parts.append(self.collect_diff_text(want.tail, got.tail)) + parts.append('\n') + return ''.join(parts) + + def collect_diff_tag(self, want, got): + if not self.tag_compare(want.tag, got.tag): + tag = '%s (got: %s)' % (want.tag, got.tag) + else: + tag = got.tag + attrs = [] + any = want.tag == 'any' or 'any' in want.attrib + for name, value in sorted(got.attrib.items()): + if name not in want.attrib and not any: + attrs.append('+%s="%s"' % (name, self.format_text(value, False))) + else: + if name in want.attrib: + text = self.collect_diff_text(want.attrib[name], value, False) + else: + text = self.format_text(value, False) + attrs.append('%s="%s"' % (name, text)) + if not any: + for name, value in sorted(want.attrib.items()): + if name in got.attrib: + continue + attrs.append('-%s="%s"' % (name, self.format_text(value, False))) + if attrs: + tag = '<%s %s>' % (tag, ' '.join(attrs)) + else: + tag = '<%s>' % tag + return tag + + def collect_diff_end_tag(self, want, got): + if want.tag != got.tag: + tag = '%s (got: %s)' % (want.tag, got.tag) + else: + tag = got.tag + return '' % tag + + def collect_diff_text(self, want, got, strip=True): + if self.text_compare(want, got, strip): + if not got: + return '' + return self.format_text(got, strip) + text = '%s (got: %s)' % (want, got) + return self.format_text(text, strip) + +class LHTMLOutputChecker(LXMLOutputChecker): + def get_default_parser(self): + return html_fromstring + +def install(html=False): + """ + Install doctestcompare for all future doctests. + + If html is true, then by default the HTML parser will be used; + otherwise the XML parser is used. + """ + if html: + doctest.OutputChecker = LHTMLOutputChecker + else: + doctest.OutputChecker = LXMLOutputChecker + +def temp_install(html=False, del_module=None): + """ + Use this *inside* a doctest to enable this checker for this + doctest only. + + If html is true, then by default the HTML parser will be used; + otherwise the XML parser is used. + """ + if html: + Checker = LHTMLOutputChecker + else: + Checker = LXMLOutputChecker + frame = _find_doctest_frame() + dt_self = frame.f_locals['self'] + checker = Checker() + old_checker = dt_self._checker + dt_self._checker = checker + # The unfortunate thing is that there is a local variable 'check' + # in the function that runs the doctests, that is a bound method + # into the output checker. We have to update that. We can't + # modify the frame, so we have to modify the object in place. The + # only way to do this is to actually change the func_code + # attribute of the method. We change it, and then wait for + # __record_outcome to be run, which signals the end of the __run + # method, at which point we restore the previous check_output + # implementation. + check_func = frame.f_locals['check'].__func__ + checker_check_func = checker.check_output.__func__ + # Because we can't patch up func_globals, this is the only global + # in check_output that we care about: + doctest.etree = etree + _RestoreChecker(dt_self, old_checker, checker, + check_func, checker_check_func, + del_module) + +class _RestoreChecker: + def __init__(self, dt_self, old_checker, new_checker, check_func, clone_func, + del_module): + self.dt_self = dt_self + self.checker = old_checker + self.checker._temp_call_super_check_output = self.call_super + self.checker._temp_override_self = new_checker + self.check_func = check_func + self.clone_func = clone_func + self.del_module = del_module + self.install_clone() + self.install_dt_self() + def install_clone(self): + self.func_code = self.check_func.__code__ + self.func_globals = self.check_func.__globals__ + self.check_func.__code__ = self.clone_func.__code__ + def uninstall_clone(self): + self.check_func.__code__ = self.func_code + def install_dt_self(self): + self.prev_func = self.dt_self._DocTestRunner__record_outcome + self.dt_self._DocTestRunner__record_outcome = self + def uninstall_dt_self(self): + self.dt_self._DocTestRunner__record_outcome = self.prev_func + def uninstall_module(self): + if self.del_module: + import sys + del sys.modules[self.del_module] + if '.' in self.del_module: + package, module = self.del_module.rsplit('.', 1) + package_mod = sys.modules[package] + delattr(package_mod, module) + def __call__(self, *args, **kw): + self.uninstall_clone() + self.uninstall_dt_self() + del self.checker._temp_override_self + del self.checker._temp_call_super_check_output + result = self.prev_func(*args, **kw) + self.uninstall_module() + return result + def call_super(self, *args, **kw): + self.uninstall_clone() + try: + return self.check_func(*args, **kw) + finally: + self.install_clone() + +def _find_doctest_frame(): + import sys + frame = sys._getframe(1) + while frame: + l = frame.f_locals + if 'BOOM' in l: + # Sign of doctest + return frame + frame = frame.f_back + raise LookupError( + "Could not find doctest (only use this function *inside* a doctest)") + +__test__ = { + 'basic': ''' + >>> temp_install() + >>> print """stuff""" + ... + >>> print """""" + + + + >>> print """blahblahblah""" # doctest: +NOPARSE_MARKUP, +ELLIPSIS + ...foo /> + '''} + +if __name__ == '__main__': + import doctest + doctest.testmod() + + diff --git a/intelliaide/vendor/lxml/dtd.pxi b/intelliaide/vendor/lxml/dtd.pxi new file mode 100644 index 0000000..ee1b3d4 --- /dev/null +++ b/intelliaide/vendor/lxml/dtd.pxi @@ -0,0 +1,479 @@ +# support for DTD validation +from lxml.includes cimport dtdvalid + +cdef class DTDError(LxmlError): + """Base class for DTD errors. + """ + +cdef class DTDParseError(DTDError): + """Error while parsing a DTD. + """ + +cdef class DTDValidateError(DTDError): + """Error while validating an XML document with a DTD. + """ + + +cdef inline int _assertValidDTDNode(node, void *c_node) except -1: + assert c_node is not NULL, "invalid DTD proxy at %s" % id(node) + + +@cython.final +@cython.internal +@cython.freelist(8) +cdef class _DTDElementContentDecl: + cdef DTD _dtd + cdef tree.xmlElementContent* _c_node + + def __repr__(self): + return "<%s.%s object name=%r type=%r occur=%r at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, self.name, self.type, self.occur, id(self)) + + @property + def name(self): + _assertValidDTDNode(self, self._c_node) + return funicodeOrNone(self._c_node.name) + + @property + def type(self): + _assertValidDTDNode(self, self._c_node) + cdef int type = self._c_node.type + if type == tree.XML_ELEMENT_CONTENT_PCDATA: + return "pcdata" + elif type == tree.XML_ELEMENT_CONTENT_ELEMENT: + return "element" + elif type == tree.XML_ELEMENT_CONTENT_SEQ: + return "seq" + elif type == tree.XML_ELEMENT_CONTENT_OR: + return "or" + else: + return None + + @property + def occur(self): + _assertValidDTDNode(self, self._c_node) + cdef int occur = self._c_node.ocur + if occur == tree.XML_ELEMENT_CONTENT_ONCE: + return "once" + elif occur == tree.XML_ELEMENT_CONTENT_OPT: + return "opt" + elif occur == tree.XML_ELEMENT_CONTENT_MULT: + return "mult" + elif occur == tree.XML_ELEMENT_CONTENT_PLUS: + return "plus" + else: + return None + + @property + def left(self): + _assertValidDTDNode(self, self._c_node) + c1 = self._c_node.c1 + if c1: + node = <_DTDElementContentDecl>_DTDElementContentDecl.__new__(_DTDElementContentDecl) + node._dtd = self._dtd + node._c_node = c1 + return node + else: + return None + + @property + def right(self): + _assertValidDTDNode(self, self._c_node) + c2 = self._c_node.c2 + if c2: + node = <_DTDElementContentDecl>_DTDElementContentDecl.__new__(_DTDElementContentDecl) + node._dtd = self._dtd + node._c_node = c2 + return node + else: + return None + + +@cython.final +@cython.internal +@cython.freelist(8) +cdef class _DTDAttributeDecl: + cdef DTD _dtd + cdef tree.xmlAttribute* _c_node + + def __repr__(self): + return "<%s.%s object name=%r elemname=%r prefix=%r type=%r default=%r default_value=%r at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, self.name, self.elemname, self.prefix, self.type, self.default, self.default_value, id(self)) + + @property + def name(self): + _assertValidDTDNode(self, self._c_node) + return funicodeOrNone(self._c_node.name) + + @property + def elemname(self): + _assertValidDTDNode(self, self._c_node) + return funicodeOrNone(self._c_node.elem) + + @property + def prefix(self): + _assertValidDTDNode(self, self._c_node) + return funicodeOrNone(self._c_node.prefix) + + @property + def type(self): + _assertValidDTDNode(self, self._c_node) + cdef int type = self._c_node.atype + if type == tree.XML_ATTRIBUTE_CDATA: + return "cdata" + elif type == tree.XML_ATTRIBUTE_ID: + return "id" + elif type == tree.XML_ATTRIBUTE_IDREF: + return "idref" + elif type == tree.XML_ATTRIBUTE_IDREFS: + return "idrefs" + elif type == tree.XML_ATTRIBUTE_ENTITY: + return "entity" + elif type == tree.XML_ATTRIBUTE_ENTITIES: + return "entities" + elif type == tree.XML_ATTRIBUTE_NMTOKEN: + return "nmtoken" + elif type == tree.XML_ATTRIBUTE_NMTOKENS: + return "nmtokens" + elif type == tree.XML_ATTRIBUTE_ENUMERATION: + return "enumeration" + elif type == tree.XML_ATTRIBUTE_NOTATION: + return "notation" + else: + return None + + @property + def default(self): + _assertValidDTDNode(self, self._c_node) + cdef int default = self._c_node.def_ + if default == tree.XML_ATTRIBUTE_NONE: + return "none" + elif default == tree.XML_ATTRIBUTE_REQUIRED: + return "required" + elif default == tree.XML_ATTRIBUTE_IMPLIED: + return "implied" + elif default == tree.XML_ATTRIBUTE_FIXED: + return "fixed" + else: + return None + + @property + def default_value(self): + _assertValidDTDNode(self, self._c_node) + return funicodeOrNone(self._c_node.defaultValue) + + def itervalues(self): + _assertValidDTDNode(self, self._c_node) + cdef tree.xmlEnumeration *c_node = self._c_node.tree + while c_node is not NULL: + yield funicode(c_node.name) + c_node = c_node.next + + def values(self): + return list(self.itervalues()) + + +@cython.final +@cython.internal +@cython.freelist(8) +cdef class _DTDElementDecl: + cdef DTD _dtd + cdef tree.xmlElement* _c_node + + def __repr__(self): + return "<%s.%s object name=%r prefix=%r type=%r at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, self.name, self.prefix, self.type, id(self)) + + @property + def name(self): + _assertValidDTDNode(self, self._c_node) + return funicodeOrNone(self._c_node.name) + + @property + def prefix(self): + _assertValidDTDNode(self, self._c_node) + return funicodeOrNone(self._c_node.prefix) + + @property + def type(self): + _assertValidDTDNode(self, self._c_node) + cdef int type = self._c_node.etype + if type == tree.XML_ELEMENT_TYPE_UNDEFINED: + return "undefined" + elif type == tree.XML_ELEMENT_TYPE_EMPTY: + return "empty" + elif type == tree.XML_ELEMENT_TYPE_ANY: + return "any" + elif type == tree.XML_ELEMENT_TYPE_MIXED: + return "mixed" + elif type == tree.XML_ELEMENT_TYPE_ELEMENT: + return "element" + else: + return None + + @property + def content(self): + _assertValidDTDNode(self, self._c_node) + cdef tree.xmlElementContent *content = self._c_node.content + if content: + node = <_DTDElementContentDecl>_DTDElementContentDecl.__new__(_DTDElementContentDecl) + node._dtd = self._dtd + node._c_node = content + return node + else: + return None + + def iterattributes(self): + _assertValidDTDNode(self, self._c_node) + cdef tree.xmlAttribute *c_node = self._c_node.attributes + while c_node: + node = <_DTDAttributeDecl>_DTDAttributeDecl.__new__(_DTDAttributeDecl) + node._dtd = self._dtd + node._c_node = c_node + yield node + c_node = c_node.nexth + + def attributes(self): + return list(self.iterattributes()) + + +@cython.final +@cython.internal +@cython.freelist(8) +cdef class _DTDEntityDecl: + cdef DTD _dtd + cdef tree.xmlEntity* _c_node + def __repr__(self): + return "<%s.%s object name=%r at 0x%x>" % (self.__class__.__module__, self.__class__.__name__, self.name, id(self)) + + @property + def name(self): + _assertValidDTDNode(self, self._c_node) + return funicodeOrNone(self._c_node.name) + + @property + def orig(self): + _assertValidDTDNode(self, self._c_node) + return funicodeOrNone(self._c_node.orig) + + @property + def content(self): + _assertValidDTDNode(self, self._c_node) + return funicodeOrNone(self._c_node.content) + + @property + def system_url(self): + _assertValidDTDNode(self, self._c_node) + return funicodeOrNone(self._c_node.SystemID) + + +################################################################################ +# DTD + +cdef class DTD(_Validator): + """DTD(self, file=None, external_id=None) + A DTD validator. + + Can load from filesystem directly given a filename or file-like object. + Alternatively, pass the keyword parameter ``external_id`` to load from a + catalog. + """ + cdef tree.xmlDtd* _c_dtd + def __init__(self, file=None, *, external_id=None): + _Validator.__init__(self) + if file is not None: + file = _getFSPathOrObject(file) + if _isString(file): + file = _encodeFilename(file) + with self._error_log: + orig_loader = _register_document_loader() + self._c_dtd = xmlparser.xmlParseDTD(NULL, _xcstr(file)) + _reset_document_loader(orig_loader) + elif hasattr(file, 'read'): + orig_loader = _register_document_loader() + self._c_dtd = _parseDtdFromFilelike(file) + _reset_document_loader(orig_loader) + else: + raise DTDParseError, "file must be a filename, file-like or path-like object" + elif external_id is not None: + external_id_utf = _utf8(external_id) + with self._error_log: + orig_loader = _register_document_loader() + self._c_dtd = xmlparser.xmlParseDTD(external_id_utf, NULL) + _reset_document_loader(orig_loader) + else: + raise DTDParseError, "either filename or external ID required" + + if self._c_dtd is NULL: + raise DTDParseError( + self._error_log._buildExceptionMessage("error parsing DTD"), + self._error_log) + + @property + def name(self): + if self._c_dtd is NULL: + return None + return funicodeOrNone(self._c_dtd.name) + + @property + def external_id(self): + if self._c_dtd is NULL: + return None + return funicodeOrNone(self._c_dtd.ExternalID) + + @property + def system_url(self): + if self._c_dtd is NULL: + return None + return funicodeOrNone(self._c_dtd.SystemID) + + def iterelements(self): + cdef tree.xmlNode *c_node = self._c_dtd.children if self._c_dtd is not NULL else NULL + while c_node is not NULL: + if c_node.type == tree.XML_ELEMENT_DECL: + node = _DTDElementDecl() + node._dtd = self + node._c_node = c_node + yield node + c_node = c_node.next + + def elements(self): + return list(self.iterelements()) + + def iterentities(self): + cdef tree.xmlNode *c_node = self._c_dtd.children if self._c_dtd is not NULL else NULL + while c_node is not NULL: + if c_node.type == tree.XML_ENTITY_DECL: + node = _DTDEntityDecl() + node._dtd = self + node._c_node = c_node + yield node + c_node = c_node.next + + def entities(self): + return list(self.iterentities()) + + def __dealloc__(self): + tree.xmlFreeDtd(self._c_dtd) + + def __call__(self, etree): + """__call__(self, etree) + + Validate doc using the DTD. + + Returns true if the document is valid, false if not. + """ + cdef _Document doc + cdef _Element root_node + cdef xmlDoc* c_doc + cdef dtdvalid.xmlValidCtxt* valid_ctxt + cdef int ret = -1 + + assert self._c_dtd is not NULL, "DTD not initialised" + doc = _documentOrRaise(etree) + root_node = _rootNodeOrRaise(etree) + + valid_ctxt = dtdvalid.xmlNewValidCtxt() + if valid_ctxt is NULL: + raise DTDError("Failed to create validation context") + + # work around error reporting bug in libxml2 <= 2.9.1 (and later?) + # https://bugzilla.gnome.org/show_bug.cgi?id=724903 + valid_ctxt.error = _nullGenericErrorFunc + valid_ctxt.userData = NULL + + try: + with self._error_log: + c_doc = _fakeRootDoc(doc._c_doc, root_node._c_node) + ret = dtdvalid.xmlValidateDtd(valid_ctxt, c_doc, self._c_dtd) + _destroyFakeDoc(doc._c_doc, c_doc) + finally: + dtdvalid.xmlFreeValidCtxt(valid_ctxt) + + if ret == -1: + raise DTDValidateError("Internal error in DTD validation", + self._error_log) + return ret == 1 + + +cdef tree.xmlDtd* _parseDtdFromFilelike(file) except NULL: + cdef _ExceptionContext exc_context + cdef _FileReaderContext dtd_parser + cdef _ErrorLog error_log + cdef tree.xmlDtd* c_dtd = NULL + exc_context = _ExceptionContext() + dtd_parser = _FileReaderContext(file, exc_context, None) + error_log = _ErrorLog() + + with error_log: + c_dtd = dtd_parser._readDtd() + + exc_context._raise_if_stored() + if c_dtd is NULL: + raise DTDParseError("error parsing DTD", error_log) + return c_dtd + +cdef DTD _dtdFactory(tree.xmlDtd* c_dtd): + # do not run through DTD.__init__()! + cdef DTD dtd + if c_dtd is NULL: + return None + dtd = DTD.__new__(DTD) + dtd._c_dtd = _copyDtd(c_dtd) + _Validator.__init__(dtd) + return dtd + + +cdef tree.xmlDtd* _copyDtd(tree.xmlDtd* c_orig_dtd) except NULL: + """ + Copy a DTD. libxml2 (currently) fails to set up the element->attributes + links when copying DTDs, so we have to rebuild them here. + """ + c_dtd = tree.xmlCopyDtd(c_orig_dtd) + if not c_dtd: + raise MemoryError + cdef tree.xmlNode* c_node = c_dtd.children + while c_node: + if c_node.type == tree.XML_ATTRIBUTE_DECL: + _linkDtdAttribute(c_dtd, c_node) + c_node = c_node.next + return c_dtd + + +cdef void _linkDtdAttribute(tree.xmlDtd* c_dtd, tree.xmlAttribute* c_attr) noexcept: + """ + Create the link to the DTD attribute declaration from the corresponding + element declaration. + """ + c_elem = dtdvalid.xmlGetDtdElementDesc(c_dtd, c_attr.elem) + if not c_elem: + # no such element? something is wrong with the DTD ... + return + c_pos = c_elem.attributes + if not c_pos: + c_elem.attributes = c_attr + c_attr.nexth = NULL + return + # libxml2 keeps namespace declarations first, and we need to make + # sure we don't re-insert attributes that are already there + if _isDtdNsDecl(c_attr): + if not _isDtdNsDecl(c_pos): + c_elem.attributes = c_attr + c_attr.nexth = c_pos + return + while c_pos != c_attr and c_pos.nexth and _isDtdNsDecl(c_pos.nexth): + c_pos = c_pos.nexth + else: + # append at end + while c_pos != c_attr and c_pos.nexth: + c_pos = c_pos.nexth + if c_pos == c_attr: + return + c_attr.nexth = c_pos.nexth + c_pos.nexth = c_attr + + +cdef bint _isDtdNsDecl(tree.xmlAttribute* c_attr) noexcept: + if cstring_h.strcmp(c_attr.name, "xmlns") == 0: + return True + if (c_attr.prefix is not NULL and + cstring_h.strcmp(c_attr.prefix, "xmlns") == 0): + return True + return False diff --git a/intelliaide/vendor/lxml/etree.cpython-312-x86_64-linux-gnu.so b/intelliaide/vendor/lxml/etree.cpython-312-x86_64-linux-gnu.so new file mode 100755 index 0000000..94b6b6a Binary files /dev/null and b/intelliaide/vendor/lxml/etree.cpython-312-x86_64-linux-gnu.so differ diff --git a/intelliaide/vendor/lxml/etree.h b/intelliaide/vendor/lxml/etree.h new file mode 100644 index 0000000..10a6039 --- /dev/null +++ b/intelliaide/vendor/lxml/etree.h @@ -0,0 +1,244 @@ +/* Generated by Cython 3.2.4 */ + +#ifndef __PYX_HAVE__lxml__etree +#define __PYX_HAVE__lxml__etree + +#include "Python.h" +struct LxmlDocument; +struct LxmlElement; +struct LxmlElementTree; +struct LxmlElementTagMatcher; +struct LxmlElementIterator; +struct LxmlElementBase; +struct LxmlElementClassLookup; +struct LxmlFallbackElementClassLookup; + +/* "lxml/etree.pyx":456 + * + * # type of a function that steps from node to node + * ctypedef public xmlNode* (*_node_to_node_function)(xmlNode*) # <<<<<<<<<<<<<< + * + * +*/ +typedef xmlNode *(*_node_to_node_function)(xmlNode *); + +/* "lxml/etree.pyx":470 + * # Public Python API + * + * @cython.final # <<<<<<<<<<<<<< + * @cython.freelist(8) + * cdef public class _Document [ type LxmlDocumentType, object LxmlDocument ]: +*/ +struct LxmlDocument { + PyObject_HEAD + struct __pyx_vtabstruct_4lxml_5etree__Document *__pyx_vtab; + int _ns_counter; + PyObject *_prefix_tail; + xmlDoc *_c_doc; + struct __pyx_obj_4lxml_5etree__BaseParser *_parser; +}; + +/* "lxml/etree.pyx":822 + * + * + * @cython.no_gc_clear # <<<<<<<<<<<<<< + * cdef public class _Element [ type LxmlElementType, object LxmlElement ]: + * """Element class. +*/ +struct LxmlElement { + PyObject_HEAD + struct LxmlDocument *_doc; + xmlNode *_c_node; + PyObject *_tag; +}; + +/* "lxml/etree.pyx":1996 + * + * + * cdef public class _ElementTree [ type LxmlElementTreeType, # <<<<<<<<<<<<<< + * object LxmlElementTree ]: + * cdef _Document _doc +*/ +struct LxmlElementTree { + PyObject_HEAD + struct __pyx_vtabstruct_4lxml_5etree__ElementTree *__pyx_vtab; + struct LxmlDocument *_doc; + struct LxmlElement *_context_node; +}; + +/* "lxml/etree.pyx":2770 + * + * + * cdef public class _ElementTagMatcher [ object LxmlElementTagMatcher, # <<<<<<<<<<<<<< + * type LxmlElementTagMatcherType ]: + * """ +*/ +struct LxmlElementTagMatcher { + PyObject_HEAD + struct __pyx_vtabstruct_4lxml_5etree__ElementTagMatcher *__pyx_vtab; + PyObject *_pystrings; + int _node_type; + char *_href; + char *_name; +}; + +/* "lxml/etree.pyx":2801 + * self._name = NULL + * + * cdef public class _ElementIterator(_ElementTagMatcher) [ # <<<<<<<<<<<<<< + * object LxmlElementIterator, type LxmlElementIteratorType ]: + * """ +*/ +struct LxmlElementIterator { + struct LxmlElementTagMatcher __pyx_base; + struct LxmlElement *_node; + _node_to_node_function _next_element; +}; + +/* "src/lxml/classlookup.pxi":6 + * # Custom Element classes + * + * cdef public class ElementBase(_Element) [ type LxmlElementBaseType, # <<<<<<<<<<<<<< + * object LxmlElementBase ]: + * """ElementBase(*children, attrib=None, nsmap=None, **_extra) +*/ +struct LxmlElementBase { + struct LxmlElement __pyx_base; +}; + +/* "src/lxml/classlookup.pxi":210 + * # Element class lookup + * + * ctypedef public object (*_element_class_lookup_function)(object, _Document, xmlNode*) # <<<<<<<<<<<<<< + * + * # class to store element class lookup functions +*/ +typedef PyObject *(*_element_class_lookup_function)(PyObject *, struct LxmlDocument *, xmlNode *); + +/* "src/lxml/classlookup.pxi":213 + * + * # class to store element class lookup functions + * cdef public class ElementClassLookup [ type LxmlElementClassLookupType, # <<<<<<<<<<<<<< + * object LxmlElementClassLookup ]: + * """ElementClassLookup(self) +*/ +struct LxmlElementClassLookup { + PyObject_HEAD + _element_class_lookup_function _lookup_function; +}; + +/* "src/lxml/classlookup.pxi":221 + * + * + * cdef public class FallbackElementClassLookup(ElementClassLookup) \ # <<<<<<<<<<<<<< + * [ type LxmlFallbackElementClassLookupType, + * object LxmlFallbackElementClassLookup ]: +*/ +struct LxmlFallbackElementClassLookup { + struct LxmlElementClassLookup __pyx_base; + struct __pyx_vtabstruct_4lxml_5etree_FallbackElementClassLookup *__pyx_vtab; + struct LxmlElementClassLookup *fallback; + _element_class_lookup_function _fallback_function; +}; + +#ifndef __PYX_HAVE_API__lxml__etree + +#ifdef CYTHON_EXTERN_C + #undef __PYX_EXTERN_C + #define __PYX_EXTERN_C CYTHON_EXTERN_C +#elif defined(__PYX_EXTERN_C) + #ifdef _MSC_VER + #pragma message ("Please do not define the '__PYX_EXTERN_C' macro externally. Use 'CYTHON_EXTERN_C' instead.") + #else + #warning Please do not define the '__PYX_EXTERN_C' macro externally. Use 'CYTHON_EXTERN_C' instead. + #endif +#else + #ifdef __cplusplus + #define __PYX_EXTERN_C extern "C" + #else + #define __PYX_EXTERN_C extern + #endif +#endif + +#ifndef DL_IMPORT + #define DL_IMPORT(_T) _T +#endif + +__PYX_EXTERN_C DL_IMPORT(PyTypeObject) LxmlDocumentType; +__PYX_EXTERN_C DL_IMPORT(PyTypeObject) LxmlElementType; +__PYX_EXTERN_C DL_IMPORT(PyTypeObject) LxmlElementTreeType; +__PYX_EXTERN_C DL_IMPORT(PyTypeObject) LxmlElementTagMatcherType; +__PYX_EXTERN_C DL_IMPORT(PyTypeObject) LxmlElementIteratorType; +__PYX_EXTERN_C DL_IMPORT(PyTypeObject) LxmlElementBaseType; +__PYX_EXTERN_C DL_IMPORT(PyTypeObject) LxmlElementClassLookupType; +__PYX_EXTERN_C DL_IMPORT(PyTypeObject) LxmlFallbackElementClassLookupType; + +__PYX_EXTERN_C struct LxmlElement *deepcopyNodeToDocument(struct LxmlDocument *, xmlNode *); +__PYX_EXTERN_C struct LxmlElementTree *elementTreeFactory(struct LxmlElement *); +__PYX_EXTERN_C struct LxmlElementTree *newElementTree(struct LxmlElement *, PyObject *); +__PYX_EXTERN_C struct LxmlElementTree *adoptExternalDocument(xmlDoc *, PyObject *, int); +__PYX_EXTERN_C struct LxmlElement *elementFactory(struct LxmlDocument *, xmlNode *); +__PYX_EXTERN_C struct LxmlElement *makeElement(PyObject *, struct LxmlDocument *, PyObject *, PyObject *, PyObject *, PyObject *, PyObject *); +__PYX_EXTERN_C struct LxmlElement *makeSubElement(struct LxmlElement *, PyObject *, PyObject *, PyObject *, PyObject *, PyObject *); +__PYX_EXTERN_C void setElementClassLookupFunction(_element_class_lookup_function, PyObject *); +__PYX_EXTERN_C PyObject *lookupDefaultElementClass(PyObject *, PyObject *, xmlNode *); +__PYX_EXTERN_C PyObject *lookupNamespaceElementClass(PyObject *, PyObject *, xmlNode *); +__PYX_EXTERN_C PyObject *callLookupFallback(struct LxmlFallbackElementClassLookup *, struct LxmlDocument *, xmlNode *); +__PYX_EXTERN_C int tagMatches(xmlNode *, const xmlChar *, const xmlChar *); +__PYX_EXTERN_C struct LxmlDocument *documentOrRaise(PyObject *); +__PYX_EXTERN_C struct LxmlElement *rootNodeOrRaise(PyObject *); +__PYX_EXTERN_C int hasText(xmlNode *); +__PYX_EXTERN_C int hasTail(xmlNode *); +__PYX_EXTERN_C PyObject *textOf(xmlNode *); +__PYX_EXTERN_C PyObject *tailOf(xmlNode *); +__PYX_EXTERN_C int setNodeText(xmlNode *, PyObject *); +__PYX_EXTERN_C int setTailText(xmlNode *, PyObject *); +__PYX_EXTERN_C PyObject *attributeValue(xmlNode *, xmlAttr *); +__PYX_EXTERN_C PyObject *attributeValueFromNsName(xmlNode *, const xmlChar *, const xmlChar *); +__PYX_EXTERN_C PyObject *getAttributeValue(struct LxmlElement *, PyObject *, PyObject *); +__PYX_EXTERN_C PyObject *iterattributes(struct LxmlElement *, int); +__PYX_EXTERN_C PyObject *collectAttributes(xmlNode *, int); +__PYX_EXTERN_C int setAttributeValue(struct LxmlElement *, PyObject *, PyObject *); +__PYX_EXTERN_C int delAttribute(struct LxmlElement *, PyObject *); +__PYX_EXTERN_C int delAttributeFromNsName(xmlNode *, const xmlChar *, const xmlChar *); +__PYX_EXTERN_C int hasChild(xmlNode *); +__PYX_EXTERN_C xmlNode *findChild(xmlNode *, Py_ssize_t); +__PYX_EXTERN_C xmlNode *findChildForwards(xmlNode *, Py_ssize_t); +__PYX_EXTERN_C xmlNode *findChildBackwards(xmlNode *, Py_ssize_t); +__PYX_EXTERN_C xmlNode *nextElement(xmlNode *); +__PYX_EXTERN_C xmlNode *previousElement(xmlNode *); +__PYX_EXTERN_C void appendChild(struct LxmlElement *, struct LxmlElement *); +__PYX_EXTERN_C int appendChildToElement(struct LxmlElement *, struct LxmlElement *); +__PYX_EXTERN_C PyObject *pyunicode(const xmlChar *); +__PYX_EXTERN_C PyObject *utf8(PyObject *); +__PYX_EXTERN_C PyObject *getNsTag(PyObject *); +__PYX_EXTERN_C PyObject *getNsTagWithEmptyNs(PyObject *); +__PYX_EXTERN_C PyObject *namespacedName(xmlNode *); +__PYX_EXTERN_C PyObject *namespacedNameFromNsName(const xmlChar *, const xmlChar *); +__PYX_EXTERN_C void iteratorStoreNext(struct LxmlElementIterator *, struct LxmlElement *); +__PYX_EXTERN_C void initTagMatch(struct LxmlElementTagMatcher *, PyObject *); +__PYX_EXTERN_C xmlNs *findOrBuildNodeNsPrefix(struct LxmlDocument *, xmlNode *, const xmlChar *, const xmlChar *); + +#endif /* !__PYX_HAVE_API__lxml__etree */ + +/* WARNING: the interface of the module init function changed in CPython 3.5. */ +/* It now returns a PyModuleDef instance instead of a PyModule instance. */ + +/* WARNING: Use PyImport_AppendInittab("etree", PyInit_etree) instead of calling PyInit_etree directly from Python 3.5 */ +PyMODINIT_FUNC PyInit_etree(void); + +#if PY_VERSION_HEX >= 0x03050000 && (defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER) || (defined(__cplusplus) && __cplusplus >= 201402L)) +#if defined(__cplusplus) && __cplusplus >= 201402L +[[deprecated("Use PyImport_AppendInittab(\"etree\", PyInit_etree) instead of calling PyInit_etree directly.")]] inline +#elif defined(__GNUC__) || defined(__clang__) +__attribute__ ((__deprecated__("Use PyImport_AppendInittab(\"etree\", PyInit_etree) instead of calling PyInit_etree directly."), __unused__)) __inline__ +#elif defined(_MSC_VER) +__declspec(deprecated("Use PyImport_AppendInittab(\"etree\", PyInit_etree) instead of calling PyInit_etree directly.")) __inline +#endif +static PyObject* __PYX_WARN_IF_PyInit_etree_INIT_CALLED(PyObject* res) { + return res; +} +#define PyInit_etree() __PYX_WARN_IF_PyInit_etree_INIT_CALLED(PyInit_etree()) +#endif + +#endif /* !__PYX_HAVE__lxml__etree */ diff --git a/intelliaide/vendor/lxml/etree.pyx b/intelliaide/vendor/lxml/etree.pyx new file mode 100644 index 0000000..149a414 --- /dev/null +++ b/intelliaide/vendor/lxml/etree.pyx @@ -0,0 +1,3858 @@ +# cython: binding=True +# cython: auto_pickle=False +# cython: language_level=3 + +""" +The ``lxml.etree`` module implements the extended ElementTree API for XML. +""" + +__docformat__ = "restructuredtext en" + +__all__ = [ + 'AttributeBasedElementClassLookup', 'C14NError', 'C14NWriterTarget', 'CDATA', + 'Comment', 'CommentBase', 'CustomElementClassLookup', 'DEBUG', + 'DTD', 'DTDError', 'DTDParseError', 'DTDValidateError', + 'DocumentInvalid', 'ETCompatXMLParser', 'ETXPath', 'Element', + 'ElementBase', 'ElementClassLookup', 'ElementDefaultClassLookup', + 'ElementNamespaceClassLookup', 'ElementTree', 'Entity', 'EntityBase', + 'Error', 'ErrorDomains', 'ErrorLevels', 'ErrorTypes', 'Extension', + 'FallbackElementClassLookup', 'FunctionNamespace', 'HTML', 'HTMLParser', + 'ICONV_COMPILED_VERSION', + 'LIBXML_COMPILED_VERSION', 'LIBXML_VERSION', + 'LIBXML_FEATURES', + 'LIBXSLT_COMPILED_VERSION', 'LIBXSLT_VERSION', + 'LXML_VERSION', + 'LxmlError', 'LxmlRegistryError', 'LxmlSyntaxError', + 'NamespaceRegistryError', 'PI', 'PIBase', 'ParseError', + 'ParserBasedElementClassLookup', 'ParserError', 'ProcessingInstruction', + 'PyErrorLog', 'PythonElementClassLookup', 'QName', 'RelaxNG', + 'RelaxNGError', 'RelaxNGErrorTypes', 'RelaxNGParseError', + 'RelaxNGValidateError', 'Resolver', 'Schematron', 'SchematronError', + 'SchematronParseError', 'SchematronValidateError', 'SerialisationError', + 'SubElement', 'TreeBuilder', 'XInclude', 'XIncludeError', 'XML', + 'XMLDTDID', 'XMLID', 'XMLParser', 'XMLSchema', 'XMLSchemaError', + 'XMLSchemaParseError', 'XMLSchemaValidateError', 'XMLSyntaxError', + 'XMLTreeBuilder', 'XPath', 'XPathDocumentEvaluator', 'XPathError', + 'XPathEvalError', 'XPathEvaluator', 'XPathFunctionError', 'XPathResultError', + 'XPathSyntaxError', 'XSLT', 'XSLTAccessControl', 'XSLTApplyError', + 'XSLTError', 'XSLTExtension', 'XSLTExtensionError', 'XSLTParseError', + 'XSLTSaveError', 'canonicalize', + 'cleanup_namespaces', 'clear_error_log', 'dump', + 'fromstring', 'fromstringlist', 'get_default_parser', 'iselement', + 'iterparse', 'iterwalk', 'parse', 'parseid', 'register_namespace', + 'set_default_parser', 'set_element_class_lookup', 'strip_attributes', + 'strip_elements', 'strip_tags', 'tostring', 'tostringlist', 'tounicode', + 'use_global_python_log' + ] + +cimport cython + +from lxml cimport python +from lxml.includes cimport tree, config +from lxml.includes.tree cimport xmlDoc, xmlNode, xmlAttr, xmlNs, _isElement, _getNs +from lxml.includes.tree cimport const_xmlChar, xmlChar, _xcstr +from lxml.python cimport _cstr, _isString +from lxml.includes cimport xpath +from lxml.includes cimport c14n + +# Cython's standard declarations +cimport cpython.mem +cimport cpython.ref +from libc cimport limits, stdio, stdlib +from libc cimport string as cstring_h # not to be confused with stdlib 'string' +from libc.string cimport const_char + +cdef object os_path_abspath +from os.path import abspath as os_path_abspath + +cdef object BytesIO, StringIO +from io import BytesIO, StringIO + +cdef object OrderedDict +from collections import OrderedDict + +cdef object _elementpath +from lxml import _elementpath + +cdef object sys +import sys + +cdef object re +import re + +cdef object partial +from functools import partial + +cdef object islice +from itertools import islice + +cdef object ITER_EMPTY = iter(()) + +cdef object MutableMapping +from collections.abc import MutableMapping + +class _ImmutableMapping(MutableMapping): + def __getitem__(self, key): + raise KeyError, key + + def __setitem__(self, key, value): + raise KeyError, key + + def __delitem__(self, key): + raise KeyError, key + + def __contains__(self, key): + return False + + def __len__(self): + return 0 + + def __iter__(self): + return ITER_EMPTY + iterkeys = itervalues = iteritems = __iter__ + +cdef object IMMUTABLE_EMPTY_MAPPING = _ImmutableMapping() +del _ImmutableMapping + + +# the rules +# --------- +# any libxml C argument/variable is prefixed with c_ +# any non-public function/class is prefixed with an underscore +# instance creation is always through factories + +# what to do with libxml2/libxslt error messages? +# 0 : drop +# 1 : use log +DEF __DEBUG = 1 + +# maximum number of lines in the libxml2/xslt log if __DEBUG == 1 +DEF __MAX_LOG_SIZE = 100 + +# make the compiled-in debug state publicly available +DEBUG = __DEBUG + +# A struct to store a cached qualified tag name+href pair. +# While we can borrow the c_name from the document dict, +# PyPy requires us to store a Python reference for the +# namespace in order to keep the byte buffer alive. +cdef struct qname: + const_xmlChar* c_name + python.PyObject* href + +# initialize parser (and threading) +xmlparser.xmlInitParser() + +# global per-thread setup +tree.xmlThrDefIndentTreeOutput(1) +tree.xmlThrDefLineNumbersDefaultValue(1) + +_initThreadLogging() + +# filename encoding +cdef bytes _FILENAME_ENCODING = (sys.getfilesystemencoding() or sys.getdefaultencoding() or 'ascii').encode("UTF-8") +cdef char* _C_FILENAME_ENCODING = _cstr(_FILENAME_ENCODING) + +# set up some default namespace prefixes +cdef dict _DEFAULT_NAMESPACE_PREFIXES = { + b"http://www.w3.org/XML/1998/namespace": b'xml', + b"http://www.w3.org/1999/xhtml": b"html", + b"http://www.w3.org/1999/XSL/Transform": b"xsl", + b"http://www.w3.org/1999/02/22-rdf-syntax-ns#": b"rdf", + b"http://schemas.xmlsoap.org/wsdl/": b"wsdl", + # xml schema + b"http://www.w3.org/2001/XMLSchema": b"xs", + b"http://www.w3.org/2001/XMLSchema-instance": b"xsi", + # dublin core + b"http://purl.org/dc/elements/1.1/": b"dc", + # objectify + b"http://codespeak.net/lxml/objectify/pytype" : b"py", +} + +# To avoid runtime encoding overhead, we keep a Unicode copy +# of the uri-prefix mapping as (str, str) items view. +cdef object _DEFAULT_NAMESPACE_PREFIXES_ITEMS = [] + +cdef _update_default_namespace_prefixes_items(): + cdef bytes ns, prefix + global _DEFAULT_NAMESPACE_PREFIXES_ITEMS + _DEFAULT_NAMESPACE_PREFIXES_ITEMS = { + ns.decode('utf-8') : prefix.decode('utf-8') + for ns, prefix in _DEFAULT_NAMESPACE_PREFIXES.items() + }.items() + +_update_default_namespace_prefixes_items() + +cdef object _check_internal_prefix = re.compile(br"ns\d+$").match + +def register_namespace(prefix, uri): + """Registers a namespace prefix that newly created Elements in that + namespace will use. The registry is global, and any existing + mapping for either the given prefix or the namespace URI will be + removed. + """ + prefix_utf, uri_utf = _utf8(prefix), _utf8(uri) + if _check_internal_prefix(prefix_utf): + raise ValueError("Prefix format reserved for internal use") + _tagValidOrRaise(prefix_utf) + _uriValidOrRaise(uri_utf) + if (uri_utf == b"http://www.w3.org/XML/1998/namespace" and prefix_utf != b'xml' + or prefix_utf == b'xml' and uri_utf != b"http://www.w3.org/XML/1998/namespace"): + raise ValueError("Cannot change the 'xml' prefix of the XML namespace") + for k, v in list(_DEFAULT_NAMESPACE_PREFIXES.items()): + if k == uri_utf or v == prefix_utf: + del _DEFAULT_NAMESPACE_PREFIXES[k] + _DEFAULT_NAMESPACE_PREFIXES[uri_utf] = prefix_utf + _update_default_namespace_prefixes_items() + + +# Error superclass for ElementTree compatibility +cdef class Error(Exception): + pass + +# module level superclass for all exceptions +cdef class LxmlError(Error): + """Main exception base class for lxml. All other exceptions inherit from + this one. + """ + def __init__(self, message, error_log=None): + super(_Error, self).__init__(message) + if error_log is None: + self.error_log = __copyGlobalErrorLog() + else: + self.error_log = error_log.copy() + +cdef object _Error = Error + + +# superclass for all syntax errors +class LxmlSyntaxError(LxmlError, SyntaxError): + """Base class for all syntax errors. + """ + +cdef class C14NError(LxmlError): + """Error during C14N serialisation. + """ + +# version information +cdef tuple __unpackDottedVersion(version): + version_list = [] + l = (version.decode("ascii").replace('-', '.').split('.') + [0]*4)[:4] + for item in l: + try: + item = int(item) + except ValueError: + if item.startswith('dev'): + count = item[3:] + item = -300 + elif item.startswith('alpha'): + count = item[5:] + item = -200 + elif item.startswith('beta'): + count = item[4:] + item = -100 + else: + count = 0 + if count: + item += int(count) + version_list.append(item) + return tuple(version_list) + +cdef tuple __unpackIntVersion(int c_version, int base=100): + return ( + ((c_version // (base*base)) % base), + ((c_version // base) % base), + (c_version % base) + ) + +cdef int _LIBXML_VERSION_INT +try: + _LIBXML_VERSION_INT = int( + re.match('[0-9]+', (tree.xmlParserVersion).decode("ascii")).group(0)) +except Exception: + print("Unknown libxml2 version: " + (tree.xmlParserVersion).decode("latin1")) + _LIBXML_VERSION_INT = 0 + +LIBXML_VERSION = __unpackIntVersion(_LIBXML_VERSION_INT) +LIBXML_COMPILED_VERSION = __unpackIntVersion(tree.LIBXML_VERSION) +LXML_VERSION = __unpackDottedVersion(tree.LXML_VERSION_STRING) + +__version__ = tree.LXML_VERSION_STRING.decode("ascii") + +cdef extern from *: + """ + #ifdef ZLIB_VERNUM + #define __lxml_zlib_version (ZLIB_VERNUM >> 4) + #else + #define __lxml_zlib_version 0 + #endif + #ifdef _LIBICONV_VERSION + #define __lxml_iconv_version (_LIBICONV_VERSION << 8) + #else + #define __lxml_iconv_version 0 + #endif + """ + # zlib isn't included automatically by libxml2's headers + #long ZLIB_HEX_VERSION "__lxml_zlib_version" + long LIBICONV_HEX_VERSION "__lxml_iconv_version" + +#ZLIB_COMPILED_VERSION = __unpackIntVersion(ZLIB_HEX_VERSION, base=0x10) +ICONV_COMPILED_VERSION = __unpackIntVersion(LIBICONV_HEX_VERSION, base=0x100)[:2] + + +cdef extern from "libxml/xmlversion.h": + """ + static const char* const _lxml_lib_features[] = { +#ifdef LIBXML_HTML_ENABLED + "html", +#endif +#ifdef LIBXML_FTP_ENABLED + "ftp", +#endif +#ifdef LIBXML_HTTP_ENABLED + "http", +#endif +#ifdef LIBXML_CATALOG_ENABLED + "catalog", +#endif +#ifdef LIBXML_XPATH_ENABLED + "xpath", +#endif +#ifdef LIBXML_ICONV_ENABLED + "iconv", +#endif +#ifdef LIBXML_ICU_ENABLED + "icu", +#endif +#ifdef LIBXML_REGEXP_ENABLED + "regexp", +#endif +#ifdef LIBXML_SCHEMAS_ENABLED + "xmlschema", +#endif +#ifdef LIBXML_SCHEMATRON_ENABLED + "schematron", +#endif +#ifdef LIBXML_ZLIB_ENABLED + "zlib", +#endif +#ifdef LIBXML_LZMA_ENABLED + "lzma", +#endif + 0 + }; + """ + const char* const* _LXML_LIB_FEATURES "_lxml_lib_features" + + +cdef set _copy_lib_features(): + features = set() + feature = _LXML_LIB_FEATURES + while feature[0]: + features.add(feature[0].decode('ASCII')) + feature += 1 + return features + +LIBXML_COMPILED_FEATURES = _copy_lib_features() +LIBXML_FEATURES = { + feature_name for feature_id, feature_name in [ + #XML_WITH_THREAD = 1 + #XML_WITH_TREE = 2 + #XML_WITH_OUTPUT = 3 + #XML_WITH_PUSH = 4 + #XML_WITH_READER = 5 + #XML_WITH_PATTERN = 6 + #XML_WITH_WRITER = 7 + #XML_WITH_SAX1 = 8 + (xmlparser.XML_WITH_FTP, "ftp"), # XML_WITH_FTP = 9 + (xmlparser.XML_WITH_HTTP, "http"), # XML_WITH_HTTP = 10 + #XML_WITH_VALID = 11 + (xmlparser.XML_WITH_HTML, "html"), # XML_WITH_HTML = 12 + #XML_WITH_LEGACY = 13 + #XML_WITH_C14N = 14 + (xmlparser.XML_WITH_CATALOG, "catalog"), # XML_WITH_CATALOG = 15 + (xmlparser.XML_WITH_XPATH, "xpath"), # XML_WITH_XPATH = 16 + #XML_WITH_XPTR = 17 + #XML_WITH_XINCLUDE = 18 + (xmlparser.XML_WITH_ICONV, "iconv"), # XML_WITH_ICONV = 19 + #XML_WITH_ISO8859X = 20 + #XML_WITH_UNICODE = 21 + (xmlparser.XML_WITH_REGEXP, "regexp"), # XML_WITH_REGEXP = 22 + #XML_WITH_AUTOMATA = 23 + #XML_WITH_EXPR = 24 + (xmlparser.XML_WITH_SCHEMAS, "xmlschema"), # XML_WITH_SCHEMAS = 25 + (xmlparser.XML_WITH_SCHEMATRON, "schematron"), # XML_WITH_SCHEMATRON = 26 + #XML_WITH_MODULES = 27 + #XML_WITH_DEBUG = 28 + #XML_WITH_DEBUG_MEM = 29 + #XML_WITH_DEBUG_RUN = 30 # unused + (xmlparser.XML_WITH_ZLIB, "zlib"), # XML_WITH_ZLIB = 31 + (xmlparser.XML_WITH_ICU, "icu"), # XML_WITH_ICU = 32 + (xmlparser.XML_WITH_LZMA, "lzma"), # XML_WITH_LZMA = 33 + ] if xmlparser.xmlHasFeature(feature_id) +} + +cdef bint HAS_ZLIB_COMPRESSION = xmlparser.xmlHasFeature(xmlparser.XML_WITH_ZLIB) + + +# class for temporary storage of Python references, +# used e.g. for XPath results +@cython.final +@cython.internal +cdef class _TempStore: + cdef list _storage + def __init__(self): + self._storage = [] + + cdef int add(self, obj) except -1: + self._storage.append(obj) + return 0 + + cdef int clear(self) except -1: + del self._storage[:] + return 0 + + +# class for temporarily storing exceptions raised in extensions +@cython.internal +cdef class _ExceptionContext: + cdef object _exc_info + + cdef int clear(self) except -1: + self._exc_info = None + return 0 + + @cython.final + cdef void _store_raised(self) noexcept: + try: + self._exc_info = sys.exc_info() + except BaseException as e: + self._store_exception(e) + finally: + return # and swallow any further exceptions + + @cython.final + cdef int _store_exception(self, exception) except -1: + self._exc_info = (exception, None, None) + return 0 + + @cython.final + cdef bint _has_raised(self) except -1: + return self._exc_info is not None + + @cython.final + cdef int _raise_if_stored(self) except -1: + if self._exc_info is None: + return 0 + type, value, traceback = self._exc_info + self._exc_info = None + if value is None and traceback is None: + raise type + else: + raise type, value, traceback + + +# type of a function that steps from node to node +ctypedef public xmlNode* (*_node_to_node_function)(xmlNode*) + + +################################################################################ +# Include submodules + +include "proxy.pxi" # Proxy handling (element backpointers/memory/etc.) +include "apihelpers.pxi" # Private helper functions +include "xmlerror.pxi" # Error and log handling + + +################################################################################ +# Public Python API + +@cython.final +@cython.freelist(8) +cdef public class _Document [ type LxmlDocumentType, object LxmlDocument ]: + """Internal base class to reference a libxml document. + + When instances of this class are garbage collected, the libxml + document is cleaned up. + """ + cdef int _ns_counter + cdef bytes _prefix_tail + cdef xmlDoc* _c_doc + cdef _BaseParser _parser + + def __dealloc__(self): + # if there are no more references to the document, it is safe + # to clean the whole thing up, as all nodes have a reference to + # the document + tree.xmlFreeDoc(self._c_doc) + + @cython.final + cdef getroot(self): + # return an element proxy for the document root + cdef xmlNode* c_node + c_node = tree.xmlDocGetRootElement(self._c_doc) + if c_node is NULL: + return None + return _elementFactory(self, c_node) + + @cython.final + cdef bint hasdoctype(self) noexcept: + # DOCTYPE gets parsed into internal subset (xmlDTD*) + return self._c_doc is not NULL and self._c_doc.intSubset is not NULL + + @cython.final + cdef getdoctype(self): + # get doctype info: root tag, public/system ID (or None if not known) + cdef tree.xmlDtd* c_dtd + cdef xmlNode* c_root_node + public_id = None + sys_url = None + c_dtd = self._c_doc.intSubset + if c_dtd is not NULL: + if c_dtd.ExternalID is not NULL: + public_id = funicode(c_dtd.ExternalID) + if c_dtd.SystemID is not NULL: + sys_url = funicode(c_dtd.SystemID) + c_dtd = self._c_doc.extSubset + if c_dtd is not NULL: + if not public_id and c_dtd.ExternalID is not NULL: + public_id = funicode(c_dtd.ExternalID) + if not sys_url and c_dtd.SystemID is not NULL: + sys_url = funicode(c_dtd.SystemID) + c_root_node = tree.xmlDocGetRootElement(self._c_doc) + if c_root_node is NULL: + root_name = None + else: + root_name = funicode(c_root_node.name) + return root_name, public_id, sys_url + + @cython.final + cdef getxmlinfo(self): + # return XML version and encoding (or None if not known) + cdef xmlDoc* c_doc = self._c_doc + if c_doc.version is NULL: + version = None + else: + version = funicode(c_doc.version) + if c_doc.encoding is NULL: + encoding = None + else: + encoding = funicode(c_doc.encoding) + return version, encoding + + @cython.final + cdef isstandalone(self): + # returns True for "standalone=true", + # False for "standalone=false", None if not provided + if self._c_doc.standalone == -1: + return None + else: + return (self._c_doc.standalone == 1) + + @cython.final + cdef bytes buildNewPrefix(self): + # get a new unique prefix ("nsX") for this document + cdef bytes ns + if self._ns_counter < len(_PREFIX_CACHE): + ns = _PREFIX_CACHE[self._ns_counter] + else: + ns = python.PyBytes_FromFormat("ns%d", self._ns_counter) + if self._prefix_tail is not None: + ns += self._prefix_tail + self._ns_counter += 1 + if self._ns_counter < 0: + # overflow! + self._ns_counter = 0 + if self._prefix_tail is None: + self._prefix_tail = b"A" + else: + self._prefix_tail += b"A" + return ns + + @cython.final + cdef xmlNs* _findOrBuildNodeNs(self, xmlNode* c_node, + const_xmlChar* c_href, const_xmlChar* c_prefix, + bint is_attribute) except NULL: + """Get or create namespace structure for a node. Reuses the prefix if + possible. + """ + cdef xmlNs* c_ns + cdef xmlNs* c_doc_ns + cdef python.PyObject* dict_result + if c_node.type != tree.XML_ELEMENT_NODE: + assert c_node.type == tree.XML_ELEMENT_NODE, \ + "invalid node type %d, expected %d" % ( + c_node.type, tree.XML_ELEMENT_NODE) + # look for existing ns declaration + c_ns = _searchNsByHref(c_node, c_href, is_attribute) + if c_ns is not NULL: + if is_attribute and c_ns.prefix is NULL: + # do not put namespaced attributes into the default + # namespace as this would break serialisation + pass + else: + return c_ns + + # none found => determine a suitable new prefix + if c_prefix is NULL: + dict_result = python.PyDict_GetItem( + _DEFAULT_NAMESPACE_PREFIXES, c_href) + if dict_result is not NULL: + prefix = dict_result + else: + prefix = self.buildNewPrefix() + c_prefix = _xcstr(prefix) + + # make sure the prefix is not in use already + while tree.xmlSearchNs(self._c_doc, c_node, c_prefix) is not NULL: + prefix = self.buildNewPrefix() + c_prefix = _xcstr(prefix) + + # declare the namespace and return it + c_ns = tree.xmlNewNs(c_node, c_href, c_prefix) + if c_ns is NULL: + raise MemoryError() + return c_ns + + @cython.final + cdef int _setNodeNs(self, xmlNode* c_node, const_xmlChar* c_href) except -1: + "Lookup namespace structure and set it for the node." + c_ns = self._findOrBuildNodeNs(c_node, c_href, NULL, 0) + tree.xmlSetNs(c_node, c_ns) + + +cdef tuple __initPrefixCache(): + cdef int i + return tuple([ python.PyBytes_FromFormat("ns%d", i) + for i in range(26) ]) + +cdef tuple _PREFIX_CACHE = __initPrefixCache() + + +cdef _Document _documentFactory(xmlDoc* c_doc, _BaseParser parser): + cdef _Document result + result = _Document.__new__(_Document) + result._c_doc = c_doc + result._ns_counter = 0 + result._prefix_tail = None + if parser is None: + parser = __GLOBAL_PARSER_CONTEXT.getDefaultParser() + result._parser = parser + return result + + +cdef object _find_invalid_public_id_characters = re.compile( + ur"[^\x20\x0D\x0Aa-zA-Z0-9'()+,./:=?;!*#@$_%-]+").search + + +cdef class DocInfo: + "Document information provided by parser and DTD." + cdef _Document _doc + def __cinit__(self, tree): + "Create a DocInfo object for an ElementTree object or root Element." + self._doc = _documentOrRaise(tree) + root_name, public_id, system_url = self._doc.getdoctype() + if not root_name and (public_id or system_url): + raise ValueError, "Could not find root node" + + @property + def root_name(self): + """Returns the name of the root node as defined by the DOCTYPE.""" + root_name, public_id, system_url = self._doc.getdoctype() + return root_name + + @cython.final + cdef tree.xmlDtd* _get_c_dtd(self) noexcept: + """"Return the DTD. Create it if it does not yet exist.""" + cdef xmlDoc* c_doc = self._doc._c_doc + cdef xmlNode* c_root_node + cdef const_xmlChar* c_name + + if c_doc.intSubset: + return c_doc.intSubset + + c_root_node = tree.xmlDocGetRootElement(c_doc) + c_name = c_root_node.name if c_root_node else NULL + return tree.xmlCreateIntSubset(c_doc, c_name, NULL, NULL) + + def clear(self): + """Removes DOCTYPE and internal subset from the document.""" + cdef xmlDoc* c_doc = self._doc._c_doc + cdef tree.xmlNode* c_dtd = c_doc.intSubset + if c_dtd is NULL: + return + tree.xmlUnlinkNode(c_dtd) + tree.xmlFreeNode(c_dtd) + + property public_id: + """Public ID of the DOCTYPE. + + Mutable. May be set to a valid string or None. If a DTD does not + exist, setting this variable (even to None) will create one. + """ + def __get__(self): + root_name, public_id, system_url = self._doc.getdoctype() + return public_id + + def __set__(self, value): + cdef xmlChar* c_value = NULL + if value is not None: + match = _find_invalid_public_id_characters(value) + if match: + raise ValueError, f'Invalid character(s) {match.group(0)!r} in public_id.' + value = _utf8(value) + c_value = tree.xmlStrdup(_xcstr(value)) + if not c_value: + raise MemoryError() + + c_dtd = self._get_c_dtd() + if not c_dtd: + tree.xmlFree(c_value) + raise MemoryError() + if c_dtd.ExternalID: + tree.xmlFree(c_dtd.ExternalID) + c_dtd.ExternalID = c_value + + property system_url: + """System ID of the DOCTYPE. + + Mutable. May be set to a valid string or None. If a DTD does not + exist, setting this variable (even to None) will create one. + """ + def __get__(self): + root_name, public_id, system_url = self._doc.getdoctype() + return system_url + + def __set__(self, value): + cdef xmlChar* c_value = NULL + if value is not None: + bvalue = _utf8(value) + # sys_url may be any valid unicode string that can be + # enclosed in single quotes or quotes. + if b"'" in bvalue and b'"' in bvalue: + raise ValueError( + 'System URL may not contain both single (\') and double quotes (").') + c_value = tree.xmlStrdup(_xcstr(bvalue)) + if not c_value: + raise MemoryError() + + c_dtd = self._get_c_dtd() + if not c_dtd: + tree.xmlFree(c_value) + raise MemoryError() + if c_dtd.SystemID: + tree.xmlFree(c_dtd.SystemID) + c_dtd.SystemID = c_value + + @property + def xml_version(self): + """Returns the XML version as declared by the document.""" + xml_version, encoding = self._doc.getxmlinfo() + return xml_version + + @property + def encoding(self): + """Returns the encoding name as declared by the document.""" + xml_version, encoding = self._doc.getxmlinfo() + return encoding + + @property + def standalone(self): + """Returns the standalone flag as declared by the document. The possible + values are True (``standalone='yes'``), False + (``standalone='no'`` or flag not provided in the declaration), + and None (unknown or no declaration found). Note that a + normal truth test on this value will always tell if the + ``standalone`` flag was set to ``'yes'`` or not. + """ + return self._doc.isstandalone() + + property URL: + "The source URL of the document (or None if unknown)." + def __get__(self): + if self._doc._c_doc.URL is NULL: + return None + return _decodeFilename(self._doc._c_doc.URL) + def __set__(self, url): + url = _encodeFilename(url) + c_oldurl = self._doc._c_doc.URL + if url is None: + self._doc._c_doc.URL = NULL + else: + self._doc._c_doc.URL = tree.xmlStrdup(_xcstr(url)) + if c_oldurl is not NULL: + tree.xmlFree(c_oldurl) + + @property + def doctype(self): + """Returns a DOCTYPE declaration string for the document.""" + root_name, public_id, system_url = self._doc.getdoctype() + if system_url: + # If '"' in system_url, we must escape it with single + # quotes, otherwise escape with double quotes. If url + # contains both a single quote and a double quote, XML + # standard is being violated. + if '"' in system_url: + quoted_system_url = f"'{system_url}'" + else: + quoted_system_url = f'"{system_url}"' + if public_id: + if system_url: + return f'' + else: + return f'' + elif system_url: + return f'' + elif self._doc.hasdoctype(): + return f'' + else: + return '' + + @property + def internalDTD(self): + """Returns a DTD validator based on the internal subset of the document.""" + return _dtdFactory(self._doc._c_doc.intSubset) + + @property + def externalDTD(self): + """Returns a DTD validator based on the external subset of the document.""" + return _dtdFactory(self._doc._c_doc.extSubset) + + +@cython.no_gc_clear +cdef public class _Element [ type LxmlElementType, object LxmlElement ]: + """Element class. + + References a document object and a libxml node. + + By pointing to a Document instance, a reference is kept to + _Document as long as there is some pointer to a node in it. + """ + cdef _Document _doc + cdef xmlNode* _c_node + cdef object _tag + + def _init(self): + """_init(self) + + Called after object initialisation. Custom subclasses may override + this if they recursively call _init() in the superclasses. + """ + + @cython.linetrace(False) + @cython.profile(False) + def __dealloc__(self): + #print("trying to free node:", self._c_node) + #displayNode(self._c_node, 0) + if self._c_node is not NULL: + _unregisterProxy(self) + attemptDeallocation(self._c_node) + + # MANIPULATORS + + def __setitem__(self, x, value): + """__setitem__(self, x, value) + + Replaces the given subelement index or slice. + """ + cdef xmlNode* c_node = NULL + cdef xmlNode* c_next + cdef xmlDoc* c_source_doc + cdef _Element element + cdef bint left_to_right + cdef Py_ssize_t slicelength = 0, step = 0 + _assertValidNode(self) + if value is None: + raise ValueError, "cannot assign None" + if isinstance(x, slice): + # slice assignment + _findChildSlice(x, self._c_node, &c_node, &step, &slicelength) + if step > 0: + left_to_right = 1 + else: + left_to_right = 0 + step = -step if step != python.PY_SSIZE_T_MIN else python.PY_SSIZE_T_MAX + _replaceSlice(self, c_node, slicelength, step, left_to_right, value) + return + else: + # otherwise: normal item assignment + element = value + _assertValidNode(element) + c_node = _findChild(self._c_node, x) + if c_node is NULL: + raise IndexError, "list index out of range" + c_source_doc = element._c_node.doc + c_next = element._c_node.next + _removeText(c_node.next) + tree.xmlReplaceNode(c_node, element._c_node) + _moveTail(c_next, element._c_node) + moveNodeToDocument(self._doc, c_source_doc, element._c_node) + if not attemptDeallocation(c_node): + moveNodeToDocument(self._doc, c_node.doc, c_node) + + def __delitem__(self, x): + """__delitem__(self, x) + + Deletes the given subelement or a slice. + """ + cdef xmlNode* c_node = NULL + cdef xmlNode* c_next + cdef Py_ssize_t step = 0, slicelength = 0 + _assertValidNode(self) + if isinstance(x, slice): + # slice deletion + if _isFullSlice(x): + c_node = self._c_node.children + if c_node is not NULL: + if not _isElement(c_node): + c_node = _nextElement(c_node) + while c_node is not NULL: + c_next = _nextElement(c_node) + _removeNode(self._doc, c_node) + c_node = c_next + else: + _findChildSlice(x, self._c_node, &c_node, &step, &slicelength) + _deleteSlice(self._doc, c_node, slicelength, step) + else: + # item deletion + c_node = _findChild(self._c_node, x) + if c_node is NULL: + raise IndexError, f"index out of range: {x}" + _removeNode(self._doc, c_node) + + def __deepcopy__(self, memo): + "__deepcopy__(self, memo)" + return self.__copy__() + + def __copy__(self): + "__copy__(self)" + cdef xmlDoc* c_doc + cdef xmlNode* c_node + cdef _Document new_doc + _assertValidNode(self) + c_doc = _copyDocRoot(self._doc._c_doc, self._c_node) # recursive + new_doc = _documentFactory(c_doc, self._doc._parser) + root = new_doc.getroot() + if root is not None: + return root + # Comment/PI + c_node = c_doc.children + while c_node is not NULL and c_node.type != self._c_node.type: + c_node = c_node.next + if c_node is NULL: + return None + return _elementFactory(new_doc, c_node) + + def set(self, key, value): + """set(self, key, value) + + Sets an element attribute. + In HTML documents (not XML or XHTML), the value None is allowed and creates + an attribute without value (just the attribute name). + """ + _assertValidNode(self) + _setAttributeValue(self, key, value) + + def append(self, _Element element not None): + """append(self, element) + + Adds a subelement to the end of this element. + """ + _assertValidNode(self) + _assertValidNode(element) + _appendChild(self, element) + + def addnext(self, _Element element not None): + """addnext(self, element) + + Adds the element as a following sibling directly after this + element. + + This is normally used to set a processing instruction or comment after + the root node of a document. Note that tail text is automatically + discarded when adding at the root level. + """ + _assertValidNode(self) + _assertValidNode(element) + if self._c_node.parent != NULL and not _isElement(self._c_node.parent): + if element._c_node.type not in (tree.XML_PI_NODE, tree.XML_COMMENT_NODE): + raise TypeError, "Only processing instructions and comments can be siblings of the root element" + element.tail = None + _appendSibling(self, element) + + def addprevious(self, _Element element not None): + """addprevious(self, element) + + Adds the element as a preceding sibling directly before this + element. + + This is normally used to set a processing instruction or comment + before the root node of a document. Note that tail text is + automatically discarded when adding at the root level. + """ + _assertValidNode(self) + _assertValidNode(element) + if self._c_node.parent != NULL and not _isElement(self._c_node.parent): + if element._c_node.type != tree.XML_PI_NODE: + if element._c_node.type != tree.XML_COMMENT_NODE: + raise TypeError, "Only processing instructions and comments can be siblings of the root element" + element.tail = None + _prependSibling(self, element) + + def extend(self, elements): + """extend(self, elements) + + Extends the current children by the elements in the iterable. + """ + cdef _Element element + _assertValidNode(self) + for element in elements: + if element is None: + raise TypeError, "Node must not be None" + _assertValidNode(element) + _appendChild(self, element) + + def clear(self, bint keep_tail=False): + """clear(self, keep_tail=False) + + Resets an element. This function removes all subelements, clears + all attributes and sets the text and tail properties to None. + + Pass ``keep_tail=True`` to leave the tail text untouched. + """ + cdef xmlAttr* c_attr + cdef xmlAttr* c_attr_next + cdef xmlNode* c_node + cdef xmlNode* c_node_next + _assertValidNode(self) + c_node = self._c_node + # remove self.text and self.tail + _removeText(c_node.children) + if not keep_tail: + _removeText(c_node.next) + # remove all attributes + c_attr = c_node.properties + if c_attr: + c_node.properties = NULL + tree.xmlFreePropList(c_attr) + # remove all subelements + c_node = c_node.children + if c_node and not _isElement(c_node): + c_node = _nextElement(c_node) + while c_node is not NULL: + c_node_next = _nextElement(c_node) + _removeNode(self._doc, c_node) + c_node = c_node_next + + def insert(self, index: int, _Element element not None): + """insert(self, index, element) + + Inserts a subelement at the given position in this element + """ + cdef xmlNode* c_node + cdef xmlNode* c_next + cdef xmlDoc* c_source_doc + _assertValidNode(self) + _assertValidNode(element) + c_node = _findChild(self._c_node, index) + if c_node is NULL: + _appendChild(self, element) + return + # prevent cycles + if _isAncestorOrSame(element._c_node, self._c_node): + raise ValueError("cannot append parent to itself") + c_source_doc = element._c_node.doc + c_next = element._c_node.next + tree.xmlAddPrevSibling(c_node, element._c_node) + _moveTail(c_next, element._c_node) + moveNodeToDocument(self._doc, c_source_doc, element._c_node) + + def remove(self, _Element element not None): + """remove(self, element) + + Removes a matching subelement. Unlike the find methods, this + method compares elements based on identity, not on tag value + or contents. + """ + cdef xmlNode* c_node + cdef xmlNode* c_next + _assertValidNode(self) + _assertValidNode(element) + c_node = element._c_node + if c_node.parent is not self._c_node: + raise ValueError, "Element is not a child of this node." + c_next = element._c_node.next + tree.xmlUnlinkNode(c_node) + _moveTail(c_next, c_node) + # fix namespace declarations + moveNodeToDocument(self._doc, c_node.doc, c_node) + + def replace(self, _Element old_element not None, + _Element new_element not None): + """replace(self, old_element, new_element) + + Replaces a subelement with the element passed as second argument. + """ + cdef xmlNode* c_old_node + cdef xmlNode* c_old_next + cdef xmlNode* c_new_node + cdef xmlNode* c_new_next + cdef xmlDoc* c_source_doc + _assertValidNode(self) + _assertValidNode(old_element) + _assertValidNode(new_element) + c_old_node = old_element._c_node + if c_old_node.parent is not self._c_node: + raise ValueError, "Element is not a child of this node." + c_new_node = new_element._c_node + # prevent cycles + if _isAncestorOrSame(c_new_node, self._c_node): + raise ValueError("cannot append parent to itself") + # replace node + c_old_next = c_old_node.next + c_new_next = c_new_node.next + c_source_doc = c_new_node.doc + tree.xmlReplaceNode(c_old_node, c_new_node) + _moveTail(c_new_next, c_new_node) + _moveTail(c_old_next, c_old_node) + moveNodeToDocument(self._doc, c_source_doc, c_new_node) + # fix namespace declarations + moveNodeToDocument(self._doc, c_old_node.doc, c_old_node) + + # PROPERTIES + property tag: + """Element tag + """ + def __get__(self): + if self._tag is not None: + return self._tag + _assertValidNode(self) + self._tag = _namespacedName(self._c_node) + return self._tag + + def __set__(self, value): + cdef _BaseParser parser + _assertValidNode(self) + ns, name = _getNsTag(value) + parser = self._doc._parser + if parser is not None and parser._for_html: + _htmlTagValidOrRaise(name) + else: + _tagValidOrRaise(name) + self._tag = value + tree.xmlNodeSetName(self._c_node, _xcstr(name)) + if ns is None: + self._c_node.ns = NULL + else: + self._doc._setNodeNs(self._c_node, _xcstr(ns)) + + @property + def attrib(self): + """Element attribute dictionary. Where possible, use get(), set(), + keys(), values() and items() to access element attributes. + """ + return _Attrib.__new__(_Attrib, self) + + property text: + """Text before the first subelement. This is either a string or + the value None, if there was no text. + """ + def __get__(self): + _assertValidNode(self) + return _collectText(self._c_node.children) + + def __set__(self, value): + _assertValidNode(self) + if isinstance(value, QName): + value = _resolveQNameText(self, value).decode('utf8') + _setNodeText(self._c_node, value) + + # using 'del el.text' is the wrong thing to do + #def __del__(self): + # _setNodeText(self._c_node, None) + + property tail: + """Text after this element's end tag, but before the next sibling + element's start tag. This is either a string or the value None, if + there was no text. + """ + def __get__(self): + _assertValidNode(self) + return _collectText(self._c_node.next) + + def __set__(self, value): + _assertValidNode(self) + _setTailText(self._c_node, value) + + # using 'del el.tail' is the wrong thing to do + #def __del__(self): + # _setTailText(self._c_node, None) + + # not in ElementTree, read-only + @property + def prefix(self): + """Namespace prefix or None. + """ + if self._c_node.ns is not NULL: + if self._c_node.ns.prefix is not NULL: + return funicode(self._c_node.ns.prefix) + return None + + # not in ElementTree, read-only + property sourceline: + """Original line number as found by the parser or None if unknown. + """ + def __get__(self): + cdef long line + _assertValidNode(self) + line = tree.xmlGetLineNo(self._c_node) + return line if line > 0 else None + + def __set__(self, line): + _assertValidNode(self) + if line <= 0: + self._c_node.line = 0 + else: + self._c_node.line = line + + # not in ElementTree, read-only + @property + def nsmap(self): + """Namespace prefix->URI mapping known in the context of this + Element. This includes all namespace declarations of the + parents. + + Note that changing the returned dict has no effect on the Element. + """ + _assertValidNode(self) + return _build_nsmap(self._c_node) + + # not in ElementTree, read-only + property base: + """The base URI of the Element (xml:base or HTML base URL). + None if the base URI is unknown. + + Note that the value depends on the URL of the document that + holds the Element if there is no xml:base attribute on the + Element or its ancestors. + + Setting this property will set an xml:base attribute on the + Element, regardless of the document type (XML or HTML). + """ + def __get__(self): + _assertValidNode(self) + c_base = tree.xmlNodeGetBase(self._doc._c_doc, self._c_node) + if c_base is NULL: + if self._doc._c_doc.URL is NULL: + return None + return _decodeFilename(self._doc._c_doc.URL) + try: + base = _decodeFilename(c_base) + finally: + tree.xmlFree(c_base) + return base + + def __set__(self, url): + _assertValidNode(self) + if url is None: + c_base = NULL + else: + url = _encodeFilename(url) + c_base = _xcstr(url) + tree.xmlNodeSetBase(self._c_node, c_base) + + # ACCESSORS + def __repr__(self): + "__repr__(self)" + return "" % (self.tag, id(self)) + + def __getitem__(self, x): + """Returns the subelement at the given position or the requested + slice. + """ + cdef xmlNode* c_node = NULL + cdef Py_ssize_t step = 0, slicelength = 0 + cdef Py_ssize_t c, i + cdef _node_to_node_function next_element + cdef list result + _assertValidNode(self) + if isinstance(x, slice): + # slicing + if _isFullSlice(x): + return _collectChildren(self) + _findChildSlice(x, self._c_node, &c_node, &step, &slicelength) + if c_node is NULL: + return [] + if step > 0: + next_element = _nextElement + else: + step = -step if step != python.PY_SSIZE_T_MIN else python.PY_SSIZE_T_MAX + next_element = _previousElement + result = [] + c = 0 + while c_node is not NULL and c < slicelength: + result.append(_elementFactory(self._doc, c_node)) + c += 1 + for i in range(step): + c_node = next_element(c_node) + if c_node is NULL: + break + return result + else: + # indexing + c_node = _findChild(self._c_node, x) + if c_node is NULL: + raise IndexError, "list index out of range" + return _elementFactory(self._doc, c_node) + + def __len__(self): + """__len__(self) + + Returns the number of subelements. + """ + _assertValidNode(self) + return _countElements(self._c_node.children) + + def __bool__(self): + """__bool__(self)""" + import warnings + warnings.warn( + "Truth-testing of elements was a source of confusion and will always " + "return True in future versions. " + "Use specific 'len(elem)' or 'elem is not None' test instead.", + FutureWarning + ) + # emulate old behaviour + _assertValidNode(self) + return _hasChild(self._c_node) + + def __contains__(self, element): + "__contains__(self, element)" + cdef xmlNode* c_node + _assertValidNode(self) + if not isinstance(element, _Element): + return 0 + c_node = (<_Element>element)._c_node + return c_node is not NULL and c_node.parent is self._c_node + + def __iter__(self): + "__iter__(self)" + return ElementChildIterator(self) + + def __reversed__(self): + "__reversed__(self)" + return ElementChildIterator(self, reversed=True) + + def index(self, child: _Element, start: int = None, stop: int = None): + """index(self, child, start=None, stop=None) + + Find the position of the child within the parent. + + This method is not part of the original ElementTree API. + """ + cdef Py_ssize_t k, l + cdef Py_ssize_t c_start, c_stop + cdef xmlNode* c_child + cdef xmlNode* c_start_node + _assertValidNode(self) + _assertValidNode(child) + c_child = child._c_node + if c_child.parent is not self._c_node: + raise ValueError, "Element is not a child of this node." + + # handle the unbounded search straight away (normal case) + if stop is None and (start is None or start == 0): + k = 0 + c_child = c_child.prev + while c_child is not NULL: + if _isElement(c_child): + k += 1 + c_child = c_child.prev + return k + + # check indices + if start is None: + c_start = 0 + else: + c_start = start + if stop is None: + c_stop = 0 + else: + c_stop = stop + if c_stop == 0 or \ + c_start >= c_stop and (c_stop > 0 or c_start < 0): + raise ValueError, "list.index(x): x not in slice" + + # for negative slice indices, check slice before searching index + if c_start < 0 or c_stop < 0: + # start from right, at most up to leftmost(c_start, c_stop) + if c_start < c_stop: + k = -c_start + else: + k = -c_stop + c_start_node = self._c_node.last + l = 1 + while c_start_node != c_child and l < k: + if _isElement(c_start_node): + l += 1 + c_start_node = c_start_node.prev + if c_start_node == c_child: + # found! before slice end? + if c_stop < 0 and l <= -c_stop: + raise ValueError, "list.index(x): x not in slice" + elif c_start < 0: + raise ValueError, "list.index(x): x not in slice" + + # now determine the index backwards from child + c_child = c_child.prev + k = 0 + if c_stop > 0: + # we can optimize: stop after c_stop elements if not found + while c_child != NULL and k < c_stop: + if _isElement(c_child): + k += 1 + c_child = c_child.prev + if k < c_stop: + return k + else: + # traverse all + while c_child != NULL: + if _isElement(c_child): + k = k + 1 + c_child = c_child.prev + if c_start > 0: + if k >= c_start: + return k + else: + return k + if c_start != 0 or c_stop != 0: + raise ValueError, "list.index(x): x not in slice" + else: + raise ValueError, "list.index(x): x not in list" + + def get(self, key, default=None): + """get(self, key, default=None) + + Gets an element attribute. + """ + _assertValidNode(self) + return _getAttributeValue(self, key, default) + + def keys(self): + """keys(self) + + Gets a list of attribute names. The names are returned in an + arbitrary order (just like for an ordinary Python dictionary). + """ + _assertValidNode(self) + return _collectAttributes(self._c_node, 1) + + def values(self): + """values(self) + + Gets element attribute values as a sequence of strings. The + attributes are returned in an arbitrary order. + """ + _assertValidNode(self) + return _collectAttributes(self._c_node, 2) + + def items(self): + """items(self) + + Gets element attributes, as a sequence. The attributes are returned in + an arbitrary order. + """ + _assertValidNode(self) + return _collectAttributes(self._c_node, 3) + + def getchildren(self): + """getchildren(self) + + Returns all direct children. The elements are returned in document + order. + + :deprecated: Note that this method has been deprecated as of + ElementTree 1.3 and lxml 2.0. New code should use + ``list(element)`` or simply iterate over elements. + """ + _assertValidNode(self) + return _collectChildren(self) + + def getparent(self): + """getparent(self) + + Returns the parent of this element or None for the root element. + """ + cdef xmlNode* c_node + #_assertValidNode(self) # not needed + c_node = _parentElement(self._c_node) + if c_node is NULL: + return None + return _elementFactory(self._doc, c_node) + + def getnext(self): + """getnext(self) + + Returns the following sibling of this element or None. + """ + cdef xmlNode* c_node + #_assertValidNode(self) # not needed + c_node = _nextElement(self._c_node) + if c_node is NULL: + return None + return _elementFactory(self._doc, c_node) + + def getprevious(self): + """getprevious(self) + + Returns the preceding sibling of this element or None. + """ + cdef xmlNode* c_node + #_assertValidNode(self) # not needed + c_node = _previousElement(self._c_node) + if c_node is NULL: + return None + return _elementFactory(self._doc, c_node) + + def itersiblings(self, tag=None, *tags, preceding=False): + """itersiblings(self, tag=None, *tags, preceding=False) + + Iterate over the following or preceding siblings of this element. + + The direction is determined by the 'preceding' keyword which + defaults to False, i.e. forward iteration over the following + siblings. When True, the iterator yields the preceding + siblings in reverse document order, i.e. starting right before + the current element and going backwards. + + Can be restricted to find only elements with specific tags, + see `iter`. + """ + if preceding: + if self._c_node and not self._c_node.prev: + return ITER_EMPTY + elif self._c_node and not self._c_node.next: + return ITER_EMPTY + if tag is not None: + tags += (tag,) + return SiblingsIterator(self, tags, preceding=preceding) + + def iterancestors(self, tag=None, *tags): + """iterancestors(self, tag=None, *tags) + + Iterate over the ancestors of this element (from parent to parent). + + Can be restricted to find only elements with specific tags, + see `iter`. + """ + if self._c_node and not self._c_node.parent: + return ITER_EMPTY + if tag is not None: + tags += (tag,) + return AncestorsIterator(self, tags) + + def iterdescendants(self, tag=None, *tags): + """iterdescendants(self, tag=None, *tags) + + Iterate over the descendants of this element in document order. + + As opposed to ``el.iter()``, this iterator does not yield the element + itself. The returned elements can be restricted to find only elements + with specific tags, see `iter`. + """ + if self._c_node and not self._c_node.children: + return ITER_EMPTY + if tag is not None: + tags += (tag,) + return ElementDepthFirstIterator(self, tags, inclusive=False) + + def iterchildren(self, tag=None, *tags, reversed=False): + """iterchildren(self, tag=None, *tags, reversed=False) + + Iterate over the children of this element. + + As opposed to using normal iteration on this element, the returned + elements can be reversed with the 'reversed' keyword and restricted + to find only elements with specific tags, see `iter`. + """ + if self._c_node and not self._c_node.children: + return ITER_EMPTY + if tag is not None: + tags += (tag,) + return ElementChildIterator(self, tags, reversed=reversed) + + def getroottree(self): + """getroottree(self) + + Return an ElementTree for the root node of the document that + contains this element. + + This is the same as following element.getparent() up the tree until it + returns None (for the root element) and then build an ElementTree for + the last parent that was returned.""" + _assertValidDoc(self._doc) + return _elementTreeFactory(self._doc, None) + + def getiterator(self, tag=None, *tags): + """getiterator(self, tag=None, *tags) + + Returns a sequence or iterator of all elements in the subtree in + document order (depth first pre-order), starting with this + element. + + Can be restricted to find only elements with specific tags, + see `iter`. + + :deprecated: Note that this method is deprecated as of + ElementTree 1.3 and lxml 2.0. It returns an iterator in + lxml, which diverges from the original ElementTree + behaviour. If you want an efficient iterator, use the + ``element.iter()`` method instead. You should only use this + method in new code if you require backwards compatibility + with older versions of lxml or ElementTree. + """ + if tag is not None: + tags += (tag,) + return ElementDepthFirstIterator(self, tags) + + def iter(self, tag=None, *tags): + """iter(self, tag=None, *tags) + + Iterate over all elements in the subtree in document order (depth + first pre-order), starting with this element. + + Can be restricted to find only elements with specific tags: + pass ``"{ns}localname"`` as tag. Either or both of ``ns`` and + ``localname`` can be ``*`` for a wildcard; ``ns`` can be empty + for no namespace. ``"localname"`` is equivalent to ``"{}localname"`` + (i.e. no namespace) but ``"*"`` is ``"{*}*"`` (any or no namespace), + not ``"{}*"``. + + You can also pass the Element, Comment, ProcessingInstruction and + Entity factory functions to look only for the specific element type. + + Passing multiple tags (or a sequence of tags) instead of a single tag + will let the iterator return all elements matching any of these tags, + in document order. + """ + if tag is not None: + tags += (tag,) + return ElementDepthFirstIterator(self, tags) + + def itertext(self, tag=None, *tags, with_tail=True): + """itertext(self, tag=None, *tags, with_tail=True) + + Iterates over the text content of a subtree. + + You can pass tag names to restrict text content to specific elements, + see `iter`. + + You can set the ``with_tail`` keyword argument to ``False`` to skip + over tail text. + """ + if tag is not None: + tags += (tag,) + return ElementTextIterator(self, tags, with_tail=with_tail) + + def makeelement(self, _tag, attrib=None, nsmap=None, **_extra): + """makeelement(self, _tag, attrib=None, nsmap=None, **_extra) + + Creates a new element associated with the same document. + """ + _assertValidDoc(self._doc) + return _makeElement(_tag, NULL, self._doc, None, None, None, + attrib, nsmap, _extra) + + def find(self, path, namespaces=None): + """find(self, path, namespaces=None) + + Finds the first matching subelement, by tag name or path. + + The optional ``namespaces`` argument accepts a + prefix-to-namespace mapping that allows the usage of XPath + prefixes in the path expression. + """ + if isinstance(path, QName): + path = (path).text + return _elementpath.find(self, path, namespaces, with_prefixes=not _isHtmlDocument(self)) + + def findtext(self, path, default=None, namespaces=None): + """findtext(self, path, default=None, namespaces=None) + + Finds text for the first matching subelement, by tag name or path. + + The optional ``namespaces`` argument accepts a + prefix-to-namespace mapping that allows the usage of XPath + prefixes in the path expression. + """ + if isinstance(path, QName): + path = (path).text + return _elementpath.findtext(self, path, default, namespaces, with_prefixes=not _isHtmlDocument(self)) + + def findall(self, path, namespaces=None): + """findall(self, path, namespaces=None) + + Finds all matching subelements, by tag name or path. + + The optional ``namespaces`` argument accepts a + prefix-to-namespace mapping that allows the usage of XPath + prefixes in the path expression. + """ + if isinstance(path, QName): + path = (path).text + return _elementpath.findall(self, path, namespaces, with_prefixes=not _isHtmlDocument(self)) + + def iterfind(self, path, namespaces=None): + """iterfind(self, path, namespaces=None) + + Iterates over all matching subelements, by tag name or path. + + The optional ``namespaces`` argument accepts a + prefix-to-namespace mapping that allows the usage of XPath + prefixes in the path expression. + """ + if isinstance(path, QName): + path = (path).text + return _elementpath.iterfind(self, path, namespaces, with_prefixes=not _isHtmlDocument(self)) + + def xpath(self, _path, *, namespaces=None, extensions=None, + smart_strings=True, **_variables): + """xpath(self, _path, namespaces=None, extensions=None, smart_strings=True, **_variables) + + Evaluate an xpath expression using the element as context node. + """ + evaluator = XPathElementEvaluator(self, namespaces=namespaces, + extensions=extensions, + smart_strings=smart_strings) + return evaluator(_path, **_variables) + + def cssselect(self, expr, *, translator='xml'): + """ + Run the CSS expression on this element and its children, + returning a list of the results. + + Equivalent to lxml.cssselect.CSSSelect(expr)(self) -- note + that pre-compiling the expression can provide a substantial + speedup. + """ + # Do the import here to make the dependency optional. + from lxml.cssselect import CSSSelector + return CSSSelector(expr, translator=translator)(self) + + +@cython.linetrace(False) +cdef _Element _elementFactory(_Document doc, xmlNode* c_node): + cdef _Element result + result = getProxy(c_node) + if result is not None: + return result + if c_node is NULL: + return None + + element_class = LOOKUP_ELEMENT_CLASS( + ELEMENT_CLASS_LOOKUP_STATE, doc, c_node) + if type(element_class) is not type: + if not isinstance(element_class, type): + raise TypeError(f"Element class is not a type, got {type(element_class)}") + if hasProxy(c_node): + # prevent re-entry race condition - we just called into Python + return getProxy(c_node) + result = element_class.__new__(element_class) + if hasProxy(c_node): + # prevent re-entry race condition - we just called into Python + result._c_node = NULL + return getProxy(c_node) + + _registerProxy(result, doc, c_node) + if element_class is not _Element: + result._init() + return result + + +@cython.internal +cdef class __ContentOnlyElement(_Element): + cdef int _raiseImmutable(self) except -1: + raise TypeError, "this element does not have children or attributes" + + def set(self, key, value): + "set(self, key, value)" + self._raiseImmutable() + + def append(self, value): + "append(self, value)" + self._raiseImmutable() + + def insert(self, index, value): + "insert(self, index, value)" + self._raiseImmutable() + + def __setitem__(self, index, value): + "__setitem__(self, index, value)" + self._raiseImmutable() + + @property + def attrib(self): + return IMMUTABLE_EMPTY_MAPPING + + property text: + def __get__(self): + _assertValidNode(self) + return funicodeOrEmpty(self._c_node.content) + + def __set__(self, value): + cdef tree.xmlDict* c_dict + _assertValidNode(self) + if value is None: + c_text = NULL + else: + value = _utf8(value) + c_text = _xcstr(value) + tree.xmlNodeSetContent(self._c_node, c_text) + + # ACCESSORS + def __getitem__(self, x): + "__getitem__(self, x)" + if isinstance(x, slice): + return [] + else: + raise IndexError, "list index out of range" + + def __len__(self): + "__len__(self)" + return 0 + + def get(self, key, default=None): + "get(self, key, default=None)" + return None + + def keys(self): + "keys(self)" + return [] + + def items(self): + "items(self)" + return [] + + def values(self): + "values(self)" + return [] + +cdef class _Comment(__ContentOnlyElement): + @property + def tag(self): + return Comment + + def __repr__(self): + return "" % self.text + +cdef class _ProcessingInstruction(__ContentOnlyElement): + @property + def tag(self): + return ProcessingInstruction + + property target: + # not in ElementTree + def __get__(self): + _assertValidNode(self) + return funicode(self._c_node.name) + + def __set__(self, value): + _assertValidNode(self) + value = _utf8(value) + c_text = _xcstr(value) + tree.xmlNodeSetName(self._c_node, c_text) + + def __repr__(self): + text = self.text + if text: + return "" % (self.target, text) + else: + return "" % self.target + + def get(self, key, default=None): + """get(self, key, default=None) + + Try to parse pseudo-attributes from the text content of the + processing instruction, search for one with the given key as + name and return its associated value. + + Note that this is only a convenience method for the most + common case that all text content is structured in + attribute-like name-value pairs with properly quoted values. + It is not guaranteed to work for all possible text content. + """ + return self.attrib.get(key, default) + + @property + def attrib(self): + """Returns a dict containing all pseudo-attributes that can be + parsed from the text content of this processing instruction. + Note that modifying the dict currently has no effect on the + XML node, although this is not guaranteed to stay this way. + """ + return { attr : (value1 or value2) + for attr, value1, value2 in _FIND_PI_ATTRIBUTES(' ' + self.text) } + +cdef object _FIND_PI_ATTRIBUTES = re.compile(r'\s+(\w+)\s*=\s*(?:\'([^\']*)\'|"([^"]*)")', re.U).findall + +cdef class _Entity(__ContentOnlyElement): + @property + def tag(self): + return Entity + + property name: + # not in ElementTree + def __get__(self): + _assertValidNode(self) + return funicode(self._c_node.name) + + def __set__(self, value): + _assertValidNode(self) + value_utf = _utf8(value) + if b'&' in value_utf or b';' in value_utf: + raise ValueError, f"Invalid entity name '{value}'" + tree.xmlNodeSetName(self._c_node, _xcstr(value_utf)) + + @property + def text(self): + # FIXME: should this be None or '&[VALUE];' or the resolved + # entity value ? + _assertValidNode(self) + return f'&{funicode(self._c_node.name)};' + + def __repr__(self): + return "&%s;" % self.name + + +cdef class QName: + """QName(text_or_uri_or_element, tag=None) + + QName wrapper for qualified XML names. + + Pass a tag name by itself or a namespace URI and a tag name to + create a qualified name. Alternatively, pass an Element to + extract its tag name. ``None`` as first argument is ignored in + order to allow for generic 2-argument usage. + + The ``text`` property holds the qualified name in + ``{namespace}tagname`` notation. The ``namespace`` and + ``localname`` properties hold the respective parts of the tag + name. + + You can pass QName objects wherever a tag name is expected. Also, + setting Element text from a QName will resolve the namespace prefix + on assignment and set a qualified text value. This is helpful in XML + languages like SOAP or XML-Schema that use prefixed tag names in + their text content. + """ + cdef readonly unicode text + cdef readonly unicode localname + cdef readonly unicode namespace + def __init__(self, text_or_uri_or_element, tag=None): + if text_or_uri_or_element is None: + # Allow None as no namespace. + text_or_uri_or_element, tag = tag, None + if not _isString(text_or_uri_or_element): + if isinstance(text_or_uri_or_element, _Element): + text_or_uri_or_element = (<_Element>text_or_uri_or_element).tag + if not _isString(text_or_uri_or_element): + raise ValueError, f"Invalid input tag of type {type(text_or_uri_or_element)!r}" + elif isinstance(text_or_uri_or_element, QName): + text_or_uri_or_element = (text_or_uri_or_element).text + elif text_or_uri_or_element is not None: + text_or_uri_or_element = unicode(text_or_uri_or_element) + else: + raise ValueError, f"Invalid input tag of type {type(text_or_uri_or_element)!r}" + + ns_utf, tag_utf = _getNsTag(text_or_uri_or_element) + if tag is not None: + # either ('ns', 'tag') or ('{ns}oldtag', 'newtag') + if ns_utf is None: + ns_utf = tag_utf # case 1: namespace ended up as tag name + tag_utf = _utf8(tag) + _tagValidOrRaise(tag_utf) + self.localname = (tag_utf).decode('utf8') + if ns_utf is None: + self.namespace = None + self.text = self.localname + else: + self.namespace = (ns_utf).decode('utf8') + self.text = "{%s}%s" % (self.namespace, self.localname) + def __str__(self): + return self.text + def __hash__(self): + return hash(self.text) + def __richcmp__(self, other, int op): + try: + if type(other) is QName: + other = (other).text + elif not isinstance(other, unicode): + other = unicode(other) + except (ValueError, UnicodeDecodeError): + return NotImplemented + return python.PyObject_RichCompare(self.text, other, op) + + +cdef public class _ElementTree [ type LxmlElementTreeType, + object LxmlElementTree ]: + cdef _Document _doc + cdef _Element _context_node + + # Note that _doc is only used to store the original document if we do not + # have a _context_node. All methods should prefer self._context_node._doc + # to honour tree restructuring. _doc can happily be None! + + @cython.final + cdef int _assertHasRoot(self) except -1: + """We have to take care here: the document may not have a root node! + This can happen if ElementTree() is called without any argument and + the caller 'forgets' to call parse() afterwards, so this is a bug in + the caller program. + """ + assert self._context_node is not None, \ + "ElementTree not initialized, missing root" + return 0 + + def parse(self, source, _BaseParser parser=None, *, base_url=None): + """parse(self, source, parser=None, base_url=None) + + Updates self with the content of source and returns its root. + """ + cdef _Document doc = None + try: + doc = _parseDocument(source, parser, base_url) + except _TargetParserResult as result_container: + # raises a TypeError if we don't get an _Element + self._context_node = result_container.result + else: + self._context_node = doc.getroot() + self._doc = None if self._context_node is not None else doc + return self._context_node + + def _setroot(self, _Element root not None): + """_setroot(self, root) + + Relocate the ElementTree to a new root node. + """ + _assertValidNode(root) + if root._c_node.type != tree.XML_ELEMENT_NODE: + raise TypeError, "Only elements can be the root of an ElementTree" + self._context_node = root + self._doc = None + + def getroot(self): + """getroot(self) + + Gets the root element for this tree. + """ + return self._context_node + + def __copy__(self): + return _elementTreeFactory(self._doc, self._context_node) + + def __deepcopy__(self, memo): + cdef _Element root + cdef _Document doc + cdef xmlDoc* c_doc + if self._context_node is not None: + root = self._context_node.__copy__() + assert root is not None + _assertValidNode(root) + _copyNonElementSiblings(self._context_node._c_node, root._c_node) + return _elementTreeFactory(None, root) + elif self._doc is not None: + _assertValidDoc(self._doc) + c_doc = tree.xmlCopyDoc(self._doc._c_doc, 1) + if c_doc is NULL: + raise MemoryError() + doc = _documentFactory(c_doc, self._doc._parser) + return _elementTreeFactory(doc, None) + else: + # so what ... + return self + + # not in ElementTree + @property + def docinfo(self) -> DocInfo: + """Information about the document provided by parser and DTD.""" + self._assertHasRoot() + return DocInfo(self._context_node._doc) + + # not in ElementTree, read-only + @property + def parser(self): + """The parser that was used to parse the document in this ElementTree. + """ + if self._context_node is not None and \ + self._context_node._doc is not None: + return self._context_node._doc._parser + if self._doc is not None: + return self._doc._parser + return None + + def write(self, file, *, encoding=None, method="xml", + bint pretty_print=False, xml_declaration=None, bint with_tail=True, + standalone=None, doctype=None, compression=0, + bint exclusive=False, inclusive_ns_prefixes=None, + bint with_comments=True, bint strip_text=False, + docstring=None): + """write(self, file, encoding=None, method="xml", + pretty_print=False, xml_declaration=None, with_tail=True, + standalone=None, doctype=None, compression=0, + exclusive=False, inclusive_ns_prefixes=None, + with_comments=True, strip_text=False) + + Write the tree to a filename, file or file-like object. + + Defaults to ASCII encoding and writing a declaration as needed. + + The keyword argument 'method' selects the output method: + 'xml', 'html', 'text', 'c14n' or 'c14n2'. Default is 'xml'. + + With ``method="c14n"`` (C14N version 1), the options ``exclusive``, + ``with_comments`` and ``inclusive_ns_prefixes`` request exclusive + C14N, include comments, and list the inclusive prefixes respectively. + + With ``method="c14n2"`` (C14N version 2), the ``with_comments`` and + ``strip_text`` options control the output of comments and text space + according to C14N 2.0. + + Passing a boolean value to the ``standalone`` option will + output an XML declaration with the corresponding + ``standalone`` flag. + + The ``doctype`` option allows passing in a plain string that will + be serialised before the XML tree. Note that passing in non + well-formed content here will make the XML output non well-formed. + Also, an existing doctype in the document tree will not be removed + when serialising an ElementTree instance. + + The ``compression`` option enables GZip compression level 1-9. + + The ``inclusive_ns_prefixes`` should be a list of namespace strings + (i.e. ['xs', 'xsi']) that will be promoted to the top-level element + during exclusive C14N serialisation. This parameter is ignored if + exclusive mode=False. + + If exclusive=True and no list is provided, a namespace will only be + rendered if it is used by the immediate parent or one of its attributes + and its prefix and values have not already been rendered by an ancestor + of the namespace node's parent element. + """ + cdef bint write_declaration + cdef int is_standalone + + self._assertHasRoot() + _assertValidNode(self._context_node) + if compression is None or compression < 0: + compression = 0 + + # C14N serialisation + if method in ('c14n', 'c14n2'): + if encoding is not None: + raise ValueError("Cannot specify encoding with C14N") + if xml_declaration: + raise ValueError("Cannot enable XML declaration in C14N") + + if method == 'c14n': + _tofilelikeC14N(file, self._context_node, exclusive, with_comments, + compression, inclusive_ns_prefixes) + else: # c14n2 + with _open_utf8_file(file, compression=compression) as f: + target = C14NWriterTarget( + f.write, with_comments=with_comments, strip_text=strip_text) + _tree_to_target(self, target) + return + + if not with_comments: + raise ValueError("Can only discard comments in C14N serialisation") + # suppress decl. in default case (purely for ElementTree compatibility) + if xml_declaration is not None: + write_declaration = xml_declaration + if encoding is None: + encoding = 'ASCII' + else: + encoding = encoding.upper() + elif encoding is None: + encoding = 'ASCII' + write_declaration = 0 + else: + encoding = encoding.upper() + write_declaration = encoding not in ( + 'US-ASCII', 'ASCII', 'UTF8', 'UTF-8') + if standalone is None: + is_standalone = -1 + elif standalone: + write_declaration = 1 + is_standalone = 1 + else: + write_declaration = 1 + is_standalone = 0 + + if docstring is not None and doctype is None: + import warnings + warnings.warn( + "The 'docstring' option is deprecated. Use 'doctype' instead.", + DeprecationWarning) + doctype = docstring + + _tofilelike(file, self._context_node, encoding, doctype, method, + write_declaration, 1, pretty_print, with_tail, + is_standalone, compression) + + def getpath(self, _Element element not None): + """getpath(self, element) + + Returns a structural, absolute XPath expression to find the element. + + For namespaced elements, the expression uses prefixes from the + document, which therefore need to be provided in order to make any + use of the expression in XPath. + + Also see the method getelementpath(self, element), which returns a + self-contained ElementPath expression. + """ + cdef _Document doc + cdef _Element root + cdef xmlDoc* c_doc + _assertValidNode(element) + if self._context_node is not None: + root = self._context_node + doc = root._doc + elif self._doc is not None: + doc = self._doc + root = doc.getroot() + else: + raise ValueError, "Element is not in this tree." + _assertValidDoc(doc) + _assertValidNode(root) + if element._doc is not doc: + raise ValueError, "Element is not in this tree." + + c_doc = _fakeRootDoc(doc._c_doc, root._c_node) + c_path = tree.xmlGetNodePath(element._c_node) + _destroyFakeDoc(doc._c_doc, c_doc) + if c_path is NULL: + raise MemoryError() + path = funicode(c_path) + tree.xmlFree(c_path) + return path + + def getelementpath(self, _Element element not None): + """getelementpath(self, element) + + Returns a structural, absolute ElementPath expression to find the + element. This path can be used in the .find() method to look up + the element, provided that the elements along the path and their + list of immediate children were not modified in between. + + ElementPath has the advantage over an XPath expression (as returned + by the .getpath() method) that it does not require additional prefix + declarations. It is always self-contained. + """ + cdef _Element root + cdef Py_ssize_t count + _assertValidNode(element) + if element._c_node.type != tree.XML_ELEMENT_NODE: + raise ValueError, "input is not an Element" + if self._context_node is not None: + root = self._context_node + elif self._doc is not None: + root = self._doc.getroot() + else: + raise ValueError, "Element is not in this tree" + _assertValidNode(root) + if element._doc is not root._doc: + raise ValueError, "Element is not in this tree" + + path = [] + c_element = element._c_node + while c_element is not root._c_node: + c_name = c_element.name + c_href = _getNs(c_element) + tag = _namespacedNameFromNsName(c_href, c_name) + if c_href is NULL: + c_href = b'' # no namespace (NULL is wildcard) + # use tag[N] if there are preceding siblings with the same tag + count = 0 + c_node = c_element.prev + while c_node is not NULL: + if c_node.type == tree.XML_ELEMENT_NODE: + if _tagMatches(c_node, c_href, c_name): + count += 1 + c_node = c_node.prev + if count: + tag = f'{tag}[{count+1}]' + else: + # use tag[1] if there are following siblings with the same tag + c_node = c_element.next + while c_node is not NULL: + if c_node.type == tree.XML_ELEMENT_NODE: + if _tagMatches(c_node, c_href, c_name): + tag += '[1]' + break + c_node = c_node.next + + path.append(tag) + c_element = c_element.parent + if c_element is NULL or c_element.type != tree.XML_ELEMENT_NODE: + raise ValueError, "Element is not in this tree." + if not path: + return '.' + path.reverse() + return '/'.join(path) + + def getiterator(self, tag=None, *tags): + """getiterator(self, *tags, tag=None) + + Returns a sequence or iterator of all elements in document order + (depth first pre-order), starting with the root element. + + Can be restricted to find only elements with specific tags, + see `_Element.iter`. + + :deprecated: Note that this method is deprecated as of + ElementTree 1.3 and lxml 2.0. It returns an iterator in + lxml, which diverges from the original ElementTree + behaviour. If you want an efficient iterator, use the + ``tree.iter()`` method instead. You should only use this + method in new code if you require backwards compatibility + with older versions of lxml or ElementTree. + """ + root = self.getroot() + if root is None: + return ITER_EMPTY + if tag is not None: + tags += (tag,) + return root.getiterator(*tags) + + def iter(self, tag=None, *tags): + """iter(self, tag=None, *tags) + + Creates an iterator for the root element. The iterator loops over + all elements in this tree, in document order. Note that siblings + of the root element (comments or processing instructions) are not + returned by the iterator. + + Can be restricted to find only elements with specific tags, + see `_Element.iter`. + """ + root = self.getroot() + if root is None: + return ITER_EMPTY + if tag is not None: + tags += (tag,) + return root.iter(*tags) + + def find(self, path, namespaces=None): + """find(self, path, namespaces=None) + + Finds the first toplevel element with given tag. Same as + ``tree.getroot().find(path)``. + + The optional ``namespaces`` argument accepts a + prefix-to-namespace mapping that allows the usage of XPath + prefixes in the path expression. + """ + self._assertHasRoot() + root = self.getroot() + if _isString(path): + if path[:1] == "/": + path = "." + path + from warnings import warn + warn( + "This search incorrectly ignores the root element, and will be " + "fixed in a future version. If you rely on the current " + f"behaviour, change it to {path!r}", + FutureWarning, stacklevel=1 + ) + return root.find(path, namespaces) + + def findtext(self, path, default=None, namespaces=None): + """findtext(self, path, default=None, namespaces=None) + + Finds the text for the first element matching the ElementPath + expression. Same as getroot().findtext(path) + + The optional ``namespaces`` argument accepts a + prefix-to-namespace mapping that allows the usage of XPath + prefixes in the path expression. + """ + self._assertHasRoot() + root = self.getroot() + if _isString(path): + if path[:1] == "/": + path = "." + path + from warnings import warn + warn( + "This search incorrectly ignores the root element, and will be " + "fixed in a future version. If you rely on the current " + f"behaviour, change it to {path!r}", + FutureWarning, stacklevel=1 + ) + return root.findtext(path, default, namespaces) + + def findall(self, path, namespaces=None): + """findall(self, path, namespaces=None) + + Finds all elements matching the ElementPath expression. Same as + getroot().findall(path). + + The optional ``namespaces`` argument accepts a + prefix-to-namespace mapping that allows the usage of XPath + prefixes in the path expression. + """ + self._assertHasRoot() + root = self.getroot() + if _isString(path): + if path[:1] == "/": + path = "." + path + from warnings import warn + warn( + "This search incorrectly ignores the root element, and will be " + "fixed in a future version. If you rely on the current " + f"behaviour, change it to {path!r}", + FutureWarning, stacklevel=1 + ) + return root.findall(path, namespaces) + + def iterfind(self, path, namespaces=None): + """iterfind(self, path, namespaces=None) + + Iterates over all elements matching the ElementPath expression. + Same as getroot().iterfind(path). + + The optional ``namespaces`` argument accepts a + prefix-to-namespace mapping that allows the usage of XPath + prefixes in the path expression. + """ + self._assertHasRoot() + root = self.getroot() + if _isString(path): + if path[:1] == "/": + path = "." + path + from warnings import warn + warn( + "This search incorrectly ignores the root element, and will be " + "fixed in a future version. If you rely on the current " + f"behaviour, change it to {path!r}", + FutureWarning, stacklevel=1 + ) + return root.iterfind(path, namespaces) + + def xpath(self, _path, *, namespaces=None, extensions=None, + smart_strings=True, **_variables): + """xpath(self, _path, namespaces=None, extensions=None, smart_strings=True, **_variables) + + XPath evaluate in context of document. + + ``namespaces`` is an optional dictionary with prefix to namespace URI + mappings, used by XPath. ``extensions`` defines additional extension + functions. + + Returns a list (nodeset), or bool, float or string. + + In case of a list result, return Element for element nodes, + string for text and attribute values. + + Note: if you are going to apply multiple XPath expressions + against the same document, it is more efficient to use + XPathEvaluator directly. + """ + self._assertHasRoot() + evaluator = XPathDocumentEvaluator(self, namespaces=namespaces, + extensions=extensions, + smart_strings=smart_strings) + return evaluator(_path, **_variables) + + def xslt(self, _xslt, extensions=None, access_control=None, **_kw): + """xslt(self, _xslt, extensions=None, access_control=None, **_kw) + + Transform this document using other document. + + xslt is a tree that should be XSLT + keyword parameters are XSLT transformation parameters. + + Returns the transformed tree. + + Note: if you are going to apply the same XSLT stylesheet against + multiple documents, it is more efficient to use the XSLT + class directly. + """ + self._assertHasRoot() + style = XSLT(_xslt, extensions=extensions, + access_control=access_control) + return style(self, **_kw) + + def relaxng(self, relaxng): + """relaxng(self, relaxng) + + Validate this document using other document. + + The relaxng argument is a tree that should contain a Relax NG schema. + + Returns True or False, depending on whether validation + succeeded. + + Note: if you are going to apply the same Relax NG schema against + multiple documents, it is more efficient to use the RelaxNG + class directly. + """ + self._assertHasRoot() + schema = RelaxNG(relaxng) + return schema.validate(self) + + def xmlschema(self, xmlschema): + """xmlschema(self, xmlschema) + + Validate this document using other document. + + The xmlschema argument is a tree that should contain an XML Schema. + + Returns True or False, depending on whether validation + succeeded. + + Note: If you are going to apply the same XML Schema against + multiple documents, it is more efficient to use the XMLSchema + class directly. + """ + self._assertHasRoot() + schema = XMLSchema(xmlschema) + return schema.validate(self) + + def xinclude(self): + """xinclude(self) + + Process the XInclude nodes in this document and include the + referenced XML fragments. + + There is support for loading files through the file system, HTTP and + FTP. + + Note that XInclude does not support custom resolvers in Python space + due to restrictions of libxml2 <= 2.6.29. + """ + self._assertHasRoot() + XInclude()(self._context_node) + + def write_c14n(self, file, *, bint exclusive=False, bint with_comments=True, + compression=0, inclusive_ns_prefixes=None): + """write_c14n(self, file, exclusive=False, with_comments=True, + compression=0, inclusive_ns_prefixes=None) + + C14N write of document. Always writes UTF-8. + + The ``compression`` option enables GZip compression level 1-9. + + The ``inclusive_ns_prefixes`` should be a list of namespace strings + (i.e. ['xs', 'xsi']) that will be promoted to the top-level element + during exclusive C14N serialisation. This parameter is ignored if + exclusive mode=False. + + If exclusive=True and no list is provided, a namespace will only be + rendered if it is used by the immediate parent or one of its attributes + and its prefix and values have not already been rendered by an ancestor + of the namespace node's parent element. + + NOTE: This method is deprecated as of lxml 4.4 and will be removed in a + future release. Use ``.write(f, method="c14n")`` instead. + """ + self._assertHasRoot() + _assertValidNode(self._context_node) + if compression is None or compression < 0: + compression = 0 + + _tofilelikeC14N(file, self._context_node, exclusive, with_comments, + compression, inclusive_ns_prefixes) + +cdef _ElementTree _elementTreeFactory(_Document doc, _Element context_node): + return _newElementTree(doc, context_node, _ElementTree) + +cdef _ElementTree _newElementTree(_Document doc, _Element context_node, + object baseclass): + cdef _ElementTree result + result = baseclass() + if context_node is None and doc is not None: + context_node = doc.getroot() + if context_node is None: + _assertValidDoc(doc) + result._doc = doc + else: + _assertValidNode(context_node) + result._context_node = context_node + return result + + +@cython.final +@cython.freelist(16) +cdef class _Attrib: + """A dict-like proxy for the ``Element.attrib`` property. + """ + cdef _Element _element + def __cinit__(self, _Element element not None): + _assertValidNode(element) + self._element = element + + # MANIPULATORS + def __setitem__(self, key, value): + _assertValidNode(self._element) + _setAttributeValue(self._element, key, value) + + def __delitem__(self, key): + _assertValidNode(self._element) + _delAttribute(self._element, key) + + def update(self, sequence_or_dict): + _assertValidNode(self._element) + if isinstance(sequence_or_dict, (dict, _Attrib)): + sequence_or_dict = sequence_or_dict.items() + for key, value in sequence_or_dict: + _setAttributeValue(self._element, key, value) + + def pop(self, key, *default): + if len(default) > 1: + raise TypeError, f"pop expected at most 2 arguments, got {len(default)+1}" + _assertValidNode(self._element) + result = _getAttributeValue(self._element, key, None) + if result is None: + if not default: + raise KeyError, key + result = default[0] + else: + _delAttribute(self._element, key) + return result + + def clear(self): + _assertValidNode(self._element) + c_attrs = self._element._c_node.properties + if c_attrs: + self._element._c_node.properties = NULL + tree.xmlFreePropList(c_attrs) + + # ACCESSORS + def __repr__(self): + _assertValidNode(self._element) + return repr(dict( _collectAttributes(self._element._c_node, 3) )) + + def __copy__(self): + _assertValidNode(self._element) + return dict(_collectAttributes(self._element._c_node, 3)) + + def __deepcopy__(self, memo): + _assertValidNode(self._element) + return dict(_collectAttributes(self._element._c_node, 3)) + + def __getitem__(self, key): + _assertValidNode(self._element) + result = _getAttributeValue(self._element, key, None) + if result is None: + raise KeyError, key + return result + + def __bool__(self): + _assertValidNode(self._element) + cdef xmlAttr* c_attr = self._element._c_node.properties + while c_attr is not NULL: + if c_attr.type == tree.XML_ATTRIBUTE_NODE: + return 1 + c_attr = c_attr.next + return 0 + + def __len__(self): + _assertValidNode(self._element) + cdef xmlAttr* c_attr = self._element._c_node.properties + cdef Py_ssize_t c = 0 + while c_attr is not NULL: + if c_attr.type == tree.XML_ATTRIBUTE_NODE: + c += 1 + c_attr = c_attr.next + return c + + def get(self, key, default=None): + _assertValidNode(self._element) + return _getAttributeValue(self._element, key, default) + + def keys(self): + _assertValidNode(self._element) + return _collectAttributes(self._element._c_node, 1) + + def __iter__(self): + _assertValidNode(self._element) + return iter(_collectAttributes(self._element._c_node, 1)) + + def iterkeys(self): + _assertValidNode(self._element) + return iter(_collectAttributes(self._element._c_node, 1)) + + def values(self): + _assertValidNode(self._element) + return _collectAttributes(self._element._c_node, 2) + + def itervalues(self): + _assertValidNode(self._element) + return iter(_collectAttributes(self._element._c_node, 2)) + + def items(self): + _assertValidNode(self._element) + return _collectAttributes(self._element._c_node, 3) + + def iteritems(self): + _assertValidNode(self._element) + return iter(_collectAttributes(self._element._c_node, 3)) + + def has_key(self, key): + _assertValidNode(self._element) + return key in self + + def __contains__(self, key): + _assertValidNode(self._element) + cdef xmlNode* c_node + ns, tag = _getNsTag(key) + c_node = self._element._c_node + c_href = NULL if ns is None else _xcstr(ns) + return 1 if tree.xmlHasNsProp(c_node, _xcstr(tag), c_href) else 0 + + def __richcmp__(self, other, int op): + try: + one = dict(self.items()) + if not isinstance(other, dict): + other = dict(other) + except (TypeError, ValueError): + return NotImplemented + return python.PyObject_RichCompare(one, other, op) + +MutableMapping.register(_Attrib) + + +@cython.final +@cython.internal +cdef class _AttribIterator: + """Attribute iterator - for internal use only! + """ + # XML attributes must not be removed while running! + cdef _Element _node + cdef xmlAttr* _c_attr + cdef int _keysvalues # 1 - keys, 2 - values, 3 - items (key, value) + def __iter__(self): + return self + + def __next__(self): + cdef xmlAttr* c_attr + if self._node is None: + raise StopIteration + c_attr = self._c_attr + while c_attr is not NULL and c_attr.type != tree.XML_ATTRIBUTE_NODE: + c_attr = c_attr.next + if c_attr is NULL: + self._node = None + raise StopIteration + + self._c_attr = c_attr.next + if self._keysvalues == 1: + return _namespacedName(c_attr) + elif self._keysvalues == 2: + return _attributeValue(self._node._c_node, c_attr) + else: + return (_namespacedName(c_attr), + _attributeValue(self._node._c_node, c_attr)) + +cdef object _attributeIteratorFactory(_Element element, int keysvalues): + cdef _AttribIterator attribs + if element._c_node.properties is NULL: + return ITER_EMPTY + attribs = _AttribIterator() + attribs._node = element + attribs._c_attr = element._c_node.properties + attribs._keysvalues = keysvalues + return attribs + + +cdef public class _ElementTagMatcher [ object LxmlElementTagMatcher, + type LxmlElementTagMatcherType ]: + """ + Dead but public. :) + """ + cdef object _pystrings + cdef int _node_type + cdef char* _href + cdef char* _name + cdef _initTagMatch(self, tag): + self._href = NULL + self._name = NULL + if tag is None: + self._node_type = 0 + elif tag is Comment: + self._node_type = tree.XML_COMMENT_NODE + elif tag is ProcessingInstruction: + self._node_type = tree.XML_PI_NODE + elif tag is Entity: + self._node_type = tree.XML_ENTITY_REF_NODE + elif tag is Element: + self._node_type = tree.XML_ELEMENT_NODE + else: + self._node_type = tree.XML_ELEMENT_NODE + self._pystrings = _getNsTag(tag) + if self._pystrings[0] is not None: + self._href = _cstr(self._pystrings[0]) + self._name = _cstr(self._pystrings[1]) + if self._name[0] == c'*' and self._name[1] == c'\0': + self._name = NULL + +cdef public class _ElementIterator(_ElementTagMatcher) [ + object LxmlElementIterator, type LxmlElementIteratorType ]: + """ + Dead but public. :) + """ + # we keep Python references here to control GC + cdef _Element _node + cdef _node_to_node_function _next_element + def __iter__(self): + return self + + cdef void _storeNext(self, _Element node): + cdef xmlNode* c_node + c_node = self._next_element(node._c_node) + while c_node is not NULL and \ + self._node_type != 0 and \ + (self._node_type != c_node.type or + not _tagMatches(c_node, self._href, self._name)): + c_node = self._next_element(c_node) + if c_node is NULL: + self._node = None + else: + # Python ref: + self._node = _elementFactory(node._doc, c_node) + + def __next__(self): + cdef xmlNode* c_node + cdef _Element current_node + if self._node is None: + raise StopIteration + # Python ref: + current_node = self._node + self._storeNext(current_node) + return current_node + +@cython.final +@cython.internal +cdef class _MultiTagMatcher: + """ + Match an xmlNode against a list of tags. + """ + cdef list _py_tags + cdef qname* _cached_tags + cdef size_t _tag_count + cdef size_t _cached_size + cdef _Document _cached_doc + cdef int _node_types + + def __cinit__(self, tags): + self._py_tags = [] + self.initTagMatch(tags) + + def __dealloc__(self): + self._clear() + + cdef bint rejectsAll(self) noexcept: + return not self._tag_count and not self._node_types + + cdef bint rejectsAllAttributes(self) noexcept: + return not self._tag_count + + cdef bint matchesType(self, int node_type) noexcept: + if node_type == tree.XML_ELEMENT_NODE and self._tag_count: + return True + return self._node_types & (1 << node_type) + + cdef void _clear(self) noexcept: + cdef size_t i, count + count = self._tag_count + self._tag_count = 0 + if self._cached_tags: + for i in range(count): + cpython.ref.Py_XDECREF(self._cached_tags[i].href) + python.lxml_free(self._cached_tags) + self._cached_tags = NULL + + cdef initTagMatch(self, tags): + self._cached_doc = None + del self._py_tags[:] + self._clear() + if tags is None or tags == (): + # no selection in tags argument => match anything + self._node_types = ( + 1 << tree.XML_COMMENT_NODE | + 1 << tree.XML_PI_NODE | + 1 << tree.XML_ENTITY_REF_NODE | + 1 << tree.XML_ELEMENT_NODE) + else: + self._node_types = 0 + self._storeTags(tags, set()) + + cdef _storeTags(self, tag, set seen): + if tag is Comment: + self._node_types |= 1 << tree.XML_COMMENT_NODE + elif tag is ProcessingInstruction: + self._node_types |= 1 << tree.XML_PI_NODE + elif tag is Entity: + self._node_types |= 1 << tree.XML_ENTITY_REF_NODE + elif tag is Element: + self._node_types |= 1 << tree.XML_ELEMENT_NODE + elif python._isString(tag): + if tag in seen: + return + seen.add(tag) + if tag in ('*', '{*}*'): + self._node_types |= 1 << tree.XML_ELEMENT_NODE + else: + href, name = _getNsTag(tag) + if name == b'*': + name = None + if href is None: + href = b'' # no namespace + elif href == b'*': + href = None # wildcard: any namespace, including none + self._py_tags.append((href, name)) + elif isinstance(tag, QName): + self._storeTags(tag.text, seen) + else: + # support a sequence of tags + for item in tag: + self._storeTags(item, seen) + + cdef inline int cacheTags(self, _Document doc, bint force_into_dict=False) except -1: + """ + Look up the tag names in the doc dict to enable string pointer comparisons. + """ + cdef size_t dict_size = tree.xmlDictSize(doc._c_doc.dict) + if doc is self._cached_doc and dict_size == self._cached_size: + # doc and dict didn't change => names already cached + return 0 + self._tag_count = 0 + if not self._py_tags: + self._cached_doc = doc + self._cached_size = dict_size + return 0 + if not self._cached_tags: + self._cached_tags = python.lxml_malloc(len(self._py_tags), sizeof(qname)) + if not self._cached_tags: + self._cached_doc = None + raise MemoryError() + self._tag_count = _mapTagsToQnameMatchArray( + doc._c_doc, self._py_tags, self._cached_tags, force_into_dict) + self._cached_doc = doc + self._cached_size = dict_size + return 0 + + cdef inline bint matches(self, xmlNode* c_node) noexcept: + cdef qname* c_qname + if self._node_types & (1 << c_node.type): + return True + elif c_node.type == tree.XML_ELEMENT_NODE: + for c_qname in self._cached_tags[:self._tag_count]: + if _tagMatchesExactly(c_node, c_qname): + return True + return False + + cdef inline bint matchesNsTag(self, const_xmlChar* c_href, + const_xmlChar* c_name) noexcept: + cdef qname* c_qname + if self._node_types & (1 << tree.XML_ELEMENT_NODE): + return True + for c_qname in self._cached_tags[:self._tag_count]: + if _nsTagMatchesExactly(c_href, c_name, c_qname): + return True + return False + + cdef inline bint matchesAttribute(self, xmlAttr* c_attr) noexcept: + """Attribute matches differ from Element matches in that they do + not care about node types. + """ + cdef qname* c_qname + for c_qname in self._cached_tags[:self._tag_count]: + if _tagMatchesExactly(c_attr, c_qname): + return True + return False + +cdef class _ElementMatchIterator: + cdef _Element _node + cdef _node_to_node_function _next_element + cdef _MultiTagMatcher _matcher + + @cython.final + cdef _initTagMatcher(self, tags): + self._matcher = _MultiTagMatcher.__new__(_MultiTagMatcher, tags) + + def __iter__(self): + return self + + @cython.final + cdef int _storeNext(self, _Element node) except -1: + self._matcher.cacheTags(node._doc) + c_node = self._next_element(node._c_node) + while c_node is not NULL and not self._matcher.matches(c_node): + c_node = self._next_element(c_node) + # store Python ref to next node to make sure it's kept alive + self._node = _elementFactory(node._doc, c_node) if c_node is not NULL else None + return 0 + + def __next__(self): + cdef _Element current_node = self._node + if current_node is None: + raise StopIteration + self._storeNext(current_node) + return current_node + +cdef class ElementChildIterator(_ElementMatchIterator): + """ElementChildIterator(self, node, tag=None, reversed=False) + Iterates over the children of an element. + """ + def __cinit__(self, _Element node not None, tag=None, *, bint reversed=False): + cdef xmlNode* c_node + _assertValidNode(node) + self._initTagMatcher(tag) + if reversed: + c_node = _findChildBackwards(node._c_node, 0) + self._next_element = _previousElement + else: + c_node = _findChildForwards(node._c_node, 0) + self._next_element = _nextElement + self._matcher.cacheTags(node._doc) + while c_node is not NULL and not self._matcher.matches(c_node): + c_node = self._next_element(c_node) + # store Python ref to next node to make sure it's kept alive + self._node = _elementFactory(node._doc, c_node) if c_node is not NULL else None + +cdef class SiblingsIterator(_ElementMatchIterator): + """SiblingsIterator(self, node, tag=None, preceding=False) + Iterates over the siblings of an element. + + You can pass the boolean keyword ``preceding`` to specify the direction. + """ + def __cinit__(self, _Element node not None, tag=None, *, bint preceding=False): + _assertValidNode(node) + self._initTagMatcher(tag) + if preceding: + self._next_element = _previousElement + else: + self._next_element = _nextElement + self._storeNext(node) + +cdef class AncestorsIterator(_ElementMatchIterator): + """AncestorsIterator(self, node, tag=None) + Iterates over the ancestors of an element (from parent to parent). + """ + def __cinit__(self, _Element node not None, tag=None): + _assertValidNode(node) + self._initTagMatcher(tag) + self._next_element = _parentElement + self._storeNext(node) + +cdef class ElementDepthFirstIterator: + """ElementDepthFirstIterator(self, node, tag=None, inclusive=True) + Iterates over an element and its sub-elements in document order (depth + first pre-order). + + Note that this also includes comments, entities and processing + instructions. To filter them out, check if the ``tag`` property + of the returned element is a string (i.e. not None and not a + factory function), or pass the ``Element`` factory for the ``tag`` + argument to receive only Elements. + + If the optional ``tag`` argument is not None, the iterator returns only + the elements that match the respective name and namespace. + + The optional boolean argument 'inclusive' defaults to True and can be set + to False to exclude the start element itself. + + Note that the behaviour of this iterator is completely undefined if the + tree it traverses is modified during iteration. + """ + # we keep Python references here to control GC + # keep the next Element after the one we return, and the (s)top node + cdef _Element _next_node + cdef _Element _top_node + cdef _MultiTagMatcher _matcher + def __cinit__(self, _Element node not None, tag=None, *, bint inclusive=True): + _assertValidNode(node) + self._top_node = node + self._next_node = node + self._matcher = _MultiTagMatcher.__new__(_MultiTagMatcher, tag) + self._matcher.cacheTags(node._doc) + if not inclusive or not self._matcher.matches(node._c_node): + # find start node (this cannot raise StopIteration, self._next_node != None) + next(self) + + def __iter__(self): + return self + + def __next__(self): + cdef xmlNode* c_node + cdef _Element current_node = self._next_node + if current_node is None: + raise StopIteration + c_node = current_node._c_node + self._matcher.cacheTags(current_node._doc) + if not self._matcher._tag_count: + # no tag name was found in the dict => not in document either + # try to match by node type + c_node = self._nextNodeAnyTag(c_node) + else: + c_node = self._nextNodeMatchTag(c_node) + if c_node is NULL: + self._next_node = None + else: + self._next_node = _elementFactory(current_node._doc, c_node) + return current_node + + @cython.final + cdef xmlNode* _nextNodeAnyTag(self, xmlNode* c_node) noexcept: + cdef int node_types = self._matcher._node_types + if not node_types: + return NULL + tree.BEGIN_FOR_EACH_ELEMENT_FROM(self._top_node._c_node, c_node, 0) + if node_types & (1 << c_node.type): + return c_node + tree.END_FOR_EACH_ELEMENT_FROM(c_node) + return NULL + + @cython.final + cdef xmlNode* _nextNodeMatchTag(self, xmlNode* c_node) noexcept: + tree.BEGIN_FOR_EACH_ELEMENT_FROM(self._top_node._c_node, c_node, 0) + if self._matcher.matches(c_node): + return c_node + tree.END_FOR_EACH_ELEMENT_FROM(c_node) + return NULL + + +cdef class ElementTextIterator: + """ElementTextIterator(self, element, tag=None, with_tail=True) + Iterates over the text content of a subtree. + + You can pass the ``tag`` keyword argument to restrict text content to a + specific tag name. + + You can set the ``with_tail`` keyword argument to ``False`` to skip over + tail text (e.g. if you know that it's only whitespace from pretty-printing). + """ + cdef object _events + cdef _Element _start_element + def __cinit__(self, _Element element not None, tag=None, *, bint with_tail=True): + _assertValidNode(element) + if with_tail: + events = ("start", "comment", "pi", "end") + else: + events = ("start",) + self._start_element = element + self._events = iterwalk(element, events=events, tag=tag) + + def __iter__(self): + return self + + def __next__(self): + cdef _Element element + result = None + while result is None: + event, element = next(self._events) # raises StopIteration + if event == "start": + result = element.text + elif element is not self._start_element: + result = element.tail + return result + + +cdef xmlNode* _createElement(xmlDoc* c_doc, object name_utf) except NULL: + cdef xmlNode* c_node + c_node = tree.xmlNewDocNode(c_doc, NULL, _xcstr(name_utf), NULL) + return c_node + +cdef xmlNode* _createComment(xmlDoc* c_doc, const_xmlChar* text) noexcept: + cdef xmlNode* c_node + c_node = tree.xmlNewDocComment(c_doc, text) + return c_node + +cdef xmlNode* _createPI(xmlDoc* c_doc, const_xmlChar* target, const_xmlChar* text) noexcept: + cdef xmlNode* c_node + c_node = tree.xmlNewDocPI(c_doc, target, text) + return c_node + +cdef xmlNode* _createEntity(xmlDoc* c_doc, const_xmlChar* name) noexcept: + cdef xmlNode* c_node + c_node = tree.xmlNewReference(c_doc, name) + return c_node + +# module-level API for ElementTree + +from abc import ABC + +class Element(ABC): + """Element(_tag, attrib=None, nsmap=None, **_extra) + + Element factory, as a class. + + An instance of this class is an object implementing the + Element interface. + + >>> element = Element("test") + >>> type(element) + + >>> isinstance(element, Element) + True + >>> issubclass(_Element, Element) + True + + Also look at the `_Element.makeelement()` and + `_BaseParser.makeelement()` methods, which provide a faster way to + create an Element within a specific document or parser context. + """ + def __new__(cls, _tag, attrib=None, nsmap=None, **_extra): + return _makeElement(_tag, NULL, None, None, None, None, + attrib, nsmap, _extra) + +# Register _Element as a virtual subclass of Element +Element.register(_Element) + + +def Comment(text=None): + """Comment(text=None) + + Comment element factory. This factory function creates a special element that will + be serialized as an XML comment. + """ + cdef _Document doc + cdef xmlNode* c_node + cdef xmlDoc* c_doc + + if text is None: + text = b'' + else: + text = _utf8(text) + if b'--' in text or text.endswith(b'-'): + raise ValueError("Comment may not contain '--' or end with '-'") + + c_doc = _newXMLDoc() + doc = _documentFactory(c_doc, None) + c_node = _createComment(c_doc, _xcstr(text)) + tree.xmlAddChild(c_doc, c_node) + return _elementFactory(doc, c_node) + + +def ProcessingInstruction(target, text=None): + """ProcessingInstruction(target, text=None) + + ProcessingInstruction element factory. This factory function creates a + special element that will be serialized as an XML processing instruction. + """ + cdef _Document doc + cdef xmlNode* c_node + cdef xmlDoc* c_doc + + target = _utf8(target) + _tagValidOrRaise(target) + if target.lower() == b'xml': + raise ValueError, f"Invalid PI name '{target}'" + + if text is None: + text = b'' + else: + text = _utf8(text) + if b'?>' in text: + raise ValueError, "PI text must not contain '?>'" + + c_doc = _newXMLDoc() + doc = _documentFactory(c_doc, None) + c_node = _createPI(c_doc, _xcstr(target), _xcstr(text)) + tree.xmlAddChild(c_doc, c_node) + return _elementFactory(doc, c_node) + +PI = ProcessingInstruction + + +cdef class CDATA: + """CDATA(data) + + CDATA factory. This factory creates an opaque data object that + can be used to set Element text. The usual way to use it is:: + + >>> el = Element('content') + >>> el.text = CDATA('a string') + + >>> print(el.text) + a string + >>> print(tostring(el, encoding="unicode")) + + """ + cdef bytes _utf8_data + def __cinit__(self, data): + self._utf8_data = _utf8(data) + + +def Entity(name): + """Entity(name) + + Entity factory. This factory function creates a special element + that will be serialized as an XML entity reference or character + reference. Note, however, that entities will not be automatically + declared in the document. A document that uses entity references + requires a DTD to define the entities. + """ + cdef _Document doc + cdef xmlNode* c_node + cdef xmlDoc* c_doc + name_utf = _utf8(name) + c_name = _xcstr(name_utf) + if c_name[0] == c'#': + if not _characterReferenceIsValid(c_name + 1): + raise ValueError, f"Invalid character reference: '{name}'" + elif not _xmlNameIsValid(c_name): + raise ValueError, f"Invalid entity reference: '{name}'" + c_doc = _newXMLDoc() + doc = _documentFactory(c_doc, None) + c_node = _createEntity(c_doc, c_name) + tree.xmlAddChild(c_doc, c_node) + return _elementFactory(doc, c_node) + + +def SubElement(_Element _parent not None, _tag, + attrib=None, nsmap=None, **_extra): + """SubElement(_parent, _tag, attrib=None, nsmap=None, **_extra) + + Subelement factory. This function creates an element instance, and + appends it to an existing element. + """ + return _makeSubElement(_parent, _tag, None, None, attrib, nsmap, _extra) + +from typing import Generic, TypeVar + +T = TypeVar("T") + +class ElementTree(ABC, Generic[T]): + def __new__(cls, _Element element=None, *, file=None, _BaseParser parser=None): + """ElementTree(element=None, file=None, parser=None) + + ElementTree wrapper class. + """ + cdef xmlNode* c_next + cdef xmlNode* c_node + cdef xmlNode* c_node_copy + cdef xmlDoc* c_doc + cdef _ElementTree etree + cdef _Document doc + + if element is not None: + doc = element._doc + elif file is not None: + try: + doc = _parseDocument(file, parser, None) + except _TargetParserResult as result_container: + return result_container.result + else: + c_doc = _newXMLDoc() + doc = _documentFactory(c_doc, parser) + + return _elementTreeFactory(doc, element) + +# Register _ElementTree as a virtual subclass of ElementTree +ElementTree.register(_ElementTree) + +# Remove "ABC" and typing helpers from module dict +del ABC, Generic, TypeVar, T + +def HTML(text, _BaseParser parser=None, *, base_url=None): + """HTML(text, parser=None, base_url=None) + + Parses an HTML document from a string constant. Returns the root + node (or the result returned by a parser target). This function + can be used to embed "HTML literals" in Python code. + + To override the parser with a different ``HTMLParser`` you can pass it to + the ``parser`` keyword argument. + + The ``base_url`` keyword argument allows to set the original base URL of + the document to support relative Paths when looking up external entities + (DTD, XInclude, ...). + """ + cdef _Document doc + if parser is None: + parser = __GLOBAL_PARSER_CONTEXT.getDefaultParser() + if not isinstance(parser, HTMLParser): + parser = __DEFAULT_HTML_PARSER + try: + doc = _parseMemoryDocument(text, base_url, parser) + return doc.getroot() + except _TargetParserResult as result_container: + return result_container.result + + +def XML(text, _BaseParser parser=None, *, base_url=None): + """XML(text, parser=None, base_url=None) + + Parses an XML document or fragment from a string constant. + Returns the root node (or the result returned by a parser target). + This function can be used to embed "XML literals" in Python code, + like in + + >>> root = XML("") + >>> print(root.tag) + root + + To override the parser with a different ``XMLParser`` you can pass it to + the ``parser`` keyword argument. + + The ``base_url`` keyword argument allows to set the original base URL of + the document to support relative Paths when looking up external entities + (DTD, XInclude, ...). + """ + cdef _Document doc + if parser is None: + parser = __GLOBAL_PARSER_CONTEXT.getDefaultParser() + if not isinstance(parser, XMLParser): + parser = __DEFAULT_XML_PARSER + try: + doc = _parseMemoryDocument(text, base_url, parser) + return doc.getroot() + except _TargetParserResult as result_container: + return result_container.result + + +def fromstring(text, _BaseParser parser=None, *, base_url=None): + """fromstring(text, parser=None, base_url=None) + + Parses an XML document or fragment from a string. Returns the + root node (or the result returned by a parser target). + + To override the default parser with a different parser you can pass it to + the ``parser`` keyword argument. + + The ``base_url`` keyword argument allows to set the original base URL of + the document to support relative Paths when looking up external entities + (DTD, XInclude, ...). + """ + cdef _Document doc + try: + doc = _parseMemoryDocument(text, base_url, parser) + return doc.getroot() + except _TargetParserResult as result_container: + return result_container.result + + +def fromstringlist(strings, _BaseParser parser=None): + """fromstringlist(strings, parser=None) + + Parses an XML document from a sequence of strings. Returns the + root node (or the result returned by a parser target). + + To override the default parser with a different parser you can pass it to + the ``parser`` keyword argument. + """ + cdef _Document doc + if isinstance(strings, (bytes, unicode)): + raise ValueError("passing a single string into fromstringlist() is not" + " efficient, use fromstring() instead") + if parser is None: + parser = __GLOBAL_PARSER_CONTEXT.getDefaultParser() + feed = parser.feed + for data in strings: + feed(data) + return parser.close() + + +def iselement(element): + """iselement(element) + + Checks if an object appears to be a valid element object. + """ + return isinstance(element, _Element) and (<_Element>element)._c_node is not NULL + + +def indent(tree, space=" ", *, Py_ssize_t level=0): + """indent(tree, space=" ", level=0) + + Indent an XML document by inserting newlines and indentation space + after elements. + + *tree* is the ElementTree or Element to modify. The (root) element + itself will not be changed, but the tail text of all elements in its + subtree will be adapted. + + *space* is the whitespace to insert for each indentation level, two + space characters by default. + + *level* is the initial indentation level. Setting this to a higher + value than 0 can be used for indenting subtrees that are more deeply + nested inside of a document. + """ + root = _rootNodeOrRaise(tree) + if level < 0: + raise ValueError(f"Initial indentation level must be >= 0, got {level}") + if _hasChild(root._c_node): + space = _utf8(space) + indent = b"\n" + level * space + _indent_children(root._c_node, 1, space, [indent, indent + space]) + + +cdef int _indent_children(xmlNode* c_node, Py_ssize_t level, bytes one_space, list indentations) except -1: + # Reuse indentation strings for speed. + if len(indentations) <= level: + indentations.append(indentations[-1] + one_space) + + # Start a new indentation level for the first child. + child_indentation = indentations[level] + if not _hasNonWhitespaceText(c_node): + _setNodeText(c_node, child_indentation) + + # Recursively indent all children. + cdef xmlNode* c_child = _findChildForwards(c_node, 0) + while c_child is not NULL: + if _hasChild(c_child): + _indent_children(c_child, level+1, one_space, indentations) + c_next_child = _nextElement(c_child) + if not _hasNonWhitespaceTail(c_child): + if c_next_child is NULL: + # Dedent after the last child. + child_indentation = indentations[level-1] + _setTailText(c_child, child_indentation) + c_child = c_next_child + return 0 + + +def dump(_Element elem not None, *, bint pretty_print=True, bint with_tail=True): + """dump(elem, pretty_print=True, with_tail=True) + + Writes an element tree or element structure to sys.stdout. This function + should be used for debugging only. + """ + xml = tostring(elem, pretty_print=pretty_print, with_tail=with_tail, encoding='unicode') + if not pretty_print: + xml += '\n' + sys.stdout.write(xml) + + +def tostring(element_or_tree, *, encoding=None, method="xml", + xml_declaration=None, bint pretty_print=False, bint with_tail=True, + standalone=None, doctype=None, + # method='c14n' + bint exclusive=False, inclusive_ns_prefixes=None, + # method='c14n2' + bint with_comments=True, bint strip_text=False, + ): + """tostring(element_or_tree, encoding=None, method="xml", + xml_declaration=None, pretty_print=False, with_tail=True, + standalone=None, doctype=None, + exclusive=False, inclusive_ns_prefixes=None, + with_comments=True, strip_text=False, + ) + + Serialize an element to an encoded string representation of its XML + tree. + + Defaults to ASCII encoding without XML declaration. This + behaviour can be configured with the keyword arguments 'encoding' + (string) and 'xml_declaration' (bool). Note that changing the + encoding to a non UTF-8 compatible encoding will enable a + declaration by default. + + You can also serialise to a Unicode string without declaration by + passing the name ``'unicode'`` as encoding (or the ``str`` function + in Py3 or ``unicode`` in Py2). This changes the return value from + a byte string to an unencoded unicode string. + + The keyword argument 'pretty_print' (bool) enables formatted XML. + + The keyword argument 'method' selects the output method: 'xml', + 'html', plain 'text' (text content without tags), 'c14n' or 'c14n2'. + Default is 'xml'. + + With ``method="c14n"`` (C14N version 1), the options ``exclusive``, + ``with_comments`` and ``inclusive_ns_prefixes`` request exclusive + C14N, include comments, and list the inclusive prefixes respectively. + + With ``method="c14n2"`` (C14N version 2), the ``with_comments`` and + ``strip_text`` options control the output of comments and text space + according to C14N 2.0. + + Passing a boolean value to the ``standalone`` option will output + an XML declaration with the corresponding ``standalone`` flag. + + The ``doctype`` option allows passing in a plain string that will + be serialised before the XML tree. Note that passing in non + well-formed content here will make the XML output non well-formed. + Also, an existing doctype in the document tree will not be removed + when serialising an ElementTree instance. + + You can prevent the tail text of the element from being serialised + by passing the boolean ``with_tail`` option. This has no impact + on the tail text of children, which will always be serialised. + """ + cdef bint write_declaration + cdef int is_standalone + # C14N serialisation + if method in ('c14n', 'c14n2'): + if encoding is not None: + raise ValueError("Cannot specify encoding with C14N") + if xml_declaration: + raise ValueError("Cannot enable XML declaration in C14N") + if method == 'c14n': + return _tostringC14N(element_or_tree, exclusive, with_comments, inclusive_ns_prefixes) + else: + out = BytesIO() + target = C14NWriterTarget( + utf8_writer(out).write, + with_comments=with_comments, strip_text=strip_text) + _tree_to_target(element_or_tree, target) + return out.getvalue() + if not with_comments: + raise ValueError("Can only discard comments in C14N serialisation") + if strip_text: + raise ValueError("Can only strip text in C14N 2.0 serialisation") + if encoding is unicode or (encoding is not None and encoding.lower() == 'unicode'): + if xml_declaration: + raise ValueError, \ + "Serialisation to unicode must not request an XML declaration" + write_declaration = 0 + encoding = unicode + elif xml_declaration is None: + # by default, write an XML declaration only for non-standard encodings + write_declaration = encoding is not None and encoding.upper() not in \ + ('ASCII', 'UTF-8', 'UTF8', 'US-ASCII') + else: + write_declaration = xml_declaration + if encoding is None: + encoding = 'ASCII' + if standalone is None: + is_standalone = -1 + elif standalone: + write_declaration = 1 + is_standalone = 1 + else: + write_declaration = 1 + is_standalone = 0 + + if isinstance(element_or_tree, _Element): + return _tostring(<_Element>element_or_tree, encoding, doctype, method, + write_declaration, 0, pretty_print, with_tail, + is_standalone) + elif isinstance(element_or_tree, _ElementTree): + return _tostring((<_ElementTree>element_or_tree)._context_node, + encoding, doctype, method, write_declaration, 1, + pretty_print, with_tail, is_standalone) + else: + raise TypeError, f"Type '{python._fqtypename(element_or_tree).decode('utf8')}' cannot be serialized." + + + +def tostringlist(element_or_tree, *args, **kwargs): + """tostringlist(element_or_tree, *args, **kwargs) + + Serialize an element to an encoded string representation of its XML + tree, stored in a list of partial strings. + + This is purely for ElementTree 1.3 compatibility. The result is a + single string wrapped in a list. + """ + return [tostring(element_or_tree, *args, **kwargs)] + + +def tounicode(element_or_tree, *, method="xml", bint pretty_print=False, + bint with_tail=True, doctype=None): + """tounicode(element_or_tree, method="xml", pretty_print=False, + with_tail=True, doctype=None) + + Serialize an element to the Python unicode representation of its XML + tree. + + :deprecated: use ``tostring(el, encoding='unicode')`` instead. + + Note that the result does not carry an XML encoding declaration and is + therefore not necessarily suited for serialization to byte streams without + further treatment. + + The boolean keyword argument 'pretty_print' enables formatted XML. + + The keyword argument 'method' selects the output method: 'xml', + 'html' or plain 'text'. + + You can prevent the tail text of the element from being serialised + by passing the boolean ``with_tail`` option. This has no impact + on the tail text of children, which will always be serialised. + """ + if isinstance(element_or_tree, _Element): + return _tostring(<_Element>element_or_tree, unicode, doctype, method, + 0, 0, pretty_print, with_tail, -1) + elif isinstance(element_or_tree, _ElementTree): + return _tostring((<_ElementTree>element_or_tree)._context_node, + unicode, doctype, method, 0, 1, pretty_print, + with_tail, -1) + else: + raise TypeError, f"Type '{type(element_or_tree)}' cannot be serialized." + + +def parse(source, _BaseParser parser=None, *, base_url=None): + """parse(source, parser=None, base_url=None) + + Return an ElementTree object loaded with source elements. If no parser + is provided as second argument, the default parser is used. + + The ``source`` can be any of the following: + + - a file name/path + - a file object + - a file-like object + - a URL using the HTTP or FTP protocol + + To parse from a string, use the ``fromstring()`` function instead. + + Note that it is generally faster to parse from a file path or URL + than from an open file object or file-like object. Transparent + decompression from gzip compressed sources is supported (unless + explicitly disabled in libxml2). + + The ``base_url`` keyword allows setting a URL for the document + when parsing from a file-like object. This is needed when looking + up external entities (DTD, XInclude, ...) with relative paths. + """ + cdef _Document doc + try: + doc = _parseDocument(source, parser, base_url) + return _elementTreeFactory(doc, None) + except _TargetParserResult as result_container: + return result_container.result + + +def adopt_external_document(capsule, _BaseParser parser=None): + """adopt_external_document(capsule, parser=None) + + Unpack a libxml2 document pointer from a PyCapsule and wrap it in an + lxml ElementTree object. + + This allows external libraries to build XML/HTML trees using libxml2 + and then pass them efficiently into lxml for further processing. + + If a ``parser`` is provided, it will be used for configuring the + lxml document. No parsing will be done. + + The capsule must have the name ``"libxml2:xmlDoc"`` and its pointer + value must reference a correct libxml2 document of type ``xmlDoc*``. + The creator of the capsule must take care to correctly clean up the + document using an appropriate capsule destructor. By default, the + libxml2 document will be copied to let lxml safely own the memory + of the internal tree that it uses. + + If the capsule context is non-NULL, it must point to a C string that + can be compared using ``strcmp()``. If the context string equals + ``"destructor:xmlFreeDoc"``, the libxml2 document will not be copied + but the capsule invalidated instead by clearing its destructor and + name. That way, lxml takes ownership of the libxml2 document in memory + without creating a copy first, and the capsule destructor will not be + called. The document will then eventually be cleaned up by lxml using + the libxml2 API function ``xmlFreeDoc()`` once it is no longer used. + + If no copy is made, later modifications of the tree outside of lxml + should not be attempted after transferring the ownership. + """ + cdef xmlDoc* c_doc + cdef bint is_owned = False + c_doc = python.lxml_unpack_xmldoc_capsule(capsule, &is_owned) + doc = _adoptForeignDoc(c_doc, parser, is_owned) + return _elementTreeFactory(doc, None) + + +################################################################################ +# Include submodules + +include "readonlytree.pxi" # Read-only implementation of Element proxies +include "classlookup.pxi" # Element class lookup mechanisms +include "nsclasses.pxi" # Namespace implementation and registry +include "docloader.pxi" # Support for custom document loaders +include "parser.pxi" # XML and HTML parsers +include "saxparser.pxi" # SAX-like Parser interface and tree builder +include "parsertarget.pxi" # ET Parser target +include "serializer.pxi" # XML output functions +include "iterparse.pxi" # incremental XML parsing +include "xmlid.pxi" # XMLID and IDDict +include "xinclude.pxi" # XInclude +include "cleanup.pxi" # Cleanup and recursive element removal functions + + +################################################################################ +# Include submodules for XPath and XSLT + +include "extensions.pxi" # XPath/XSLT extension functions +include "xpath.pxi" # XPath evaluation +include "xslt.pxi" # XSL transformations +include "xsltext.pxi" # XSL extension elements + + +################################################################################ +# Validation + +cdef class DocumentInvalid(LxmlError): + """Validation error. + + Raised by all document validators when their ``assertValid(tree)`` + method fails. + """ + + +cdef class _Validator: + "Base class for XML validators." + cdef _ErrorLog _error_log + def __cinit__(self): + self._error_log = _ErrorLog() + + def validate(self, etree): + """validate(self, etree) + + Validate the document using this schema. + + Returns true if document is valid, false if not. + """ + return self(etree) + + def assertValid(self, etree): + """assertValid(self, etree) + + Raises `DocumentInvalid` if the document does not comply with the schema. + """ + if not self(etree): + raise DocumentInvalid(self._error_log._buildExceptionMessage( + "Document does not comply with schema"), + self._error_log) + + def assert_(self, etree): + """assert_(self, etree) + + Raises `AssertionError` if the document does not comply with the schema. + """ + if not self(etree): + raise AssertionError, self._error_log._buildExceptionMessage( + "Document does not comply with schema") + + cpdef _append_log_message(self, int domain, int type, int level, int line, + message, filename): + self._error_log._receiveGeneric(domain, type, level, line, message, + filename) + + cpdef _clear_error_log(self): + self._error_log.clear() + + @property + def error_log(self): + """The log of validation errors and warnings.""" + assert self._error_log is not None, "XPath evaluator not initialised" + return self._error_log.copy() + +include "dtd.pxi" # DTD +include "relaxng.pxi" # RelaxNG +include "xmlschema.pxi" # XMLSchema +include "schematron.pxi" # Schematron (requires libxml2 2.6.21+) + +################################################################################ +# Public C API + +include "public-api.pxi" + +################################################################################ +# Other stuff + +include "debug.pxi" diff --git a/intelliaide/vendor/lxml/etree_api.h b/intelliaide/vendor/lxml/etree_api.h new file mode 100644 index 0000000..702fef2 --- /dev/null +++ b/intelliaide/vendor/lxml/etree_api.h @@ -0,0 +1,214 @@ +/* Generated by Cython 3.2.4 */ + +#ifndef __PYX_HAVE_API__lxml__etree +#define __PYX_HAVE_API__lxml__etree +#ifdef __MINGW64__ +#define MS_WIN64 +#endif +#include "Python.h" +#include "etree.h" + +static struct LxmlElement *(*__pyx_api_f_4lxml_5etree_deepcopyNodeToDocument)(struct LxmlDocument *, xmlNode *) = 0; +#define deepcopyNodeToDocument __pyx_api_f_4lxml_5etree_deepcopyNodeToDocument +static struct LxmlElementTree *(*__pyx_api_f_4lxml_5etree_elementTreeFactory)(struct LxmlElement *) = 0; +#define elementTreeFactory __pyx_api_f_4lxml_5etree_elementTreeFactory +static struct LxmlElementTree *(*__pyx_api_f_4lxml_5etree_newElementTree)(struct LxmlElement *, PyObject *) = 0; +#define newElementTree __pyx_api_f_4lxml_5etree_newElementTree +static struct LxmlElementTree *(*__pyx_api_f_4lxml_5etree_adoptExternalDocument)(xmlDoc *, PyObject *, int) = 0; +#define adoptExternalDocument __pyx_api_f_4lxml_5etree_adoptExternalDocument +static struct LxmlElement *(*__pyx_api_f_4lxml_5etree_elementFactory)(struct LxmlDocument *, xmlNode *) = 0; +#define elementFactory __pyx_api_f_4lxml_5etree_elementFactory +static struct LxmlElement *(*__pyx_api_f_4lxml_5etree_makeElement)(PyObject *, struct LxmlDocument *, PyObject *, PyObject *, PyObject *, PyObject *, PyObject *) = 0; +#define makeElement __pyx_api_f_4lxml_5etree_makeElement +static struct LxmlElement *(*__pyx_api_f_4lxml_5etree_makeSubElement)(struct LxmlElement *, PyObject *, PyObject *, PyObject *, PyObject *, PyObject *) = 0; +#define makeSubElement __pyx_api_f_4lxml_5etree_makeSubElement +static void (*__pyx_api_f_4lxml_5etree_setElementClassLookupFunction)(_element_class_lookup_function, PyObject *) = 0; +#define setElementClassLookupFunction __pyx_api_f_4lxml_5etree_setElementClassLookupFunction +static PyObject *(*__pyx_api_f_4lxml_5etree_lookupDefaultElementClass)(PyObject *, PyObject *, xmlNode *) = 0; +#define lookupDefaultElementClass __pyx_api_f_4lxml_5etree_lookupDefaultElementClass +static PyObject *(*__pyx_api_f_4lxml_5etree_lookupNamespaceElementClass)(PyObject *, PyObject *, xmlNode *) = 0; +#define lookupNamespaceElementClass __pyx_api_f_4lxml_5etree_lookupNamespaceElementClass +static PyObject *(*__pyx_api_f_4lxml_5etree_callLookupFallback)(struct LxmlFallbackElementClassLookup *, struct LxmlDocument *, xmlNode *) = 0; +#define callLookupFallback __pyx_api_f_4lxml_5etree_callLookupFallback +static int (*__pyx_api_f_4lxml_5etree_tagMatches)(xmlNode *, const xmlChar *, const xmlChar *) = 0; +#define tagMatches __pyx_api_f_4lxml_5etree_tagMatches +static struct LxmlDocument *(*__pyx_api_f_4lxml_5etree_documentOrRaise)(PyObject *) = 0; +#define documentOrRaise __pyx_api_f_4lxml_5etree_documentOrRaise +static struct LxmlElement *(*__pyx_api_f_4lxml_5etree_rootNodeOrRaise)(PyObject *) = 0; +#define rootNodeOrRaise __pyx_api_f_4lxml_5etree_rootNodeOrRaise +static int (*__pyx_api_f_4lxml_5etree_hasText)(xmlNode *) = 0; +#define hasText __pyx_api_f_4lxml_5etree_hasText +static int (*__pyx_api_f_4lxml_5etree_hasTail)(xmlNode *) = 0; +#define hasTail __pyx_api_f_4lxml_5etree_hasTail +static PyObject *(*__pyx_api_f_4lxml_5etree_textOf)(xmlNode *) = 0; +#define textOf __pyx_api_f_4lxml_5etree_textOf +static PyObject *(*__pyx_api_f_4lxml_5etree_tailOf)(xmlNode *) = 0; +#define tailOf __pyx_api_f_4lxml_5etree_tailOf +static int (*__pyx_api_f_4lxml_5etree_setNodeText)(xmlNode *, PyObject *) = 0; +#define setNodeText __pyx_api_f_4lxml_5etree_setNodeText +static int (*__pyx_api_f_4lxml_5etree_setTailText)(xmlNode *, PyObject *) = 0; +#define setTailText __pyx_api_f_4lxml_5etree_setTailText +static PyObject *(*__pyx_api_f_4lxml_5etree_attributeValue)(xmlNode *, xmlAttr *) = 0; +#define attributeValue __pyx_api_f_4lxml_5etree_attributeValue +static PyObject *(*__pyx_api_f_4lxml_5etree_attributeValueFromNsName)(xmlNode *, const xmlChar *, const xmlChar *) = 0; +#define attributeValueFromNsName __pyx_api_f_4lxml_5etree_attributeValueFromNsName +static PyObject *(*__pyx_api_f_4lxml_5etree_getAttributeValue)(struct LxmlElement *, PyObject *, PyObject *) = 0; +#define getAttributeValue __pyx_api_f_4lxml_5etree_getAttributeValue +static PyObject *(*__pyx_api_f_4lxml_5etree_iterattributes)(struct LxmlElement *, int) = 0; +#define iterattributes __pyx_api_f_4lxml_5etree_iterattributes +static PyObject *(*__pyx_api_f_4lxml_5etree_collectAttributes)(xmlNode *, int) = 0; +#define collectAttributes __pyx_api_f_4lxml_5etree_collectAttributes +static int (*__pyx_api_f_4lxml_5etree_setAttributeValue)(struct LxmlElement *, PyObject *, PyObject *) = 0; +#define setAttributeValue __pyx_api_f_4lxml_5etree_setAttributeValue +static int (*__pyx_api_f_4lxml_5etree_delAttribute)(struct LxmlElement *, PyObject *) = 0; +#define delAttribute __pyx_api_f_4lxml_5etree_delAttribute +static int (*__pyx_api_f_4lxml_5etree_delAttributeFromNsName)(xmlNode *, const xmlChar *, const xmlChar *) = 0; +#define delAttributeFromNsName __pyx_api_f_4lxml_5etree_delAttributeFromNsName +static int (*__pyx_api_f_4lxml_5etree_hasChild)(xmlNode *) = 0; +#define hasChild __pyx_api_f_4lxml_5etree_hasChild +static xmlNode *(*__pyx_api_f_4lxml_5etree_findChild)(xmlNode *, Py_ssize_t) = 0; +#define findChild __pyx_api_f_4lxml_5etree_findChild +static xmlNode *(*__pyx_api_f_4lxml_5etree_findChildForwards)(xmlNode *, Py_ssize_t) = 0; +#define findChildForwards __pyx_api_f_4lxml_5etree_findChildForwards +static xmlNode *(*__pyx_api_f_4lxml_5etree_findChildBackwards)(xmlNode *, Py_ssize_t) = 0; +#define findChildBackwards __pyx_api_f_4lxml_5etree_findChildBackwards +static xmlNode *(*__pyx_api_f_4lxml_5etree_nextElement)(xmlNode *) = 0; +#define nextElement __pyx_api_f_4lxml_5etree_nextElement +static xmlNode *(*__pyx_api_f_4lxml_5etree_previousElement)(xmlNode *) = 0; +#define previousElement __pyx_api_f_4lxml_5etree_previousElement +static void (*__pyx_api_f_4lxml_5etree_appendChild)(struct LxmlElement *, struct LxmlElement *) = 0; +#define appendChild __pyx_api_f_4lxml_5etree_appendChild +static int (*__pyx_api_f_4lxml_5etree_appendChildToElement)(struct LxmlElement *, struct LxmlElement *) = 0; +#define appendChildToElement __pyx_api_f_4lxml_5etree_appendChildToElement +static PyObject *(*__pyx_api_f_4lxml_5etree_pyunicode)(const xmlChar *) = 0; +#define pyunicode __pyx_api_f_4lxml_5etree_pyunicode +static PyObject *(*__pyx_api_f_4lxml_5etree_utf8)(PyObject *) = 0; +#define utf8 __pyx_api_f_4lxml_5etree_utf8 +static PyObject *(*__pyx_api_f_4lxml_5etree_getNsTag)(PyObject *) = 0; +#define getNsTag __pyx_api_f_4lxml_5etree_getNsTag +static PyObject *(*__pyx_api_f_4lxml_5etree_getNsTagWithEmptyNs)(PyObject *) = 0; +#define getNsTagWithEmptyNs __pyx_api_f_4lxml_5etree_getNsTagWithEmptyNs +static PyObject *(*__pyx_api_f_4lxml_5etree_namespacedName)(xmlNode *) = 0; +#define namespacedName __pyx_api_f_4lxml_5etree_namespacedName +static PyObject *(*__pyx_api_f_4lxml_5etree_namespacedNameFromNsName)(const xmlChar *, const xmlChar *) = 0; +#define namespacedNameFromNsName __pyx_api_f_4lxml_5etree_namespacedNameFromNsName +static void (*__pyx_api_f_4lxml_5etree_iteratorStoreNext)(struct LxmlElementIterator *, struct LxmlElement *) = 0; +#define iteratorStoreNext __pyx_api_f_4lxml_5etree_iteratorStoreNext +static void (*__pyx_api_f_4lxml_5etree_initTagMatch)(struct LxmlElementTagMatcher *, PyObject *) = 0; +#define initTagMatch __pyx_api_f_4lxml_5etree_initTagMatch +static xmlNs *(*__pyx_api_f_4lxml_5etree_findOrBuildNodeNsPrefix)(struct LxmlDocument *, xmlNode *, const xmlChar *, const xmlChar *) = 0; +#define findOrBuildNodeNsPrefix __pyx_api_f_4lxml_5etree_findOrBuildNodeNsPrefix +static int __Pyx_ImportFunction_3_2_4(PyObject *module, const char *funcname, void (**f)(void), const char *sig); + +#ifndef __PYX_HAVE_RT_ImportFromPxd_3_2_4 +#define __PYX_HAVE_RT_ImportFromPxd_3_2_4 +static int __Pyx_ImportFromPxd_3_2_4(PyObject *module, const char *name, void **p, const char *sig, const char *what) { + PyObject *d = 0; + PyObject *cobj = 0; + d = PyObject_GetAttrString(module, "__pyx_capi__"); + if (!d) + goto bad; +#if (defined(Py_LIMITED_API) && Py_LIMITED_API >= 0x030d0000) || (!defined(Py_LIMITED_API) && PY_VERSION_HEX >= 0x030d0000) + PyDict_GetItemStringRef(d, name, &cobj); +#else + cobj = PyDict_GetItemString(d, name); + Py_XINCREF(cobj); +#endif + if (!cobj) { + PyErr_Format(PyExc_ImportError, + "%.200s does not export expected C %.8s %.200s", + PyModule_GetName(module), what, name); + goto bad; + } + if (!PyCapsule_IsValid(cobj, sig)) { + PyErr_Format(PyExc_TypeError, + "C %.8s %.200s.%.200s has wrong signature (expected %.500s, got %.500s)", + what, PyModule_GetName(module), name, sig, PyCapsule_GetName(cobj)); + goto bad; + } + *p = PyCapsule_GetPointer(cobj, sig); + if (!(*p)) + goto bad; + Py_DECREF(d); + Py_DECREF(cobj); + return 0; +bad: + Py_XDECREF(d); + Py_XDECREF(cobj); + return -1; +} +#endif + +#ifndef __PYX_HAVE_RT_ImportFunction_3_2_4 +#define __PYX_HAVE_RT_ImportFunction_3_2_4 +static int __Pyx_ImportFunction_3_2_4(PyObject *module, const char *funcname, void (**f)(void), const char *sig) { + union { + void (*fp)(void); + void *p; + } tmp; + int result = __Pyx_ImportFromPxd_3_2_4(module, funcname, &tmp.p, sig, "function"); + if (result == 0) { + *f = tmp.fp; + } + return result; +} +#endif + + +static int import_lxml__etree(void) { + PyObject *module = 0; + module = PyImport_ImportModule("lxml.etree"); + if (!module) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "deepcopyNodeToDocument", (void (**)(void))&__pyx_api_f_4lxml_5etree_deepcopyNodeToDocument, "struct LxmlElement *(struct LxmlDocument *, xmlNode *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "elementTreeFactory", (void (**)(void))&__pyx_api_f_4lxml_5etree_elementTreeFactory, "struct LxmlElementTree *(struct LxmlElement *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "newElementTree", (void (**)(void))&__pyx_api_f_4lxml_5etree_newElementTree, "struct LxmlElementTree *(struct LxmlElement *, PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "adoptExternalDocument", (void (**)(void))&__pyx_api_f_4lxml_5etree_adoptExternalDocument, "struct LxmlElementTree *(xmlDoc *, PyObject *, int)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "elementFactory", (void (**)(void))&__pyx_api_f_4lxml_5etree_elementFactory, "struct LxmlElement *(struct LxmlDocument *, xmlNode *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "makeElement", (void (**)(void))&__pyx_api_f_4lxml_5etree_makeElement, "struct LxmlElement *(PyObject *, struct LxmlDocument *, PyObject *, PyObject *, PyObject *, PyObject *, PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "makeSubElement", (void (**)(void))&__pyx_api_f_4lxml_5etree_makeSubElement, "struct LxmlElement *(struct LxmlElement *, PyObject *, PyObject *, PyObject *, PyObject *, PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "setElementClassLookupFunction", (void (**)(void))&__pyx_api_f_4lxml_5etree_setElementClassLookupFunction, "void (_element_class_lookup_function, PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "lookupDefaultElementClass", (void (**)(void))&__pyx_api_f_4lxml_5etree_lookupDefaultElementClass, "PyObject *(PyObject *, PyObject *, xmlNode *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "lookupNamespaceElementClass", (void (**)(void))&__pyx_api_f_4lxml_5etree_lookupNamespaceElementClass, "PyObject *(PyObject *, PyObject *, xmlNode *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "callLookupFallback", (void (**)(void))&__pyx_api_f_4lxml_5etree_callLookupFallback, "PyObject *(struct LxmlFallbackElementClassLookup *, struct LxmlDocument *, xmlNode *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "tagMatches", (void (**)(void))&__pyx_api_f_4lxml_5etree_tagMatches, "int (xmlNode *, const xmlChar *, const xmlChar *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "documentOrRaise", (void (**)(void))&__pyx_api_f_4lxml_5etree_documentOrRaise, "struct LxmlDocument *(PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "rootNodeOrRaise", (void (**)(void))&__pyx_api_f_4lxml_5etree_rootNodeOrRaise, "struct LxmlElement *(PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "hasText", (void (**)(void))&__pyx_api_f_4lxml_5etree_hasText, "int (xmlNode *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "hasTail", (void (**)(void))&__pyx_api_f_4lxml_5etree_hasTail, "int (xmlNode *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "textOf", (void (**)(void))&__pyx_api_f_4lxml_5etree_textOf, "PyObject *(xmlNode *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "tailOf", (void (**)(void))&__pyx_api_f_4lxml_5etree_tailOf, "PyObject *(xmlNode *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "setNodeText", (void (**)(void))&__pyx_api_f_4lxml_5etree_setNodeText, "int (xmlNode *, PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "setTailText", (void (**)(void))&__pyx_api_f_4lxml_5etree_setTailText, "int (xmlNode *, PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "attributeValue", (void (**)(void))&__pyx_api_f_4lxml_5etree_attributeValue, "PyObject *(xmlNode *, xmlAttr *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "attributeValueFromNsName", (void (**)(void))&__pyx_api_f_4lxml_5etree_attributeValueFromNsName, "PyObject *(xmlNode *, const xmlChar *, const xmlChar *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "getAttributeValue", (void (**)(void))&__pyx_api_f_4lxml_5etree_getAttributeValue, "PyObject *(struct LxmlElement *, PyObject *, PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "iterattributes", (void (**)(void))&__pyx_api_f_4lxml_5etree_iterattributes, "PyObject *(struct LxmlElement *, int)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "collectAttributes", (void (**)(void))&__pyx_api_f_4lxml_5etree_collectAttributes, "PyObject *(xmlNode *, int)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "setAttributeValue", (void (**)(void))&__pyx_api_f_4lxml_5etree_setAttributeValue, "int (struct LxmlElement *, PyObject *, PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "delAttribute", (void (**)(void))&__pyx_api_f_4lxml_5etree_delAttribute, "int (struct LxmlElement *, PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "delAttributeFromNsName", (void (**)(void))&__pyx_api_f_4lxml_5etree_delAttributeFromNsName, "int (xmlNode *, const xmlChar *, const xmlChar *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "hasChild", (void (**)(void))&__pyx_api_f_4lxml_5etree_hasChild, "int (xmlNode *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "findChild", (void (**)(void))&__pyx_api_f_4lxml_5etree_findChild, "xmlNode *(xmlNode *, Py_ssize_t)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "findChildForwards", (void (**)(void))&__pyx_api_f_4lxml_5etree_findChildForwards, "xmlNode *(xmlNode *, Py_ssize_t)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "findChildBackwards", (void (**)(void))&__pyx_api_f_4lxml_5etree_findChildBackwards, "xmlNode *(xmlNode *, Py_ssize_t)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "nextElement", (void (**)(void))&__pyx_api_f_4lxml_5etree_nextElement, "xmlNode *(xmlNode *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "previousElement", (void (**)(void))&__pyx_api_f_4lxml_5etree_previousElement, "xmlNode *(xmlNode *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "appendChild", (void (**)(void))&__pyx_api_f_4lxml_5etree_appendChild, "void (struct LxmlElement *, struct LxmlElement *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "appendChildToElement", (void (**)(void))&__pyx_api_f_4lxml_5etree_appendChildToElement, "int (struct LxmlElement *, struct LxmlElement *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "pyunicode", (void (**)(void))&__pyx_api_f_4lxml_5etree_pyunicode, "PyObject *(const xmlChar *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "utf8", (void (**)(void))&__pyx_api_f_4lxml_5etree_utf8, "PyObject *(PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "getNsTag", (void (**)(void))&__pyx_api_f_4lxml_5etree_getNsTag, "PyObject *(PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "getNsTagWithEmptyNs", (void (**)(void))&__pyx_api_f_4lxml_5etree_getNsTagWithEmptyNs, "PyObject *(PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "namespacedName", (void (**)(void))&__pyx_api_f_4lxml_5etree_namespacedName, "PyObject *(xmlNode *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "namespacedNameFromNsName", (void (**)(void))&__pyx_api_f_4lxml_5etree_namespacedNameFromNsName, "PyObject *(const xmlChar *, const xmlChar *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "iteratorStoreNext", (void (**)(void))&__pyx_api_f_4lxml_5etree_iteratorStoreNext, "void (struct LxmlElementIterator *, struct LxmlElement *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "initTagMatch", (void (**)(void))&__pyx_api_f_4lxml_5etree_initTagMatch, "void (struct LxmlElementTagMatcher *, PyObject *)") < 0) goto bad; + if (__Pyx_ImportFunction_3_2_4(module, "findOrBuildNodeNsPrefix", (void (**)(void))&__pyx_api_f_4lxml_5etree_findOrBuildNodeNsPrefix, "xmlNs *(struct LxmlDocument *, xmlNode *, const xmlChar *, const xmlChar *)") < 0) goto bad; + Py_DECREF(module); module = 0; + return 0; + bad: + Py_XDECREF(module); + return -1; +} + +#endif /* !__PYX_HAVE_API__lxml__etree */ diff --git a/intelliaide/vendor/lxml/extensions.pxi b/intelliaide/vendor/lxml/extensions.pxi new file mode 100644 index 0000000..5945080 --- /dev/null +++ b/intelliaide/vendor/lxml/extensions.pxi @@ -0,0 +1,838 @@ +# support for extension functions in XPath and XSLT + +cdef class XPathError(LxmlError): + """Base class of all XPath errors. + """ + +cdef class XPathEvalError(XPathError): + """Error during XPath evaluation. + """ + +cdef class XPathFunctionError(XPathEvalError): + """Internal error looking up an XPath extension function. + """ + +cdef class XPathResultError(XPathEvalError): + """Error handling an XPath result. + """ + + +# forward declarations + +ctypedef int (*_register_function)(void* ctxt, name_utf, ns_uri_utf) +cdef class _ExsltRegExp + +################################################################################ +# Base class for XSLT and XPath evaluation contexts: functions, namespaces, ... + +@cython.internal +cdef class _BaseContext: + cdef xpath.xmlXPathContext* _xpathCtxt + cdef _Document _doc + cdef dict _extensions + cdef list _namespaces + cdef list _global_namespaces + cdef dict _utf_refs + cdef dict _function_cache + cdef dict _eval_context_dict + cdef bint _build_smart_strings + # for exception handling and temporary reference keeping: + cdef _TempStore _temp_refs + cdef set _temp_documents + cdef _ExceptionContext _exc + cdef _ErrorLog _error_log + + def __init__(self, namespaces, extensions, error_log, enable_regexp, + build_smart_strings): + cdef _ExsltRegExp _regexp + cdef dict new_extensions + cdef list ns + self._utf_refs = {} + self._global_namespaces = [] + self._function_cache = {} + self._eval_context_dict = None + self._error_log = error_log + + if extensions is not None: + # convert extensions to UTF-8 + if isinstance(extensions, dict): + extensions = (extensions,) + # format: [ {(ns, name):function} ] -> {(ns_utf, name_utf):function} + new_extensions = {} + for extension in extensions: + for (ns_uri, name), function in extension.items(): + if name is None: + raise ValueError, "extensions must have non empty names" + ns_utf = self._to_utf(ns_uri) + name_utf = self._to_utf(name) + new_extensions[(ns_utf, name_utf)] = function + extensions = new_extensions or None + + if namespaces is not None: + if isinstance(namespaces, dict): + namespaces = namespaces.items() + if namespaces: + ns = [] + for prefix, ns_uri in namespaces: + if prefix is None or not prefix: + raise TypeError, \ + "empty namespace prefix is not supported in XPath" + if ns_uri is None or not ns_uri: + raise TypeError, \ + "setting default namespace is not supported in XPath" + prefix_utf = self._to_utf(prefix) + ns_uri_utf = self._to_utf(ns_uri) + ns.append( (prefix_utf, ns_uri_utf) ) + namespaces = ns + else: + namespaces = None + + self._doc = None + self._exc = _ExceptionContext() + self._extensions = extensions + self._namespaces = namespaces + self._temp_refs = _TempStore() + self._temp_documents = set() + self._build_smart_strings = build_smart_strings + + if enable_regexp: + _regexp = _ExsltRegExp() + _regexp._register_in_context(self) + + cdef _BaseContext _copy(self): + cdef _BaseContext context + if self._namespaces is not None: + namespaces = self._namespaces[:] + else: + namespaces = None + context = self.__class__(namespaces, None, self._error_log, False, + self._build_smart_strings) + if self._extensions is not None: + context._extensions = self._extensions.copy() + return context + + cdef bytes _to_utf(self, s): + "Convert to UTF-8 and keep a reference to the encoded string" + cdef python.PyObject* dict_result + if s is None: + return None + dict_result = python.PyDict_GetItem(self._utf_refs, s) + if dict_result is not NULL: + return dict_result + utf = _utf8(s) + self._utf_refs[s] = utf + if python.IS_PYPY: + # use C level refs, PyPy refs are not enough! + python.Py_INCREF(utf) + return utf + + cdef void _set_xpath_context(self, xpath.xmlXPathContext* xpathCtxt) noexcept: + self._xpathCtxt = xpathCtxt + xpathCtxt.userData = self + # Need a cast here because older libxml2 releases do not use 'const' in the functype. + xpathCtxt.error = _receiveXPathError + + @cython.final + cdef _register_context(self, _Document doc): + self._doc = doc + self._exc.clear() + + @cython.final + cdef _cleanup_context(self): + #xpath.xmlXPathRegisteredNsCleanup(self._xpathCtxt) + #self.unregisterGlobalNamespaces() + if python.IS_PYPY: + # clean up double refs in PyPy (see "_to_utf()" method) + for ref in self._utf_refs.itervalues(): + python.Py_DECREF(ref) + self._utf_refs.clear() + self._eval_context_dict = None + self._doc = None + + @cython.final + cdef _release_context(self): + if self._xpathCtxt is not NULL: + self._xpathCtxt.userData = NULL + self._xpathCtxt = NULL + + # namespaces (internal UTF-8 methods with leading '_') + + cdef addNamespace(self, prefix, ns_uri): + cdef list namespaces + if prefix is None: + raise TypeError, "empty prefix is not supported in XPath" + prefix_utf = self._to_utf(prefix) + ns_uri_utf = self._to_utf(ns_uri) + new_item = (prefix_utf, ns_uri_utf) + if self._namespaces is None: + self._namespaces = [new_item] + else: + namespaces = [] + for item in self._namespaces: + if item[0] == prefix_utf: + item = new_item + new_item = None + namespaces.append(item) + if new_item is not None: + namespaces.append(new_item) + self._namespaces = namespaces + if self._xpathCtxt is not NULL: + xpath.xmlXPathRegisterNs( + self._xpathCtxt, _xcstr(prefix_utf), _xcstr(ns_uri_utf)) + + cdef registerNamespace(self, prefix, ns_uri): + if prefix is None: + raise TypeError, "empty prefix is not supported in XPath" + prefix_utf = self._to_utf(prefix) + ns_uri_utf = self._to_utf(ns_uri) + self._global_namespaces.append(prefix_utf) + xpath.xmlXPathRegisterNs(self._xpathCtxt, + _xcstr(prefix_utf), _xcstr(ns_uri_utf)) + + cdef registerLocalNamespaces(self): + if self._namespaces is None: + return + for prefix_utf, ns_uri_utf in self._namespaces: + xpath.xmlXPathRegisterNs( + self._xpathCtxt, _xcstr(prefix_utf), _xcstr(ns_uri_utf)) + + cdef registerGlobalNamespaces(self): + cdef list ns_prefixes = _find_all_extension_prefixes() + if python.PyList_GET_SIZE(ns_prefixes) > 0: + for prefix_utf, ns_uri_utf in ns_prefixes: + self._global_namespaces.append(prefix_utf) + xpath.xmlXPathRegisterNs( + self._xpathCtxt, _xcstr(prefix_utf), _xcstr(ns_uri_utf)) + + cdef unregisterGlobalNamespaces(self): + if python.PyList_GET_SIZE(self._global_namespaces) > 0: + for prefix_utf in self._global_namespaces: + xpath.xmlXPathRegisterNs(self._xpathCtxt, + _xcstr(prefix_utf), NULL) + del self._global_namespaces[:] + + cdef void _unregisterNamespace(self, prefix_utf) noexcept: + xpath.xmlXPathRegisterNs(self._xpathCtxt, + _xcstr(prefix_utf), NULL) + + # extension functions + + cdef int _addLocalExtensionFunction(self, ns_utf, name_utf, function) except -1: + if self._extensions is None: + self._extensions = {} + self._extensions[(ns_utf, name_utf)] = function + return 0 + + cdef registerGlobalFunctions(self, void* ctxt, + _register_function reg_func): + cdef python.PyObject* dict_result + cdef dict d + for ns_utf, ns_functions in __FUNCTION_NAMESPACE_REGISTRIES.iteritems(): + dict_result = python.PyDict_GetItem( + self._function_cache, ns_utf) + if dict_result is not NULL: + d = dict_result + else: + d = {} + self._function_cache[ns_utf] = d + for name_utf, function in ns_functions.iteritems(): + d[name_utf] = function + reg_func(ctxt, name_utf, ns_utf) + + cdef registerLocalFunctions(self, void* ctxt, + _register_function reg_func): + cdef python.PyObject* dict_result + cdef dict d + if self._extensions is None: + return # done + last_ns = None + d = None + for (ns_utf, name_utf), function in self._extensions.iteritems(): + if ns_utf is not last_ns or d is None: + last_ns = ns_utf + dict_result = python.PyDict_GetItem( + self._function_cache, ns_utf) + if dict_result is not NULL: + d = dict_result + else: + d = {} + self._function_cache[ns_utf] = d + d[name_utf] = function + reg_func(ctxt, name_utf, ns_utf) + + cdef unregisterAllFunctions(self, void* ctxt, + _register_function unreg_func): + for ns_utf, functions in self._function_cache.iteritems(): + for name_utf in functions: + unreg_func(ctxt, name_utf, ns_utf) + + cdef unregisterGlobalFunctions(self, void* ctxt, + _register_function unreg_func): + for ns_utf, functions in self._function_cache.items(): + for name_utf in functions: + if self._extensions is None or \ + (ns_utf, name_utf) not in self._extensions: + unreg_func(ctxt, name_utf, ns_utf) + + @cython.final + cdef _find_cached_function(self, const_xmlChar* c_ns_uri, const_xmlChar* c_name): + """Lookup an extension function in the cache and return it. + + Parameters: c_ns_uri may be NULL, c_name must not be NULL + """ + cdef python.PyObject* c_dict + cdef python.PyObject* dict_result + c_dict = python.PyDict_GetItem( + self._function_cache, None if c_ns_uri is NULL else c_ns_uri) + if c_dict is not NULL: + dict_result = python.PyDict_GetItem( + c_dict, c_name) + if dict_result is not NULL: + return dict_result + return None + + # Python access to the XPath context for extension functions + + @property + def context_node(self): + cdef xmlNode* c_node + if self._xpathCtxt is NULL: + raise XPathError, \ + "XPath context is only usable during the evaluation" + c_node = self._xpathCtxt.node + if c_node is NULL: + raise XPathError, "no context node" + if c_node.doc != self._xpathCtxt.doc: + raise XPathError, \ + "document-external context nodes are not supported" + if self._doc is None: + raise XPathError, "document context is missing" + return _elementFactory(self._doc, c_node) + + @property + def eval_context(self): + if self._eval_context_dict is None: + self._eval_context_dict = {} + return self._eval_context_dict + + # Python reference keeping during XPath function evaluation + + @cython.final + cdef _release_temp_refs(self): + "Free temporarily referenced objects from this context." + self._temp_refs.clear() + self._temp_documents.clear() + + @cython.final + cdef _hold(self, obj): + """A way to temporarily hold references to nodes in the evaluator. + + This is needed because otherwise nodes created in XPath extension + functions would be reference counted too soon, during the XPath + evaluation. This is most important in the case of exceptions. + """ + cdef _Element element + if isinstance(obj, _Element): + self._temp_refs.add(obj) + self._temp_documents.add((<_Element>obj)._doc) + return + elif _isString(obj) or not python.PySequence_Check(obj): + return + for o in obj: + if isinstance(o, _Element): + #print "Holding element:", element._c_node + self._temp_refs.add(o) + #print "Holding document:", element._doc._c_doc + self._temp_documents.add((<_Element>o)._doc) + + @cython.final + cdef _Document _findDocumentForNode(self, xmlNode* c_node): + """If an XPath expression returns an element from a different + document than the current context document, we call this to + see if it was possibly created by an extension and is a known + document instance. + """ + cdef _Document doc + for doc in self._temp_documents: + if doc is not None and doc._c_doc is c_node.doc: + return doc + return None + + +# libxml2 keeps these error messages in a static array in its code +# and doesn't give us access to them ... + +cdef tuple LIBXML2_XPATH_ERROR_MESSAGES = ( + b"Ok", + b"Number encoding", + b"Unfinished literal", + b"Start of literal", + b"Expected $ for variable reference", + b"Undefined variable", + b"Invalid predicate", + b"Invalid expression", + b"Missing closing curly brace", + b"Unregistered function", + b"Invalid operand", + b"Invalid type", + b"Invalid number of arguments", + b"Invalid context size", + b"Invalid context position", + b"Memory allocation error", + b"Syntax error", + b"Resource error", + b"Sub resource error", + b"Undefined namespace prefix", + b"Encoding error", + b"Char out of XML range", + b"Invalid or incomplete context", + b"Stack usage error", + b"Forbidden variable\n", + b"?? Unknown error ??\n", +) + +cdef void _forwardXPathError(void* c_ctxt, const xmlerror.xmlError* c_error) noexcept with gil: + cdef xmlerror.xmlError error + cdef int xpath_code + if c_error.message is not NULL: + error.message = c_error.message + else: + xpath_code = c_error.code - xmlerror.XML_XPATH_EXPRESSION_OK + if 0 <= xpath_code < len(LIBXML2_XPATH_ERROR_MESSAGES): + error.message = _cstr(LIBXML2_XPATH_ERROR_MESSAGES[xpath_code]) + else: + error.message = b"unknown error" + error.domain = c_error.domain + error.code = c_error.code + error.level = c_error.level + error.line = c_error.line + error.int2 = c_error.int1 # column + error.file = c_error.file + error.node = NULL + + (<_BaseContext>c_ctxt)._error_log._receive(&error) + +cdef void _receiveXPathError(void* c_context, const xmlerror.xmlError* error) noexcept nogil: + if not __DEBUG: + return + if c_context is NULL: + _forwardError(NULL, error) + else: + _forwardXPathError(c_context, error) + + +def Extension(module, function_mapping=None, *, ns=None): + """Extension(module, function_mapping=None, ns=None) + + Build a dictionary of extension functions from the functions + defined in a module or the methods of an object. + + As second argument, you can pass an additional mapping of + attribute names to XPath function names, or a list of function + names that should be taken. + + The ``ns`` keyword argument accepts a namespace URI for the XPath + functions. + """ + cdef dict functions = {} + if isinstance(function_mapping, dict): + for function_name, xpath_name in function_mapping.items(): + functions[(ns, xpath_name)] = getattr(module, function_name) + else: + if function_mapping is None: + function_mapping = [ name for name in dir(module) + if not name.startswith('_') ] + for function_name in function_mapping: + functions[(ns, function_name)] = getattr(module, function_name) + return functions + +################################################################################ +# EXSLT regexp implementation + +@cython.final +@cython.internal +cdef class _ExsltRegExp: + cdef dict _compile_map + def __cinit__(self): + self._compile_map = {} + + cdef _make_string(self, value): + if _isString(value): + return value + elif isinstance(value, list): + # node set: take recursive text concatenation of first element + if python.PyList_GET_SIZE(value) == 0: + return '' + firstnode = value[0] + if _isString(firstnode): + return firstnode + elif isinstance(firstnode, _Element): + c_text = tree.xmlNodeGetContent((<_Element>firstnode)._c_node) + if c_text is NULL: + raise MemoryError() + try: + return funicode(c_text) + finally: + tree.xmlFree(c_text) + else: + return unicode(firstnode) + else: + return unicode(value) + + cdef _compile(self, rexp, ignore_case): + cdef python.PyObject* c_result + rexp = self._make_string(rexp) + key = (rexp, ignore_case) + c_result = python.PyDict_GetItem(self._compile_map, key) + if c_result is not NULL: + return c_result + py_flags = re.UNICODE + if ignore_case: + py_flags = py_flags | re.IGNORECASE + rexp_compiled = re.compile(rexp, py_flags) + self._compile_map[key] = rexp_compiled + return rexp_compiled + + def test(self, ctxt, s, rexp, flags=''): + flags = self._make_string(flags) + s = self._make_string(s) + rexpc = self._compile(rexp, 'i' in flags) + if rexpc.search(s) is None: + return False + else: + return True + + def match(self, ctxt, s, rexp, flags=''): + cdef list result_list + flags = self._make_string(flags) + s = self._make_string(s) + rexpc = self._compile(rexp, 'i' in flags) + if 'g' in flags: + results = rexpc.findall(s) + if not results: + return () + else: + result = rexpc.search(s) + if not result: + return () + results = [ result.group() ] + results.extend( result.groups('') ) + result_list = [] + root = Element('matches') + for s_match in results: + if python.PyTuple_CheckExact(s_match): + s_match = ''.join(s_match) + elem = SubElement(root, 'match') + elem.text = s_match + result_list.append(elem) + return result_list + + def replace(self, ctxt, s, rexp, flags, replacement): + replacement = self._make_string(replacement) + flags = self._make_string(flags) + s = self._make_string(s) + rexpc = self._compile(rexp, 'i' in flags) + count: object = 0 if 'g' in flags else 1 + return rexpc.sub(replacement, s, count) + + cdef _register_in_context(self, _BaseContext context): + ns = b"http://exslt.org/regular-expressions" + context._addLocalExtensionFunction(ns, b"test", self.test) + context._addLocalExtensionFunction(ns, b"match", self.match) + context._addLocalExtensionFunction(ns, b"replace", self.replace) + + +################################################################################ +# helper functions + +cdef xpath.xmlXPathObject* _wrapXPathObject(object obj, _Document doc, + _BaseContext context) except NULL: + cdef xpath.xmlNodeSet* resultSet + cdef _Element fake_node = None + cdef xmlNode* c_node + + if isinstance(obj, unicode): + obj = _utf8(obj) + if isinstance(obj, bytes): + # libxml2 copies the string value + return xpath.xmlXPathNewCString(_cstr(obj)) + if isinstance(obj, bool): + return xpath.xmlXPathNewBoolean(obj) + if python.PyNumber_Check(obj): + return xpath.xmlXPathNewFloat(obj) + if obj is None: + resultSet = xpath.xmlXPathNodeSetCreate(NULL) + elif isinstance(obj, _Element): + resultSet = xpath.xmlXPathNodeSetCreate((<_Element>obj)._c_node) + elif python.PySequence_Check(obj): + resultSet = xpath.xmlXPathNodeSetCreate(NULL) + try: + for value in obj: + if isinstance(value, _Element): + if context is not None: + context._hold(value) + xpath.xmlXPathNodeSetAdd(resultSet, (<_Element>value)._c_node) + else: + if context is None or doc is None: + raise XPathResultError, \ + f"Non-Element values not supported at this point - got {value!r}" + # support strings by appending text nodes to an Element + if isinstance(value, unicode): + value = _utf8(value) + if isinstance(value, bytes): + if fake_node is None: + fake_node = _makeElement("text-root", NULL, doc, None, + None, None, None, None, None) + context._hold(fake_node) + else: + # append a comment node to keep the text nodes separate + c_node = tree.xmlNewDocComment(doc._c_doc, "") + if c_node is NULL: + raise MemoryError() + tree.xmlAddChild(fake_node._c_node, c_node) + context._hold(value) + c_node = tree.xmlNewDocText(doc._c_doc, _xcstr(value)) + if c_node is NULL: + raise MemoryError() + tree.xmlAddChild(fake_node._c_node, c_node) + xpath.xmlXPathNodeSetAdd(resultSet, c_node) + else: + raise XPathResultError, \ + f"This is not a supported node-set result: {value!r}" + except: + xpath.xmlXPathFreeNodeSet(resultSet) + raise + else: + raise XPathResultError, f"Unknown return type: {python._fqtypename(obj).decode('utf8')}" + return xpath.xmlXPathWrapNodeSet(resultSet) + +cdef object _unwrapXPathObject(xpath.xmlXPathObject* xpathObj, + _Document doc, _BaseContext context): + if xpathObj.type == xpath.XPATH_UNDEFINED: + raise XPathResultError, "Undefined xpath result" + elif xpathObj.type == xpath.XPATH_NODESET: + return _createNodeSetResult(xpathObj, doc, context) + elif xpathObj.type == xpath.XPATH_BOOLEAN: + return xpathObj.boolval + elif xpathObj.type == xpath.XPATH_NUMBER: + return xpathObj.floatval + elif xpathObj.type == xpath.XPATH_STRING: + stringval = funicode(xpathObj.stringval) + if context._build_smart_strings: + stringval = _elementStringResultFactory( + stringval, None, None, False) + return stringval + elif xpathObj.type == xpath.XPATH_POINT: + raise NotImplementedError, "XPATH_POINT" + elif xpathObj.type == xpath.XPATH_RANGE: + raise NotImplementedError, "XPATH_RANGE" + elif xpathObj.type == xpath.XPATH_LOCATIONSET: + raise NotImplementedError, "XPATH_LOCATIONSET" + elif xpathObj.type == xpath.XPATH_USERS: + raise NotImplementedError, "XPATH_USERS" + elif xpathObj.type == xpath.XPATH_XSLT_TREE: + return _createNodeSetResult(xpathObj, doc, context) + else: + raise XPathResultError, f"Unknown xpath result {xpathObj.type}" + +cdef object _createNodeSetResult(xpath.xmlXPathObject* xpathObj, _Document doc, + _BaseContext context): + cdef xmlNode* c_node + cdef int i + cdef list result + result = [] + if xpathObj.nodesetval is NULL: + return result + for i in range(xpathObj.nodesetval.nodeNr): + c_node = xpathObj.nodesetval.nodeTab[i] + _unpackNodeSetEntry(result, c_node, doc, context, + xpathObj.type == xpath.XPATH_XSLT_TREE) + return result + +cdef _unpackNodeSetEntry(list results, xmlNode* c_node, _Document doc, + _BaseContext context, bint is_fragment): + cdef xmlNode* c_child + if _isElement(c_node): + if c_node.doc != doc._c_doc and c_node.doc._private is NULL: + # XXX: works, but maybe not always the right thing to do? + # XPath: only runs when extensions create or copy trees + # -> we store Python refs to these, so that is OK + # XSLT: can it leak when merging trees from multiple sources? + c_node = tree.xmlDocCopyNode(c_node, doc._c_doc, 1) + if not c_node: + raise MemoryError() + # FIXME: call _instantiateElementFromXPath() instead? + results.append( + _fakeDocElementFactory(doc, c_node)) + elif c_node.type == tree.XML_TEXT_NODE or \ + c_node.type == tree.XML_CDATA_SECTION_NODE or \ + c_node.type == tree.XML_ATTRIBUTE_NODE: + results.append( + _buildElementStringResult(doc, c_node, context)) + elif c_node.type == tree.XML_NAMESPACE_DECL: + results.append( (funicodeOrNone((c_node).prefix), + funicodeOrNone((c_node).href)) ) + elif c_node.type == tree.XML_DOCUMENT_NODE or \ + c_node.type == tree.XML_HTML_DOCUMENT_NODE: + # ignored for everything but result tree fragments + if is_fragment: + c_child = c_node.children + while c_child is not NULL: + _unpackNodeSetEntry(results, c_child, doc, context, 0) + c_child = c_child.next + elif c_node.type == tree.XML_XINCLUDE_START or \ + c_node.type == tree.XML_XINCLUDE_END: + pass + else: + raise NotImplementedError, \ + f"Not yet implemented result node type: {c_node.type}" + +cdef void _freeXPathObject(xpath.xmlXPathObject* xpathObj) noexcept: + """Free the XPath object, but *never* free the *content* of node sets. + Python dealloc will do that for us. + """ + if xpathObj.nodesetval is not NULL: + xpath.xmlXPathFreeNodeSet(xpathObj.nodesetval) + xpathObj.nodesetval = NULL + xpath.xmlXPathFreeObject(xpathObj) + +cdef _Element _instantiateElementFromXPath(xmlNode* c_node, _Document doc, + _BaseContext context): + # NOTE: this may copy the element - only call this when it can't leak + if c_node.doc != doc._c_doc and c_node.doc._private is NULL: + # not from the context document and not from a fake document + # either => may still be from a known document, e.g. one + # created by an extension function + node_doc = context._findDocumentForNode(c_node) + if node_doc is None: + # not from a known document at all! => can only make a + # safety copy here + c_node = tree.xmlDocCopyNode(c_node, doc._c_doc, 1) + if not c_node: + raise MemoryError() + else: + doc = node_doc + return _fakeDocElementFactory(doc, c_node) + +################################################################################ +# special str/unicode subclasses + +@cython.final +cdef class _ElementUnicodeResult(unicode): + cdef _Element _parent + cdef readonly object attrname + cdef readonly bint is_tail + + def getparent(self): + return self._parent + + @property + def is_text(self): + return self._parent is not None and not (self.is_tail or self.attrname is not None) + + @property + def is_attribute(self): + return self.attrname is not None + +cdef object _elementStringResultFactory(string_value, _Element parent, + attrname, bint is_tail): + result = _ElementUnicodeResult(string_value) + result._parent = parent + result.is_tail = is_tail + result.attrname = attrname + return result + +cdef object _buildElementStringResult(_Document doc, xmlNode* c_node, + _BaseContext context): + cdef _Element parent = None + cdef object attrname = None + cdef xmlNode* c_element + cdef bint is_tail + + if c_node.type == tree.XML_ATTRIBUTE_NODE: + attrname = _namespacedName(c_node) + is_tail = 0 + s = tree.xmlNodeGetContent(c_node) + if s is NULL: + raise MemoryError() + try: + value = funicode(s) + finally: + tree.xmlFree(s) + c_element = NULL + else: + #assert c_node.type == tree.XML_TEXT_NODE or c_node.type == tree.XML_CDATA_SECTION_NODE, "invalid node type" + # may be tail text or normal text + value = funicode(c_node.content) + c_element = _previousElement(c_node) + is_tail = c_element is not NULL + + if not context._build_smart_strings: + return value + + if c_element is NULL: + # non-tail text or attribute text + c_element = c_node.parent + while c_element is not NULL and not _isElement(c_element): + c_element = c_element.parent + + if c_element is not NULL: + parent = _instantiateElementFromXPath(c_element, doc, context) + + return _elementStringResultFactory( + value, parent, attrname, is_tail) + +################################################################################ +# callbacks for XPath/XSLT extension functions + +cdef void _extension_function_call(_BaseContext context, function, + xpath.xmlXPathParserContext* ctxt, int nargs) noexcept: + cdef _Document doc + cdef xpath.xmlXPathObject* obj + cdef list args + cdef int i + doc = context._doc + try: + args = [] + for i in range(nargs): + obj = xpath.valuePop(ctxt) + try: + o = _unwrapXPathObject(obj, doc, context) + finally: + _freeXPathObject(obj) + args.append(o) + args.reverse() + + res = function(context, *args) + # wrap result for XPath consumption + obj = _wrapXPathObject(res, doc, context) + # prevent Python from deallocating elements handed to libxml2 + context._hold(res) + xpath.valuePush(ctxt, obj) + except: + xpath.xmlXPathErr(ctxt, xpath.XPATH_EXPR_ERROR) + context._exc._store_raised() + finally: + return # swallow any further exceptions + +# lookup the function by name and call it + +cdef void _xpath_function_call(xpath.xmlXPathParserContext* ctxt, + int nargs) noexcept with gil: + cdef _BaseContext context + cdef xpath.xmlXPathContext* rctxt = ctxt.context + context = <_BaseContext> rctxt.userData + try: + function = context._find_cached_function(rctxt.functionURI, rctxt.function) + if function is not None: + _extension_function_call(context, function, ctxt, nargs) + else: + xpath.xmlXPathErr(ctxt, xpath.XPATH_UNKNOWN_FUNC_ERROR) + context._exc._store_exception(XPathFunctionError( + f"XPath function '{_namespacedNameFromNsName(rctxt.functionURI, rctxt.function)}' not found")) + except: + # may not be the right error, but we need to tell libxml2 *something* + xpath.xmlXPathErr(ctxt, xpath.XPATH_UNKNOWN_FUNC_ERROR) + context._exc._store_raised() + finally: + return # swallow any further exceptions diff --git a/intelliaide/vendor/lxml/html/ElementSoup.py b/intelliaide/vendor/lxml/html/ElementSoup.py new file mode 100644 index 0000000..c35365d --- /dev/null +++ b/intelliaide/vendor/lxml/html/ElementSoup.py @@ -0,0 +1,10 @@ +__doc__ = """Legacy interface to the BeautifulSoup HTML parser. +""" + +__all__ = ["parse", "convert_tree"] + +from .soupparser import convert_tree, parse as _parse + +def parse(file, beautifulsoup=None, makeelement=None): + root = _parse(file, beautifulsoup=beautifulsoup, makeelement=makeelement) + return root.getroot() diff --git a/intelliaide/vendor/lxml/html/__init__.py b/intelliaide/vendor/lxml/html/__init__.py new file mode 100644 index 0000000..2cee9f4 --- /dev/null +++ b/intelliaide/vendor/lxml/html/__init__.py @@ -0,0 +1,1927 @@ +# Copyright (c) 2004 Ian Bicking. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# 3. Neither the name of Ian Bicking nor the names of its contributors may +# be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL IAN BICKING OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""The ``lxml.html`` tool set for HTML handling. +""" + + +__all__ = [ + 'document_fromstring', 'fragment_fromstring', 'fragments_fromstring', 'fromstring', + 'tostring', 'Element', 'defs', 'open_in_browser', 'submit_form', + 'find_rel_links', 'find_class', 'make_links_absolute', + 'resolve_base_href', 'iterlinks', 'rewrite_links', 'parse'] + + +import copy +import re + +from collections.abc import MutableMapping, MutableSet +from functools import partial +from urllib.parse import urljoin + +from .. import etree +from . import defs +from ._setmixin import SetMixin + + +def __fix_docstring(s): + # TODO: remove and clean up doctests + if not s: + return s + sub = re.compile(r"^(\s*)u'", re.M).sub + return sub(r"\1'", s) + + +XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml" + +_rel_links_xpath = etree.XPath("descendant-or-self::a[@rel]|descendant-or-self::x:a[@rel]", + namespaces={'x':XHTML_NAMESPACE}) +_options_xpath = etree.XPath("descendant-or-self::option|descendant-or-self::x:option", + namespaces={'x':XHTML_NAMESPACE}) +_forms_xpath = etree.XPath("descendant-or-self::form|descendant-or-self::x:form", + namespaces={'x':XHTML_NAMESPACE}) +#_class_xpath = etree.XPath(r"descendant-or-self::*[regexp:match(@class, concat('\b', $class_name, '\b'))]", {'regexp': 'http://exslt.org/regular-expressions'}) +_class_xpath = etree.XPath("descendant-or-self::*[@class and contains(concat(' ', normalize-space(@class), ' '), concat(' ', $class_name, ' '))]") +_id_xpath = etree.XPath("descendant-or-self::*[@id=$id]") +_collect_string_content = etree.XPath("string()", smart_strings=False) +_iter_css_urls = re.compile(r'url\(('+'["][^"]*["]|'+"['][^']*[']|"+r'[^)]*)\)', re.I).finditer +_iter_css_imports = re.compile(r'@import "(.*?)"').finditer +_label_xpath = etree.XPath("//label[@for=$id]|//x:label[@for=$id]", + namespaces={'x':XHTML_NAMESPACE}) +_archive_re = re.compile(r'[^ ]+') +_parse_meta_refresh_url = re.compile( + r'[^;=]*;\s*(?:url\s*=\s*)?(?P.*)$', re.I).search + + +def _unquote_match(s, pos): + if s[:1] == '"' and s[-1:] == '"' or s[:1] == "'" and s[-1:] == "'": + return s[1:-1], pos+1 + else: + return s,pos + + +def _transform_result(typ, result): + """Convert the result back into the input type. + """ + if issubclass(typ, bytes): + return tostring(result, encoding='utf-8') + elif issubclass(typ, str): + return tostring(result, encoding='unicode') + else: + return result + + +def _nons(tag): + if isinstance(tag, str): + if tag[0] == '{' and tag[1:len(XHTML_NAMESPACE)+1] == XHTML_NAMESPACE: + return tag.split('}')[-1] + return tag + + +class Classes(MutableSet): + """Provides access to an element's class attribute as a set-like collection. + Usage:: + + >>> el = fromstring('') + >>> classes = el.classes # or: classes = Classes(el.attrib) + >>> classes |= ['block', 'paragraph'] + >>> el.get('class') + 'hidden large block paragraph' + >>> classes.toggle('hidden') + False + >>> el.get('class') + 'large block paragraph' + >>> classes -= ('some', 'classes', 'block') + >>> el.get('class') + 'large paragraph' + """ + def __init__(self, attributes): + self._attributes = attributes + self._get_class_value = partial(attributes.get, 'class', '') + + def add(self, value): + """ + Add a class. + + This has no effect if the class is already present. + """ + if not value or re.search(r'\s', value): + raise ValueError("Invalid class name: %r" % value) + classes = self._get_class_value().split() + if value in classes: + return + classes.append(value) + self._attributes['class'] = ' '.join(classes) + + def discard(self, value): + """ + Remove a class if it is currently present. + + If the class is not present, do nothing. + """ + if not value or re.search(r'\s', value): + raise ValueError("Invalid class name: %r" % value) + classes = [name for name in self._get_class_value().split() + if name != value] + if classes: + self._attributes['class'] = ' '.join(classes) + elif 'class' in self._attributes: + del self._attributes['class'] + + def remove(self, value): + """ + Remove a class; it must currently be present. + + If the class is not present, raise a KeyError. + """ + if not value or re.search(r'\s', value): + raise ValueError("Invalid class name: %r" % value) + super().remove(value) + + def __contains__(self, name): + classes = self._get_class_value() + return name in classes and name in classes.split() + + def __iter__(self): + return iter(self._get_class_value().split()) + + def __len__(self): + return len(self._get_class_value().split()) + + # non-standard methods + + def update(self, values): + """ + Add all names from 'values'. + """ + classes = self._get_class_value().split() + extended = False + for value in values: + if value not in classes: + classes.append(value) + extended = True + if extended: + self._attributes['class'] = ' '.join(classes) + + def toggle(self, value): + """ + Add a class name if it isn't there yet, or remove it if it exists. + + Returns true if the class was added (and is now enabled) and + false if it was removed (and is now disabled). + """ + if not value or re.search(r'\s', value): + raise ValueError("Invalid class name: %r" % value) + classes = self._get_class_value().split() + try: + classes.remove(value) + enabled = False + except ValueError: + classes.append(value) + enabled = True + if classes: + self._attributes['class'] = ' '.join(classes) + else: + del self._attributes['class'] + return enabled + + +class HtmlMixin: + + def set(self, key, value=None): + """set(self, key, value=None) + + Sets an element attribute. If no value is provided, or if the value is None, + creates a 'boolean' attribute without value, e.g. "
" + for ``form.set('novalidate')``. + """ + super().set(key, value) + + @property + def classes(self): + """ + A set-like wrapper around the 'class' attribute. + """ + return Classes(self.attrib) + + @classes.setter + def classes(self, classes): + assert isinstance(classes, Classes) # only allow "el.classes |= ..." etc. + value = classes._get_class_value() + if value: + self.set('class', value) + elif self.get('class') is not None: + del self.attrib['class'] + + @property + def base_url(self): + """ + Returns the base URL, given when the page was parsed. + + Use with ``urlparse.urljoin(el.base_url, href)`` to get + absolute URLs. + """ + return self.getroottree().docinfo.URL + + @property + def forms(self): + """ + Return a list of all the forms + """ + return _forms_xpath(self) + + @property + def body(self): + """ + Return the element. Can be called from a child element + to get the document's head. + """ + for element in self.getroottree().iter("body", f"{{{XHTML_NAMESPACE}}}body"): + return element + return None + + @property + def head(self): + """ + Returns the element. Can be called from a child + element to get the document's head. + """ + for element in self.getroottree().iter("head", f"{{{XHTML_NAMESPACE}}}head"): + return element + return None + + @property + def label(self): + """ + Get or set any