mirror of
https://github.com/matrix-construct/construct
synced 2024-10-01 21:28:53 +02:00
2324 lines
41 KiB
C++
2324 lines
41 KiB
C++
|
// Matrix Construct
|
||
|
//
|
||
|
// Copyright (C) Matrix Construct Developers, Authors & Contributors
|
||
|
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
|
||
|
//
|
||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||
|
// purpose with or without fee is hereby granted, provided that the above
|
||
|
// copyright notice and this permission notice is present in all copies. The
|
||
|
// full license for this software is available in the LICENSE file.
|
||
|
|
||
|
size_t
|
||
|
ircd::m::room::purge(const room &room)
|
||
|
{
|
||
|
size_t ret(0);
|
||
|
db::txn txn
|
||
|
{
|
||
|
*m::dbs::events
|
||
|
};
|
||
|
|
||
|
room.for_each([&txn, &ret]
|
||
|
(const m::event::idx &idx)
|
||
|
{
|
||
|
const m::event::fetch event
|
||
|
{
|
||
|
idx
|
||
|
};
|
||
|
|
||
|
m::dbs::write_opts opts;
|
||
|
opts.op = db::op::DELETE;
|
||
|
opts.event_idx = idx;
|
||
|
m::dbs::write(txn, event, opts);
|
||
|
++ret;
|
||
|
});
|
||
|
|
||
|
txn();
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ircd::m::room
|
||
|
ircd::m::create(const id::room &room_id,
|
||
|
const id::user &creator,
|
||
|
const string_view &preset)
|
||
|
{
|
||
|
return create(createroom
|
||
|
{
|
||
|
{ "room_id", room_id },
|
||
|
{ "creator", creator },
|
||
|
{ "preset", preset },
|
||
|
});
|
||
|
}
|
||
|
|
||
|
ircd::m::event::id::buf
|
||
|
ircd::m::invite(const room &room,
|
||
|
const id::user &target,
|
||
|
const id::user &sender)
|
||
|
{
|
||
|
json::iov content;
|
||
|
return invite(room, target, sender, content);
|
||
|
}
|
||
|
|
||
|
ircd::m::event::id::buf
|
||
|
ircd::m::invite(const room &room,
|
||
|
const id::user &target,
|
||
|
const id::user &sender,
|
||
|
json::iov &content)
|
||
|
{
|
||
|
using prototype = event::id::buf (const m::room &, const id::user &, const id::user &, json::iov &);
|
||
|
|
||
|
static mods::import<prototype> call
|
||
|
{
|
||
|
"client_rooms", "ircd::m::invite"
|
||
|
};
|
||
|
|
||
|
return call(room, target, sender, content);
|
||
|
}
|
||
|
|
||
|
ircd::m::event::id::buf
|
||
|
ircd::m::redact(const room &room,
|
||
|
const id::user &sender,
|
||
|
const id::event &event_id,
|
||
|
const string_view &reason)
|
||
|
{
|
||
|
json::iov event;
|
||
|
const json::iov::push push[]
|
||
|
{
|
||
|
{ event, { "type", "m.room.redaction" }},
|
||
|
{ event, { "sender", sender }},
|
||
|
{ event, { "redacts", event_id }},
|
||
|
};
|
||
|
|
||
|
json::iov content;
|
||
|
const json::iov::set _reason
|
||
|
{
|
||
|
content, !empty(reason),
|
||
|
{
|
||
|
"reason", [&reason]() -> json::value
|
||
|
{
|
||
|
return reason;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
return commit(room, event, content);
|
||
|
}
|
||
|
|
||
|
ircd::m::event::id::buf
|
||
|
ircd::m::notice(const room &room,
|
||
|
const string_view &body)
|
||
|
{
|
||
|
return message(room, me.user_id, body, "m.notice");
|
||
|
}
|
||
|
|
||
|
ircd::m::event::id::buf
|
||
|
ircd::m::notice(const room &room,
|
||
|
const m::id::user &sender,
|
||
|
const string_view &body)
|
||
|
{
|
||
|
return message(room, sender, body, "m.notice");
|
||
|
}
|
||
|
|
||
|
ircd::m::event::id::buf
|
||
|
ircd::m::msghtml(const room &room,
|
||
|
const m::id::user &sender,
|
||
|
const string_view &html,
|
||
|
const string_view &alt,
|
||
|
const string_view &msgtype)
|
||
|
{
|
||
|
return message(room, sender,
|
||
|
{
|
||
|
{ "msgtype", msgtype },
|
||
|
{ "format", "org.matrix.custom.html" },
|
||
|
{ "body", { alt?: html, json::STRING } },
|
||
|
{ "formatted_body", { html, json::STRING } },
|
||
|
});
|
||
|
}
|
||
|
|
||
|
ircd::m::event::id::buf
|
||
|
ircd::m::message(const room &room,
|
||
|
const m::id::user &sender,
|
||
|
const string_view &body,
|
||
|
const string_view &msgtype)
|
||
|
{
|
||
|
return message(room, sender,
|
||
|
{
|
||
|
{ "body", { body, json::STRING } },
|
||
|
{ "msgtype", { msgtype, json::STRING } },
|
||
|
});
|
||
|
}
|
||
|
|
||
|
ircd::m::event::id::buf
|
||
|
ircd::m::message(const room &room,
|
||
|
const m::id::user &sender,
|
||
|
const json::members &contents)
|
||
|
{
|
||
|
return send(room, sender, "m.room.message", contents);
|
||
|
}
|
||
|
|
||
|
#pragma GCC diagnostic push
|
||
|
#pragma GCC diagnostic ignored "-Wstack-usage="
|
||
|
ircd::m::event::id::buf
|
||
|
__attribute__((stack_protect))
|
||
|
ircd::m::send(const room &room,
|
||
|
const m::id::user &sender,
|
||
|
const string_view &type,
|
||
|
const string_view &state_key,
|
||
|
const json::members &contents)
|
||
|
{
|
||
|
const size_t contents_count
|
||
|
{
|
||
|
std::min(contents.size(), json::object::max_sorted_members)
|
||
|
};
|
||
|
|
||
|
json::iov _content;
|
||
|
json::iov::push content[contents_count]; // 48B each
|
||
|
return send(room, sender, type, state_key, make_iov(_content, content, contents_count, contents));
|
||
|
}
|
||
|
#pragma GCC diagnostic pop
|
||
|
|
||
|
#pragma GCC diagnostic push
|
||
|
#pragma GCC diagnostic ignored "-Wstack-usage="
|
||
|
ircd::m::event::id::buf
|
||
|
__attribute__((stack_protect))
|
||
|
ircd::m::send(const room &room,
|
||
|
const m::id::user &sender,
|
||
|
const string_view &type,
|
||
|
const string_view &state_key,
|
||
|
const json::object &contents)
|
||
|
{
|
||
|
const size_t contents_count
|
||
|
{
|
||
|
std::min(contents.size(), json::object::max_sorted_members)
|
||
|
};
|
||
|
|
||
|
json::iov _content;
|
||
|
json::iov::push content[contents_count]; // 48B each
|
||
|
return send(room, sender, type, state_key, make_iov(_content, content, contents_count, contents));
|
||
|
}
|
||
|
#pragma GCC diagnostic pop
|
||
|
|
||
|
ircd::m::event::id::buf
|
||
|
ircd::m::send(const room &room,
|
||
|
const m::id::user &sender,
|
||
|
const string_view &type,
|
||
|
const string_view &state_key,
|
||
|
const json::iov &content)
|
||
|
{
|
||
|
json::iov event;
|
||
|
const json::iov::push push[]
|
||
|
{
|
||
|
{ event, { "sender", sender }},
|
||
|
{ event, { "type", type }},
|
||
|
{ event, { "state_key", state_key }},
|
||
|
};
|
||
|
|
||
|
return commit(room, event, content);
|
||
|
}
|
||
|
|
||
|
#pragma GCC diagnostic push
|
||
|
#pragma GCC diagnostic ignored "-Wstack-usage="
|
||
|
ircd::m::event::id::buf
|
||
|
__attribute__((stack_protect))
|
||
|
ircd::m::send(const room &room,
|
||
|
const m::id::user &sender,
|
||
|
const string_view &type,
|
||
|
const json::members &contents)
|
||
|
{
|
||
|
const size_t contents_count
|
||
|
{
|
||
|
std::min(contents.size(), json::object::max_sorted_members)
|
||
|
};
|
||
|
|
||
|
json::iov _content;
|
||
|
json::iov::push content[contents_count]; // 48B each
|
||
|
return send(room, sender, type, make_iov(_content, content, contents_count, contents));
|
||
|
}
|
||
|
#pragma GCC diagnostic pop
|
||
|
|
||
|
#pragma GCC diagnostic push
|
||
|
#pragma GCC diagnostic ignored "-Wstack-usage="
|
||
|
ircd::m::event::id::buf
|
||
|
__attribute__((stack_protect))
|
||
|
ircd::m::send(const room &room,
|
||
|
const m::id::user &sender,
|
||
|
const string_view &type,
|
||
|
const json::object &contents)
|
||
|
{
|
||
|
const size_t contents_count
|
||
|
{
|
||
|
std::min(contents.size(), json::object::max_sorted_members)
|
||
|
};
|
||
|
|
||
|
json::iov _content;
|
||
|
json::iov::push content[contents_count]; // 48B each
|
||
|
return send(room, sender, type, make_iov(_content, content, contents_count, contents));
|
||
|
}
|
||
|
#pragma GCC diagnostic pop
|
||
|
|
||
|
ircd::m::event::id::buf
|
||
|
ircd::m::send(const room &room,
|
||
|
const id::user &sender,
|
||
|
const string_view &type,
|
||
|
const json::iov &content)
|
||
|
{
|
||
|
json::iov event;
|
||
|
const json::iov::push push[]
|
||
|
{
|
||
|
{ event, { "sender", sender }},
|
||
|
{ event, { "type", type }},
|
||
|
};
|
||
|
|
||
|
return commit(room, event, content);
|
||
|
}
|
||
|
|
||
|
ircd::m::event::id::buf
|
||
|
ircd::m::commit(const room &room,
|
||
|
json::iov &event,
|
||
|
const json::iov &contents)
|
||
|
{
|
||
|
// Set the room_id on the iov
|
||
|
json::iov::push room_id
|
||
|
{
|
||
|
event, { "room_id", room.room_id }
|
||
|
};
|
||
|
|
||
|
vm::copts opts
|
||
|
{
|
||
|
room.copts?
|
||
|
*room.copts:
|
||
|
vm::default_copts
|
||
|
};
|
||
|
|
||
|
// Some functionality on this server may create an event on behalf
|
||
|
// of remote users. It's safe for us to mask this here, but eval'ing
|
||
|
// this event in any replay later will require special casing.
|
||
|
opts.non_conform |= event::conforms::MISMATCH_ORIGIN_SENDER;
|
||
|
|
||
|
// Don't need this here
|
||
|
opts.verify = false;
|
||
|
|
||
|
return vm::eval
|
||
|
{
|
||
|
event, contents, opts
|
||
|
};
|
||
|
}
|
||
|
|
||
|
ircd::m::id::room::buf
|
||
|
ircd::m::room_id(const id::room_alias &room_alias)
|
||
|
{
|
||
|
char buf[m::id::MAX_SIZE + 1];
|
||
|
static_assert(sizeof(buf) <= 256);
|
||
|
return room_id(buf, room_alias);
|
||
|
}
|
||
|
|
||
|
ircd::m::id::room::buf
|
||
|
ircd::m::room_id(const id::event &event_id)
|
||
|
{
|
||
|
char buf[m::id::MAX_SIZE + 1];
|
||
|
static_assert(sizeof(buf) <= 256);
|
||
|
return room_id(buf, event_id);
|
||
|
}
|
||
|
|
||
|
ircd::m::id::room::buf
|
||
|
ircd::m::room_id(const string_view &mxid)
|
||
|
{
|
||
|
char buf[m::id::MAX_SIZE + 1];
|
||
|
static_assert(sizeof(buf) <= 256);
|
||
|
return room_id(buf, mxid);
|
||
|
}
|
||
|
|
||
|
ircd::m::id::room
|
||
|
ircd::m::room_id(const mutable_buffer &out,
|
||
|
const string_view &mxid)
|
||
|
{
|
||
|
switch(m::sigil(mxid))
|
||
|
{
|
||
|
case id::ROOM:
|
||
|
return id::room{out, mxid};
|
||
|
|
||
|
case id::USER:
|
||
|
{
|
||
|
const m::user::room user_room(mxid);
|
||
|
return string_view{data(out), copy(out, user_room.room_id)};
|
||
|
}
|
||
|
|
||
|
case id::NODE:
|
||
|
{
|
||
|
const m::node node(lstrip(mxid, ':'));
|
||
|
return node.room_id(out);
|
||
|
}
|
||
|
|
||
|
case id::EVENT:
|
||
|
return room_id(out, id::event{mxid});
|
||
|
|
||
|
default:
|
||
|
return room_id(out, id::room_alias{mxid});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ircd::m::id::room
|
||
|
ircd::m::room_id(const mutable_buffer &out,
|
||
|
const id::event &event_id)
|
||
|
{
|
||
|
room::id ret;
|
||
|
m::get(event_id, "room_id", [&out, &ret]
|
||
|
(const room::id &room_id)
|
||
|
{
|
||
|
ret = string_view { data(out), copy(out, room_id) };
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ircd::m::id::room
|
||
|
ircd::m::room_id(const mutable_buffer &out,
|
||
|
const id::room_alias &room_alias)
|
||
|
{
|
||
|
room::id ret;
|
||
|
room::aliases::cache::get(room_alias, [&out, &ret]
|
||
|
(const room::id &room_id)
|
||
|
{
|
||
|
ret = string_view { data(out), copy(out, room_id) };
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::exists(const id::room_alias &room_alias,
|
||
|
const bool &remote_query)
|
||
|
{
|
||
|
if(room::aliases::cache::has(room_alias))
|
||
|
return true;
|
||
|
|
||
|
if(!remote_query)
|
||
|
return false;
|
||
|
|
||
|
return room::aliases::cache::get(std::nothrow, room_alias, [](const room::id &room_id) {});
|
||
|
}
|
||
|
|
||
|
int64_t
|
||
|
ircd::m::depth(const id::room &room_id)
|
||
|
{
|
||
|
return std::get<int64_t>(top(room_id));
|
||
|
}
|
||
|
|
||
|
int64_t
|
||
|
ircd::m::depth(std::nothrow_t,
|
||
|
const id::room &room_id)
|
||
|
{
|
||
|
const auto it
|
||
|
{
|
||
|
dbs::room_events.begin(room_id)
|
||
|
};
|
||
|
|
||
|
if(!it)
|
||
|
return -1;
|
||
|
|
||
|
const auto part
|
||
|
{
|
||
|
dbs::room_events_key(it->first)
|
||
|
};
|
||
|
|
||
|
return std::get<0>(part);
|
||
|
}
|
||
|
|
||
|
ircd::m::event::idx
|
||
|
ircd::m::head_idx(const id::room &room_id)
|
||
|
{
|
||
|
return std::get<event::idx>(top(room_id));
|
||
|
}
|
||
|
|
||
|
ircd::m::event::idx
|
||
|
ircd::m::head_idx(std::nothrow_t,
|
||
|
const id::room &room_id)
|
||
|
{
|
||
|
const auto it
|
||
|
{
|
||
|
dbs::room_events.begin(room_id)
|
||
|
};
|
||
|
|
||
|
if(!it)
|
||
|
return 0;
|
||
|
|
||
|
const auto part
|
||
|
{
|
||
|
dbs::room_events_key(it->first)
|
||
|
};
|
||
|
|
||
|
return std::get<1>(part);
|
||
|
}
|
||
|
|
||
|
ircd::m::event::id::buf
|
||
|
ircd::m::head(const id::room &room_id)
|
||
|
{
|
||
|
return std::get<event::id::buf>(top(room_id));
|
||
|
}
|
||
|
|
||
|
ircd::m::event::id::buf
|
||
|
ircd::m::head(std::nothrow_t,
|
||
|
const id::room &room_id)
|
||
|
{
|
||
|
return std::get<event::id::buf>(top(std::nothrow, room_id));
|
||
|
}
|
||
|
|
||
|
std::tuple<ircd::m::event::id::buf, int64_t, ircd::m::event::idx>
|
||
|
ircd::m::top(const id::room &room_id)
|
||
|
{
|
||
|
const auto ret
|
||
|
{
|
||
|
top(std::nothrow, room_id)
|
||
|
};
|
||
|
|
||
|
if(std::get<int64_t>(ret) == -1)
|
||
|
throw m::NOT_FOUND
|
||
|
{
|
||
|
"No head for room %s", string_view{room_id}
|
||
|
};
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
std::tuple<ircd::m::event::id::buf, int64_t, ircd::m::event::idx>
|
||
|
ircd::m::top(std::nothrow_t,
|
||
|
const id::room &room_id)
|
||
|
{
|
||
|
const auto it
|
||
|
{
|
||
|
dbs::room_events.begin(room_id)
|
||
|
};
|
||
|
|
||
|
if(!it)
|
||
|
return
|
||
|
{
|
||
|
event::id::buf{}, -1, 0
|
||
|
};
|
||
|
|
||
|
const auto part
|
||
|
{
|
||
|
dbs::room_events_key(it->first)
|
||
|
};
|
||
|
|
||
|
const int64_t &depth
|
||
|
{
|
||
|
int64_t(std::get<0>(part))
|
||
|
};
|
||
|
|
||
|
const event::idx &event_idx
|
||
|
{
|
||
|
std::get<1>(part)
|
||
|
};
|
||
|
|
||
|
std::tuple<event::id::buf, int64_t, event::idx> ret
|
||
|
{
|
||
|
event::id::buf{}, depth, event_idx
|
||
|
};
|
||
|
|
||
|
m::event_id(event_idx, std::nothrow, [&ret]
|
||
|
(const event::id &event_id)
|
||
|
{
|
||
|
std::get<event::id::buf>(ret) = event_id;
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ircd::m::id::user::buf
|
||
|
ircd::m::any_user(const room &room,
|
||
|
const string_view &host,
|
||
|
const string_view &membership)
|
||
|
{
|
||
|
user::id::buf ret;
|
||
|
const room::members members{room};
|
||
|
members.for_each(membership, [&host, &ret]
|
||
|
(const auto &user_id, const auto &event_idx)
|
||
|
{
|
||
|
if(host && user_id.host() != host)
|
||
|
return true;
|
||
|
|
||
|
ret = user_id;
|
||
|
return false;
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/// Receive the join_rule of the room into buffer of sufficient size.
|
||
|
/// The protocol does not specify a join_rule string longer than 7
|
||
|
/// characters but do be considerate of the future. This function
|
||
|
/// properly defaults the string as per the protocol spec.
|
||
|
ircd::string_view
|
||
|
ircd::m::join_rule(const mutable_buffer &out,
|
||
|
const room &room)
|
||
|
{
|
||
|
static const string_view default_join_rule
|
||
|
{
|
||
|
"invite"
|
||
|
};
|
||
|
|
||
|
string_view ret
|
||
|
{
|
||
|
default_join_rule
|
||
|
};
|
||
|
|
||
|
const event::keys::include keys
|
||
|
{
|
||
|
"content"
|
||
|
};
|
||
|
|
||
|
const m::event::fetch::opts fopts
|
||
|
{
|
||
|
keys, room.fopts? room.fopts->gopts : db::gopts{}
|
||
|
};
|
||
|
|
||
|
const room::state state
|
||
|
{
|
||
|
room, &fopts
|
||
|
};
|
||
|
|
||
|
state.get(std::nothrow, "m.room.join_rules", "", [&ret, &out]
|
||
|
(const m::event &event)
|
||
|
{
|
||
|
const auto &content
|
||
|
{
|
||
|
json::get<"content"_>(event)
|
||
|
};
|
||
|
|
||
|
const json::string &rule
|
||
|
{
|
||
|
content.get("join_rule", default_join_rule)
|
||
|
};
|
||
|
|
||
|
ret = string_view
|
||
|
{
|
||
|
data(out), copy(out, rule)
|
||
|
};
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ircd::string_view
|
||
|
ircd::m::version(const mutable_buffer &buf,
|
||
|
const room &room)
|
||
|
{
|
||
|
const auto ret
|
||
|
{
|
||
|
version(buf, room, std::nothrow)
|
||
|
};
|
||
|
|
||
|
if(!ret)
|
||
|
throw m::NOT_FOUND
|
||
|
{
|
||
|
"Failed to find room %s to query its version",
|
||
|
string_view{room.room_id}
|
||
|
};
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ircd::string_view
|
||
|
ircd::m::version(const mutable_buffer &buf,
|
||
|
const room &room,
|
||
|
std::nothrow_t)
|
||
|
{
|
||
|
const auto event_idx
|
||
|
{
|
||
|
room.get(std::nothrow, "m.room.create", "")
|
||
|
};
|
||
|
|
||
|
string_view ret
|
||
|
{
|
||
|
strlcpy{buf, "1"_sv}
|
||
|
};
|
||
|
|
||
|
m::get(std::nothrow, event_idx, "content", [&buf, &ret]
|
||
|
(const json::object &content)
|
||
|
{
|
||
|
const json::string &version
|
||
|
{
|
||
|
content.get("room_version", "1")
|
||
|
};
|
||
|
|
||
|
ret = strlcpy
|
||
|
{
|
||
|
buf, version
|
||
|
};
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ircd::string_view
|
||
|
ircd::m::type(const mutable_buffer &buf,
|
||
|
const room &room)
|
||
|
{
|
||
|
string_view ret;
|
||
|
const auto event_idx
|
||
|
{
|
||
|
room.get(std::nothrow, "m.room.create", "")
|
||
|
};
|
||
|
|
||
|
m::get(std::nothrow, event_idx, "content", [&buf, &ret]
|
||
|
(const json::object &content)
|
||
|
{
|
||
|
const json::string &type
|
||
|
{
|
||
|
content.get("type")
|
||
|
};
|
||
|
|
||
|
ret = strlcpy
|
||
|
{
|
||
|
buf, type
|
||
|
};
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ircd::m::id::user::buf
|
||
|
ircd::m::creator(const id::room &room_id)
|
||
|
{
|
||
|
// Query the sender field of the event to get the creator. This is for
|
||
|
// future compatibility if the content.creator field gets eliminated.
|
||
|
static const event::fetch::opts fopts
|
||
|
{
|
||
|
event::keys::include {"sender"}
|
||
|
};
|
||
|
|
||
|
const room::state state
|
||
|
{
|
||
|
room_id, &fopts
|
||
|
};
|
||
|
|
||
|
id::user::buf ret;
|
||
|
state.get("m.room.create", "", [&ret]
|
||
|
(const m::event &event)
|
||
|
{
|
||
|
ret = user::id
|
||
|
{
|
||
|
json::get<"sender"_>(event)
|
||
|
};
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// boolean suite
|
||
|
//
|
||
|
|
||
|
/// The only members are from our origin, in any membership state. This
|
||
|
/// indicates we won't have any other federation servers that could possibly
|
||
|
/// be party to anything about this room.
|
||
|
bool
|
||
|
ircd::m::local_only(const room &room)
|
||
|
{
|
||
|
// Branch to test if any remote users are joined to the room, meaning
|
||
|
// this result must be false; this is a fast query.
|
||
|
if(remote_joined(room))
|
||
|
return false;
|
||
|
|
||
|
const room::members members
|
||
|
{
|
||
|
room
|
||
|
};
|
||
|
|
||
|
return members.for_each([]
|
||
|
(const id::user &user_id)
|
||
|
{
|
||
|
return my(user_id);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/// Member(s) from our server are presently joined to the room. Returns false
|
||
|
/// if there's a room on the server where all of our users have left. Note that
|
||
|
/// some internal rooms have no memberships at all and this will also be false.
|
||
|
/// This can return true if other servers have memberships in the room too, as
|
||
|
/// long as one of our users is joined.
|
||
|
bool
|
||
|
ircd::m::local_joined(const room &room)
|
||
|
{
|
||
|
const room::members members
|
||
|
{
|
||
|
room
|
||
|
};
|
||
|
|
||
|
return !members.empty("join", my_host());
|
||
|
}
|
||
|
|
||
|
/// Member(s) from another server are presently joined to the room. For example
|
||
|
/// if another user leaves a PM with our user who is still joined, this returns
|
||
|
/// false. This can return true even if the room has no memberships in any
|
||
|
/// state from our server, as long as there's a joined member from a remote.
|
||
|
bool
|
||
|
ircd::m::remote_joined(const room &room)
|
||
|
{
|
||
|
const room::members members
|
||
|
{
|
||
|
room
|
||
|
};
|
||
|
|
||
|
return !members.for_each("join", []
|
||
|
(const id::user &user_id)
|
||
|
{
|
||
|
return my(user_id)? true : false; // false to break.
|
||
|
});
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::visible(const room &room,
|
||
|
const string_view &mxid,
|
||
|
const event *const &event)
|
||
|
{
|
||
|
if(event)
|
||
|
return m::visible(*event, mxid);
|
||
|
|
||
|
const m::event event_
|
||
|
{
|
||
|
json::members
|
||
|
{
|
||
|
{ "event_id", room.event_id },
|
||
|
{ "room_id", room.room_id },
|
||
|
}
|
||
|
};
|
||
|
|
||
|
return m::visible(event_, mxid);
|
||
|
}
|
||
|
|
||
|
/// Test of the join_rule of the room is the argument.
|
||
|
bool
|
||
|
ircd::m::join_rule(const room &room,
|
||
|
const string_view &rule)
|
||
|
{
|
||
|
char buf[32];
|
||
|
return join_rule(buf, room) == rule;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::creator(const room::id &room_id,
|
||
|
const user::id &user_id)
|
||
|
{
|
||
|
const auto creator_user_id
|
||
|
{
|
||
|
creator(room_id)
|
||
|
};
|
||
|
|
||
|
return creator_user_id == user_id;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::federated(const id::room &room_id)
|
||
|
{
|
||
|
static const m::event::fetch::opts fopts
|
||
|
{
|
||
|
event::keys::include { "content" },
|
||
|
};
|
||
|
|
||
|
const m::room::state state
|
||
|
{
|
||
|
room_id, &fopts
|
||
|
};
|
||
|
|
||
|
bool ret;
|
||
|
state.get("m.room.create", "", [&ret]
|
||
|
(const m::event &event)
|
||
|
{
|
||
|
ret = json::get<"content"_>(event).get("m.federate", true);
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/// Determine if this is an internal room. The following must be satisfied:
|
||
|
///
|
||
|
/// - The room was created by this origin.
|
||
|
/// - The creator was the server itself, not any other user.
|
||
|
bool
|
||
|
ircd::m::internal(const id::room &room_id)
|
||
|
{
|
||
|
const m::room room
|
||
|
{
|
||
|
room_id
|
||
|
};
|
||
|
|
||
|
if(!my(room))
|
||
|
return false;
|
||
|
|
||
|
if(!exists(room))
|
||
|
return false;
|
||
|
|
||
|
if(!creator(room, m::me))
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::exists(const id::room &room_id)
|
||
|
{
|
||
|
const m::room::events it
|
||
|
{
|
||
|
room_id, 0UL
|
||
|
};
|
||
|
|
||
|
if(!it)
|
||
|
return false;
|
||
|
|
||
|
if(likely(it.depth() < 2UL))
|
||
|
return true;
|
||
|
|
||
|
if(my_host(room_id.host()) && creator(room_id, m::me))
|
||
|
return true;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::exists(const room &room)
|
||
|
{
|
||
|
return exists(room.room_id);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// util
|
||
|
//
|
||
|
|
||
|
bool
|
||
|
ircd::m::operator==(const room &a, const room &b)
|
||
|
{
|
||
|
return !(a != b);
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::operator!=(const room &a_, const room &b_)
|
||
|
{
|
||
|
const string_view &a{a_.room_id}, &b{b_.room_id};
|
||
|
return a != b;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::operator!(const room &a)
|
||
|
{
|
||
|
return !a.room_id;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::my(const room &room)
|
||
|
{
|
||
|
return my(room.room_id);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// room
|
||
|
//
|
||
|
|
||
|
/// A room index is just the event::idx of its create event.
|
||
|
ircd::m::event::idx
|
||
|
ircd::m::room::index(const room::id &room_id)
|
||
|
{
|
||
|
const auto ret
|
||
|
{
|
||
|
index(room_id, std::nothrow)
|
||
|
};
|
||
|
|
||
|
if(!ret)
|
||
|
throw m::NOT_FOUND
|
||
|
{
|
||
|
"No index for room %s", string_view{room_id}
|
||
|
};
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ircd::m::event::idx
|
||
|
ircd::m::room::index(const room::id &room_id,
|
||
|
std::nothrow_t)
|
||
|
{
|
||
|
uint64_t depth{0};
|
||
|
room::events it
|
||
|
{
|
||
|
room_id, depth
|
||
|
};
|
||
|
|
||
|
return it? it.event_idx() : 0;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// room::room
|
||
|
//
|
||
|
|
||
|
size_t
|
||
|
ircd::m::room::count()
|
||
|
const
|
||
|
{
|
||
|
size_t ret(0);
|
||
|
for_each(event::closure_idx_bool{[&ret]
|
||
|
(const event::idx &event_idx)
|
||
|
{
|
||
|
++ret;
|
||
|
return true;
|
||
|
}});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
size_t
|
||
|
ircd::m::room::count(const string_view &type)
|
||
|
const
|
||
|
{
|
||
|
size_t ret(0);
|
||
|
for_each(type, event::closure_idx_bool{[&ret]
|
||
|
(const event::idx &event_idx)
|
||
|
{
|
||
|
++ret;
|
||
|
return true;
|
||
|
}});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
size_t
|
||
|
ircd::m::room::count(const string_view &type,
|
||
|
const string_view &state_key)
|
||
|
const
|
||
|
{
|
||
|
size_t ret(0);
|
||
|
for_each(type, event::closure_idx_bool{[&state_key, &ret]
|
||
|
(const event::idx &event_idx)
|
||
|
{
|
||
|
ret += query(std::nothrow, event_idx, "state_key", [&state_key]
|
||
|
(const string_view &_state_key) -> bool
|
||
|
{
|
||
|
return state_key == _state_key;
|
||
|
});
|
||
|
|
||
|
return true;
|
||
|
}});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::has(const string_view &type)
|
||
|
const
|
||
|
{
|
||
|
return get(std::nothrow, type, event::closure{});
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ircd::m::room::get(const string_view &type,
|
||
|
const event::closure &closure)
|
||
|
const
|
||
|
{
|
||
|
if(!get(std::nothrow, type, closure))
|
||
|
throw m::NOT_FOUND
|
||
|
{
|
||
|
"No events of type '%s' found in '%s'",
|
||
|
type,
|
||
|
room_id
|
||
|
};
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::get(std::nothrow_t,
|
||
|
const string_view &type,
|
||
|
const event::closure &closure)
|
||
|
const
|
||
|
{
|
||
|
bool ret{false};
|
||
|
for_each(type, event::closure_bool{[&ret, &closure]
|
||
|
(const event &event)
|
||
|
{
|
||
|
if(closure)
|
||
|
closure(event);
|
||
|
|
||
|
ret = true;
|
||
|
return false;
|
||
|
}});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ircd::m::event::idx
|
||
|
ircd::m::room::get(const string_view &type)
|
||
|
const
|
||
|
{
|
||
|
const event::idx ret
|
||
|
{
|
||
|
get(std::nothrow, type)
|
||
|
};
|
||
|
|
||
|
if(unlikely(!ret))
|
||
|
throw m::NOT_FOUND
|
||
|
{
|
||
|
"No events of type '%s' found in '%s'",
|
||
|
type,
|
||
|
room_id
|
||
|
};
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ircd::m::event::idx
|
||
|
ircd::m::room::get(std::nothrow_t,
|
||
|
const string_view &type)
|
||
|
const
|
||
|
{
|
||
|
event::idx ret{0};
|
||
|
for_each(type, event::closure_idx_bool{[&ret]
|
||
|
(const event::idx &event_idx)
|
||
|
{
|
||
|
ret = event_idx;
|
||
|
return false;
|
||
|
}});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ircd::m::event::idx
|
||
|
ircd::m::room::get(const string_view &type,
|
||
|
const string_view &state_key)
|
||
|
const
|
||
|
{
|
||
|
return state(*this).get(type, state_key);
|
||
|
}
|
||
|
|
||
|
ircd::m::event::idx
|
||
|
ircd::m::room::get(std::nothrow_t,
|
||
|
const string_view &type,
|
||
|
const string_view &state_key)
|
||
|
const
|
||
|
{
|
||
|
return state(*this).get(std::nothrow, type, state_key);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ircd::m::room::get(const string_view &type,
|
||
|
const string_view &state_key,
|
||
|
const event::closure &closure)
|
||
|
const
|
||
|
{
|
||
|
const state state{*this};
|
||
|
state.get(type, state_key, closure);
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::get(std::nothrow_t,
|
||
|
const string_view &type,
|
||
|
const string_view &state_key,
|
||
|
const event::closure &closure)
|
||
|
const
|
||
|
{
|
||
|
const state state{*this};
|
||
|
return state.get(std::nothrow, type, state_key, closure);
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::has(const string_view &type,
|
||
|
const string_view &state_key)
|
||
|
const
|
||
|
{
|
||
|
const state state{*this};
|
||
|
return state.has(type, state_key);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ircd::m::room::for_each(const event::closure &closure)
|
||
|
const
|
||
|
{
|
||
|
for_each(string_view{}, closure);
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::for_each(const event::closure_bool &closure)
|
||
|
const
|
||
|
{
|
||
|
return for_each(string_view{}, closure);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ircd::m::room::for_each(const event::id::closure &closure)
|
||
|
const
|
||
|
{
|
||
|
for_each(string_view{}, closure);
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::for_each(const event::id::closure_bool &closure)
|
||
|
const
|
||
|
{
|
||
|
return for_each(string_view{}, closure);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ircd::m::room::for_each(const event::closure_idx &closure)
|
||
|
const
|
||
|
{
|
||
|
for_each(string_view{}, closure);
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::for_each(const event::closure_idx_bool &closure)
|
||
|
const
|
||
|
{
|
||
|
return for_each(string_view{}, closure);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ircd::m::room::for_each(const string_view &type,
|
||
|
const event::closure &closure)
|
||
|
const
|
||
|
{
|
||
|
for_each(type, event::closure_bool{[&closure]
|
||
|
(const event &event)
|
||
|
{
|
||
|
closure(event);
|
||
|
return true;
|
||
|
}});
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::for_each(const string_view &type,
|
||
|
const event::closure_bool &closure)
|
||
|
const
|
||
|
{
|
||
|
event::fetch event
|
||
|
{
|
||
|
fopts? *fopts : event::fetch::default_opts
|
||
|
};
|
||
|
|
||
|
return for_each(type, event::closure_idx_bool{[&closure, &event]
|
||
|
(const event::idx &event_idx)
|
||
|
{
|
||
|
if(!seek(event, event_idx, std::nothrow))
|
||
|
return true;
|
||
|
|
||
|
return closure(event);
|
||
|
}});
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ircd::m::room::for_each(const string_view &type,
|
||
|
const event::id::closure &closure)
|
||
|
const
|
||
|
{
|
||
|
for_each(type, event::id::closure_bool{[&closure]
|
||
|
(const event::id &event_id)
|
||
|
{
|
||
|
closure(event_id);
|
||
|
return true;
|
||
|
}});
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::for_each(const string_view &type,
|
||
|
const event::id::closure_bool &closure)
|
||
|
const
|
||
|
{
|
||
|
return for_each(type, event::closure_idx_bool{[&closure]
|
||
|
(const event::idx &idx)
|
||
|
{
|
||
|
bool ret{true};
|
||
|
m::event_id(idx, std::nothrow, [&ret, &closure]
|
||
|
(const event::id &event_id)
|
||
|
{
|
||
|
ret = closure(event_id);
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}});
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ircd::m::room::for_each(const string_view &type,
|
||
|
const event::closure_idx &closure)
|
||
|
const
|
||
|
{
|
||
|
for_each(type, event::closure_idx_bool{[&closure]
|
||
|
(const event::idx &idx)
|
||
|
{
|
||
|
closure(idx);
|
||
|
return true;
|
||
|
}});
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::for_each(const string_view &type,
|
||
|
const event::closure_idx_bool &closure)
|
||
|
const
|
||
|
{
|
||
|
static constexpr auto idx
|
||
|
{
|
||
|
json::indexof<event, "type"_>()
|
||
|
};
|
||
|
|
||
|
auto &column
|
||
|
{
|
||
|
dbs::event_column.at(idx)
|
||
|
};
|
||
|
|
||
|
events it{*this};
|
||
|
for(; it; --it)
|
||
|
{
|
||
|
const auto &event_idx
|
||
|
{
|
||
|
it.event_idx()
|
||
|
};
|
||
|
|
||
|
bool match
|
||
|
{
|
||
|
empty(type) // allow empty type to always match and bypass query
|
||
|
};
|
||
|
|
||
|
if(!match)
|
||
|
column(byte_view<string_view>(event_idx), std::nothrow, [&match, &type]
|
||
|
(const string_view &value)
|
||
|
{
|
||
|
match = value == type;
|
||
|
});
|
||
|
|
||
|
if(match)
|
||
|
if(!closure(event_idx))
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// room::origins
|
||
|
//
|
||
|
|
||
|
ircd::string_view
|
||
|
ircd::m::room::origins::random(const mutable_buffer &buf,
|
||
|
const closure_bool &proffer)
|
||
|
const
|
||
|
{
|
||
|
string_view ret;
|
||
|
const auto closure{[&buf, &proffer, &ret]
|
||
|
(const string_view &origin)
|
||
|
{
|
||
|
ret = { data(buf), copy(buf, origin) };
|
||
|
}};
|
||
|
|
||
|
random(closure, proffer);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::origins::random(const closure &view,
|
||
|
const closure_bool &proffer)
|
||
|
const
|
||
|
{
|
||
|
return random(*this, view, proffer);
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::origins::random(const origins &origins,
|
||
|
const closure &view,
|
||
|
const closure_bool &proffer)
|
||
|
{
|
||
|
bool ret{false};
|
||
|
const size_t max
|
||
|
{
|
||
|
origins.count()
|
||
|
};
|
||
|
|
||
|
if(unlikely(!max))
|
||
|
return ret;
|
||
|
|
||
|
auto select
|
||
|
{
|
||
|
ssize_t(rand::integer(0, max - 1))
|
||
|
};
|
||
|
|
||
|
const closure_bool closure{[&proffer, &view, &select]
|
||
|
(const string_view &origin)
|
||
|
{
|
||
|
if(select-- > 0)
|
||
|
return true;
|
||
|
|
||
|
// Test if this random selection is "ok" e.g. the callback allows the
|
||
|
// user to test a blacklist for this origin. Skip to next if not.
|
||
|
if(proffer && !proffer(origin))
|
||
|
{
|
||
|
++select;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
view(origin);
|
||
|
return false;
|
||
|
}};
|
||
|
|
||
|
const auto iteration{[&origins, &closure, &ret]
|
||
|
{
|
||
|
ret = !origins.for_each(closure);
|
||
|
}};
|
||
|
|
||
|
// Attempt select on first iteration
|
||
|
iteration();
|
||
|
|
||
|
// If nothing was OK between the random int and the end of the iteration
|
||
|
// then start again and pick the first OK.
|
||
|
if(!ret && select >= 0)
|
||
|
iteration();
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::origins::empty()
|
||
|
const
|
||
|
{
|
||
|
return for_each(closure_bool{[]
|
||
|
(const string_view &)
|
||
|
{
|
||
|
// return false to break and return false.
|
||
|
return false;
|
||
|
}});
|
||
|
}
|
||
|
|
||
|
size_t
|
||
|
ircd::m::room::origins::count()
|
||
|
const
|
||
|
{
|
||
|
size_t ret{0};
|
||
|
for_each([&ret](const string_view &)
|
||
|
{
|
||
|
++ret;
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
size_t
|
||
|
ircd::m::room::origins::count_error()
|
||
|
const
|
||
|
{
|
||
|
size_t ret{0};
|
||
|
for_each([&ret](const string_view &server)
|
||
|
{
|
||
|
ret += !ircd::empty(server::errmsg(server));
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
size_t
|
||
|
ircd::m::room::origins::count_online()
|
||
|
const
|
||
|
{
|
||
|
ssize_t ret
|
||
|
{
|
||
|
0 - ssize_t(count_error())
|
||
|
};
|
||
|
|
||
|
for_each([&ret](const string_view &hostport)
|
||
|
{
|
||
|
ret += bool(server::exists(hostport));
|
||
|
});
|
||
|
|
||
|
assert(ret >= 0L);
|
||
|
return std::max(ret, 0L);
|
||
|
}
|
||
|
|
||
|
/// Tests if argument is the only origin in the room.
|
||
|
/// If a zero or more than one origins exist, returns false. If the only origin
|
||
|
/// in the room is the argument origin, returns true.
|
||
|
bool
|
||
|
ircd::m::room::origins::only(const string_view &origin)
|
||
|
const
|
||
|
{
|
||
|
ushort ret{2};
|
||
|
for_each(closure_bool{[&ret, &origin]
|
||
|
(const string_view &origin_) -> bool
|
||
|
{
|
||
|
if(origin == origin_)
|
||
|
ret = 1;
|
||
|
else
|
||
|
ret = 0;
|
||
|
|
||
|
return ret;
|
||
|
}});
|
||
|
|
||
|
return ret == 1;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::origins::has(const string_view &origin)
|
||
|
const
|
||
|
{
|
||
|
db::domain &index
|
||
|
{
|
||
|
dbs::room_joined
|
||
|
};
|
||
|
|
||
|
char querybuf[dbs::ROOM_JOINED_KEY_MAX_SIZE];
|
||
|
const auto query
|
||
|
{
|
||
|
dbs::room_joined_key(querybuf, room.room_id, origin)
|
||
|
};
|
||
|
|
||
|
auto it
|
||
|
{
|
||
|
index.begin(query)
|
||
|
};
|
||
|
|
||
|
if(!it)
|
||
|
return false;
|
||
|
|
||
|
const string_view &key
|
||
|
{
|
||
|
lstrip(it->first, "\0"_sv)
|
||
|
};
|
||
|
|
||
|
const string_view &key_origin
|
||
|
{
|
||
|
std::get<0>(dbs::room_joined_key(key))
|
||
|
};
|
||
|
|
||
|
return key_origin == origin;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ircd::m::room::origins::for_each(const closure &view)
|
||
|
const
|
||
|
{
|
||
|
for_each(closure_bool{[&view]
|
||
|
(const string_view &origin)
|
||
|
{
|
||
|
view(origin);
|
||
|
return true;
|
||
|
}});
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::origins::for_each(const closure_bool &view)
|
||
|
const
|
||
|
{
|
||
|
string_view last;
|
||
|
char lastbuf[rfc1035::NAME_BUFSIZE];
|
||
|
return _for_each(*this, [&last, &lastbuf, &view]
|
||
|
(const string_view &key)
|
||
|
{
|
||
|
const string_view &origin
|
||
|
{
|
||
|
std::get<0>(dbs::room_joined_key(key))
|
||
|
};
|
||
|
|
||
|
if(origin == last)
|
||
|
return true;
|
||
|
|
||
|
if(!view(origin))
|
||
|
return false;
|
||
|
|
||
|
last = { lastbuf, copy(lastbuf, origin) };
|
||
|
return true;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::origins::_for_each(const origins &origins,
|
||
|
const closure_bool &view)
|
||
|
{
|
||
|
db::domain &index
|
||
|
{
|
||
|
dbs::room_joined
|
||
|
};
|
||
|
|
||
|
auto it
|
||
|
{
|
||
|
index.begin(origins.room.room_id)
|
||
|
};
|
||
|
|
||
|
for(; bool(it); ++it)
|
||
|
{
|
||
|
const string_view &key
|
||
|
{
|
||
|
lstrip(it->first, "\0"_sv)
|
||
|
};
|
||
|
|
||
|
if(!view(key))
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// room::aliases
|
||
|
//
|
||
|
|
||
|
size_t
|
||
|
ircd::m::room::aliases::count()
|
||
|
const
|
||
|
{
|
||
|
return count(string_view{});
|
||
|
}
|
||
|
|
||
|
size_t
|
||
|
ircd::m::room::aliases::count(const string_view &server)
|
||
|
const
|
||
|
{
|
||
|
size_t ret(0);
|
||
|
for_each(server, [&ret](const auto &a)
|
||
|
{
|
||
|
++ret;
|
||
|
return true;
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::aliases::has(const alias &alias)
|
||
|
const
|
||
|
{
|
||
|
return !for_each(alias.host(), [&alias]
|
||
|
(const id::room_alias &a)
|
||
|
{
|
||
|
assert(a.host() == alias.host());
|
||
|
return a == alias? false : true; // false to break on found
|
||
|
});
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::aliases::for_each(const closure_bool &closure)
|
||
|
const
|
||
|
{
|
||
|
const room::state state
|
||
|
{
|
||
|
room
|
||
|
};
|
||
|
|
||
|
return state.for_each("m.room.aliases", [this, &closure]
|
||
|
(const string_view &type, const string_view &state_key, const event::idx &)
|
||
|
{
|
||
|
return for_each(state_key, closure);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::aliases::for_each(const string_view &server,
|
||
|
const closure_bool &closure)
|
||
|
const
|
||
|
{
|
||
|
if(!server)
|
||
|
return for_each(closure);
|
||
|
|
||
|
return for_each(room, server, closure);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// room::aliases::cache
|
||
|
//
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::aliases::cache::fetch(std::nothrow_t,
|
||
|
const alias &a,
|
||
|
const net::hostport &hp)
|
||
|
try
|
||
|
{
|
||
|
fetch(a, hp);
|
||
|
return true;
|
||
|
}
|
||
|
catch(const std::exception &e)
|
||
|
{
|
||
|
thread_local char buf[384];
|
||
|
log::error
|
||
|
{
|
||
|
log, "Failed to fetch room_id for %s from %s :%s",
|
||
|
string_view{a},
|
||
|
string(buf, hp),
|
||
|
e.what(),
|
||
|
};
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
ircd::m::room::id::buf
|
||
|
ircd::m::room::aliases::cache::get(const alias &a)
|
||
|
{
|
||
|
id::buf ret;
|
||
|
get(a, [&ret]
|
||
|
(const id &room_id)
|
||
|
{
|
||
|
ret = room_id;
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ircd::m::room::id::buf
|
||
|
ircd::m::room::aliases::cache::get(std::nothrow_t,
|
||
|
const alias &a)
|
||
|
{
|
||
|
id::buf ret;
|
||
|
get(std::nothrow, a, [&ret]
|
||
|
(const id &room_id)
|
||
|
{
|
||
|
ret = room_id;
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ircd::m::room::aliases::cache::get(const alias &a,
|
||
|
const id::closure &c)
|
||
|
{
|
||
|
if(!get(std::nothrow, a, c))
|
||
|
throw m::NOT_FOUND
|
||
|
{
|
||
|
"Cannot find room_id for %s",
|
||
|
string_view{a}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::aliases::cache::for_each(const closure_bool &c)
|
||
|
{
|
||
|
return for_each(string_view{}, c);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// room::power
|
||
|
//
|
||
|
|
||
|
decltype(ircd::m::room::power::default_creator_level)
|
||
|
ircd::m::room::power::default_creator_level
|
||
|
{
|
||
|
100
|
||
|
};
|
||
|
|
||
|
decltype(ircd::m::room::power::default_power_level)
|
||
|
ircd::m::room::power::default_power_level
|
||
|
{
|
||
|
50
|
||
|
};
|
||
|
|
||
|
decltype(ircd::m::room::power::default_event_level)
|
||
|
ircd::m::room::power::default_event_level
|
||
|
{
|
||
|
0
|
||
|
};
|
||
|
|
||
|
decltype(ircd::m::room::power::default_user_level)
|
||
|
ircd::m::room::power::default_user_level
|
||
|
{
|
||
|
0
|
||
|
};
|
||
|
|
||
|
ircd::json::object
|
||
|
ircd::m::room::power::default_content(const mutable_buffer &buf,
|
||
|
const m::user::id &creator)
|
||
|
{
|
||
|
return compose_content(buf, [&creator]
|
||
|
(const string_view &key, json::stack::object &object)
|
||
|
{
|
||
|
if(key != "users")
|
||
|
return;
|
||
|
|
||
|
assert(default_creator_level == 100);
|
||
|
json::stack::member
|
||
|
{
|
||
|
object, creator, json::value(default_creator_level)
|
||
|
};
|
||
|
});
|
||
|
}
|
||
|
|
||
|
ircd::json::object
|
||
|
ircd::m::room::power::compose_content(const mutable_buffer &buf,
|
||
|
const compose_closure &closure)
|
||
|
{
|
||
|
json::stack out{buf};
|
||
|
json::stack::object content{out};
|
||
|
|
||
|
assert(default_power_level == 50);
|
||
|
json::stack::member
|
||
|
{
|
||
|
content, "ban", json::value(default_power_level)
|
||
|
};
|
||
|
|
||
|
{
|
||
|
json::stack::object events
|
||
|
{
|
||
|
content, "events"
|
||
|
};
|
||
|
|
||
|
closure("events", events);
|
||
|
}
|
||
|
|
||
|
assert(default_event_level == 0);
|
||
|
json::stack::member
|
||
|
{
|
||
|
content, "events_default", json::value(default_event_level)
|
||
|
};
|
||
|
|
||
|
json::stack::member
|
||
|
{
|
||
|
content, "invite", json::value(default_power_level)
|
||
|
};
|
||
|
|
||
|
json::stack::member
|
||
|
{
|
||
|
content, "kick", json::value(default_power_level)
|
||
|
};
|
||
|
|
||
|
{
|
||
|
json::stack::object notifications
|
||
|
{
|
||
|
content, "notifications"
|
||
|
};
|
||
|
|
||
|
json::stack::member
|
||
|
{
|
||
|
notifications, "room", json::value(default_power_level)
|
||
|
};
|
||
|
|
||
|
closure("notifications", notifications);
|
||
|
}
|
||
|
|
||
|
json::stack::member
|
||
|
{
|
||
|
content, "redact", json::value(default_power_level)
|
||
|
};
|
||
|
|
||
|
json::stack::member
|
||
|
{
|
||
|
content, "state_default", json::value(default_power_level)
|
||
|
};
|
||
|
|
||
|
{
|
||
|
json::stack::object users
|
||
|
{
|
||
|
content, "users"
|
||
|
};
|
||
|
|
||
|
closure("users", users);
|
||
|
}
|
||
|
|
||
|
assert(default_user_level == 0);
|
||
|
json::stack::member
|
||
|
{
|
||
|
content, "users_default", json::value(default_user_level)
|
||
|
};
|
||
|
|
||
|
content.~object();
|
||
|
return json::object{out.completed()};
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// room::power::power
|
||
|
//
|
||
|
|
||
|
ircd::m::room::power::power(const m::room &room)
|
||
|
:power
|
||
|
{
|
||
|
room, room.get(std::nothrow, "m.room.power_levels", "")
|
||
|
}
|
||
|
{
|
||
|
}
|
||
|
|
||
|
ircd::m::room::power::power(const m::room &room,
|
||
|
const event::idx &power_event_idx)
|
||
|
:room
|
||
|
{
|
||
|
room
|
||
|
}
|
||
|
,power_event_idx
|
||
|
{
|
||
|
power_event_idx
|
||
|
}
|
||
|
{
|
||
|
}
|
||
|
|
||
|
ircd::m::room::power::power(const m::event &power_event,
|
||
|
const m::event &create_event)
|
||
|
:power
|
||
|
{
|
||
|
power_event, m::user::id(unquote(json::get<"content"_>(create_event).get("creator")))
|
||
|
}
|
||
|
{
|
||
|
}
|
||
|
|
||
|
ircd::m::room::power::power(const m::event &power_event,
|
||
|
const m::user::id &room_creator_id)
|
||
|
:power
|
||
|
{
|
||
|
json::get<"content"_>(power_event), room_creator_id
|
||
|
}
|
||
|
{
|
||
|
}
|
||
|
|
||
|
ircd::m::room::power::power(const json::object &power_event_content,
|
||
|
const m::user::id &room_creator_id)
|
||
|
:power_event_content
|
||
|
{
|
||
|
power_event_content
|
||
|
}
|
||
|
,room_creator_id
|
||
|
{
|
||
|
room_creator_id
|
||
|
}
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/// "all who attain great power and riches make use of either force or fraud"
|
||
|
///
|
||
|
/// Returns bool for "allow" or "deny"
|
||
|
///
|
||
|
/// Provide the user invoking the power. The return value indicates whether
|
||
|
/// they have the power.
|
||
|
///
|
||
|
/// Provide the property/event_type. There are two usages here: 1. This is a
|
||
|
/// string corresponding to one of the spec top-level properties like "ban"
|
||
|
/// and "redact". In this case, the type and state_key parameters to this
|
||
|
/// function are not used. 2. This string is empty or "events" in which case
|
||
|
/// the type parameter is used to fetch the power threshold for that type.
|
||
|
/// For state events of a type, the state_key must be provided for inspection
|
||
|
/// here as well.
|
||
|
bool
|
||
|
ircd::m::room::power::operator()(const m::user::id &user_id,
|
||
|
const string_view &prop,
|
||
|
const string_view &type,
|
||
|
const string_view &state_key)
|
||
|
const
|
||
|
{
|
||
|
const auto &user_level
|
||
|
{
|
||
|
level_user(user_id)
|
||
|
};
|
||
|
|
||
|
const auto &required_level
|
||
|
{
|
||
|
empty(prop) || prop == "events"?
|
||
|
level_event(type, state_key):
|
||
|
level(prop)
|
||
|
};
|
||
|
|
||
|
return user_level >= required_level;
|
||
|
}
|
||
|
|
||
|
int64_t
|
||
|
ircd::m::room::power::level_user(const m::user::id &user_id)
|
||
|
const try
|
||
|
{
|
||
|
int64_t ret
|
||
|
{
|
||
|
default_user_level
|
||
|
};
|
||
|
|
||
|
const auto closure{[&user_id, &ret]
|
||
|
(const json::object &content)
|
||
|
{
|
||
|
const auto users_default
|
||
|
{
|
||
|
content.get<int64_t>("users_default", default_user_level)
|
||
|
};
|
||
|
|
||
|
const json::object &users
|
||
|
{
|
||
|
content.get("users")
|
||
|
};
|
||
|
|
||
|
ret = users.get<int64_t>(user_id, users_default);
|
||
|
}};
|
||
|
|
||
|
const bool has_power_levels_event
|
||
|
{
|
||
|
view(closure)
|
||
|
};
|
||
|
|
||
|
if(!has_power_levels_event)
|
||
|
{
|
||
|
if(room_creator_id && user_id == room_creator_id)
|
||
|
ret = default_creator_level;
|
||
|
|
||
|
if(room.room_id && creator(room, user_id))
|
||
|
ret = default_creator_level;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
catch(const json::error &e)
|
||
|
{
|
||
|
return default_user_level;
|
||
|
}
|
||
|
|
||
|
int64_t
|
||
|
ircd::m::room::power::level_event(const string_view &type)
|
||
|
const try
|
||
|
{
|
||
|
int64_t ret
|
||
|
{
|
||
|
default_event_level
|
||
|
};
|
||
|
|
||
|
const auto closure{[&type, &ret]
|
||
|
(const json::object &content)
|
||
|
{
|
||
|
const auto &events_default
|
||
|
{
|
||
|
content.get<int64_t>("events_default", default_event_level)
|
||
|
};
|
||
|
|
||
|
const json::object &events
|
||
|
{
|
||
|
content.get("events")
|
||
|
};
|
||
|
|
||
|
ret = events.get<int64_t>(type, events_default);
|
||
|
}};
|
||
|
|
||
|
const bool has_power_levels_event
|
||
|
{
|
||
|
view(closure)
|
||
|
};
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
catch(const json::error &e)
|
||
|
{
|
||
|
return default_event_level;
|
||
|
}
|
||
|
|
||
|
int64_t
|
||
|
ircd::m::room::power::level_event(const string_view &type,
|
||
|
const string_view &state_key)
|
||
|
const try
|
||
|
{
|
||
|
if(!defined(state_key))
|
||
|
return level_event(type);
|
||
|
|
||
|
int64_t ret
|
||
|
{
|
||
|
default_power_level
|
||
|
};
|
||
|
|
||
|
const auto closure{[&type, &ret]
|
||
|
(const json::object &content)
|
||
|
{
|
||
|
const auto &state_default
|
||
|
{
|
||
|
content.get<int64_t>("state_default", default_power_level)
|
||
|
};
|
||
|
|
||
|
const json::object &events
|
||
|
{
|
||
|
content.get("events")
|
||
|
};
|
||
|
|
||
|
ret = events.get<int64_t>(type, state_default);
|
||
|
}};
|
||
|
|
||
|
const bool has_power_levels_event
|
||
|
{
|
||
|
view(closure)
|
||
|
};
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
catch(const json::error &e)
|
||
|
{
|
||
|
return default_power_level;
|
||
|
}
|
||
|
|
||
|
int64_t
|
||
|
ircd::m::room::power::level(const string_view &prop)
|
||
|
const try
|
||
|
{
|
||
|
int64_t ret
|
||
|
{
|
||
|
default_power_level
|
||
|
};
|
||
|
|
||
|
view([&prop, &ret]
|
||
|
(const json::object &content)
|
||
|
{
|
||
|
ret = content.at<int64_t>(prop);
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
catch(const json::error &e)
|
||
|
{
|
||
|
return default_power_level;
|
||
|
}
|
||
|
|
||
|
size_t
|
||
|
ircd::m::room::power::count_levels()
|
||
|
const
|
||
|
{
|
||
|
size_t ret{0};
|
||
|
for_each([&ret]
|
||
|
(const string_view &, const int64_t &)
|
||
|
{
|
||
|
++ret;
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
size_t
|
||
|
ircd::m::room::power::count_collections()
|
||
|
const
|
||
|
{
|
||
|
size_t ret{0};
|
||
|
view([&ret]
|
||
|
(const json::object &content)
|
||
|
{
|
||
|
for(const auto &member : content)
|
||
|
ret += json::type(member.second) == json::OBJECT;
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
size_t
|
||
|
ircd::m::room::power::count(const string_view &prop)
|
||
|
const
|
||
|
{
|
||
|
size_t ret{0};
|
||
|
for_each(prop, [&ret]
|
||
|
(const string_view &, const int64_t &)
|
||
|
{
|
||
|
++ret;
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::power::has_event(const string_view &type)
|
||
|
const try
|
||
|
{
|
||
|
bool ret{false};
|
||
|
view([&type, &ret]
|
||
|
(const json::object &content)
|
||
|
{
|
||
|
const json::object &events
|
||
|
{
|
||
|
content.at("events")
|
||
|
};
|
||
|
|
||
|
const string_view &level
|
||
|
{
|
||
|
unquote(events.at(type))
|
||
|
};
|
||
|
|
||
|
ret = json::type(level) == json::NUMBER;
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
catch(const json::error &)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::power::has_user(const m::user::id &user_id)
|
||
|
const try
|
||
|
{
|
||
|
bool ret{false};
|
||
|
view([&user_id, &ret]
|
||
|
(const json::object &content)
|
||
|
{
|
||
|
const json::object &users
|
||
|
{
|
||
|
content.at("users")
|
||
|
};
|
||
|
|
||
|
const string_view &level
|
||
|
{
|
||
|
unquote(users.at(user_id))
|
||
|
};
|
||
|
|
||
|
ret = json::type(level) == json::NUMBER;
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
catch(const json::error &)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::power::has_collection(const string_view &prop)
|
||
|
const
|
||
|
{
|
||
|
bool ret{false};
|
||
|
view([&prop, &ret]
|
||
|
(const json::object &content)
|
||
|
{
|
||
|
const auto &value{content.get(prop)};
|
||
|
if(value && json::type(value) == json::OBJECT)
|
||
|
ret = true;
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::power::has_level(const string_view &prop)
|
||
|
const
|
||
|
{
|
||
|
bool ret{false};
|
||
|
view([&prop, &ret]
|
||
|
(const json::object &content)
|
||
|
{
|
||
|
const auto &value(unquote(content.get(prop)));
|
||
|
if(value && json::type(value) == json::NUMBER)
|
||
|
ret = true;
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ircd::m::room::power::for_each(const closure &closure)
|
||
|
const
|
||
|
{
|
||
|
for_each(string_view{}, closure);
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::power::for_each(const closure_bool &closure)
|
||
|
const
|
||
|
{
|
||
|
return for_each(string_view{}, closure);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ircd::m::room::power::for_each(const string_view &prop,
|
||
|
const closure &closure)
|
||
|
const
|
||
|
{
|
||
|
for_each(prop, closure_bool{[&closure]
|
||
|
(const string_view &key, const int64_t &level)
|
||
|
{
|
||
|
closure(key, level);
|
||
|
return true;
|
||
|
}});
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::power::for_each(const string_view &prop,
|
||
|
const closure_bool &closure)
|
||
|
const
|
||
|
{
|
||
|
bool ret{true};
|
||
|
view([&prop, &closure, &ret]
|
||
|
(const json::object &content)
|
||
|
{
|
||
|
const json::object &collection
|
||
|
{
|
||
|
// This little cmov gimmick sets collection to be the outer object
|
||
|
// itself if no property was given, allowing us to reuse this func
|
||
|
// for all iterations of key -> level mappings.
|
||
|
prop? json::object{content.get(prop)} : content
|
||
|
};
|
||
|
|
||
|
const string_view _collection{collection};
|
||
|
if(prop && (!_collection || json::type(_collection) != json::OBJECT))
|
||
|
return;
|
||
|
|
||
|
for(auto it(begin(collection)); it != end(collection) && ret; ++it)
|
||
|
{
|
||
|
const auto &member(*it);
|
||
|
if(json::type(unquote(member.second)) != json::NUMBER)
|
||
|
continue;
|
||
|
|
||
|
const auto &key
|
||
|
{
|
||
|
unquote(member.first)
|
||
|
};
|
||
|
|
||
|
const auto &val
|
||
|
{
|
||
|
lex_cast<int64_t>(member.second)
|
||
|
};
|
||
|
|
||
|
ret = closure(key, val);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
ircd::m::room::power::view(const std::function<void (const json::object &)> &closure)
|
||
|
const
|
||
|
{
|
||
|
if(power_event_idx)
|
||
|
if(m::get(std::nothrow, power_event_idx, "content", closure))
|
||
|
return true;
|
||
|
|
||
|
closure(power_event_content);
|
||
|
return !empty(power_event_content);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// room::stats
|
||
|
//
|
||
|
|
||
|
size_t
|
||
|
__attribute__((noreturn))
|
||
|
ircd::m::room::stats::bytes_total(const m::room &room)
|
||
|
{
|
||
|
throw m::UNSUPPORTED
|
||
|
{
|
||
|
"Not yet implemented."
|
||
|
};
|
||
|
}
|
||
|
|
||
|
size_t
|
||
|
__attribute__((noreturn))
|
||
|
ircd::m::room::stats::bytes_total_compressed(const m::room &room)
|
||
|
{
|
||
|
throw m::UNSUPPORTED
|
||
|
{
|
||
|
"Not yet implemented."
|
||
|
};
|
||
|
}
|
||
|
|
||
|
size_t
|
||
|
ircd::m::room::stats::bytes_json(const m::room &room)
|
||
|
{
|
||
|
size_t ret(0);
|
||
|
for(m::room::events it(room); it; --it)
|
||
|
{
|
||
|
const m::event::idx &event_idx
|
||
|
{
|
||
|
it.event_idx()
|
||
|
};
|
||
|
|
||
|
const byte_view<string_view> key
|
||
|
{
|
||
|
event_idx
|
||
|
};
|
||
|
|
||
|
static const db::gopts gopts
|
||
|
{
|
||
|
db::get::NO_CACHE
|
||
|
};
|
||
|
|
||
|
ret += db::bytes_value(m::dbs::event_json, key, gopts);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
size_t
|
||
|
__attribute__((noreturn))
|
||
|
ircd::m::room::stats::bytes_json_compressed(const m::room &room)
|
||
|
{
|
||
|
throw m::UNSUPPORTED
|
||
|
{
|
||
|
"Not yet implemented."
|
||
|
};
|
||
|
}
|