aprf-reader 0.6.0__tar.gz → 0.6.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.
- {aprf_reader-0.6.0/src/aprf_reader.egg-info → aprf_reader-0.6.1}/PKG-INFO +2 -2
- {aprf_reader-0.6.0 → aprf_reader-0.6.1}/README.md +1 -1
- {aprf_reader-0.6.0 → aprf_reader-0.6.1}/pyproject.toml +1 -1
- {aprf_reader-0.6.0 → aprf_reader-0.6.1}/src/aprf_reader/__init__.py +1 -1
- {aprf_reader-0.6.0 → aprf_reader-0.6.1}/src/aprf_reader/imap.py +11 -3
- {aprf_reader-0.6.0 → aprf_reader-0.6.1/src/aprf_reader.egg-info}/PKG-INFO +2 -2
- {aprf_reader-0.6.0 → aprf_reader-0.6.1}/tests/test_imap.py +15 -2
- {aprf_reader-0.6.0 → aprf_reader-0.6.1}/LICENSE +0 -0
- {aprf_reader-0.6.0 → aprf_reader-0.6.1}/setup.cfg +0 -0
- {aprf_reader-0.6.0 → aprf_reader-0.6.1}/src/aprf_reader/cli.py +0 -0
- {aprf_reader-0.6.0 → aprf_reader-0.6.1}/src/aprf_reader/load.py +0 -0
- {aprf_reader-0.6.0 → aprf_reader-0.6.1}/src/aprf_reader/render.py +0 -0
- {aprf_reader-0.6.0 → aprf_reader-0.6.1}/src/aprf_reader/report.py +0 -0
- {aprf_reader-0.6.0 → aprf_reader-0.6.1}/src/aprf_reader/summary.py +0 -0
- {aprf_reader-0.6.0 → aprf_reader-0.6.1}/src/aprf_reader.egg-info/SOURCES.txt +0 -0
- {aprf_reader-0.6.0 → aprf_reader-0.6.1}/src/aprf_reader.egg-info/dependency_links.txt +0 -0
- {aprf_reader-0.6.0 → aprf_reader-0.6.1}/src/aprf_reader.egg-info/entry_points.txt +0 -0
- {aprf_reader-0.6.0 → aprf_reader-0.6.1}/src/aprf_reader.egg-info/requires.txt +0 -0
- {aprf_reader-0.6.0 → aprf_reader-0.6.1}/src/aprf_reader.egg-info/top_level.txt +0 -0
- {aprf_reader-0.6.0 → aprf_reader-0.6.1}/tests/test_reader.py +0 -0
- {aprf_reader-0.6.0 → aprf_reader-0.6.1}/tests/test_web_parity.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aprf-reader
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Summarise APRF (Aggregate Performance Reporting) inbox placement reports.
|
|
5
5
|
License: MIT
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -79,7 +79,7 @@ marked read, moved or deleted. It reads messages whose subject contains
|
|
|
79
79
|
from the `APRF_IMAP_PASSWORD` environment variable, or it asks. Gmail and
|
|
80
80
|
Outlook.com need an app password, not your account password, and a Gmail
|
|
81
81
|
label is read as a folder of the same name. The connection uses TLS on port
|
|
82
|
-
993; give another port as `--imap host:port`. Files it skips are listed
|
|
82
|
+
993 and checks the server's certificate; give another port as `--imap host:port`. Files it skips are listed
|
|
83
83
|
on stderr and at the end of the summary, each with a reason.
|
|
84
84
|
|
|
85
85
|
Each stream (provider, signing domain, selector) gets a table with one row per
|
|
@@ -66,7 +66,7 @@ marked read, moved or deleted. It reads messages whose subject contains
|
|
|
66
66
|
from the `APRF_IMAP_PASSWORD` environment variable, or it asks. Gmail and
|
|
67
67
|
Outlook.com need an app password, not your account password, and a Gmail
|
|
68
68
|
label is read as a folder of the same name. The connection uses TLS on port
|
|
69
|
-
993; give another port as `--imap host:port`. Files it skips are listed
|
|
69
|
+
993 and checks the server's certificate; give another port as `--imap host:port`. Files it skips are listed
|
|
70
70
|
on stderr and at the end of the summary, each with a reason.
|
|
71
71
|
|
|
72
72
|
Each stream (provider, signing domain, selector) gets a table with one row per
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import imaplib
|
|
6
6
|
import re
|
|
7
|
+
import ssl
|
|
7
8
|
from collections.abc import Iterator
|
|
8
9
|
from datetime import date
|
|
9
10
|
|
|
@@ -26,6 +27,12 @@ def _check(typ: str, data: list, what: str) -> list:
|
|
|
26
27
|
return data
|
|
27
28
|
|
|
28
29
|
|
|
30
|
+
def _text(e: Exception) -> str:
|
|
31
|
+
# imaplib puts the server's reply bytes in the exception as they came
|
|
32
|
+
a = e.args[0] if e.args else ""
|
|
33
|
+
return a.decode(errors="replace") if isinstance(a, bytes) else str(e)
|
|
34
|
+
|
|
35
|
+
|
|
29
36
|
def _quote(s: str) -> str:
|
|
30
37
|
return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
|
31
38
|
|
|
@@ -43,16 +50,17 @@ def fetch(host: str, user: str, password: str, folder: str = "INBOX", port: int
|
|
|
43
50
|
|
|
44
51
|
The folder is opened read-only and bodies are fetched with BODY.PEEK, so no
|
|
45
52
|
message is marked read, moved or deleted."""
|
|
46
|
-
|
|
53
|
+
# imaplib's own default context checks neither the certificate nor the host name
|
|
54
|
+
m = conn or imaplib.IMAP4_SSL(host, port, ssl_context=ssl.create_default_context(), timeout=60)
|
|
47
55
|
try:
|
|
48
56
|
try:
|
|
49
57
|
_check(*m.login(user, password), "login")
|
|
50
58
|
except imaplib.IMAP4.error as e:
|
|
51
|
-
raise ImapError(f"login failed: {e}") from None
|
|
59
|
+
raise ImapError(f"login failed: {_text(e)}") from None
|
|
52
60
|
try:
|
|
53
61
|
_check(*m.select(_quote(folder), readonly=True), f"opening folder {folder}")
|
|
54
62
|
except imaplib.IMAP4.error as e:
|
|
55
|
-
raise ImapError(f"opening folder {folder} failed: {e}") from None
|
|
63
|
+
raise ImapError(f"opening folder {folder} failed: {_text(e)}") from None
|
|
56
64
|
(ids,) = _check(*m.uid("SEARCH", *criteria(since, every)), "search")
|
|
57
65
|
uids = (ids or b"").split()
|
|
58
66
|
if len(uids) > MAX_MESSAGES:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aprf-reader
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Summarise APRF (Aggregate Performance Reporting) inbox placement reports.
|
|
5
5
|
License: MIT
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -79,7 +79,7 @@ marked read, moved or deleted. It reads messages whose subject contains
|
|
|
79
79
|
from the `APRF_IMAP_PASSWORD` environment variable, or it asks. Gmail and
|
|
80
80
|
Outlook.com need an app password, not your account password, and a Gmail
|
|
81
81
|
label is read as a folder of the same name. The connection uses TLS on port
|
|
82
|
-
993; give another port as `--imap host:port`. Files it skips are listed
|
|
82
|
+
993 and checks the server's certificate; give another port as `--imap host:port`. Files it skips are listed
|
|
83
83
|
on stderr and at the end of the summary, each with a reason.
|
|
84
84
|
|
|
85
85
|
Each stream (provider, signing domain, selector) gets a table with one row per
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import gzip
|
|
2
2
|
import imaplib
|
|
3
3
|
import json
|
|
4
|
+
import ssl
|
|
4
5
|
from datetime import date
|
|
5
6
|
|
|
6
7
|
import pytest
|
|
@@ -87,7 +88,7 @@ def test_fetched_mail_read_like_an_mbox():
|
|
|
87
88
|
|
|
88
89
|
def test_cli_imap(monkeypatch, capsys):
|
|
89
90
|
fake = FakeImap(mails(2))
|
|
90
|
-
monkeypatch.setattr(imaplib, "IMAP4_SSL", lambda host, port, timeout: fake)
|
|
91
|
+
monkeypatch.setattr(imaplib, "IMAP4_SSL", lambda host, port, ssl_context, timeout: fake)
|
|
91
92
|
monkeypatch.setenv("APRF_IMAP_PASSWORD", "right")
|
|
92
93
|
assert cli.main(["--imap", "imap.example.com", "--user", "u", "--folder", "APRF", "--json"]) == 0
|
|
93
94
|
out, err = capsys.readouterr()
|
|
@@ -96,10 +97,22 @@ def test_cli_imap(monkeypatch, capsys):
|
|
|
96
97
|
|
|
97
98
|
|
|
98
99
|
def test_cli_imap_failure_and_usage(monkeypatch, capsys):
|
|
99
|
-
monkeypatch.setattr(imaplib, "IMAP4_SSL", lambda host, port, timeout: FakeImap({}))
|
|
100
|
+
monkeypatch.setattr(imaplib, "IMAP4_SSL", lambda host, port, ssl_context, timeout: FakeImap({}))
|
|
100
101
|
monkeypatch.setenv("APRF_IMAP_PASSWORD", "wrong")
|
|
101
102
|
assert cli.main(["--imap", "h", "--user", "u"]) == 2
|
|
102
103
|
assert "login failed" in capsys.readouterr().err
|
|
103
104
|
for argv in ([], ["--imap", "h"], ["--imap", "h:x", "--user", "u"]):
|
|
104
105
|
with pytest.raises(SystemExit):
|
|
105
106
|
cli.main(argv)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_certificate_is_checked(monkeypatch):
|
|
110
|
+
seen = {}
|
|
111
|
+
|
|
112
|
+
def connect(host, port, ssl_context, timeout):
|
|
113
|
+
seen["ctx"] = ssl_context
|
|
114
|
+
return FakeImap({})
|
|
115
|
+
|
|
116
|
+
monkeypatch.setattr(imaplib, "IMAP4_SSL", connect)
|
|
117
|
+
list(fetch("h", "u", "right"))
|
|
118
|
+
assert seen["ctx"].verify_mode == ssl.CERT_REQUIRED and seen["ctx"].check_hostname
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|