everalgo-parser 0.2.0__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/.gitignore +4 -0
  2. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/CHANGELOG.md +14 -7
  3. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/PKG-INFO +22 -14
  4. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/README.md +19 -11
  5. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/docs/architecture.md +1 -1
  6. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/pyproject.toml +3 -3
  7. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/__init__.py +8 -0
  8. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/_utils.py +3 -3
  9. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/document.py +5 -0
  10. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/url.py +5 -5
  11. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/LICENSE +0 -0
  12. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/audio.py +0 -0
  13. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/image.py +0 -0
  14. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/prompts/__init__.py +0 -0
  15. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/prompts/en/__init__.py +0 -0
  16. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/prompts/en/audio.py +0 -0
  17. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/prompts/en/document.py +0 -0
  18. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/prompts/en/image.py +0 -0
  19. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/prompts/zh/__init__.py +0 -0
  20. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/prompts/zh/audio.py +0 -0
  21. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/prompts/zh/document.py +0 -0
  22. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/prompts/zh/image.py +0 -0
  23. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/py.typed +0 -0
  24. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/video.py +0 -0
  25. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.bmp +0 -0
  26. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.docx +0 -0
  27. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.eml +0 -0
  28. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.htm +0 -0
  29. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.html +0 -0
  30. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.jpg +0 -0
  31. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.pdf +0 -0
  32. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.png +0 -0
  33. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.pptx +0 -0
  34. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.svg +0 -0
  35. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.tiff +0 -0
  36. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.txt +0 -0
  37. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.wav +0 -0
  38. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.webp +0 -0
  39. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.xlsx +0 -0
  40. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/__init__.py +0 -0
  41. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_audio.py +0 -0
  42. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_dispatch.py +0 -0
  43. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_document.py +0 -0
  44. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_e2e_all_formats.py +0 -0
  45. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_e2e_pdf_openrouter.py +0 -0
  46. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_image.py +0 -0
  47. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_public_api.py +0 -0
  48. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_url.py +0 -0
  49. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_utils.py +0 -0
  50. {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_video.py +0 -0
@@ -56,3 +56,7 @@ local/
56
56
 
57
57
  # Claude Code session-local state (scheduled tasks, agent caches, locks)
58
58
  .claude/
59
+ benchmarks/claudecode
60
+
61
+ # Git worktrees (isolated workspaces)
62
+ .worktrees/
@@ -9,14 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
  ## [Unreleased]
10
10
  <!-- git-cliff-unreleased-end -->
11
11
 
12
+ ## [0.2.1] - 2026-06-15
13
+
14
+ ### Changed
15
+
16
+ - **License relicensed from MIT to Apache-2.0** as part of the pre-open-source security audit.
17
+
12
18
  ## [0.2.0] - 2026-05-27
13
19
 
14
20
  > First archived changelog. Entries below accumulated since the initial `0.1.0` PyPI release (published manually, without a git tag or per-package changelog), so this `0.2.0` section also consolidates the previously-unarchived `0.1.0` surface.
15
21
 
16
22
  ### Added
17
23
 
18
- - **PDF parser** — single multimodal LLM call, mirrors
19
- `evermemos-multimodal/src/parser/pdf/gemini_parser.py`.
24
+ - **PDF parser** — single multimodal LLM call, mirrors the upstream
25
+ internal multimodal PDF parser reference implementation.
20
26
  - **Image parser** — single LLM call for normal-ratio images; tall
21
27
  screenshots (height/width > 10) are split into overlapping vertical
22
28
  slices, OCR'd per slice, and merged with a second LLM call using
@@ -41,8 +47,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
41
47
  unknown) → HTML handler with Open Graph / Twitter Card / `<meta>` tag
42
48
  extraction into `ParsedContent.metadata`. `file://` and other local-
43
49
  filesystem URIs are rejected (AGENTS.md §1). Inner modality recorded in
44
- `metadata["inner_modality"]`. Metadata-extractor schema lifted from
45
- `evermemos-opensource/src/common_utils/url_extractor.py`.
50
+ `metadata["inner_modality"]`. Metadata-extractor schema lifted from the
51
+ upstream internal URL extractor reference implementation.
46
52
  - **MIME-aware top-level dispatch** — `parser.aparse` now falls back to
