diff options
| author | Fuwn <[email protected]> | 2022-06-14 08:45:09 +0000 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2022-06-14 08:45:09 +0000 |
| commit | b5703a59c59bfe95143cf8dc6cc54f20f67fb4fd (patch) | |
| tree | 44022e3944119c39df5b9082aaac3a77841c8c10 /src | |
| parent | fix(ast): list ast construction (diff) | |
| download | germ-b5703a59c59bfe95143cf8dc6cc54f20f67fb4fd.tar.xz germ-b5703a59c59bfe95143cf8dc6cc54f20f67fb4fd.zip | |
feat(macros): general utility macros
Diffstat (limited to 'src')
| -rw-r--r-- | src/ast.rs | 446 | ||||
| -rw-r--r-- | src/convert.rs | 69 | ||||
| -rw-r--r-- | src/convert/html.rs | 75 | ||||
| -rw-r--r-- | src/convert/markdown.rs | 77 | ||||
| -rw-r--r-- | src/lib.rs | 43 | ||||
| -rw-r--r-- | src/meta.rs | 162 | ||||
| -rw-r--r-- | src/request.rs | 73 | ||||
| -rw-r--r-- | src/request/response.rs | 71 | ||||
| -rw-r--r-- | src/request/status.rs | 110 | ||||
| -rw-r--r-- | src/request/verifier.rs | 39 |
10 files changed, 0 insertions, 1165 deletions
diff --git a/src/ast.rs b/src/ast.rs deleted file mode 100644 index eae0683..0000000 --- a/src/ast.rs +++ /dev/null @@ -1,446 +0,0 @@ -// 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 - -/// 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)] -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, -} - -/// An AST structure which contains an AST tree -/// -/// # Example -/// -/// ```rust -/// let _ = germ::ast::Ast::from_string(r#"=> gemini://gem.rest/ GemRest"#); -/// ``` -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_string(source: &str) -> Self { - let mut ast = vec![]; - let mut in_preformatted = false; - let mut in_list = false; - 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 = "".to_string(); - - for node in &self.inner { - match node { - Node::Text(text) => gemtext.push_str(&format!("{}\n", text)), - Node::Link { - to, - text, - } => - gemtext.push_str(&format!( - "=> {}{}\n", - to, - text - .clone() - .map_or_else(|| "".to_string(), |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!("> {}\n", text)), - Node::PreformattedText { - alt_text, - text, - } => - gemtext.push_str(&format!( - "```{}\n{}```\n", - alt_text.clone().unwrap_or_else(|| "".to_string()), - 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 = match line.get(0..3) { - Some(root) => - if root.contains("###") { - 3 - } else if root.contains("##") { - 2 - } else if root.contains('#') { - 1 - } else { - 0 - }, - None => 0, - }; - - 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(); - line = lines.next().unwrap(); - } 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!("{}\n", line)); - - line = lines.next().unwrap(); - } 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/convert.rs b/src/convert.rs deleted file mode 100644 index c68696e..0000000 --- a/src/convert.rs +++ /dev/null @@ -1,69 +0,0 @@ -// 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; - -/// Different targets to convert Gemtext to -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: &str, target: &Target) -> String { - from_ast(&Ast::from_string(source), target) -} diff --git a/src/convert/html.rs b/src/convert/html.rs deleted file mode 100644 index 7b1cafe..0000000 --- a/src/convert/html.rs +++ /dev/null @@ -1,75 +0,0 @@ -// 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>{}</p>", text)), - 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>{}</li>", i)) - .collect::<Vec<String>>() - .join("\n") - )), - Node::Blockquote(text) => - html.push_str(&format!("<blockquote>{}</blockquote>", text)), - Node::PreformattedText { - text, .. - } => { - html.push_str(&format!("<pre>{}</pre>", text)); - } - Node::Whitespace => {} - } - } - - html -} diff --git a/src/convert/markdown.rs b/src/convert/markdown.rs deleted file mode 100644 index a38da9f..0000000 --- a/src/convert/markdown.rs +++ /dev/null @@ -1,77 +0,0 @@ -// 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!("{}\n", text)), - Node::Link { - to, - text, - } => - markdown.push_str(&*text.clone().map_or_else( - || format!("<{}>\n", to), - |text| format!("[{}]({})\n", text, to), - )), - 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!("> {}\n", text)), - Node::PreformattedText { - alt_text, - text, - } => { - markdown.push_str(&format!( - "```{}\n{}```\n", - alt_text.clone().unwrap_or_else(|| "".to_string()), - text - )); - } - Node::Whitespace => markdown.push('\n'), - } - } - - markdown -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 83aef0e..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,43 +0,0 @@ -// 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 -)] -#![doc = include_str!("../README.md")] -#![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; diff --git a/src/meta.rs b/src/meta.rs deleted file mode 100644 index f8a9dfb..0000000 --- a/src/meta.rs +++ /dev/null @@ -1,162 +0,0 @@ -// 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::collections::HashMap; - -/// Structure-ize a Gemini response's meta section into it's mime type and it's -/// parameters. -#[derive(Debug, Default)] -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() { - "".to_string() - } 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: &str) -> Self { - 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) -> &str { &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/request.rs b/src/request.rs deleted file mode 100644 index 07d3552..0000000 --- a/src/request.rs +++ /dev/null @@ -1,73 +0,0 @@ -// 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; - -use std::io::{Read, Write}; - -pub use response::Response; -pub use status::Status; -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!("{}\r\n", url).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 deleted file mode 100644 index 5e1f436..0000000 --- a/src/request/response.rs +++ /dev/null @@ -1,71 +0,0 @@ -// 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 rustls::SupportedCipherSuite; - -use crate::request::Status; - -#[derive(Debug)] -pub struct Response { - status: Status, - meta: String, - content: Option<String>, - size: usize, - suite: Option<SupportedCipherSuite>, -} -impl Response { - pub(super) 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.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) -> &str { &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 deleted file mode 100644 index f46059a..0000000 --- a/src/request/status.rs +++ /dev/null @@ -1,110 +0,0 @@ -// 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)] -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/verifier.rs b/src/request/verifier.rs deleted file mode 100644 index d6511c3..0000000 --- a/src/request/verifier.rs +++ /dev/null @@ -1,39 +0,0 @@ -// 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}; - -pub(super) 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()) - } -} |