#include <boost/url/url_view.hpp>
#include <boost/url/url.hpp>
#include <boost/url/optional.hpp>
#include <boost/url/parse.hpp>
#include <boost/url/rfc/absolute_uri_rule.hpp>
#include <boost/url/grammar/digit_chars.hpp>
#include <boost/url/grammar/parse.hpp>
#include "filter_view.hpp"
#include <iostream>
namespace urls = boost::urls;
struct is_exact_topic {
bool
operator()(urls::param_view p);
};
template <class MutableString>
class is_url_with_key {
urls::string_view k_;
MutableString& buf_;
public:
explicit
is_url_with_key(
urls::string_view key,
MutableString& buffer)
: k_(key)
, buf_(buffer) {}
bool
operator()(urls::param_view p);
};
struct param_view_to_url {
urls::url_view
operator()(urls::param_view p);
};
struct param_view_to_param_key {
urls::url_view
operator()(urls::param_view p);
};
struct to_decoded_value {
urls::string_view
operator()(urls::param_view p)
{
return p.value;
}
};
struct param_view_to_infohash {
urls::string_view
operator()(urls::param_view p);
};
struct to_protocol {
urls::string_view
operator()(urls::param_view p);
};
struct magnet_link_rule_t;
class magnet_link_view
{
urls::url_view u_;
public:
using topics_view =
filter_view<
urls::params_view,
urls::url_view,
is_exact_topic,
param_view_to_url>;
using info_hashes_view =
filter_view<
urls::params_view,
urls::string_view,
is_exact_topic,
param_view_to_infohash>;
using protocols_view =
filter_view<
urls::params_view,
urls::string_view,
is_exact_topic,
to_protocol>;
template <class MutableString>
using keys_view =
filter_view<
urls::params_view,
urls::string_view,
is_url_with_key<MutableString>,
to_decoded_value>;
topics_view
exact_topics() const noexcept;
info_hashes_view
info_hashes() const noexcept;
protocols_view
protocols() const noexcept;
template <class MutableString>
keys_view<MutableString>
address_trackers(MutableString& buffer) const;
template <class MutableString>
keys_view<MutableString>
exact_sources(MutableString& buffer) const;
template <class MutableString>
keys_view<MutableString>
acceptable_sources(MutableString& buffer) const;
boost::optional<urls::decode_view>
keyword_topic() const noexcept;
template <class MutableString>
keys_view<MutableString>
manifest_topics(MutableString& buffer) const;
boost::optional<urls::decode_view>
display_name() const noexcept;
template <class MutableString>
keys_view<MutableString>
web_seed(MutableString& buffer) const;
boost::optional<urls::decode_view>
param(urls::string_view key) const noexcept;
friend
std::ostream&
operator<<(std::ostream& os, magnet_link_view m)
{
return os << m.u_;
}
private:
boost::optional<urls::decode_view>
decoded_param(urls::string_view key) const noexcept;
boost::optional<urls::url_view>
url_param(urls::string_view key) const noexcept;
friend magnet_link_rule_t;
};
bool
is_exact_topic::
operator()(urls::param_view p)
{
if (p.key == "xt")
return true;
return
p.key.size() > 3 &&
*std::next(p.key.begin(), 0) == 'x' &&
*std::next(p.key.begin(), 1) == 't' &&
*std::next(p.key.begin(), 2) == '.' &&
std::all_of(
std::next(p.key.begin(), 3),
p.key.end(),
urls::grammar::digit_chars);
}
template <class MutableString>
bool
is_url_with_key<MutableString>::
operator()(urls::param_view p)
{
if (p.key != k_)
return false;
urls::error_code ec;
buf_.assign(p.value.begin(), p.value.end());
if (ec.failed())
return false;
urls::result<urls::url_view> r =
urls::parse_uri(buf_);
return r.has_value();
}
urls::url_view
param_view_to_url::
operator()(urls::param_view p)
{
return
urls::parse_uri(p.value).value();
}
urls::string_view
param_view_to_infohash::
operator()(urls::param_view p)
{
urls::url_view topic =
urls::parse_uri(p.value).value();
urls::string_view t = topic.encoded_path();
std::size_t pos = t.find_last_of(':');
if (pos != urls::string_view::npos)
return t.substr(pos + 1);
return t;
}
urls::string_view
to_protocol::
operator()(urls::param_view p)
{
urls::url_view topic =
urls::parse_uri(p.value).value();
urls::string_view t = topic.encoded_path();
std::size_t pos = t.find_last_of(':');
return t.substr(0, pos);
}
auto
magnet_link_view::exact_topics() const noexcept
-> topics_view
{
return {u_.params()};
}
auto
magnet_link_view::info_hashes() const noexcept
-> info_hashes_view
{
return {u_.params()};
}
auto
magnet_link_view::protocols() const noexcept
-> protocols_view
{
return {u_.params()};
}
template <class MutableString>
auto
magnet_link_view::address_trackers(MutableString& buffer) const
-> keys_view<MutableString>
{
return {
u_.params(),
is_url_with_key<MutableString>{"tr", buffer}};
}
template <class MutableString>
auto
magnet_link_view::exact_sources(MutableString& buffer) const
-> keys_view<MutableString>
{
return {
u_.params(),
is_url_with_key<MutableString>{"xs", buffer}};
}
template <class MutableString>
auto
magnet_link_view::acceptable_sources(MutableString& buffer) const
-> keys_view<MutableString>
{
return {
u_.params(),
is_url_with_key<MutableString>{"as", buffer}};
}
boost::optional<urls::decode_view>
magnet_link_view::keyword_topic() const noexcept
{
return decoded_param("kt");
}
template <class MutableString>
auto
magnet_link_view::manifest_topics(MutableString& buffer) const
-> keys_view<MutableString>
{
return {
u_.params(),
is_url_with_key<MutableString>{"mt", buffer}};
}
boost::optional<urls::decode_view>
magnet_link_view::display_name() const noexcept
{
return decoded_param("dn");
}
template <class MutableString>
auto
magnet_link_view::web_seed(MutableString& buffer) const
-> keys_view<MutableString>
{
return {
u_.params(),
is_url_with_key<MutableString>{"ws", buffer}};
}
boost::optional<urls::decode_view>
magnet_link_view::param(urls::string_view key) const noexcept
{
urls::params_view ps = u_.params();
auto it = ps.begin();
auto end = ps.end();
while (it != end)
{
urls::param_view p = *it;
if (p.key.size() < 2)
{
++it;
continue;
}
auto first = p.key.begin();
auto mid = std::next(p.key.begin(), 2);
auto last = p.key.end();
urls::decode_view prefix(
urls::string_view(first, mid));
urls::decode_view suffix(
urls::string_view(mid, last));
if (prefix == "x." &&
suffix == key &&
p.has_value)
return urls::decode_view(p.value);
++it;
}
return boost::none;
}
boost::optional<urls::decode_view>
magnet_link_view::decoded_param(urls::string_view key) const noexcept
{
urls::params_encoded_view ps = u_.encoded_params();
auto it = ps.find(key);
if (it != ps.end() && (*it).has_value)
return urls::decode_view((*it).value);
return boost::none;
}
boost::optional<urls::url_view>
magnet_link_view::url_param(urls::string_view key) const noexcept
{
urls::params_encoded_view ps = u_.encoded_params();
auto it = ps.find(key);
if (it != ps.end() && (*it).has_value)
{
urls::result<urls::url_view> r =
urls::parse_uri((*it).value);
if (r)
return *r;
}
return boost::none;
}
struct magnet_link_rule_t
{
using value_type = magnet_link_view;
urls::result< value_type >
parse( char const*& it, char const* end ) const noexcept;
};
auto
magnet_link_rule_t::parse(
char const*& it,
char const* end ) const noexcept
-> urls::result< value_type >
{
urls::result<urls::url_view> r =
urls::grammar::parse(it, end, urls::absolute_uri_rule);
if(!r)
return urls::grammar::error::invalid;
magnet_link_view m;
m.u_ = *r;
auto ps = m.u_.params();
auto pit = ps.begin();
auto pend = ps.end();
pit = std::find_if(pit, pend, is_exact_topic{});
if (pit == pend)
{
return urls::grammar::error::invalid;
}
if (!std::all_of(pit, pend, [](
urls::param_view p)
{
if (!is_exact_topic{}(p))
return true;
urls::result<urls::url_view> u =
urls::parse_uri(p.value);
return u.has_value();
}))
return urls::grammar::error::invalid;
return m;
}
constexpr magnet_link_rule_t magnet_link_rule{};
urls::result< magnet_link_view >
parse_magnet_link( urls::string_view s ) noexcept
{
return urls::grammar::parse(s, magnet_link_rule);
}
int main(int argc, char** argv)
{
if (argc != 2) {
std::cout << argv[0] << "\n";
std::cout << "magnet <link>\n"
"example: magnet magnet:?xt=urn:btih:d2474e86c95b19b8bcfdb92bc12c9d44667cfa36"
"&dn=Leaves+of+Grass+by+Walt+Whitman.epub"
"&tr=udp%3A%2F%2Ftracker.example4.com%3A80"
"&tr=udp%3A%2F%2Ftracker.example5.com%3A80"
"&tr=udp%3A%2F%2Ftracker.example3.com%3A6969"
"&tr=udp%3A%2F%2Ftracker.example2.com%3A80"
"&tr=udp%3A%2F%2Ftracker.example1.com%3A1337\n";
return EXIT_FAILURE;
}
urls::result<magnet_link_view> r =
parse_magnet_link(argv[1]);
if (!r)
return EXIT_FAILURE;
magnet_link_view m = *r;
std::cout << "link: " << m << "\n";
auto xt = m.exact_topics();
for (auto h : xt)
std::cout << "topic: " << h << "\n";
auto hs = m.info_hashes();
for (auto h : hs)
std::cout << "hash: " << h << "\n";
auto ps = m.protocols();
for (auto p : ps)
std::cout << "protocol: " << p << "\n";
std::string buffer;
auto tr = m.address_trackers(buffer);
for (auto h : tr)
std::cout << "tracker: " << h << "\n";
auto xs = m.exact_sources(buffer);
for (auto x : xs)
std::cout << "exact source: " << x << "\n";
auto as = m.acceptable_sources(buffer);
for (auto a : as)
std::cout << "topic: " << a << "\n";
auto mt = m.manifest_topics(buffer);
for (auto a : mt)
std::cout << "manifest topic: " << a << "\n";
auto ws = m.web_seed(buffer);
for (auto a : ws)
std::cout << "web seed: " << a << "\n";
auto kt = m.keyword_topic();
if (kt)
std::cout << "keyword topic: " << *kt << "\n";
auto dn = m.display_name();
if (dn)
std::cout << "display name: " << *dn << "\n";
return EXIT_SUCCESS;
}
#include <boost/url/error.hpp>
#include <boost/url/parse.hpp>
#include <boost/url/segments_encoded_ref.hpp>
#include <boost/url/segments_encoded_view.hpp>
#include <boost/url/string_view.hpp>
#include <boost/url/url.hpp>
#include <boost/url/url_view.hpp>
#include <boost/url/static_url.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <iostream>
namespace urls = boost::urls;
namespace fs = boost::filesystem;
using string_view = urls::string_view;
bool match_prefix(
urls::segments_view target,
urls::segments_view prefix)
{
if (target.size() < prefix.size())
return false;
auto it0 = target.begin();
auto end0 = target.end();
auto it1 = prefix.begin();
auto end1 = prefix.end();
while (
it0 != end0 &&
it1 != end1 &&
*it0 == *it1)
{
++it0;
++it1;
}
return it1 == end1;
}
class route
{
public:
route(string_view prefix, fs::path root)
: prefix_(urls::parse_uri_reference(prefix).value())
, root_(std::move(root))
{}
route(urls::url prefix, fs::path root)
: prefix_(std::move(prefix))
, root_(std::move(root))
{}
bool match(
urls::url_view target,
fs::path& result)
{
if (match_prefix(
target.segments(),
static_cast<urls::url_view>(prefix_).segments()))
{
result = root_;
auto segs = target.segments();
auto it = segs.begin();
auto end = segs.end();
std::advance(it, prefix_.segments().size());
while (it != end)
{
auto seg = *it;
result.append(seg.begin(), seg.end());
++it;
}
return true;
}
return false;
}
private:
urls::url prefix_;
fs::path root_;
};
int
main(int argc, char **argv)
{
namespace urls = boost::urls;
namespace fs = boost::filesystem;
if (argc != 4)
{
fs::path exec = argv[0];
exec = exec.filename();
std::cerr
<< "Usage: " << exec.c_str()
<< " <target> <prefix> <doc_root>\n"
"target: path to make a request\n"
"prefix: url prefix\n"
"doc_root: dir to look for files\n";
return EXIT_FAILURE;
}
try {
urls::url target =
urls::parse_uri_reference(argv[1]).value();
target.normalize_path();
std::string prefix = argv[2];
fs::path root = argv[2];
if (!fs::is_directory(root))
{
std::cerr
<< "Error: " << root
<< " is not a directory\n";
return EXIT_FAILURE;
}
route r(prefix, root);
fs::path result;
if (r.match(target, result))
{
fs::ifstream f(result);
std::string l;
while (std::getline(f, l))
std::cout << l << '\n';
f.close();
}
else
{
std::cout
<< "No " << target << " in prefix "
<< prefix << std::endl;
}
return EXIT_SUCCESS;
}
catch (std::exception &e)
{
std::cerr << e.what() << "\n";
return EXIT_FAILURE;
}
}