47
53
  `mime` when `extension` is missing or `UNKNOWN`, so the canonical
48
54
  `RawFile(uri=..., mime="application/pdf")` example actually routes to
@@ -59,8 +65,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
59
65
 
60
66
  ### Notes
61
67
 
62
- - `video` submodule remains a stub — upstream evermemos-multimodal has no
63
- video implementation to port; backend selection pending an ADR.
68
+ - `video` submodule remains a stub — the upstream internal multimodal parser
69
+ has no video implementation to port; backend selection pending an ADR.
64
70
  - The parser layer deliberately omits retry / fallback / multi-key
65
71
  rotation / Prometheus metrics. Those are deployment concerns
66
72
  (ADR-012); algorithm-layer operators surface failures via `LLMError`
@@ -74,5 +80,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
74
80
  - Optional `cairosvg >= 2.7.0` via `[svg]` extra.
75
81
  - System: LibreOffice (`soffice` on PATH) for Office document parsing.
76
82
 
77
- [Unreleased]: https://github.com/EverMind-AI/EverAlgo/compare/everalgo-parser/v0.2.0...HEAD
83
+ [Unreleased]: https://github.com/EverMind-AI/EverAlgo/compare/everalgo-parser/v0.2.1...HEAD
84
+ [0.2.1]: https://github.com/EverMind-AI/EverAlgo/compare/everalgo-parser/v0.2.0...everalgo-parser/v0.2.1
78
85
  [0.2.0]: https://github.com/EverMind-AI/EverAlgo/releases/tag/everalgo-parser/v0.2.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: everalgo-parser
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: EverAlgo parser: multimodal raw-file parsing (image / audio / document / video / url) into ParsedContent.
5
5
  Project-URL: Homepage, https://github.com/EverMind-AI/EverAlgo
6
6
  Project-URL: Repository, https://github.com/EverMind-AI/EverAlgo
@@ -8,12 +8,12 @@ Project-URL: Issues, https://github.com/EverMind-AI/EverAlgo/issues
8
8
  Project-URL: Documentation, https://github.com/EverMind-AI/EverAlgo/tree/main/packages/everalgo-parser
9
9
  Project-URL: Changelog, https://github.com/EverMind-AI/EverAlgo/blob/main/packages/everalgo-parser/CHANGELOG.md
10
10
  Author-email: EverMind <Evermind@shanda.com>
11
- License-Expression: MIT
11
+ License-Expression: Apache-2.0
12
12
  License-File: LICENSE
13
13
  Keywords: everalgo,llm,multimodal,parsing
14
14
  Classifier: Development Status :: 3 - Alpha
15
15
  Classifier: Intended Audience :: Developers
16
- Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: License :: OSI Approved :: Apache Software License
17
17
  Classifier: Programming Language :: Python :: 3
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
@@ -29,33 +29,32 @@ Description-Content-Type: text/markdown
29
29
 
30
30
  # everalgo-parser
31
31
 
32
- Multimodal parsing — image / audio / document / video / url raw inputs into `ParsedContent`. Used by `everalgo-knowledge` for file ingestion and by evermem step 1 for inline parsing.
32
+ Multimodal parsing — image / audio / document / video / url raw inputs into `ParsedContent`. Used by `everalgo-knowledge` for file ingestion and by EverOS step 1 for inline parsing.
33
33
 
34
34
  See the umbrella project: [EverAlgo monorepo](../../README.md) and the architecture document at [`docs/concepts/architecture.md`](../../docs/concepts/architecture.md).
35
35
 
36
36
  ## Quick start
37
37
 
