open_chess_server/http/
mod.rs1pub mod account;
4pub mod app_files;
5pub mod challenges;
6pub mod fairplay;
7pub mod games;
8pub mod lab;
9pub mod oauth;
10pub mod play;
11pub mod streams;
12
13use std::convert::Infallible;
14
15use axum::Json;
16use axum::body::Body;
17use axum::extract::State;
18use axum::http::{HeaderValue, header};
19use axum::response::{IntoResponse, Response};
20use bytes::Bytes;
21use serde::Serialize;
22use serde_json::json;
23use tokio::sync::mpsc;
24
25use crate::app::Ctx;
26
27pub async fn index(State(ctx): State<Ctx>, headers: axum::http::HeaderMap) -> Response {
30 if app_files::wants_html(&headers) && ctx.cfg.app_dir.as_deref().is_some_and(|d| !d.is_empty()) {
31 let base = ctx.cfg.base_path().unwrap_or_default();
32 return axum::response::Redirect::to(&format!("{base}/app/")).into_response();
33 }
34 Json(json!({
35 "name": "open-chess",
36 "site": ctx.cfg.site_name,
37 "api": "Lichess-compatible: Board, Bot, Challenge, OAuth (PKCE), event and game streams, game export",
38 "url": ctx.cfg.public_url(),
39 "version": env!("CARGO_PKG_VERSION"),
40 "login": ctx.cfg.esports_login,
42 "app": ctx.cfg.app_dir.as_deref().is_some_and(|d| !d.is_empty()).then(|| format!("{}/app/", ctx.cfg.public_url())),
43 }))
44 .into_response()
45}
46
47pub fn ok() -> Response {
48 Json(chess_proto::OK).into_response()
49}
50
51pub fn line<T: Serialize>(v: &T) -> Bytes {
53 let mut b = serde_json::to_vec(v).unwrap_or_default();
54 b.push(b'\n');
55 Bytes::from(b)
56}
57
58pub fn ndjson(rx: mpsc::Receiver<Bytes>) -> Response {
61 let stream = futures_util::stream::unfold(rx, |mut rx| async move {
62 rx.recv().await.map(|b| (Ok::<Bytes, Infallible>(b), rx))
63 });
64 let mut r = Body::from_stream(stream).into_response();
65 let h = r.headers_mut();
66 h.insert(header::CONTENT_TYPE, HeaderValue::from_static("application/x-ndjson"));
67 h.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-cache"));
68 h.insert("x-accel-buffering", HeaderValue::from_static("no"));
70 r
71}
72
73pub fn form(body: &[u8]) -> std::collections::HashMap<String, String> {
75 url::form_urlencoded::parse(body).into_owned().collect()
76}
77
78pub fn html_escape(s: &str) -> String {
79 s.replace('&', "&")
80 .replace('<', "<")
81 .replace('>', ">")
82 .replace('"', """)
83 .replace('\'', "'")
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn escapes() {
92 assert_eq!(html_escape("<a href=\"x\">&'"), "<a href="x">&'");
93 }
94
95 #[test]
96 fn lines_end_in_newline() {
97 assert_eq!(&line(&json!({"a": 1}))[..], b"{\"a\":1}\n");
98 }
99}