Skip to main content

trotter/
response.rs

1use crate::error::ResponseErr;
2use openssl::{hash::MessageDigest, x509::X509};
3use std::{fs::File, io::Write, path::PathBuf};
4
5/// A gemini response.
6#[derive(Debug)]
7pub struct Response {
8	pub status: u8,
9	pub meta: String,
10	pub content: Vec<u8>,
11	/// The certificate of the responding server.
12	pub certificate: X509,
13}
14
15type Result<T> = std::result::Result<T, ResponseErr>;
16
17impl Response {
18	/// Returns true if the response is gemtext.
19	pub fn is_gemtext(&self) -> bool {
20		if let Some(pos) = self.meta.find("text/gemini") {
21			if pos == 0 {
22				return true;
23			}
24		}
25		false
26	}
27
28	/// Return gemtext (if any) inside this response.
29	pub fn gemtext(&self) -> Result<String> {
30		self.require_status(20)?;
31
32		if self.is_gemtext() {
33			return self.text();
34		}
35
36		Err(ResponseErr::UnexpectedFiletype(
37			"text/gemini".to_string(),
38			self.meta.clone(),
39		))
40	}
41
42	/// Return utf8 text (if any) inside this response, regardless of mimetype.
43	pub fn text(&self) -> Result<String> {
44		self.require_status(20)?;
45		Ok(std::str::from_utf8(&self.content)
46			.map_err(ResponseErr::Utf8Content)?
47			.to_string())
48	}
49
50	/// Save response to file.
51	pub fn save(&self, file: &mut File) -> Result<()> {
52		self.require_status(20)?;
53		file.write_all(&self.content)
54			.map_err(ResponseErr::FileWrite)?;
55		Ok(())
56	}
57
58	/// Save response to path.
59	pub fn save_to_path(&self, path: impl Into<PathBuf>) -> Result<()> {
60		self.require_status(20)?;
61
62		let path = path.into();
63		let mut file = File::create(path).map_err(ResponseErr::FileCreate)?;
64		file.write_all(&self.content)
65			.map_err(ResponseErr::FileWrite)?;
66		Ok(())
67	}
68
69	/// Return the server's certificate pem
70	pub fn certificate_pem(&self) -> Result<String> {
71		Ok(std::str::from_utf8(
72			&self
73				.certificate
74				.to_pem()
75				.map_err(ResponseErr::SerializingToPem)?,
76		)
77		.map_err(ResponseErr::PemInvalidUtf8)?
78		.to_string())
79	}
80
81	/// Returns a human-readable string displaying info about the
82	/// server's certificate.
83	///
84	/// *primarily for debugging purposes*
85	pub fn certificate_info(&self) -> Result<String> {
86		Ok(std::str::from_utf8(
87			&self
88				.certificate
89				.to_text()
90				.map_err(ResponseErr::FailedToInspectCert)?,
91		)
92		.map_err(ResponseErr::CertInfoIsntValidUtf8)?
93		.to_string())
94	}
95
96	/// Return the server certificate's fingerprint
97	pub fn certificate_fingerprint(&self) -> Result<String> {
98		use std::fmt::Write;
99		Ok(self
100			.certificate
101			.digest(MessageDigest::sha256())
102			.map_err(ResponseErr::FailedToFingerprint)?
103			.iter()
104			.fold(String::new(), |mut out, x| {
105				let _ = write!(out, "{:02x}", x);
106				out
107			}))
108	}
109
110	/// (private) Error if `s` doesn't match the status
111	fn require_status(&self, s: u8) -> Result<()> {
112		if self.status != s {
113			Err(ResponseErr::UnexpectedStatus {
114				expected: s.into(),
115				received: self.status.into(),
116				meta: self.meta.clone(),
117			})
118		} else {
119			Ok(())
120		}
121	}
122}