38
38
  ```python
39
- import everalgo
40
39
  from everalgo.llm.config import LLMConfig
41
40
  from everalgo.llm.providers.openai_compat import OpenAICompatClient
42
41
  from everalgo.parser import aparse, RawFile
43
42
 
44
- # Configure an LLM once (process-wide). The parser uses OpenAI-compatible
45
- # clients; OpenRouter is the reference deployment (Gemini multimodal via
46
- # OpenRouter passthrough).
47
- everalgo.configure(OpenAICompatClient(LLMConfig(
43
+ # Create an LLM client per call (or share one instance). The parser uses
44
+ # OpenAI-compatible clients; OpenRouter is the reference deployment
45
+ # (Gemini multimodal via OpenRouter passthrough).
46
+ client = OpenAICompatClient(LLMConfig(
48
47
  model="google/gemini-3-flash-preview",
49
48
  api_key="sk-or-v1-...",
50
49
  base_url="https://openrouter.ai/api/v1",
51
- )))
50
+ ))
52
51
 
53
52
  # Bytes-in: caller already hydrated the file.
54
- parsed = await aparse(RawFile(content=pdf_bytes, extension="pdf"))
53
+ parsed = await aparse(RawFile(content=pdf_bytes, extension="pdf"), llm=client)
55
54
  print(parsed.text)
56
55
 
57
56
  # URL-in: parser fetches over HTTP, then delegates to the HTML handler.
58
- parsed = await aparse(RawFile(uri="https://example.com/article"))
57
+ parsed = await aparse(RawFile(uri="https://example.com/article"), llm=client)
59
58
  print(parsed.metadata["title"], parsed.text[:500])
60
59
  ```
61
60
 
@@ -101,8 +100,17 @@ The parser detects `soffice` via `shutil.which("soffice")` and the canonical mac
101
100
  - The library is **stateless**: it never reads the filesystem and never owns business state. HTTP I/O (LLM calls, URL fetching) is explicitly allowed.
102
101
  - No retry / fallback / metrics inside operators — surface failures via `LLMError`, let the caller wrap.
103
102
 
103
+ ## Security notes for callers
104
+
105
+ `everalgo-parser` is a stateless library; production safety properties belong to the calling service. The points below are easy to miss and worth wiring into your integration:
106
+
107
+ - **SSRF — `url.aparse` fetches arbitrary HTTP(S) URIs.** `fetch_uri` rejects non-http(s) schemes (no `file://`, no `gopher://`) but does **not** block private / link-local / loopback IPs (e.g. `127.0.0.1`, `10.0.0.0/8`, `169.254.169.254` cloud metadata, IPv6 ULA). If callers may pass attacker-controlled URLs, resolve the hostname first and reject private ranges before invoking `aparse`, or front the parser with an egress proxy that does the filtering.
108
+ - **Decompression-bomb cap is set at import time.** `everalgo/parser/__init__.py` pins `PIL.Image.MAX_IMAGE_PIXELS = 100_000_000` (10000×10000) — Pillow will raise `DecompressionBombError` above that. Re-assign after import if your workload genuinely needs larger images.
109
+ - **Office conversion runs LibreOffice as a subprocess.** `document._convert_to_pdf_via_soffice` validates the `extension` parameter (alphanumeric, ≤8 chars) before writing the temp file, but the LibreOffice attack surface itself is large. Run the host with a non-root user and treat untrusted office docs as you would any other complex binary input.
110
+ - **`cairosvg` (optional `[svg]` extra) is LGPL-3.0-or-later.** EverAlgo itself is Apache-2.0. LGPLv3 is compatible with Apache-2.0 when `cairosvg` is consumed as an unmodified library through its public Python API (which is what the SVG path does); if you statically vendor or modify `cairosvg`, LGPLv3 §4 / §5 obligations apply to that derivative. Not installing the `[svg]` extra removes the dependency entirely.
111
+
104
112
  ## Reference
105
113
 
106
114
  - Architecture (definitive): [`docs/concepts/architecture.md`](../../docs/concepts/architecture.md)
107
- - Schema source for PDF / image / audio / document / html / email: `evermemos-multimodal` (tag `prod-20260306-0331-v1`).
108
- - Schema source for URL metadata extraction: `evermemos-opensource/src/common_utils/url_extractor.py`.
115
+ - Schema source for PDF / image / audio / document / html / email: upstream internal multimodal parser library (snapshot `prod-20260306-0331-v1`).
116
+ - Schema source for URL metadata extraction: upstream internal URL extractor reference implementation.
@@ -1,32 +1,31 @@
1
1
  # everalgo-parser
