On top of Amending my Offpunk redirection implementation, I have now fixed the issue where list:/// would get turned into list:/ URIs when there would be a redirection rule for just a wildcard netloc, such as one that for any URL purges the utm_source tracking query parameter. That was resulting in user’s inability to list any list.
I have introduced an exception that requires redirections to be explicitly applicable only to list: URIs in case of list URIs. This is not yet having consistency in UX if someone were to actually use it, but there is potential for adding special logic for lists to handle those redirections as aliases. I imagine it could even allow one to define aliases to files on the filesystem, without someone having to test if our file writing won’t ever replace a symbolic link existing in place of a list file.
BTW, I have updated the patches in the very first blog post (Experimentally expanding Offpunk browser Part 1 (nightly)) so that now they are actually applicable on the upstream.
Also BTW, in a sec I will also publish patches that this time will be applicable each directly to the upstream, introducing:
- fix/feature to allow hierarchical (slash-containing) names of lists, implemented as directories, (now published: Slash-hierarchical list names — my draft implementation for Offpunk)
- extended functionality of subscribe subcommand allowing subscription into a list then put into tour. (now published: Subscription into list rather than tour — Offpunk draft feature patch)
For executing the test on this change, I needed to eliminate a circular import of a constant dictionary of standard_ports for the few supported schemas, between offutils and netcache — I have moved it into offutils. That’s the first of the two commits that follow below.
From 1b2cd5e05cead70e200b4d086b4e4ace90a72c02 Mon Sep 17 00:00:00 2001
Date: Thu, 29 Jan 2026 18:49:24 +0100
Subject: [PATCH 1/2] Circular import fix: netcache.standard_ports -> offutils
---
netcache.py | 10 +---------
offutils.py | 15 ++++++++++++---
2 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/netcache.py b/netcache.py
index c1880f5..f1f8258 100755
--- a/netcache.py
+++ b/netcache.py
@@ -18,7 +18,7 @@ import gettext
import ansicat
import offutils
-from offutils import xdg, _LOCALE_DIR
+from offutils import xdg, _LOCALE_DIR, standard_ports
gettext.bindtextdomain('offpunk', _LOCALE_DIR)
gettext.textdomain('offpunk')
@@ -52,14 +52,6 @@ except (ModuleNotFoundError, ImportError):
_DO_HTTP = False
# This list is also used as a list of supported protocols
-standard_ports = {
- "gemini": 1965,
- "gopher": 70,
- "finger": 79,
- "http": 80,
- "https": 443,
- "spartan": 300,
-}
default_protocol = "gemini"
CRLF = "\r\n"
diff --git a/offutils.py b/offutils.py
index fc3851c..59f32a3 100644
--- a/offutils.py
+++ b/offutils.py
@@ -15,7 +15,6 @@ import gettext
import cert_migration
import create_preconfig
-import netcache
import netcache_migration
# We can later add some logic to decide this based on OS family/version if needed?
@@ -255,7 +254,7 @@ def looks_like_url(word):
port = parsed.port
scheme = word.split("://")[0]
mailto = word.startswith("mailto:")
- start = scheme in netcache.standard_ports
+ start = scheme in standard_ports
local = scheme in ["file", "list"]
if mailto:
return "@" in word
@@ -469,4 +468,14 @@ def looks_like_base64(src, baseurl):
imgurl = None
else:
imgurl = urllib.parse.urljoin(baseurl, imgname)
- return imgurl, imgdata
\ No newline at end of file
+ return imgurl, imgdata
+
+
+standard_ports = {
+ "gemini": 1965,
+ "gopher": 70,
+ "finger": 79,
+ "http": 80,
+ "https": 443,
+ "spartan": 300,
+}
--
2.52.0
From 3c4b7ef4677dd0174806b6438d68da7ecdfa52aa Mon Sep 17 00:00:00 2001
Date: Thu, 29 Jan 2026 18:50:42 +0100
Subject: [PATCH 2/2] Redirections regression fix: list URI failed when
wildcard
---
implementation_parts/redirection_classes.py | 12 ++++----
implementation_parts/redirections_stuff.py | 6 +++-
tests/redirections_test.py | 34 +++++++++++++++++++++
3 files changed, 45 insertions(+), 7 deletions(-)
create mode 100644 tests/redirections_test.py
diff --git a/implementation_parts/redirection_classes.py b/implementation_parts/redirection_classes.py
index 5cba31d..8acd6f8 100644
--- a/implementation_parts/redirection_classes.py
+++ b/implementation_parts/redirection_classes.py
@@ -1,8 +1,8 @@
from typing import NamedTuple, Literal
class RedirectionRewrite(NamedTuple):
- locator: str
- purge_query: any
+ locator: str = None
+ purge_query: set[str] = set()
def recover_line(self) -> str:
result = []
@@ -19,10 +19,10 @@ FrozenParamGlobDictEntry = tuple[str, FrozenTruthinessGlobPairs]
class RedirectionOrigin(NamedTuple):
netloc: str
- noport_toggle: bool
- pathpattern: FrozenTruthinessGlobPairs
- fragpattern: FrozenTruthinessGlobPairs
- parampatterns: tuple[FrozenParamGlobDictEntry, ...]
+ noport_toggle: bool = False
+ pathpattern: FrozenTruthinessGlobPairs = tuple()
+ fragpattern: FrozenTruthinessGlobPairs = tuple()
+ parampatterns: tuple[FrozenParamGlobDictEntry, ...] = tuple()
def recover_line(self):
result = [self.netloc]
diff --git a/implementation_parts/redirections_stuff.py b/implementation_parts/redirections_stuff.py
index 9a75c93..db8c5a5 100644
--- a/implementation_parts/redirections_stuff.py
+++ b/implementation_parts/redirections_stuff.py
@@ -23,9 +23,13 @@ def redirections_engine(redirects: dict[RedirectionOrigin, RedirectionDestinatio
url: str):
blocked = None
parsed: ParseResult = urllib.parse.urlparse(url)
+ is_list_page: bool = parsed.scheme == "list"
netloc = parsed.netloc
for criterion, rewrite in redirects.items():
- applicable = fnmatch(
+ applicable = True
+ if is_list_page:
+ applicable &= criterion.netloc.startswith("list:")
+ applicable &= fnmatch(
remove_port_if_criterion_port_inexplicit(criterion, netloc),
criterion.netloc)
if not applicable: continue
diff --git a/tests/redirections_test.py b/tests/redirections_test.py
new file mode 100644
index 0000000..4ab4502
--- /dev/null
+++ b/tests/redirections_test.py
@@ -0,0 +1,34 @@
+import unittest
+
+from implementation_parts.redirection_classes import RedirectionOrigin, RedirectionDestination, RedirectionRewrite
+from implementation_parts.redirections_stuff import redirections_engine
+
+class MyTestCase(unittest.TestCase):
+ def test_zero_redir_list_root(self):
+ self.assertEqual((None, "list:///"), redirections_engine({}, "list:///"))
+ def test_one_redir_list_root(self):
+ self.assertEqual((None, "list:///"),
+ redirections_engine(
+ {RedirectionOrigin(
+ netloc="example.com"
+ ): "BLOCK"},
+ "list:///"
+ ))
+ def test_wildcard_redir_file_root(self):
+ self.assertEqual((None, "file:///"),
+ redirections_engine(
+ {RedirectionOrigin(
+ netloc="*"
+ ): RedirectionRewrite()}
+ , "file:///"))
+ def test_wildcard_redir_list_root(self):
+ self.assertEqual((None, "list:///"),
+ redirections_engine(
+ {RedirectionOrigin(
+ netloc="*"
+ ): RedirectionRewrite()}
+ , "list:///"))
+
+
+if __name__ == '__main__':
+ unittest.main()
--
2.52.0
Comment on “Bugfix for list URI for my Offpunk redirections implementation draft”