From 31a0880d8058f17bd1a1d919c2777e85058dc063 Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Sun, 3 Jan 2021 20:02:12 +0000 Subject: [PATCH] wip links - does not compile Signed-off-by: Ian Jackson --- Cargo.lock.example | 1 + Cargo.toml | 1 + daemon/cmdlistener.rs | 20 +++++++++++++++++++- src/bin/otter.rs | 6 +++--- src/commands.rs | 6 +++--- src/global.rs | 17 ++++++++++++++++- src/imports.rs | 1 + src/spec.rs | 36 ++++++++++++++++++++++++++++++++++-- src/sse.rs | 6 ++++-- src/updates.rs | 20 +++++++++++++++++--- 10 files changed, 99 insertions(+), 15 deletions(-) diff --git a/Cargo.lock.example b/Cargo.lock.example index 8d78b041..319d251d 100644 --- a/Cargo.lock.example +++ b/Cargo.lock.example @@ -1622,6 +1622,7 @@ dependencies = [ "toml 0.5.8", "typetag", "uds", + "url 2.2.0", "vecdeque-stableix", ] diff --git a/Cargo.toml b/Cargo.toml index 25383485..8647c62d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,4 +63,5 @@ tera = "0.11" toml = "0.5" typetag = "0.1.6" uds = "0.2" +url = "2" vecdeque-stableix = "1" diff --git a/daemon/cmdlistener.rs b/daemon/cmdlistener.rs index 3a3bc05b..d018d107 100644 --- a/daemon/cmdlistener.rs +++ b/daemon/cmdlistener.rs @@ -435,12 +435,30 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>( let table_size = ig.gs.table_size; let links = ig.links.iter().filter_map( |(k,v)| - Some((k.clone(), v.as_ref()?.clone().0)) + Some((k.clone(), UrlSpec(v.as_ref()?.clone()))) ).collect(); let info = MgmtGameResponseGameInfo { table_size, players, links }; Ok(Resp::Info(info)) })?, + Insn::SetLinks(spec_links) => { + let ig = cs.check_acl(ag, ig, PCH::Instance, &[TP::SetLinks])?.0; + let mut new_links : LinksTable = default(); + for (k,v) in spec_links.drain() { + let url : Url = (&v).try_into()?; + new_links[k] = Some(url.into_string()); + } + ig.links = new_links; + let log = vec![LogEntry { + html: Html(format!("{} set the links to off-server game resources", + &who.0)), + }]; + (U{ log, + pcs: vec![], + raw: vec![ PreparedUpdateEntry::SetLinks ]}, + Fine, eg) + } + ResetPlayerAccess(player) => { let (ig, auth) = cs.check_acl_manip_player_access (ag, ig, player, TP::ResetOthersAccess)?; diff --git a/src/bin/otter.rs b/src/bin/otter.rs index cc963288..a9a5f1f5 100644 --- a/src/bin/otter.rs +++ b/src/bin/otter.rs @@ -866,7 +866,7 @@ mod set_link { None => { let MgmtGameResponseGameInfo { links, .. } = chan.info()?; for (tk, v) in links { - // xxx check syntax + let v : Url = (&v).try_into().context("reparse sererr's UrlSpec")?; match args.kind { None => { println!("{:<10} {}", tk, &v); @@ -884,9 +884,9 @@ mod set_link { let kind = args.kind.unwrap(); chan.alter_game(vec![ if url == "" { - MGI::SetLink { kind, url } - } else { MGI::RemoveLink { kind } + } else { + MGI::SetLink { kind, url: UrlSpec(url) } } ], None)?; }, diff --git a/src/commands.rs b/src/commands.rs index 7408d3eb..09f9eb2c 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -86,9 +86,9 @@ pub enum MgmtGameInstruction { UpdatePlayer { player: PlayerId, details: MgmtPlayerDetails }, LeaveGame(PlayerId), - SetLinks(HashMap), + SetLinks(HashMap), RemoveLink { kind: LinkKind }, - SetLink { kind: LinkKind, url: String }, + SetLink { kind: LinkKind, url: UrlSpec }, ClearLog, SetACL { acl: Acl }, @@ -126,7 +126,7 @@ pub struct AccessTokenReport { pub lines: Vec } pub struct MgmtGameResponseGameInfo { pub table_size: Pos, pub players: SecondarySlotMap, - pub links: Vec<(LinkKind, String)>, + pub links: Vec<(LinkKind, UrlSpec)>, } #[derive(Debug,Clone,Serialize,Deserialize)] diff --git a/src/global.rs b/src/global.rs index 696e7921..db9b950e 100644 --- a/src/global.rs +++ b/src/global.rs @@ -38,7 +38,9 @@ pub struct InstanceName { #[derive(Debug,Clone)] pub struct InstanceRef (Arc>); -pub type LinksTable = EnumMap>; +#[derive(Debug,Clone,Serialize,Deserialize,Default)] +#[serde(transparent)] +pub struct LinksTable(pub EnumMap>); pub struct Instance { pub name: Arc, @@ -453,6 +455,19 @@ impl Display for InstanceName { } } +impl From<&LinksTable> for Html { + fn from(links: &LinksTable) -> Html { + links.iter() + .filter_map(|(k,v)| { + let v = v.as_ref()?; + let url = htmlescape::encode_minimal(v); + Some(Html(format!("{kind}", + url=url, kind=k))) + }) + .join(" | ") + } +} + // ---------- Player and token functionality ---------- impl InstanceAccessDetails diff --git a/src/imports.rs b/src/imports.rs index ff078e8d..c1fd183b 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -74,6 +74,7 @@ pub use serde_with::SerializeDisplay; pub use slotmap::dense::DenseSlotMap; pub use strum::EnumString; pub use thiserror::Error; +pub use url::Url; pub use vecdeque_stableix::Deque as StableIndexVecDeque; pub use zcoord::{self, ZCoord}; diff --git a/src/spec.rs b/src/spec.rs index 4e4b3b25..5b33af9b 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -50,7 +50,13 @@ define_index_type! { #[derive(Debug,Default)] #[repr(transparent)] #[serde(transparent)] -pub struct ColourSpec(String); +pub struct ColourSpec(pub String); + +#[derive(Debug,Default,Clone,Eq,PartialEq,Hash,Ord,PartialOrd)] +#[derive(Serialize,Deserialize)] +#[repr(transparent)] +#[serde(transparent)] +pub struct UrlSpec(pub String); #[derive(Error,Clone,Serialize,Deserialize,Debug)] pub enum SpecError { @@ -64,6 +70,8 @@ pub enum SpecError { AclInvalidAccountGlob, AclEntryOverlappingAllowDeny, InconsistentPieceCount, + BadUrlSyntax, + UrlTooLong, } display_as_debug!{SpecError} @@ -74,7 +82,7 @@ pub struct TableSpec { #[serde(default)] pub players: Vec, pub player_perms: Option>, #[serde(default)] pub acl: Acl, - #[serde(default)] pub links: HashMap, + #[serde(default)] pub links: HashMap, } #[derive(Debug,Serialize,Deserialize)] @@ -108,6 +116,7 @@ pub enum TablePermission { ViewNotSecret, Play, ChangePieces, + SetLinks, ResetOthersAccess, RedeliverOthersAccess, ModifyOtherPlayer, @@ -524,4 +533,27 @@ pub mod implementation { Html(spec.0.clone()) } } + + impl UrlSpec { + const MAX_LEN : usize = 200; + } + + impl TryFrom<&UrlSpec> for Url { + type Error = SpecError; + #[throws(SpecError)] + fn try_from(spec: &UrlSpec) -> Url { + if spec.0.len() > UrlSpec::MAX_LEN { + throw!(SE::UrlTooLong); + } + let base = Url::parse(&config().public_url) + .or_else(|_| Url::parse( + "https://bad-otter-config-public-url.example.net/" + )).unwrap(); + let url = Url::options() + .base_url(Some(&base)) + .parse(&spec.0) + .map_err(|_| SE::BadUrlSyntax)?; + url + } + } } diff --git a/src/sse.rs b/src/sse.rs index 36b1800d..01482159 100644 --- a/src/sse.rs +++ b/src/sse.rs @@ -91,7 +91,9 @@ impl Read for UpdateReader { self.player, self.client, ig.gs.gen, self.to_send)?; } - let iplayer = &mut match ig.iplayers.get_mut(self.player) { + let g = &mut *ig; + let for_json_len = InstanceForJsonLen { links: &g.links }; + let iplayer = &mut match g.iplayers.get_mut(self.player) { Some(x) => x, None => { let data = format!("event: player-gone\n\ @@ -131,7 +133,7 @@ impl Read for UpdateReader { break } }; - let next_len = UPDATE_MAX_FRAMING_SIZE + next.json_len(); + let next_len = UPDATE_MAX_FRAMING_SIZE + next.json_len(&for_json_len); if next_len > buf.len() { if buf.len() != orig_wanted { break } diff --git a/src/updates.rs b/src/updates.rs index db1f9e83..a979c085 100644 --- a/src/updates.rs +++ b/src/updates.rs @@ -48,6 +48,7 @@ pub enum PreparedUpdateEntry { }, SetTableSize(Pos), SetTableColour(Colour), + SetLinks, // we use whatever the most recent is AddPlayer { player: PlayerId, data: DataLoadPlayer }, RemovePlayer { player: PlayerId }, Log (Arc), @@ -115,6 +116,7 @@ enum TransmitUpdateEntry<'u> { SetTableColour(&'u Colour), AddPlayer { player: PlayerId, data: &'u DataLoadPlayer }, RemovePlayer { player: PlayerId }, + SetLinks(Html), #[serde(serialize_with="serialize_logentry")] Log(TransmitUpdateLogEntry<'u>), Error(&'u ErrorSignaledViaUpdate), @@ -145,6 +147,10 @@ impl PlayerUpdatesBuildContext { } } +pub struct InstanceForJsonLen<'i> { + links: &'i LinksTable, +} + impl PlayerUpdates { pub fn new_begin(gs: &GameState) -> PlayerUpdatesBuildContext { let u1 = Arc::new(PreparedUpdate { @@ -181,13 +187,13 @@ impl PlayerUpdates { } impl PreparedUpdate { - pub fn json_len(&self) -> usize { - self.us.iter().map(|u| 20 + u.json_len()).sum() + pub fn json_len(&self, ig: &InstanceForJsonLen) -> usize { + self.us.iter().map(|u| 20 + u.json_len(ig)).sum() } } impl PreparedUpdateEntry { - pub fn json_len(&self) -> usize { + pub fn json_len(&self, ig: &InstanceForJsonLen) -> usize { use PreparedUpdateEntry::*; match self { Piece { ref op, .. } => { @@ -203,6 +209,11 @@ impl PreparedUpdateEntry { SetTableColour(colour) => { colour.0.as_bytes().len() + 50 } + SetLinks => { + ig.links.iter().filter_map( + |(_k,v)| Some(50 + v.as_ref()?.len()) + ).sum::() + 50 + } SetTableSize(_) | RemovePlayer { player:_ } | Error(_,_) => { @@ -552,6 +563,9 @@ impl PreparedUpdate { continue } } + PUE::SetLinks => { + + } }; ents.push(ue); }; -- 2.30.2