2
2
 
3
- Multimodal parsing — image / audio / document / video / url raw inputs into `ParsedContent`. Used by `everalgo-knowledge` for file ingestion and by evermem step 1 for inline parsing.
3
+ Multimodal parsing — image / audio / document / video / url raw inputs into `ParsedContent`. Used by `everalgo-knowledge` for file ingestion and by EverOS step 1 for inline parsing.
4
4
 
5
5
  See the umbrella project: [EverAlgo monorepo](../../README.md) and the architecture document at [`docs/concepts/architecture.md`](../../docs/concepts/architecture.md).
6
6
 
7
7
  ## Quick start
8
8
 
9
9
  ```python
10
- import everalgo
11
10
  from everalgo.llm.config import LLMConfig
12
11
  from everalgo.llm.providers.openai_compat import OpenAICompatClient
13
12
  from everalgo.parser import aparse, RawFile
14
13
 
15
- # Configure an LLM once (process-wide). The parser uses OpenAI-compatible
16
- # clients; OpenRouter is the reference deployment (Gemini multimodal via
17
- # OpenRouter passthrough).
18
- everalgo.configure(OpenAICompatClient(LLMConfig(
14
+ # Create an LLM client per call (or share one instance). The parser uses
15
+ # OpenAI-compatible clients; OpenRouter is the reference deployment
16
+ # (Gemini multimodal via OpenRouter passthrough).
17
+ client = OpenAICompatClient(LLMConfig(
19
18
  model="google/gemini-3-flash-preview",
20
19
  api_key="sk-or-v1-...",
21
20
  base_url="https://openrouter.ai/api/v1",
22
- )))
21
+ ))
23
22
 
24
23
  # Bytes-in: caller already hydrated the file.
25
- parsed = await aparse(RawFile(content=pdf_bytes, extension="pdf"))
24
+ parsed = await aparse(RawFile(content=pdf_bytes, extension="pdf"), llm=client)
26
25
  print(parsed.text)
27
26
 
28
27
  # URL-in: parser fetches over HTTP, then delegates to the HTML handler.
29
- parsed = await aparse(RawFile(uri="https://example.com/article"))
28
+ parsed = await aparse(RawFile(uri="https://example.com/article"), llm=client)
30
29
  print(parsed.metadata["title"], parsed.text[:500])
31
30
  ```
32
31
 
@@ -72,8 +71,17 @@ The parser detects `soffice` via `shutil.which("soffice")` and the canonical mac
72
71
  - The library is **stateless**: it never reads the filesystem and never owns business state. HTTP I/O (LLM calls, URL fetching) is explicitly allowed.
73
72
  - No retry / fallback / metrics inside operators — surface failures via `LLMError`, let the caller wrap.
74
73
 
74
+ ## Security notes for callers
75
+
76
+ `everalgo-parser` is a stateless library; production safety properties belong to the calling service. The points below are easy to miss and worth wiring into your integration:
77
+
78
+ - **SSRF — `url.aparse` fetches arbitrary HTTP(S) URIs.** `fetch_uri` rejects non-http(s) schemes (no `file://`, no `gopher://`) but does **not** block private / link-local / loopback IPs (e.g. `127.0.0.1`, `10.0.0.0/8`, `169.254.169.254` cloud metadata, IPv6 ULA). If callers may pass attacker-controlled URLs, resolve the hostname first and reject private ranges before invoking `aparse`, or front the parser with an egress proxy that does the filtering.
79
+ - **Decompression-bomb cap is set at import time.** `everalgo/parser/__init__.py` pins `PIL.Image.MAX_IMAGE_PIXELS = 100_000_000` (10000×10000) — Pillow will raise `DecompressionBombError` above that. Re-assign after import if your workload genuinely needs larger images.
80
+ - **Office conversion runs LibreOffice as a subprocess.** `document._convert_to_pdf_via_soffice` validates the `extension` parameter (alphanumeric, ≤8 chars) before writing the temp file, but the LibreOffice attack surface itself is large. Run the host with a non-root user and treat untrusted office docs as you would any other complex binary input.
81
+ - **`cairosvg` (optional `[svg]` extra) is LGPL-3.0-or-later.** EverAlgo itself is Apache-2.0. LGPLv3 is compatible with Apache-2.0 when `cairosvg` is consumed as an unmodified library through its public Python API (which is what the SVG path does); if you statically vendor or modify `cairosvg`, LGPLv3 §4 / §5 obligations apply to that derivative. Not installing the `[svg]` extra removes the dependency entirely.
82
+
75
83
  ## Reference
