mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-26 23:51:14 +02:00
feat: add HttpUrlStr shared URL validator
This commit is contained in:
parent
ca4f231577
commit
797dd2df47
3 changed files with 56 additions and 0 deletions
24
surfsense_backend/app/capabilities/core/validation.py
Normal file
24
surfsense_backend/app/capabilities/core/validation.py
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
"""Shared Pydantic field types for capability I/O schemas."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Annotated
|
||||||
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
|
import validators
|
||||||
|
from pydantic import AfterValidator
|
||||||
|
from pydantic_core import PydanticCustomError
|
||||||
|
|
||||||
|
_HTTP_SCHEMES = frozenset({"http", "https"})
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_http_url(value: str) -> str:
|
||||||
|
"""Accept only well-formed http(s) URLs, returned trimmed and unchanged."""
|
||||||
|
url = value.strip()
|
||||||
|
if not validators.url(url) or urlsplit(url).scheme.lower() not in _HTTP_SCHEMES:
|
||||||
|
raise PydanticCustomError("http_url", "must be a valid http(s) URL")
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
HttpUrlStr = Annotated[str, AfterValidator(_validate_http_url)]
|
||||||
|
"""A request URL validated as http(s) and kept as ``str`` (no normalization)."""
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
"""``HttpUrlStr`` boundary type: accept well-formed http(s) URLs, reject the rest."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from pydantic import BaseModel, ValidationError
|
||||||
|
|
||||||
|
from app.capabilities.core.validation import HttpUrlStr
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.unit
|
||||||
|
|
||||||
|
|
||||||
|
class _Model(BaseModel):
|
||||||
|
urls: list[HttpUrlStr]
|
||||||
|
|
||||||
|
|
||||||
|
def test_accepts_http_and_https_urls_unchanged() -> None:
|
||||||
|
model = _Model(urls=["https://example.com/path?q=1", "http://a.co"])
|
||||||
|
assert model.urls == ["https://example.com/path?q=1", "http://a.co"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_trims_surrounding_whitespace() -> None:
|
||||||
|
assert _Model(urls=[" https://example.com "]).urls == ["https://example.com"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"bad",
|
||||||
|
["not-a-url", "example.com", "ftp://example.com", "http://", "javascript:alert(1)"],
|
||||||
|
)
|
||||||
|
def test_rejects_malformed_or_non_http_urls(bad: str) -> None:
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
_Model(urls=[bad])
|
||||||
Loading…
Add table
Add a link
Reference in a new issue