diff options
| author | Fuwn <[email protected]> | 2023-04-17 06:57:19 +0000 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2023-04-17 06:57:19 +0000 |
| commit | 3854c711b097b39e858d8ceabb4099a659f875a1 (patch) | |
| tree | eaeb6edb104306f17d2bbba3895ee9b93ec39036 /src/request | |
| parent | chore(README): Update examples directory path (diff) | |
| download | germ-3854c711b097b39e858d8ceabb4099a659f875a1.tar.xz germ-3854c711b097b39e858d8ceabb4099a659f875a1.zip | |
refactor: remove seldom used procedural macros
Diffstat (limited to 'src/request')
| -rw-r--r-- | src/request/response.rs | 74 | ||||
| -rw-r--r-- | src/request/status.rs | 112 | ||||
| -rw-r--r-- | src/request/sync.rs | 77 | ||||
| -rw-r--r-- | src/request/verifier.rs | 42 |
4 files changed, 305 insertions, 0 deletions
diff --git a/src/request/response.rs b/src/request/response.rs new file mode 100644 index 0000000..4c822e1 --- /dev/null +++ b/src/request/response.rs @@ -0,0 +1,74 @@ +// This file is part of Germ <https://github.com/gemrest/germ>. +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +use std::borrow::Cow; + +use rustls::SupportedCipherSuite; + +use crate::request::Status; + +#[derive(Debug, Clone)] +pub struct Response { + status: Status, + meta: String, + content: Option<String>, + size: usize, + suite: Option<SupportedCipherSuite>, +} + +impl Response { + pub(crate) fn new(data: &[u8], suite: Option<SupportedCipherSuite>) -> Self { + let string_form = String::from_utf8_lossy(data).to_string(); + let mut content = None; + let header; + + if string_form.ends_with("\r\n") { + header = string_form; + } else { + let mut string_split = string_form.split("\r\n"); + + header = string_split.next().unwrap_or("").to_string(); + content = Some(string_split.map(|s| format!("{s}\r\n")).collect()); + } + + let header_split = header.split_at(2); + + Self { + status: Status::from(header_split.0.parse::<i32>().unwrap_or(0)), + meta: header_split.1.trim_start().to_string(), + content, + size: data.len(), + suite, + } + } + + #[must_use] + pub const fn status(&self) -> &Status { &self.status } + + #[must_use] + pub fn meta(&self) -> Cow<'_, str> { Cow::Borrowed(&self.meta) } + + #[must_use] + pub const fn content(&self) -> &Option<String> { &self.content } + + #[must_use] + pub const fn size(&self) -> &usize { &self.size } + + #[must_use] + pub const fn suite(&self) -> &Option<SupportedCipherSuite> { &self.suite } +} diff --git a/src/request/status.rs b/src/request/status.rs new file mode 100644 index 0000000..c18f171 --- /dev/null +++ b/src/request/status.rs @@ -0,0 +1,112 @@ +// This file is part of Germ <https://github.com/gemrest/germ>. +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +use std::{fmt, fmt::Formatter}; + +/// Simple Gemini status reporting +/// +/// # Examples +/// +/// ```rust +/// use germ::request::Status; +/// +/// assert_eq!(Status::from(10), Status::Input); +/// assert_eq!(i32::from(Status::Input), 10); +/// ``` +#[derive(Debug, PartialEq, Clone, Eq)] +pub enum Status { + Input, + SensitiveInput, + Success, + TemporaryRedirect, + PermanentRedirect, + TemporaryFailure, + ServerUnavailable, + CGIError, + ProxyError, + SlowDown, + PermanentFailure, + NotFound, + Gone, + ProxyRefused, + BadRequest, + ClientCertificateRequired, + CertificateNotAuthorised, + CertificateNotValid, + Unsupported, +} + +impl Default for Status { + fn default() -> Self { Self::Success } +} + +impl From<Status> for i32 { + fn from(n: Status) -> Self { + match n { + Status::Input => 10, + Status::SensitiveInput => 11, + Status::Success => 20, + Status::TemporaryRedirect => 30, + Status::PermanentRedirect => 31, + Status::TemporaryFailure => 40, + Status::ServerUnavailable => 41, + Status::CGIError => 42, + Status::ProxyError => 43, + Status::SlowDown => 44, + Status::PermanentFailure => 50, + Status::NotFound => 51, + Status::Gone => 52, + Status::ProxyRefused => 53, + Status::BadRequest => 59, + Status::ClientCertificateRequired => 60, + Status::CertificateNotAuthorised => 61, + Status::CertificateNotValid => 62, + Status::Unsupported => 0, + } + } +} + +impl From<i32> for Status { + fn from(n: i32) -> Self { + match n { + 10 => Self::Input, + 11 => Self::SensitiveInput, + 20 => Self::Success, + 30 => Self::TemporaryRedirect, + 31 => Self::PermanentRedirect, + 40 => Self::TemporaryFailure, + 41 => Self::ServerUnavailable, + 42 => Self::CGIError, + 43 => Self::ProxyError, + 44 => Self::SlowDown, + 50 => Self::PermanentFailure, + 51 => Self::NotFound, + 52 => Self::Gone, + 53 => Self::ProxyRefused, + 59 => Self::BadRequest, + 60 => Self::ClientCertificateRequired, + 61 => Self::CertificateNotAuthorised, + 62 => Self::CertificateNotValid, + _ => Self::Unsupported, + } + } +} + +impl fmt::Display for Status { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") } +} diff --git a/src/request/sync.rs b/src/request/sync.rs new file mode 100644 index 0000000..68c8a0f --- /dev/null +++ b/src/request/sync.rs @@ -0,0 +1,77 @@ +// This file is part of Germ <https://github.com/gemrest/germ>. +// Copyright (C) 2022-2023 Fuwn <[email protected]> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +use crate::request::Response; + +/// Make a request to a Gemini server +/// +/// The `url` **should** be prefixed with a scheme (e.g. "gemini://"). +/// +/// # Example +/// +/// ```rust +/// match germ::request::request(&url::Url::parse("gemini://fuwn.me").unwrap()) +/// .await +/// { +/// Ok(response) => println!("{:?}", response), +/// Err(_) => {} +/// } +/// ``` +/// +/// # Errors +/// +/// - May error if the URL is invalid +/// - May error if the server is unreachable +/// - May error if the TLS write fails +/// - May error if the TLS read fails +pub async fn request(url: &url::Url) -> anyhow::Result<Response> { + let mut tls = tokio_rustls::TlsConnector::from(std::sync::Arc::new( + rustls::ClientConfig::builder() + .with_safe_defaults() + .with_custom_certificate_verifier(std::sync::Arc::new( + crate::request::GermVerifier::new(), + )) + .with_no_client_auth(), + )) + .connect( + rustls::ServerName::try_from(url.domain().unwrap_or_default())?, + tokio::net::TcpStream::connect(format!( + "{}:{}", + url.domain().unwrap_or(""), + url.port().unwrap_or(1965) + )) + .await?, + ) + .await?; + let cipher_suite = tls.get_mut().1.negotiated_cipher_suite(); + + tls.write_all(format!("{url}\r\n").as_bytes()).await?; + + Ok(Response::new( + &{ + let mut plain_text = Vec::new(); + + tls.read_to_end(&mut plain_text).await?; + + plain_text + }, + cipher_suite, + )) +} diff --git a/src/request/verifier.rs b/src/request/verifier.rs new file mode 100644 index 0000000..b0120bd --- /dev/null +++ b/src/request/verifier.rs @@ -0,0 +1,42 @@ +// This file is part of Germ <https://github.com/gemrest/germ>. +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +use std::time::SystemTime; + +use rustls::{client, client::ServerCertVerified, Certificate}; + +#[allow(clippy::module_name_repetitions)] +pub struct GermVerifier; + +impl GermVerifier { + pub const fn new() -> Self { Self {} } +} + +impl client::ServerCertVerifier for GermVerifier { + fn verify_server_cert( + &self, + _end_entity: &Certificate, + _intermediates: &[Certificate], + _server_name: &client::ServerName, + _scts: &mut dyn Iterator<Item = &[u8]>, + _ocsp_response: &[u8], + _now: SystemTime, + ) -> Result<ServerCertVerified, rustls::Error> { + Ok(ServerCertVerified::assertion()) + } +} |