diff options
| author | Fuwn <[email protected]> | 2026-02-10 13:12:46 +0000 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-10 13:12:46 +0000 |
| commit | 1cb2515da857374b695dc82138f271d287ad2e15 (patch) | |
| tree | 629f0a2bdb7ed92ced8fd976e4653f717ee59f45 /src/http09.rs | |
| parent | chore: Bump version patch to v0.3.2 (diff) | |
| download | september-1cb2515da857374b695dc82138f271d287ad2e15.tar.xz september-1cb2515da857374b695dc82138f271d287ad2e15.zip | |
feat: Support HTTP/0.9
Diffstat (limited to 'src/http09.rs')
| -rw-r--r-- | src/http09.rs | 100 |
1 files changed, 100 insertions, 0 deletions
diff --git a/src/http09.rs b/src/http09.rs new file mode 100644 index 0000000..b18cd96 --- /dev/null +++ b/src/http09.rs @@ -0,0 +1,100 @@ +use { + crate::{environment::ENVIRONMENT, url::from_path}, + tokio::{ + io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, + net::TcpListener, + }, +}; + +pub async fn serve() { + let address = format!("0.0.0.0:{}", ENVIRONMENT.http09_port); + let listener = match TcpListener::bind(&address).await { + Ok(listener) => { + info!("HTTP/0.9 server listening on {address}"); + + listener + } + Err(error) => { + error!("failed to bind HTTP/0.9 server to {address}: {error}"); + + return; + } + }; + + loop { + let (stream, peer) = match listener.accept().await { + Ok(connection) => connection, + Err(error) => { + warn!("HTTP/0.9 accept error: {error}"); + + continue; + } + }; + + tokio::spawn(async move { + if let Err(error) = handle(stream).await { + warn!("HTTP/0.9 error from {peer}: {error}"); + } + }); + } +} + +async fn handle( + stream: tokio::net::TcpStream, +) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { + let (reader, mut writer) = stream.into_split(); + let mut reader = BufReader::new(reader); + let mut request_line = String::new(); + + reader.read_line(&mut request_line).await?; + + let path = parse_request(&request_line)?; + let mut configuration = crate::response::configuration::Configuration::new(); + let url = from_path(&path, false, &mut configuration)?; + let mut response = germ::request::request(&url).await?; + + if *response.status() == germ::request::Status::PermanentRedirect + || *response.status() == germ::request::Status::TemporaryRedirect + { + let redirect = if response.meta().starts_with('/') { + format!( + "gemini://{}{}", + url.domain().unwrap_or_default(), + response.meta() + ) + } else { + response.meta().to_string() + }; + + response = germ::request::request(&url::Url::parse(&redirect)?).await?; + } + + if response.meta().starts_with("image/") { + if let Some(bytes) = response.content_bytes() { + writer.write_all(bytes).await?; + } + } else if let Some(content) = response.content() { + writer.write_all(content.as_bytes()).await?; + } + + writer.shutdown().await?; + + Ok(()) +} + +fn parse_request( + line: &str, +) -> Result<String, Box<dyn std::error::Error + Send + Sync>> { + let line = line.trim(); + + line.strip_prefix("GET ").map_or_else( + || { + if line.starts_with('/') { + Ok(line.to_string()) + } else { + Err(format!("invalid HTTP/0.9 request: {line}").into()) + } + }, + |path| Ok(path.split_whitespace().next().unwrap_or("/").to_string()), + ) +} |