Something went wrong. Try again.
atproto git client
Something went wrong. Try again.
Rust
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586//! The HTML atgc emits, and the helpers every page of it shares.//!//! Two kinds of page live here. [`pages`] is what a browser lands on when//! `atgc auth login` finishes, which is the only part of atgc a person sees//! rendered rather than printed; `site` is atgc.codes, which `atgc www`//! writes out and nothing at runtime ever asks for. That one is behind the//! off-by-default `www` feature, so an installed atgc has neither the module//! nor the command — see the `[features]` note in `Cargo.toml`.//!//! The first of them used to live inside the OAuth client, next to the//! socket that serves it, which put markup and SVG in the middle of a module//! whose subject is tokens, and made the field of DNA it hangs on an import//! from a *command* module. Both are the same mistake in different//! directions: a page is neither a client nor a command, it is a document.//!//! So documents live here, and what they have in common lives in [`brand`]://! one palette, one mark, one favicon, one field. Two pages that each//! declared their own `--a` is exactly the drift `brand/build.py --check`//! catches on the raster side and nothing was catching here.//!//! # What is not here//!//! Writing a page to a socket. `respond_html` stays in//! [`crate::clients::atproto::oauth`], because putting bytes on a `TcpStream`//! is talking to a counterpart and that is what `clients/` is for. This//! module builds strings and opens nothing.//!//! The art is not here either — [`crate::art`] draws the field for both the//! terminal and the browser, and [`field`] is only its markup rendering.
pub(crate) mod brand;pub(crate) mod field;pub(crate) mod pages;#[cfg(any(feature = "www", test))]pub(crate) mod site;
/// Text bound for a page.////// A handle and a DID are constrained enough to be safe as they stand, but a/// display name is whatever the account put in its Bluesky profile and the/// avatar is a URL from the same place, so both arrive as somebody else's/// string. Escaped rather than trusted: the page is served from a loopback/// port this process opened while holding fresh tokens, and markup smuggled/// through a profile has no business running there.////// Named `escape` and not `escape_html` now that it lives in a module called/// `html` — `html::escape` says it once.pub(crate) fn escape(text: &str) -> String { let mut out = String::with_capacity(text.len()); for ch in text.chars() { match ch { '&' => out.push_str("&"), '<' => out.push_str("<"), '>' => out.push_str(">"), '"' => out.push_str("""), '\'' => out.push_str("'"), _ => out.push(ch), } } out}
#[cfg(test)]mod tests { use super::escape;
/// All five, including the two that only matter inside an attribute: /// the avatar URL is interpolated into one. #[test] fn every_character_that_can_end_a_context_is_escaped() { assert_eq!( escape(r#"<img src="x" onerror='alert(1)'>&"#), "<img src="x" onerror='alert(1)'>&" ); }
/// Ordinary text is returned unchanged, display names included: a /// person whose name is written in another script must not arrive as /// entities. #[test] fn ordinary_text_passes_through() { assert_eq!(escape("Ada Lovelace"), "Ada Lovelace"); assert_eq!(escape("さくら"), "さくら"); }}