2015-11-01 01:22:41 +08:00
|
|
|
/**
|
|
|
|
|
* Address.h
|
|
|
|
|
*
|
|
|
|
|
* An AMQP address in the "amqp://user:password@hostname:port/vhost" notation
|
|
|
|
|
*
|
|
|
|
|
* @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
|
2018-03-06 00:29:37 +08:00
|
|
|
* @copyright 2015 - 2018 Copernica BV
|
2015-11-01 01:22:41 +08:00
|
|
|
*/
|
2018-03-02 19:14:43 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
/**
|
|
|
|
|
* Include guard
|
|
|
|
|
*/
|
|
|
|
|
#pragma once
|
|
|
|
|
|
2020-09-28 21:48:45 +08:00
|
|
|
/**
|
|
|
|
|
* Includes
|
|
|
|
|
*/
|
|
|
|
|
#include <type_traits>
|
|
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
/**
|
|
|
|
|
* Set up namespace
|
|
|
|
|
*/
|
|
|
|
|
namespace AMQP {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Class definition
|
|
|
|
|
*/
|
|
|
|
|
class Address
|
|
|
|
|
{
|
2020-10-05 21:43:36 +08:00
|
|
|
private:
|
|
|
|
|
/**
|
|
|
|
|
* Helper class to do case insensitive comparison
|
|
|
|
|
*/
|
|
|
|
|
struct icasecmp
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* Comparison operator <. Should exhibit SWO.
|
|
|
|
|
* @param lhs
|
|
|
|
|
* @param rhs
|
|
|
|
|
* @return bool lhs < rhs
|
|
|
|
|
*/
|
|
|
|
|
bool operator() (const std::string& lhs, const std::string& rhs) const { return strcasecmp(lhs.c_str(), rhs.c_str()) < 0; }
|
|
|
|
|
};
|
|
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
private:
|
2018-03-02 19:14:43 +08:00
|
|
|
/**
|
|
|
|
|
* The auth method
|
2018-03-02 20:53:00 +08:00
|
|
|
* @var bool
|
2018-03-02 19:14:43 +08:00
|
|
|
*/
|
2018-03-10 17:31:40 +08:00
|
|
|
bool _secure = false;
|
2018-03-02 19:14:43 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
/**
|
|
|
|
|
* Login data (username + password)
|
|
|
|
|
* @var Login
|
|
|
|
|
*/
|
|
|
|
|
Login _login;
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
/**
|
|
|
|
|
* The hostname
|
|
|
|
|
* @var std::string
|
|
|
|
|
*/
|
|
|
|
|
std::string _hostname;
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
/**
|
|
|
|
|
* Port number
|
|
|
|
|
* @var uint16_t
|
|
|
|
|
*/
|
|
|
|
|
uint16_t _port = 5672;
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
/**
|
|
|
|
|
* The vhost
|
|
|
|
|
* @var std::string
|
|
|
|
|
*/
|
|
|
|
|
std::string _vhost;
|
2020-09-28 21:48:45 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Extra provided options after the question mark /vhost?option=value
|
|
|
|
|
* @var std::map<std::string,std::string>
|
|
|
|
|
*/
|
2020-10-05 21:43:36 +08:00
|
|
|
std::map<std::string, std::string, icasecmp> _options;
|
2018-03-10 17:31:40 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The default port
|
|
|
|
|
* @return uint16_t
|
|
|
|
|
*/
|
|
|
|
|
uint16_t defaultport() const
|
|
|
|
|
{
|
|
|
|
|
return _secure ? 5671 : 5672;
|
|
|
|
|
}
|
2015-11-01 01:22:41 +08:00
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
/**
|
|
|
|
|
* Constructor to parse an address string
|
2017-05-02 03:59:45 +08:00
|
|
|
* The address should start with "amqp://
|
|
|
|
|
* @param data
|
|
|
|
|
* @param size
|
2015-11-01 01:22:41 +08:00
|
|
|
* @throws std::runtime_error
|
|
|
|
|
*/
|
2017-05-02 03:59:45 +08:00
|
|
|
Address(const char *data, size_t size) : _vhost("/")
|
2015-11-01 01:22:41 +08:00
|
|
|
{
|
2017-05-02 03:59:45 +08:00
|
|
|
// position of the last byte
|
|
|
|
|
const char *last = data + size;
|
|
|
|
|
|
2018-03-06 00:29:37 +08:00
|
|
|
// must start with ampqs:// to have a secure connection (and we also assign a different default port)
|
2018-03-10 17:31:40 +08:00
|
|
|
if (strncmp(data, "amqps://", 8) == 0) _secure = true;
|
2018-03-06 15:45:12 +08:00
|
|
|
|
|
|
|
|
// otherwise protocol must be amqp://
|
2018-03-10 17:32:48 +08:00
|
|
|
else if (strncmp(data, "amqp://", 7) != 0) throw std::runtime_error("AMQP address should start with \"amqp://\" or \"amqps://\"");
|
2018-03-10 17:31:40 +08:00
|
|
|
|
|
|
|
|
// assign default port (we may overwrite it later)
|
|
|
|
|
_port = defaultport();
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2017-05-02 03:59:45 +08:00
|
|
|
// begin of the string was parsed
|
2018-03-02 20:53:00 +08:00
|
|
|
data += _secure ? 8 : 7;
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
// do we have a '@' to split user-data and hostname?
|
2017-05-02 03:59:45 +08:00
|
|
|
const char *at = (const char *)memchr(data, '@', last - data);
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
// do we have one?
|
|
|
|
|
if (at != nullptr)
|
|
|
|
|
{
|
2017-05-02 03:59:45 +08:00
|
|
|
// size of the user:password
|
|
|
|
|
size_t loginsize = at - data;
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
// colon could split username and password
|
2017-05-02 03:59:45 +08:00
|
|
|
const char *colon = (const char *)memchr(data, ':', loginsize);
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
// assign the login
|
|
|
|
|
_login = Login(
|
2017-05-02 03:59:45 +08:00
|
|
|
std::string(data, colon ? colon - data : loginsize),
|
2015-11-01 01:22:41 +08:00
|
|
|
std::string(colon ? colon + 1 : "", colon ? at - colon - 1 : 0)
|
|
|
|
|
);
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2017-05-02 03:59:45 +08:00
|
|
|
// set data to the start of the hostname
|
|
|
|
|
data = at + 1;
|
2015-11-01 01:22:41 +08:00
|
|
|
}
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
// find out where the vhost is set (starts with a slash)
|
2017-05-02 03:59:45 +08:00
|
|
|
const char *slash = (const char *)memchr(data, '/', last - data);
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2020-09-29 18:04:09 +08:00
|
|
|
// where to start looking for the question mark, we also want to support urls where the
|
|
|
|
|
// hostname does not have a slash.
|
|
|
|
|
const char *start = slash ? slash : data;
|
|
|
|
|
|
|
|
|
|
// we search for the ? for extra options
|
|
|
|
|
const char *qm = static_cast<const char *>(memchr(start, '?', last - start));
|
2020-09-28 21:48:45 +08:00
|
|
|
|
|
|
|
|
// if there is a questionmark, we need to parse all options
|
|
|
|
|
if (qm != nullptr && last - qm > 1)
|
|
|
|
|
{
|
2020-09-29 18:04:09 +08:00
|
|
|
// we start at question mark now
|
|
|
|
|
start = qm;
|
2020-09-28 21:48:45 +08:00
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
// find the next equals sign and start of the next parameter
|
|
|
|
|
const char *equals = (const char *)memchr(start + 1, '=', last - start - 1);
|
|
|
|
|
const char *next = (const char *)memchr(start + 1, '&', last - start - 1);
|
|
|
|
|
|
|
|
|
|
// assign it to the options if we found an equals sign
|
|
|
|
|
if (equals) _options[std::string(start + 1, equals - start - 1)] = std::string(equals + 1, (next ? next - equals : last - equals) - 1);
|
|
|
|
|
|
2020-09-29 18:04:09 +08:00
|
|
|
// we now have a new start, the next '&...'
|
2020-09-28 21:48:45 +08:00
|
|
|
start = next;
|
|
|
|
|
|
|
|
|
|
// keep iterating as long as there are more vars
|
|
|
|
|
} while (start);
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
// was a vhost set?
|
2020-09-28 21:48:45 +08:00
|
|
|
if (slash != nullptr && last - slash > 1) _vhost.assign(slash + 1, (qm ? qm - slash : last - slash) - 1);
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
// the hostname is everything until the slash, check is portnumber was set
|
2017-05-02 03:59:45 +08:00
|
|
|
const char *colon = (const char *)memchr(data, ':', last - data);
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
// was a portnumber specified (colon must appear before the slash of the vhost)
|
|
|
|
|
if (colon && (!slash || colon < slash))
|
|
|
|
|
{
|
|
|
|
|
// a portnumber was set to
|
2017-05-02 03:59:45 +08:00
|
|
|
_hostname.assign(data, colon - data);
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2017-05-02 03:59:45 +08:00
|
|
|
// calculate the port
|
|
|
|
|
_port = atoi(std::string(colon + 1, slash ? slash - colon - 1 : last - colon - 1).data());
|
2015-11-01 01:22:41 +08:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// no portnumber was set
|
2017-05-02 03:59:45 +08:00
|
|
|
_hostname.assign(data, slash ? slash - data : last - data);
|
2015-11-01 01:22:41 +08:00
|
|
|
}
|
|
|
|
|
}
|
2017-05-02 03:59:45 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Constructor to parse an address string
|
2018-03-02 19:14:43 +08:00
|
|
|
* The address should start with amqp:// or amqps://
|
2017-05-02 03:59:45 +08:00
|
|
|
* @param data
|
|
|
|
|
* @throws std::runtime_error
|
|
|
|
|
*/
|
|
|
|
|
Address(const char *data) : Address(data, strlen(data)) {}
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 19:17:33 +08:00
|
|
|
/**
|
|
|
|
|
* Constructor based on std::string
|
|
|
|
|
* @param address
|
|
|
|
|
*/
|
2017-05-02 03:59:45 +08:00
|
|
|
Address(const std::string &address) : Address(address.data(), address.size()) {}
|
2015-11-03 00:47:21 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Constructor based on already known properties
|
|
|
|
|
* @param host
|
|
|
|
|
* @param port
|
|
|
|
|
* @param login
|
|
|
|
|
* @param vhost
|
2018-03-06 15:45:12 +08:00
|
|
|
* @param secure
|
2015-11-03 00:47:21 +08:00
|
|
|
*/
|
2018-03-02 20:53:00 +08:00
|
|
|
Address(std::string host, uint16_t port, Login login, std::string vhost, bool secure = false) :
|
|
|
|
|
_secure(secure),
|
2017-05-02 03:59:45 +08:00
|
|
|
_login(std::move(login)),
|
|
|
|
|
_hostname(std::move(host)),
|
2017-05-11 19:59:07 +08:00
|
|
|
_port(port),
|
2017-05-02 03:59:45 +08:00
|
|
|
_vhost(std::move(vhost)) {}
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
/**
|
|
|
|
|
* Destructor
|
|
|
|
|
*/
|
2017-05-02 03:59:45 +08:00
|
|
|
virtual ~Address() = default;
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2018-03-02 19:14:43 +08:00
|
|
|
/**
|
2018-03-02 20:53:00 +08:00
|
|
|
* Should we open a secure connection?
|
|
|
|
|
* @return bool
|
2018-03-02 19:14:43 +08:00
|
|
|
*/
|
2018-03-02 20:53:00 +08:00
|
|
|
bool secure() const
|
2018-03-02 19:14:43 +08:00
|
|
|
{
|
2018-03-02 20:53:00 +08:00
|
|
|
return _secure;
|
2018-03-02 19:14:43 +08:00
|
|
|
}
|
|
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
/**
|
|
|
|
|
* Expose the login data
|
|
|
|
|
* @return Login
|
|
|
|
|
*/
|
|
|
|
|
const Login &login() const
|
|
|
|
|
{
|
|
|
|
|
return _login;
|
|
|
|
|
}
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
/**
|
|
|
|
|
* Host name
|
|
|
|
|
* @return std::string
|
|
|
|
|
*/
|
|
|
|
|
const std::string &hostname() const
|
|
|
|
|
{
|
|
|
|
|
return _hostname;
|
|
|
|
|
}
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
/**
|
|
|
|
|
* Port number
|
|
|
|
|
* @return uint16_t
|
|
|
|
|
*/
|
|
|
|
|
uint16_t port() const
|
|
|
|
|
{
|
|
|
|
|
return _port;
|
|
|
|
|
}
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
/**
|
|
|
|
|
* The vhost to connect to
|
|
|
|
|
* @return std::string
|
|
|
|
|
*/
|
|
|
|
|
const std::string &vhost() const
|
|
|
|
|
{
|
|
|
|
|
return _vhost;
|
|
|
|
|
}
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2020-09-28 21:48:45 +08:00
|
|
|
/**
|
|
|
|
|
* Get access to the options
|
|
|
|
|
* @return std::map<std::string,std::string>
|
|
|
|
|
*/
|
2020-10-05 21:43:36 +08:00
|
|
|
const decltype(_options) &options() const
|
2020-09-28 21:48:45 +08:00
|
|
|
{
|
|
|
|
|
return _options;
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
/**
|
|
|
|
|
* Cast to a string
|
2018-03-02 19:14:43 +08:00
|
|
|
* @return std::string
|
2015-11-01 01:22:41 +08:00
|
|
|
*/
|
|
|
|
|
operator std::string () const
|
|
|
|
|
{
|
|
|
|
|
// result object
|
2018-03-02 20:53:00 +08:00
|
|
|
std::string str(_secure ? "amqps://" : "amqp://");
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
// append login
|
2015-11-03 00:53:43 +08:00
|
|
|
str.append(_login.user()).append(":").append(_login.password()).append("@").append(_hostname);
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
// do we need a special portnumber?
|
|
|
|
|
if (_port != 5672) str.append(":").append(std::to_string(_port));
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
// append default vhost
|
|
|
|
|
str.append("/");
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
// do we have a special vhost?
|
2020-09-29 18:04:09 +08:00
|
|
|
if (_vhost != "/") str.append(_vhost);
|
2020-09-28 21:48:45 +08:00
|
|
|
|
|
|
|
|
// iterate over all options, appending them
|
|
|
|
|
if (!_options.empty())
|
|
|
|
|
{
|
|
|
|
|
// first append a question mark
|
|
|
|
|
str.push_back('?');
|
|
|
|
|
|
|
|
|
|
// iterate over all the options
|
|
|
|
|
for (const auto &kv : _options) str.append(kv.first).append("=").append(kv.second).append("&");
|
|
|
|
|
|
|
|
|
|
// remove the extra &
|
|
|
|
|
str.erase(str.size() - 1);
|
|
|
|
|
}
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2015-11-01 01:22:41 +08:00
|
|
|
// done
|
|
|
|
|
return str;
|
|
|
|
|
}
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2017-04-26 15:00:01 +08:00
|
|
|
/**
|
|
|
|
|
* Comparison operator
|
|
|
|
|
* @param that
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
bool operator==(const Address &that) const
|
|
|
|
|
{
|
2018-03-02 20:53:00 +08:00
|
|
|
// security setting should match
|
|
|
|
|
if (_secure != that._secure) return false;
|
2018-03-02 19:14:43 +08:00
|
|
|
|
2017-04-26 15:00:01 +08:00
|
|
|
// logins must match
|
|
|
|
|
if (_login != that._login) return false;
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2017-12-30 02:27:40 +08:00
|
|
|
// hostname must match, but are not case sensitive
|
2017-04-26 15:00:01 +08:00
|
|
|
if (strcasecmp(_hostname.data(), that._hostname.data()) != 0) return false;
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2017-04-26 15:00:01 +08:00
|
|
|
// portnumber must match
|
|
|
|
|
if (_port != that._port) return false;
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2023-01-10 17:45:43 +08:00
|
|
|
// and the vhosts, they must match too
|
|
|
|
|
if (_vhost != that._vhost) return false;
|
2020-09-28 21:48:45 +08:00
|
|
|
|
|
|
|
|
// and the options as well
|
|
|
|
|
return _options == that._options;
|
2017-04-26 15:00:01 +08:00
|
|
|
}
|
2017-05-11 19:59:07 +08:00
|
|
|
|
2017-04-26 15:00:01 +08:00
|
|
|
/**
|
|
|
|
|
* Comparison operator
|
|
|
|
|
* @param that
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
bool operator!=(const Address &that) const
|
|
|
|
|
{
|
|
|
|
|
// the opposite of operator==
|
|
|
|
|
return !operator==(that);
|
|
|
|
|
}
|
2017-06-21 15:44:52 +08:00
|
|
|
|
2017-12-30 02:27:40 +08:00
|
|
|
/**
|
|
|
|
|
* Comparison operator that is useful if addresses have to be ordered
|
|
|
|
|
* @param that
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
bool operator<(const Address &that) const
|
|
|
|
|
{
|
2018-03-02 20:53:00 +08:00
|
|
|
// compare auth methods (amqp comes before amqps)
|
|
|
|
|
if (_secure != that._secure) return !_secure;
|
2018-03-02 19:14:43 +08:00
|
|
|
|
2017-12-30 02:27:40 +08:00
|
|
|
// compare logins
|
|
|
|
|
if (_login != that._login) return _login < that._login;
|
|
|
|
|
|
|
|
|
|
// hostname must match, but are not case sensitive
|
|
|
|
|
int result = strcasecmp(_hostname.data(), that._hostname.data());
|
|
|
|
|
|
|
|
|
|
// if hostnames are not equal, we know the result
|
|
|
|
|
if (result != 0) return result < 0;
|
|
|
|
|
|
|
|
|
|
// portnumber must match
|
|
|
|
|
if (_port != that._port) return _port < that._port;
|
|
|
|
|
|
|
|
|
|
// and finally compare the vhosts
|
2020-09-28 21:48:45 +08:00
|
|
|
if (_vhost < that._vhost) return _vhost < that._vhost;
|
|
|
|
|
|
|
|
|
|
// and finally lexicographically compare the options
|
|
|
|
|
return _options < that._options;
|
2017-12-30 02:27:40 +08:00
|
|
|
}
|
|
|
|
|
|
2017-06-21 15:44:52 +08:00
|
|
|
/**
|
|
|
|
|
* Friend function to allow writing the address to a stream
|
|
|
|
|
* @param stream
|
|
|
|
|
* @param address
|
|
|
|
|
* @return std::ostream
|
|
|
|
|
*/
|
|
|
|
|
friend std::ostream &operator<<(std::ostream &stream, const Address &address)
|
|
|
|
|
{
|
|
|
|
|
// start with the protocol and login
|
2018-03-02 20:53:00 +08:00
|
|
|
stream << (address._secure ? "amqps://" : "amqp://");
|
2018-03-10 17:20:52 +08:00
|
|
|
|
|
|
|
|
// do we have a login?
|
|
|
|
|
if (address._login) stream << address._login << "@";
|
|
|
|
|
|
|
|
|
|
// write hostname
|
|
|
|
|
stream << address._hostname;
|
2017-06-21 15:44:52 +08:00
|
|
|
|
|
|
|
|
// do we need a special portnumber?
|
2018-03-10 17:31:40 +08:00
|
|
|
if (address._port != address.defaultport()) stream << ":" << address._port;
|
2017-06-21 15:44:52 +08:00
|
|
|
|
|
|
|
|
// append default vhost
|
|
|
|
|
stream << "/";
|
|
|
|
|
|
2020-09-28 21:48:45 +08:00
|
|
|
// do we have a special vhost or options?
|
2020-09-29 18:04:09 +08:00
|
|
|
if (address._vhost != "/") stream << address._vhost;
|
2020-09-28 21:48:45 +08:00
|
|
|
|
|
|
|
|
// iterate over all options, appending them
|
|
|
|
|
if (!address._options.empty())
|
|
|
|
|
{
|
|
|
|
|
// first append a question mark
|
|
|
|
|
stream << '?';
|
|
|
|
|
|
|
|
|
|
// is this the first option?
|
|
|
|
|
bool first = true;
|
|
|
|
|
|
|
|
|
|
// iterate over all the options
|
|
|
|
|
for (const auto &kv : address._options)
|
|
|
|
|
{
|
|
|
|
|
// write the pair to the stream
|
|
|
|
|
stream << (first ? "" : "&") << kv.first << "=" << kv.second;
|
|
|
|
|
|
|
|
|
|
// no longer on first option
|
|
|
|
|
first = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-06-21 15:44:52 +08:00
|
|
|
|
|
|
|
|
// done
|
|
|
|
|
return stream;
|
|
|
|
|
}
|
2020-09-28 21:48:45 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get an integer option
|
|
|
|
|
* @param name
|
|
|
|
|
* @param fallback
|
|
|
|
|
* @return T
|
|
|
|
|
*/
|
|
|
|
|
template <typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
|
|
|
|
|
T option(const char *name, T fallback) const
|
2020-09-29 18:04:09 +08:00
|
|
|
{
|
|
|
|
|
// find the const char* version of the option
|
|
|
|
|
const char *value = option(name);
|
|
|
|
|
|
|
|
|
|
// if there is a value, convert it to integral, otherwise return the fallback
|
|
|
|
|
return value ? static_cast<T>(atoll(value)) : fallback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a const char * option, returns nullptr if it does not exist.
|
|
|
|
|
* @return const char *
|
|
|
|
|
*/
|
|
|
|
|
const char *option(const char *name) const
|
2020-09-28 21:48:45 +08:00
|
|
|
{
|
|
|
|
|
// find the option
|
|
|
|
|
auto iter = _options.find(name);
|
|
|
|
|
|
|
|
|
|
// if not found, we return the default
|
2020-09-29 18:04:09 +08:00
|
|
|
if (iter == _options.end()) return nullptr;
|
2020-09-28 21:48:45 +08:00
|
|
|
|
|
|
|
|
// return the value in the map
|
2020-09-29 18:04:09 +08:00
|
|
|
return iter->second.c_str();
|
2020-09-28 21:48:45 +08:00
|
|
|
}
|
2015-11-01 01:22:41 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* End of namespace
|
|
|
|
|
*/
|
|
|
|
|
}
|
|
|
|
|
|