Compare commits
No commits in common. "2b6150a8e53bd2e821645725ab0e7e35df1cd633" and "95362fd9e8944788998c2ad139cd2ebaf32f79a9" have entirely different histories.
2b6150a8e5
...
95362fd9e8
2 changed files with 2 additions and 41 deletions
27
src/lib.rs
27
src/lib.rs
|
@ -46,68 +46,43 @@ impl From<rustls::ClientConfig> for Client {
|
|||
impl Client {
|
||||
pub async fn request(self: &Self, url_str: &str) -> Result<Response, LibError> {
|
||||
let url = Url::parse(url_str).map_err(|e| InvalidUrl::ParseError(e))?;
|
||||
// for proxying http(s) through gemini server,
|
||||
// use Client::request_with_host
|
||||
if url.scheme() != "gemini" {
|
||||
// deny non-Gemini req
|
||||
return Err(InvalidUrl::SchemeNotGemini.into());
|
||||
}
|
||||
// userinfo (user:pswd@) is not allowed in Gemini
|
||||
if !url.username().is_empty() {
|
||||
return Err(InvalidUrl::UserinfoPresent.into());
|
||||
}
|
||||
|
||||
let host = url.host_str().ok_or(InvalidUrl::ConvertError)?;
|
||||
let addr = (host, url.port().unwrap_or(1965))
|
||||
.to_socket_addrs()?
|
||||
.next()
|
||||
.ok_or(InvalidUrl::ConvertError)?;
|
||||
|
||||
let domain = pki_types::ServerName::try_from(host)
|
||||
.map_err(|_| InvalidUrl::ConvertError)?
|
||||
.to_owned();
|
||||
|
||||
let stream = TcpStream::connect(&addr).await?;
|
||||
let mut stream = self.connector.connect(domain, stream).await?;
|
||||
|
||||
// Write URL, then CRLF
|
||||
stream.write_all(url_str.as_bytes()).await?;
|
||||
stream.write_all(b"\r\n").await?;
|
||||
|
||||
let mut buf: [u8; 3] = [0, 0, 0]; // 2 digits, space
|
||||
stream.read_exact(&mut buf).await?;
|
||||
let status = Status::parse_status(&buf)?;
|
||||
|
||||
let mut message: Vec<u8> = Vec::new();
|
||||
let mut buf_reader = tokio::io::BufReader::new(&mut stream);
|
||||
let mut buf: [u8; 1] = [0]; // buffer for LF (\n)
|
||||
|
||||
// reading message after status code
|
||||
// until CRLF (\r\n)
|
||||
let mut buf: [u8; 1] = [0];
|
||||
loop {
|
||||
// until CR
|
||||
buf_reader.read_until(b'\r', &mut message).await?;
|
||||
// now read next char...
|
||||
buf_reader.read_exact(&mut buf).await?;
|
||||
if buf[0] == b'\n' {
|
||||
// ...and check if it's LF
|
||||
break;
|
||||
} else {
|
||||
// ...otherwise, CR is a part of message, not a CRLF terminator,
|
||||
// so append that one byte that's supposed to be LF (but not LF)
|
||||
// to the message buffer
|
||||
message.push(buf[0].into());
|
||||
}
|
||||
}
|
||||
|
||||
// trim last CR
|
||||
if message.last().is_some_and(|c| c == &b'\r') {
|
||||
message.pop();
|
||||
}
|
||||
|
||||
// Vec<u8> -> ASCII or UTF-8 String
|
||||
let message = String::from_utf8(message)?;
|
||||
|
||||
Ok(Response::new(status, message, stream))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,31 +48,17 @@ pub enum StatusCode {
|
|||
ClientCerts = 60,
|
||||
CertNotAuthorized = 61,
|
||||
CertNotValid = 62,
|
||||
|
||||
// 1..6 first digit range check is still
|
||||
// covered by conversion into ReplyType
|
||||
// (see Status::parse_status)
|
||||
#[num_enum(catch_all)]
|
||||
Unknown(u8),
|
||||
}
|
||||
|
||||
const ASCII_ZERO: u8 = 48; // '0'
|
||||
|
||||
impl Status {
|
||||
pub fn parse_status(buf: &[u8]) -> Result<Self, LibError> {
|
||||
// simple decimal digit conversion
|
||||
// '2' - '0' = 50 - 48 = 2 (from byte '2' to uint 2)
|
||||
let first = buf[0] - ASCII_ZERO;
|
||||
let second = buf[1] - ASCII_ZERO;
|
||||
|
||||
let code = first * 10 + second;
|
||||
|
||||
Ok(Status {
|
||||
// get enum item for 2-digit status code
|
||||
status_code: StatusCode::try_from_primitive(code)?,
|
||||
// get enum entry for first digit
|
||||
status_code: StatusCode::try_from_primitive(first * 10 + second)?,
|
||||
reply_type: ReplyType::try_from_primitive(first)?,
|
||||
// provide separate field for the 2nd digit
|
||||
second_digit: second,
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue