"toml 0.5.8",
"typetag",
"uds",
+ "url 2.2.0",
"vecdeque-stableix",
]
toml = "0.5"
typetag = "0.1.6"
uds = "0.2"
+url = "2"
vecdeque-stableix = "1"
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)?;
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);
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)?;
},
UpdatePlayer { player: PlayerId, details: MgmtPlayerDetails },
LeaveGame(PlayerId),
- SetLinks(HashMap<LinkKind,String>),
+ SetLinks(HashMap<LinkKind, UrlSpec>),
RemoveLink { kind: LinkKind },
- SetLink { kind: LinkKind, url: String },
+ SetLink { kind: LinkKind, url: UrlSpec },
ClearLog,
SetACL { acl: Acl<TablePermission> },
pub struct MgmtGameResponseGameInfo {
pub table_size: Pos,
pub players: SecondarySlotMap<PlayerId, MgmtPlayerInfo>,
- pub links: Vec<(LinkKind, String)>,
+ pub links: Vec<(LinkKind, UrlSpec)>,
}
#[derive(Debug,Clone,Serialize,Deserialize)]
#[derive(Debug,Clone)]
pub struct InstanceRef (Arc<Mutex<InstanceContainer>>);
-pub type LinksTable = EnumMap<LinkKind, Option<Html>>;
+#[derive(Debug,Clone,Serialize,Deserialize,Default)]
+#[serde(transparent)]
+pub struct LinksTable(pub EnumMap<LinkKind, Option<String>>);
pub struct Instance {
pub name: Arc<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!("<a href={url}>{kind}</a>",
+ url=url, kind=k)))
+ })
+ .join(" | ")
+ }
+}
+
// ---------- Player and token functionality ----------
impl<Id> InstanceAccessDetails<Id>
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};
#[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 {
AclInvalidAccountGlob,
AclEntryOverlappingAllowDeny,
InconsistentPieceCount,
+ BadUrlSyntax,
+ UrlTooLong,
}
display_as_debug!{SpecError}
#[serde(default)] pub players: Vec<TablePlayerSpec>,
pub player_perms: Option<HashSet<TablePermission>>,
#[serde(default)] pub acl: Acl<TablePermission>,
- #[serde(default)] pub links: HashMap<LinkKind, String>,
+ #[serde(default)] pub links: HashMap<LinkKind, UrlSpec>,
}
#[derive(Debug,Serialize,Deserialize)]
ViewNotSecret,
Play,
ChangePieces,
+ SetLinks,
ResetOthersAccess,
RedeliverOthersAccess,
ModifyOtherPlayer,
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
+ }
+ }
}
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\
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 }
},
SetTableSize(Pos),
SetTableColour(Colour),
+ SetLinks, // we use whatever the most recent is
AddPlayer { player: PlayerId, data: DataLoadPlayer },
RemovePlayer { player: PlayerId },
Log (Arc<CommittedLogEntry>),
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),
}
}
+pub struct InstanceForJsonLen<'i> {
+ links: &'i LinksTable,
+}
+
impl PlayerUpdates {
pub fn new_begin(gs: &GameState) -> PlayerUpdatesBuildContext {
let u1 = Arc::new(PreparedUpdate {
}
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, .. } => {
SetTableColour(colour) => {
colour.0.as_bytes().len() + 50
}
+ SetLinks => {
+ ig.links.iter().filter_map(
+ |(_k,v)| Some(50 + v.as_ref()?.len())
+ ).sum::<usize>() + 50
+ }
SetTableSize(_) |
RemovePlayer { player:_ } |
Error(_,_) => {
continue
}
}
+ PUE::SetLinks => {
+
+ }
};
ents.push(ue);
};