aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFuwn <[email protected]>2023-04-17 06:57:19 +0000
committerFuwn <[email protected]>2023-04-17 06:57:19 +0000
commit3854c711b097b39e858d8ceabb4099a659f875a1 (patch)
treeeaeb6edb104306f17d2bbba3895ee9b93ec39036 /src
parentchore(README): Update examples directory path (diff)
downloadgerm-3854c711b097b39e858d8ceabb4099a659f875a1.tar.xz
germ-3854c711b097b39e858d8ceabb4099a659f875a1.zip
refactor: remove seldom used procedural macros
Diffstat (limited to 'src')
-rw-r--r--src/ast.rs28
-rw-r--r--src/ast/container.rs324
-rw-r--r--src/ast/macros.rs36
-rw-r--r--src/ast/node.rs173
-rw-r--r--src/convert.rs76
-rw-r--r--src/convert/html.rs75
-rw-r--r--src/convert/macros.rs69
-rw-r--r--src/convert/markdown.rs77
-rw-r--r--src/lib.rs45
-rw-r--r--src/meta.rs165
-rw-r--r--src/quick.rs75
-rw-r--r--src/request.rs76
-rw-r--r--src/request/response.rs74
-rw-r--r--src/request/status.rs112
-rw-r--r--src/request/sync.rs77
-rw-r--r--src/request/verifier.rs42
16 files changed, 1524 insertions, 0 deletions
diff --git a/src/ast.rs b/src/ast.rs
new file mode 100644
index 0000000..8c00b52
--- /dev/null
+++ b/src/ast.rs
@@ -0,0 +1,28 @@
+// 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
+
+//! Build AST trees from Gemtext
+
+mod container;
+mod node;
+
+#[cfg(feature = "macros")]
+mod macros;
+
+pub use container::Ast;
+pub use node::Node;
diff --git a/src/ast/container.rs b/src/ast/container.rs
new file mode 100644
index 0000000..acb8894
--- /dev/null
+++ b/src/ast/container.rs
@@ -0,0 +1,324 @@
+// 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 super::Node;
+
+/// An AST structure which contains an AST tree
+///
+/// # Example
+///
+/// ```rust
+/// let _ = germ::ast::Ast::from_string(r#"=> gemini://gem.rest/ GemRest"#);
+/// ```
+#[derive(Clone)]
+pub struct Ast {
+ inner: Vec<Node>,
+}
+
+impl Ast {
+ /// Build an AST tree from Gemtext
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// let _ = germ::ast::Ast::from_string(r#"=> gemini://gem.rest/ GemRest"#);
+ /// ```
+ #[must_use]
+ pub fn from_owned(value: &(impl AsRef<str> + ?Sized)) -> Self {
+ Self::from_value(value.as_ref())
+ }
+
+ /// Build an AST tree from Gemtext
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// let _ = germ::ast::Ast::from_string(r#"=> gemini://gem.rest/ GemRest"#);
+ /// ```
+ #[must_use]
+ #[allow(clippy::needless_pass_by_value)]
+ pub fn from_string(value: (impl Into<String> + ?Sized)) -> Self {
+ Self::from_value(&value.into())
+ }
+
+ /// Build an AST tree from a value
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// let _ = germ::ast::Ast::from_value(r#"=> gemini://gem.rest/ GemRest"#);
+ /// ```
+ #[must_use]
+ pub fn from_value(value: &(impl ToString + ?Sized)) -> Self {
+ let mut ast = vec![];
+ let mut in_preformatted = false;
+ let mut in_list = false;
+ let source = value.to_string();
+ let mut lines = source.lines();
+
+ // Iterate over all lines in the Gemtext `source`
+ while let Some(line) = lines.next() {
+ // Evaluate the Gemtext line and append its AST node to the `ast` tree
+ ast.append(&mut Self::evaluate(
+ line,
+ &mut lines,
+ &mut in_preformatted,
+ &mut in_list,
+ ));
+ }
+
+ Self {
+ inner: ast
+ }
+ }
+
+ #[must_use]
+ pub fn to_gemtext(&self) -> String {
+ let mut gemtext = String::new();
+
+ for node in &self.inner {
+ match node {
+ Node::Text(text) => gemtext.push_str(&format!("{text}\n")),
+ Node::Link {
+ to,
+ text,
+ } =>
+ gemtext.push_str(&format!(
+ "=> {}{}\n",
+ to,
+ text
+ .clone()
+ .map_or_else(String::new, |text| format!(" {text}")),
+ )),
+ Node::Heading {
+ level,
+ text,
+ } =>
+ gemtext.push_str(&format!(
+ "{} {}\n",
+ match level {
+ 1 => "#",
+ 2 => "##",
+ 3 => "###",
+ _ => "",
+ },
+ text
+ )),
+ Node::List(items) =>
+ gemtext.push_str(&format!(
+ "{}\n",
+ items
+ .iter()
+ .map(|i| format!("* {i}"))
+ .collect::<Vec<String>>()
+ .join("\n"),
+ )),
+ Node::Blockquote(text) => gemtext.push_str(&format!("> {text}\n")),
+ Node::PreformattedText {
+ alt_text,
+ text,
+ } =>
+ gemtext.push_str(&format!(
+ "```{}\n{}```\n",
+ alt_text.clone().unwrap_or_default(),
+ text
+ )),
+ Node::Whitespace => gemtext.push('\n'),
+ }
+ }
+
+ gemtext
+ }
+
+ /// The actual AST of `Ast`
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// let _ =
+ /// germ::ast::Ast::from_string(r#"=> gemini://gem.rest/ GemRest"#).inner();
+ /// ```
+ #[must_use]
+ pub const fn inner(&self) -> &Vec<Node> { &self.inner }
+
+ #[allow(clippy::too_many_lines)]
+ fn evaluate(
+ line: &str,
+ lines: &mut std::str::Lines<'_>,
+ in_preformatted: &mut bool,
+ in_list: &mut bool,
+ ) -> Vec<Node> {
+ let mut preformatted = String::new();
+ let mut alt_text = String::new();
+ let mut nodes = vec![];
+ let mut line = line;
+ let mut list_items = vec![];
+
+ // Enter a not-so-infinite loop as sometimes, we may need to stay in an
+ // evaluation loop, e.g., multiline contexts: preformatted text, lists, etc.
+ loop {
+ // Match the first character of the Gemtext line to understand the line
+ // type
+ match line.get(0..1).unwrap_or("") {
+ "=" => {
+ // If the Gemtext line starts with an "=" ("=>"), it is a link line,
+ // so splitting it up should be easy enough.
+ let line = line.get(2..).unwrap();
+ let mut split = line
+ .split_whitespace()
+ .map(String::from)
+ .collect::<Vec<String>>()
+ .into_iter();
+
+ nodes.push(Node::Link {
+ to: split.next().expect("no location in link"),
+ text: {
+ let rest = split.collect::<Vec<String>>().join(" ");
+
+ if rest.is_empty() {
+ None
+ } else {
+ Some(rest)
+ }
+ },
+ });
+
+ break;
+ }
+ "#" => {
+ // If the Gemtext line starts with an "#", it is a heading, so let's
+ // find out how deep it goes.
+ let level = line.get(0..3).map_or(0, |root| {
+ if root.contains("###") {
+ 3
+ } else if root.contains("##") {
+ 2
+ } else {
+ // Converting the boolean response of `contains` to an integer
+ usize::from(root.contains('#'))
+ }
+ });
+
+ nodes.push(Node::Heading {
+ level,
+ // Here, we are `get`ing the `&str` starting at the `level`-th
+ // index, then trimming the start. These operations
+ // effectively off the line identifier.
+ text: line.get(level..).unwrap_or("").trim_start().to_string(),
+ });
+
+ break;
+ }
+ "*" => {
+ // If the Gemtext line starts with an asterisk, it is a list item, so
+ // let's enter a list context.
+ if !*in_list {
+ *in_list = true;
+ }
+
+ list_items.push(line.get(1..).unwrap_or("").trim_start().to_string());
+
+ if let Some(next_line) = lines.next() {
+ line = next_line;
+ } else {
+ break;
+ }
+ }
+ ">" => {
+ // If the Gemtext line starts with an ">", it is a blockquote, so
+ // let's just clip off the line identifier.
+ nodes.push(Node::Blockquote(
+ line.get(1..).unwrap_or("").trim_start().to_string(),
+ ));
+
+ break;
+ }
+ "`" => {
+ // If the Gemtext line starts with a backtick, it is a list item, so
+ // let's enter a preformatted text context.
+ *in_preformatted = !*in_preformatted;
+
+ if *in_preformatted {
+ alt_text = line.get(3..).unwrap_or("").to_string();
+
+ if let Some(next_line) = lines.next() {
+ line = next_line;
+ } else {
+ break;
+ }
+ } else {
+ nodes.push(Node::PreformattedText {
+ alt_text: if alt_text.is_empty() {
+ None
+ } else {
+ Some(alt_text)
+ },
+ text: preformatted,
+ });
+
+ break;
+ }
+ }
+ "" if !*in_preformatted => {
+ // If the line has nothing on it, it is a whitespace line, as long as
+ // we aren't in a preformatted line context.
+ nodes.push(Node::Whitespace);
+
+ break;
+ }
+ // This as a catchall, it does a number of things.
+ _ => {
+ if *in_preformatted {
+ // If we are in a preformatted line context, add the line to the
+ // preformatted blocks content and increment the line.
+ preformatted.push_str(&format!("{line}\n"));
+
+ if let Some(next_line) = lines.next() {
+ line = next_line;
+ } else {
+ break;
+ }
+ } else {
+ // If we are in a list item and hit a catchall, that must mean that
+ // we encountered a line which is not a list line, so
+ // let's stop adding items to the list context.
+ if *in_list {
+ *in_list = false;
+
+ nodes.push(Node::Text(line.to_string()));
+
+ break;
+ }
+
+ nodes.push(Node::Text(line.to_string()));
+
+ break;
+ }
+ }
+ }
+ }
+
+ if !list_items.is_empty() {
+ nodes.reverse();
+ nodes.push(Node::List(list_items));
+ nodes.reverse();
+ }
+
+ nodes
+ }
+}
diff --git a/src/ast/macros.rs b/src/ast/macros.rs
new file mode 100644
index 0000000..037766b
--- /dev/null
+++ b/src/ast/macros.rs
@@ -0,0 +1,36 @@
+// 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
+
+/// Convert Gemtext an `Ast`
+///
+/// # Examples
+///
+/// ```rust
+/// // Using a value
+/// assert_eq!(
+/// germ::gemini_to_ast!("=> / A link!").to_gemtext(),
+/// // `to_gemtext` appends a newline to all responses, so let's make sure we
+/// // account for that.
+/// format!("{}\n", "=> / A link!"),
+/// );
+#[macro_export]
+macro_rules! gemini_to_ast {
+ ($gemini:expr) => {
+ germ::ast::Ast::from_string($gemini)
+ };
+}
diff --git a/src/ast/node.rs b/src/ast/node.rs
new file mode 100644
index 0000000..e80ef84
--- /dev/null
+++ b/src/ast/node.rs
@@ -0,0 +1,173 @@
+// 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
+
+/// A Gemtext AST node.
+///
+/// Each Gemtext line is a `Node`, and some lines can even be grouped together,
+/// such as the `Node::List` `Node`!
+///
+/// # Gemtext Resources
+///
+/// - [Gemtext Documentation](https://gemini.circumlunar.space/docs/gemtext.gmi)
+/// - [Gemtext Cheatsheet](https://gemini.circumlunar.space/docs/cheatsheet.gmi).
+/// - [Gemini Specification](https://gemini.circumlunar.space/docs/specification.gmi).
+#[derive(Debug, PartialEq, Clone, Eq)]
+pub enum Node {
+ /// A text line
+ ///
+ /// # Example
+ ///
+ /// ```gemini
+ /// This is a text line
+ /// ```
+ Text(String),
+ /// A link line
+ ///
+ /// # Examples
+ ///
+ /// ```gemini
+ /// => /this-is-the-to This is the text
+ ///
+ /// => gemini://to.somewhere.link
+ /// ```
+ Link {
+ /// The location that a link line is pointing to
+ ///
+ /// # Examples
+ ///
+ /// ```gemini
+ /// => /this-is-the-to This is the text
+ ///
+ /// => gemini://to.somewhere.link
+ /// ```
+ to: String,
+ /// The text a link line *may* have
+ ///
+ /// # Examples
+ ///
+ /// ```gemini
+ /// => /this-is-the-to This line has text, unlike the next one.
+ ///
+ /// => gemini://to.somewhere.link
+ /// ```
+ text: Option<String>,
+ },
+ /// A heading line
+ ///
+ /// # Examples
+ ///
+ /// ```gemini
+ /// # This is a heading
+ ///
+ /// ## This is a sub-heading
+ ///
+ /// ### This is a sub-sub-heading
+ /// ```
+ Heading {
+ /// The level of a heading
+ ///
+ /// # Examples
+ ///
+ /// ```gemini
+ /// # This is a level 1 heading
+ ///
+ /// ## This is a level 2 sub-heading
+ ///
+ /// ### This is a level 3 sub-sub-heading
+ /// ```
+ level: usize,
+ /// The text of a heading
+ ///
+ /// # Examples
+ ///
+ /// ```gemini
+ /// # This is the headings text
+ ///
+ /// # This is also the headings text
+ /// ```
+ text: String,
+ },
+ /// A collection of sequential list item lines
+ ///
+ /// # Examples
+ ///
+ /// ```gemini
+ /// * These are
+ /// * sequential list
+ /// * items.
+ /// ```
+ List(Vec<String>),
+ /// A blockquote line
+ ///
+ /// # Examples
+ ///
+ /// ```gemini
+ /// > This is a blockquote line
+ ///
+ /// > This is also a blockquote line
+ /// ```
+ Blockquote(String),
+ /// A preformatted block
+ ///
+ /// # Examples
+ ///
+ /// Try to ignore the leading backslash in-front of the triple backticks,
+ /// they are there to not confuse the Markdown engine.
+ ///
+ /// ```gemini
+ /// \```This is the alt-text
+ /// This is the preformatted block
+ ///
+ /// This is the rest of the preformatted block
+ /// \```
+ /// ```
+ PreformattedText {
+ /// A preformatted blocks alt-text
+ ///
+ /// # Examples
+ ///
+ /// Try to ignore the leading backslash in-front of the triple backticks,
+ /// they are there to not confuse the Markdown engine.
+ ///
+ /// ```gemini
+ /// \```This is the alt-text
+ /// This is the preformatted block
+ ///
+ /// This is the rest of the preformatted block
+ /// \```
+ /// ```
+ alt_text: Option<String>,
+ /// A preformatted blocks content
+ ///
+ /// # Examples
+ ///
+ /// Try to ignore the leading backslash in-front of the triple backticks,
+ /// they are there to not confuse the Markdown engine.
+ ///
+ /// ```gemini
+ /// \```This is the alt-text
+ /// This is the preformatted blocks content
+ ///
+ /// This is the rest of the preformatted blocks content
+ /// \```
+ /// ```
+ text: String,
+ },
+ /// A whitespace line, a line which contains nothing but whitespace.
+ Whitespace,
+}
diff --git a/src/convert.rs b/src/convert.rs
new file mode 100644
index 0000000..22329d0
--- /dev/null
+++ b/src/convert.rs
@@ -0,0 +1,76 @@
+// 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
+
+//! Convert Gemtext into many types of markup.
+
+use crate::ast::Ast;
+
+mod html;
+mod markdown;
+
+#[cfg(feature = "macros")]
+mod macros;
+
+/// Different targets to convert Gemtext to
+#[derive(Clone)]
+pub enum Target {
+ /// Convert Gemtext to HTML
+ HTML,
+ /// Convert Gemtext to Markdown
+ Markdown,
+}
+
+/// Convert AST'd Gemtext into an alternative markup format.
+///
+/// # Example
+///
+/// ```rust
+/// use germ::convert;
+///
+/// let _ = convert::from_ast(
+/// &germ::ast::Ast::from_string(r#"=> gemini://gem.rest/ GemRest"#),
+/// &convert::Target::HTML,
+/// );
+/// ```
+#[must_use]
+pub fn from_ast(source: &Ast, target: &Target) -> String {
+ match target {
+ Target::Markdown => markdown::convert(source.inner()),
+ Target::HTML => html::convert(source.inner()),
+ }
+}
+
+/// Convert raw Gemtext into an alternative markup format.
+///
+/// # Example
+///
+/// ```rust
+/// use germ::convert;
+///
+/// let _ = convert::from_string(
+/// r#"=> gemini://gem.rest/ GemRest"#,
+/// &convert::Target::HTML,
+/// );
+/// ```
+#[must_use]
+pub fn from_string(
+ source: &(impl ToString + ?Sized),
+ target: &Target,
+) -> String {
+ from_ast(&Ast::from_owned(&source.to_string()), target)
+}
diff --git a/src/convert/html.rs b/src/convert/html.rs
new file mode 100644
index 0000000..581cbab
--- /dev/null
+++ b/src/convert/html.rs
@@ -0,0 +1,75 @@
+// 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 crate::ast::Node;
+
+pub fn convert(source: &[Node]) -> String {
+ let mut html = String::new();
+
+ // Since we have an AST tree of the Gemtext, it is very easy to convert from
+ // this AST tree to an alternative markup format.
+ for node in source {
+ match node {
+ Node::Text(text) => html.push_str(&format!("<p>{text}</p>")),
+ Node::Link {
+ to,
+ text,
+ } => {
+ html.push_str(&format!(
+ "<a href=\"{}\">{}</a><br>",
+ to,
+ text.clone().unwrap_or_else(|| to.clone())
+ ));
+ }
+ Node::Heading {
+ level,
+ text,
+ } => {
+ html.push_str(&format!(
+ "<{}>{}</{0}>",
+ match level {
+ 1 => "h1",
+ 2 => "h2",
+ 3 => "h3",
+ _ => "p",
+ },
+ text
+ ));
+ }
+ Node::List(items) =>
+ html.push_str(&format!(
+ "<ul>{}</ul>",
+ items
+ .iter()
+ .map(|i| format!("<li>{i}</li>"))
+ .collect::<Vec<String>>()
+ .join("\n")
+ )),
+ Node::Blockquote(text) =>
+ html.push_str(&format!("<blockquote>{text}</blockquote>")),
+ Node::PreformattedText {
+ text, ..
+ } => {
+ html.push_str(&format!("<pre>{text}</pre>"));
+ }
+ Node::Whitespace => {}
+ }
+ }
+
+ html
+}
diff --git a/src/convert/macros.rs b/src/convert/macros.rs
new file mode 100644
index 0000000..696c341
--- /dev/null
+++ b/src/convert/macros.rs
@@ -0,0 +1,69 @@
+// 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
+
+/// Convert Gemtext into HTML
+///
+/// # Examples
+///
+/// ```rust
+/// // Using a value
+/// assert_eq!(
+/// germ::gemini_to_html!("=> /to hello !"),
+/// "<a href=\"/to\">hello !</a><br>",
+/// );
+#[macro_export]
+macro_rules! gemini_to_html {
+ ($gemini:expr) => {
+ germ::convert::from_ast(
+ &germ::gemini_to_ast!($gemini),
+ &germ::convert::Target::HTML,
+ )
+ };
+ ($($gemini:tt)*) => {
+ germ::convert::from_ast(
+ &germ::gemini_to_ast!{ $($gemini)* },
+ &germ::convert::Target::HTML,
+ )
+ };
+}
+
+/// Convert Gemtext into Markdown
+///
+/// # Examples
+///
+/// ```rust
+/// assert_eq!(
+/// // Using a value
+/// germ::gemini_to_md!("=> /to hello !"),
+/// "[hello !](/to)\n",
+/// );
+#[macro_export]
+macro_rules! gemini_to_md {
+ ($gemini:expr) => {
+ germ::convert::from_ast(
+ &germ::gemini_to_ast!($gemini),
+ &germ::convert::Target::Markdown,
+ )
+ };
+ ($($gemini:tt)*) => {
+ germ::convert::from_ast(
+ &germ::gemini_to_ast!{ $($gemini)* },
+ &germ::convert::Target::Markdown,
+ )
+ };
+}
diff --git a/src/convert/markdown.rs b/src/convert/markdown.rs
new file mode 100644
index 0000000..3401940
--- /dev/null
+++ b/src/convert/markdown.rs
@@ -0,0 +1,77 @@
+// 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 crate::ast::Node;
+
+pub fn convert(source: &[Node]) -> String {
+ let mut markdown = String::new();
+
+ // Since we have an AST tree of the Gemtext, it is very easy to convert from
+ // this AST tree to an alternative markup format.
+ for node in source {
+ match node {
+ Node::Text(text) => markdown.push_str(&format!("{text}\n")),
+ Node::Link {
+ to,
+ text,
+ } =>
+ markdown.push_str(&text.clone().map_or_else(
+ || format!("<{to}>\n"),
+ |text| format!("[{text}]({to})\n"),
+ )),
+ Node::Heading {
+ level,
+ text,
+ } => {
+ markdown.push_str(&format!(
+ "{} {}\n",
+ match level {
+ 1 => "#",
+ 2 => "##",
+ 3 => "###",
+ _ => "",
+ },
+ text
+ ));
+ }
+ Node::List(items) =>
+ markdown.push_str(&format!(
+ "{}\n",
+ items
+ .iter()
+ .map(|i| format!("- {i}"))
+ .collect::<Vec<String>>()
+ .join("\n"),
+ )),
+ Node::Blockquote(text) => markdown.push_str(&format!("> {text}\n")),
+ Node::PreformattedText {
+ alt_text,
+ text,
+ } => {
+ markdown.push_str(&format!(
+ "```{}\n{}```\n",
+ alt_text.clone().unwrap_or_default(),
+ text
+ ));
+ }
+ Node::Whitespace => markdown.push('\n'),
+ }
+ }
+
+ markdown
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..6bdebcc
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,45 @@
+// 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
+
+#![deny(
+ warnings,
+ nonstandard_style,
+ unused,
+ future_incompatible,
+ rust_2018_idioms,
+ unsafe_code,
+ clippy::all,
+ clippy::nursery,
+ clippy::pedantic
+)]
+#![recursion_limit = "128"]
+
+#[cfg(feature = "ast")]
+pub mod ast;
+
+#[cfg(feature = "convert")]
+pub mod convert;
+
+#[cfg(feature = "request")]
+pub mod request;
+
+#[cfg(feature = "meta")]
+pub mod meta;
+
+#[cfg(feature = "quick")]
+pub mod quick;
diff --git a/src/meta.rs b/src/meta.rs
new file mode 100644
index 0000000..2a210a0
--- /dev/null
+++ b/src/meta.rs
@@ -0,0 +1,165 @@
+// 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, collections::HashMap};
+
+/// Structure-ize a Gemini response's meta section into it's mime type and it's
+/// parameters.
+#[derive(Debug, Default, Clone)]
+pub struct Meta {
+ /// The mime type of a Gemini response
+ mime: String,
+ /// The parameters of a Gemini response
+ parameters: HashMap<String, String>,
+}
+
+impl ToString for Meta {
+ /// Convert a `Meta` into a `String`
+ ///
+ /// # Example
+ /// ```rust
+ /// let original_string = "text/gemini; hi=2; hi2=string=2";
+ ///
+ /// assert_eq!(
+ /// germ::meta::Meta::from_string(original_string).to_string(),
+ /// original_string
+ /// );
+ /// ```
+ fn to_string(&self) -> String {
+ format!("{}{}", self.mime, {
+ let mut parameters = self
+ .parameters
+ .iter()
+ .map(|(k, v)| format!("{}={}", *k, v))
+ .collect::<Vec<_>>();
+
+ parameters.sort();
+ parameters.reverse();
+
+ if parameters.is_empty() {
+ String::new()
+ } else {
+ format!("; {}", parameters.join("; "))
+ }
+ })
+ }
+}
+
+impl Meta {
+ /// Create a new `Meta`
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// let mut meta = germ::meta::Meta::new();
+ /// ```
+ #[must_use]
+ pub fn new() -> Self { Self::default() }
+
+ /// Create a `Meta` from a string
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// assert_eq!(
+ /// germ::meta::Meta::from_string("text/gemini; hi=2; hi2=string=2").mime(),
+ /// "text/gemini",
+ /// );
+ /// ```
+ #[must_use]
+ pub fn from_string(meta: impl Into<std::borrow::Cow<'static, str>>) -> Self {
+ let meta = meta.into().to_string();
+ let mut metas = meta.split(';');
+ let mime = metas.next().unwrap_or("").to_string();
+ let mut parameters = HashMap::new();
+
+ for parameter in metas {
+ let key_value = parameter
+ .trim_start()
+ .split_at(parameter.find('=').unwrap_or(0));
+
+ parameters.insert(
+ key_value.0.to_string().replace('=', ""),
+ key_value.1.to_string(),
+ );
+ }
+
+ Self {
+ mime,
+ parameters,
+ }
+ }
+
+ /// Obtain non-mutable access to the mime of the `Meta`
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// assert_eq!(
+ /// germ::meta::Meta::from_string("text/gemini; hi=2; hi2=string=2").mime(),
+ /// "text/gemini",
+ /// );
+ /// ```
+ #[must_use]
+ pub fn mime(&self) -> Cow<'_, str> { Cow::Borrowed(&self.mime) }
+
+ /// Obtain mutable access to the mime of the `Meta`
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// let mut meta = germ::meta::Meta::new();
+ ///
+ /// *meta.mime_mut() = "text/gemini".to_string();
+ /// ```
+ pub fn mime_mut(&mut self) -> &mut String { &mut self.mime }
+
+ /// Obtain non-mutable access to the parameters of the `Meta`
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// assert_eq!(
+ /// germ::meta::Meta::from_string("text/gemini; hi=2; hi2=string=2")
+ /// .parameters()
+ /// .get("hi2"),
+ /// Some(&"string=2".to_string()),
+ /// );
+ /// ```
+ #[must_use]
+ pub const fn parameters(&self) -> &HashMap<String, String> {
+ &self.parameters
+ }
+
+ /// Obtain mutable access to the parameters of the `Meta`
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// let mut meta = germ::meta::Meta::new();
+ /// let mut parameters = std::collections::HashMap::new();
+ ///
+ /// parameters.insert("hi".to_string(), "2".to_string());
+ /// parameters.insert("hi2".to_string(), "string=2".to_string());
+ ///
+ /// *meta.parameters_mut() = parameters;
+ /// ```
+ pub fn parameters_mut(&mut self) -> &mut HashMap<String, String> {
+ &mut self.parameters
+ }
+}
diff --git a/src/quick.rs b/src/quick.rs
new file mode 100644
index 0000000..6bc5913
--- /dev/null
+++ b/src/quick.rs
@@ -0,0 +1,75 @@
+// 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
+
+pub enum HeadingLevel {
+ One,
+ Two,
+ Three,
+}
+
+#[must_use]
+pub fn heading(
+ text: &(impl ToString + ?Sized),
+ level: &HeadingLevel,
+) -> String {
+ format!(
+ "{} {}",
+ match level {
+ HeadingLevel::One => "#",
+ HeadingLevel::Two => "##",
+ HeadingLevel::Three => "###",
+ },
+ text.to_string()
+ )
+}
+
+#[must_use]
+pub fn list_item(text: &(impl ToString + ?Sized)) -> String {
+ format!("* {}", text.to_string())
+}
+
+#[must_use]
+pub fn list_items(items: &[&(impl ToString + ?Sized)]) -> String {
+ items
+ .iter()
+ .map(|item| list_item(&item.to_string()))
+ .collect::<Vec<_>>()
+ .join("\n")
+}
+
+#[must_use]
+pub fn link(text: &(impl ToString + ?Sized), location: Option<&str>) -> String {
+ format!(
+ "=> {}{}",
+ text.to_string(),
+ location.map_or_else(String::new, |l| format!(" {l}"))
+ )
+}
+
+#[must_use]
+pub fn block_quote(text: &(impl ToString + ?Sized)) -> String {
+ format!("> {}", text.to_string())
+}
+
+#[must_use]
+pub fn preformatted_text(
+ text: &(impl ToString + ?Sized),
+ alt_text: Option<&str>,
+) -> String {
+ format!("```{}\n{}\n```", alt_text.unwrap_or(""), text.to_string())
+}
diff --git a/src/request.rs b/src/request.rs
new file mode 100644
index 0000000..2c7c273
--- /dev/null
+++ b/src/request.rs
@@ -0,0 +1,76 @@
+// 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
+
+//! Make Gemini requests and get sane, structured results
+
+mod response;
+mod status;
+mod verifier;
+
+#[cfg(feature = "sync")]
+pub mod sync;
+
+use std::io::{Read, Write};
+
+pub use response::Response;
+pub use status::Status;
+pub(crate) use verifier::GermVerifier;
+
+/// 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()) {
+/// Ok(response) => println!("{:?}", response),
+/// Err(_) => {}
+/// }
+/// ```
+///
+/// # Errors
+/// - May error if the URL is invalid
+/// - May error if the TLS write fails
+/// - May error if the TLS read fails
+pub fn request(url: &url::Url) -> anyhow::Result<Response> {
+ let config = rustls::ClientConfig::builder()
+ .with_safe_defaults()
+ .with_custom_certificate_verifier(std::sync::Arc::new(GermVerifier::new()))
+ .with_no_client_auth();
+ let mut connection = rustls::ClientConnection::new(
+ std::sync::Arc::new(config),
+ url.domain().unwrap_or("").try_into()?,
+ )?;
+ let mut stream = std::net::TcpStream::connect(format!(
+ "{}:{}",
+ url.domain().unwrap_or(""),
+ url.port().unwrap_or(1965)
+ ))?;
+ let mut tls = rustls::Stream::new(&mut connection, &mut stream);
+
+ tls.write_all(format!("{url}\r\n").as_bytes())?;
+
+ let mut plain_text = Vec::new();
+
+ tls.read_to_end(&mut plain_text)?;
+
+ Ok(Response::new(
+ &plain_text,
+ tls.conn.negotiated_cipher_suite(),
+ ))
+}
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())
+ }
+}