use http::{Response, StatusCode};
use crate::app_state::AppState;
use crate::router::{Body, html_response};
use crate::routes::layout::{Nav, html_escape, wrap_page};
fn render_info_page(title: &str, theme: &str, body: &str) -> Response<Body> {
let sub_nav = r#"<div class="card" style="padding:0.75rem 1rem;">
<a href="/info/about">About</a> ·
<a href="/info/license">License</a> ·
<a href="/info/system">System Info</a> ·
<a href="/info/privacy">Privacy</a> ·
<a href="/info/security">Security</a>
</div>"#;
let content = format!("{sub_nav}{body}");
html_response(StatusCode::OK, wrap_page(title, theme, Nav::Info, &content))
}
pub async fn about(
state: AppState,
_parts: http::request::Parts,
_params: Vec<(String, String)>,
) -> Response<Body> {
let body = r#"<h1>About CSAF</h1>
<div class="card">
<p><strong>CSAF</strong> is a web-based management tool for creating, reading,
updating, and deleting Common Security Advisory Framework (CSAF) documents
conforming to OASIS CSAF versions 2.0 and 2.1.</p>
<h2>Features</h2>
<ul>
<li>Full CRUD operations for CSAF security advisories, VEX, and informational advisories</li>
<li>Built-in validation against CSAF 2.0/2.1 schema rules</li>
<li>CVSS v3.1 and v4.0 scoring display</li>
<li>SHA-256 and SHA3-512 sidecar hash file generation</li>
<li>Bulk import/export with filesystem directory scanning</li>
<li>Audit trail for all document operations</li>
<li>HATEOAS-compliant REST API</li>
<li>Embedded storage (redb + SQLite) — no external database required</li>
<li>TLS 1.3 with automatic certificate generation</li>
</ul>
<h2>Standards</h2>
<ul>
<li><a href="https://docs.oasis-open.org/csaf/csaf/v2.0/csaf-v2.0.html"
target="_blank" rel="noopener">OASIS CSAF 2.0</a></li>
<li><a href="https://docs.oasis-open.org/csaf/csaf/v2.1/csaf-v2.1.html"
target="_blank" rel="noopener">OASIS CSAF 2.1</a></li>
</ul>
<h2>Contact</h2>
<p>Developed by <strong>ndaal Gesellschaft für Sicherheit in der
Informationstechnik mbH & Co KG</strong>, Cologne, Germany.</p>
</div>"#;
render_info_page("About", &state.settings().theme, body)
}
pub async fn license(
state: AppState,
_parts: http::request::Parts,
_params: Vec<(String, String)>,
) -> Response<Body> {
let body = r#"<h1>License</h1>
<div class="card">
<p>CSAF is licensed under the <strong>Apache License, Version 2.0</strong>.</p>
<p>SPDX-License-Identifier: <code>Apache-2.0</code></p>
<p>Copyright (c) 2026 Pierre Gronau, ndaal in Cologne.</p>
<h2>Apache License 2.0 Summary</h2>
<ul>
<li>You may use, modify, and distribute this software freely.</li>
<li>You must include the copyright notice and license in any distribution.</li>
<li>The software is provided "AS IS", without warranties.</li>
<li>Contributors grant a patent license for their contributions.</li>
</ul>
<p>Full license text:
<a href="https://www.apache.org/licenses/LICENSE-2.0"
target="_blank" rel="noopener">https://www.apache.org/licenses/LICENSE-2.0</a>
</p>
</div>"#;
render_info_page("License", &state.settings().theme, body)
}
pub async fn system_info(
state: AppState,
_parts: http::request::Parts,
_params: Vec<(String, String)>,
) -> Response<Body> {
let mut sys = sysinfo::System::new_all();
sys.refresh_all();
let os_name = sysinfo::System::name().unwrap_or_else(|| "Unknown".to_owned());
let os_version = sysinfo::System::os_version().unwrap_or_else(|| "Unknown".to_owned());
let kernel_version = sysinfo::System::kernel_version().unwrap_or_else(|| "Unknown".to_owned());
let hostname = sysinfo::System::host_name().unwrap_or_else(|| "Unknown".to_owned());
let total_memory_mb = sys.total_memory() / 1_048_576;
let used_memory_mb = sys.used_memory() / 1_048_576;
let cpu_count = sys.cpus().len();
let doc_count = state.csaf_storage().count_documents().unwrap_or(0);
let storage_ok = state.csaf_storage().check_storage_up().unwrap_or(false);
let config = state.config();
let settings = state.settings();
let body = format!(
r#"<h1>System Information</h1>
<div class="card">
<h2>Host</h2>
<table>
<tr><th>Hostname</th><td>{hostname}</td></tr>
<tr><th>OS</th><td>{os_name} {os_version}</td></tr>
<tr><th>Kernel</th><td>{kernel_version}</td></tr>
<tr><th>CPUs</th><td>{cpu_count}</td></tr>
<tr><th>Memory</th><td>{used_memory_mb} MB / {total_memory_mb} MB</td></tr>
</table>
</div>
<div class="card">
<h2>Application</h2>
<table>
<tr><th>Version</th><td>{app_version}</td></tr>
<tr><th>Rust Version</th><td>{rust_version}</td></tr>
<tr><th>Listen Address</th><td>{listen}</td></tr>
<tr><th>Data Directory</th><td>{data_dir}</td></tr>
<tr><th>Storage Status</th><td>{storage_status}</td></tr>
<tr><th>Documents Stored</th><td>{doc_count}</td></tr>
<tr><th>CSAF Mode</th><td>{csaf_mode}</td></tr>
</table>
</div>"#,
hostname = html_escape(&hostname),
os_name = html_escape(&os_name),
os_version = html_escape(&os_version),
kernel_version = html_escape(&kernel_version),
app_version = env!("CARGO_PKG_VERSION"),
rust_version = html_escape(env!("CARGO_PKG_RUST_VERSION", "unknown")),
listen = html_escape(&config.listen_address()),
data_dir = html_escape(&config.data_dir.display().to_string()),
storage_status = if storage_ok { "Healthy" } else { "Degraded" },
csaf_mode = html_escape(&settings.csaf_mode),
);
render_info_page("System Info", &settings.theme, &body)
}
pub async fn privacy(
state: AppState,
_parts: http::request::Parts,
_params: Vec<(String, String)>,
) -> Response<Body> {
let body = r#"<h1>Privacy</h1>
<div class="card">
<h2>Data Processing</h2>
<p>CSAF is designed as a self-hosted application. All data is stored
locally on the server where the application is deployed.</p>
<h2>Data Collected</h2>
<ul>
<li><strong>CSAF documents</strong> — stored in the embedded redb database</li>
<li><strong>User accounts</strong> — login credentials (hashed) stored in SQLite</li>
<li><strong>Audit logs</strong> — timestamps and actions for document operations</li>
<li><strong>Session data</strong> — temporary session tokens for authentication</li>
</ul>
<h2>No External Communication</h2>
<p>The application does not send data to external services, analytics
platforms, or third-party APIs. All processing occurs locally.</p>
<h2>Data Retention</h2>
<p>Data is retained until explicitly deleted by the user or administrator.
Audit logs are stored indefinitely for compliance purposes.</p>
<h2>GDPR Compliance</h2>
<p>As a self-hosted tool, GDPR compliance responsibility lies with the
deploying organisation. The application supports data export and deletion
to facilitate compliance.</p>
</div>"#;
render_info_page("Privacy", &state.settings().theme, body)
}
pub async fn security(
state: AppState,
_parts: http::request::Parts,
_params: Vec<(String, String)>,
) -> Response<Body> {
let body = r#"<h1>Security</h1>
<div class="card">
<h2>Transport Security</h2>
<ul>
<li>TLS 1.3 enforced via Rustls (no OpenSSL dependency)</li>
<li>RSA ciphers excluded; only elliptic-curve cryptography (ECC) used</li>
<li>Automatic self-signed certificate generation (45-day validity)</li>
<li>HTTP/1.1 + HTTP/2 auto-negotiated via ALPN over TCP</li>
<li>HTTP/3 served over QUIC/UDP</li>
</ul>
<h2>Authentication</h2>
<ul>
<li>Password hashing using Argon2id (RFC 9106)</li>
<li>API key authentication for programmatic access</li>
<li>Session-based authentication for the web UI</li>
</ul>
<h2>Data Integrity</h2>
<ul>
<li>SHA-256 and SHA3-512 sidecar hash files for exported documents</li>
<li>CSAF document validation on every create, update, import, and export</li>
<li>Audit trail for all document lifecycle operations</li>
</ul>
<h2>Input Validation</h2>
<ul>
<li>All CSAF documents validated against structural and semantic rules</li>
<li>CVSS score range validation (0.0 to 10.0)</li>
<li>Product ID cross-reference validation</li>
<li>HTML output is escaped to prevent XSS</li>
<li>RFC 9457 Problem Details for all API errors</li>
</ul>
<h2>Reporting Vulnerabilities</h2>
<p>If you discover a security vulnerability in CSAF, please report it
responsibly by contacting the development team at ndaal in Cologne.</p>
</div>"#;
render_info_page("Security", &state.settings().theme, body)
}