76
84
 
77
85
  - Architecture (definitive): [`docs/concepts/architecture.md`](../../docs/concepts/architecture.md)
78
- - Schema source for PDF / image / audio / document / html / email: `evermemos-multimodal` (tag `prod-20260306-0331-v1`).
79
- - Schema source for URL metadata extraction: `evermemos-opensource/src/common_utils/url_extractor.py`.
86
+ - Schema source for PDF / image / audio / document / html / email: upstream internal multimodal parser library (snapshot `prod-20260306-0331-v1`).
87
+ - Schema source for URL metadata extraction: upstream internal URL extractor reference implementation.
@@ -301,7 +301,7 @@ flowchart LR
301
301
 
302
302
  | 项 | 状态 | 备注 |
303
303
  |---|---|---|
304
- | `video.py` | stub | 上游 evermemos-multimodal **也没有**实现,Gemini Video vs Whisper+帧抽取 选型待 ADR |
304
+ | `video.py` | stub | 上游内部多模态解析库**也没有**实现,Gemini Video vs Whisper+帧抽取 选型待 ADR |
305
305
  | `audio` BMP-like 转码 | 没做 | mp3/wav 测过;m4a/amr/aiff/aac/ogg/flac 未在 e2e 覆盖,Gemini 兼容度未全测 |
306
306
  | `Office` 系统依赖动态检测降级 | 仅运行时报错 | 没有 import 时友好警告;`_find_soffice` 缺失就 `RuntimeError` |
307
307
  | `T1` 主条目其他 schema | 待 RESOLVED | 我们只 RESOLVED 了 `T1.parser` 子集(`ParsedContent` / `Modality` / `RawFile`) |
@@ -4,17 +4,17 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "everalgo-parser"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  description = "EverAlgo parser: multimodal raw-file parsing (image / audio / document / video / url) into ParsedContent."
9
9
  requires-python = ">=3.12"
10
- license = "MIT"
10
+ license = "Apache-2.0"
11
11
  readme = "README.md"
12
12
  authors = [{ name = "EverMind", email = "Evermind@shanda.com" }]
13
13
  keywords = ["llm", "multimodal", "parsing", "everalgo"]
