|
7 | 7 | import socket |
8 | 8 | import logging |
9 | 9 | import mimetypes |
| 10 | +from urllib.parse import urlparse |
10 | 11 | from time import time |
11 | 12 | from typing import Any, Iterable, Optional, Tuple, Union, IO |
12 | 13 |
|
@@ -97,8 +98,12 @@ def _mail_recipient( |
97 | 98 | smtp_user = config.get('smtp.user') |
98 | 99 | smtp_password = config.get('smtp.password') |
99 | 100 |
|
| 101 | + host, port = _parse_smtp_server(smtp_server) |
| 102 | + if not host: |
| 103 | + raise MailerException('SMTP server hostname is not configured') |
| 104 | + |
100 | 105 | try: |
101 | | - smtp_connection = smtplib.SMTP(smtp_server) |
| 106 | + smtp_connection = smtplib.SMTP(host, port) |
102 | 107 | except (socket.error, smtplib.SMTPConnectError) as e: |
103 | 108 | log.exception(e) |
104 | 109 | raise MailerException('SMTP server could not be connected to: "%s" %s' |
@@ -310,3 +315,23 @@ def verify_reset_link(user: model.User, key: Optional[str]) -> bool: |
310 | 315 | if not user.reset_key or len(user.reset_key) < 5: |
311 | 316 | return False |
312 | 317 | return key.strip() == user.reset_key |
| 318 | + |
| 319 | + |
| 320 | +def _parse_smtp_server(smtp_server: str) -> tuple[str | None, int]: |
| 321 | + """Parse SMTP server that may include port. |
| 322 | +
|
| 323 | + Examples: |
| 324 | + 'smtp.example.com' -> ('smtp.example.com', 0) |
| 325 | + 'smtp.example.com:587' -> ('smtp.example.com', 587) |
| 326 | + '[::1]:587' -> ('::1', 587) |
| 327 | + """ |
| 328 | + default_port = 0 |
| 329 | + |
| 330 | + if "://" not in smtp_server: |
| 331 | + smtp_server = f"smtp://{smtp_server}" |
| 332 | + |
| 333 | + parsed = urlparse(smtp_server) |
| 334 | + host = parsed.hostname |
| 335 | + port = parsed.port or default_port |
| 336 | + |
| 337 | + return host, port |
0 commit comments