mirror of
https://github.com/matrix-construct/construct
synced 2024-12-11 08:02:59 +01:00
271 lines
5.7 KiB
C++
271 lines
5.7 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.
|
|
|
|
#include "rooms.h"
|
|
|
|
using namespace ircd;
|
|
|
|
struct pagination_tokens
|
|
{
|
|
uint8_t limit;
|
|
char dir;
|
|
m::event::id::buf from;
|
|
m::event::id::buf to;
|
|
|
|
pagination_tokens(const m::resource::request &);
|
|
};
|
|
|
|
conf::item<size_t>
|
|
max_filter_miss
|
|
{
|
|
{ "name", "ircd.client.rooms.messages.max_filter_miss" },
|
|
{ "default", 2048L },
|
|
};
|
|
|
|
conf::item<float>
|
|
postfetch_multiplier
|
|
{
|
|
{ "name", "ircd.client.rooms.messages.postfetch.multiplier" },
|
|
{ "default", 2.0 },
|
|
};
|
|
|
|
log::log
|
|
messages_log
|
|
{
|
|
"m.messages"
|
|
};
|
|
|
|
m::resource::response
|
|
get__messages(client &client,
|
|
const m::resource::request &request,
|
|
const m::room::id &room_id)
|
|
{
|
|
const pagination_tokens page
|
|
{
|
|
request
|
|
};
|
|
|
|
const auto &filter_query
|
|
{
|
|
request.query["filter"]
|
|
};
|
|
|
|
const unique_buffer<mutable_buffer> filter_buf
|
|
{
|
|
size(filter_query)
|
|
};
|
|
|
|
const json::object &filter_json
|
|
{
|
|
url::decode(filter_buf, filter_query)
|
|
};
|
|
|
|
const m::room_event_filter filter
|
|
{
|
|
filter_json.has("filter_json")?
|
|
json::object{filter_json.get("filter_json")}:
|
|
filter_json
|
|
};
|
|
|
|
const m::room room
|
|
{
|
|
room_id, page.from
|
|
};
|
|
|
|
if(!visible(room, request.user_id))
|
|
throw m::ACCESS_DENIED
|
|
{
|
|
"You are not permitted to view the room at this event"
|
|
};
|
|
|
|
const m::user::room user_room
|
|
{
|
|
request.user_id
|
|
};
|
|
|
|
const auto room_depth
|
|
{
|
|
m::depth(std::nothrow, room)
|
|
};
|
|
|
|
m::resource::response::chunked response
|
|
{
|
|
client, http::OK
|
|
};
|
|
|
|
json::stack out
|
|
{
|
|
response.buf, response.flusher()
|
|
};
|
|
|
|
json::stack::object top
|
|
{
|
|
out
|
|
};
|
|
|
|
m::event::id::buf start
|
|
{
|
|
page.from
|
|
};
|
|
|
|
m::event::id::buf end
|
|
{
|
|
page.to
|
|
};
|
|
|
|
json::stack::array chunk
|
|
{
|
|
top, "chunk"
|
|
};
|
|
|
|
size_t hit{0}, miss{0};
|
|
m::room::events it
|
|
{
|
|
room
|
|
};
|
|
|
|
m::event::fetch event;
|
|
for(; it; page.dir == 'b'? --it : ++it)
|
|
{
|
|
const auto &event_idx(it.event_idx());
|
|
if(!seek(std::nothrow, event, event_idx))
|
|
continue;
|
|
|
|
end = event.event_id;
|
|
if(hit >= page.limit || miss >= size_t(max_filter_miss))
|
|
break;
|
|
|
|
const bool ok
|
|
{
|
|
(empty(filter_json) || match(filter, event))
|
|
|
|
&& visible(event, request.user_id)
|
|
|
|
&& m::event::append
|
|
{
|
|
chunk, event,
|
|
{
|
|
.event_idx = &event_idx,
|
|
.user_id = &user_room.user.user_id,
|
|
.user_room = &user_room,
|
|
.room_depth = &room_depth,
|
|
}
|
|
}
|
|
};
|
|
|
|
hit += ok;
|
|
miss += !ok;
|
|
}
|
|
chunk.~array();
|
|
|
|
if(it || page.dir == 'b')
|
|
json::stack::member
|
|
{
|
|
top, "start", json::value{start}
|
|
};
|
|
|
|
if(it || page.dir != 'b')
|
|
json::stack::member
|
|
{
|
|
top, "end", json::value{end}
|
|
};
|
|
|
|
// Manually close out and flush here so the client isn't impacted by
|
|
// anything related to the postprefetching loop below.
|
|
top.~object();
|
|
out.flush(true);
|
|
response.finish();
|
|
|
|
// Continue the room::events iteration to prefetch the next results for a
|
|
// client hitting /messages to paginate. This endpoint does not conduct any
|
|
// other prefetching because: 1. The first /messages is predicted by other
|
|
// requests which initiate prefetches. 2. Subsequent /messages are
|
|
// prefetched by the following loop after each request from here. Note that
|
|
// this loop does not yet account for visibility and filters etc.
|
|
size_t postfetched(0);
|
|
const size_t postfetch_max(page.limit * float(postfetch_multiplier));
|
|
for(size_t i(0); i <= postfetch_max && it; ++i, page.dir == 'b'? --it : ++it)
|
|
{
|
|
const auto &event_idx(it.event_idx());
|
|
postfetched += m::prefetch(event_idx);
|
|
}
|
|
|
|
log::debug
|
|
{
|
|
messages_log, "%s in %s from:%s to:%s dir:%c limit:%zu start:%s end:%s hit:%zu miss:%zu post:%zu",
|
|
loghead(client),
|
|
string_view{room_id},
|
|
string_view{page.from},
|
|
string_view{page.to},
|
|
page.dir,
|
|
page.limit,
|
|
string_view{start},
|
|
string_view{end},
|
|
hit,
|
|
miss,
|
|
postfetched,
|
|
};
|
|
|
|
return {};
|
|
}
|
|
|
|
// Client-Server 6.3.6 query parameters
|
|
pagination_tokens::pagination_tokens(const m::resource::request &request)
|
|
try
|
|
:limit
|
|
{
|
|
// The maximum number of events to return. Default: 10.
|
|
// > we limit this to 255 via narrowing cast
|
|
request.query["limit"]?
|
|
uint8_t(lex_cast<ulong>(request.query.at("limit"))):
|
|
uint8_t(10)
|
|
}
|
|
,dir
|
|
{
|
|
// Required. The direction to return events from. One of: ["b", "f"]
|
|
request.query.at("dir").at(0)
|
|
}
|
|
{
|
|
// Required. The token to start returning events from. This token can be
|
|
// obtained from a prev_batch token returned for each room by the sync
|
|
// API, or from a start or end token returned by a previous request to
|
|
// this endpoint.
|
|
//
|
|
// NOTE: Synapse doesn't require this and many clients have forgotten to
|
|
// NOTE: use it so we have to relax the requirement here.
|
|
if(!empty(request.query["from"]))
|
|
from = url::decode(from, request.query.at("from"));
|
|
|
|
// The token to stop returning events at. This token can be obtained from
|
|
// a prev_batch token returned for each room by the sync endpoint, or from
|
|
// a start or end token returned by a previous request to this endpoint.
|
|
if(!empty(request.query["to"]))
|
|
to = url::decode(to, request.query.at("to"));
|
|
|
|
if(dir != 'b' && dir != 'f')
|
|
throw m::BAD_PAGINATION
|
|
{
|
|
"query parameter 'dir' must be 'b' or 'f'"
|
|
};
|
|
}
|
|
catch(const bad_lex_cast &)
|
|
{
|
|
throw m::BAD_PAGINATION
|
|
{
|
|
"query parameter 'limit' is invalid"
|
|
};
|
|
}
|
|
catch(const m::INVALID_MXID &)
|
|
{
|
|
throw m::BAD_PAGINATION
|
|
{
|
|
"query parameter 'from' or 'to' is not a valid token"
|
|
};
|
|
}
|