Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions src/core/upload/sub2api_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import logging
from datetime import datetime, timezone
from typing import List, Tuple, Optional
from urllib.parse import urlsplit

from curl_cffi import requests as cffi_requests

Expand All @@ -16,6 +17,26 @@
logger = logging.getLogger(__name__)


def _normalize_sub2api_base_url(api_url: str) -> str:
"""清洗并校验 Sub2API 基础地址,避免隐藏字符导致 curl 解析失败。"""
normalized = (api_url or "").strip().strip("'\"")
if not normalized:
raise ValueError("Sub2API URL 未配置")

parsed = urlsplit(normalized)
if parsed.scheme not in ("http", "https"):
raise ValueError("Sub2API URL 必须以 http:// 或 https:// 开头")
if not parsed.netloc:
raise ValueError("Sub2API URL 缺少主机名")

try:
_ = parsed.port
except ValueError as exc:
raise ValueError(f"Sub2API URL 端口无效: {exc}") from exc

return normalized.rstrip("/")


def upload_to_sub2api(
accounts: List[Account],
api_url: str,
Expand Down Expand Up @@ -46,6 +67,11 @@ def upload_to_sub2api(
if not api_key:
return False, "Sub2API API Key 未配置"

try:
api_url = _normalize_sub2api_base_url(api_url)
except ValueError as e:
return False, str(e)

exported_at = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")

account_items = []
Expand Down Expand Up @@ -99,7 +125,7 @@ def upload_to_sub2api(
"skip_default_group_bind": True,
}

url = api_url.rstrip("/") + "/api/v1/admin/accounts/data"
url = api_url + "/api/v1/admin/accounts/data"
headers = {
"Content-Type": "application/json",
"x-api-key": api_key,
Expand Down Expand Up @@ -197,7 +223,12 @@ def test_sub2api_connection(api_url: str, api_key: str) -> Tuple[bool, str]:
if not api_key:
return False, "API Key 不能为空"

url = api_url.rstrip("/") + "/api/v1/admin/accounts/data"
try:
api_url = _normalize_sub2api_base_url(api_url)
except ValueError as e:
return False, str(e)

url = api_url + "/api/v1/admin/accounts/data"
headers = {"x-api-key": api_key}

try:
Expand Down
21 changes: 16 additions & 5 deletions src/web/routes/upload/sub2api_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@

from ....database import crud
from ....database.session import get_db
from ....core.upload.sub2api_upload import test_sub2api_connection, batch_upload_to_sub2api
from ....core.upload.sub2api_upload import (
test_sub2api_connection,
batch_upload_to_sub2api,
_normalize_sub2api_base_url,
)

router = APIRouter()

Expand Down Expand Up @@ -85,11 +89,15 @@ async def list_sub2api_services(enabled: Optional[bool] = None):
@router.post("", response_model=Sub2ApiServiceResponse)
async def create_sub2api_service(request: Sub2ApiServiceCreate):
"""新增 Sub2API 服务"""
try:
api_url = _normalize_sub2api_base_url(request.api_url)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
with get_db() as db:
svc = crud.create_sub2api_service(
db,
name=request.name,
api_url=request.api_url,
name=request.name.strip(),
api_url=api_url,
api_key=request.api_key,
target_type=request.target_type,
enabled=request.enabled,
Expand Down Expand Up @@ -136,9 +144,12 @@ async def update_sub2api_service(service_id: int, request: Sub2ApiServiceUpdate)

update_data = {}
if request.name is not None:
update_data["name"] = request.name
update_data["name"] = request.name.strip()
if request.api_url is not None:
update_data["api_url"] = request.api_url
try:
update_data["api_url"] = _normalize_sub2api_base_url(request.api_url)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
# api_key 留空则保持原值
if request.api_key:
update_data["api_key"] = request.api_key
Expand Down
6 changes: 3 additions & 3 deletions static/js/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -1513,9 +1513,9 @@ async function handleSaveSub2ApiService(e) {
e.preventDefault();
const id = document.getElementById('sub2api-service-id').value;
const data = {
name: document.getElementById('sub2api-service-name').value,
api_url: document.getElementById('sub2api-service-url').value,
api_key: document.getElementById('sub2api-service-key').value || undefined,
name: document.getElementById('sub2api-service-name').value.trim(),
api_url: document.getElementById('sub2api-service-url').value.trim(),
api_key: document.getElementById('sub2api-service-key').value.trim() || undefined,
priority: parseInt(document.getElementById('sub2api-service-priority').value) || 0,
enabled: document.getElementById('sub2api-service-enabled').checked,
};
Expand Down