finger_protocol/lib.rs
1//! # finger-protocol
2//!
3//! Finger ([RFC 1288](https://datatracker.ietf.org/doc/html/rfc1288), port 79)
4//! and its successor WebFinger
5//! ([RFC 7033](https://www.rfc-editor.org/rfc/rfc7033.html)), which honours it
6//! by name.
7//!
8//! Both answer the same question, thirty years apart: *who is this person?*
9//! Finger answers with whatever text the host felt like printing. WebFinger
10//! answers with a JSON Resource Descriptor, which is why it, and not finger,
11//! is what resolves `@alice@example.social` on the fediverse.
12//!
13//! ## Two protocols, two features
14//!
15//! | | Module | Feature | Pulls |
16//! |---|---|---|---|
17//! | Finger, RFC 1288 | [`client`] | `client` | tokio, url |
18//! | Finger, serving | [`server`] | `server` *(off)* | tokio, log |
19//! | WebFinger, RFC 7033 | [`webfinger`] | `webfinger` | serde, serde_json, percent-encoding |
20//!
21//! Both are on by default. Take `default-features = false` with just one when
22//! the other is dead weight.
23//!
24//! ## WebFinger without an HTTP client
25//!
26//! WebFinger rides on HTTPS and this crate deliberately contains no HTTP
27//! stack: it builds the request URL and parses the response, and the GET
28//! itself belongs to the caller, who already has an HTTP client and opinions
29//! about timeouts, redirects, and TLS. See [`webfinger`].
30//!
31//! Fingering a host is [`client::fetch`], documented on that module so the
32//! example stays honest when the `client` feature is off.
33
34#![forbid(unsafe_code)]
35
36#[cfg(feature = "client")]
37pub mod client;
38
39#[cfg(feature = "server")]
40pub mod server;
41#[cfg(feature = "webfinger")]
42pub mod webfinger;
43
44#[cfg(feature = "client")]
45pub use client::{ClientError, DEFAULT_PORT, Response, fetch, query};
46
47#[cfg(feature = "server")]
48pub use server::{ServerConfig, serve};
49#[cfg(feature = "webfinger")]
50pub use webfinger::{Jrd, Link, MEDIA_TYPE, acct, request_url};
51
52/// One finger request.
53#[derive(Clone, Debug, Default, PartialEq, Eq)]
54pub struct Query {
55 /// The user to ask about. `None` requests the host's listing.
56 pub user: Option<String>,
57 /// RFC 1288's `/W` switch, asking the server for its longer answer. Servers
58 /// are free to ignore it.
59 pub verbose: bool,
60}
61
62impl Query {
63 /// A query for one user.
64 pub fn user(name: impl Into<String>) -> Self {
65 Self {
66 user: Some(name.into()),
67 verbose: false,
68 }
69 }
70
71 /// The same query with RFC 1288's `/W` switch set.
72 pub fn verbose(mut self) -> Self {
73 self.verbose = true;
74 self
75 }
76
77 /// The wire form: `{W}{S}{U}{C}` in RFC 1288's grammar.
78 ///
79 /// ```
80 /// use finger_protocol::Query;
81 ///
82 /// assert_eq!(Query::user("alice").wire(), "alice\r\n");
83 /// assert_eq!(Query::user("alice").verbose().wire(), "/W alice\r\n");
84 /// assert_eq!(Query::default().wire(), "\r\n");
85 /// ```
86 pub fn wire(&self) -> String {
87 let user = self.user.as_deref().unwrap_or("");
88 match (self.verbose, user.is_empty()) {
89 (true, true) => "/W\r\n".to_string(),
90 (true, false) => format!("/W {user}\r\n"),
91 (false, _) => format!("{user}\r\n"),
92 }
93 }
94}