use crate::server::app::AppState;
use crate::server::errors::unmatched;
use crate::server::static_assets::{normalize_request_path, read_asset, read_shell, serves_shell};
use axum::extract::State;
use axum::http::{header, HeaderValue, Method, StatusCode, Uri};
use axum::response::{IntoResponse, Response};
const HTML: &str = "text/html; charset=utf-8";
fn with_credential(html: String, credential: Option<&str>) -> String {
let Some(credential) = credential else {
return html;
};
let payload = serde_json::json!({ "credential": credential })
.to_string()
.replace('<', "\\u003c");
let script = format!(
"<script>Object.defineProperty(window,'__NOMOREIDE_WEB__',\
{{value:Object.freeze({payload}),enumerable:false,configurable:false,writable:false}});</script>"
);
match html.find("</head>") {
Some(at) => {
let mut out = String::with_capacity(html.len() + script.len());
out.push_str(&html[..at]);
out.push_str(&script);
out.push_str(&html[at..]);
out
}
None => format!("{script}{html}"),
}
}
pub(crate) async fn serve(State(state): State<AppState>, method: Method, uri: Uri) -> Response {
serve_inner(method, uri, Some(state.credential.as_str())).await
}
pub(crate) async fn serve_unauthenticated(method: Method, uri: Uri) -> Response {
serve_inner(method, uri, None).await
}
async fn serve_inner(method: Method, uri: Uri, credential: Option<&str>) -> Response {
let path = normalize_request_path(uri.path());
let path = path.as_str();
if method == Method::GET && path.starts_with("/assets/") {
return match read_asset(path) {
Some((bytes, content_type)) => (
[(header::CONTENT_TYPE, HeaderValue::from_static(content_type))],
bytes,
)
.into_response(),
None => (
StatusCode::NOT_FOUND,
[(header::CONTENT_TYPE, HeaderValue::from_static(HTML))],
"Not found",
)
.into_response(),
};
}
if serves_shell(path) {
match method {
Method::HEAD => {
return (
[(header::CONTENT_TYPE, HeaderValue::from_static(HTML))],
axum::body::Body::empty(),
)
.into_response()
}
Method::GET => {
return match read_shell() {
Ok(html) => (
[(header::CONTENT_TYPE, HeaderValue::from_static(HTML))],
with_credential(html, credential),
)
.into_response(),
Err(reason) => {
crate::server::errors::error(StatusCode::INTERNAL_SERVER_ERROR, &reason)
}
};
}
_ => {}
}
}
unmatched().await
}
#[cfg(test)]
mod tests {
use super::*;
const SHELL: &str = "<!doctype html><html><head><title>x</title></head><body></body></html>";
#[test]
fn the_document_carries_the_credential_before_any_script_runs() {
let html = with_credential(SHELL.to_string(), Some("abc123"));
let script_at = html
.find("__NOMOREIDE_WEB__")
.expect("the global is injected");
let head_end = html.find("</head>").expect("head is still closed");
assert!(
script_at < head_end,
"the credential has to exist before the bundle loads, or the first \
API call races the script that authorises it"
);
assert!(html.contains("abc123"));
}
#[test]
fn no_credential_means_the_document_is_untouched() {
assert_eq!(with_credential(SHELL.to_string(), None), SHELL);
}
#[test]
fn a_credential_cannot_break_out_of_its_script_element() {
let html = with_credential(SHELL.to_string(), Some("</script><img src=x>"));
assert!(
!html.contains("</script><img src=x>"),
"the raw value escaped into the document"
);
assert!(html.contains("<\\/script>") || html.contains("\\u003c"));
}
#[test]
fn a_shell_without_a_head_still_gets_the_credential_first() {
let html = with_credential("<body>hi</body>".to_string(), Some("abc123"));
assert!(html.starts_with("<script>"));
assert!(html.contains("abc123"));
}
}