14
14
  classifiers = [
15
15
  "Development Status :: 3 - Alpha",
16
16
  "Intended Audience :: Developers",
17
- "License :: OSI Approved :: MIT License",
17
+ "License :: OSI Approved :: Apache Software License",
18
18
  "Programming Language :: Python :: 3",
19
19
  "Programming Language :: Python :: 3.12",
20
20
  "Topic :: Scientific/Engineering :: Artificial Intelligence",
@@ -14,6 +14,7 @@ from typing import TYPE_CHECKING
14
14
  from urllib.parse import urlparse
15
15
 
16
16
  from asgiref.sync import async_to_sync
17
+ from PIL import Image
17
18
 
18
19
  from everalgo.parser import audio, document, image, url
19
20
  from everalgo.types import (
@@ -39,6 +40,13 @@ __all__ = [
39
40
  logger = logging.getLogger(__name__)
40
41
  logger.addHandler(logging.NullHandler())
41
42
 
43
+ # Explicit decompression-bomb cap (Pillow default raises ``DecompressionBombError``
44
+ # only at ~2x this threshold). 100M pixels = 10000x10000, ample for any realistic
45
+ # screenshot / scan while keeping a hostile caller from exhausting memory via a
46
+ # crafted PNG/TIFF. Applications can override by re-assigning
47
+ # ``PIL.Image.MAX_IMAGE_PIXELS`` after importing the parser.
48
+ Image.MAX_IMAGE_PIXELS = 100_000_000
49
+
42
50
 
43
51
  async def aparse(raw_file: RawFile, *, llm: LLMClient) -> ParsedContent:
44
52
  """Top-level dispatch: classify the input and route to a submodule.
@@ -6,8 +6,8 @@
6
6
  - ``clean_html_content`` — strict text extraction (drops all tags).
7
7
  - ``clean_html_for_llm`` — preserves structural tags inside ``<body>``,
8
8
  strips known-noise tags and useless attributes, returns HTML.
9
- - ``fetch_uri`` — HTTP(S) fetch for the URL parser. Adapted from
10
- ``evermemos-opensource/src/common_utils/url_extractor.py``.
9
+ - ``fetch_uri`` — HTTP(S) fetch for the URL parser. Adapted from an
10
+ upstream internal URL extractor reference implementation.
11
11
  """
12
12
 
13
13
  from __future__ import annotations
@@ -40,7 +40,7 @@ _DEFAULT_USER_AGENT = "Mozilla/5.0 (compatible; everalgo-parser/0.1; +https://ev
40
40
  async def fetch_uri(uri: str, *, timeout: float = 30.0) -> tuple[bytes, str]:
41
41
  """Fetch ``http``/``https`` URI and return ``(content_bytes, content_type)``.
42
42
 
43
- Adapted from ``evermemos-opensource/src/common_utils/url_extractor.py``;
43
+ Adapted from an upstream internal URL extractor reference implementation;
44
44
  rejects non-http(s) schemes per AGENTS.md §1 (no filesystem access).
45
45
 
46
46
  Parameters
@@ -399,6 +399,11 @@ async def _convert_to_pdf_via_soffice(
399
399
  content: bytes, extension: str, soffice: str
400
400
  ) -> bytes: # pragma: no cover (libreoffice binary, not in CI)
401
401
  """Run LibreOffice headless to convert bytes → PDF bytes."""
402
+ # Reject path-traversal / directory separators in `extension` so a hostile
403
+ # caller cannot escape the tempdir via e.g. ``extension="../../etc/passwd"``.
404
+ # `extension` is expected to be a short ASCII suffix (docx / xlsx / ...).
405
+ if not extension or not extension.isalnum() or len(extension) > 8:
406
+ raise ValueError(f"invalid extension for soffice conversion: {extension!r}")
402
407
  with tempfile.TemporaryDirectory() as tmpdir: # pragma: no cover
403
408
  in_path = Path(tmpdir) / f"input.{extension}" # pragma: no cover
404
409
  in_path.write_bytes(content) # pragma: no cover
@@ -18,10 +18,10 @@ The returned ``ParsedContent.modality`` is always ``Modality.URL`` so
18
18
  callers can distinguish "loaded from network" from "passed in as bytes";
19
19
  the inner modality is recorded in ``metadata["inner_modality"]``.
20
20
 
21
- Schema source for the metadata extractor:
22
- ``evermemos-opensource/src/common_utils/url_extractor.py``
23
- (``_extract_og_tags`` / ``_extract_twitter_tags`` / ``_extract_meta_tags`` /
24
- ``_extract_title`` / ``_extract_favicon``).
21
+ Schema source for the metadata extractor: an upstream internal URL
22
+ extractor reference implementation (``_extract_og_tags`` /
23
+ ``_extract_twitter_tags`` / ``_extract_meta_tags`` / ``_extract_title`` /
24
+ ``_extract_favicon``).
25
25
  """
26
26
 
27
27
  from __future__ import annotations
@@ -166,7 +166,7 @@ async def _dispatch_inner(
166
166
  raise ValueError(f"url.aparse: cannot dispatch fetched body for modality={modality.value!r}")
167
167
 
168
168
 
169
- # ---- metadata extraction (port from evermemos-opensource) ----
169
+ # ---- metadata extraction (port from upstream internal URL extractor) ----
170
170
 
171
171
 
172
172
  def _extract_metadata(html_text: str, *, base_url: str) -> dict[str, object]:
File without changes