// The Construct // // Copyright (C) The Construct Developers, Authors & Contributors // Copyright (C) 2016-2020 Jason Volk // // 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. namespace ircd::m::bridge { static thread_local char tmp[4][512]; } decltype(ircd::m::bridge::log) ircd::m::bridge::log { "m.bridge" }; ircd::json::object ircd::m::bridge::protocol(const mutable_buffer &buf, const config &config, const string_view &name) { const string_view &uri { make_uri(tmp[0], config, fmt::sprintf { tmp[1], "thirdparty/protocol/%s", url::encode(tmp[2], name), }) }; switch(const query query{config, uri, buf}; query.code) { case http::OK: return json::object { query.request.in.content }; default: throw http::error { query.code }; } } bool ircd::m::bridge::exists(const config &config, const m::user::id &user_id) { const string_view &uri { make_uri(tmp[0], config, fmt::sprintf { tmp[1], "users/%s", url::encode(tmp[2], user_id), }) }; switch(const query query{config, uri}; query.code) { case http::OK: return true; case http::NOT_FOUND: return false; default: throw http::error { query.code }; } } bool ircd::m::bridge::exists(const config &config, const m::room::alias &room_alias) { const string_view &uri { make_uri(tmp[0], config, fmt::sprintf { tmp[1], "rooms/%s", url::encode(tmp[2], room_alias), }) }; switch(const query query{config, uri}; query.code) { case http::OK: return true; case http::NOT_FOUND: return false; default: throw http::error { query.code }; } } ircd::string_view ircd::m::bridge::make_uri(const mutable_buffer &buf, const config &config, const string_view &path) { const rfc3986::uri base_url { at<"url"_>(config) }; char hs_token[256]; return fmt::sprintf { buf, "%s/_matrix/app/v1/%s?access_token=%s", base_url.path, path, url::encode(hs_token, at<"hs_token"_>(config)), }; } // // query // decltype(ircd::m::bridge::query::timeout) ircd::m::bridge::query::timeout { { "name", "ircd.m.bridge.query.timeout" }, { "default", 10L }, }; ircd::m::bridge::query::query(const config &config, const string_view &uri, const mutable_buffer &in_body) :base_url { at<"url"_>(config) } ,buf { 8_KiB } ,uri { uri } ,window { buf } ,hypertext { window, base_url.remote, "GET", uri, } ,sopts { false, // http_exceptions } ,request { net::hostport { base_url.remote }, server::out { window.completed(), {} }, server::in { window.remains(), empty(in_body)? window.remains(): in_body }, &sopts, } ,code { request.get(seconds(timeout)) } { } // // config // bool ircd::m::bridge::config::exists(const string_view &id) { return get(std::nothrow, id, [] (const auto &, const auto &, const auto &) noexcept { // no action }); } void ircd::m::bridge::config::get(const string_view &id, const closure &closure) { if(unlikely(!get(std::nothrow, id, closure))) throw m::NOT_FOUND { "No bridge config found for '%s'", id, }; } bool ircd::m::bridge::config::get(std::nothrow_t, const string_view &id, const closure &closure) { return !config::for_each([&id, &closure] (const auto &event_idx, const auto &event, const config &config) { if(json::get<"id"_>(config) != id) return true; closure(event_idx, event, config); return false; }); } bool ircd::m::bridge::config::for_each(const closure_bool &closure) { return events::type::for_each_in("ircd.bridge", [&closure] (const string_view &, const event::idx &event_idx) { const m::event::fetch event { std::nothrow, event_idx }; if(unlikely(!event.valid)) return true; if(!my(event)) return true; if(!defined(json::get<"state_key"_>(event))) return true; const bridge::config config { json::get<"content"_>(event) }; if(!json::get<"id"_>(config)) return true; // the state_key has to match the id for now if(json::get<"id"_>(config) != json::get<"state_key"_>(event)) return true; // filter replaced state if(room::state::next(event_idx)) return true; // filter redacted state if(redacted(event_idx)) return true; const user::id sender { json::get<"sender"_>(event) }; if(!is_oper(sender)) return true; if(likely(closure(event_idx, event, config))) return true; return false; }); }