Commit eb446158 authored by liuyu's avatar liuyu

Initial commit

parents
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/usr/include/c++/7"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "gnu11",
"cppStandard": "gnu++14",
"intelliSenseMode": "linux-gcc-x64"
}
],
"version": 4
}
\ No newline at end of file
{
"files.associations": {
"cctype": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"csignal": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"array": "cpp",
"atomic": "cpp",
"*.tcc": "cpp",
"bitset": "cpp",
"chrono": "cpp",
"codecvt": "cpp",
"complex": "cpp",
"condition_variable": "cpp",
"cstdint": "cpp",
"deque": "cpp",
"forward_list": "cpp",
"list": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"map": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"ratio": "cpp",
"set": "cpp",
"string": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"fstream": "cpp",
"future": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"ostream": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"typeindex": "cpp",
"typeinfo": "cpp",
"valarray": "cpp",
"*.h++": "cpp",
"unordered_set": "cpp",
"regex": "cpp",
"variant": "cpp",
"pluginapi.h": "c",
"optional.h": "c",
"http_headers.h": "c",
"random": "cpp"
}
}
\ No newline at end of file
File added
This diff is collapsed.
This diff is collapsed.
/*
Copyright (C) 2019-2020, Kip Warner.
Released under the terms of Apache License 2.0.
*/
// Multiple include protection...
#ifndef _BASE_64_H_
#define _BASE_64_H_
// Includes...
// Build environment configuration...
#include <pistache/config.h>
// Standard C++ / POSIX system headers...
#include <cstddef>
#include <string>
#include <vector>
#if __cplusplus < 201703L
namespace std {
typedef uint8_t byte;
}
#endif
// A class for performing decoding to raw bytes from base 64 encoding...
class Base64Decoder {
// Public methods...
public:
// Constructor...
explicit Base64Decoder(const std::string &Base64EncodedString)
: m_Base64EncodedString(Base64EncodedString) {}
// Calculate length of decoded raw bytes from that would be generated if
// the base 64 encoded input buffer was decoded. This is not a static
// method because we need to examine the string...
std::vector<std::byte>::size_type CalculateDecodedSize() const;
// Decode base 64 encoding into raw bytes...
const std::vector<std::byte> &Decode();
// Get raw decoded data...
const std::vector<std::byte> &GetRawDecodedData() const noexcept {
return m_DecodedData;
}
// Protected methods...
protected:
// Convert an octet character to corresponding sextet, provided it can
// safely be represented as such. Otherwise return 0xff...
std::byte DecodeCharacter(const unsigned char Character) const;
// Protected attributes...
protected:
// Base 64 encoded string to decode...
const std::string &m_Base64EncodedString;
// Decoded raw data...
std::vector<std::byte> m_DecodedData;
};
// A class for performing base 64 encoding from raw bytes...
class Base64Encoder {
// Public methods...
public:
// Construct encoder to encode from a raw input buffer...
explicit Base64Encoder(const std::vector<std::byte> &InputBuffer)
: m_InputBuffer(InputBuffer) {}
// Calculate length of base 64 string that would need to be generated
// for raw data of a given length...
static std::string::size_type CalculateEncodedSize(
const std::vector<std::byte>::size_type DecodedSize) noexcept;
// Encode raw data input buffer to base 64...
const std::string &Encode() noexcept;
// Encode a string into base 64 format...
static std::string EncodeString(const std::string &StringInput);
// Get the encoded data...
const std::string &GetBase64EncodedString() const noexcept {
return m_Base64EncodedString;
}
// Protected methods...
protected:
// Encode single binary byte to 6-bit base 64 character...
unsigned char EncodeByte(const std::byte Byte) const;
// Protected attributes...
protected:
// Raw bytes to encode to base 64 string...
const std::vector<std::byte> &m_InputBuffer;
// Base64 encoded string...
std::string m_Base64EncodedString;
};
// Multiple include protection...
#endif
/*
Mathieu Stefani, 29 janvier 2016
The Http client
*/
#pragma once
#include <pistache/async.h>
#include <pistache/http.h>
#include <pistache/os.h>
#include <pistache/reactor.h>
#include <pistache/timer_pool.h>
#include <pistache/view.h>
#include <atomic>
#include <chrono>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
namespace Pistache {
namespace Http {
namespace Default {
constexpr int Threads = 1;
constexpr int MaxConnectionsPerHost = 8;
constexpr bool KeepAlive = true;
constexpr size_t MaxResponseSize = std::numeric_limits<uint32_t>::max();
} // namespace Default
class Transport;
struct Connection : public std::enable_shared_from_this<Connection> {
using OnDone = std::function<void()>;
explicit Connection(size_t maxResponseSize);
struct RequestData {
RequestData(Async::Resolver resolve, Async::Rejection reject,
const Http::Request &request, OnDone onDone)
: resolve(std::move(resolve)), reject(std::move(reject)),
request(request), onDone(std::move(onDone)) {}
Async::Resolver resolve;
Async::Rejection reject;
Http::Request request;
OnDone onDone;
};
enum State : uint32_t { Idle, Used };
enum ConnectionState { NotConnected, Connecting, Connected };
void connect(const Address &addr);
void close();
bool isIdle() const;
bool tryUse();
void setAsIdle();
bool isConnected() const;
bool hasTransport() const;
void associateTransport(const std::shared_ptr<Transport> &transport);
Async::Promise<Response> perform(const Http::Request &request, OnDone onDone);
Async::Promise<Response> asyncPerform(const Http::Request &request,
OnDone onDone);
void performImpl(const Http::Request &request, Async::Resolver resolve,
Async::Rejection reject, OnDone onDone);
Fd fd() const;
void handleResponsePacket(const char *buffer, size_t totalBytes);
void handleError(const char *error);
void handleTimeout();
std::string dump() const;
private:
void processRequestQueue();
struct RequestEntry {
RequestEntry(Async::Resolver resolve, Async::Rejection reject,
std::shared_ptr<TimerPool::Entry> timer, OnDone onDone)
: resolve(std::move(resolve)), reject(std::move(reject)),
timer(std::move(timer)), onDone(std::move(onDone)) {}
Async::Resolver resolve;
Async::Rejection reject;
std::shared_ptr<TimerPool::Entry> timer;
OnDone onDone;
};
Fd fd_;
struct sockaddr_in saddr;
std::unique_ptr<RequestEntry> requestEntry;
std::atomic<uint32_t> state_;
std::atomic<ConnectionState> connectionState_;
std::shared_ptr<Transport> transport_;
Queue<RequestData> requestsQueue;
TimerPool timerPool_;
ResponseParser parser;
};
class ConnectionPool {
public:
ConnectionPool() = default;
void init(size_t maxConnsPerHost, size_t maxResponseSize);
std::shared_ptr<Connection> pickConnection(const std::string &domain);
static void releaseConnection(const std::shared_ptr<Connection> &connection);
size_t usedConnections(const std::string &domain) const;
size_t idleConnections(const std::string &domain) const;
size_t availableConnections(const std::string &domain) const;
void closeIdleConnections(const std::string &domain);
void shutdown();
private:
using Connections = std::vector<std::shared_ptr<Connection>>;
using Lock = std::mutex;
using Guard = std::lock_guard<Lock>;
mutable Lock connsLock;
std::unordered_map<std::string, Connections> conns;
size_t maxConnectionsPerHost;
size_t maxResponseSize;
};
class Client;
class RequestBuilder {
public:
friend class Client;
RequestBuilder &method(Method method);
RequestBuilder &resource(const std::string &val);
RequestBuilder &params(const Uri::Query &query);
RequestBuilder &header(const std::shared_ptr<Header::Header> &header);
template <typename H, typename... Args>
typename std::enable_if<Header::IsHeader<H>::value, RequestBuilder &>::type
header(Args &&... args) {
return header(std::make_shared<H>(std::forward<Args>(args)...));
}
RequestBuilder &cookie(const Cookie &cookie);
RequestBuilder &body(const std::string &val);
RequestBuilder &body(std::string &&val);
RequestBuilder &timeout(std::chrono::milliseconds val);
Async::Promise<Response> send();
private:
explicit RequestBuilder(Client *const client) : client_(client), request_() {}
Client *const client_;
Request request_;
};
class Client {
public:
friend class RequestBuilder;
struct Options {
friend class Client;
Options()
: threads_(Default::Threads),
maxConnectionsPerHost_(Default::MaxConnectionsPerHost),
keepAlive_(Default::KeepAlive),
maxResponseSize_(Default::MaxResponseSize) {}
Options &threads(int val);
Options &keepAlive(bool val);
Options &maxConnectionsPerHost(int val);
Options &maxResponseSize(size_t val);
private:
int threads_;
int maxConnectionsPerHost_;
bool keepAlive_;
size_t maxResponseSize_;
};
Client();
~Client();
static Options options();
void init(const Options &options = Options());
RequestBuilder get(const std::string &resource);
RequestBuilder post(const std::string &resource);
RequestBuilder put(const std::string &resource);
RequestBuilder patch(const std::string &resource);
RequestBuilder del(const std::string &resource);
void shutdown();
private:
std::shared_ptr<Aio::Reactor> reactor_;
ConnectionPool pool;
Aio::Reactor::Key transportKey;
std::atomic<uint64_t> ioIndex;
using Lock = std::mutex;
using Guard = std::lock_guard<Lock>;
Lock queuesLock;
std::unordered_map<std::string,
MPMCQueue<std::shared_ptr<Connection::RequestData>, 2048>>
requestsQueues;
bool stopProcessPequestsQueues;
private:
RequestBuilder prepareRequest(const std::string &resource,
Http::Method method);
Async::Promise<Response> doRequest(Http::Request request);
void processRequestQueue();
};
} // namespace Http
} // namespace Pistache
/* common.h
Mathieu Stefani, 12 August 2015
A collection of macro / utilities / constants
*/
#pragma once
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <cstring>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#define TRY(...) \
do { \
auto ret = __VA_ARGS__; \
if (ret < 0) { \
const char *str = #__VA_ARGS__; \
std::ostringstream oss; \
oss << str << ": "; \
if (errno == 0) { \
oss << gai_strerror(static_cast<int>(ret)); \
} else { \
oss << strerror(errno); \
} \
oss << " (" << __FILE__ << ":" << __LINE__ << ")"; \
throw std::runtime_error(oss.str()); \
} \
} while (0)
#define TRY_RET(...) \
[&]() { \
auto ret = __VA_ARGS__; \
if (ret < 0) { \
const char *str = #__VA_ARGS__; \
std::ostringstream oss; \
oss << str << ": " << strerror(errno); \
oss << " (" << __FILE__ << ":" << __LINE__ << ")"; \
throw std::runtime_error(oss.str()); \
} \
return ret; \
}(); \
(void)0
struct PrintException {
void operator()(std::exception_ptr exc) const {
try {
std::rethrow_exception(exc);
} catch (const std::exception &e) {
std::cerr << "An exception occured: " << e.what() << std::endl;
}
}
};
#define unreachable() __builtin_unreachable()
// Until we require C++17 compiler with [[maybe_unused]]
#define UNUSED(x) (void)(x);
#pragma once
#include <cstddef>
#include <cstdint>
#include <limits>
// Allow compile-time overload
namespace Pistache {
namespace Const {
static constexpr size_t MaxBacklog = 128;
static constexpr size_t MaxEvents = 1024;
static constexpr size_t MaxBuffer = 4096;
static constexpr size_t DefaultWorkers = 1;
static constexpr size_t DefaultTimerPoolSize = 128;
// Defined from CMakeLists.txt in project root
static constexpr size_t DefaultMaxRequestSize = 4096;
static constexpr size_t DefaultMaxResponseSize =
std::numeric_limits<uint32_t>::max();
static constexpr size_t ChunkSize = 1024;
static constexpr uint16_t HTTP_STANDARD_PORT = 80;
} // namespace Const
} // namespace Pistache
/*
Mathieu Stefani, 16 janvier 2016
Representation of a Cookie as per http://tools.ietf.org/html/rfc6265
*/
#pragma once
#include <ctime>
#include <list>
#include <map>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <pistache/http_defs.h>
#include <pistache/optional.h>
namespace Pistache {
namespace Http {
struct Cookie {
friend std::ostream &operator<<(std::ostream &os, const Cookie &cookie);
Cookie(std::string name, std::string value);
std::string name;
std::string value;
Optional<std::string> path;
Optional<std::string> domain;
Optional<FullDate> expires;
Optional<int> maxAge;
bool secure;
bool httpOnly;
std::map<std::string, std::string> ext;
static Cookie fromRaw(const char *str, size_t len);
static Cookie fromString(const std::string &str);
private:
void write(std::ostream &os) const;
};
std::ostream &operator<<(std::ostream &os, const Cookie &cookie);
class CookieJar {
public:
using HashMapCookies =
std::unordered_map<std::string, Cookie>; // "value" -> Cookie
using Storage = std::unordered_map<
std::string, HashMapCookies>; // "name" -> Hashmap("value" -> Cookie)
struct iterator : std::iterator<std::bidirectional_iterator_tag, Cookie> {
explicit iterator(const Storage::const_iterator &_iterator)
: iter_storage(_iterator), iter_cookie_values(), iter_storage_end() {}
iterator(const Storage::const_iterator &_iterator,
const Storage::const_iterator &end)
: iter_storage(_iterator), iter_cookie_values(), iter_storage_end(end) {
if (iter_storage != iter_storage_end) {
iter_cookie_values = iter_storage->second.begin();
}
}
Cookie operator*() const {
return iter_cookie_values->second; // return iter_storage->second;
}
const Cookie *operator->() const { return &(iter_cookie_values->second); }
iterator &operator++() {
++iter_cookie_values;
if (iter_cookie_values == iter_storage->second.end()) {
++iter_storage;
if (iter_storage != iter_storage_end)
iter_cookie_values = iter_storage->second.begin();
}
return *this;
}
iterator operator++(int) {
iterator ret(iter_storage, iter_storage_end);
++iter_cookie_values;
if (iter_cookie_values == iter_storage->second.end()) {
++iter_storage;
if (iter_storage != iter_storage_end) // this check is important
iter_cookie_values = iter_storage->second.begin();
}
return ret;
}
bool operator!=(iterator other) const {
return iter_storage != other.iter_storage;
}
bool operator==(iterator other) const {
return iter_storage == other.iter_storage;
}
private:
Storage::const_iterator iter_storage;
HashMapCookies::const_iterator iter_cookie_values;
Storage::const_iterator
iter_storage_end; // we need to know where main hashmap ends.
};
CookieJar();
void add(const Cookie &cookie);
void removeAllCookies();
void addFromRaw(const char *str, size_t len);
Cookie get(const std::string &name) const;
bool has(const std::string &name) const;
iterator begin() const { return iterator(cookies.begin(), cookies.end()); }
iterator end() const { return iterator(cookies.end()); }
private:
Storage cookies;
};
} // namespace Http
} // namespace Pistache
This diff is collapsed.
/*
Mathieu Stefani, 22 janvier 2016
An Http endpoint
*/
#pragma once
#include <pistache/http.h>
#include <pistache/listener.h>
#include <pistache/net.h>
namespace Pistache {
namespace Http {
class Endpoint {
public:
struct Options {
friend class Endpoint;
Options &threads(int val);
Options &threadsName(const std::string &val);
Options &flags(Flags<Tcp::Options> flags);
Options &flags(Tcp::Options tcp_opts) {
flags(Flags<Tcp::Options>(tcp_opts));
return *this;
}
Options &backlog(int val);
Options &maxRequestSize(size_t val);
Options &maxResponseSize(size_t val);
Options &logger(PISTACHE_STRING_LOGGER_T logger);
[[deprecated("Replaced by maxRequestSize(val)")]] Options &
maxPayload(size_t val);
private:
int threads_;
std::string threadsName_;
Flags<Tcp::Options> flags_;
int backlog_;
size_t maxRequestSize_;
size_t maxResponseSize_;
PISTACHE_STRING_LOGGER_T logger_;
Options();
};
Endpoint();
explicit Endpoint(const Address &addr);
template <typename... Args> void initArgs(Args &&... args) {
listener.init(std::forward<Args>(args)...);
}
void init(const Options &options = Options());
void setHandler(const std::shared_ptr<Handler> &handler);
void bind();
void bind(const Address &addr);
void serve();
void serveThreaded();
void shutdown();
/*!
* \brief Use SSL on this endpoint
*
* \param[in] cert Server certificate path
* \param[in] key Server key path
* \param[in] use_compression Wether or not use compression on the encryption
*
* Setup the SSL configuration for an endpoint. In order to do that, this
* function will init OpenSSL constants and load *all* algorithms. It will
* then load the server certificate and key, in order to use it later.
* *If the private key does not match the certificate, an exception will
* be thrown*
*
* \note use_compression is false by default to mitigate BREACH[1] and
* CRIME[2] vulnerabilities
* \note This function will throw an exception if pistache has not been
* compiled with PISTACHE_USE_SSL
*
* [1] https://en.wikipedia.org/wiki/BREACH
* [2] https://en.wikipedia.org/wiki/CRIME
*/
void useSSL(const std::string &cert, const std::string &key, bool use_compression = false);
/*!
* \brief Use SSL certificate authentication on this endpoint
*
* \param[in] ca_file Certificate Authority file
* \param[in] ca_path Certificate Authority path
* \param[in] cb OpenSSL verify callback[1]
*
* Change the SSL configuration in order to only accept verified client
* certificates. The function 'useSSL' *should* be called before this
* function.
* Due to the way we actually doesn't expose any OpenSSL internal types, the
* callback function is Cpp generic. The 'real' callback will be:
*
* int callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
*
* It is up to the caller to cast the second argument to an appropriate
* pointer:
*
* int store_callback(int preverify_ok, void *ctx) {
* X509_STORE_CTX *x509_ctx = (X509_STORE_CTX *)ctx;
*
* [...]
*
* if (all_good)
* return 1;
* return 0;
* }
*
* [...]
*
* endpoint->useSSLAuth(ca_file, ca_path, &store_callback);
*
* See the documentation[1] for more information about this callback.
*
* \sa useSSL
* \note This function will throw an exception if pistache has not been
* compiled with PISTACHE_USE_SSL
*
* [1] https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_verify.html
*/
void useSSLAuth(std::string ca_file, std::string ca_path = "",
int (*cb)(int, void *) = NULL);
bool isBound() const { return listener.isBound(); }
Port getPort() const { return listener.getPort(); }
Async::Promise<Tcp::Listener::Load>
requestLoad(const Tcp::Listener::Load &old);
static Options options();
private:
template <typename Method> void serveImpl(Method method) {
#define CALL_MEMBER_FN(obj, pmf) ((obj).*(pmf))
if (!handler_)
throw std::runtime_error("Must call setHandler() prior to serve()");
listener.setHandler(handler_);
listener.bind();
CALL_MEMBER_FN(listener, method)();
#undef CALL_MEMBER_FN
}
std::shared_ptr<Handler> handler_;
Tcp::Listener listener;
size_t maxRequestSize_ = Const::DefaultMaxRequestSize;
size_t maxResponseSize_ = Const::DefaultMaxResponseSize;
PISTACHE_STRING_LOGGER_T logger_ = PISTACHE_NULL_STRING_LOGGER;
};
template <typename Handler>
void listenAndServe(Address addr,
const Endpoint::Options &options = Endpoint::options()) {
Endpoint endpoint(addr);
endpoint.init(options);
endpoint.setHandler(make_handler<Handler>());
endpoint.serve();
}
} // namespace Http
} // namespace Pistache
#pragma once
#include <stdexcept>
namespace Pistache {
namespace Tcp {
class SocketError : public std::runtime_error {
public:
explicit SocketError(const char *what_arg) : std::runtime_error(what_arg) {}
};
class ServerError : public std::runtime_error {
public:
explicit ServerError(const char *what_arg) : std::runtime_error(what_arg) {}
};
} // namespace Tcp
} // namespace Pistache
\ No newline at end of file
/* flags.h
Mathieu Stefani, 18 August 2015
Make it easy to have bitwise operators for scoped or unscoped enumerations
*/
#pragma once
#include <climits>
#include <iostream>
#include <type_traits>
namespace Pistache {
// Looks like gcc 4.6 does not implement std::underlying_type
namespace detail {
template <size_t N> struct TypeStorage;
template <> struct TypeStorage<sizeof(uint8_t)> { typedef uint8_t Type; };
template <> struct TypeStorage<sizeof(uint16_t)> { typedef uint16_t Type; };
template <> struct TypeStorage<sizeof(uint32_t)> { typedef uint32_t Type; };
template <> struct TypeStorage<sizeof(uint64_t)> { typedef uint64_t Type; };
template <typename T> struct UnderlyingType {
typedef typename TypeStorage<sizeof(T)>::Type Type;
};
template <typename Enum> struct HasNone {
template <typename U>
static auto test(U *) -> decltype(U::None, std::true_type());
template <typename U> static auto test(...) -> std::false_type;
static constexpr bool value =
std::is_same<decltype(test<Enum>(0)), std::true_type>::value;
};
} // namespace detail
template <typename T> class Flags {
public:
typedef typename detail::UnderlyingType<T>::Type Type;
static_assert(std::is_enum<T>::value, "Flags only works with enumerations");
static_assert(detail::HasNone<T>::value, "The enumartion needs a None value");
static_assert(static_cast<Type>(T::None) == 0, "None should be 0");
Flags() : val(T::None) {}
explicit Flags(T _val) : val(_val) {}
#define DEFINE_BITWISE_OP_CONST(Op) \
Flags<T> operator Op(T rhs) const { \
return Flags<T>( \
static_cast<T>(static_cast<Type>(val) Op static_cast<Type>(rhs))); \
} \
\
Flags<T> operator Op(Flags<T> rhs) const { \
return Flags<T>( \
static_cast<T>(static_cast<Type>(val) Op static_cast<Type>(rhs.val))); \
}
DEFINE_BITWISE_OP_CONST(|)
DEFINE_BITWISE_OP_CONST(&)
DEFINE_BITWISE_OP_CONST(^)
#undef DEFINE_BITWISE_OP_CONST
#define DEFINE_BITWISE_OP(Op) \
Flags<T> &operator Op##=(T rhs) { \
val = static_cast<T>(static_cast<Type>(val) Op static_cast<Type>(rhs)); \
return *this; \
} \
\
Flags<T> &operator Op##=(Flags<T> rhs) { \
val = \
static_cast<T>(static_cast<Type>(val) Op static_cast<Type>(rhs.val)); \
return *this; \
}
DEFINE_BITWISE_OP(|)
DEFINE_BITWISE_OP(&)
DEFINE_BITWISE_OP(^)
#undef DEFINE_BITWISE_OP
bool hasFlag(T flag) const {
return static_cast<Type>(val) & static_cast<Type>(flag);
}
Flags<T> &setFlag(T flag) {
*this |= flag;
return *this;
}
Flags<T> &toggleFlag(T flag) { return *this ^= flag; }
operator T() const { return val; }
private:
T val;
};
} // namespace Pistache
#define DEFINE_BITWISE_OP(Op, T) \
inline T operator Op(T lhs, T rhs) { \
typedef Pistache::detail::UnderlyingType<T>::Type UnderlyingType; \
return static_cast<T>(static_cast<UnderlyingType>(lhs) \
Op static_cast<UnderlyingType>(rhs)); \
}
#define DECLARE_FLAGS_OPERATORS(T) \
DEFINE_BITWISE_OP(&, T) \
DEFINE_BITWISE_OP(|, T)
template <typename T>
std::ostream &operator<<(std::ostream &os, Pistache::Flags<T> flags) {
typedef typename Pistache::detail::UnderlyingType<T>::Type UnderlyingType;
auto val = static_cast<UnderlyingType>(static_cast<T>(flags));
for (ssize_t i = (sizeof(UnderlyingType) * CHAR_BIT) - 1; i >= 0; --i) {
os << ((val >> i) & 0x1);
}
return os;
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/* http_headers.h
Mathieu Stefani, 19 August 2015
A list of HTTP headers
*/
#pragma once
#include <algorithm>
#include <functional>
#include <memory>
#include <unordered_map>
#include <vector>
#include <pistache/http_header.h>
#include <pistache/type_checkers.h>
namespace Pistache {
namespace Http {
namespace Header {
std::string toLowercase(std::string str);
struct LowercaseHash {
size_t operator()(const std::string &key) const {
return std::hash<std::string>{}(toLowercase(key));
}
};
bool LowercaseEqualStatic(const std::string &dynamic,
const std::string &statik);
struct LowercaseEqual {
bool operator()(const std::string &left, const std::string &right) const {
return std::equal(left.begin(), left.end(), right.begin(), right.end(),
[](const char &a, const char &b) {
return std::tolower(a) == std::tolower(b);
});
};
};
class Collection {
public:
Collection() : headers(), rawHeaders() {}
template <typename H>
typename std::enable_if<IsHeader<H>::value, std::shared_ptr<const H>>::type
get() const {
return std::static_pointer_cast<const H>(get(H::Name));
}
template <typename H>
typename std::enable_if<IsHeader<H>::value, std::shared_ptr<H>>::type get() {
return std::static_pointer_cast<H>(get(H::Name));
}
template <typename H>
typename std::enable_if<IsHeader<H>::value, std::shared_ptr<const H>>::type
tryGet() const {
return std::static_pointer_cast<const H>(tryGet(H::Name));
}
template <typename H>
typename std::enable_if<IsHeader<H>::value, std::shared_ptr<H>>::type
tryGet() {
return std::static_pointer_cast<H>(tryGet(H::Name));
}
Collection &add(const std::shared_ptr<Header> &header);
Collection &addRaw(const Raw &raw);
template <typename H, typename... Args>
typename std::enable_if<IsHeader<H>::value, Collection &>::type
add(Args &&... args) {
return add(std::make_shared<H>(std::forward<Args>(args)...));
}
template <typename H>
typename std::enable_if<IsHeader<H>::value, bool>::type remove() {
return remove(H::Name);
}
std::shared_ptr<const Header> get(const std::string &name) const;
std::shared_ptr<Header> get(const std::string &name);
Raw getRaw(const std::string &name) const;
std::shared_ptr<const Header> tryGet(const std::string &name) const;
std::shared_ptr<Header> tryGet(const std::string &name);
Optional<Raw> tryGetRaw(const std::string &name) const;
template <typename H>
typename std::enable_if<IsHeader<H>::value, bool>::type has() const {
return has(H::Name);
}
bool has(const std::string &name) const;
std::vector<std::shared_ptr<Header>> list() const;
const std::unordered_map<std::string, Raw, LowercaseHash, LowercaseEqual> &
rawList() const {
return rawHeaders;
}
bool remove(const std::string &name);
void clear();
private:
std::pair<bool, std::shared_ptr<Header>>
getImpl(const std::string &name) const;
std::unordered_map<std::string, std::shared_ptr<Header>, LowercaseHash,
LowercaseEqual>
headers;
std::unordered_map<std::string, Raw, LowercaseHash, LowercaseEqual>
rawHeaders;
};
class Registry {
public:
Registry(const Registry &) = delete;
Registry &operator=(const Registry &) = delete;
static Registry &instance();
template <typename H, REQUIRES(IsHeader<H>::value)> void registerHeader() {
registerHeader(H::Name, []() -> std::unique_ptr<Header> {
return std::unique_ptr<Header>(new H());
});
}
std::vector<std::string> headersList();
std::unique_ptr<Header> makeHeader(const std::string &name);
bool isRegistered(const std::string &name);
private:
Registry();
~Registry();
using RegistryFunc = std::function<std::unique_ptr<Header>()>;
using RegistryStorageType = std::unordered_map<std::string, RegistryFunc,
LowercaseHash, LowercaseEqual>;
void registerHeader(const std::string &name, RegistryFunc func);
RegistryStorageType registry;
};
template <typename H> struct Registrar {
static_assert(IsHeader<H>::value, "Registrar only works with header types");
Registrar() { Registry::instance().registerHeader<H>(); }
};
/* Crazy macro machinery to generate a unique variable name
* Don't touch it !
*/
#define CAT(a, b) CAT_I(a, b)
#define CAT_I(a, b) a##b
#define UNIQUE_NAME(base) CAT(base, __LINE__)
#define RegisterHeader(Header) \
Registrar<Header> UNIQUE_NAME(CAT(CAT_I(__, Header), __))
} // namespace Header
} // namespace Http
} // namespace Pistache
/*
Mathieu Stefani, 28 février 2016
A collection of sample iterator adapters
*/
#pragma once
namespace Pistache {
template <typename Map> struct FlatMapIteratorAdapter {
typedef typename Map::key_type Key;
typedef typename Map::mapped_type Value;
typedef typename Map::const_iterator const_iterator;
explicit FlatMapIteratorAdapter(const_iterator _it) : it(_it) {}
FlatMapIteratorAdapter &operator++() {
++it;
return *this;
}
const Value &operator*() { return it->second; }
bool operator==(FlatMapIteratorAdapter other) { return other.it == it; }
bool operator!=(FlatMapIteratorAdapter other) { return !(*this == other); }
private:
const_iterator it;
};
template <typename Map>
FlatMapIteratorAdapter<Map>
makeFlatMapIterator(const Map &, typename Map::const_iterator it) {
return FlatMapIteratorAdapter<Map>(it);
}
} // namespace Pistache
/* listener.h
Mathieu Stefani, 12 August 2015
A TCP Listener
*/
#pragma once
#include <pistache/async.h>
#include <pistache/config.h>
#include <pistache/flags.h>
#include <pistache/log.h>
#include <pistache/net.h>
#include <pistache/os.h>
#include <pistache/reactor.h>
#include <pistache/ssl_wrappers.h>
#include <pistache/tcp.h>
#include <sys/resource.h>
#include <memory>
#include <thread>
#include <vector>
#ifdef PISTACHE_USE_SSL
#include <openssl/ssl.h>
#endif /* PISTACHE_USE_SSL */
namespace Pistache {
namespace Tcp {
class Peer;
class Transport;
void setSocketOptions(Fd fd, Flags<Options> options);
class Listener {
public:
struct Load {
using TimePoint = std::chrono::system_clock::time_point;
double global;
std::vector<double> workers;
std::vector<rusage> raw;
TimePoint tick;
};
Listener() = default;
~Listener();
explicit Listener(const Address &address);
void init(size_t workers,
Flags<Options> options = Flags<Options>(Options::None),
const std::string &workersName = "",
int backlog = Const::MaxBacklog,
PISTACHE_STRING_LOGGER_T logger = PISTACHE_NULL_STRING_LOGGER);
void setHandler(const std::shared_ptr<Handler> &handler);
void bind();
void bind(const Address &address);
bool isBound() const;
Port getPort() const;
void run();
void runThreaded();
void shutdown();
Async::Promise<Load> requestLoad(const Load &old);
Options options() const;
Address address() const;
void pinWorker(size_t worker, const CpuSet &set);
void setupSSL(const std::string &cert_path, const std::string &key_path,
bool use_compression);
void setupSSLAuth(const std::string &ca_file, const std::string &ca_path,
int (*cb)(int, void *));
private:
Address addr_;
int listen_fd = -1;
int backlog_ = Const::MaxBacklog;
NotifyFd shutdownFd;
Polling::Epoll poller;
Flags<Options> options_;
std::thread acceptThread;
size_t workers_ = Const::DefaultWorkers;
std::string workersName_;
std::shared_ptr<Handler> handler_;
Aio::Reactor reactor_;
Aio::Reactor::Key transportKey;
void handleNewConnection();
int acceptConnection(struct sockaddr_in &peer_addr) const;
void dispatchPeer(const std::shared_ptr<Peer> &peer);
bool useSSL_ = false;
ssl::SSLCtxPtr ssl_ctx_ = nullptr;
PISTACHE_STRING_LOGGER_T logger_ = PISTACHE_NULL_STRING_LOGGER;
};
} // namespace Tcp
} // namespace Pistache
/* log.h
Michael Ellison, 27 May 2020
Logging macro definitions - use these to log messages in Pistache.
*/
#pragma once
#include <pistache/string_logger.h>
#ifndef PISTACHE_LOG_STRING_FATAL
#define PISTACHE_LOG_STRING_FATAL(logger, message) do {\
if (logger && logger->isEnabledFor(::Pistache::Log::Level::FATAL)) { \
std::ostringstream oss_; \
oss_ << message; \
logger->log(::Pistache::Log::Level::FATAL, oss_.str()); \
} \
} while (0)
#endif
#ifndef PISTACHE_LOG_STRING_ERROR
#define PISTACHE_LOG_STRING_ERROR(logger, message) do {\
if (logger && logger->isEnabledFor(::Pistache::Log::Level::ERROR)) { \
std::ostringstream oss_; \
oss_ << message; \
logger->log(::Pistache::Log::Level::ERROR, oss_.str()); \
} \
} while (0)
#endif
#ifndef PISTACHE_LOG_STRING_WARN
#define PISTACHE_LOG_STRING_WARN(logger, message) do {\
if (logger && logger->isEnabledFor(::Pistache::Log::Level::WARN)) { \
std::ostringstream oss_; \
oss_ << message; \
logger->log(::Pistache::Log::Level::WARN, oss_.str()); \
} \
} while (0)
#endif
#ifndef PISTACHE_LOG_STRING_INFO
#define PISTACHE_LOG_STRING_INFO(logger, message) do {\
if (logger && logger->isEnabledFor(::Pistache::Log::Level::INFO)) { \
std::ostringstream oss_; \
oss_ << message; \
logger->log(::Pistache::Log::Level::INFO, oss_.str()); \
} \
} while (0)
#endif
#ifndef PISTACHE_LOG_STRING_DEBUG
#define PISTACHE_LOG_STRING_DEBUG(logger, message) do {\
if (logger && logger->isEnabledFor(::Pistache::Log::Level::DEBUG)) { \
std::ostringstream oss_; \
oss_ << message; \
logger->log(::Pistache::Log::Level::DEBUG, oss_.str()); \
} \
} while (0)
#endif
#ifndef PISTACHE_LOG_STRING_TRACE
#ifndef NDEBUG // Only enable trace logging in debug builds.
#define PISTACHE_LOG_STRING_TRACE(logger, message) do {\
if (logger && logger->isEnabledFor(::Pistache::Log::Level::TRACE)) { \
std::ostringstream oss_; \
oss_ << message; \
logger->log(::Pistache::Log::Level::TRACE, oss_.str()); \
} \
} while (0)
#else
#define PISTACHE_LOG_STRING_TRACE(logger, message) do {\
if (0) { \
std::ostringstream oss_; \
oss_ << message; \
logger->log(::Pistache::Log::Level::TRACE, oss_.str()); \
} \
} while (0)
#endif
#endif
#ifndef PISTACHE_STRING_LOGGER_T
#define PISTACHE_STRING_LOGGER_T \
std::shared_ptr<::Pistache::Log::StringLogger>
#endif
#ifndef PISTACHE_DEFAULT_STRING_LOGGER
#define PISTACHE_DEFAULT_STRING_LOGGER \
std::make_shared<::Pistache::Log::StringToStreamLogger>(::Pistache::Log::Level::WARN)
#endif
#ifndef PISTACHE_NULL_STRING_LOGGER
#define PISTACHE_NULL_STRING_LOGGER \
nullptr
#endif
This diff is collapsed.
configure_file(input: 'version.h.in', output: 'version.h', configuration: version_data_conf, install: get_option('PISTACHE_INSTALL'), install_dir: prefix/get_option('includedir')/'pistache')
install_headers([
'async.h',
'base64.h',
'client.h',
'common.h',
'config.h',
'cookie.h',
'description.h',
'endpoint.h',
'errors.h',
'flags.h',
'http_defs.h',
'http.h',
'http_header.h',
'http_headers.h',
'iterator_adapter.h',
'listener.h',
'log.h',
'mailbox.h',
'mime.h',
'net.h',
'optional.h',
'os.h',
'peer.h',
'prototype.h',
'reactor.h',
'route_bind.h',
'router.h',
'ssl_wrappers.h',
'stream.h',
'string_logger.h',
'string_view.h',
'tcp.h',
'timer_pool.h',
'transport.h',
'type_checkers.h',
'typeid.h',
'utils.h',
'view.h'
], subdir: 'pistache')
install_subdir('thirdparty', install_dir: prefix/get_option('includedir')/'pistache')
/* mime.h
Mathieu Stefani, 29 August 2015
Type safe representation of a MIME Type (RFC 1590)
*/
#pragma once
#include <cassert>
#include <cmath>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <pistache/optional.h>
namespace Pistache {
namespace Http {
namespace Mime {
#define MIME_TYPES \
TYPE(Star, "*") \
TYPE(Text, "text") \
TYPE(Image, "image") \
TYPE(Audio, "audio") \
TYPE(Video, "video") \
TYPE(Application, "application") \
TYPE(Message, "message") \
TYPE(Multipart, "multipart")
#define MIME_SUBTYPES \
SUB_TYPE(Star, "*") \
SUB_TYPE(Plain, "plain") \
SUB_TYPE(Html, "html") \
SUB_TYPE(Xhtml, "xhtml") \
SUB_TYPE(Xml, "xml") \
SUB_TYPE(Javascript, "javascript") \
SUB_TYPE(Css, "css") \
\
SUB_TYPE(OctetStream, "octet-stream") \
SUB_TYPE(Json, "json") \
SUB_TYPE(JsonSchema, "schema+json") \
SUB_TYPE(JsonSchemaInstance, "schema-instance+json") \
SUB_TYPE(FormUrlEncoded, "x-www-form-urlencoded") \
SUB_TYPE(FormData, "form-data") \
\
SUB_TYPE(Png, "png") \
SUB_TYPE(Gif, "gif") \
SUB_TYPE(Bmp, "bmp") \
SUB_TYPE(Jpeg, "jpeg")
#define MIME_SUFFIXES \
SUFFIX(Json, "json", "JavaScript Object Notation") \
SUFFIX(Ber, "ber", "Basic Encoding Rules") \
SUFFIX(Der, "der", "Distinguished Encoding Rules") \
SUFFIX(Fastinfoset, "fastinfoset", "Fast Infoset") \
SUFFIX(Wbxml, "wbxml", "WAP Binary XML") \
SUFFIX(Zip, "zip", "ZIP file storage") \
SUFFIX(Xml, "xml", "Extensible Markup Language")
enum class Type {
#define TYPE(val, _) val,
MIME_TYPES
#undef TYPE
None
};
enum class Subtype {
#define SUB_TYPE(val, _) val,
MIME_SUBTYPES
#undef SUB_TYPE
Vendor,
Ext,
None
};
enum class Suffix {
#define SUFFIX(val, _, __) val,
MIME_SUFFIXES
#undef SUFFIX
None,
Ext
};
// 3.9 Quality Values
class Q {
public:
// typedef uint8_t Type;
typedef uint16_t Type;
explicit Q(Type val) : val_() {
if (val > 100) {
throw std::runtime_error(
"Invalid quality value, must be in the [0; 100] range");
}
val_ = val;
}
static Q fromFloat(double f) {
return Q(static_cast<Type>(round(f * 100.0)));
}
Type value() const { return val_; }
operator Type() const { return val_; }
std::string toString() const;
private:
Type val_;
};
inline bool operator==(Q lhs, Q rhs) { return lhs.value() == rhs.value(); }
// 3.7 Media Types
class MediaType {
public:
enum Parse { DoParse, DontParse };
MediaType()
: top_(Type::None), sub_(Subtype::None), suffix_(Suffix::None), raw_(),
rawSubIndex(), rawSuffixIndex(), params(), q_() {}
explicit MediaType(std::string raw, Parse parse = DontParse)
: top_(Type::None), sub_(Subtype::None), suffix_(Suffix::None), raw_(),
rawSubIndex(), rawSuffixIndex(), params(), q_() {
if (parse == DoParse) {
parseRaw(raw.c_str(), raw.length());
} else {
raw_ = std::move(raw);
}
}
MediaType(Mime::Type top, Mime::Subtype sub)
: top_(top), sub_(sub), suffix_(Suffix::None), raw_(), rawSubIndex(),
rawSuffixIndex(), params(), q_() {}
MediaType(Mime::Type top, Mime::Subtype sub, Mime::Suffix suffix)
: top_(top), sub_(sub), suffix_(suffix), raw_(), rawSubIndex(),
rawSuffixIndex(), params(), q_() {}
void parseRaw(const char *str, size_t len);
static MediaType fromRaw(const char *str, size_t len);
static MediaType fromString(const std::string &str);
static MediaType fromString(std::string &&str);
static MediaType fromFile(const char *fileName);
Mime::Type top() const { return top_; }
Mime::Subtype sub() const { return sub_; }
Mime::Suffix suffix() const { return suffix_; }
std::string rawSub() const { return rawSubIndex.splice(raw_); }
std::string raw() const { return raw_; }
const Optional<Q> &q() const { return q_; }
void setQuality(Q quality);
Optional<std::string> getParam(const std::string &name) const;
void setParam(const std::string &name, std::string value);
std::string toString() const;
bool isValid() const;
private:
Mime::Type top_;
Mime::Subtype sub_;
Mime::Suffix suffix_;
/* Let's save some extra memory allocations by only storing the
raw MediaType along with indexes of the relevant parts
Note: experimental for now as it might not be a good idea
*/
std::string raw_;
struct Index {
size_t beg;
size_t end;
std::string splice(const std::string &str) const {
assert(end >= beg);
return str.substr(beg, end - beg + 1);
}
};
Index rawSubIndex;
Index rawSuffixIndex;
std::unordered_map<std::string, std::string> params;
Optional<Q> q_;
};
inline bool operator==(const MediaType &lhs, const MediaType &rhs) {
return lhs.top() == rhs.top() && lhs.sub() == rhs.sub() &&
lhs.suffix() == rhs.suffix();
}
inline bool operator!=(const MediaType &lhs, const MediaType &rhs) {
return !operator==(lhs, rhs);
}
} // namespace Mime
} // namespace Http
} // namespace Pistache
#define MIME(top, sub) \
Pistache::Http::Mime::MediaType(Pistache::Http::Mime::Type::top, \
Pistache::Http::Mime::Subtype::sub)
#define MIME3(top, sub, suffix) \
Pistache::Http::Mime::MediaType(Pistache::Http::Mime::Type::top, \
Pistache::Http::Mime::Subtype::sub, \
Pistache::Http::Mime::Suffix::suffix)
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/* peer.h
Mathieu Stefani, 12 August 2015
A class representing a TCP Peer
*/
#pragma once
#include <iostream>
#include <memory>
#include <string>
#include <pistache/async.h>
#include <pistache/http.h>
#include <pistache/net.h>
#include <pistache/os.h>
#include <pistache/stream.h>
#ifdef PISTACHE_USE_SSL
#include <openssl/ssl.h>
#endif /* PISTACHE_USE_SSL */
namespace Pistache {
namespace Tcp {
class Transport;
class Peer {
public:
friend class Transport;
friend class Http::Handler;
friend class Http::Timeout;
~Peer();
static std::shared_ptr<Peer> Create(Fd fd, const Address &addr);
static std::shared_ptr<Peer> CreateSSL(Fd fd, const Address &addr, void *ssl);
const Address &address() const;
const std::string &hostname();
Fd fd() const;
void *ssl() const;
Async::Promise<ssize_t> send(const RawBuffer &buffer, int flags = 0);
size_t getID() const;
protected:
Peer(Fd fd, const Address &addr, void *ssl);
private:
void setParser(std::shared_ptr<Http::RequestParser> parser);
std::shared_ptr<Http::RequestParser> getParser() const;
Http::Request &request();
void associateTransport(Transport *transport);
Transport *transport() const;
Transport *transport_ = nullptr;
Fd fd_ = -1;
Address addr;
std::string hostname_;
std::shared_ptr<Http::RequestParser> parser_;
void *ssl_ = nullptr;
const size_t id_;
};
std::ostream &operator<<(std::ostream &os, Peer &peer);
} // namespace Tcp
} // namespace Pistache
/*
Mathieu Stefani, 28 janvier 2016
Simple Prototype design pattern implement
*/
#pragma once
#include <memory>
#include <type_traits>
namespace Pistache {
/* In a sense, a Prototype is just a class that provides a clone() method */
template <typename Class> struct Prototype {
virtual ~Prototype() {}
virtual std::shared_ptr<Class> clone() const = 0;
};
} // namespace Pistache
#define PROTOTYPE_OF(Base, Class) \
private: \
std::shared_ptr<Base> clone() const override { \
return std::make_shared<Class>(*this); \
} \
\
public:
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
#pragma once
namespace Pistache {
#define REQUIRES(condition) typename std::enable_if<(condition), int>::type = 0
} // namespace Pistache
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Please put here your contribs. Popular contribs will be moved to main tree after stablization
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
json @ 7126d888
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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