From c47ec545d87e622aa950e653e4f91721d79bb049 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 21 May 2018 13:58:47 +0200 Subject: Add command to change avatar and channel name Fix bug where application crashes when deleting message, improve url parsing for messages --- include/Channel.hpp | 8 ++- include/Command.hpp | 3 + include/Message.hpp | 2 +- src/Channel.cpp | 49 +++++++++++++-- src/Command.cpp | 6 +- src/Message.cpp | 4 +- src/MessageBoard.cpp | 2 +- src/Text.cpp | 6 ++ src/main.cpp | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++- 9 files changed, 241 insertions(+), 13 deletions(-) diff --git a/include/Channel.hpp b/include/Channel.hpp index 26b5286..5f5699e 100644 --- a/include/Channel.hpp +++ b/include/Channel.hpp @@ -26,7 +26,9 @@ namespace dchat ADD_MESSAGE, EDIT_MESSAGE, DELETE_MESSAGE, - NICKNAME_CHANGE + NICKNAME_CHANGE, + CHANGE_AVATAR, + CHANGE_CHANNEL_NAME }; class Channel @@ -49,6 +51,7 @@ namespace dchat // If timestamp is 0, then current time is used void addLocalMessage(const std::string &msg, User *owner, u64 timestampSeconds = 0); void addLocalMessage(const std::string &msg, User *owner, u64 timestampSeconds, const odhtdb::Hash &id); + void addSystemMessage(const std::string &msg, bool plainText = true); void addMessage(const std::string &msg); void deleteLocalMessage(const odhtdb::Hash &id, const odhtdb::Signature::PublicKey &requestedByUser); void deleteMessage(const odhtdb::Hash &id, const odhtdb::Signature::PublicKey &requestedByUser); @@ -57,6 +60,9 @@ namespace dchat bool addUser(const odhtdb::Signature::PublicKey &userId, const odhtdb::DataView &groupId); void replaceLocalUser(OnlineLocalUser *newOnlineLocalUser); void changeNick(const std::string &newNick); + void setAvatar(const std::string &newAvatarUrl); + void setNameLocally(const std::string &name); + void setName(const std::string &name); void processEvent(const sf::Event &event, Cache &cache); void draw(sf::RenderWindow &window, Cache &cache); diff --git a/include/Command.hpp b/include/Command.hpp index fe8a947..fe08664 100644 --- a/include/Command.hpp +++ b/include/Command.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace dchat { @@ -13,5 +14,7 @@ namespace dchat public: static bool add(const std::string &cmd, CommandHandlerFunc handlerFunc); static bool call(const std::string &cmd, const std::vector &args); + + static const std::unordered_map& getCommands(); }; } diff --git a/include/Message.hpp b/include/Message.hpp index a2d67e8..2327a0b 100644 --- a/include/Message.hpp +++ b/include/Message.hpp @@ -18,7 +18,7 @@ namespace dchat }; // If timestamp is 0, then timestamp is not used - Message(User *user, const std::string &text, u64 timestampSeconds = 0); + Message(User *user, const std::string &text, u64 timestampSeconds = 0, bool plainText = false); const User *user; Text text; diff --git a/src/Channel.cpp b/src/Channel.cpp index 1ac34c0..5015975 100644 --- a/src/Channel.cpp +++ b/src/Channel.cpp @@ -118,12 +118,12 @@ namespace dchat return databaseNodeInfo; } - void Channel::addLocalMessage(const std::string &msg, User *owner, u64 timestampSeconds) + void Channel::addLocalMessage(const string &msg, User *owner, u64 timestampSeconds) { addLocalMessage(msg, owner, timestampSeconds, odhtdb::Hash()); } - void Channel::addLocalMessage(const std::string &msg, User *owner, u64 timestampSeconds, const odhtdb::Hash &id) + void Channel::addLocalMessage(const string &msg, User *owner, u64 timestampSeconds, const odhtdb::Hash &id) { assert(owner); if(timestampSeconds == 0) @@ -133,7 +133,13 @@ namespace dchat messageBoard.addMessage(new Message(owner, msg, timestampSeconds), id); } - void Channel::addMessage(const std::string &msg) + void Channel::addSystemMessage(const string &msg, bool plainText) + { + u64 timestampSeconds = time(NULL); + messageBoard.addMessage(new Message(&systemUser, msg, timestampSeconds, plainText), odhtdb::Hash()); + } + + void Channel::addMessage(const string &msg) { if(database && localUser->type == User::Type::ONLINE_LOCAL_USER) { @@ -222,7 +228,7 @@ namespace dchat addUserLocally(newOnlineLocalUser); } - void Channel::changeNick(const std::string &newNick) + void Channel::changeNick(const string &newNick) { if(database && localUser->type == User::Type::ONLINE_LOCAL_USER) { @@ -237,6 +243,41 @@ namespace dchat } } + void Channel::setAvatar(const string &newAvatarUrl) + { + if(database && localUser->type == User::Type::ONLINE_LOCAL_USER) + { + auto onlineLocalUser = static_cast(localUser); + + sibs::SafeSerializer serializer; + serializer.add(ChannelDataType::CHANGE_AVATAR); + serializer.add((u16)newAvatarUrl.size()); + serializer.add((const u8*)newAvatarUrl.data(), newAvatarUrl.size()); + + database->addData(databaseNodeInfo, onlineLocalUser->keyPair, odhtdb::DataView(serializer.getBuffer().data(), serializer.getBuffer().size())); + } + } + + void Channel::setNameLocally(const string &name) + { + this->name = name; + } + + void Channel::setName(const string &name) + { + if(database && localUser->type == User::Type::ONLINE_LOCAL_USER) + { + auto onlineLocalUser = static_cast(localUser); + + sibs::SafeSerializer serializer; + serializer.add(ChannelDataType::CHANGE_CHANNEL_NAME); + serializer.add((u16)name.size()); + serializer.add((const u8*)name.data(), name.size()); + + database->addData(databaseNodeInfo, onlineLocalUser->keyPair, odhtdb::DataView(serializer.getBuffer().data(), serializer.getBuffer().size())); + } + } + void Channel::processEvent(const sf::Event &event, Cache &cache) { chatbar.processEvent(event, cache, this); diff --git a/src/Command.cpp b/src/Command.cpp index a28cd42..fb4c0cb 100644 --- a/src/Command.cpp +++ b/src/Command.cpp @@ -1,5 +1,4 @@ #include "../include/Command.hpp" -#include using namespace std; @@ -34,4 +33,9 @@ namespace dchat } return false; } + + const std::unordered_map& Command::getCommands() + { + return commandHandlerFuncs; + } } diff --git a/src/Message.cpp b/src/Message.cpp index a408aee..1b42626 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -7,9 +7,9 @@ using namespace std; namespace dchat { - Message::Message(User *_user, const std::string &_text, u64 _timestampSeconds) : + Message::Message(User *_user, const std::string &_text, u64 _timestampSeconds, bool plainText) : user(_user), - text(sf::String::fromUtf8(_text.begin(), _text.end()), ResourceCache::getFont("fonts/Roboto-Regular.ttf"), 18 * Settings::getScaling(), 0.0f, false), + text(sf::String::fromUtf8(_text.begin(), _text.end()), ResourceCache::getFont("fonts/Roboto-Regular.ttf"), 18 * Settings::getScaling(), 0.0f, plainText), timestampSeconds(_timestampSeconds), type(Type::REGULAR) { diff --git a/src/MessageBoard.cpp b/src/MessageBoard.cpp index c75573a..0152cb5 100644 --- a/src/MessageBoard.cpp +++ b/src/MessageBoard.cpp @@ -326,7 +326,7 @@ namespace dchat if(onlineLocalUser && event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Button::Right) openContextMenu = true; - if(visibleMessageStartIndex != -1) + if(visibleMessageStartIndex != -1 && visibleMessageStartIndex < messages.size() && visibleMessageEndIndex < messages.size()) { //printf("visibleMessageStartIndex: %u, visibleMessageEndIndex: %u\n", visibleMessageStartIndex, visibleMessageEndIndex); for(usize i = visibleMessageStartIndex; i <= visibleMessageEndIndex; ++i) diff --git a/src/Text.cpp b/src/Text.cpp index 0e24e73..5c8db41 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -170,14 +170,20 @@ namespace dchat size_t urlStart = textElementStr.find(StringViewUtf32(urlStr.getData(), urlStr.getSize()), offset); if(urlStart != -1) { + bool foundDot = false; offset = urlStart + urlStr.getSize(); while(offset < textElementStr.size) { if(isspace(textElementStr[offset])) break; + else if(textElementStr[offset] == '.') + foundDot = true; ++offset; } + if(!foundDot) + return -1; + StringViewUtf32 beforeUrlStr(textElementStr.data + stringStart, urlStart - stringStart); newTextElements.push_back({ beforeUrlStr, TextElement::Type::TEXT }); diff --git a/src/main.cpp b/src/main.cpp index 8c13c80..ecfd058 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,6 +47,65 @@ static void channelChangeUserNickname(Channel *channel, const StringView data, c // We dont care if there is more data to read (malicious packet), we already got all the data we need } +static bool looksLikeUrl(const string &str) +{ + if(str.find('.') == string::npos) + return false; + + if(str.size() >= 7 && strncmp(str.c_str(), "http://", 7) == 0) + return true; + + if(str.size() >= 8 && strncmp(str.c_str(), "https://", 8) == 0) + return true; + + return false; +} + +static void channelChangeUserAvatar(Channel *channel, const StringView data, const odhtdb::Signature::PublicKey &userPublicKey) +{ + auto user = channel->getUserByPublicKey(userPublicKey); + if(!user) + { + fprintf(stderr, "Avatar change: user with public key %s not found in channel %s\n", userPublicKey.toString().c_str(), channel->getName().c_str()); + return; + } + + sibs::SafeDeserializer deserializer((const u8*)data.data, data.size); + u16 avatarLength = deserializer.extract(); + if(avatarLength >= 10 && avatarLength <= 512) + { + string avatarUrl; + avatarUrl.resize(avatarLength); + deserializer.extract((u8*)&avatarUrl[0], avatarLength); + if(looksLikeUrl(avatarUrl)) + { + user->avatarUrl = move(avatarUrl); + } + } + // We dont care if there is more data to read (malicious packet), we already got all the data we need +} + +static void channelChangeChannelName(Channel *channel, const StringView data, const odhtdb::Signature::PublicKey &userPublicKey) +{ + auto user = channel->getUserByPublicKey(userPublicKey); + if(!user) + { + fprintf(stderr, "Channel change name: user with public key %s not found in channel %s\n", userPublicKey.toString().c_str(), channel->getName().c_str()); + return; + } + + sibs::SafeDeserializer deserializer((const u8*)data.data, data.size); + u16 channelNameLength = deserializer.extract(); + if(channelNameLength > 0 && channelNameLength <= 32) + { + string channelName; + channelName.resize(channelNameLength); + deserializer.extract((u8*)&channelName[0], channelNameLength); + channel->setNameLocally(channelName); + } + // We dont care if there is more data to read (malicious packet), we already got all the data we need +} + static void channelAddStoredMessage(Channel *channel, const odhtdb::Hash &requestHash, const odhtdb::Signature::PublicKey &creatorPublicKey, const StringView decryptedObject, u64 timestamp) { User *user = channel->getUserByPublicKey(creatorPublicKey); @@ -97,6 +156,30 @@ static void channelAddStoredMessage(Channel *channel, const odhtdb::Hash &reques } break; } + case ChannelDataType::CHANGE_AVATAR: + { + try + { + channelChangeUserAvatar(channel, decryptedData, creatorPublicKey); + } + catch(sibs::DeserializeException &e) + { + fprintf(stderr, "Failed to deserialize avatar change\n"); + } + break; + } + case ChannelDataType::CHANGE_CHANNEL_NAME: + { + try + { + channelChangeChannelName(channel, decryptedData, creatorPublicKey); + } + catch(sibs::DeserializeException &e) + { + fprintf(stderr, "Failed to deserialize channel name change\n"); + } + break; + } default: fprintf(stderr, "Got unexpected channel data type: %u\n", channelDataType); break; @@ -242,9 +325,9 @@ int main(int argc, char **argv) }; database = new odhtdb::Database("bootstrap.ring.cx", 4222, Cache::getDchatDir(), callbackFuncs); - auto addSystemMessage = [&lastFocusedTimer](const std::string &msg) + auto addSystemMessage = [&lastFocusedTimer](const std::string &msg, bool plainText = true) { - Channel::getCurrent()->addLocalMessage(msg, Channel::getCurrent()->getSystemUser()); + Channel::getCurrent()->addSystemMessage(msg, plainText); lastFocusedTimer.restart(); }; @@ -634,7 +717,7 @@ int main(int argc, char **argv) msg += " "; msg += bind.second; } - addSystemMessage(msg); + addSystemMessage(msg, false); }); // Change nick of current user in current channel @@ -673,11 +756,96 @@ int main(int argc, char **argv) addSystemMessage(msg); }); + // Change avatar of current user in current channel + Command::add("avatar", [&loggedIn, &offlineChannel, addSystemMessage](const vector &args) + { + if(args.size() != 1) + { + string errMsg = "Expected 1 argument for command avatar, got "; + errMsg += to_string(args.size()); + errMsg += " argument(s)"; + addSystemMessage(errMsg); + return; + } + + if(!loggedIn) + { + addSystemMessage("You need to be logged in to change your avatar"); + return; + } + + if(Channel::getCurrent() == &offlineChannel) + { + addSystemMessage("You need to be in a channel to change your avatar"); + return; + } + + if(args[0].size() < 10 || args[0].size() > 512) + { + addSystemMessage("Invalid avatar url size, expected to be between 10 and 512 bytes"); + return; + } + + if(looksLikeUrl(args[0])) + { + Channel::getCurrent()->setAvatar(args[0]); + addSystemMessage("Your avatar has been changed (Note: max avatar size is 1 Mb, if your avatar is larger then it will not be visible)"); + } + else + { + addSystemMessage("Avatar url needs to start with either http:// or https:// and include a dot"); + } + }); + + // Change name of the current channel + Command::add("channelname", [&loggedIn, &offlineChannel, addSystemMessage](const vector &args) + { + if(args.size() != 1) + { + string errMsg = "Expected 1 argument for command channelname, got "; + errMsg += to_string(args.size()); + errMsg += " argument(s)"; + addSystemMessage(errMsg); + return; + } + + if(!loggedIn) + { + addSystemMessage("You need to be logged in to change channel name"); + return; + } + + if(Channel::getCurrent() == &offlineChannel) + { + addSystemMessage("You need to be in a channel to change channel name"); + return; + } + + if(args[0].size() == 0 || args[0].size() > 32) + { + addSystemMessage("Channel name has to be between 1 and 32 bytes long"); + return; + } + + Channel::getCurrent()->setName(args[0]); + string msg = "Channel name has been changed to "; + msg += args[0]; + addSystemMessage(msg); + }); + Command::add("clearcache", [&database](const vector &args) { printf("Cleared cache (%d bytes)\n", database->clearCache()); }); + string commandsMsg = "Available commands: "; + for(const auto &commandIt : Command::getCommands()) + { + commandsMsg += "\n/"; + commandsMsg += commandIt.first; + } + addSystemMessage(commandsMsg); + sf::Clock frameTimer; while (window.isOpen()) -- cgit v1.2.3