Unverified Commit 7884197d authored by Dennis Jenkins's avatar Dennis Jenkins Committed by GitHub

Merge pull request #3 from oktal/master

merge from head
parents f72ef2ad a32b5db1
......@@ -53,6 +53,10 @@ public:
return listener.isBound();
}
Port getPort() const {
return listener.getPort();
}
Async::Promise<Tcp::Listener::Load> requestLoad(const Tcp::Listener::Load& old);
static Options options();
......
......@@ -815,15 +815,8 @@ namespace helpers
{
inline Address httpAddr(const StringView& view) {
auto const str = view.toString();
auto const pos = str.find(':');
if (pos == std::string::npos) {
return Address(std::move(str), HTTP_STANDARD_PORT);
return Address(str);
}
auto const host = str.substr(0, pos);
auto const port = std::stoi(str.substr(pos + 1));
return Address(std::move(host), port);
}
} // namespace helpers
} // namespace Http
} // namespace Pistache
......@@ -236,8 +236,8 @@ public:
: val_(val)
{ }
void parse(const std::string& data);
void write(std::ostream& os) const;
void parse(const std::string& data) override;
void write(std::ostream& os) const override;
void setUri(std::string val) {
val_ = std::move(val);
......@@ -264,8 +264,8 @@ public:
: val_(val)
{ }
void parse(const std::string& data);
void write(std::ostream& os) const;
void parse(const std::string& data) override;
void write(std::ostream& os) const override;
void setUri(std::string val) {
val_ = std::move(val);
......@@ -290,7 +290,7 @@ public:
{ }
explicit CacheControl(Http::CacheDirective directive);
void parseRaw(const char* str, size_t len);
void parseRaw(const char* str, size_t len) override;
void write(std::ostream& os) const override;
std::vector<Http::CacheDirective> directives() const { return directives_; }
......@@ -314,7 +314,7 @@ public:
: control_(control)
{ }
void parseRaw(const char* str, size_t len);
void parseRaw(const char* str, size_t len) override;
void write(std::ostream& os) const override;
ConnectionControl control() const { return control_; }
......@@ -330,7 +330,7 @@ public:
: encoding_()
{ }
void parseRaw(const char* str, size_t len);
void parseRaw(const char* str, size_t len) override;
void write(std::ostream& os) const override;
Encoding encoding() const {
......@@ -405,7 +405,7 @@ public:
: mime_(mime)
{ }
void parseRaw(const char* str, size_t len);
void parseRaw(const char* str, size_t len) override;
void write(std::ostream& os) const override;
Mime::MediaType mime() const { return mime_; }
......@@ -449,7 +449,7 @@ public:
: expectation_(expectation)
{ }
void parseRaw(const char* str, size_t len);
void parseRaw(const char* str, size_t len) override;
void write(std::ostream& os) const override;
Http::Expectation expectation() const { return expectation_; }
......
......@@ -53,6 +53,7 @@ public:
void bind(const Address& address);
bool isBound() const;
Port getPort() const;
void run();
void runThreaded();
......
......@@ -53,6 +53,7 @@ public:
static Ipv4 any();
std::string toString() const;
void toNetwork(in_addr_t*) const;
private:
uint8_t a;
......@@ -61,6 +62,25 @@ private:
uint8_t d;
};
class Ipv6 {
public:
Ipv6(uint16_t a, uint16_t b, uint16_t c, uint16_t d, uint16_t e, uint16_t f, uint16_t g, uint16_t h);
static Ipv6 any();
std::string toString() const;
void toNetwork(in6_addr*) const;
private:
uint16_t a;
uint16_t b;
uint16_t c;
uint16_t d;
uint16_t e;
uint16_t f;
uint16_t g;
uint16_t h;
};
class Address {
public:
Address();
......@@ -68,6 +88,7 @@ public:
Address(std::string addr);
Address(const char* addr);
Address(Ipv4 ip, Port port);
Address(Ipv6 ip, Port port);
Address(const Address& other) = default;
Address(Address&& other) = default;
......@@ -79,11 +100,13 @@ public:
std::string host() const;
Port port() const;
int family() const;
private:
void init(const std::string& addr);
std::string host_;
Port port_;
int family_ = AF_INET;
};
class Error : public std::runtime_error {
......
......@@ -6,6 +6,7 @@
#include <stdexcept>
#include <iterator>
#include <limits>
#include <cstring>
#include <iostream>
......@@ -13,6 +14,7 @@
#include <pistache/common.h>
#include <pistache/http.h>
#include <pistache/stream.h>
#include <arpa/inet.h>
using namespace std;
......@@ -340,15 +342,49 @@ Host::Host(const std::string& data)
void
Host::parse(const std::string& data) {
auto pos = data.find(':');
unsigned long pos = data.find(']');
unsigned long s_pos = data.find('[');
if (pos != std::string::npos && s_pos != std::string::npos) {
//IPv6 address
host_ = data.substr(s_pos, pos+1);
try {
in6_addr addr6;
char buff6[INET6_ADDRSTRLEN+1];
memcpy(buff6, host_.c_str(), INET6_ADDRSTRLEN);
inet_pton(AF_INET6, buff6, &(addr6.s6_addr16));
} catch (std::runtime_error) {
throw std::invalid_argument("Invalid IPv6 address");
}
pos++;
} else {
//IPv4 address
pos = data.find(':');
if (pos == std::string::npos) {
host_ = data;
port_ = HTTP_STANDARD_PORT;
}
host_ = data.substr(0, pos);
if (host_ == "*") {
host_ = "0.0.0.0";
}
try {
in_addr addr;
char buff[INET_ADDRSTRLEN+1];
memcpy(buff, host_.c_str(), INET_ADDRSTRLEN);
inet_pton(AF_INET, buff, &(addr));
} catch (std::runtime_error) {
throw std::invalid_argument("Invalid IPv4 address");
}
}
char *end;
const std::string portPart = data.substr(pos + 1);
long port;
if (pos != std::string::npos) {
std::string h = data.substr(0, pos);
int16_t p = std::stoi(data.substr(pos + 1));
host_ = h;
port_ = p;
port = strtol(portPart.c_str(), &end, 10);
if (port < std::numeric_limits<uint16_t>::min()|| port > std::numeric_limits<uint16_t>::max())
throw std::invalid_argument("Invalid port");
port_ = static_cast<uint16_t>(port);
} else {
host_ = data;
port_ = HTTP_STANDARD_PORT;
}
}
......
......@@ -52,12 +52,71 @@ Ipv4::any() {
std::string
Ipv4::toString() const {
static constexpr size_t MaxSize = sizeof("255") * 4 + 3 + 1; /* 4 * 255 + 3 * dot + \0 */
// Use the built-in ipv4 string length from arpa/inet.h
char buff[INET_ADDRSTRLEN+1];
in_addr_t addr;
toNetwork(&addr);
// Convert the network format address into display format
inet_ntop(AF_INET, &addr, buff, INET_ADDRSTRLEN);
return std::string(buff);
}
char buff[MaxSize];
snprintf(buff, MaxSize, "%d.%d.%d.%d", a, b, c, d);
void Ipv4::toNetwork(in_addr_t *addr) const {
// Bitshift the bytes into an in_addr_t (a single 32bit unsigned int);
*addr = htonl( (uint32_t)(a<<24) | (uint32_t)(b<<16) | (uint32_t)(c<<8) | (uint32_t)d );;
}
return std::string(buff);
Ipv6::Ipv6(uint16_t a, uint16_t b, uint16_t c, uint16_t d, uint16_t e, uint16_t f, uint16_t g, uint16_t h)
: a(a)
, b(b)
, c(c)
, d(d)
, e(e)
, f(f)
, g(g)
, h(h)
{ }
Ipv6
Ipv6::any() {
return Ipv6(0, 0, 0, 0, 0, 0, 0, 0);
}
std::string
Ipv6::toString() const {
// Use the built-in ipv6 string length from arpa/inet.h
char buff6[INET6_ADDRSTRLEN+1];
in6_addr addr;
toNetwork(&addr);
inet_ntop(AF_INET6, &addr, buff6, INET6_ADDRSTRLEN);
return std::string("[") + std::string(buff6) + std::string("]");
}
void Ipv6::toNetwork(in6_addr *addr6) const {
uint16_t temp_ip6[8] = {a, b, c, d, e, f, g, h};
uint16_t remap_ip6[8] = {0};
uint16_t x, y;
// If native endianness is little-endian swap the bytes, otherwise just copy them into the new array
if ( htonl(1) != 1 ) {
for (uint16_t i = 0; i<8; i++) {
x = temp_ip6[i];
y = htons(x);
remap_ip6[i] = y;
}
} else {
memcpy(remap_ip6, temp_ip6, 16);
}
// Copy the bytes into the in6_addr struct
memcpy(addr6->s6_addr16, remap_ip6, 16);
}
Address::Address()
......@@ -66,9 +125,12 @@ Address::Address()
{ }
Address::Address(std::string host, Port port)
: host_(std::move(host))
, port_(port)
{ }
{
std::string addr = host;
addr.append(":");
addr.append(port.toString());
init(std::move(addr));
}
Address::Address(std::string addr)
......@@ -84,18 +146,33 @@ Address::Address(const char* addr)
Address::Address(Ipv4 ip, Port port)
: host_(ip.toString())
, port_(port)
, family_(AF_INET)
{ }
Address::Address(Ipv6 ip, Port port)
: host_(ip.toString())
, port_(port)
, family_(AF_INET6)
{ }
Address
Address::fromUnix(struct sockaddr* addr) {
struct sockaddr_in *in_addr = reinterpret_cast<struct sockaddr_in *>(addr);
char *host = inet_ntoa(in_addr->sin_addr);
assert(addr);
int port = ntohs(in_addr->sin_port);
return Address(host, port);
if (addr->sa_family == AF_INET) {
struct sockaddr_in *in_addr = reinterpret_cast<struct sockaddr_in *>(addr);
char host[INET_ADDRSTRLEN+1];
inet_ntop(AF_INET, &(in_addr->sin_addr), host, INET_ADDRSTRLEN);
int port = ntohs(in_addr->sin_port);
assert(addr);
return Address(host, port);
} else if (addr->sa_family == AF_INET6) {
struct sockaddr_in6 *in_addr = reinterpret_cast<struct sockaddr_in6 *>(addr);
char host[INET6_ADDRSTRLEN+1];
inet_ntop(AF_INET6, &(in_addr->sin6_addr), host, INET6_ADDRSTRLEN);
int port = ntohs(in_addr->sin6_port);
assert(addr);
return Address(host, port);
}
throw Error("Not an IP socket");
}
std::string
......@@ -108,18 +185,45 @@ Address::port() const {
return port_;
}
int
Address::family() const {
return family_;
}
void
Address::init(const std::string& addr) {
const auto pos = addr.find(':');
if (pos == std::string::npos)
throw std::invalid_argument("Invalid address");
host_ = addr.substr(0, pos);
if (host_ == "*") {
host_ = "0.0.0.0";
unsigned long pos = addr.find(']');
unsigned long s_pos = addr.find('[');
if (pos != std::string::npos && s_pos != std::string::npos) {
//IPv6 address
host_ = addr.substr(s_pos, pos+1);
try {
in6_addr addr6;
char buff6[INET6_ADDRSTRLEN+1];
memcpy(buff6, host_.c_str(), INET6_ADDRSTRLEN);
inet_pton(AF_INET6, buff6, &(addr6.s6_addr16));
} catch (std::runtime_error) {
throw std::invalid_argument("Invalid IPv6 address");
}
pos++;
} else {
//IPv4 address
pos = addr.find(':');
if (pos == std::string::npos)
throw std::invalid_argument("Invalid address");
host_ = addr.substr(0, pos);
if (host_ == "*") {
host_ = "0.0.0.0";
}
try {
in_addr addr;
char buff[INET_ADDRSTRLEN+1];
memcpy(buff, host_.c_str(), INET6_ADDRSTRLEN);
inet_pton(AF_INET, buff, &(addr));
} catch (std::runtime_error) {
throw std::invalid_argument("Invalid IPv4 address");
}
}
char *end;
const std::string portPart = addr.substr(pos + 1);
if (portPart.empty())
......
......@@ -157,7 +157,7 @@ Listener::bind(const Address& address) {
addr_ = address;
struct addrinfo hints;
hints.ai_family = AF_INET;
hints.ai_family = address.family();
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
hints.ai_protocol = 0;
......@@ -184,7 +184,7 @@ Listener::bind(const Address& address) {
TRY(::listen(fd, backlog_));
break;
}
// At this point, it is still possible that we couldn't bind any socket. If it is the case, the previous
// loop would have exited naturally and addr will be null.
if (addr == nullptr) {
......@@ -207,6 +207,31 @@ Listener::isBound() const {
return listen_fd != -1;
}
// Return actual TCP port Listener is on, or 0 on error / no port.
// Notes:
// 1) Default constructor for 'Port()' sets value to 0.
// 2) Socket is created inside 'Listener::run()', which is called from
// 'Endpoint::serve()' and 'Endpoint::serveThreaded()'. So getting the
// port is only useful if you attempt to do so from a _different_ thread
// than the one running 'Listener::run()'. So for a traditional single-
// threaded program this method is of little value.
Port
Listener::getPort() const {
if (listen_fd == -1) {
return Port();
}
struct sockaddr_in sock_addr = {0};
socklen_t addrlen = sizeof(sock_addr);
auto sock_addr_alias = reinterpret_cast<struct sockaddr*>(&sock_addr);
if (-1 == getsockname(listen_fd, sock_addr_alias, &addrlen)) {
return Port();
}
return Port(ntohs(sock_addr.sin_port));
}
void
Listener::run() {
reactor_.run();
......
......@@ -3,7 +3,7 @@ function(pistache_test test_name)
set(TEST_SOURCE ${test_name}.cc)
add_executable(${TEST_EXECUTABLE} ${TEST_SOURCE})
target_link_libraries(${TEST_EXECUTABLE} gtest gtest_main pistache curl)
target_link_libraries(${TEST_EXECUTABLE} gtest gtest_main pistache curl pthread)
add_test(${test_name} ${TEST_EXECUTABLE})
endfunction()
......
......@@ -257,6 +257,22 @@ TEST(headers_test, host) {
host.parse("localhost:8080");
ASSERT_EQ(host.host(), "localhost");
ASSERT_EQ(host.port(), 8080);
/* Due to an error in GLIBC these tests don't fail as expected, further research needed */
// ASSERT_THROW( host.parse("256.256.256.256:8080");, std::invalid_argument);
// ASSERT_THROW( host.parse("1.0.0.256:8080");, std::invalid_argument);
host.parse("[::1]:8080");
ASSERT_EQ(host.host(), "[::1]");
ASSERT_EQ(host.port(), 8080);
host.parse("[2001:0DB8:AABB:CCDD:EEFF:0011:2233:4455]:8080");
ASSERT_EQ(host.host(), "[2001:0DB8:AABB:CCDD:EEFF:0011:2233:4455]");
ASSERT_EQ(host.port(), 8080);
/* Due to an error in GLIBC these tests don't fail as expected, further research needed */
// ASSERT_THROW( host.parse("[GGGG:GGGG:GGGG:GGGG:GGGG:GGGG:GGGG:GGGG]:8080");, std::invalid_argument);
// ASSERT_THROW( host.parse("[::GGGG]:8080");, std::invalid_argument);
}
TEST(headers_test, user_agent) {
......
......@@ -179,3 +179,16 @@ TEST(listener_test, listener_bind_port_not_free_throw_runtime) {
FAIL() << "Expected std::runtime_error";
}
}
// Listener should be able to bind port 0 directly to get an ephemeral port.
TEST(listener_test, listener_bind_ephemeral_port) {
Pistache::Port port(0);
Pistache::Address address(Pistache::Ipv4::any(), port);
Pistache::Tcp::Listener listener;
listener.setHandler(Pistache::Http::make_handler<DummyHandler>());
listener.bind(address);
Pistache::Port bound_port = listener.getPort();
ASSERT_TRUE(bound_port > (uint16_t)0);
}
......@@ -46,8 +46,33 @@ TEST(net_test, address_creation)
ASSERT_EQ(address4.port(), 8080);
Address address5("*:8080");
ASSERT_EQ(address4.host(), "0.0.0.0");
ASSERT_EQ(address4.port(), 8080);
ASSERT_EQ(address5.host(), "0.0.0.0");
ASSERT_EQ(address5.port(), 8080);
Address address6("[::1]:8080");
ASSERT_EQ(address6.host(), "[::1]");
ASSERT_EQ(address6.port(), 8080);
std::string addr2 = "[::1]";
Address address7(addr2, Port(8080));
ASSERT_EQ(address7.host(), "[::1]");
ASSERT_EQ(address7.port(), 8080);
Address address8(Ipv6(0, 0, 0, 0, 0, 0, 0, 1), Port(8080));
ASSERT_EQ(address8.host(), "[::1]");
ASSERT_EQ(address8.port(), 8080);
Address address9(Ipv6::any(), Port(8080));
ASSERT_EQ(address9.host(), "[::]");
ASSERT_EQ(address9.port(), 8080);
Address address10("[::]:8080");
ASSERT_EQ(address10.host(), "[::]");
ASSERT_EQ(address10.port(), 8080);
Address address11("[2001:0DB8:AABB:CCDD:EEFF:0011:2233:4455]:8080");
ASSERT_EQ(address11.host(), "[2001:0DB8:AABB:CCDD:EEFF:0011:2233:4455]");
ASSERT_EQ(address11.port(), 8080);
}
TEST(net_test, invalid_address)
......@@ -56,4 +81,10 @@ TEST(net_test, invalid_address)
ASSERT_THROW(Address("127.0.0.1:9999999"), std::invalid_argument);
ASSERT_THROW(Address("127.0.0.1:"), std::invalid_argument);
ASSERT_THROW(Address("127.0.0.1:-10"), std::invalid_argument);
}
\ No newline at end of file
/* Due to an error in GLIBC these tests don't fail as expected, further research needed */
// ASSERT_THROW(Address("[GGGG:GGGG:GGGG:GGGG:GGGG:GGGG:GGGG:GGGG]:8080");, std::invalid_argument);
// ASSERT_THROW(Address("[::GGGG]:8080");, std::invalid_argument);
// ASSERT_THROW(Address("256.256.256.256:8080");, std::invalid_argument);
// ASSERT_THROW(Address("1.0.0.256:8080");, std::invalid_argument);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment