From e864ff419532c5fff825fdf47d85ad97539e7748 Mon Sep 17 00:00:00 2001 From: LucienRay Date: Fri, 6 Jun 2025 14:58:32 +0800 Subject: [PATCH 1/7] Add proxy prototype. --- tldr.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tldr.py b/tldr.py index 161e960..20db44c 100755 --- a/tldr.py +++ b/tldr.py @@ -11,7 +11,7 @@ from io import BytesIO from typing import List, Optional, Tuple, Union from urllib.parse import quote -from urllib.request import urlopen, Request +from urllib.request import urlopen, Request, ProxyHandler, build_opener, install_opener from urllib.error import HTTPError, URLError from termcolor import colored import ssl @@ -648,6 +648,11 @@ def create_parser() -> ArgumentParser: action="store_true", help='Display longform options over shortform') + parser.add_argument('-x', '--proxy', + default=None, + type=str, + help='Proxy') + parser.add_argument( 'command', type=str, nargs='*', help="command to lookup", metavar='command' ).complete = {"bash": "shtab_tldr_cmd_list", "zsh": "shtab_tldr_cmd_list"} @@ -689,6 +694,12 @@ def main() -> None: if options.color is False: os.environ["FORCE_COLOR"] = "true" + if options.proxy != None: + proxy = ProxyHandler({'http': f'{options.proxy}', + 'https': f'{options.proxy}'}) + opener = build_opener(proxy) + install_opener(opener) + if options.update: update_cache(language=options.language) return From 57440bfc88582b453203d2212a49b042a22ea707 Mon Sep 17 00:00:00 2001 From: LucienRay Date: Fri, 6 Jun 2025 15:33:28 +0800 Subject: [PATCH 2/7] Improve proxy option help message --- tldr.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tldr.py b/tldr.py index 20db44c..ca460ae 100755 --- a/tldr.py +++ b/tldr.py @@ -651,7 +651,9 @@ def create_parser() -> ArgumentParser: parser.add_argument('-x', '--proxy', default=None, type=str, - help='Proxy') + help='Specify a proxy server address. When a proxy is used, ' + 'only HTTPS connections will be routed through it. ' + 'Example: https://user:pass@host:port.') parser.add_argument( 'command', type=str, nargs='*', help="command to lookup", metavar='command' From 93369315884add33ab40c5c394799f56db26d349 Mon Sep 17 00:00:00 2001 From: LucienRay Date: Fri, 6 Jun 2025 15:46:50 +0800 Subject: [PATCH 3/7] Add proxy format validation --- tldr.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tldr.py b/tldr.py index ca460ae..d26ab0f 100755 --- a/tldr.py +++ b/tldr.py @@ -563,6 +563,15 @@ def clear_cache(language: Optional[List[str]] = None) -> None: else: print(f"No cache directory found for language {language}") +def set_proxy(proxy: str) -> None: + pattern = re.compile( + r"^https:\/\/((?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,6}|(?:\d{1,3}\.){3}\d{1,3}):(\d{1,5})$") + if (not pattern.match(proxy)): + sys.exit("Error: Invalid proxy format. Expected 'https://host:port' or 'https://ip_address:port'.") + proxyHandler = ProxyHandler({'http': f'{proxy}', + 'https': f'{proxy}'}) + opener = build_opener(proxyHandler) + install_opener(opener) def create_parser() -> ArgumentParser: parser = ArgumentParser( @@ -697,10 +706,7 @@ def main() -> None: os.environ["FORCE_COLOR"] = "true" if options.proxy != None: - proxy = ProxyHandler({'http': f'{options.proxy}', - 'https': f'{options.proxy}'}) - opener = build_opener(proxy) - install_opener(opener) + set_proxy(options.proxy) if options.update: update_cache(language=options.language) From 8db06d25648f1560010cbcb1d1ecbfbdd7322d53 Mon Sep 17 00:00:00 2001 From: LucienRay Date: Fri, 6 Jun 2025 15:58:09 +0800 Subject: [PATCH 4/7] Improve proxy option help message --- tldr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tldr.py b/tldr.py index d26ab0f..bb3742b 100755 --- a/tldr.py +++ b/tldr.py @@ -662,7 +662,7 @@ def create_parser() -> ArgumentParser: type=str, help='Specify a proxy server address. When a proxy is used, ' 'only HTTPS connections will be routed through it. ' - 'Example: https://user:pass@host:port.') + 'Example: https://host:port.') parser.add_argument( 'command', type=str, nargs='*', help="command to lookup", metavar='command' From c416ab6e355346290ed1c79eee06e4e9934b85f4 Mon Sep 17 00:00:00 2001 From: LucienRay Date: Fri, 6 Jun 2025 16:16:06 +0800 Subject: [PATCH 5/7] Add proxy validation and reachability check --- tldr.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/tldr.py b/tldr.py index bb3742b..5a95a1a 100755 --- a/tldr.py +++ b/tldr.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # PYTHON_ARGCOMPLETE_OK - +import socket import sys import os import re @@ -10,7 +10,7 @@ from datetime import datetime from io import BytesIO from typing import List, Optional, Tuple, Union -from urllib.parse import quote +from urllib.parse import quote, urlparse from urllib.request import urlopen, Request, ProxyHandler, build_opener, install_opener from urllib.error import HTTPError, URLError from termcolor import colored @@ -563,13 +563,29 @@ def clear_cache(language: Optional[List[str]] = None) -> None: else: print(f"No cache directory found for language {language}") +def check_proxy(proxy: str) -> None: + if not proxy.startswith("https://") or ":" not in proxy: + sys.exit("Error: Invalid proxy format. Expected 'https://host:port'.") + + parsed = urlparse(proxy) + host = parsed.hostname + port = parsed.port + + if not host or not port: + sys.exit(f"Error: Could not extract host/port from proxy URL: {proxy}") + + try: + sock = socket.create_connection((host, port), timeout=10) + sock.close() + except socket.timeout: + sys.exit(f"Error: Proxy server {host}:{port} connection timed out after 10 seconds.") + except socket.error as e: + sys.exit(f"Error: Could not connect to proxy server {host}:{port}. Error: {e}") + def set_proxy(proxy: str) -> None: - pattern = re.compile( - r"^https:\/\/((?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,6}|(?:\d{1,3}\.){3}\d{1,3}):(\d{1,5})$") - if (not pattern.match(proxy)): - sys.exit("Error: Invalid proxy format. Expected 'https://host:port' or 'https://ip_address:port'.") + check_proxy(proxy) proxyHandler = ProxyHandler({'http': f'{proxy}', - 'https': f'{proxy}'}) + 'https': f'{proxy}'}) opener = build_opener(proxyHandler) install_opener(opener) From 5c0b7d54bf244736cfcfce327b91e495103812e2 Mon Sep 17 00:00:00 2001 From: LucienRay Date: Sat, 7 Jun 2025 01:50:49 +0800 Subject: [PATCH 6/7] Fix linting issues --- tldr.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tldr.py b/tldr.py index 5a95a1a..7e68fdd 100755 --- a/tldr.py +++ b/tldr.py @@ -563,6 +563,7 @@ def clear_cache(language: Optional[List[str]] = None) -> None: else: print(f"No cache directory found for language {language}") + def check_proxy(proxy: str) -> None: if not proxy.startswith("https://") or ":" not in proxy: sys.exit("Error: Invalid proxy format. Expected 'https://host:port'.") @@ -578,10 +579,12 @@ def check_proxy(proxy: str) -> None: sock = socket.create_connection((host, port), timeout=10) sock.close() except socket.timeout: - sys.exit(f"Error: Proxy server {host}:{port} connection timed out after 10 seconds.") + sys.exit(f"Error: Proxy server {host}:{port} " + "connection timed out after 10 seconds.") except socket.error as e: sys.exit(f"Error: Could not connect to proxy server {host}:{port}. Error: {e}") + def set_proxy(proxy: str) -> None: check_proxy(proxy) proxyHandler = ProxyHandler({'http': f'{proxy}', @@ -589,6 +592,7 @@ def set_proxy(proxy: str) -> None: opener = build_opener(proxyHandler) install_opener(opener) + def create_parser() -> ArgumentParser: parser = ArgumentParser( prog="tldr", @@ -721,7 +725,7 @@ def main() -> None: if options.color is False: os.environ["FORCE_COLOR"] = "true" - if options.proxy != None: + if options.proxy is not None: set_proxy(options.proxy) if options.update: From 70964d6a77fa0a71247bb737dd0e4d40390adb9c Mon Sep 17 00:00:00 2001 From: LucienRay Date: Sat, 7 Jun 2025 03:12:33 +0800 Subject: [PATCH 7/7] Fix linting issues --- tldr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tldr.py b/tldr.py index 7e68fdd..fcaee6f 100755 --- a/tldr.py +++ b/tldr.py @@ -580,7 +580,7 @@ def check_proxy(proxy: str) -> None: sock.close() except socket.timeout: sys.exit(f"Error: Proxy server {host}:{port} " - "connection timed out after 10 seconds.") + "connection timed out after 10 seconds.") except socket.error as e: sys.exit(f"Error: Could not connect to proxy server {host}:{port}. Error: {e}")