mirror of
https://github.com/matrix-construct/construct
synced 2024-11-29 02:02:38 +01:00
ircd:Ⓜ️:fed: Split well_known into namespace into unit.
This commit is contained in:
parent
bc3ad3f89c
commit
640e81ef4d
6 changed files with 455 additions and 425 deletions
|
@ -17,9 +17,6 @@ namespace ircd::m::fed
|
|||
id::event::buf fetch_head(const id::room &room_id, const string_view &remote, const id::user &);
|
||||
id::event::buf fetch_head(const id::room &room_id, const string_view &remote);
|
||||
|
||||
string_view fetch_well_known(const mutable_buffer &out, const string_view &origin);
|
||||
string_view well_known(const mutable_buffer &out, const string_view &origin);
|
||||
|
||||
net::hostport matrix_service(net::hostport remote) noexcept;
|
||||
string_view server(const mutable_buffer &out, const string_view &origin);
|
||||
|
||||
|
@ -31,6 +28,7 @@ namespace ircd::m::fed
|
|||
bool clear_error(const string_view &origin);
|
||||
}
|
||||
|
||||
#include "well_known.h"
|
||||
#include "request.h"
|
||||
#include "version.h"
|
||||
#include "key.h"
|
||||
|
|
24
include/ircd/m/fed/well_known.h
Normal file
24
include/ircd/m/fed/well_known.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
// The Construct
|
||||
//
|
||||
// Copyright (C) The Construct Developers, Authors & Contributors
|
||||
// Copyright (C) 2016-2019 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.
|
||||
|
||||
#pragma once
|
||||
#define HAVE_IRCD_M_FED_WELL_KNOWN_H
|
||||
|
||||
namespace ircd::m::fed::well_known
|
||||
{
|
||||
string_view fetch(const mutable_buffer &out, const string_view &origin);
|
||||
string_view get(const mutable_buffer &out, const string_view &origin);
|
||||
|
||||
extern conf::item<size_t> fetch_redirects;
|
||||
extern conf::item<seconds> fetch_timeout;
|
||||
extern conf::item<seconds> cache_max;
|
||||
extern conf::item<seconds> cache_error;
|
||||
extern conf::item<seconds> cache_default;
|
||||
}
|
|
@ -141,6 +141,7 @@ libircd_matrix_la_SOURCES += event_append.cc
|
|||
libircd_matrix_la_SOURCES += event_horizon.cc
|
||||
libircd_matrix_la_SOURCES += events.cc
|
||||
libircd_matrix_la_SOURCES += fed.cc
|
||||
libircd_matrix_la_SOURCES += fed_well_known.cc
|
||||
libircd_matrix_la_SOURCES += feds.cc
|
||||
libircd_matrix_la_SOURCES += fetch.cc
|
||||
libircd_matrix_la_SOURCES += gossip.cc
|
||||
|
|
422
matrix/fed.cc
422
matrix/fed.cc
|
@ -1600,7 +1600,7 @@ ircd::m::fed::server(const mutable_buffer &buf,
|
|||
string_view target
|
||||
{
|
||||
!port(remote)?
|
||||
well_known(buf, host(remote)):
|
||||
well_known::get(buf, host(remote)):
|
||||
origin
|
||||
};
|
||||
|
||||
|
@ -1695,423 +1695,3 @@ ircd::m::fed::fetch_head(const id::room &room_id,
|
|||
|
||||
return prev_event_id;
|
||||
}
|
||||
|
||||
//
|
||||
// well-known matrix server
|
||||
//
|
||||
|
||||
ircd::log::log
|
||||
well_known_log
|
||||
{
|
||||
"m.well-known"
|
||||
};
|
||||
|
||||
ircd::conf::item<ircd::seconds>
|
||||
well_known_cache_default
|
||||
{
|
||||
{ "name", "ircd.m.fed.well-known.cache.default" },
|
||||
{ "default", 24 * 60 * 60L },
|
||||
};
|
||||
|
||||
ircd::conf::item<ircd::seconds>
|
||||
well_known_cache_error
|
||||
{
|
||||
{ "name", "ircd.m.fed.well-known.cache.error" },
|
||||
{ "default", 36 * 60 * 60L },
|
||||
};
|
||||
|
||||
///NOTE: not yet used until HTTP cache headers in response are respected.
|
||||
ircd::conf::item<ircd::seconds>
|
||||
well_known_cache_max
|
||||
{
|
||||
{ "name", "ircd.m.fed.well-known.cache.max" },
|
||||
{ "default", 48 * 60 * 60L },
|
||||
};
|
||||
|
||||
ircd::conf::item<ircd::seconds>
|
||||
well_known_fetch_timeout
|
||||
{
|
||||
{ "name", "ircd.m.fed.well-known.fetch.timeout" },
|
||||
{ "default", 15L },
|
||||
};
|
||||
|
||||
ircd::conf::item<size_t>
|
||||
well_known_fetch_redirects
|
||||
{
|
||||
{ "name", "ircd.m.fed.well-known.fetch.redirects" },
|
||||
{ "default", 2L },
|
||||
};
|
||||
|
||||
ircd::string_view
|
||||
ircd::m::fed::well_known(const mutable_buffer &buf,
|
||||
const string_view &origin)
|
||||
try
|
||||
{
|
||||
static const string_view type
|
||||
{
|
||||
"well-known.matrix.server"
|
||||
};
|
||||
|
||||
const m::room::id::buf room_id
|
||||
{
|
||||
"dns", m::my_host()
|
||||
};
|
||||
|
||||
const m::room room
|
||||
{
|
||||
room_id
|
||||
};
|
||||
|
||||
const m::event::idx event_idx
|
||||
{
|
||||
room.get(std::nothrow, type, origin)
|
||||
};
|
||||
|
||||
const milliseconds origin_server_ts
|
||||
{
|
||||
m::get<time_t>(std::nothrow, event_idx, "origin_server_ts", time_t(0))
|
||||
};
|
||||
|
||||
const json::object content
|
||||
{
|
||||
origin_server_ts > 0ms?
|
||||
m::get(std::nothrow, event_idx, "content", buf):
|
||||
const_buffer{}
|
||||
};
|
||||
|
||||
const seconds ttl
|
||||
{
|
||||
content.get<time_t>("ttl", time_t(86400))
|
||||
};
|
||||
|
||||
const string_view cached
|
||||
{
|
||||
data(buf), move(buf, json::string(content["m.server"]))
|
||||
};
|
||||
|
||||
const system_point expires
|
||||
{
|
||||
origin_server_ts + ttl
|
||||
};
|
||||
|
||||
const bool expired
|
||||
{
|
||||
ircd::now<system_point>() > expires
|
||||
};
|
||||
|
||||
const bool valid
|
||||
{
|
||||
!empty(cached)
|
||||
};
|
||||
|
||||
// Crucial value that will provide us with a return string for this
|
||||
// function in any case. This is obtained by either using the value
|
||||
// found in cache or making a network query for a new value. expired=true
|
||||
// when a network query needs to be made, otherwise we can return the
|
||||
// cached value. If the network query fails, this value is still defaulted
|
||||
// as the origin string to return and we'll also cache that too.
|
||||
const string_view delegated
|
||||
{
|
||||
expired || !valid?
|
||||
fetch_well_known(buf + size(cached), origin):
|
||||
cached
|
||||
};
|
||||
|
||||
// Branch on valid cache hit to return result.
|
||||
if(valid && !expired)
|
||||
{
|
||||
char tmbuf[48];
|
||||
log::debug
|
||||
{
|
||||
well_known_log, "%s found in cache delegated to %s event_idx:%u expires %s",
|
||||
origin,
|
||||
cached,
|
||||
event_idx,
|
||||
timef(tmbuf, expires, localtime),
|
||||
};
|
||||
|
||||
return delegated;
|
||||
}
|
||||
|
||||
// Conditions for valid expired cache hit w/ failure to reacquire.
|
||||
const bool fallback
|
||||
{
|
||||
valid
|
||||
&& expired
|
||||
&& cached != delegated
|
||||
&& origin == delegated
|
||||
&& now<system_point>() < expires + seconds(well_known_cache_max)
|
||||
};
|
||||
|
||||
if(fallback)
|
||||
{
|
||||
char tmbuf[48];
|
||||
log::debug
|
||||
{
|
||||
well_known_log, "%s found in cache delegated to %s event_idx:%u expired %s",
|
||||
origin,
|
||||
cached,
|
||||
event_idx,
|
||||
timef(tmbuf, expires, localtime),
|
||||
};
|
||||
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Any time the well-known result is the same as the origin (that
|
||||
// includes legitimate errors where fetch_well_known() returns the
|
||||
// origin to default) we consider that an error and use the error
|
||||
// TTL value. Sorry, no exponential backoff implemented yet.
|
||||
const auto cache_ttl
|
||||
{
|
||||
origin == delegated?
|
||||
seconds(well_known_cache_error).count():
|
||||
seconds(well_known_cache_default).count()
|
||||
};
|
||||
|
||||
// Write our record to the cache room; note that this doesn't really
|
||||
// match the format of other DNS records in this room since it's a bit
|
||||
// simpler, but we don't share the ircd.dns.rr type prefix anyway.
|
||||
const auto cache_id
|
||||
{
|
||||
m::send(room, m::me(), type, origin, json::members
|
||||
{
|
||||
{ "ttl", cache_ttl },
|
||||
{ "m.server", delegated },
|
||||
})
|
||||
};
|
||||
|
||||
log::debug
|
||||
{
|
||||
well_known_log, "%s caching delegation to %s to cache in %s",
|
||||
origin,
|
||||
delegated,
|
||||
string_view{cache_id},
|
||||
};
|
||||
|
||||
return delegated;
|
||||
}
|
||||
catch(const ctx::interrupted &)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
{
|
||||
log::error
|
||||
{
|
||||
well_known_log, "%s :%s",
|
||||
origin,
|
||||
e.what(),
|
||||
};
|
||||
|
||||
return string_view
|
||||
{
|
||||
data(buf), move(buf, origin)
|
||||
};
|
||||
}
|
||||
|
||||
namespace ircd::m::fed
|
||||
{
|
||||
static std::tuple<http::code, string_view, string_view>
|
||||
fetch_well_known(const mutable_buffer &out,
|
||||
const net::hostport &remote,
|
||||
const string_view &url);
|
||||
}
|
||||
|
||||
ircd::string_view
|
||||
ircd::m::fed::fetch_well_known(const mutable_buffer &user_buf,
|
||||
const string_view &origin)
|
||||
try
|
||||
{
|
||||
static const string_view path
|
||||
{
|
||||
"/.well-known/matrix/server"
|
||||
};
|
||||
|
||||
// If the supplied buffer isn't large enough to make the full
|
||||
// HTTP request, allocate one that is.
|
||||
const unique_mutable_buffer req_buf
|
||||
{
|
||||
size(user_buf) < 8_KiB? 8_KiB: 0UL
|
||||
};
|
||||
|
||||
const mutable_buffer buf
|
||||
{
|
||||
req_buf? req_buf: user_buf
|
||||
};
|
||||
|
||||
rfc3986::uri uri;
|
||||
uri.remote = origin;
|
||||
uri.path = path;
|
||||
json::object response;
|
||||
unique_mutable_buffer carry;
|
||||
for(size_t i(0); i < well_known_fetch_redirects; ++i)
|
||||
{
|
||||
const auto &[code, location, content]
|
||||
{
|
||||
fetch_well_known(buf, uri.remote, uri.path)
|
||||
};
|
||||
|
||||
// Successful error; bail
|
||||
if(code >= 400)
|
||||
return string_view
|
||||
{
|
||||
data(user_buf), move(user_buf, origin)
|
||||
};
|
||||
|
||||
// Successful result; response content handled after loop.
|
||||
if(code < 300)
|
||||
{
|
||||
response = content;
|
||||
break;
|
||||
}
|
||||
|
||||
// Indirection code, but no location response header
|
||||
if(!location)
|
||||
return string_view
|
||||
{
|
||||
data(user_buf), move(user_buf, origin)
|
||||
};
|
||||
|
||||
// Redirection; carry over the new target by copying it because it's
|
||||
// in the buffer which we'll be overwriting for the new request.
|
||||
carry = unique_mutable_buffer{location};
|
||||
uri = string_view{carry};
|
||||
|
||||
// Indirection code, bad location header.
|
||||
if(!uri.path || !uri.remote)
|
||||
return string_view
|
||||
{
|
||||
data(user_buf), move(user_buf, origin)
|
||||
};
|
||||
}
|
||||
|
||||
const json::string &m_server
|
||||
{
|
||||
response["m.server"]
|
||||
};
|
||||
|
||||
if(!m_server)
|
||||
return string_view
|
||||
{
|
||||
data(user_buf), move(user_buf, origin)
|
||||
};
|
||||
|
||||
// This construction validates we didn't get a junk string
|
||||
volatile const net::hostport ret
|
||||
{
|
||||
m_server
|
||||
};
|
||||
|
||||
log::debug
|
||||
{
|
||||
well_known_log, "%s query to %s found delegation to %s",
|
||||
origin,
|
||||
uri.remote,
|
||||
m_server,
|
||||
};
|
||||
|
||||
// Move the returned string to the front of the buffer; this overwrites
|
||||
// any other incoming content to focus on just the unquoted string.
|
||||
return string_view
|
||||
{
|
||||
data(user_buf), move(user_buf, m_server)
|
||||
};
|
||||
}
|
||||
catch(const ctx::interrupted &)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
{
|
||||
log::derror
|
||||
{
|
||||
well_known_log, "%s in network query :%s",
|
||||
origin,
|
||||
e.what(),
|
||||
};
|
||||
|
||||
return string_view
|
||||
{
|
||||
data(user_buf), move(user_buf, origin)
|
||||
};
|
||||
}
|
||||
|
||||
/// Return a tuple of the HTTP code, any Location header, and the response
|
||||
/// content. These values are unconditional if this function doesn't throw,
|
||||
/// but if there's no Location header and/or content then those will be empty
|
||||
/// string_view's. This function is intended to be run in a loop by the caller
|
||||
/// to chase redirection. No HTTP codes will throw from here; server and
|
||||
/// network errors (and others) will.
|
||||
static std::tuple<ircd::http::code, ircd::string_view, ircd::string_view>
|
||||
ircd::m::fed::fetch_well_known(const mutable_buffer &buf,
|
||||
const net::hostport &remote,
|
||||
const string_view &url)
|
||||
{
|
||||
// Hard target https service; do not inherit any matrix service from remote.
|
||||
const net::hostport target
|
||||
{
|
||||
host(remote), "https", port(remote)
|
||||
};
|
||||
|
||||
window_buffer wb
|
||||
{
|
||||
buf
|
||||
};
|
||||
|
||||
http::request
|
||||
{
|
||||
wb, host(target), "GET", url
|
||||
};
|
||||
|
||||
const const_buffer out_head
|
||||
{
|
||||
wb.completed()
|
||||
};
|
||||
|
||||
// Remaining space in buffer is used for received head; note that below
|
||||
// we specify this same buffer for in.content, but that's a trick
|
||||
// recognized by ircd::server to place received content directly after
|
||||
// head in this buffer without any additional dynamic allocation.
|
||||
const mutable_buffer in_head
|
||||
{
|
||||
buf + size(out_head)
|
||||
};
|
||||
|
||||
server::request::opts opts;
|
||||
opts.http_exceptions = false; // 3xx/4xx/5xx response won't throw.
|
||||
server::request request
|
||||
{
|
||||
target,
|
||||
{ out_head },
|
||||
{ in_head, in_head },
|
||||
&opts
|
||||
};
|
||||
|
||||
const auto code
|
||||
{
|
||||
request.get(seconds(well_known_fetch_timeout))
|
||||
};
|
||||
|
||||
thread_local char rembuf[rfc3986::DOMAIN_BUFSIZE * 2];
|
||||
log::debug
|
||||
{
|
||||
well_known_log, "fetch from %s %s :%u %s",
|
||||
string(rembuf, target),
|
||||
url,
|
||||
uint(code),
|
||||
http::status(code)
|
||||
};
|
||||
|
||||
const http::response::head head
|
||||
{
|
||||
request.in.gethead(request)
|
||||
};
|
||||
|
||||
return
|
||||
{
|
||||
code,
|
||||
head.location,
|
||||
request.in.content,
|
||||
};
|
||||
}
|
||||
|
|
427
matrix/fed_well_known.cc
Normal file
427
matrix/fed_well_known.cc
Normal file
|
@ -0,0 +1,427 @@
|
|||
// The Construct
|
||||
//
|
||||
// Copyright (C) The Construct Developers, Authors & Contributors
|
||||
// Copyright (C) 2016-2020 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.
|
||||
|
||||
namespace ircd::m::fed::well_known
|
||||
{
|
||||
static std::tuple<ircd::http::code, ircd::string_view, ircd::string_view>
|
||||
fetch(const mutable_buffer &,
|
||||
const net::hostport &,
|
||||
const string_view &url);
|
||||
|
||||
extern log::log log;
|
||||
}
|
||||
|
||||
decltype(ircd::m::fed::well_known::log)
|
||||
ircd::m::fed::well_known::log
|
||||
{
|
||||
"m.well-known"
|
||||
};
|
||||
|
||||
decltype(ircd::m::fed::well_known::cache_default)
|
||||
ircd::m::fed::well_known::cache_default
|
||||
{
|
||||
{ "name", "ircd.m.fed.well-known.cache.default" },
|
||||
{ "default", 24 * 60 * 60L },
|
||||
};
|
||||
|
||||
decltype(ircd::m::fed::well_known::cache_error)
|
||||
ircd::m::fed::well_known::cache_error
|
||||
{
|
||||
{ "name", "ircd.m.fed.well-known.cache.error" },
|
||||
{ "default", 36 * 60 * 60L },
|
||||
};
|
||||
|
||||
///NOTE: not yet used until HTTP cache headers in response are respected.
|
||||
decltype(ircd::m::fed::well_known::cache_max)
|
||||
ircd::m::fed::well_known::cache_max
|
||||
{
|
||||
{ "name", "ircd.m.fed.well-known.cache.max" },
|
||||
{ "default", 48 * 60 * 60L },
|
||||
};
|
||||
|
||||
decltype(ircd::m::fed::well_known::fetch_timeout)
|
||||
ircd::m::fed::well_known::fetch_timeout
|
||||
{
|
||||
{ "name", "ircd.m.fed.well-known.fetch.timeout" },
|
||||
{ "default", 15L },
|
||||
};
|
||||
|
||||
decltype(ircd::m::fed::well_known::fetch_redirects)
|
||||
ircd::m::fed::well_known::fetch_redirects
|
||||
{
|
||||
{ "name", "ircd.m.fed.well-known.fetch.redirects" },
|
||||
{ "default", 2L },
|
||||
};
|
||||
|
||||
ircd::string_view
|
||||
ircd::m::fed::well_known::get(const mutable_buffer &buf,
|
||||
const string_view &origin)
|
||||
try
|
||||
{
|
||||
static const string_view type
|
||||
{
|
||||
"well-known.matrix.server"
|
||||
};
|
||||
|
||||
const m::room::id::buf room_id
|
||||
{
|
||||
"dns", m::my_host()
|
||||
};
|
||||
|
||||
const m::room room
|
||||
{
|
||||
room_id
|
||||
};
|
||||
|
||||
const m::event::idx event_idx
|
||||
{
|
||||
room.get(std::nothrow, type, origin)
|
||||
};
|
||||
|
||||
const milliseconds origin_server_ts
|
||||
{
|
||||
m::get<time_t>(std::nothrow, event_idx, "origin_server_ts", time_t(0))
|
||||
};
|
||||
|
||||
const json::object content
|
||||
{
|
||||
origin_server_ts > 0ms?
|
||||
m::get(std::nothrow, event_idx, "content", buf):
|
||||
const_buffer{}
|
||||
};
|
||||
|
||||
const seconds ttl
|
||||
{
|
||||
content.get<time_t>("ttl", time_t(86400))
|
||||
};
|
||||
|
||||
const string_view cached
|
||||
{
|
||||
data(buf), move(buf, json::string(content["m.server"]))
|
||||
};
|
||||
|
||||
const system_point expires
|
||||
{
|
||||
origin_server_ts + ttl
|
||||
};
|
||||
|
||||
const bool expired
|
||||
{
|
||||
ircd::now<system_point>() > expires
|
||||
};
|
||||
|
||||
const bool valid
|
||||
{
|
||||
!empty(cached)
|
||||
};
|
||||
|
||||
// Crucial value that will provide us with a return string for this
|
||||
// function in any case. This is obtained by either using the value
|
||||
// found in cache or making a network query for a new value. expired=true
|
||||
// when a network query needs to be made, otherwise we can return the
|
||||
// cached value. If the network query fails, this value is still defaulted
|
||||
// as the origin string to return and we'll also cache that too.
|
||||
const string_view delegated
|
||||
{
|
||||
expired || !valid?
|
||||
fetch(buf + size(cached), origin):
|
||||
cached
|
||||
};
|
||||
|
||||
// Branch on valid cache hit to return result.
|
||||
if(valid && !expired)
|
||||
{
|
||||
char tmbuf[48];
|
||||
log::debug
|
||||
{
|
||||
log, "%s found in cache delegated to %s event_idx:%u expires %s",
|
||||
origin,
|
||||
cached,
|
||||
event_idx,
|
||||
timef(tmbuf, expires, localtime),
|
||||
};
|
||||
|
||||
return delegated;
|
||||
}
|
||||
|
||||
// Conditions for valid expired cache hit w/ failure to reacquire.
|
||||
const bool fallback
|
||||
{
|
||||
valid
|
||||
&& expired
|
||||
&& cached != delegated
|
||||
&& origin == delegated
|
||||
&& now<system_point>() < expires + seconds(cache_max)
|
||||
};
|
||||
|
||||
if(fallback)
|
||||
{
|
||||
char tmbuf[48];
|
||||
log::debug
|
||||
{
|
||||
log, "%s found in cache delegated to %s event_idx:%u expired %s",
|
||||
origin,
|
||||
cached,
|
||||
event_idx,
|
||||
timef(tmbuf, expires, localtime),
|
||||
};
|
||||
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Any time the well-known result is the same as the origin (that
|
||||
// includes legitimate errors where fetch_well_known() returns the
|
||||
// origin to default) we consider that an error and use the error
|
||||
// TTL value. Sorry, no exponential backoff implemented yet.
|
||||
const auto cache_ttl
|
||||
{
|
||||
origin == delegated?
|
||||
seconds(cache_error).count():
|
||||
seconds(cache_default).count()
|
||||
};
|
||||
|
||||
// Write our record to the cache room; note that this doesn't really
|
||||
// match the format of other DNS records in this room since it's a bit
|
||||
// simpler, but we don't share the ircd.dns.rr type prefix anyway.
|
||||
const auto cache_id
|
||||
{
|
||||
m::send(room, m::me(), type, origin, json::members
|
||||
{
|
||||
{ "ttl", cache_ttl },
|
||||
{ "m.server", delegated },
|
||||
})
|
||||
};
|
||||
|
||||
log::debug
|
||||
{
|
||||
log, "%s caching delegation to %s to cache in %s",
|
||||
origin,
|
||||
delegated,
|
||||
string_view{cache_id},
|
||||
};
|
||||
|
||||
return delegated;
|
||||
}
|
||||
catch(const ctx::interrupted &)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
{
|
||||
log::error
|
||||
{
|
||||
log, "%s :%s",
|
||||
origin,
|
||||
e.what(),
|
||||
};
|
||||
|
||||
return string_view
|
||||
{
|
||||
data(buf), move(buf, origin)
|
||||
};
|
||||
}
|
||||
|
||||
ircd::string_view
|
||||
ircd::m::fed::well_known::fetch(const mutable_buffer &user_buf,
|
||||
const string_view &origin)
|
||||
try
|
||||
{
|
||||
static const string_view path
|
||||
{
|
||||
"/.well-known/matrix/server"
|
||||
};
|
||||
|
||||
// If the supplied buffer isn't large enough to make the full
|
||||
// HTTP request, allocate one that is.
|
||||
const unique_mutable_buffer req_buf
|
||||
{
|
||||
size(user_buf) < 8_KiB? 8_KiB: 0UL
|
||||
};
|
||||
|
||||
const mutable_buffer buf
|
||||
{
|
||||
req_buf? req_buf: user_buf
|
||||
};
|
||||
|
||||
rfc3986::uri uri;
|
||||
uri.remote = origin;
|
||||
uri.path = path;
|
||||
json::object response;
|
||||
unique_mutable_buffer carry;
|
||||
for(size_t i(0); i < fetch_redirects; ++i)
|
||||
{
|
||||
const auto &[code, location, content]
|
||||
{
|
||||
fetch(buf, uri.remote, uri.path)
|
||||
};
|
||||
|
||||
// Successful error; bail
|
||||
if(code >= 400)
|
||||
return string_view
|
||||
{
|
||||
data(user_buf), move(user_buf, origin)
|
||||
};
|
||||
|
||||
// Successful result; response content handled after loop.
|
||||
if(code < 300)
|
||||
{
|
||||
response = content;
|
||||
break;
|
||||
}
|
||||
|
||||
// Indirection code, but no location response header
|
||||
if(!location)
|
||||
return string_view
|
||||
{
|
||||
data(user_buf), move(user_buf, origin)
|
||||
};
|
||||
|
||||
// Redirection; carry over the new target by copying it because it's
|
||||
// in the buffer which we'll be overwriting for the new request.
|
||||
carry = unique_mutable_buffer{location};
|
||||
uri = string_view{carry};
|
||||
|
||||
// Indirection code, bad location header.
|
||||
if(!uri.path || !uri.remote)
|
||||
return string_view
|
||||
{
|
||||
data(user_buf), move(user_buf, origin)
|
||||
};
|
||||
}
|
||||
|
||||
const json::string &m_server
|
||||
{
|
||||
response["m.server"]
|
||||
};
|
||||
|
||||
if(!m_server)
|
||||
return string_view
|
||||
{
|
||||
data(user_buf), move(user_buf, origin)
|
||||
};
|
||||
|
||||
// This construction validates we didn't get a junk string
|
||||
volatile const net::hostport ret
|
||||
{
|
||||
m_server
|
||||
};
|
||||
|
||||
log::debug
|
||||
{
|
||||
log, "%s query to %s found delegation to %s",
|
||||
origin,
|
||||
uri.remote,
|
||||
m_server,
|
||||
};
|
||||
|
||||
// Move the returned string to the front of the buffer; this overwrites
|
||||
// any other incoming content to focus on just the unquoted string.
|
||||
return string_view
|
||||
{
|
||||
data(user_buf), move(user_buf, m_server)
|
||||
};
|
||||
}
|
||||
catch(const ctx::interrupted &)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
{
|
||||
log::derror
|
||||
{
|
||||
log, "%s in network query :%s",
|
||||
origin,
|
||||
e.what(),
|
||||
};
|
||||
|
||||
return string_view
|
||||
{
|
||||
data(user_buf), move(user_buf, origin)
|
||||
};
|
||||
}
|
||||
|
||||
/// Return a tuple of the HTTP code, any Location header, and the response
|
||||
/// content. These values are unconditional if this function doesn't throw,
|
||||
/// but if there's no Location header and/or content then those will be empty
|
||||
/// string_view's. This function is intended to be run in a loop by the caller
|
||||
/// to chase redirection. No HTTP codes will throw from here; server and
|
||||
/// network errors (and others) will.
|
||||
std::tuple<ircd::http::code, ircd::string_view, ircd::string_view>
|
||||
ircd::m::fed::well_known::fetch(const mutable_buffer &buf,
|
||||
const net::hostport &remote,
|
||||
const string_view &url)
|
||||
{
|
||||
// Hard target https service; do not inherit any matrix service from remote.
|
||||
const net::hostport target
|
||||
{
|
||||
host(remote), "https", port(remote)
|
||||
};
|
||||
|
||||
window_buffer wb
|
||||
{
|
||||
buf
|
||||
};
|
||||
|
||||
http::request
|
||||
{
|
||||
wb, host(target), "GET", url
|
||||
};
|
||||
|
||||
const const_buffer out_head
|
||||
{
|
||||
wb.completed()
|
||||
};
|
||||
|
||||
// Remaining space in buffer is used for received head; note that below
|
||||
// we specify this same buffer for in.content, but that's a trick
|
||||
// recognized by ircd::server to place received content directly after
|
||||
// head in this buffer without any additional dynamic allocation.
|
||||
const mutable_buffer in_head
|
||||
{
|
||||
buf + size(out_head)
|
||||
};
|
||||
|
||||
server::request::opts opts;
|
||||
opts.http_exceptions = false; // 3xx/4xx/5xx response won't throw.
|
||||
server::request request
|
||||
{
|
||||
target,
|
||||
{ out_head },
|
||||
{ in_head, in_head },
|
||||
&opts
|
||||
};
|
||||
|
||||
const auto code
|
||||
{
|
||||
request.get(seconds(fetch_timeout))
|
||||
};
|
||||
|
||||
thread_local char rembuf[rfc3986::DOMAIN_BUFSIZE * 2];
|
||||
log::debug
|
||||
{
|
||||
log, "fetch from %s %s :%u %s",
|
||||
string(rembuf, target),
|
||||
url,
|
||||
uint(code),
|
||||
http::status(code)
|
||||
};
|
||||
|
||||
const http::response::head head
|
||||
{
|
||||
request.in.gethead(request)
|
||||
};
|
||||
|
||||
return
|
||||
{
|
||||
code,
|
||||
head.location,
|
||||
request.in.content,
|
||||
};
|
||||
}
|
|
@ -15458,7 +15458,7 @@ console_cmd__well_known__matrix__server(opt &out, const string_view &line)
|
|||
|
||||
const net::hostport result
|
||||
{
|
||||
m::fed::fetch_well_known(buf, remote)
|
||||
m::fed::well_known::fetch(buf, remote)
|
||||
};
|
||||
|
||||
out << result << std::endl;
|
||||
|
|
Loading…
Reference in a new issue