1use std::convert::TryFrom;
2use std::net::{SocketAddr, ToSocketAddrs};
3use std::str::FromStr;
4use std::sync::Arc;
5
6use anyhow::{format_err, Result};
8use rustls::{
9 Certificate, ClientConfig, RootCertStore, ServerCertVerified, ServerCertVerifier, TLSError,
10};
11use thiserror::Error;
12use tokio::io::{AsyncReadExt, AsyncWriteExt};
13use tokio::net::TcpStream;
14use tokio_rustls::TlsConnector;
15use url::Url;
16use webpki::DNSNameRef;
17
18const REDIRECT_CAP: usize = 5;
19
20#[derive(Debug, PartialEq)]
25pub enum Status {
26 Input,
28 SensitiveInput,
30 Success,
32 TemporaryRedirect,
34 PermanentRedirect,
36 TemporaryFailure,
38 ServerUnavailable,
40 CgiError,
42 ProxyError,
44 SlowDown,
46 PermanentFailure,
48 NotFound,
50 Gone,
52 ProxyRequestRefused,
54 BadRequest,
56 ClientCertificateRequired,
58 CertificateNotAuthorized,
60 CertificateNotValid,
62}
63
64#[derive(Debug, Error)]
65pub enum ParseStatusError {
66 #[error("invalid status \"{0}\"")]
67 InvalidStatus(String),
68}
69
70impl FromStr for Status {
71 type Err = ParseStatusError;
72
73 fn from_str(s: &str) -> Result<Self, Self::Err> {
74 match s {
75 "10" => Ok(Status::Input),
76 "11" => Ok(Status::SensitiveInput),
77 "20" => Ok(Status::Success),
78 "30" => Ok(Status::TemporaryRedirect),
79 "31" => Ok(Status::PermanentRedirect),
80 "40" => Ok(Status::TemporaryFailure),
81 "41" => Ok(Status::ServerUnavailable),
82 "42" => Ok(Status::CgiError),
83 "43" => Ok(Status::ProxyError),
84 "44" => Ok(Status::SlowDown),
85 "50" => Ok(Status::PermanentFailure),
86 "51" => Ok(Status::NotFound),
87 "52" => Ok(Status::Gone),
88 "53" => Ok(Status::ProxyRequestRefused),
89 "59" => Ok(Status::BadRequest),
90 "60" => Ok(Status::ClientCertificateRequired),
91 "61" => Ok(Status::CertificateNotAuthorized),
92 "62" => Ok(Status::CertificateNotValid),
93 _ => Err(ParseStatusError::InvalidStatus(s.to_string())),
94 }
95 }
96}
97
98#[derive(Debug, PartialEq)]
103pub struct Header {
104 pub status: Status,
106
107 pub meta: String,
109}
110
111#[derive(Debug, Error)]
112pub enum ParseHeaderError {
113 #[error("missing status")]
114 MissingStatus,
115 #[error("missing meta")]
116 MissingMeta,
117 #[error(transparent)]
118 InvalidStatus(#[from] ParseStatusError),
119}
120
121impl FromStr for Header {
122 type Err = ParseHeaderError;
123
124 fn from_str(s: &str) -> Result<Self, Self::Err> {
125 let parts: Vec<&str> = s.trim().splitn(2, ' ').collect();
126
127 let status: Status = parts
128 .get(0)
129 .ok_or(ParseHeaderError::MissingStatus)?
130 .parse()?;
131 let meta = parts.get(1).ok_or(ParseHeaderError::MissingMeta)?;
132
133 Ok(Header {
134 status,
135 meta: meta.to_string(),
136 })
137 }
138}
139
140#[derive(Debug)]
145pub struct Page {
146 pub url: Url,
148
149 pub header: Header,
151
152 pub body: Option<String>,
154}
155
156pub enum ServerTLSValidation {
157 SelfSigned(CertificateFingerprint),
158 Chained,
159}
160
161pub struct CertificateFingerprint {
162 digest: [u8; ring::digest::SHA256_OUTPUT_LEN],
163 not_after: i64,
164}
165
166fn map_sig_to_webpki_err(e: x509_signature::Error) -> webpki::Error {
167 match e {
168 x509_signature::Error::UnsupportedCertVersion => webpki::Error::UnsupportedCertVersion,
169 x509_signature::Error::UnsupportedSignatureAlgorithm => {
170 webpki::Error::UnsupportedSignatureAlgorithm
171 }
172 x509_signature::Error::UnsupportedSignatureAlgorithmForPublicKey => {
173 webpki::Error::UnsupportedSignatureAlgorithmForPublicKey
174 }
175 x509_signature::Error::InvalidSignatureForPublicKey => {
176 webpki::Error::InvalidSignatureForPublicKey
177 }
178 x509_signature::Error::SignatureAlgorithmMismatch => {
179 webpki::Error::SignatureAlgorithmMismatch
180 }
181 x509_signature::Error::BadDER => webpki::Error::BadDER,
182 x509_signature::Error::BadDERTime => webpki::Error::BadDERTime,
183 x509_signature::Error::CertNotValidYet => webpki::Error::CertNotValidYet,
184 x509_signature::Error::CertExpired => webpki::Error::CertExpired,
185 x509_signature::Error::InvalidCertValidity => webpki::Error::InvalidCertValidity,
186 x509_signature::Error::UnknownIssuer => webpki::Error::UnknownIssuer,
187 _ => webpki::Error::UnknownIssuer,
190 }
191}
192
193fn unix_now() -> Result<i64, rustls::TLSError> {
194 let now = std::time::SystemTime::now();
195 let unix_now = now
196 .duration_since(std::time::UNIX_EPOCH)
197 .map_err(|_| TLSError::FailedToGetCurrentTime)?
198 .as_secs();
199
200 i64::try_from(unix_now).map_err(|_| TLSError::FailedToGetCurrentTime)
201}
202
203fn verify_selfsigned_certificate(
204 cert: &Certificate,
205 _dns_name: DNSNameRef<'_>,
206 now: i64,
207) -> Result<ServerCertVerified, x509_signature::Error> {
208 let xcert = x509_signature::parse_certificate(cert.as_ref())?;
209 xcert.valid_at_timestamp(now)?;
210 xcert.check_self_issued()?;
211 Ok(ServerCertVerified::assertion())
214}
215
216struct ExpectSelfSignedVerifier {
217 webpki: rustls::WebPKIVerifier,
218 fingerprint: CertificateFingerprint,
219}
220
221impl ServerCertVerifier for ExpectSelfSignedVerifier {
222 fn verify_server_cert(
223 &self,
224 roots: &RootCertStore,
225 presented_certs: &[Certificate],
226 dns_name: DNSNameRef<'_>,
227 ocsp_response: &[u8],
228 ) -> Result<ServerCertVerified, TLSError> {
229 if presented_certs.len() == 1 {
231 let now = unix_now()?;
232
233 if now > self.fingerprint.not_after {
234 let dig =
236 ring::digest::digest(&ring::digest::SHA256, presented_certs[0].0.as_ref());
237 if let Ok(()) = ring::constant_time::verify_slices_are_equal(
238 dig.as_ref(),
239 &self.fingerprint.digest,
240 ) {
241 return Ok(ServerCertVerified::assertion());
242 }
243 } else {
244 return verify_selfsigned_certificate(&presented_certs[0], dns_name, now)
245 .map_err(map_sig_to_webpki_err)
246 .map_err(rustls::TLSError::WebPKIError);
247 }
248 }
249
250 let verified =
251 self.webpki
252 .verify_server_cert(roots, presented_certs, dns_name, ocsp_response)?;
253
254 Ok(verified)
255 }
256}
257
258struct PossiblySelfSignedVerifier {
259 webpki: rustls::WebPKIVerifier,
260}
261
262impl ServerCertVerifier for PossiblySelfSignedVerifier {
263 fn verify_server_cert(
264 &self,
265 roots: &RootCertStore,
266 presented_certs: &[Certificate],
267 dns_name: DNSNameRef<'_>,
268 ocsp_response: &[u8],
269 ) -> Result<ServerCertVerified, TLSError> {
270 if presented_certs.len() == 1 {
273 let verified =
274 verify_selfsigned_certificate(&presented_certs[0], dns_name, unix_now()?)
275 .map_err(map_sig_to_webpki_err)
276 .map_err(TLSError::WebPKIError)?;
277
278 return Ok(verified);
279 }
280
281 let verified =
282 self.webpki
283 .verify_server_cert(roots, presented_certs, dns_name, ocsp_response)?;
284
285 Ok(verified)
286 }
287}
288
289async fn build_tls_config<'a>(
290 validation: Option<ServerTLSValidation>,
291) -> Result<Arc<ClientConfig>> {
292 let mut config = ClientConfig::new();
293 config
294 .root_store
295 .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
296 match validation {
297 None => {
298 config
299 .dangerous()
300 .set_certificate_verifier(Arc::new(PossiblySelfSignedVerifier {
301 webpki: rustls::WebPKIVerifier::new(),
302 }));
303 }
304 Some(ServerTLSValidation::SelfSigned(fingerprint)) => {
305 config
306 .dangerous()
307 .set_certificate_verifier(Arc::new(ExpectSelfSignedVerifier {
308 fingerprint,
309 webpki: rustls::WebPKIVerifier::new(),
310 }));
311 }
312 _ => {}
313 }
314
315 Ok(Arc::new(config))
316}
317
318#[derive(Debug, Error)]
319pub enum FetchPageError {
320 #[error("unsupported scheme \"{0}\", only gemini is supported")]
321 UnsupportedScheme(String),
322 #[error("missing host in URL \"{0}\"")]
323 MissingHost(String),
324 #[error("failed to resolve URL \"{0}\"")]
325 FailedToResolve(String),
326 #[error("response is missing its header")]
327 MissingHeader,
328}
329
330impl Page {
331 pub async fn fetch(url: &Url, tls_validation: Option<ServerTLSValidation>) -> Result<Page> {
335 let host = url
336 .host_str()
337 .ok_or_else(|| FetchPageError::MissingHost(url.to_string()))?;
338
339 let port = url.port().unwrap_or(1965);
340
341 let addr = format!("{}:{}", host, port)
342 .to_socket_addrs()?
343 .next()
344 .ok_or_else(|| FetchPageError::FailedToResolve(url.to_string()))?;
345
346 Self::fetch_from(url, addr, tls_validation).await
347 }
348
349 pub async fn fetch_from(
353 url: &Url,
354 addr: SocketAddr,
355 tls_validation: Option<ServerTLSValidation>,
356 ) -> Result<Page> {
357 if url.scheme() != "gemini" {
358 return Err(FetchPageError::UnsupportedScheme(url.to_string()).into());
359 }
360
361 let host = url
362 .host_str()
363 .ok_or_else(|| FetchPageError::MissingHost(url.to_string()))?;
364
365 let dns_name = DNSNameRef::try_from_ascii_str(&host)?;
366 let socket = TcpStream::connect(&addr).await?;
367 let config = TlsConnector::from(build_tls_config(tls_validation).await?);
368
369 let mut socket = config.connect(dns_name, socket).await?;
370
371 socket.write_all(format!("{}\r\n", url).as_bytes()).await?;
372
373 let mut data = Vec::new();
374 socket.read_to_end(&mut data).await?;
375
376 let mut response = String::from_utf8(data)?.replace("\r\n", "\n");
377
378 let (header, body) = if let Some(i) = response.find('\n') {
379 let remainder = response.split_off(i + 1);
380 (response.parse::<Header>()?, remainder)
381 } else {
382 return Err(FetchPageError::MissingHeader.into());
383 };
384
385 Ok(Page {
386 url: url.clone(),
387 header,
388 body: if body.is_empty() { None } else { Some(body) },
389 })
390 }
391
392 pub async fn fetch_and_handle_redirects(url: &Url) -> Result<Page> {
394 let mut url = url.clone();
395
396 let mut attempts = 0;
397 while attempts < REDIRECT_CAP {
398 let page = Page::fetch(&url, None).await?;
400
401 if let Status::TemporaryRedirect | Status::PermanentRedirect = page.header.status {
402 attempts += 1;
403 url = Url::parse(&page.header.meta)?;
404 } else {
405 return Ok(page);
406 }
407 }
408
409 Err(format_err!(
410 "reached maximum redirect cap of {}",
411 REDIRECT_CAP
412 ))
413 }
414}