diff options
34 files changed, 1540 insertions, 624 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index a625dd8..2949165 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,19 +2,20 @@ cmake_minimum_required(VERSION 3.0) project(TgBot) ### options -option(ENABLE_TESTS "Set to ON to enable building of tests and samples" ON) +option(ENABLE_TESTS "Set to ON to enable building of tests" OFF) +option(ENABLE_SAMPLES "Set to ON to enable building of samples" OFF) ### sources set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") include_directories("${PROJECT_SOURCE_DIR}/src") set(SRC_LIST - src/tgbot/Bot.cpp src/tgbot/Api.cpp - src/tgbot/EventManager.cpp - src/tgbot/Http.cpp - src/tgbot/Url.cpp - src/tgbot/Parser.cpp + src/tgbot/TgTypeParser.cpp src/tgbot/TgException.cpp + src/tgbot/net/Url.cpp + src/tgbot/net/HttpClient.cpp + src/tgbot/net/HttpParser.cpp + src/tgbot/net/TgLongPoll.cpp src/tgbot/tools/StringTools.cpp ) @@ -25,7 +26,7 @@ include_directories(${OPENSSL_INCLUDE_DIR}) # boost set(Boost_USE_MULTITHREADED ON) -find_package(Boost COMPONENTS system container iostreams REQUIRED) +find_package(Boost COMPONENTS system container iostreams unit_test_framework REQUIRED) include_directories(${Boost_INCLUDE_DIR}) set(LIB_LIST @@ -39,6 +40,13 @@ target_link_libraries(${PROJECT_NAME} ${LIB_LIST}) ### tests if (ENABLE_TESTS) - message(STATUS "Building of tests and sambles is enabled") + message(STATUS "Building of tests is enabled") + enable_testing() + add_subdirectory(test) +endif() + +### samples +if (ENABLE_SAMPLES) + message(STATUS "Building of sambles is enabled") add_subdirectory(samples) endif() diff --git a/samples/echobot/src/main.cpp b/samples/echobot/src/main.cpp index 4874aab..03d9d3d 100644 --- a/samples/echobot/src/main.cpp +++ b/samples/echobot/src/main.cpp @@ -25,8 +25,9 @@ #include <exception> #include <tgbot/Bot.h> -#include <tgbot/tools/StringTools.h> +#include <tgbot/net/TgLongPoll.h> #include <tgbot/TgException.h> +#include <tgbot/tools/StringTools.h> using namespace std; using namespace TgBot; @@ -39,28 +40,31 @@ int main() { sigintGot = true; }); - Bot bot("PLACE_YOUR_TOKEN_HERE"); - bot.getEvents().onCommand("start", [](Message::Ptr message, Bot* const bot) { - bot->getApi().sendMessage(message->chat->id, "Hi!"); + Bot bot("100743144:AAEzJJSWYezyUYlwgzSjf0-5hdokGyUjbXM"); + bot.getEvents().onCommand("start", [&bot](Message::Ptr message) { + bot.getApi().sendMessage(message->chat->id, "Hi!"); }); - bot.getEvents().onAnyMessage([](Message::Ptr message, Bot* const bot) { + bot.getEvents().onAnyMessage([&bot](Message::Ptr message) { printf("User wrote %s\n", message->text.c_str()); if (StringTools::startsWith(message->text, "/start")) { return; } - bot->getApi().sendMessage(message->chat->id, "Your message is: " + message->text); + bot.getApi().sendMessage(message->chat->id, "Your message is: " + message->text); }); + printf("try {\n"); try { printf("Bot username: %s\n", bot.getApi().getMe()->username.c_str()); + TgLongPoll longPoll(bot); while (!sigintGot) { printf("Long poll started\n"); - bot.startLongPoll(); + longPoll.start(); } } catch (TgException& e) { printf("error: %s\n", e.what()); } + printf("}\n"); return 0; } diff --git a/src/tgbot/Api.cpp b/src/tgbot/Api.cpp index 9dd7314..8da88c8 100644 --- a/src/tgbot/Api.cpp +++ b/src/tgbot/Api.cpp @@ -22,241 +22,240 @@ #include "Api.h" -#include <boost/property_tree/json_parser.hpp> - -#include "tgbot/Bot.h" +#include "tgbot/TgTypeParser.h" #include "tgbot/TgException.h" +#include "tgbot/net/HttpClient.h" using namespace std; using namespace boost::property_tree; namespace TgBot { -Api::Api(Bot* const bot) : _bot(bot) { +Api::Api(const std::string& token) : _token(token) { } User::Ptr Api::getMe() const { - return _bot->getParser().parseUser(sendRequest("getMe").find("result")->second); + return TgTypeParser::getInstance().parseUser(sendRequest("getMe").find("result")->second); } Message::Ptr Api::sendMessage(int32_t chatId, const string& text, bool disableWebPagePreview, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const { - vector<Http::Argument> args; - args.push_back(Http::Argument("chat_id", chatId)); - args.push_back(Http::Argument("text", text)); + vector<HttpReqArg> args; + args.push_back(HttpReqArg("chat_id", chatId)); + args.push_back(HttpReqArg("text", text)); if (disableWebPagePreview) { - args.push_back(Http::Argument("disable_web_page_preview", disableWebPagePreview)); + args.push_back(HttpReqArg("disable_web_page_preview", disableWebPagePreview)); } if (replyToMessageId) { - args.push_back(Http::Argument("reply_to_message_id", replyToMessageId)); + args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId)); } if (replyMarkup) { - args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup))); + args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup))); } - return _bot->getParser().parseMessage(sendRequest("sendMessage", args).find("result")->second); + return TgTypeParser::getInstance().parseMessage(sendRequest("sendMessage", args).find("result")->second); } Message::Ptr Api::forwardMessage(int32_t chatId, int32_t fromChatId, int32_t messageId) const { - vector<Http::Argument> args; - args.push_back(Http::Argument("chat_id", chatId)); - args.push_back(Http::Argument("from_chat_id", fromChatId)); - args.push_back(Http::Argument("message_id", messageId)); - return _bot->getParser().parseMessage(sendRequest("forwardMessage", args).find("result")->second); + vector<HttpReqArg> args; + args.push_back(HttpReqArg("chat_id", chatId)); + args.push_back(HttpReqArg("from_chat_id", fromChatId)); + args.push_back(HttpReqArg("message_id", messageId)); + return TgTypeParser::getInstance().parseMessage(sendRequest("forwardMessage", args).find("result")->second); } Message::Ptr Api::sendPhoto(int32_t chatId, const InputFile::Ptr& photo, const string& caption, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const { - vector<Http::Argument> args; - args.push_back(Http::Argument("chat_id", chatId)); - args.push_back(Http::Argument("photo", photo->data, true, photo->mimeType)); + vector<HttpReqArg> args; + args.push_back(HttpReqArg("chat_id", chatId)); + args.push_back(HttpReqArg("photo", photo->data, true, photo->mimeType)); if (!caption.empty()) { - args.push_back(Http::Argument("caption", caption)); + args.push_back(HttpReqArg("caption", caption)); } if (replyToMessageId) { - args.push_back(Http::Argument("reply_to_message_id", replyToMessageId)); + args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId)); } if (replyMarkup) { - args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup))); + args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup))); } - return _bot->getParser().parseMessage(sendRequest("sendPhoto", args).find("result")->second); + return TgTypeParser::getInstance().parseMessage(sendRequest("sendPhoto", args).find("result")->second); } Message::Ptr Api::sendPhoto(int32_t chatId, const string& photo, const string& caption, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const { - vector<Http::Argument> args; - args.push_back(Http::Argument("chat_id", chatId)); - args.push_back(Http::Argument("photo", photo)); + vector<HttpReqArg> args; + args.push_back(HttpReqArg("chat_id", chatId)); + args.push_back(HttpReqArg("photo", photo)); if (!caption.empty()) { - args.push_back(Http::Argument("caption", caption)); + args.push_back(HttpReqArg("caption", caption)); } if (replyToMessageId) { - args.push_back(Http::Argument("reply_to_message_id", replyToMessageId)); + args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId)); } if (replyMarkup) { - args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup))); + args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup))); } - return _bot->getParser().parseMessage(sendRequest("sendPhoto", args).find("result")->second); + return TgTypeParser::getInstance().parseMessage(sendRequest("sendPhoto", args).find("result")->second); } Message::Ptr Api::sendAudio(int32_t chatId, const InputFile::Ptr& audio, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const { - vector<Http::Argument> args; - args.push_back(Http::Argument("chat_id", chatId)); - args.push_back(Http::Argument("audio", audio->data, true, audio->mimeType)); + vector<HttpReqArg> args; + args.push_back(HttpReqArg("chat_id", chatId)); + args.push_back(HttpReqArg("audio", audio->data, true, audio->mimeType)); if (replyToMessageId) { - args.push_back(Http::Argument("reply_to_message_id", replyToMessageId)); + args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId)); } if (replyMarkup) { - args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup))); + args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup))); } - return _bot->getParser().parseMessage(sendRequest("sendAudio", args).find("result")->second); + return TgTypeParser::getInstance().parseMessage(sendRequest("sendAudio", args).find("result")->second); } Message::Ptr Api::sendAudio(int32_t chatId, const string& audio, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const { - vector<Http::Argument> args; - args.push_back(Http::Argument("chat_id", chatId)); - args.push_back(Http::Argument("audio", audio)); + vector<HttpReqArg> args; + args.push_back(HttpReqArg("chat_id", chatId)); + args.push_back(HttpReqArg("audio", audio)); if (replyToMessageId) { - args.push_back(Http::Argument("reply_to_message_id", replyToMessageId)); + args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId)); } if (replyMarkup) { - args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup))); + args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup))); } - return _bot->getParser().parseMessage(sendRequest("sendAudio", args).find("result")->second); + return TgTypeParser::getInstance().parseMessage(sendRequest("sendAudio", args).find("result")->second); } Message::Ptr Api::sendDocument(int32_t chatId, const InputFile::Ptr& document, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const { - vector<Http::Argument> args; - args.push_back(Http::Argument("chat_id", chatId)); - args.push_back(Http::Argument("document", document->data, true, document->mimeType)); + vector<HttpReqArg> args; + args.push_back(HttpReqArg("chat_id", chatId)); + args.push_back(HttpReqArg("document", document->data, true, document->mimeType)); if (replyToMessageId) { - args.push_back(Http::Argument("reply_to_message_id", replyToMessageId)); + args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId)); } if (replyMarkup) { - args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup))); + args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup))); } - return _bot->getParser().parseMessage(sendRequest("sendDocument", args).find("result")->second); + return TgTypeParser::getInstance().parseMessage(sendRequest("sendDocument", args).find("result")->second); } Message::Ptr Api::sendDocument(int32_t chatId, const string& document, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const { - vector<Http::Argument> args; - args.push_back(Http::Argument("chat_id", chatId)); - args.push_back(Http::Argument("document", document)); + vector<HttpReqArg> args; + args.push_back(HttpReqArg("chat_id", chatId)); + args.push_back(HttpReqArg("document", document)); if (replyToMessageId) { - args.push_back(Http::Argument("reply_to_message_id", replyToMessageId)); + args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId)); } if (replyMarkup) { - args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup))); + args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup))); } - return _bot->getParser().parseMessage(sendRequest("sendDocument", args).find("result")->second); + return TgTypeParser::getInstance().parseMessage(sendRequest("sendDocument", args).find("result")->second); } Message::Ptr Api::sendSticker(int32_t chatId, const InputFile::Ptr& sticker, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const { - vector<Http::Argument> args; - args.push_back(Http::Argument("chat_id", chatId)); - args.push_back(Http::Argument("sticker", sticker->data, true, sticker->mimeType)); + vector<HttpReqArg> args; + args.push_back(HttpReqArg("chat_id", chatId)); + args.push_back(HttpReqArg("sticker", sticker->data, true, sticker->mimeType)); if (replyToMessageId) { - args.push_back(Http::Argument("reply_to_message_id", replyToMessageId)); + args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId)); } if (replyMarkup) { - args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup))); + args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup))); } - return _bot->getParser().parseMessage(sendRequest("sendSticker", args).find("result")->second); + return TgTypeParser::getInstance().parseMessage(sendRequest("sendSticker", args).find("result")->second); } Message::Ptr Api::sendSticker(int32_t chatId, const string& sticker, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const { - vector<Http::Argument> args; - args.push_back(Http::Argument("chat_id", chatId)); - args.push_back(Http::Argument("sticker", sticker)); + vector<HttpReqArg> args; + args.push_back(HttpReqArg("chat_id", chatId)); + args.push_back(HttpReqArg("sticker", sticker)); if (replyToMessageId) { - args.push_back(Http::Argument("reply_to_message_id", replyToMessageId)); + args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId)); } if (replyMarkup) { - args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup))); + args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup))); } - return _bot->getParser().parseMessage(sendRequest("sendSticker", args).find("result")->second); + return TgTypeParser::getInstance().parseMessage(sendRequest("sendSticker", args).find("result")->second); } Message::Ptr Api::sendVideo(int32_t chatId, const InputFile::Ptr& video, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const { - vector<Http::Argument> args; - args.push_back(Http::Argument("chat_id", chatId)); - args.push_back(Http::Argument("video", video->data, true, video->mimeType)); + vector<HttpReqArg> args; + args.push_back(HttpReqArg("chat_id", chatId)); + args.push_back(HttpReqArg("video", video->data, true, video->mimeType)); if (replyToMessageId) { - args.push_back(Http::Argument("reply_to_message_id", replyToMessageId)); + args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId)); } if (replyMarkup) { - args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup))); + args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup))); } - return _bot->getParser().parseMessage(sendRequest("sendVideo", args).find("result")->second); + return TgTypeParser::getInstance().parseMessage(sendRequest("sendVideo", args).find("result")->second); } Message::Ptr Api::sendVideo(int32_t chatId, const string& video, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const { - vector<Http::Argument> args; - args.push_back(Http::Argument("chat_id", chatId)); - args.push_back(Http::Argument("video", video)); + vector<HttpReqArg> args; + args.push_back(HttpReqArg("chat_id", chatId)); + args.push_back(HttpReqArg("video", video)); if (replyToMessageId) { - args.push_back(Http::Argument("reply_to_message_id", replyToMessageId)); + args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId)); } if (replyMarkup) { - args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup))); + args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup))); } - return _bot->getParser().parseMessage(sendRequest("sendVideo", args).find("result")->second); + return TgTypeParser::getInstance().parseMessage(sendRequest("sendVideo", args).find("result")->second); } Message::Ptr Api::sendLocation(int32_t chatId, float latitude, float longitude, int32_t replyToMessageId, const GenericReply::Ptr& replyMarkup) const { - vector<Http::Argument> args; - args.push_back(Http::Argument("chat_id", chatId)); - args.push_back(Http::Argument("latitude", latitude)); - args.push_back(Http::Argument("longitude", longitude)); + vector<HttpReqArg> args; + args.push_back(HttpReqArg("chat_id", chatId)); + args.push_back(HttpReqArg("latitude", latitude)); + args.push_back(HttpReqArg("longitude", longitude)); if (replyToMessageId) { - args.push_back(Http::Argument("reply_to_message_id", replyToMessageId)); + args.push_back(HttpReqArg("reply_to_message_id", replyToMessageId)); } if (replyMarkup) { - args.push_back(Http::Argument("reply_markup", _bot->getParser().parseGenericReply(replyMarkup))); + args.push_back(HttpReqArg("reply_markup", TgTypeParser::getInstance().parseGenericReply(replyMarkup))); } - return _bot->getParser().parseMessage(sendRequest("sendLocation", args).find("result")->second); + return TgTypeParser::getInstance().parseMessage(sendRequest("sendLocation", args).find("result")->second); } void Api::sendChatAction(int32_t chatId, const string& action) const { - vector<Http::Argument> args; - args.push_back(Http::Argument("chat_id", chatId)); - args.push_back(Http::Argument("action", action)); + vector<HttpReqArg> args; + args.push_back(HttpReqArg("chat_id", chatId)); + args.push_back(HttpReqArg("action", action)); sendRequest("sendChatAction", args); } UserProfilePhotos::Ptr Api::getUserProfilePhotos(int32_t userId, int32_t offset, int32_t limit) const { - vector<Http::Argument> args; - args.push_back(Http::Argument("user_id", userId)); + vector<HttpReqArg> args; + args.push_back(HttpReqArg("user_id", userId)); if (offset) { - args.push_back(Http::Argument("offset", offset)); + args.push_back(HttpReqArg("offset", offset)); } limit = max(1, min(100, limit)); - args.push_back(Http::Argument("limit", limit)); - return _bot->getParser().parseUserProfilePhotos(sendRequest("getUserProfilePhotos", args).find("result")->second); + args.push_back(HttpReqArg("limit", limit)); + return TgTypeParser::getInstance().parseUserProfilePhotos(sendRequest("getUserProfilePhotos", args).find("result")->second); } vector<Update::Ptr> Api::getUpdates(int32_t offset, int32_t limit, int32_t timeout) const { - vector<Http::Argument> args; + vector<HttpReqArg> args; if (offset) { - args.push_back(Http::Argument("offset", offset)); + args.push_back(HttpReqArg("offset", offset)); } limit = max(1, min(100, limit)); - args.push_back(Http::Argument("limit", limit)); + args.push_back(HttpReqArg("limit", limit)); if (timeout) { - args.push_back(Http::Argument("timeout", timeout)); + args.push_back(HttpReqArg("timeout", timeout)); } - return _bot->getParser().parseArray<Update>(_bot->getParser().parseUpdate, sendRequest("getUpdates", args), "result"); + return TgTypeParser::getInstance().parseArray<Update>(TgTypeParser::getInstance().parseUpdate, sendRequest("getUpdates", args), "result"); } void Api::setWebhook(const string& url) const { - vector<Http::Argument> args; - args.push_back(Http::Argument("url", url)); + vector<HttpReqArg> args; + args.push_back(HttpReqArg("url", url)); sendRequest("setWebhook", args); } -boost::property_tree::ptree Api::sendRequest(const std::string& method, const std::vector<Http::Argument>& args) const { +boost::property_tree::ptree Api::sendRequest(const std::string& method, const std::vector<HttpReqArg>& args) const { std::string url = "https://api.telegram.org/bot"; - url += _bot->getToken(); + url += _token; url += "/"; url += method; try { - ptree result = _bot->getParser().parseJson(_bot->getHttp().makeRequest(url, args)); + ptree result = TgTypeParser::getInstance().parseJson(HttpClient::getInstance().makeRequest(url, args)); if (result.get<bool>("ok")) { return result; } else { diff --git a/src/tgbot/Api.h b/src/tgbot/Api.h index c10fe56..384de55 100644 --- a/src/tgbot/Api.h +++ b/src/tgbot/Api.h @@ -28,7 +28,7 @@ #include <boost/property_tree/ptree.hpp> -#include "tgbot/Http.h" +#include "tgbot/net/HttpReqArg.h" #include "tgbot/types/User.h" #include "tgbot/types/Message.h" #include "tgbot/types/GenericReply.h" @@ -45,6 +45,8 @@ class Api { friend Bot; public: + Api(const std::string& token); + User::Ptr getMe() const; Message::Ptr sendMessage(int32_t chatId, const std::string& text, bool disableWebPagePreview = false, int32_t replyToMessageId = 0, const GenericReply::Ptr& replyMarkup = GenericReply::Ptr()) const; Message::Ptr forwardMessage(int32_t chatId, int32_t fromChatId, int32_t messageId) const; @@ -65,11 +67,9 @@ public: void setWebhook(const std::string& url = "") const; private: - explicit Api(Bot* const bot); - - boost::property_tree::ptree sendRequest(const std::string& method, const std::vector<Http::Argument>& args = std::vector<Http::Argument>()) const; + boost::property_tree::ptree sendRequest(const std::string& method, const std::vector<HttpReqArg>& args = std::vector<HttpReqArg>()) const; - Bot* const _bot; + const std::string _token; }; } diff --git a/src/tgbot/Bot.h b/src/tgbot/Bot.h index 488ceb1..eeef27a 100644 --- a/src/tgbot/Bot.h +++ b/src/tgbot/Bot.h @@ -26,25 +26,15 @@ #include <string> #include "tgbot/Api.h" -#include "tgbot/EventManager.h" -#include "tgbot/Http.h" -#include "tgbot/Parser.h" +#include "tgbot/EventBroadcaster.h" +#include "tgbot/EventHandler.h" namespace TgBot { class Bot { public: - explicit Bot(const std::string& token); - - void startLongPoll(); - - inline void startServer(unsigned short port) { - _http.startServer(port, _webhooksServerHandler); - } - - inline void startServer(const std::string& unixSocketPath) { - _http.startServer(unixSocketPath, _webhooksServerHandler); + explicit Bot(const std::string& token) : _token(token), _api(token), _eventHandler(&_eventBroadcaster) { } inline const std::string& getToken() const { @@ -55,34 +45,19 @@ public: return _api; } - inline const EventManager& getEvents() const { - return _events; - } - - inline EventManager& getEvents() { - return _events; - } - - inline const Http& getHttp() const { - return _http; - } - - inline Http& getHttp() { - return _http; + inline EventBroadcaster& getEvents() { + return _eventBroadcaster; } - inline const Parser& getParser() const { - return _parser; + inline const EventHandler& getEventHandler() const { + return _eventHandler; } private: const std::string _token; const Api _api; - EventManager _events; - Http _http; - const Parser _parser; - const Http::ServerHandler _webhooksServerHandler; - int32_t _lastUpdateId = 0; + EventBroadcaster _eventBroadcaster; + const EventHandler _eventHandler; }; } diff --git a/src/tgbot/EventBroadcaster.h b/src/tgbot/EventBroadcaster.h new file mode 100644 index 0000000..a6074e8 --- /dev/null +++ b/src/tgbot/EventBroadcaster.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2015 Oleg Morozenkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef TGBOT_EVENTBROADCASTER_H +#define TGBOT_EVENTBROADCASTER_H + +#include <string> +#include <functional> +#include <vector> +#include <map> + +#include "tgbot/types/Message.h" + +namespace TgBot { + +class EventHandler; + +class EventBroadcaster { + +friend EventHandler; + +public: + typedef std::function<void (const Message::Ptr&)> MessageListener; + + inline void onAnyMessage(const MessageListener& listener) { + _onAnyMessageListeners.push_back(listener); + } + + inline void onCommand(const std::string& commandName, const MessageListener& listener) { + _onCommandListeners[commandName] = listener; + } + + inline void onUnknownCommand(const MessageListener& listener) { + _onUnknownCommandListeners.push_back(listener); + } + + inline void onNonCommandMessage(const MessageListener& listener) { + _onNonCommandMessageListeners.push_back(listener); + } + +private: + inline void broadcastAnyMessage(const Message::Ptr& message) const { + for (const MessageListener& item : _onAnyMessageListeners) { + item(message); + } + } + + inline bool broadcastCommand(const std::string command, const Message::Ptr& message) const { + std::map<std::string, MessageListener>::const_iterator iter = _onCommandListeners.find(command); + if (iter == _onCommandListeners.end()) { + return false; + } + iter->second(message); + return true; + } + + inline void broadcastUnknownCommand(const Message::Ptr& message) const { + for (const MessageListener& item : _onUnknownCommandListeners) { + item(message); + } + } + + inline void broadcastNonCommandMessage(const Message::Ptr& message) const { + for (const MessageListener& item : _onNonCommandMessageListeners) { + item(message); + } + } + + std::vector<MessageListener> _onAnyMessageListeners; + std::map<std::string, MessageListener> _onCommandListeners; + std::vector<MessageListener> _onUnknownCommandListeners; + std::vector<MessageListener> _onNonCommandMessageListeners; +}; + +} + +#endif //TGBOT_EVENTBROADCASTER_H diff --git a/src/tgbot/EventManager.cpp b/src/tgbot/EventHandler.h index fdd4859..abcf925 100644 --- a/src/tgbot/EventManager.cpp +++ b/src/tgbot/EventHandler.h @@ -20,37 +20,37 @@ * SOFTWARE. */ -#include "EventManager.h" +#ifndef TGBOT_EVENTHANDLER_H +#define TGBOT_EVENTHANDLER_H +#include "tgbot/EventBroadcaster.h" +#include "tgbot/types/Update.h" #include "tgbot/tools/StringTools.h" -using namespace std; - namespace TgBot { -EventManager::EventManager(Bot* const bot) : _bot(bot) { -} +class EventHandler { -void EventManager::handleUpdate(const Update::Ptr& update) { - for (EventManager::Listener& item : _onAnyMessageListeners) { - item(update->message, _bot); +public: + explicit EventHandler(const EventBroadcaster* broadcaster) : _broadcaster(broadcaster) { } - if (StringTools::startsWith(update->message->text, "/")) { - string command = update->message->text.substr(1, update->message->text.find(' ') - 2); - for (pair<const string, Listener>& item : _onCommandListeners) { - if (item.first == command) { - item.second(update->message, _bot); - return; + + inline void handleUpdate(const Update::Ptr& update) const { + _broadcaster->broadcastAnyMessage(update->message); + if (StringTools::startsWith(update->message->text, "/")) { + std::string command = update->message->text.substr(1, update->message->text.find(' ') - 2); + if (!_broadcaster->broadcastCommand(command, update->message)) { + _broadcaster->broadcastUnknownCommand(update->message); } - } - for (EventManager::Listener& item : _onUnknownCommandListeners) { - item(update->message, _bot); - } - } else { - for (EventManager::Listener& item : _onNonCommandMessageListeners) { - item(update->message, _bot); + } else { + _broadcaster->broadcastNonCommandMessage(update->message); } } -} + +private: + const EventBroadcaster* _broadcaster; +}; } + +#endif //TGBOT_EVENTHANDLER_H diff --git a/src/tgbot/EventManager.h b/src/tgbot/EventManager.h deleted file mode 100644 index cfe0b04..0000000 --- a/src/tgbot/EventManager.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2015 Oleg Morozenkov - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef TGBOT_CPP_EVENTMANAGER_H -#define TGBOT_CPP_EVENTMANAGER_H - -#include <string> -#include <functional> -#include <vector> -#include <map> - -#include "tgbot/types/Update.h" -#include "tgbot/types/Message.h" - -namespace TgBot { - -class Bot; - -class EventManager { - -friend Bot; - -public: - typedef std::function<void (const Message::Ptr&, Bot* const)> Listener; - - inline void onAnyMessage(const Listener& listener) { - _onAnyMessageListeners.push_back(listener); - } - - inline void onCommand(const std::string& commandName, const Listener& listener) { - _onCommandListeners[commandName] = listener; - } - - inline void onUnknownCommand(const Listener& listener) { - _onUnknownCommandListeners.push_back(listener); - } - - inline void onNonCommandMessage(const Listener& listener) { - _onNonCommandMessageListeners.push_back(listener); - } - -private: - explicit EventManager(Bot* const bot); - - void handleUpdate(const Update::Ptr& update); - - Bot* const _bot; - std::vector<Listener> _onAnyMessageListeners; - std::map<std::string, Listener> _onCommandListeners; - std::vector<Listener> _onUnknownCommandListeners; - std::vector<Listener> _onNonCommandMessageListeners; -}; - -} - -#endif //TGBOT_CPP_EVENTMANAGER_H diff --git a/src/tgbot/Http.cpp b/src/tgbot/Http.cpp deleted file mode 100644 index d512e88..0000000 --- a/src/tgbot/Http.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (c) 2015 Oleg Morozenkov - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "Http.h" - -#include <iostream> - -#include <boost/asio/ssl.hpp> - -#include "tgbot/tools/StringTools.h" - -using namespace std; -using namespace boost::asio; -using namespace boost::asio::ip; -using namespace boost::asio::local; -using boost::lexical_cast; - -namespace TgBot { - -string Http::makeRequest(const Url& url, const vector<Argument>& args) { - string result; - - ssl::context context(ssl::context::sslv23); - context.set_default_verify_paths(); - - ssl::stream<tcp::socket> socket(_ioService, context); - tcp::resolver resolver(_ioService); - tcp::resolver::query query(url.host, url.protocol); - connect(socket.lowest_layer(), resolver.resolve(query)); - - socket.set_verify_mode(ssl::verify_none); - socket.set_verify_callback(ssl::rfc2818_verification(url.host)); - socket.handshake(ssl::stream<tcp::socket>::client); - - string requestText; - if (args.empty()) { - requestText += "GET "; - } else { - requestText += "POST "; - } - requestText += url.path; - requestText += url.query.empty() ? "" : "?" + url.query; - requestText += " HTTP/1.1\r\n"; - requestText += "Host: "; - requestText += url.host; - requestText += "\r\nConnection: close\r\n"; - if (args.empty()) { - requestText += "\r\n"; - } else { - string requestData; - - bool isMultipart = false; - string bondary; - srand((unsigned int) time(nullptr)); - for (const Argument& item : args) { - if (item.isFile) { - isMultipart = true; - while (bondary.empty() || item.value.find(bondary) != item.value.npos) { - bondary += StringTools::generateRandomString(4); - } - } - } - if (isMultipart) { - requestText += "Content-Type: multipart/form-data; boundary="; - requestText += bondary; - requestText += "\r\n"; - for (const Argument& item : args) { - requestData += "--"; - requestData += bondary; - requestData += "\r\nContent-Disposition: form-data; name=\""; - requestData += item.name; - requestData += "\"\r\n"; - if (item.isFile) { - requestData += "Content-Type: "; - requestData += item.mimeType; - requestData += "\r\n"; - } - requestData += "\r\n"; - requestData += item.value; - requestData += "\r\n\r\n"; - } - } else { - requestText += "Content-Type: application/x-www-form-urlencoded\r\n"; - bool firstRun = true; - for (const Argument& item : args) { - if (firstRun) { - firstRun = false; - } else { - requestData += '&'; - } - requestData += StringTools::urlEncode(item.name); - requestData += '='; - requestData += StringTools::urlEncode(item.value); - } - } - - requestText += "Content-Length: "; - requestText += lexical_cast<string>(requestData.length()); - requestText += "\r\n\r\n"; - requestText += requestData; - } - write(socket, buffer(requestText.c_str(), requestText.length())); - - char buff[1024]; - boost::system::error_code error; - while (!error) { - size_t bytes = read(socket, buffer(buff), error); - result += string(buff, bytes); - } - - cout << "REQUEST" << endl << requestText << endl << "RESPONSE" << endl << result << endl; - - size_t headerEnd = result.find("\r\n\r\n"); - if (headerEnd == result.npos) { - headerEnd = result.find("\n\n"); - } - if (headerEnd == result.npos) { - headerEnd = 0; - } - result.erase(0, headerEnd); - - return result; -} - -void Http::startServer(unsigned short port, const Http::ServerHandler& handler) { - stopServer(); - _serverHandler = handler; - tcp::acceptor* acceptor = new tcp::acceptor(_ioService, tcp::endpoint(tcp::v4(), port)); - _tcpServer = new Server<tcp>(this, acceptor); - _ioService.run(); -} - -void Http::startServer(const std::string& unixSocketPath, const Http::ServerHandler& handler) { - stopServer(); - _serverHandler = handler; - stream_protocol::acceptor* acceptor = new stream_protocol::acceptor(_ioService, stream_protocol::endpoint(unixSocketPath)); - _unixSocketServer = new Server<stream_protocol>(this, acceptor); - _ioService.run(); -} - -void Http::stopServer() { - _ioService.stop(); - if (_tcpServer != nullptr) { - delete _tcpServer; - _tcpServer = nullptr; - } - if (_unixSocketServer != nullptr) { - delete _unixSocketServer; - _unixSocketServer = nullptr; - } -} - -}
\ No newline at end of file diff --git a/src/tgbot/Http.h b/src/tgbot/Http.h deleted file mode 100644 index aedcd93..0000000 --- a/src/tgbot/Http.h +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2015 Oleg Morozenkov - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef TGBOT_CPP_HTTP_H -#define TGBOT_CPP_HTTP_H - -#include <string> - -#include <boost/asio.hpp> -#include <boost/lexical_cast.hpp> - -#include "tgbot/Url.h" - -namespace TgBot { - -class Http { - -public: - typedef std::function<void (const std::string&)> ServerHandler; - - class Argument { - - public: - template<typename T> - Argument(const std::string& name, const T& value, bool isFile = false, const std::string& mimeType = "") : - name(name), value(boost::lexical_cast<std::string>(value)), isFile(isFile), mimeType(mimeType) - { - } - - std::string name; - std::string value; - bool isFile = false; - std::string mimeType; - }; - - std::string makeRequest(const Url& url, const std::vector<Argument>& args); - void startServer(unsigned short port, const ServerHandler& handler); - void startServer(const std::string& unixSocketPath, const ServerHandler& handler); - void stopServer(); - -private: - template<typename Protocol> - class Connection { - - public: - Connection(Http* http, boost::asio::basic_stream_socket<Protocol>* socket) : http(http), socket(socket) { - } - - ~Connection() { - delete socket; - } - - void start() { - data.reserve(10240); - socket->async_receive(data, [this]() { - size_t headerEnd = data.find("\r\n\r\n"); - if (headerEnd == data.npos) { - headerEnd = data.find("\n\n"); - } - if (headerEnd == data.npos) { - headerEnd = 0; - } - data.erase(0, headerEnd); - http->_serverHandler(data); - }); - } - - Http* http; - boost::asio::basic_stream_socket<Protocol>* socket; - std::string data; - }; - - template<typename Protocol> - class Server { - - public: - Server(Http* http, boost::asio::basic_socket_acceptor<Protocol>* acceptor) : http(http), acceptor(acceptor) { - } - - ~Server() { - delete acceptor; - } - - void start() { - boost::asio::basic_stream_socket<Protocol>* socket = new boost::asio::basic_stream_socket<Protocol>(acceptor->get_io_service()); - std::shared_ptr<Http::Connection<Protocol>> connection(new Connection<Protocol>(http, socket)); - acceptor->async_accept(*connection->socket, [this, connection]() { - connection->start(); - start(); - }); - } - - Http* http; - boost::asio::basic_socket_acceptor<Protocol>* acceptor; - }; - - - boost::asio::io_service _ioService; - Server<boost::asio::ip::tcp>* _tcpServer = nullptr; - Server<boost::asio::local::stream_protocol>* _unixSocketServer = nullptr; - ServerHandler _serverHandler; -}; - -} - -#endif //TGBOT_CPP_HTTP_H diff --git a/src/tgbot/Parser.cpp b/src/tgbot/TgTypeParser.cpp index 3730607..7abcc0e 100644 --- a/src/tgbot/Parser.cpp +++ b/src/tgbot/TgTypeParser.cpp @@ -20,14 +20,19 @@ * SOFTWARE. */ -#include "Parser.h" +#include "TgTypeParser.h" using namespace std; using namespace boost::property_tree; namespace TgBot { -User::Ptr Parser::parseUser(const ptree& data) const { +TgTypeParser& TgTypeParser::getInstance() { + static TgTypeParser result; + return result; +} + +User::Ptr TgTypeParser::parseUser(const ptree& data) const { User::Ptr result(new User); result->id = data.get<int32_t>("id"); result->firstName = data.get<string>("first_name"); @@ -36,7 +41,7 @@ User::Ptr Parser::parseUser(const ptree& data) const { return result; } -string Parser::parseUser(const User::Ptr& object) const { +string TgTypeParser::parseUser(const User::Ptr& object) const { if (!object) { return ""; } @@ -51,14 +56,14 @@ string Parser::parseUser(const User::Ptr& object) const { return result; } -GroupChat::Ptr Parser::parseGroupChat(const ptree& data) const { +GroupChat::Ptr TgTypeParser::parseGroupChat(const ptree& data) const { GroupChat::Ptr result(new GroupChat); result->id = data.get<int32_t>("id"); result->title = data.get<string>("title"); return result; } -string Parser::parseGroupChat(const GroupChat::Ptr& object) const { +string TgTypeParser::parseGroupChat(const GroupChat::Ptr& object) const { if (!object) { return ""; } @@ -71,7 +76,7 @@ string Parser::parseGroupChat(const GroupChat::Ptr& object) const { return result; } -Message::Ptr Parser::parseMessage(const ptree& data) const { +Message::Ptr TgTypeParser::parseMessage(const ptree& data) const { Message::Ptr result(new Message); result->messageId = data.get<int32_t>("message_id"); result->from = parseUser(data.find("from")->second); @@ -97,7 +102,7 @@ Message::Ptr Parser::parseMessage(const ptree& data) const { return result; } -string Parser::parseMessage(const Message::Ptr& object) const { +string TgTypeParser::parseMessage(const Message::Ptr& object) const { if (!object) { return ""; } @@ -129,7 +134,7 @@ string Parser::parseMessage(const Message::Ptr& object) const { return result; } -PhotoSize::Ptr Parser::parsePhotoSize(const ptree& data) const { +PhotoSize::Ptr TgTypeParser::parsePhotoSize(const ptree& data) const { PhotoSize::Ptr result(new PhotoSize); result->fileId = data.get<string>("file_id"); result->width = data.get<int32_t>("width"); @@ -138,7 +143,7 @@ PhotoSize::Ptr Parser::parsePhotoSize(const ptree& data) const { return result; } -string Parser::parsePhotoSize(const PhotoSize::Ptr& object) const { +string TgTypeParser::parsePhotoSize(const PhotoSize::Ptr& object) const { if (!object) { return ""; } @@ -153,7 +158,7 @@ string Parser::parsePhotoSize(const PhotoSize::Ptr& object) const { return result; } -Audio::Ptr Parser::parseAudio(const ptree& data) const { +Audio::Ptr TgTypeParser::parseAudio(const ptree& data) const { Audio::Ptr result(new Audio); result->fileId = data.get<string>("file_id"); result->duration = data.get<int32_t>("duration"); @@ -162,7 +167,7 @@ Audio::Ptr Parser::parseAudio(const ptree& data) const { return result; } -string Parser::parseAudio(const Audio::Ptr& object) const { +string TgTypeParser::parseAudio(const Audio::Ptr& object) const { if (!object) { return ""; } @@ -177,7 +182,7 @@ string Parser::parseAudio(const Audio::Ptr& object) const { return result; } -Document::Ptr Parser::parseDocument(const ptree& data) const { +Document::Ptr TgTypeParser::parseDocument(const ptree& data) const { Document::Ptr result(new Document); result->fileId = data.get<string>("file_id"); result->thumb = parsePhotoSize(data.find("thumb")->second); @@ -187,7 +192,7 @@ Document::Ptr Parser::parseDocument(const ptree& data) const { return result; } -string Parser::parseDocument(const Document::Ptr& object) const { +string TgTypeParser::parseDocument(const Document::Ptr& object) const { if (!object) { return ""; } @@ -203,7 +208,7 @@ string Parser::parseDocument(const Document::Ptr& object) const { return result; } -Sticker::Ptr Parser::parseSticker(const ptree& data) const { +Sticker::Ptr TgTypeParser::parseSticker(const ptree& data) const { Sticker::Ptr result(new Sticker); result->fileId = data.get<string>("file_id"); result->width = data.get<int32_t>("width"); @@ -213,7 +218,7 @@ Sticker::Ptr Parser::parseSticker(const ptree& data) const { return result; } -string Parser::parseSticker(const Sticker::Ptr& object) const { +string TgTypeParser::parseSticker(const Sticker::Ptr& object) const { if (!object) { return ""; } @@ -229,7 +234,7 @@ string Parser::parseSticker(const Sticker::Ptr& object) const { return result; } -Video::Ptr Parser::parseVideo(const ptree& data) const { +Video::Ptr TgTypeParser::parseVideo(const ptree& data) const { Video::Ptr result(new Video); result->fileId = data.get<string>("file_id"); result->width = data.get<int32_t>("width"); @@ -242,7 +247,7 @@ Video::Ptr Parser::parseVideo(const ptree& data) const { return result; } -string Parser::parseVideo(const Video::Ptr& object) const { +string TgTypeParser::parseVideo(const Video::Ptr& object) const { if (!object) { return ""; } @@ -261,7 +266,7 @@ string Parser::parseVideo(const Video::Ptr& object) const { return result; } -Contact::Ptr Parser::parseContact(const ptree& data) const { +Contact::Ptr TgTypeParser::parseContact(const ptree& data) const { Contact::Ptr result(new Contact); result->phoneNumber = data.get<string>("phone_number"); result->firstName = data.get<string>("first_name"); @@ -270,7 +275,7 @@ Contact::Ptr Parser::parseContact(const ptree& data) const { return result; } -string Parser::parseContact(const Contact::Ptr& object) const { +string TgTypeParser::parseContact(const Contact::Ptr& object) const { if (!object) { return ""; } @@ -285,14 +290,14 @@ string Parser::parseContact(const Contact::Ptr& object) const { return result; } -Location::Ptr Parser::parseLocation(const ptree& data) const { +Location::Ptr TgTypeParser::parseLocation(const ptree& data) const { Location::Ptr result(new Location); result->longitude = data.get<float>("longitude"); result->latitude = data.get<float>("latitude"); return result; } -string Parser::parseLocation(const Location::Ptr& object) const { +string TgTypeParser::parseLocation(const Location::Ptr& object) const { if (!object) { return ""; } @@ -305,14 +310,14 @@ string Parser::parseLocation(const Location::Ptr& object) const { return result; } -Update::Ptr Parser::parseUpdate(const ptree& data) const { +Update::Ptr TgTypeParser::parseUpdate(const ptree& data) const { Update::Ptr result(new Update); result->updateId = data.get<int32_t>("update_id"); result->message = parseMessage(data.find("message")->second); return result; } -string Parser::parseUpdate(const Update::Ptr& object) const { +string TgTypeParser::parseUpdate(const Update::Ptr& object) const { if (!object) { return ""; } @@ -325,14 +330,14 @@ string Parser::parseUpdate(const Update::Ptr& object) const { return result; } -UserProfilePhotos::Ptr Parser::parseUserProfilePhotos(const ptree& data) const { +UserProfilePhotos::Ptr TgTypeParser::parseUserProfilePhotos(const ptree& data) const { UserProfilePhotos::Ptr result(new UserProfilePhotos); result->totalCount = data.get<int32_t>("total_count"); result->photos = parse2DArray<PhotoSize>(parsePhotoSize, data, "photos"); return result; } -string Parser::parseUserProfilePhotos(const UserProfilePhotos::Ptr& object) const { +string TgTypeParser::parseUserProfilePhotos(const UserProfilePhotos::Ptr& object) const { if (!object) { return ""; } @@ -345,7 +350,7 @@ string Parser::parseUserProfilePhotos(const UserProfilePhotos::Ptr& object) cons return result; } -ReplyKeyboardMarkup::Ptr Parser::parseReplyKeyboardMarkup(const boost::property_tree::ptree& data) const { +ReplyKeyboardMarkup::Ptr TgTypeParser::parseReplyKeyboardMarkup(const boost::property_tree::ptree& data) const { ReplyKeyboardMarkup::Ptr result(new ReplyKeyboardMarkup); for (const pair<const string, ptree>& item : data.find("keyboard")->second) { vector<string> array; @@ -360,7 +365,7 @@ ReplyKeyboardMarkup::Ptr Parser::parseReplyKeyboardMarkup(const boost::property_ return result; } -std::string Parser::parseReplyKeyboardMarkup(const ReplyKeyboardMarkup::Ptr& object) const { +std::string TgTypeParser::parseReplyKeyboardMarkup(const ReplyKeyboardMarkup::Ptr& object) const { if (!object) { return ""; } @@ -387,13 +392,13 @@ std::string Parser::parseReplyKeyboardMarkup(const ReplyKeyboardMarkup::Ptr& obj return result; } -ReplyKeyboardHide::Ptr Parser::parseReplyKeyboardHide(const boost::property_tree::ptree& data) const { +ReplyKeyboardHide::Ptr TgTypeParser::parseReplyKeyboardHide(const boost::property_tree::ptree& data) const { ReplyKeyboardHide::Ptr result(new ReplyKeyboardHide); result->selective = data.get<bool>("selective"); return result; } -std::string Parser::parseReplyKeyboardHide(const ReplyKeyboardHide::Ptr& object) const { +std::string TgTypeParser::parseReplyKeyboardHide(const ReplyKeyboardHide::Ptr& object) const { if (!object) { return ""; } @@ -405,13 +410,13 @@ std::string Parser::parseReplyKeyboardHide(const ReplyKeyboardHide::Ptr& object) return result; } -ForceReply::Ptr Parser::parseForceReply(const boost::property_tree::ptree& data) const { +ForceReply::Ptr TgTypeParser::parseForceReply(const boost::property_tree::ptree& data) const { ForceReply::Ptr result(new ForceReply); result->selective = data.get<bool>("selective"); return result; } -std::string Parser::parseForceReply(const ForceReply::Ptr& object) const { +std::string TgTypeParser::parseForceReply(const ForceReply::Ptr& object) const { if (!object) { return ""; } @@ -423,7 +428,7 @@ std::string Parser::parseForceReply(const ForceReply::Ptr& object) const { return result; } -GenericChat::Ptr Parser::parseGenericChat(const ptree& data) const { +GenericChat::Ptr TgTypeParser::parseGenericChat(const ptree& data) const { if (data.find("first_name") == data.not_found()) { return static_pointer_cast<GenericChat>(parseGroupChat(data)); } else { @@ -431,7 +436,7 @@ GenericChat::Ptr Parser::parseGenericChat(const ptree& data) const { } } -string Parser::parseGenericChat(const GenericChat::Ptr& object) const { +string TgTypeParser::parseGenericChat(const GenericChat::Ptr& object) const { if (!object) { return ""; } @@ -442,7 +447,7 @@ string Parser::parseGenericChat(const GenericChat::Ptr& object) const { } } -GenericReply::Ptr Parser::parseGenericReply(const boost::property_tree::ptree& data) const { +GenericReply::Ptr TgTypeParser::parseGenericReply(const boost::property_tree::ptree& data) const { if (data.find("force_reply") != data.not_found()) { return static_pointer_cast<GenericReply>(parseForceReply(data)); } else if (data.find("hide_keyboard") != data.not_found()) { @@ -452,7 +457,7 @@ GenericReply::Ptr Parser::parseGenericReply(const boost::property_tree::ptree& d } } -std::string Parser::parseGenericReply(const GenericReply::Ptr& object) const { +std::string TgTypeParser::parseGenericReply(const GenericReply::Ptr& object) const { if (!object) { return ""; } @@ -465,7 +470,7 @@ std::string Parser::parseGenericReply(const GenericReply::Ptr& object) const { } } -void Parser::appendToJson(string& json, const string& varName, const string& value) const { +void TgTypeParser::appendToJson(string& json, const string& varName, const string& value) const { if (value.empty()) { return; } diff --git a/src/tgbot/Parser.h b/src/tgbot/TgTypeParser.h index 48a7cea..0571c25 100644 --- a/src/tgbot/Parser.h +++ b/src/tgbot/TgTypeParser.h @@ -20,8 +20,8 @@ * SOFTWARE. */ -#ifndef TGBOT_CPP_PARSER_H -#define TGBOT_CPP_PARSER_H +#ifndef TGBOT_CPP_TGTYPEPARSER_H +#define TGBOT_CPP_TGTYPEPARSER_H #include <string> @@ -47,9 +47,11 @@ namespace TgBot { -class Parser { +class TgTypeParser { public: + static TgTypeParser& getInstance(); + User::Ptr parseUser(const boost::property_tree::ptree& data) const; std::string parseUser(const User::Ptr& object) const; GroupChat::Ptr parseGroupChat(const boost::property_tree::ptree& data) const; @@ -93,7 +95,7 @@ public: } template<typename T> - std::shared_ptr<T> tryParse(std::shared_ptr<T> (Parser::*const parseFunc)(const boost::property_tree::ptree&) const, const boost::property_tree::ptree& data, const std::string& keyName) const { + std::shared_ptr<T> tryParse(std::shared_ptr<T> (TgTypeParser::*const parseFunc)(const boost::property_tree::ptree&) const, const boost::property_tree::ptree& data, const std::string& keyName) const { auto treeItem = data.find(keyName); if (treeItem == data.not_found()) { return std::shared_ptr<T>(); @@ -102,7 +104,7 @@ public: } template<typename T> - std::vector<std::shared_ptr<T>> parseArray(std::shared_ptr<T> (Parser::*const parseFunc)(const boost::property_tree::ptree&) const, const boost::property_tree::ptree& data, const std::string& keyName) const { + std::vector<std::shared_ptr<T>> parseArray(std::shared_ptr<T> (TgTypeParser::*const parseFunc)(const boost::property_tree::ptree&) const, const boost::property_tree::ptree& data, const std::string& keyName) const { std::vector<std::shared_ptr<T>> result; auto treeItem = data.find(keyName); if (treeItem == data.not_found()) { @@ -115,7 +117,7 @@ public: } template<typename T> - std::vector<std::vector<std::shared_ptr<T>>> parse2DArray(std::shared_ptr<T> (Parser::*const parseFunc)(const boost::property_tree::ptree&) const, const boost::property_tree::ptree& data, const std::string& keyName) const { + std::vector<std::vector<std::shared_ptr<T>>> parse2DArray(std::shared_ptr<T> (TgTypeParser::*const parseFunc)(const boost::property_tree::ptree&) const, const boost::property_tree::ptree& data, const std::string& keyName) const { std::vector<std::vector<std::shared_ptr<T>>> result; auto treeItem = data.find(keyName); if (treeItem == data.not_found()) { @@ -132,7 +134,7 @@ public: } template<typename T> - std::string parseArray(std::string (Parser::*const parseFunc)(const std::shared_ptr<T>&) const, const std::vector<std::shared_ptr<T>>& objects) const { + std::string parseArray(std::string (TgTypeParser::*const parseFunc)(const std::shared_ptr<T>&) const, const std::vector<std::shared_ptr<T>>& objects) const { std::string result; result += '['; for (const std::shared_ptr<T>& item : objects) { @@ -145,7 +147,7 @@ public: } template<typename T> - std::string parse2DArray(std::string (Parser::*const parseFunc)(const std::shared_ptr<T>&) const, const std::vector<std::vector<std::shared_ptr<T>>>& objects) const { + std::string parse2DArray(std::string (TgTypeParser::*const parseFunc)(const std::shared_ptr<T>&) const, const std::vector<std::vector<std::shared_ptr<T>>>& objects) const { std::string result; result += '['; for (const std::vector<std::shared_ptr<T>>& item : objects) { @@ -175,4 +177,4 @@ private: } -#endif //TGBOT_CPP_PARSER_H +#endif //TGBOT_CPP_TGTYPEPARSER_H diff --git a/src/tgbot/net/HttpClient.cpp b/src/tgbot/net/HttpClient.cpp new file mode 100644 index 0000000..fefeac5 --- /dev/null +++ b/src/tgbot/net/HttpClient.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2015 Oleg Morozenkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "HttpClient.h" + +#include <boost/asio/ssl.hpp> + +using namespace std; +using namespace boost::asio; +using namespace boost::asio::ip; +using namespace boost::asio::local; + +namespace TgBot { + +HttpClient& HttpClient::getInstance() { + static HttpClient result; + return result; +} + +string HttpClient::makeRequest(const Url& url, const vector<HttpReqArg>& args) { + ssl::context context(ssl::context::sslv23); + context.set_default_verify_paths(); + + ssl::stream<tcp::socket> socket(_ioService, context); + tcp::resolver resolver(_ioService); + tcp::resolver::query query(url.host, url.protocol); + + connect(socket.lowest_layer(), resolver.resolve(query)); + +// boost::asio::socket_base::keep_alive keepAliveOption(true); +// socket.lowest_layer().set_option(keepAliveOption); + + socket.set_verify_mode(ssl::verify_none); + socket.set_verify_callback(ssl::rfc2818_verification(url.host)); + socket.handshake(ssl::stream<tcp::socket>::client); + + string requestText = HttpParser::getInstance().generateRequest(url, args, false); + write(socket, buffer(requestText.c_str(), requestText.length())); + + string response; + char buff[1024]; + boost::system::error_code error; + while (!error) { + size_t bytes = read(socket, buffer(buff), error); + response += string(buff, bytes); + printf("%s", string(buff, bytes).c_str()); + } + + return HttpParser::getInstance().parseResponse(response); +} + +} diff --git a/src/tgbot/net/HttpClient.h b/src/tgbot/net/HttpClient.h new file mode 100644 index 0000000..4b84dc3 --- /dev/null +++ b/src/tgbot/net/HttpClient.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2015 Oleg Morozenkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef TGBOT_HTTPCLIENT_H +#define TGBOT_HTTPCLIENT_H + +#include <string> + +#include <boost/asio.hpp> + +#include "tgbot/net/Url.h" +#include "tgbot/net/HttpReqArg.h" +#include "tgbot/net/HttpParser.h" + +namespace TgBot { + +class HttpClient { + +public: + static HttpClient& getInstance(); + + std::string makeRequest(const Url& url, const std::vector<HttpReqArg>& args); + +private: + boost::asio::io_service _ioService; +}; + +} + +#endif //TGBOT_HTTPCLIENT_H diff --git a/src/tgbot/net/HttpParser.cpp b/src/tgbot/net/HttpParser.cpp new file mode 100644 index 0000000..070c630 --- /dev/null +++ b/src/tgbot/net/HttpParser.cpp @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2015 Oleg Morozenkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "HttpParser.h" + +#include <boost/algorithm/string.hpp> + +#include "tgbot/tools/StringTools.h" + +using namespace std; +using namespace boost; + +namespace TgBot { + +HttpParser& HttpParser::getInstance() { + static HttpParser result; + return result; +} + +string HttpParser::generateRequest(const Url& url, const vector<HttpReqArg>& args, bool isKeepAlive) { + string result; + if (args.empty()) { + result += "GET "; + } else { + result += "POST "; + } + result += url.path; + result += url.query.empty() ? "" : "?" + url.query; + result += " HTTP/1.1\r\n"; + result += "Host: "; + result += url.host; + result += "\r\nConnection: "; + if (isKeepAlive) { + result += "keep-alive"; + } else { + result += "close"; + } + result += "\r\n"; + if (args.empty()) { + result += "\r\n"; + } else { + string requestData; + + string bondary = generateMultipartBoundary(args); + if (bondary.empty()) { + result += "Content-Type: application/x-www-form-urlencoded\r\n"; + requestData = generateWwwFormUrlencoded(args); + } else { + result += "Content-Type: multipart/form-data; boundary="; + result += bondary; + result += "\r\n"; + requestData = generateMultipartFormData(args, bondary); + } + + result += "Content-Length: "; + result += lexical_cast<string>(requestData.length()); + result += "\r\n\r\n"; + result += requestData; + } + return result; +} + +string HttpParser::generateMultipartFormData(const vector<HttpReqArg>& args, const string& bondary) { + string result; + for (const HttpReqArg& item : args) { + result += "--"; + result += bondary; + result += "\r\nContent-Disposition: form-data; name=\""; + result += item.name; + result += "\"\r\n"; + if (item.isFile) { + result += "Content-Type: "; + result += item.mimeType; + result += "\r\n"; + } + result += "\r\n"; + result += item.value; + result += "\r\n"; + } + return result; +} + +string HttpParser::generateMultipartBoundary(const vector<HttpReqArg>& args) { + string result; + srand((unsigned int) time(nullptr)); + for (const HttpReqArg& item : args) { + if (item.isFile) { + while (result.empty() || item.value.find(result) != item.value.npos) { + result += StringTools::generateRandomString(4); + } + } + } + return result; +} + +string HttpParser::generateWwwFormUrlencoded(const vector<HttpReqArg>& args) { + string result; + + bool firstRun = true; + for (const HttpReqArg& item : args) { + if (firstRun) { + firstRun = false; + } else { + result += '&'; + } + result += StringTools::urlEncode(item.name); + result += '='; + result += StringTools::urlEncode(item.value); + } + + return result; +} + +string HttpParser::generateResponse(const string& data, const string& mimeType, unsigned short statusCode, const string& statusStr, bool isKeepAlive) { + string result; + result += "HTTP/1.1 "; + result += lexical_cast<string>(statusCode); + result += ' '; + result += statusStr; + result += "\r\nContent-Type: "; + result += mimeType; + result += "\r\nContent-Length: "; + result += lexical_cast<string>(data.length()); + result += "\r\n\r\n"; + result += data; + return result; +} + +string HttpParser::parseHttp(bool isRequest, const string& data, map<string, string>& headers) { + bool onlyNewLineChar = false; + size_t headerEnd = data.find("\r\n\r\n"); + if (headerEnd == data.npos) { + headerEnd = data.find("\n\n"); + if (headerEnd != data.npos) { + onlyNewLineChar = true; + headerEnd += 2; + } + } else { + headerEnd += 4; + } + + size_t lineStart = 0; + size_t lineEnd = 0; + size_t lineSepPos = 0; + size_t lastLineEnd = data.npos; + while (lastLineEnd != lineEnd) { + lastLineEnd = lineEnd; + if (lineEnd == 0) { + if (isRequest) { + lineSepPos = data.find(' '); + lineEnd = data.find(onlyNewLineChar ? "\n" : "\r\n"); + headers["method"] = data.substr(0, lineSepPos); + headers["path"] = data.substr(lineSepPos + 1, data.find(' ', lineSepPos + 1) - lineSepPos - 1); + } else { + lineSepPos = data.find(' '); + lineEnd = data.find(onlyNewLineChar ? "\n" : "\r\n"); + headers["status"] = data.substr(lineSepPos + 1, data.find(' ', lineSepPos + 1) - lineSepPos - 1); + } + } else { + lineStart = lineEnd; + lineStart += onlyNewLineChar ? 1 : 2; + lineEnd = data.find(onlyNewLineChar ? "\n" : "\r\n", lineStart); + lineSepPos = data.find(':', lineStart); + if (lineEnd >= headerEnd || lastLineEnd == lineEnd || lineSepPos >= headerEnd) { + break; + } + headers[to_lower_copy(data.substr(lineStart, lineSepPos - lineStart))] = trim_copy(data.substr(lineSepPos + 1, lineEnd - lineSepPos - 1)); + } + } + + return headerEnd == data.npos ? "" : data.substr(headerEnd); +} + +string HttpParser::parseHttp(bool isRequest, const string& data) { + size_t headerEnd = data.find("\r\n\r\n"); + if (headerEnd == data.npos) { + headerEnd = data.find("\n\n"); + } + if (headerEnd == data.npos) { + headerEnd = 0; + } + return data.substr(headerEnd); +} + +} diff --git a/src/tgbot/net/HttpParser.h b/src/tgbot/net/HttpParser.h new file mode 100644 index 0000000..dcb857b --- /dev/null +++ b/src/tgbot/net/HttpParser.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2015 Oleg Morozenkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef TGBOT_HTTPPARSER_H +#define TGBOT_HTTPPARSER_H + +#include <string> +#include <map> + +#include "tgbot/net/Url.h" +#include "tgbot/net/HttpReqArg.h" + +namespace TgBot { + +class HttpParser { + +public: + static HttpParser& getInstance(); + + std::string generateRequest(const Url& url, const std::vector<HttpReqArg>& args, bool isKeepAlive = false); + std::string generateMultipartFormData(const std::vector<HttpReqArg>& args, const std::string& bondary); + std::string generateMultipartBoundary(const std::vector<HttpReqArg>& args); + std::string generateWwwFormUrlencoded(const std::vector<HttpReqArg>& args); + std::string generateResponse(const std::string& data, const std::string& mimeType = "text/plain", short unsigned statusCode = 200, const std::string& statusStr = "OK", bool isKeepAlive = false); + + inline std::string parseRequest(const std::string& data, std::map<std::string, std::string>& headers) { + return parseHttp(true, data, headers); + } + + inline std::string parseRequest(const std::string& data) { + return parseHttp(true, data); + } + + inline std::string parseResponse(const std::string& data, std::map<std::string, std::string>& headers) { + return parseHttp(false, data, headers); + } + + inline std::string parseResponse(const std::string& data) { + return parseHttp(false, data); + } + +private: + std::string parseHttp(bool isRequest, const std::string& data, std::map<std::string, std::string>& headers); + std::string parseHttp(bool isRequest, const std::string& data); +}; + +} + +#endif //TGBOT_HTTPPARSER_H diff --git a/src/tgbot/net/HttpReqArg.h b/src/tgbot/net/HttpReqArg.h new file mode 100644 index 0000000..e906e94 --- /dev/null +++ b/src/tgbot/net/HttpReqArg.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015 Oleg Morozenkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef TGBOT_HTTPPARAMETER_H +#define TGBOT_HTTPPARAMETER_H + +#include <string> + +#include <boost/lexical_cast.hpp> + +namespace TgBot { + +class HttpReqArg { + +public: + template<typename T> + HttpReqArg(const std::string& name, const T& value, bool isFile = false, const std::string& mimeType = "text/plain") : + name(name), value(boost::lexical_cast<std::string>(value)), isFile(isFile), mimeType(mimeType) + { + } + + std::string name; + std::string value; + bool isFile = false; + std::string mimeType; +}; + +} + + +#endif //TGBOT_HTTPPARAMETER_H diff --git a/src/tgbot/net/HttpServer.h b/src/tgbot/net/HttpServer.h new file mode 100644 index 0000000..92a667a --- /dev/null +++ b/src/tgbot/net/HttpServer.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2015 Oleg Morozenkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef TGBOT_HTTPSERVER_H +#define TGBOT_HTTPSERVER_H + +#include <string> + +#include <boost/asio.hpp> + +#include "tgbot/net/HttpParser.h" + +namespace TgBot { + +template<typename Protocol> +class HttpServer { + +private: + class Connection; + +public: + typedef std::function<std::string (const std::string&, const std::map<std::string, std::string>)> ServerHandler; + + HttpServer(std::shared_ptr<boost::asio::basic_socket_acceptor<Protocol>>& acceptor, const ServerHandler& handler) : _acceptor(acceptor), _handler(handler) { + } + + void start() { + std::shared_ptr<boost::asio::basic_stream_socket<Protocol>> socket(new boost::asio::basic_stream_socket<Protocol>(acceptor->get_io_service())); + std::shared_ptr<Connection<Protocol>> connection(new Connection<Protocol>(socket, _handler)); + acceptor->async_accept(*connection->socket, [this, connection]() { + connection->start(); + start(); + }); + _ioService.run(); + } + + void stop() { + _ioService.stop(); + } + +private: + template<typename Protocol> + class Connection { + + public: + Connection(std::shared_ptr<boost::asio::basic_stream_socket<Protocol>>& socket, const ServerHandler& handler) : socket(socket), _handler(handler) { + boost::asio::socket_base::keep_alive option(true); + socket.set_option(option); + } + + void start() { + data.reserve(10240); + socket->async_receive(data, [this]() { + std::map<std::string, std::string> headers; + std::string body = HttpParser::parseResponse(data, headers); + socket->async_send(_handler(body, headers)); + }); + } + + std::shared_ptr<boost::asio::basic_stream_socket<Protocol>> socket; + std::string data; + + private: + const ServerHandler _handler; + }; + + boost::asio::io_service _ioService; + std::shared_ptr<boost::asio::basic_socket_acceptor<Protocol>> _acceptor; + const ServerHandler _handler; +}; + +} + +#endif //TGBOT_HTTPSERVER_H diff --git a/src/tgbot/Bot.cpp b/src/tgbot/net/TgLongPoll.cpp index 091b548..6359ec6 100644 --- a/src/tgbot/Bot.cpp +++ b/src/tgbot/net/TgLongPoll.cpp @@ -20,32 +20,23 @@ * SOFTWARE. */ -#include "Bot.h" - -using namespace std; -using namespace boost::property_tree; +#include "TgLongPoll.h" namespace TgBot { -Bot::Bot(const string& token) : - _token(token), - _api(this), - _events(this), - _http(), - _parser(), - _webhooksServerHandler([this](const string& data) { - _events.handleUpdate(_parser.parseUpdate(_parser.parseJson(data))); - }) -{ +TgLongPoll::TgLongPoll(const Api* api, const EventHandler* eventHandler) : _api(api), _eventHandler(eventHandler) { +} + +TgLongPoll::TgLongPoll(const Bot& bot) : TgLongPoll(&bot.getApi(), &bot.getEventHandler()) { } -void Bot::startLongPoll() { - std::vector<Update::Ptr> updates = _api.getUpdates(_lastUpdateId, 100, 60); +void TgLongPoll::start() { + std::vector<Update::Ptr> updates = _api->getUpdates(_lastUpdateId, 100, 60); for (Update::Ptr& item : updates) { if (item->updateId >= _lastUpdateId) { _lastUpdateId = item->updateId + 1; } - _events.handleUpdate(item); + _eventHandler->handleUpdate(item); } } diff --git a/src/tgbot/net/TgLongPoll.h b/src/tgbot/net/TgLongPoll.h new file mode 100644 index 0000000..6c5175e --- /dev/null +++ b/src/tgbot/net/TgLongPoll.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2015 Oleg Morozenkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef TGBOT_TGLONGPOLL_H +#define TGBOT_TGLONGPOLL_H + +#include "tgbot/Bot.h" +#include "tgbot/Api.h" +#include "tgbot/EventHandler.h" + +namespace TgBot { + +class TgLongPoll { + +public: + TgLongPoll(const Api* api, const EventHandler* eventHandler); + TgLongPoll(const Bot& bot); + + void start(); + +private: + int32_t _lastUpdateId = 0; + const Api* _api; + const EventHandler* _eventHandler; +}; + +} + +#endif //TGBOT_TGLONGPOLL_H diff --git a/src/tgbot/net/TgWebhookLocalServer.h b/src/tgbot/net/TgWebhookLocalServer.h new file mode 100644 index 0000000..ec99ad7 --- /dev/null +++ b/src/tgbot/net/TgWebhookLocalServer.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2015 Oleg Morozenkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef TGBOT_TGWEBHOOKTCPSERVER_H +#define TGBOT_TGWEBHOOKTCPSERVER_H + +#include "tgbot/net/TgWebhookServer.h" + +namespace TgBot { + +class TgWebhookLocalServer : public TgWebhookServer<boost::asio::local::stream_protocol> { + +public: + TgWebhookLocalServer(std::shared_ptr<boost::asio::basic_socket_acceptor<boost::asio::local::stream_protocol>>& acceptor, const std::string& path, EventHandler* eventHandler) = delete; + + TgWebhookLocalServer(const std::string& path, const EventHandler* eventHandler) : + TgWebhookServer(std::shared_ptr<boost::asio::basic_socket_acceptor<boost::asio::local::stream_protocol>>(new boost::asio::local::stream_protocol::acceptor(_ioService, boost::asio::local::stream_protocol::endpoint(path))), path, eventHandler) + { + } + + TgWebhookLocalServer(const std::string& path, const Bot& bot) : TgWebhookLocalServer(path, &bot.getEventHandler()) { + + } +}; + +} + +#endif //TGBOT_TGWEBHOOKTCPSERVER_H diff --git a/src/tgbot/net/TgWebhookServer.h b/src/tgbot/net/TgWebhookServer.h new file mode 100644 index 0000000..a8155ff --- /dev/null +++ b/src/tgbot/net/TgWebhookServer.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2015 Oleg Morozenkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef TGBOT_TGHTTPSERVER_H +#define TGBOT_TGHTTPSERVER_H + +#include "tgbot/Bot.h" +#include "tgbot/EventHandler.h" +#include "tgbot/TgTypeParser.h" +#include "tgbot/net/HttpServer.h" + +namespace TgBot { + +template<typename Protocol> +class TgWebhookServer : public HttpServer<Protocol> { + +public: + TgWebhookServer(std::shared_ptr<boost::asio::basic_socket_acceptor<Protocol>>& acceptor, const ServerHandler& handler) = delete; + + TgWebhookServer(std::shared_ptr<boost::asio::basic_socket_acceptor<Protocol>>& acceptor, const std::string& path, const EventHandler* eventHandler) : + HttpServer(acceptor, [this, eventHandler, &path](const std::string& data, const std::map<std::string, std::string>& headers) -> std::string { + if (headers["method"] == "POST" && headers["path"] == path) { + eventHandler->handleUpdate(TgTypeParser::getInstance().parseUpdate(TgTypeParser::getInstance().parseJson(data))); + } + return HttpParser::generateResponse(""); + }) + { + } + + TgWebhookServer(std::shared_ptr<boost::asio::basic_socket_acceptor<Protocol>>& acceptor, const std::string& path, const Bot& bot) : + TgWebhookServer(acceptor, path, &bot.getEventHandler()) + { + } +}; + +} + +#endif //TGBOT_TGHTTPSERVER_H diff --git a/src/tgbot/net/TgWebhookTcpServer.h b/src/tgbot/net/TgWebhookTcpServer.h new file mode 100644 index 0000000..4dc58e1 --- /dev/null +++ b/src/tgbot/net/TgWebhookTcpServer.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2015 Oleg Morozenkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef TGBOT_TGWEBHOOKTCPSERVER_H +#define TGBOT_TGWEBHOOKTCPSERVER_H + +#include "tgbot/net/TgWebhookServer.h" + +namespace TgBot { + +class TgWebhookTcpServer : public TgWebhookServer<boost::asio::ip::tcp> { + +public: + TgWebhookTcpServer(std::shared_ptr<boost::asio::basic_socket_acceptor<boost::asio::ip::tcp>>& acceptor, const std::string& path, EventHandler* eventHandler) = delete; + + TgWebhookTcpServer(unsigned short port, const std::string& path, const EventHandler* eventHandler) : + TgWebhookServer(std::shared_ptr<boost::asio::basic_socket_acceptor<boost::asio::ip::tcp>>(new boost::asio::ip::tcp::acceptor(_ioService, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))), path, eventHandler) + { + } + + TgWebhookTcpServer(const std::string& path, const Bot& bot) : TgWebhookTcpServer(path, &bot.getEventHandler()) { + } +}; + +} + +#endif //TGBOT_TGWEBHOOKTCPSERVER_H diff --git a/src/tgbot/Url.cpp b/src/tgbot/net/Url.cpp index 94b9004..7424849 100644 --- a/src/tgbot/Url.cpp +++ b/src/tgbot/net/Url.cpp @@ -30,6 +30,7 @@ Url::Url(const string& url) { bool isProtocolParsed = false; bool isHostParsed = false; bool isPathParsed = false; + bool isQueryParsed = false; for (size_t i = 0, count = url.length(); i < count; ++i) { char c = url[i]; @@ -44,20 +45,32 @@ Url::Url(const string& url) { } else if (!isHostParsed) { if (c == '/') { isHostParsed = true; - path += c; + path += '/'; } else if (c == '?') { isHostParsed = isPathParsed = true; + path += '/'; + } else if (c == '#') { + isHostParsed = isPathParsed = isQueryParsed = true; + path += '/'; } else { host += c; } } else if (!isPathParsed) { if (c == '?') { isPathParsed = true; + } else if (c == '#') { + isPathParsed = isQueryParsed = true; } else { path += c; } + } else if (!isQueryParsed) { + if (c == '#') { + isQueryParsed = true; + } else { + query += c; + } } else { - query += c; + fragment += c; } } } diff --git a/src/tgbot/Url.h b/src/tgbot/net/Url.h index de5a7d2..39ae348 100644 --- a/src/tgbot/Url.h +++ b/src/tgbot/net/Url.h @@ -36,6 +36,7 @@ public: std::string host; std::string path; std::string query; + std::string fragment; }; } diff --git a/src/tgbot/tools/StringTools.cpp b/src/tgbot/tools/StringTools.cpp index 7a8b740..de3bbb8 100644 --- a/src/tgbot/tools/StringTools.cpp +++ b/src/tgbot/tools/StringTools.cpp @@ -24,6 +24,7 @@ #include <stdlib.h> #include <iomanip> +#include <stdio.h> using namespace std; @@ -46,15 +47,15 @@ bool startsWith(const string& str1, const string& str2) { bool endsWith(const string& str1, const string& str2) { string::const_iterator it1(str1.end()); - string::const_iterator start1(str1.begin()); - string::const_iterator it2(str2.begin()); - string::const_iterator end2(str2.end()); - while (it1 != start1 && it2 != end2) { + string::const_iterator begin1(str1.begin()); + string::const_iterator it2(str2.end()); + string::const_iterator begin2(str2.begin()); + while (it1 != begin1 && it2 != begin2) { if (*it1 != *it2) { return false; } --it1; - ++it2; + --it2; } return true; } @@ -78,15 +79,13 @@ string generateRandomString(size_t length) { } string urlEncode(const string& value) { - static const string legitPunctuation = "-_.~!*()'"; + static const string legitPunctuation = "-_.~"; ostringstream result; result.fill('0'); result << hex; for (const char& c : value) { if (isalnum(c) || legitPunctuation.find(c) != legitPunctuation.npos) { result << c; - } else if (c == ' ') { - result << '+'; } else { result << '%' << setw(2) << int((unsigned char) c); } @@ -95,4 +94,20 @@ string urlEncode(const string& value) { return result.str(); } +string urlDecode(const string& value) { + string result; + for (size_t i = 0, count = value.length(); i < count; ++i) { + const char c = value[i]; + if (c == '%') { + int t = 0; + sscanf(value.substr(i + 1, 2).c_str(), "%x", &t); + result += (char) t; + i += 2; + } else { + result += c; + } + } + return result; +} + } diff --git a/src/tgbot/tools/StringTools.h b/src/tgbot/tools/StringTools.h index 0c94338..37ab858 100644 --- a/src/tgbot/tools/StringTools.h +++ b/src/tgbot/tools/StringTools.h @@ -34,6 +34,7 @@ bool endsWith(const std::string& str1, const std::string& str2); void split(const std::string& str, char delimiter, std::vector<std::string>& dest); std::string generateRandomString(size_t length); std::string urlEncode(const std::string& value); +std::string urlDecode(const std::string& value); inline std::vector<std::string> split(const std::string& str, char delimiter) { std::vector<std::string> result; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..4139418 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,12 @@ +set(TEST_SRC_LIST + main.cpp + utils.cpp + tgbot/net/Url.cpp + tgbot/net/HttpParser.cpp + tgbot/tools/StringTools.cpp +) + +include_directories("${PROJECT_SOURCE_DIR}/test") +add_executable(${PROJECT_NAME}_test ${TEST_SRC_LIST}) +target_link_libraries(${PROJECT_NAME}_test TgBot) +add_test(${PROJECT_NAME}_test ${PROJECT_NAME}_test)
\ No newline at end of file diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000..e4b12bf --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2015 Oleg Morozenkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE TgBot + +#include <boost/test/unit_test.hpp> diff --git a/test/tgbot/net/HttpParser.cpp b/test/tgbot/net/HttpParser.cpp new file mode 100644 index 0000000..57ba187 --- /dev/null +++ b/test/tgbot/net/HttpParser.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2015 Oleg Morozenkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <boost/test/unit_test.hpp> + +#include <tgbot/net/HttpParser.h> + +#include "utils.h" + +using namespace std; +using namespace TgBot; + +BOOST_AUTO_TEST_SUITE(tHttpParser) + +BOOST_AUTO_TEST_CASE(generateRequest) { + vector<HttpReqArg> args = { HttpReqArg("email", "test@example.com"), HttpReqArg("text", "Hello, world!") }; + string t = HttpParser::getInstance().generateRequest(Url("http://example.com/index.html"), args, true); + string e = "" + "POST /index.html HTTP/1.1\r\n" + "Host: example.com\r\n" + "Connection: keep-alive\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: 49\r\n" + "\r\n" + "email=test%40example.com&text=Hello%2c%20world%21"; + BOOST_CHECK_MESSAGE(t == e, diff(t, e)); +} + +BOOST_AUTO_TEST_CASE(generateMultipartFormData) { + vector<HttpReqArg> args = { HttpReqArg("email", "test@example.com"), HttpReqArg("text", "Hello, world!", true) }; + string boundary = HttpParser::getInstance().generateMultipartBoundary(args); + string t = HttpParser::getInstance().generateMultipartFormData(args, boundary); + string e = "" + "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"email\"\r\n" + "\r\n" + "test@example.com\r\n" + "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"text\"\r\n" + "Content-Type: text/plain\r\n" + "\r\n" + "Hello, world!\r\n"; + BOOST_CHECK_MESSAGE(t == e, diff(t, e)); +} + +BOOST_AUTO_TEST_CASE(generateWwwFormUrlencoded) { + vector<HttpReqArg> args = { HttpReqArg("email", "test@example.com"), HttpReqArg("text", "Hello, world!") }; + string t = HttpParser::getInstance().generateWwwFormUrlencoded(args); + string e = "email=test%40example.com&text=Hello%2c%20world%21"; + BOOST_CHECK_MESSAGE(t == e, diff(t, e)); +} + +BOOST_AUTO_TEST_CASE(generateResponse) { + string t = HttpParser::getInstance().generateResponse("testdata"); + string e = "" + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 8\r\n" + "\r\n" + "testdata"; + BOOST_CHECK_MESSAGE(t == e, diff(t, e)); +} + +BOOST_AUTO_TEST_CASE(parseRequest) { + string data = "" + "POST /index.html HTTP/1.1\r\n" + "Host: example.com\r\n" + "Connection: keep-alive\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 8\r\n" + "\r\n" + "testdata"; + + map<string, string> tHeaders; + string t = HttpParser::getInstance().parseRequest(data, tHeaders); + + map<string, string> eHeaders = { + { "method", "POST" }, + { "path", "/index.html" }, + { "host", "example.com" }, + { "connection", "keep-alive" }, + { "content-type", "text/plain" }, + { "content-length", "8" } + }; + string e = "testdata"; + + BOOST_CHECK_MESSAGE(t == e, diff(t, e)); + BOOST_CHECK_MESSAGE(tHeaders == eHeaders, diff(tHeaders, eHeaders, [](const pair<const string, string>& item) -> string { + return item.first + '=' + item.second; + })); +} + +BOOST_AUTO_TEST_CASE(parseResponse) { + string data = "" + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 8\r\n" + "\r\n" + "testdata"; + + map<string, string> tHeaders; + string t = HttpParser::getInstance().parseResponse(data, tHeaders); + + map<string, string> eHeaders = { + { "status", "200" }, + { "content-type", "text/plain" }, + { "content-length", "8" } + }; + string e = "testdata"; + + BOOST_CHECK_MESSAGE(t == e, diff(t, e)); + BOOST_CHECK_MESSAGE(tHeaders == eHeaders, diff(tHeaders, eHeaders, [](const pair<const string, string>& item) -> string { + return item.first + '=' + item.second; + })); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/tgbot/net/Url.cpp b/test/tgbot/net/Url.cpp new file mode 100644 index 0000000..0974ab6 --- /dev/null +++ b/test/tgbot/net/Url.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2015 Oleg Morozenkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <boost/test/unit_test.hpp> + +#include <tgbot/net/Url.h> + +using namespace TgBot; + +BOOST_AUTO_TEST_SUITE(tUrl) + +BOOST_AUTO_TEST_CASE(parsingUrlNoPath) { + Url t("https://test.example.com?test=123&123=test#title"); + BOOST_CHECK_EQUAL(t.protocol, "https"); + BOOST_CHECK_EQUAL(t.host, "test.example.com"); + BOOST_CHECK_EQUAL(t.path, "/"); + BOOST_CHECK_EQUAL(t.query, "test=123&123=test"); + BOOST_CHECK_EQUAL(t.fragment, "title"); +} + +BOOST_AUTO_TEST_CASE(parsingUrlNoPathAndQuery) { + Url t("https://test.example.com#title"); + BOOST_CHECK_EQUAL(t.protocol, "https"); + BOOST_CHECK_EQUAL(t.host, "test.example.com"); + BOOST_CHECK_EQUAL(t.path, "/"); + BOOST_CHECK_EQUAL(t.query, ""); + BOOST_CHECK_EQUAL(t.fragment, "title"); +} + +BOOST_AUTO_TEST_CASE(parsingUrlFull) { + Url t("https://test.example.com/example-page/index.html?test=123&123=test#title"); + BOOST_CHECK_EQUAL(t.protocol, "https"); + BOOST_CHECK_EQUAL(t.host, "test.example.com"); + BOOST_CHECK_EQUAL(t.path, "/example-page/index.html"); + BOOST_CHECK_EQUAL(t.query, "test=123&123=test"); + BOOST_CHECK_EQUAL(t.fragment, "title"); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/tgbot/tools/StringTools.cpp b/test/tgbot/tools/StringTools.cpp new file mode 100644 index 0000000..c3bf709 --- /dev/null +++ b/test/tgbot/tools/StringTools.cpp @@ -0,0 +1,63 @@ +/* + * + * Copyright (c) 2015 Oleg Morozenkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <string> +#include <vector> + +#include <boost/test/unit_test.hpp> + +#include <tgbot/tools/StringTools.h> + +#include "utils.h" + +using namespace std; + +BOOST_AUTO_TEST_SUITE(tStringTools) + +BOOST_AUTO_TEST_CASE(startsWith) { + BOOST_CHECK(StringTools::startsWith("abc123", "abc")); + BOOST_CHECK(StringTools::startsWith("abc", "abc123")); +} + +BOOST_AUTO_TEST_CASE(endsWith) { + BOOST_CHECK(StringTools::endsWith("abc123", "123")); + BOOST_CHECK(StringTools::endsWith("123", "abc123")); +} + +BOOST_AUTO_TEST_CASE(split) { + BOOST_CHECK(StringTools::split("123 456 789", ' ') == vector<string>({"123", "456", "789"})); +} + +BOOST_AUTO_TEST_CASE(urlEncode) { + string t = StringTools::urlEncode("`1234567890-qwertyuiop[]\\asdfghjkl;'zxcvbnm,./~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:ZXCVBNM<>? "); + string e = "%601234567890-qwertyuiop%5b%5d%5casdfghjkl%3b%27zxcvbnm%2c.%2f~%21%40%23%24%25%5e%26%2a%28%29_%2bQWERTYUIOP%7b%7d%7cASDFGHJKL%3aZXCVBNM%3c%3e%3f%20"; + BOOST_CHECK_MESSAGE(t == e, diff(t, e)); +} + +BOOST_AUTO_TEST_CASE(urlDecode) { + string t = StringTools::urlDecode("%601234567890-qwertyuiop%5b%5d%5casdfghjkl%3b%27zxcvbnm%2c.%2f~%21%40%23%24%25%5e%26%2a%28%29_%2bQWERTYUIOP%7b%7d%7cASDFGHJKL%3aZXCVBNM%3c%3e%3f%20"); + string e = "`1234567890-qwertyuiop[]\\asdfghjkl;'zxcvbnm,./~!@#$%^&*()_+QWERTYUIOP{}|ASDFGHJKL:ZXCVBNM<>? "; + BOOST_CHECK_MESSAGE(t == e, diff(t, e)); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/utils.cpp b/test/utils.cpp new file mode 100644 index 0000000..9a9f48c7 --- /dev/null +++ b/test/utils.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2015 Oleg Morozenkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "utils.h" + +#include <sstream> + +using namespace std; +using namespace boost; + +string diff(const string& test, const string& expected) { + string result; + result += "\n*** BEGIN ***\n"; + + istringstream ss1(test); + istringstream ss2(expected); + + string s1, s2; + bool r1, r2; + size_t i = 0; + do { + r1 = getline(ss1, s1) ? true : false; + r2 = getline(ss2, s2) ? true : false; + if (r1 && r2 && s1 == s2) { + result += lexical_cast<string>(i); + result += " [=] "; + result += s1; + result += "\n"; + } else { + if (r1) { + result += lexical_cast<string>(i); + result += " [t] "; + result += s1; + result += "\n"; + } + if (r2) { + result += lexical_cast<string>(i); + result += " [e] "; + result += s2; + result += "\n"; + } + } + ++i; + } while (r1 || r2); + + result += "*** END ***\n"; + return result; +} diff --git a/test/utils.h b/test/utils.h new file mode 100644 index 0000000..d8e64e3 --- /dev/null +++ b/test/utils.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2015 Oleg Morozenkov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef TGBOT_UTILS_H +#define TGBOT_UTILS_H + +#include <string> + +#include <boost/lexical_cast.hpp> + +std::string diff(const std::string& test, const std::string& expected); + +template<typename T> +std::string diff(const T& test, const T& expected, std::string (*toStringFunc)(const typename T::value_type&)) { + std::string result; + result += "\n*** BEGIN *** Count: t="; + result += boost::lexical_cast<std::string>(test.size()); + result += " e="; + result += boost::lexical_cast<std::string>(expected.size()); + result += '\n'; + + typename T::const_iterator iter1 = test.begin(); + typename T::const_iterator end1 = test.end(); + typename T::const_iterator iter2 = expected.begin(); + typename T::const_iterator end2 = expected.end(); + bool r1, r2; + std::string s1, s2; + size_t i = 0; + do { + r1 = iter1 != end1; + r2 = iter2 != end2; + if (r1) { + s1 = toStringFunc(*iter1++); + } + if (r2) { + s2 = toStringFunc(*iter2++); + } + if (r1 && r2 && s1 == s2) { + result += boost::lexical_cast<std::string>(i); + result += " [=] "; + result += s1; + result += "\n"; + } else { + if (r1) { + result += boost::lexical_cast<std::string>(i); + result += " [t] "; + result += s1; + result += "\n"; + } + if (r2) { + result += boost::lexical_cast<std::string>(i); + result += " [e] "; + result += s2; + result += "\n"; + } + } + ++i; + } while (r1 || r2); + + result += "*** END ***\n"; + return result; +} + +#endif //TGBOT_UTILS_H |