Panniantong/Agent-Reach · error · ValueError

invalid V2EX API URL

Error message

invalid V2EX API URL

What it means

Raised by _validate_api_url() in the V2EX channel when urllib.parse.urlsplit() itself raises ValueError while parsing, or when accessing parsed.port fails — i.e. the URL string is structurally malformed (bad port syntax like 'https://v2ex.com:notaport/api/...', unmatched brackets, invalid IPv6). It is the parse-level guard before the stricter allowlist check (error 18).

Source

Thrown at agent_reach/channels/v2ex.py:34

_UA = "agent-reach/1.0"
_TIMEOUT = 10
_MAX_RESPONSE_BYTES = 1024 * 1024
_API_BASE = "https://www.v2ex.com"


def _v2ex_url(path: str, **params: Any) -> str:
    """Build a V2EX URL without letting caller values alter its query."""
    return f"{_API_BASE}{path}?{urlencode(params)}"


def _validate_api_url(url: str) -> None:
    """Allow only the public V2EX HTTPS JSON API."""
    try:
        parsed = urlsplit(url)
        port = parsed.port
    except ValueError as exc:
        raise ValueError("invalid V2EX API URL") from exc
    if (
        parsed.scheme.lower() != "https"
        or (parsed.hostname or "").lower() not in {"v2ex.com", "www.v2ex.com"}
        or port not in {None, 443}
        or parsed.username is not None
        or parsed.password is not None
        or not parsed.path.startswith("/api/")
    ):
        raise ValueError("only the V2EX HTTPS API is allowed")


def _get_json_with_urllib(url: str) -> Any:
    """Fetch JSON with Python's standard HTTP stack."""
    _validate_api_url(url)
    req = urllib.request.Request(url, headers={"User-Agent": _UA})
    with urllib.request.urlopen(req, timeout=_TIMEOUT) as resp:
        raw = resp.read(_MAX_RESPONSE_BYTES + 1)
    if len(raw) > _MAX_RESPONSE_BYTES:

View on GitHub (pinned to 93ae1d18c3)

Solutions

  1. Fix or discard the malformed URL — check the port segment syntax (https://host:443/api/... or omit it)
  2. Use the channel's own URL builder (_v2ex_url(path, **params)) instead of hand-building URLs
  3. If taking external input, validate with urlsplit() in a try/except before calling the channel

Example fix

# before
 _get_json_with_urllib("https://v2ex.com:99x/api/topics/show.json")
# after
 _get_json_with_urllib("https://v2ex.com/api/topics/show.json")
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlsplit

def url_parseable(url: str) -> bool:
    try:
        urlsplit(url).port
        return True
    except ValueError:
        return False

Type guard

def is_parseable_url(url) -> bool:
    if not isinstance(url, str):
        return False
    try:
        urlsplit(url).port
    except ValueError:
        return False
    return True

Try / catch

try:
    _get_json_with_urllib(url)
except ValueError as exc:
    if str(exc) == "invalid V2EX API URL":
        reject_input_url(url)  # caller-supplied URL was malformed

Prevention

When it happens

Trigger: Calling _get_json_with_urllib() (or any V2EX read/search that builds a custom URL) with a URL whose port component is non-numeric or out of syntax, or otherwise unparseable. Note: URLs built by _v2ex_url() never trigger this; only caller-supplied URLs can.

Common situations: Passing a user-provided or LLM-constructed URL into the channel's fetch path; string interpolation adding ':8443:' or leaving ':port' empty; URLs copy-pasted with invisible Unicode characters.

Related errors


AI-assisted analysis of Panniantong/Agent-Reach@93ae1d18c3 (2026-08-14). Data as JSON: /api/errors/6ed988428849f706. Report an issue: GitHub.