use rustls::pki_types::ServerName;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use url::Url;
use crate::gemini::parse as parse_gemini;
use crate::tls::connector;
use crate::{Error, Response, Scheme};
pub(crate) async fn fetch(url: &Url) -> Result<Response, Error> {
upload_inner(url, &[], "", None).await
}
pub async fn upload(
url: &Url,
body: &[u8],
mime: &str,
token: Option<&str>,
) -> Result<Response, Error> {
upload_inner(url, body, mime, token).await
}
async fn upload_inner(
url: &Url,
body: &[u8],
mime: &str,
token: Option<&str>,
) -> Result<Response, Error> {
let host = url
.host_str()
.ok_or_else(|| Error::BadUrl("titan URL has no host".into()))?;
let port = url.port().unwrap_or_else(|| Scheme::Titan.default_port());
let mut request = format!("{url};size={}", body.len());
if !mime.is_empty() {
request.push_str(";mime=");
request.push_str(mime);
}
if let Some(tok) = token {
request.push_str(";token=");
request.push_str(tok);
}
request.push_str("\r\n");
let tcp = TcpStream::connect((host, port))
.await
.map_err(|e| Error::Connect(format!("tcp {host}:{port}: {e}")))?;
let server_name = ServerName::try_from(host.to_string())
.map_err(|e| Error::Connect(format!("server name {host}: {e}")))?;
let mut tls = connector()
.connect(server_name, tcp)
.await
.map_err(|e| Error::Connect(format!("tls handshake: {e}")))?;
tls.write_all(request.as_bytes())
.await
.map_err(|e| Error::Io(e.to_string()))?;
if !body.is_empty() {
tls.write_all(body)
.await
.map_err(|e| Error::Io(e.to_string()))?;
}
let mut raw = Vec::new();
tls.read_to_end(&mut raw)
.await
.map_err(|e| Error::Io(e.to_string()))?;
parse_gemini(url, &raw)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn titan_shares_gemini_port() {
assert_eq!(Scheme::Titan.default_port(), Scheme::Gemini.default_port());
}
}