fix: Go-пакеты со слешами в имени ломали роутинг
Использован :path в FastAPI-роутах, имя+версия парсятся из URL. Шаблоны urlencode-ят имена пакетов при генерации ссылок.
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import csv
|
import csv
|
||||||
import io
|
import io
|
||||||
|
from urllib.parse import unquote
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query, Response
|
from fastapi import APIRouter, Depends, Query, Response
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
@@ -108,17 +109,20 @@ async def export_packages_csv(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{name}/{version}")
|
@router.get("/{name:path}")
|
||||||
async def get_package(
|
async def get_package(
|
||||||
name: str,
|
name: str,
|
||||||
version: str,
|
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
):
|
):
|
||||||
|
parts = name.rsplit("/", 1)
|
||||||
|
pkg_name = unquote(parts[0])
|
||||||
|
pkg_version = unquote(parts[1]) if len(parts) == 2 else ""
|
||||||
|
|
||||||
scans = (
|
scans = (
|
||||||
(
|
(
|
||||||
await session.execute(
|
await session.execute(
|
||||||
select(Scan)
|
select(Scan)
|
||||||
.where(Scan.package_name == name, Scan.package_version == version)
|
.where(Scan.package_name == pkg_name, Scan.package_version == pkg_version)
|
||||||
.order_by(Scan.started_at.desc())
|
.order_by(Scan.started_at.desc())
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
"""Web UI routes — Jinja2 + htmx pages."""
|
"""Web UI routes — Jinja2 + htmx pages."""
|
||||||
|
|
||||||
|
from urllib.parse import unquote
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Request
|
from fastapi import APIRouter, Depends, Request
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
from jinja2 import Environment, PackageLoader, select_autoescape
|
from jinja2 import Environment, PackageLoader, select_autoescape
|
||||||
@@ -154,20 +156,25 @@ async def packages_list(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/packages/{name}/{version}", response_class=HTMLResponse)
|
@router.get("/packages/{name:path}", response_class=HTMLResponse)
|
||||||
async def package_detail(
|
async def package_detail(
|
||||||
name: str,
|
name: str,
|
||||||
version: str,
|
|
||||||
request: Request,
|
request: Request,
|
||||||
session: AsyncSession = Depends(get_session),
|
session: AsyncSession = Depends(get_session),
|
||||||
):
|
):
|
||||||
|
# name:path captures the entire path after /packages/
|
||||||
|
# e.g. "eviltest/0.1.0" or "github.com/attacker/evilmodule/v0.1.0"
|
||||||
|
parts = name.rsplit("/", 1)
|
||||||
|
pkg_name = unquote(parts[0])
|
||||||
|
pkg_version = unquote(parts[1]) if len(parts) == 2 else ""
|
||||||
|
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
scans = (
|
scans = (
|
||||||
(
|
(
|
||||||
await session.execute(
|
await session.execute(
|
||||||
select(Scan)
|
select(Scan)
|
||||||
.where(Scan.package_name == name, Scan.package_version == version)
|
.where(Scan.package_name == pkg_name, Scan.package_version == pkg_version)
|
||||||
.options(selectinload(Scan.findings))
|
.options(selectinload(Scan.findings))
|
||||||
.order_by(Scan.started_at.desc())
|
.order_by(Scan.started_at.desc())
|
||||||
)
|
)
|
||||||
@@ -185,8 +192,8 @@ async def package_detail(
|
|||||||
|
|
||||||
return _render(
|
return _render(
|
||||||
"package_detail.html",
|
"package_detail.html",
|
||||||
pkg_name=name,
|
pkg_name=pkg_name,
|
||||||
pkg_version=version,
|
pkg_version=pkg_version,
|
||||||
scans=scans,
|
scans=scans,
|
||||||
findings=all_findings,
|
findings=all_findings,
|
||||||
request=request,
|
request=request,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for p in packages %}
|
{% for p in packages %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="/packages/{{ p.pkg_name }}/{{ p.pkg_ver }}">{{ p.pkg_name }}</a></td>
|
<td><a href="/packages/{{ p.pkg_name | urlencode }}/{{ p.pkg_ver | urlencode }}">{{ p.pkg_name }}</a></td>
|
||||||
<td>{{ p.pkg_ver }}</td>
|
<td>{{ p.pkg_ver }}</td>
|
||||||
<td>{{ p.ecosystem }}</td>
|
<td>{{ p.ecosystem }}</td>
|
||||||
<td>{{ p.repository }}</td>
|
<td>{{ p.repository }}</td>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for s in latest_scans %}
|
{% for s in latest_scans %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="/packages/{{ s.package_name }}/{{ s.package_version }}">{{ s.package_name }}</a></td>
|
<td><a href="/packages/{{ s.package_name | urlencode }}/{{ s.package_version | urlencode }}">{{ s.package_name }}</a></td>
|
||||||
<td>{{ s.package_version }}</td>
|
<td>{{ s.package_version }}</td>
|
||||||
<td><small>{{ s.repository }}</small></td>
|
<td><small>{{ s.repository }}</small></td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<article class="scan-info-block">
|
<article class="scan-info-block">
|
||||||
<div class="scan-info-grid">
|
<div class="scan-info-grid">
|
||||||
<div><strong>Package</strong><br><a href="/packages/{{ scan.package_name }}/{{ scan.package_version }}">{{ scan.package_name }}</a></div>
|
<div><strong>Package</strong><br><a href="/packages/{{ scan.package_name | urlencode }}/{{ scan.package_version | urlencode }}">{{ scan.package_name }}</a></div>
|
||||||
<div><strong>Version</strong><br>{{ scan.package_version }}</div>
|
<div><strong>Version</strong><br>{{ scan.package_version }}</div>
|
||||||
<div><strong>Ecosystem</strong><br>{{ scan.ecosystem }}</div>
|
<div><strong>Ecosystem</strong><br>{{ scan.ecosystem }}</div>
|
||||||
<div><strong>Repository</strong><br>{{ scan.repository }}</div>
|
<div><strong>Repository</strong><br>{{ scan.repository }}</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user