]> git.netwichtig.de Git - user/henk/code/inspircd.git/commitdiff
Implement IRCv3 message tag support.
authorPeter Powell <petpow@saberuk.com>
Mon, 13 Aug 2018 19:17:46 +0000 (20:17 +0100)
committerPeter Powell <petpow@saberuk.com>
Mon, 13 Aug 2018 20:51:11 +0000 (21:51 +0100)
Co-authored-by: Attila Molnar <attilamolnar@hush.com>
62 files changed:
include/channels.h
include/clientprotocol.h [new file with mode: 0644]
include/clientprotocolevent.h [new file with mode: 0644]
include/clientprotocolmsg.h [new file with mode: 0644]
include/command_parse.h
include/configreader.h
include/ctables.h
include/event.h
include/inspircd.h
include/message.h
include/mode.h
include/modechange.h
include/modules.h
include/modules/cap.h
include/modules/ircv3.h
include/stdalgo.h
include/typedefs.h
include/users.h
src/channels.cpp
src/clientprotocol.cpp [new file with mode: 0644]
src/command_parse.cpp
src/configreader.cpp
src/coremods/core_channel/cmd_invite.cpp
src/coremods/core_channel/core_channel.cpp
src/coremods/core_oper/cmd_die.cpp
src/coremods/core_oper/cmd_kill.cpp
src/coremods/core_oper/core_oper.h
src/coremods/core_privmsg.cpp
src/coremods/core_reloadmodule.cpp
src/coremods/core_serialize_rfc.cpp [new file with mode: 0644]
src/coremods/core_user/core_user.cpp
src/coremods/core_wallops.cpp
src/inspircd.cpp
src/mode.cpp
src/modules.cpp
src/modules/m_alias.cpp
src/modules/m_auditorium.cpp
src/modules/m_cap.cpp
src/modules/m_chanhistory.cpp
src/modules/m_chanlog.cpp
src/modules/m_cloaking.cpp
src/modules/m_conn_waitpong.cpp
src/modules/m_delayjoin.cpp
src/modules/m_hostcycle.cpp
src/modules/m_ircv3.cpp
src/modules/m_ircv3_capnotify.cpp
src/modules/m_ircv3_chghost.cpp
src/modules/m_ircv3_echomessage.cpp
src/modules/m_ircv3_invitenotify.cpp
src/modules/m_knock.cpp
src/modules/m_ldapoper.cpp
src/modules/m_passforward.cpp
src/modules/m_samode.cpp
src/modules/m_sasl.cpp
src/modules/m_showfile.cpp
src/modules/m_spanningtree/main.cpp
src/modules/m_spanningtree/main.h
src/modules/m_spanningtree/treesocket2.cpp
src/modules/m_sqloper.cpp
src/modules/m_timedbans.cpp
src/usermanager.cpp
src/users.cpp

index 365cdeabd1a2c1f09a7dd646ce82fb0a11ab0dbf..0557a5898b5974b74b7334eddc7f28f97f5d49a5 100644 (file)
@@ -232,75 +232,22 @@ class CoreExport Channel : public Extensible
         */
        Membership* ForceJoin(User* user, const std::string* privs = NULL, bool bursting = false, bool created_by_local = false);
 
-       /** Write to a channel, from a user, using va_args for text
-        * @param user User whos details to prefix the line with
-        * @param text A printf-style format string which builds the output line without prefix
-        * @param ... Zero or more POD types
-        */
-       void WriteChannel(User* user, const char* text, ...) CUSTOM_PRINTF(3, 4);
-
-       /** Write to a channel, from a user, using std::string for text
-        * @param user User whos details to prefix the line with
-        * @param text A std::string containing the output line without prefix
-        */
-       void WriteChannel(User* user, const std::string &text);
-
-       /** Write to a channel, from a server, using va_args for text
-        * @param ServName Server name to prefix the line with
-        * @param text A printf-style format string which builds the output line without prefix
-        * @param ... Zero or more POD type
-        */
-       void WriteChannelWithServ(const std::string& ServName, const char* text, ...) CUSTOM_PRINTF(3, 4);
-
-       /** Write to a channel, from a server, using std::string for text
-        * @param ServName Server name to prefix the line with
-        * @param text A std::string containing the output line without prefix
-        */
-       void WriteChannelWithServ(const std::string& ServName, const std::string &text);
-
-       /** Write to all users on a channel except a specific user, using va_args for text.
-        * Internally, this calls WriteAllExcept().
-        * @param user User whos details to prefix the line with, and to omit from receipt of the message
-        * @param serversource If this parameter is true, use the local server name as the source of the text, otherwise,
-        * use the nick!user\@host of the user.
-        * @param status The status of the users to write to, e.g. '@' or '%'. Use a value of 0 to write to everyone
-        * @param text A printf-style format string which builds the output line without prefix
-        * @param ... Zero or more POD type
-        */
-       void WriteAllExceptSender(User* user, bool serversource, char status, const char* text, ...) CUSTOM_PRINTF(5, 6);
-
-       /** Write to all users on a channel except a list of users, using va_args for text
-        * @param user User whos details to prefix the line with, and to omit from receipt of the message
-        * @param serversource If this parameter is true, use the local server name as the source of the text, otherwise,
-        * use the nick!user\@host of the user.
-        * @param status The status of the users to write to, e.g. '@' or '%'. Use a value of 0 to write to everyone
-        * @param except_list A list of users NOT to send the text to
-        * @param text A printf-style format string which builds the output line without prefix
-        * @param ... Zero or more POD type
-        */
-       void WriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const char* text, ...) CUSTOM_PRINTF(6, 7);
-
-       /** Write to all users on a channel except a specific user, using std::string for text.
-        * Internally, this calls WriteAllExcept().
-        * @param user User whos details to prefix the line with, and to omit from receipt of the message
-        * @param serversource If this parameter is true, use the local server name as the source of the text, otherwise,
-        * use the nick!user\@host of the user.
+       /** Write to all users on a channel except some users
+        * @param protoev Event to send, may contain any number of messages.
         * @param status The status of the users to write to, e.g. '@' or '%'. Use a value of 0 to write to everyone
         * @param text A std::string containing the output line without prefix
+        * @param except_list List of users not to send to
         */
-       void WriteAllExceptSender(User* user, bool serversource, char status, const std::string& text);
+       void Write(ClientProtocol::Event& protoev, char status = 0, const CUList& except_list = CUList());
 
-       /** Write to all users on a channel except a list of users, using std::string for text
-        * @param user User whos details to prefix the line with, and to omit from receipt of the message
-        * @param serversource If this parameter is true, use the local server name as the source of the text, otherwise,
-        * use the nick!user\@host of the user.
+       /** Write to all users on a channel except some users.
+        * @param protoevprov Protocol event provider for the message.
+        * @param msg Message to send.
         * @param status The status of the users to write to, e.g. '@' or '%'. Use a value of 0 to write to everyone
-        * @param except_list A list of users NOT to send the text to
         * @param text A std::string containing the output line without prefix
+        * @param except_list List of users not to send to
         */
-       void WriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const std::string& text);
-       /** Write a line of text that already includes the source */
-       void RawWriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const std::string& text);
+       void Write(ClientProtocol::EventProvider& protoevprov, ClientProtocol::Message& msg, char status = 0, const CUList& except_list = CUList());
 
        /** Return the channel's modes with parameters.
         * @param showkey If this is set to true, the actual key is shown,
diff --git a/include/clientprotocol.h b/include/clientprotocol.h
new file mode 100644 (file)
index 0000000..a3efcf9
--- /dev/null
@@ -0,0 +1,726 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ *   Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd.  InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#pragma once
+
+#include "event.h"
+
+namespace ClientProtocol
+{
+       class EventHook;
+       class MessageSource;
+       struct RFCEvents;
+       struct ParseOutput;
+       class TagSelection;
+}
+
+/** Contains a message parsed from wire format.
+ * Used by Serializer::Parse().
+ */
+struct ClientProtocol::ParseOutput
+{
+       /** Command name, must not be empty.
+        */
+       std::string cmd;
+
+       /** Parameter list, may be empty.
+        */
+       ClientProtocol::ParamList params;
+
+       /** Message tags, may be empty.
+        */
+       ClientProtocol::TagMap tags;
+};
+
+/** A selection of zero or more tags in a TagMap.
+ */
+class ClientProtocol::TagSelection
+{
+       std::bitset<64> selection;
+
+ public:
+       /** Check if a tag is selected.
+        * @param tags TagMap the tag is in. The TagMap must contain the same tags as it had when the tag
+        * was selected with Select(), otherwise the result is not meaningful.
+        * @param it Iterator to the tag to check.
+        * @return True if the tag is selected, false otherwise.
+        */
+       bool IsSelected(const TagMap& tags, TagMap::const_iterator it) const
+       {
+               const size_t index = std::distance(tags.begin(), it);
+               return ((index < selection.size()) && (selection[index]));
+       }
+
+       /** Select a tag.
+        * @param tags TagMap the tag is in. This parameter must be the same every time the method is called.
+        * The TagMap must not be altered otherwise the results of IsSelected() is not meaningful.
+        * @param it Iterator to the tag to mark as selected.
+        */
+       void Select(const TagMap& tags, TagMap::const_iterator it)
+       {
+               const size_t index = std::distance(tags.begin(), it);
+               if (index < selection.size())
+                       selection[index] = true;
+       }
+
+       /** Check if a TagSelection is equivalent to this object.
+        * @param other Other TagSelection object to compare this with.
+        * @return True if the objects are equivalent, false if they aren't.
+        */
+       bool operator==(const TagSelection& other) const
+       {
+               return (this->selection == other.selection);
+       }
+};
+
+class ClientProtocol::MessageSource
+{
+       User* sourceuser;
+       const std::string* sourcestr;
+
+ public:
+       /** Constructor, sets the source to be the full host of a user or sets it to be nothing.
+        * The actual source string when serializing will be obtained from User::GetFullHost() if the user is non-NULL.
+        * @param Sourceuser User to set as source of the message. If NULL, the message won't have a source when serialized.
+        * Optional, defaults to NULL.
+        */
+       MessageSource(User* Sourceuser = NULL)
+       {
+               SetSourceUser(Sourceuser);
+       }
+
+       /** Constructor, sets the source to the supplied string and optionally sets the source user.
+        * @param Sourcestr String to use as message source for serialization purposes. Must remain valid
+        * as long as this object is alive.
+        * @param Sourceuser User to set as source. Optional, defaults to NULL. It will not be used for serialization but
+        * if provided it may be used internally, for example to create message tags.
+        * Useful when the source string is synthesized but it is still related to a User.
+        */
+       MessageSource(const std::string& Sourcestr, User* Sourceuser = NULL)
+       {
+               SetSource(Sourcestr, Sourceuser);
+       }
+
+       /** Get the source of this message as a string.
+        * @return Pointer to the message source string or NULL if there is no source.
+        */
+       const std::string* GetSource() const
+       {
+               // Return string if there's one explicitly set
+               if (sourcestr)
+                       return sourcestr;
+               if (sourceuser)
+                       return &sourceuser->GetFullHost();
+               return NULL;
+       }
+
+       /** Get the source User.
+        * This shouldn't be used for serialization, use GetSource() for that.
+        * @return User pointer if the message has a source user, NULL otherwise.
+        */
+       User* GetSourceUser() const { return sourceuser; }
+
+       /** Set the source of this message to a User.
+        * See the one parameter constructor for a more detailed description.
+        * @param Sourceuser User to set as source.
+        */
+       void SetSourceUser(User* Sourceuser)
+       {
+               sourceuser = Sourceuser;
+               sourcestr = NULL;
+       }
+
+       /** Set the source string and optionally source user.
+        * See the two parameter constructor for a more detailed description.
+        * @param Sourcestr String source, to be used for serialization purposes. Must remain valid as long
+        * as this object is alive.
+        * @param Sourceuser Source user to set, optional.
+        */
+       void SetSource(const std::string& Sourcestr, User* Sourceuser = NULL)
+       {
+               sourcestr = &Sourcestr;
+               sourceuser = Sourceuser;
+       }
+
+       /** Copy the source from a MessageSource object.
+        * @param other MessageSource object to copy from.
+        */
+       void SetSource(const MessageSource& other)
+       {
+               sourcestr = other.sourcestr;
+               sourceuser = other.sourceuser;
+       }
+};
+
+/** Outgoing client protocol message.
+ * Represents a high level client protocol message which is turned into raw or wire format
+ * by a Serializer. Messages can be serialized into different format by different serializers.
+ *
+ * Messages are created on demand and are disposed of after they have been sent.
+ *
+ * All messages have a command name, a list of parameters and a map of tags, the last two can be empty.
+ * They also always have a source, see class MessageSource.
+ */
+class ClientProtocol::Message : public ClientProtocol::MessageSource
+{
+ public:
+       /** Contains information required to identify a specific version of a serialized message.
+        */
+       struct SerializedInfo
+       {
+               const Serializer* serializer;
+               TagSelection tagwl;
+
+               /** Constructor.
+                * @param Ser Serializer used to serialize the message.
+                * @param Tagwl Tag whitelist used to serialize the message.
+                */
+               SerializedInfo(const Serializer* Ser, const TagSelection& Tagwl)
+                       : serializer(Ser)
+                       , tagwl(Tagwl)
+               {
+               }
+
+               /** Check if a SerializedInfo object is equivalent to this object.
+                * @param other Other SerializedInfo object.
+                * @return True if other is equivalent to this object, false otherwise.
+                */
+               bool operator==(const SerializedInfo& other) const
+               {
+                       return ((serializer == other.serializer) && (tagwl == other.tagwl));
+               }
+       };
+
+       class Param
+       {
+               const std::string* ptr;
+               insp::aligned_storage<std::string> str;
+               bool owned;
+
+               void InitFrom(const Param& other)
+               {
+                       owned = other.owned;
+                       if (owned)
+                               new(str) std::string(*other.str);
+                       else
+                               ptr = other.ptr;
+               }
+
+        public:
+               operator const std::string&() const { return (owned ? *str : *ptr); }
+
+               Param()
+                       : ptr(NULL)
+                       , owned(false)
+               {
+               }
+
+               Param(const std::string& s)
+                       : ptr(&s)
+                       , owned(false)
+               {
+               }
+
+               Param(int, const char* s)
+                       : owned(true)
+               {
+                       new(str) std::string(s);
+               }
+
+               Param(int, const std::string& s)
+                       : owned(true)
+               {
+                        new(str) std::string(s);
+               }
+
+               Param(const Param& other)
+               {
+                       InitFrom(other);
+               }
+
+               ~Param()
+               {
+                       using std::string;
+                       if (owned)
+                               str->~string();
+               }
+
+               Param& operator=(const Param& other)
+               {
+                       if (&other == this)
+                               return *this;
+
+                       using std::string;
+                       if (owned)
+                               str->~string();
+                       InitFrom(other);
+                       return *this;
+               }
+
+               bool IsOwned() const { return owned; }
+       };
+
+       typedef std::vector<Param> ParamList;
+
+ private:
+       typedef std::vector<std::pair<SerializedInfo, SerializedMessage> > SerializedList;
+
+       ParamList params;
+       TagMap tags;
+       std::string command;
+       bool msginit_done;
+       mutable SerializedList serlist;
+       bool sideeffect;
+
+ protected:
+       /** Set command string.
+        * @param cmd Command string to set.
+        */
+       void SetCommand(const char* cmd)
+       {
+               command.clear();
+               if (cmd)
+                       command = cmd;
+       }
+
+ public:
+       /** Constructor.
+        * @param cmd Command name, e.g. "JOIN", "NICK". May be NULL. If NULL, the command must be set
+        * with SetCommand() before the message is serialized.
+        * @param Sourceuser See the one parameter constructor of MessageSource for description.
+        */
+       Message(const char* cmd, User* Sourceuser = NULL)
+               : ClientProtocol::MessageSource(Sourceuser)
+               , command(cmd ? cmd : std::string())
+               , msginit_done(false)
+               , sideeffect(false)
+       {
+               params.reserve(8);
+               serlist.reserve(8);
+       }
+
+       /** Constructor.
+        * @param cmd Command name, e.g. "JOIN", "NICK". May be NULL. If NULL, the command must be set
+        * with SetCommand() before the message is serialized.
+        * @param Sourcestr See the two parameter constructor of MessageSource for description.
+        * Must remain valid as long as this object is alive.
+        * @param Sourceuser See the two parameter constructor of MessageSource for description.
+        */
+       Message(const char* cmd, const std::string& Sourcestr, User* Sourceuser = NULL)
+               : ClientProtocol::MessageSource(Sourcestr, Sourceuser)
+               , command(cmd ? cmd : std::string())
+               , msginit_done(false)
+               , sideeffect(false)
+       {
+               params.reserve(8);
+               serlist.reserve(8);
+       }
+
+       /** Get the parameters of this message.
+        * @return List of parameters.
+        */
+       const ParamList& GetParams() const { return params; }
+
+       /** Get a map of tags attached to this message.
+        * The map contains the tag providers that attached the tag to the message.
+        * @return Map of tags.
+        */
+       const TagMap& GetTags() const { return tags; }
+
+       /** Get the command string.
+        * @return Command string, e.g. "NICK", "001".
+        */
+       const char* GetCommand() const { return command.c_str(); }
+
+       /** Add a parameter to the parameter list.
+        * @param str String to add, will be copied.
+        */
+       void PushParam(const char* str) { params.push_back(Param(0, str)); }
+
+       /** Add a parameter to the parameter list.
+        * @param str String to add, will be copied.
+        */
+       void PushParam(const std::string& str) { params.push_back(Param(0, str)); }
+
+       /** Add a parameter to the parameter list.
+        * @param str String to add.
+        * The string will NOT be copied, it must remain alive until ClearParams() is called or until the object is destroyed.
+        */
+       void PushParamRef(const std::string& str) { params.push_back(str); }
+
+       /** Add a placeholder parameter to the parameter list.
+        * Placeholder parameters must be filled in later with actual parameters using ReplaceParam() or ReplaceParamRef().
+        */
+       void PushParamPlaceholder() { params.push_back(Param()); }
+
+       /** Replace a parameter or a placeholder that is already in the parameter list.
+        * @param index Index of the parameter to replace. Must be less than GetParams().size().
+        * @param str String to replace the parameter or placeholder with, will be copied.
+        */
+       void ReplaceParam(unsigned int index, const char* str) { params[index] = Param(0, str); }
+
+       /** Replace a parameter or a placeholder that is already in the parameter list.
+        * @param index Index of the parameter to replace. Must be less than GetParams().size().
+        * @param str String to replace the parameter or placeholder with, will be copied.
+        */
+       void ReplaceParam(unsigned int index, const std::string& str) { params[index] = Param(0, str); }
+
+       /** Replace a parameter or a placeholder that is already in the parameter list.
+        * @param index Index of the parameter to replace. Must be less than GetParams().size().
+        * @param str String to replace the parameter or placeholder with.
+        * The string will NOT be copied, it must remain alive until ClearParams() is called or until the object is destroyed.
+        */
+       void ReplaceParamRef(unsigned int index, const std::string& str) { params[index] = Param(str); }
+
+       /** Add a tag.
+        * @param tagname Raw name of the tag to use in the protocol.
+        * @param tagprov Provider of the tag.
+        * @param val Tag value. If empty no value will be sent with the tag.
+        * @param tagdata Tag provider specific data, will be passed to MessageTagProvider::ShouldSendTag(). Optional, defaults to NULL.
+        */
+       void AddTag(const std::string& tagname, MessageTagProvider* tagprov, const std::string& val, void* tagdata = NULL)
+       {
+               tags.insert(std::make_pair(tagname, MessageTagData(tagprov, val, tagdata)));
+       }
+
+       /** Add all tags in a TagMap to the tags in this message. Existing tags will not be overwritten.
+        * @param newtags New tags to add.
+        */
+       void AddTags(const ClientProtocol::TagMap& newtags)
+       {
+               tags.insert(newtags.begin(), newtags.end());
+       }
+
+       /** Get the message in a serialized form.
+        * @param serializeinfo Information about which exact serialized form of the message is the caller asking for
+        * (which serializer to use and which tags to include).
+        * @return Serialized message according to serializeinfo. The returned reference remains valid until the
+        * next call to this method.
+        */
+       const SerializedMessage& GetSerialized(const SerializedInfo& serializeinfo) const;
+
+       /** Clear the parameter list and tags.
+        */
+       void ClearParams()
+       {
+               msginit_done = false;
+               params.clear();
+               tags.clear();
+               InvalidateCache();
+       }
+
+       /** Remove all serialized messages.
+        * If a parameter is changed after the message has been sent at least once, this method must be called before
+        * serializing the message again to ensure the cache won't contain stale data.
+        */
+       void InvalidateCache()
+       {
+               serlist.clear();
+       }
+
+       void CopyAll()
+       {
+               size_t j = 0;
+               for (ParamList::iterator i = params.begin(); i != params.end(); ++i, j++)
+               {
+                       Param& curr = *i;
+                       if (!curr.IsOwned())
+                               ReplaceParam(j, curr);
+               }
+       }
+
+       void SetSideEffect(bool Sideeffect) { sideeffect = Sideeffect; }
+       bool IsSideEffect() const { return sideeffect; }
+
+       friend class Serializer;
+};
+
+/** Client protocol event class.
+ * All messages sent to a user must be part of an event. A single event may result in more than one protocol message
+ * being sent, for example a join event may result in a JOIN and a MODE protocol message sent to members of the channel
+ * if the joining user has some prefix modes set.
+ *
+ * Event hooks attached to a specific event can alter the messages sent for that event.
+ */
+class ClientProtocol::Event
+{
+       EventProvider* event;
+       Message* initialmsg;
+       const MessageList* initialmsglist;
+       bool eventinit_done;
+
+ public:
+       /** Constructor.
+        * @param protoevent Protocol event provider the event is an instance of.
+        */
+       Event(EventProvider& protoeventprov)
+               : event(&protoeventprov)
+               , initialmsg(NULL)
+               , initialmsglist(NULL)
+               , eventinit_done(false)
+       {
+       }
+
+       /** Constructor.
+        * @param protoevent Protocol event provider the event is an instance of.
+        * @param msg Message to include in this event by default.
+        */
+       Event(EventProvider& protoeventprov, ClientProtocol::Message& msg)
+               : event(&protoeventprov)
+               , initialmsg(&msg)
+               , initialmsglist(NULL)
+               , eventinit_done(false)
+       {
+       }
+
+       /** Set a single message as the initial message in the event.
+        * Modules may alter this later.
+        */
+       void SetMessage(Message* msg)
+       {
+               initialmsg = msg;
+               initialmsglist = NULL;
+       }
+
+       /** Set a list of messages as the initial messages in the event.
+        * Modules may alter this later.
+        */
+       void SetMessageList(const MessageList& msglist)
+       {
+               initialmsg = NULL;
+               initialmsglist = &msglist;
+       }
+
+       /** Get a list of messages to send to a user.
+        * The exact messages sent to a user are determined by the initial message(s) set and hooks.
+        * @param user User to get the messages for.
+        * @param messagelist List to fill in with messages to send to the user for the event
+        */
+       void GetMessagesForUser(LocalUser* user, MessageList& messagelist);
+};
+
+/** Base class for message tag providers.
+ * All message tags belong to a message tag provider. Message tag providers can populate messages
+ * with tags before the message is sent and they have the job of determining whether a user should
+ * get a message tag or be allowed to send one.
+ */
+class ClientProtocol::MessageTagProvider : public Events::ModuleEventListener
+{
+ public:
+       /** Constructor.
+        * @param mod Module owning the provider.
+        */
+       MessageTagProvider(Module* mod)
+               : Events::ModuleEventListener(mod, "event/messagetag")
+       {
+       }
+
+       /** Called when a message is ready to be sent to give the tag provider a chance to add tags to the message.
+        * To add tags call Message::AddTag(). If the provided tag or tags have been added already elsewhere or if the
+        * provider doesn't want its tag(s) to be on the message, the implementation doesn't have to do anything special.
+        * The default implementation does nothing.
+        * @param msg Message to be populated with tags.
+        */
+       virtual void OnClientProtocolPopulateTags(ClientProtocol::Message& msg)
+       {
+       }
+
+       /** Called for each tag that the server receives from a client in a message.
+        * @param user User that sent the tag.
+        * @param tagname Name of the tag.
+        * @param value Value of the tag, empty string if the tag has no value. May be modified.
+        * @return MOD_RES_ALLOW to accept the tag with the value in 'value', MOD_RES_DENY to reject the tag and act as if it wasn't sent,
+        * MOD_RES_PASSTHRU to make no decision. If no hooks accept a tag, the tag is rejected.
+        * The default implementation returns MOD_RES_PASSTHRU.
+        */
+       virtual ModResult OnClientProtocolProcessTag(LocalUser* user, const std::string& tagname, std::string& tagvalue)
+       {
+               return MOD_RES_PASSTHRU;
+       }
+
+       /** Called whenever a user is about to receive a message that has a tag attached which is provided by this provider
+        * to determine whether or not the user should get the tag.
+        * @param user User in question.
+        * @param tagdata Tag in question.
+        * @return True if the tag should be sent to the user, false otherwise.
+        */
+       virtual bool ShouldSendTag(LocalUser* user, const MessageTagData& tagdata) = 0;
+};
+
+/** Base class for client protocol event hooks.
+ * A protocol event hook is attached to a single event type. It has the ability to alter or block messages
+ * sent to users which belong to the event the hook is attached to.
+ */
+class ClientProtocol::EventHook : public Events::ModuleEventListener
+{
+ public:
+       static std::string GetEventName(const std::string& name)
+       {
+               return "event/protoevent_" + name;
+       }
+
+       /** Constructor.
+        * @param mod Owner of the hook.
+        * @param name Name of the event to hook.
+        * @param priority Priority of the hook. Determines the order in which hooks for the same event get called.
+        * Optional.
+        */
+       EventHook(Module* mod, const std::string& name, unsigned int priority = Events::ModuleEventListener::DefaultPriority)
+               : Events::ModuleEventListener(mod, GetEventName(name), priority)
+       {
+       }
+
+       /** Called exactly once before an event is sent to any user.
+        * The default implementation doesn't do anything.
+        * @param ev Event being sent to one or more users.
+        */
+       virtual void OnEventInit(const ClientProtocol::Event& ev)
+       {
+       }
+
+       /** Called for each user that may receive the event.
+        * The hook may alter the messages sent to the user and decide whether further hooks should be executed.
+        * @param user User the message list is being prepared to be sent to.
+        * @param ev Event associated with the messages.
+        * @param messagelist List of messages to send to the user. The hook can alter this in any way it sees fit, for example it can replace messages,
+        * add new messages, etc. The list must not be empty when the method returns.
+        * @return MOD_RES_PASSTHRU to run hooks after the called hook or if no hooks are after the called hook, send the messages in messagelist to the user.
+        * MOD_RES_DENY to not send any messages to the user and to not run other hooks,
+        * MOD_RES_ALLOW to send the messages in messagelist to the user and to not run other hooks.
+        */
+       virtual ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) = 0;
+};
+
+/** Event provider for client protocol events.
+ * Protocol event hooks can be attached to the instances of these providers. The core has event
+ * providers for most common IRC events defined in RFC1459.
+ */
+class ClientProtocol::EventProvider : public Events::ModuleEventProvider
+{
+ public:
+       /** Constructor.
+        * @param Mod Module that owns the event provider.
+        * @param eventname Name of the event this provider is for, e.g. "JOIN", "PART", "NUMERIC".
+        * Should match command name if applicable.
+        */
+       EventProvider(Module* Mod, const std::string& eventname)
+               : Events::ModuleEventProvider(Mod, ClientProtocol::EventHook::GetEventName(eventname))
+       {
+       }
+};
+
+/** Commonly used client protocol events.
+ * Available via InspIRCd::GetRFCEvents().
+ */
+struct ClientProtocol::RFCEvents
+{
+       EventProvider numeric;
+       EventProvider join;
+       EventProvider part;
+       EventProvider kick;
+       EventProvider quit;
+       EventProvider nick;
+       EventProvider mode;
+       EventProvider topic;
+       EventProvider privmsg;
+       EventProvider invite;
+       EventProvider ping;
+       EventProvider pong;
+       EventProvider error;
+
+       RFCEvents()
+               : numeric(NULL, "NUMERIC")
+               , join(NULL, "JOIN")
+               , part(NULL, "PART")
+               , kick(NULL, "KICK")
+               , quit(NULL, "QUIT")
+               , nick(NULL, "NICK")
+               , mode(NULL, "MODE")
+               , topic(NULL, "TOPIC")
+               , privmsg(NULL, "PRIVMSG")
+               , invite(NULL, "INVITE")
+               , ping(NULL, "PING")
+               , pong(NULL, "PONG")
+               , error(NULL, "ERROR")
+       {
+       }
+};
+
+/** Base class for client protocol serializers.
+ * A serializer has to implement serialization and parsing of protocol messages to/from wire format.
+ */
+class CoreExport ClientProtocol::Serializer : public DataProvider
+{
+       Events::ModuleEventProvider evprov;
+
+       /** Make a white list containing which tags a user should get.
+        * @param user User in question.
+        * @param tagmap Tag map that contains all possible tags.
+        * @return Whitelist of tags to send to the user.
+        */
+       TagSelection MakeTagWhitelist(LocalUser* user, const TagMap& tagmap) const;
+
+ public:
+       /** Constructor.
+        * @param mod Module owning the serializer.
+        * @param Name Name of the serializer, e.g. "rfc".
+        */
+       Serializer(Module* mod, const char* Name);
+
+       /** Handle a tag in a message being parsed. Call this method for each parsed tag.
+        * @param user User sending the tag.
+        * @param tagname Name of the tag.
+        * @param tagvalue Tag value, may be empty.
+        * @param tags TagMap to place the tag into, if it gets accepted.
+        * @return True if no error occured, false if the tag name is invalid or if this tag already exists.
+        */
+       bool HandleTag(LocalUser* user, const std::string& tagname, std::string& tagvalue, TagMap& tags) const;
+
+       /** Serialize a message for a user.
+        * @param user User to serialize the message for.
+        * @param msg Message to serialize.
+        * @return Raw serialized message, only containing the appropriate tags for the user.
+        * The reference is guaranteed to be valid as long as the Message object is alive and until the same
+        * Message is serialized for another user.
+        */
+       const SerializedMessage& SerializeForUser(LocalUser* user, Message& msg);
+
+       /** Serialize a high level protocol message into wire format.
+        * @param msg High level message to serialize. Contains all necessary information about the message, including all possible tags.
+        * @param tagwl Message tags to include in the serialized message. Tags attached to the message but not included in the whitelist must not
+        * appear in the output. This is because each user may get a different set of tags for the same message.
+        * @return Protocol message in wire format. Must contain message delimiter as well, if any (e.g. CRLF for RFC1459).
+        */
+       virtual std::string Serialize(const Message& msg, const TagSelection& tagwl) const = 0;
+
+       /** Parse a protocol message from wire format.
+        * @param user Source of the message.
+        * @param line Raw protocol message.
+        * @param parseoutput Output of the parser.
+        * @return True if the message was parsed successfully into parseoutput and should be processed, false to drop the message.
+        */
+       virtual bool Parse(LocalUser* user, const std::string& line, ParseOutput& parseoutput) = 0;
+};
+
+inline ClientProtocol::MessageTagData::MessageTagData(MessageTagProvider* prov, const std::string& val, void* data)
+       : tagprov(prov)
+       , value(val)
+       , provdata(data)
+{
+}
diff --git a/include/clientprotocolevent.h b/include/clientprotocolevent.h
new file mode 100644 (file)
index 0000000..6b89d0f
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ *   Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd.  InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#pragma once
+
+namespace ClientProtocol
+{
+       namespace Events
+       {
+               struct Join;
+               class Mode;
+       }
+}
+
+struct ClientProtocol::Events::Join : public ClientProtocol::Messages::Join, public ClientProtocol::Event
+{
+       Join()
+               : ClientProtocol::Event(ServerInstance->GetRFCEvents().join, *this)
+       {
+       }
+
+       Join(Membership* Memb)
+               : ClientProtocol::Messages::Join(Memb)
+               , ClientProtocol::Event(ServerInstance->GetRFCEvents().join, *this)
+       {
+       }
+
+       Join(Membership* Memb, const std::string& Sourcestr)
+               : ClientProtocol::Messages::Join(Memb, Sourcestr)
+               , ClientProtocol::Event(ServerInstance->GetRFCEvents().join, *this)
+       {
+       }
+};
+
+class ClientProtocol::Events::Mode : public ClientProtocol::Event
+{
+       std::list<ClientProtocol::Messages::Mode> modelist;
+       std::vector<Message*> modemsgplist;
+       const Modes::ChangeList& modechanges;
+
+ public:
+       static void BuildMessages(User* source, Channel* Chantarget, User* Usertarget, const Modes::ChangeList& changelist, std::list<ClientProtocol::Messages::Mode>& modelist, std::vector<Message*>& modemsgplist)
+       {
+               // Build as many MODEs as necessary
+               for (Modes::ChangeList::List::const_iterator i = changelist.getlist().begin(); i != changelist.getlist().end(); i = modelist.back().GetEndIterator())
+               {
+                       modelist.push_back(ClientProtocol::Messages::Mode(source, Chantarget, Usertarget, changelist, i));
+                       modemsgplist.push_back(&modelist.back());
+               }
+       }
+
+       Mode(User* source, Channel* Chantarget, User* Usertarget, const Modes::ChangeList& changelist)
+               : ClientProtocol::Event(ServerInstance->GetRFCEvents().mode)
+               , modechanges(changelist)
+       {
+               BuildMessages(source, Chantarget, Usertarget, changelist, modelist, modemsgplist);
+               SetMessageList(modemsgplist);
+       }
+
+       const Modes::ChangeList& GetChangeList() const { return modechanges; }
+       const std::list<ClientProtocol::Messages::Mode>& GetMessages() const { return modelist; }
+};
diff --git a/include/clientprotocolmsg.h b/include/clientprotocolmsg.h
new file mode 100644 (file)
index 0000000..d1562d7
--- /dev/null
@@ -0,0 +1,677 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ *   Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd.  InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#pragma once
+
+namespace ClientProtocol
+{
+       namespace Messages
+       {
+               class Numeric;
+               class Join;
+               struct Part;
+               struct Kick;
+               struct Quit;
+               struct Nick;
+               class Mode;
+               struct Topic;
+               class Privmsg;
+               struct Invite;
+               struct Ping;
+               struct Pong;
+               struct Error;
+       }
+}
+
+/** Numeric message.
+ * Doesn't have a fixed command name, it's always a 3 digit number padded with zeroes if necessary.
+ * The first parameter is the target of the numeric which is almost always the nick of the user
+ * the numeric will be sent to.
+ */
+class ClientProtocol::Messages::Numeric : public ClientProtocol::Message
+{
+       char numericstr[4];
+
+       void InitCommand(unsigned int number)
+       {
+               snprintf(numericstr, sizeof(numericstr), "%03u", number);
+               SetCommand(numericstr);
+       }
+
+       void InitFromNumeric(const ::Numeric::Numeric& numeric)
+       {
+               InitCommand(numeric.GetNumeric());
+               for (std::vector<std::string>::const_iterator i = numeric.GetParams().begin(); i != numeric.GetParams().end(); ++i)
+                       PushParamRef(*i);
+       }
+
+ public:
+       /** Constructor, target is a User.
+        * @param num Numeric object to send. Must remain valid as long as this object is alive and must not be modified.
+        * @param user User to send the numeric to. May be unregistered, must remain valid as long as this object is alive.
+        */
+       Numeric(const ::Numeric::Numeric& num, User* user)
+               : ClientProtocol::Message(NULL, (num.GetServer() ? num.GetServer()->GetName() : ServerInstance->Config->ServerName))
+       {
+               if (user->registered & REG_NICK)
+                       PushParamRef(user->nick);
+               else
+                       PushParam("*");
+               InitFromNumeric(num);
+       }
+
+       /** Constructor, target is a string.
+        * @param num Numeric object to send. Must remain valid as long as this object is alive and must not be modified.
+        * @param target Target string, must stay valid as long as this object is alive.
+        */
+       Numeric(const ::Numeric::Numeric& num, const std::string& target)
+               : ClientProtocol::Message(NULL, (num.GetServer() ? num.GetServer()->GetName() : ServerInstance->Config->ServerName))
+       {
+               PushParamRef(target);
+               InitFromNumeric(num);
+       }
+
+       /** Constructor. Only the numeric number has to be specified.
+        * @param num Numeric number.
+        */
+       Numeric(unsigned int num)
+               : ClientProtocol::Message(NULL, ServerInstance->Config->ServerName)
+       {
+               InitCommand(num);
+               PushParam("*");
+       }
+};
+
+/** JOIN message.
+ * Sent when a user joins a channel.
+ */
+class ClientProtocol::Messages::Join : public ClientProtocol::Message
+{
+       Membership* memb;
+
+ public:
+       /** Constructor. Does not populate parameters, call SetParams() before sending the message.
+        */
+       Join()
+               : ClientProtocol::Message("JOIN")
+               , memb(NULL)
+       {
+       }
+
+       /** Constructor.
+        * @param Memb Membership of the joining user.
+        */
+       Join(Membership* Memb)
+               : ClientProtocol::Message("JOIN", Memb->user)
+       {
+               SetParams(Memb);
+       }
+
+       /** Constructor.
+        * @param Memb Membership of the joining user.
+        * @param sourcestrref Message source string, must remain valid as long as this object is alive.
+        */
+       Join(Membership* Memb, const std::string& sourcestrref)
+               : ClientProtocol::Message("JOIN", sourcestrref, Memb->user)
+       {
+               SetParams(Memb);
+       }
+
+       /** Populate parameters from a Membership
+        * @param Memb Membership of the joining user.
+        */
+       void SetParams(Membership* Memb)
+       {
+               memb = Memb;
+               PushParamRef(memb->chan->name);
+       }
+
+       /** Get the Membership of the joining user.
+        * @return Membership of the joining user.
+        */
+       Membership* GetMember() const { return memb; }
+};
+
+/** PART message.
+ * Sent when a user parts a channel.
+ */
+struct ClientProtocol::Messages::Part : public ClientProtocol::Message
+{
+       /** Constructor.
+        * @param memb Member parting.
+        * @param reason Part reason, may be empty. If non-empty, must remain valid as long as this object is alive.
+        */
+       Part(Membership* memb, const std::string& reason)
+               : ClientProtocol::Message("PART", memb->user)
+       {
+               PushParamRef(memb->chan->name);
+               if (!reason.empty())
+                       PushParamRef(reason);
+       }
+};
+
+/** KICK message.
+ * Sent when a user is kicked from a channel.
+ */
+struct ClientProtocol::Messages::Kick : public ClientProtocol::Message
+{
+       /** Constructor.
+        * @param source User that does the kick.
+        * @param memb Membership of the user being kicked.
+        * @param reason Kick reason. Must remain valid as long as this object is alive.
+        */
+       Kick(User* source, Membership* memb, const std::string& reason)
+               : ClientProtocol::Message("KICK", source)
+       {
+               PushParamRef(memb->chan->name);
+               PushParamRef(memb->user->nick);
+               PushParamRef(reason);
+       }
+};
+
+/** QUIT message.
+ * Sent when a user quits.
+ */
+struct ClientProtocol::Messages::Quit : public ClientProtocol::Message
+{
+       /** Constructor.
+        * @param source User quitting.
+        * @param reason Quit reason, may be empty. Must remain valid as long as this object is alive.
+        */
+       Quit(User* source, const std::string& reason)
+               : ClientProtocol::Message("QUIT", source)
+       {
+               if (!reason.empty())
+                       PushParamRef(reason);
+       }
+};
+
+/** NICK message.
+ * Sent when a user changes their nickname.
+ */
+struct ClientProtocol::Messages::Nick : public ClientProtocol::Message
+{
+       /** Constructor.
+        * @param source User changing nicks.
+        * @param newnick New nickname. Must remain valid as long as this object is alive.
+        */
+       Nick(User* source, const std::string& newnick)
+               : ClientProtocol::Message("NICK", source)
+       {
+               PushParamRef(newnick);
+       }
+};
+
+/** MODE message.
+ * Sent when modes are changed on a user or channel.
+ */
+class ClientProtocol::Messages::Mode : public ClientProtocol::Message
+{
+       Channel* chantarget;
+       User* usertarget;
+       Modes::ChangeList::List::const_iterator beginit;
+       Modes::ChangeList::List::const_iterator lastit;
+
+       /** Convert a range of a mode change list to mode letters and '+', '-' symbols.
+        * @param list Mode change list.
+        * @param maxlinelen Maximum output length.
+        * @param beginit Iterator to the first element in 'list' to process.
+        * @param lastit Iterator which is set to the first element not processed due to length limitations by the method.
+        */
+       static std::string ToModeLetters(const Modes::ChangeList::List& list, std::string::size_type maxlinelen, Modes::ChangeList::List::const_iterator beginit, Modes::ChangeList::List::const_iterator& lastit)
+       {
+               std::string ret;
+               std::string::size_type paramlength = 0;
+               char output_pm = '\0'; // current output state, '+' or '-'
+
+               Modes::ChangeList::List::const_iterator i;
+               for (i = beginit; i != list.end(); ++i)
+               {
+                       const Modes::Change& item = *i;
+
+                       const char needed_pm = (item.adding ? '+' : '-');
+                       if (needed_pm != output_pm)
+                       {
+                               output_pm = needed_pm;
+                               ret.push_back(output_pm);
+                       }
+
+                       if (!item.param.empty())
+                               paramlength += item.param.length() + 1;
+                       if (ret.length() + 1 + paramlength > maxlinelen)
+                       {
+                               // Mode sequence is getting too long
+                               const char c = *ret.rbegin();
+                               if ((c == '+') || (c == '-'))
+                                       ret.erase(ret.size()-1);
+                               break;
+                       }
+
+                       ret.push_back(item.mh->GetModeChar());
+               }
+
+               lastit = i;
+               return ret;
+       }
+
+       /** Push mode parameters for modes that have one, starting at beginit to lastit (not including lastit).
+        */
+       void PushModeParams()
+       {
+               for (Modes::ChangeList::List::const_iterator i = beginit; i != lastit; ++i)
+               {
+                       const Modes::Change& item = *i;
+                       if (!item.param.empty())
+                               PushParamRef(item.param);
+               }
+       }
+
+ public:
+       /** Convert an entire mode change list into mode letters and '+' and '-' characters.
+        * @param changelist Mode change list to convert into mode letters.
+        * @return Mode letters.
+        */
+       static std::string ToModeLetters(const Modes::ChangeList& changelist)
+       {
+               // TODO: This assumes that std::string::max_size() >= UINT_MAX
+               Modes::ChangeList::List::const_iterator dummy;
+               return ToModeLetters(changelist.getlist(), UINT_MAX, changelist.getlist().begin(), dummy);
+       }
+
+       /** Constructor, populate parameters starting from a given position in a mode change list.
+        * @param source User doing the mode change.
+        * @param Chantarget Channel target of the mode change. May be NULL if Usertarget is non-NULL.
+        * @param Usertarget User target of the mode change. May be NULL if Chantarget is non-NULL.
+        * @param changelist Mode change list. Must remain valid and unchanged as long as this object is alive or until the next SetParams() call.
+        * @param beginiter Starting position of mode changes in 'changelist'.
+        */
+       Mode(User* source, Channel* Chantarget, User* Usertarget, const Modes::ChangeList& changelist, Modes::ChangeList::List::const_iterator beginiter)
+               : ClientProtocol::Message("MODE", source)
+               , chantarget(Chantarget)
+               , usertarget(Usertarget)
+               , beginit(beginiter)
+       {
+               PushParamRef(GetStrTarget());
+               PushParam(ToModeLetters(changelist.getlist(), 450, beginit, lastit));
+               PushModeParams();
+       }
+
+       /** Constructor, populate parameters starting from the beginning of a mode change list.
+        * @param source User doing the mode change.
+        * @param Chantarget Channel target of the mode change. May be NULL if Usertarget is non-NULL.
+        * @param Usertarget User target of the mode change. May be NULL if Chantarget is non-NULL.
+        * @param changelist Mode change list. Must remain valid and unchanged as long as this object is alive or until the next SetParams() call.
+        */
+       Mode(User* source, Channel* Chantarget, User* Usertarget, const Modes::ChangeList& changelist)
+               : ClientProtocol::Message("MODE", source)
+               , chantarget(Chantarget)
+               , usertarget(Usertarget)
+               , beginit(changelist.getlist().begin())
+       {
+               PushParamRef(GetStrTarget());
+               PushParam(ToModeLetters(changelist.getlist(), 450, beginit, lastit));
+               PushModeParams();
+       }
+
+       /** Constructor. Does not populate parameters, call SetParams() before sending the message.
+        * The message source is set to the local server.
+        */
+       Mode()
+               : ClientProtocol::Message("MODE", ServerInstance->FakeClient)
+               , chantarget(NULL)
+               , usertarget(NULL)
+       {
+       }
+
+       /** Set parameters
+        * @param Chantarget Channel target of the mode change. May be NULL if Usertarget is non-NULL.
+        * @param Usertarget User target of the mode change. May be NULL if Chantarget is non-NULL.
+        * @param changelist Mode change list. Must remain valid and unchanged as long as this object is alive or until the next SetParams() call.
+        */
+       void SetParams(Channel* Chantarget, User* Usertarget, const Modes::ChangeList& changelist)
+       {
+               ClearParams();
+
+               chantarget = Chantarget;
+               usertarget = Usertarget;
+               beginit = changelist.getlist().begin();
+
+               PushParamRef(GetStrTarget());
+               PushParam(ToModeLetters(changelist.getlist(), 450, beginit, lastit));
+               PushModeParams();
+       }
+
+       /** Get first mode change included in this MODE message.
+        * @return Iterator to the first mode change that is included in this MODE message.
+        */
+       Modes::ChangeList::List::const_iterator GetBeginIterator() const { return beginit; }
+
+       /** Get first mode change not included in this MODE message.
+        * @return Iterator to the first mode change that is not included in this MODE message.
+        */
+       Modes::ChangeList::List::const_iterator GetEndIterator() const { return lastit; }
+
+       /** Get mode change target as a string.
+        * This is the name of the channel if the mode change targets a channel or the nickname of the user
+        * if the target is a user.
+        * @return Name of target as a string.
+        */
+       const std::string& GetStrTarget() const { return (chantarget ? chantarget->name : usertarget->nick); }
+
+       /** Get user target.
+        * @return User target or NULL if the mode change targets a channel.
+        */
+       User* GetUserTarget() const { return usertarget; }
+
+       /** Get channel target.
+        * @return Channel target or NULL if the mode change targets a user.
+        */
+       Channel* GetChanTarget() const { return chantarget; }
+};
+
+/** TOPIC message.
+ */
+struct ClientProtocol::Messages::Topic : public ClientProtocol::Message
+{
+       /** Constructor.
+        * @param source User changing the topic.
+        * @param chan Channel the topic is being changed on.
+        * @param newtopic New topic. May be empty, must remain valid as long as this object is alive.
+        */
+       Topic(User* source, const Channel* chan, const std::string& newtopic)
+               : ClientProtocol::Message("TOPIC", source)
+       {
+               PushParamRef(chan->name);
+               PushParamRef(newtopic);
+       }
+};
+
+/** PRIVMSG and NOTICE message.
+ */
+class ClientProtocol::Messages::Privmsg : public ClientProtocol::Message
+{
+       void PushTargetChan(char status, const Channel* targetchan)
+       {
+               if (status)
+               {
+                       std::string rawtarget(1, status);
+                       rawtarget.append(targetchan->name);
+                       PushParam(rawtarget);
+               }
+               else
+               {
+                       PushParamRef(targetchan->name);
+               }
+       }
+
+       void PushTargetUser(const User* targetuser)
+       {
+               if (targetuser->registered & REG_NICK)
+                       PushParamRef(targetuser->nick);
+               else
+                       PushParam("*");
+       }
+
+ public:
+       /** Used to differentiate constructors that copy the text from constructors that do not.
+        */
+       enum NoCopy { nocopy };
+
+       /** Get command name from MessageType.
+        * @param mt Message type to get command name for.
+        * @return Command name for the message type.
+        */
+       static const char* CommandStrFromMsgType(MessageType mt)
+       {
+               return ((mt == MSG_PRIVMSG) ? "PRIVMSG" : "NOTICE");
+       }
+
+       /** Constructor, user source, string target, copies text.
+        * @param source Source user.
+        * @param target Privmsg target string.
+        * @param text Privmsg text, will be copied.
+        * @param mt Message type.
+        */
+       Privmsg(User* source, const std::string& target, const std::string& text, MessageType mt = MSG_PRIVMSG)
+               : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+       {
+               PushParam(target);
+               PushParam(text);
+       }
+
+       /** Constructor, user source, user target, copies text.
+        * @param source Source user.
+        * @param targetchan Target channel.
+        * @param text Privmsg text, will be copied.
+        * @param mt Message type.
+        * @param status Prefix character for status messages. If non-zero the message is a status message. Optional, defaults to 0.
+        */
+       Privmsg(User* source, const Channel* targetchan, const std::string& text, MessageType mt = MSG_PRIVMSG, char status = 0)
+               : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+       {
+               PushTargetChan(status, targetchan);
+               PushParam(text);
+       }
+
+       /** Constructor, user source, user target, copies text.
+        * @param source Source user.
+        * @param targetuser Target user.
+        * @param text Privmsg text, will be copied.
+        * @param mt Message type.
+        */
+       Privmsg(User* source, const User* targetuser, const std::string& text, MessageType mt = MSG_PRIVMSG)
+               : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+       {
+               PushTargetUser(targetuser);
+               PushParam(text);
+       }
+
+       /** Constructor, string source, string target, copies text.
+        * @param source Source user.
+        * @param targetuser Target user.
+        * @param text Privmsg text, will be copied.
+        * @param mt Message type.
+        */
+       Privmsg(const std::string& source, const std::string& target, const std::string& text, MessageType mt = MSG_PRIVMSG)
+               : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+       {
+               PushParam(target);
+               PushParam(text);
+       }
+
+       /** Constructor, string source, channel target, copies text.
+        * @param source Source string.
+        * @param targetchan Target channel.
+        * @param text Privmsg text, will be copied.
+        * @param status Prefix character for status messages. If non-zero the message is a status message. Optional, defaults to 0.
+        * @param mt Message type.
+        */
+       Privmsg(const std::string& source, const Channel* targetchan, const std::string& text, MessageType mt = MSG_PRIVMSG, char status = 0)
+               : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+       {
+               PushTargetChan(status, targetchan);
+               PushParam(text);
+       }
+
+       /** Constructor, string source, user target, copies text.
+        * @param source Source string.
+        * @param targetuser Target user.
+        * @param text Privmsg text, will be copied.
+        * @param mt Message type.
+        */
+       Privmsg(const std::string& source, const User* targetuser, const std::string& text, MessageType mt = MSG_PRIVMSG)
+               : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+       {
+               PushTargetUser(targetuser);
+               PushParam(text);
+       }
+
+       /** Constructor, user source, string target, copies text.
+        * @param source Source user.
+        * @param targetuser Target string.
+        * @param text Privmsg text, will not be copied.
+        * @param mt Message type.
+        */
+       Privmsg(NoCopy, User* source, const std::string& target, const std::string& text, MessageType mt = MSG_PRIVMSG)
+               : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+       {
+               PushParam(target);
+               PushParamRef(text);
+       }
+
+       /** Constructor, user source, channel target, does not copy text.
+        * @param source Source user.
+        * @param targetchan Target channel.
+        * @param text Privmsg text, will not be copied.
+        * @param status Prefix character for status messages. If non-zero the message is a status message. Optional, defaults to 0.
+        * @param mt Message type.
+        */
+       Privmsg(NoCopy, User* source, const Channel* targetchan, const std::string& text, MessageType mt = MSG_PRIVMSG, char status = 0)
+               : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+       {
+               PushTargetChan(status, targetchan);
+               PushParamRef(text);
+       }
+
+       /** Constructor, user source, user target, does not copy text.
+        * @param source Source user.
+        * @param targetuser Target user.
+        * @param text Privmsg text, will not be copied.
+        * @param mt Message type.
+        */
+       Privmsg(NoCopy, User* source, const User* targetuser, const std::string& text, MessageType mt = MSG_PRIVMSG)
+               : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+       {
+               PushTargetUser(targetuser);
+               PushParamRef(text);
+       }
+
+       /** Constructor, string source, string target, does not copy text.
+        * @param source Source string.
+        * @param targetuser Target string.
+        * @param text Privmsg text, will not be copied.
+        * @param mt Message type.
+        */
+       Privmsg(NoCopy, const std::string& source, const std::string& target, const std::string& text, MessageType mt = MSG_PRIVMSG)
+               : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+       {
+               PushParam(target);
+               PushParamRef(text);
+       }
+
+       /** Constructor, string source, channel target, does not copy text.
+        * @param source Source string.
+        * @param targetchan Target channel.
+        * @param text Privmsg text, will not be copied.
+        * @param status Prefix character for status messages. If non-zero the message is a status message. Optional, defaults to 0.
+        * @param mt Message type.
+        */
+       Privmsg(NoCopy, const std::string& source, const Channel* targetchan, const std::string& text, MessageType mt = MSG_PRIVMSG, char status = 0)
+               : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+       {
+               PushTargetChan(status, targetchan);
+               PushParamRef(text);
+       }
+
+       /** Constructor, string source, user target, does not copy text.
+        * @param source Source string.
+        * @param targetchan Target user.
+        * @param text Privmsg text, will not be copied.
+        * @param mt Message type.
+        */
+       Privmsg(NoCopy, const std::string& source, const User* targetuser, const std::string& text, MessageType mt = MSG_PRIVMSG)
+               : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+       {
+               PushTargetUser(targetuser);
+               PushParamRef(text);
+       }
+};
+
+/** INVITE message.
+ * Sent when a user is invited to join a channel.
+ */
+struct ClientProtocol::Messages::Invite : public ClientProtocol::Message
+{
+       /** Constructor.
+        * @param source User inviting the target user.
+        * @param target User being invited by source.
+        * @param chan Channel the target user is being invited to.
+        */
+       Invite(User* source, User* target, Channel* chan)
+               : ClientProtocol::Message("INVITE", source)
+       {
+               PushParamRef(target->nick);
+               PushParamRef(chan->name);
+       }
+};
+
+/** PING message.
+ * Used to check if a connection is still alive.
+ */
+struct ClientProtocol::Messages::Ping : public ClientProtocol::Message
+{
+       /** Constructor.
+        * The ping cookie is the name of the local server.
+        */
+       Ping()
+               : ClientProtocol::Message("PING")
+       {
+               PushParamRef(ServerInstance->Config->ServerName);
+       }
+
+       /** Constructor.
+        * @param cookie Ping cookie. Must remain valid as long as this object is alive.
+        */
+       Ping(const std::string& cookie)
+               : ClientProtocol::Message("PING")
+       {
+               PushParamRef(cookie);
+       }
+};
+
+/** PONG message.
+ * Sent as a reply to PING.
+ */
+struct ClientProtocol::Messages::Pong : public ClientProtocol::Message
+{
+       /** Constructor.
+        * @param cookie Ping cookie. Must remain valid as long as this object is alive.
+        */
+       Pong(const std::string& cookie)
+               : ClientProtocol::Message("PONG", ServerInstance->Config->ServerName)
+       {
+               PushParamRef(ServerInstance->Config->ServerName);
+               PushParamRef(cookie);
+       }
+};
+
+/** ERROR message.
+ * Sent to clients upon disconnection.
+ */
+struct ClientProtocol::Messages::Error : public ClientProtocol::Message
+{
+       /** Constructor.
+        * @param text Error text.
+        */
+       Error(const std::string& text)
+                       : ClientProtocol::Message("ERROR")
+       {
+               PushParam(text);
+       }
+};
index f5cb476204ea3b2cf17eea104b45fb780a6d785b..98484ca540f798618c20fdc4a06f99439c03751d 100644 (file)
@@ -38,7 +38,7 @@ class CoreExport CommandParser
         * @param command The name of the command.
         * @param parameters The parameters to the command.
         */
-       void ProcessCommand(LocalUser* user, std::string& command, Command::Params& parameters);
+       void ProcessCommand(LocalUser* user, std::string& command, CommandBase::Params& parameters);
 
        /** Command list, a hash_map of command names to Command*
         */
index 81ec014a029e60c9919f92ecc5bf4bb13c07186c..4cb051efff3d1e9f27928627306f55e19e713ec0 100644 (file)
@@ -430,11 +430,6 @@ class CoreExport ServerConfig
         */
        std::string CaseMapping;
 
-       /** If set to true, the CycleHosts mode change will be sourced from the user,
-        * rather than the server
-        */
-       bool CycleHostsFromUser;
-
        /** If set to true, the full nick!user\@host will be shown in the TOPIC command
         * for who set the topic last. If false, only the nick is shown.
         */
index c34e4abeb4ab9f1b6336a2b42fa0394068b97daf..8be40cc54d597350b21a2b51ff845cc8b641c973 100644 (file)
@@ -110,7 +110,40 @@ struct RouteDescriptor
 class CoreExport CommandBase : public ServiceProvider
 {
  public:
-       typedef std::vector<std::string> Params;
+       /** Encapsulates parameters to a command. */
+       class Params : public std::vector<std::string>
+       {
+        private:
+               /* IRCv3 message tags. */
+               ClientProtocol::TagMap tags;
+
+        public:
+               /** Initializes a new instance from parameter and tag references.
+                * @param paramsref Message parameters.
+                * @param tagsref IRCv3 message tags.
+                */
+               Params(const std::vector<std::string>& paramsref, const ClientProtocol::TagMap& tagsref)
+                       : std::vector<std::string>(paramsref)
+                       , tags(tagsref)
+               {
+               }
+
+               /** Initializes a new instance from parameter iterators.
+                * @param first The first element in the parameter array.
+                * @param last The last element in the parameter array.
+                */
+               template<typename Iterator>
+               Params(Iterator first, Iterator last)
+                       : std::vector<std::string>(first, last)
+               {
+               }
+
+               /** Initializes a new empty instance. */
+               Params() { }
+
+               /** Retrieves the IRCv3 message tags. */
+               const ClientProtocol::TagMap& GetTags() const { return tags; }
+       };
 
        /** User flags needed to execute the command or 0
         */
index c9bad7d045f81423bb3d24f030fe3f72e46e59c2..73a45a5419a7396950851e210078e7cdebfb6aa3 100644 (file)
@@ -34,7 +34,12 @@ namespace Events
 class Events::ModuleEventProvider : public ServiceProvider, private dynamic_reference_base::CaptureHook
 {
  public:
-       typedef std::vector<ModuleEventListener*> SubscriberList;
+       struct Comp
+       {
+               bool operator()(ModuleEventListener* one, ModuleEventListener* two) const;
+       };
+
+       typedef insp::flat_multiset<ModuleEventListener*, Comp> SubscriberList;
 
        /** Constructor
         * @param mod Module providing the event(s)
@@ -84,20 +89,25 @@ class Events::ModuleEventListener : private dynamic_reference_base::CaptureHook
         */
        dynamic_reference_nocheck<ModuleEventProvider> prov;
 
+       const unsigned int eventpriority;
+
        /** Called by the dynref when the event provider becomes available
         */
        void OnCapture() CXX11_OVERRIDE
        {
-               prov->subscribers.push_back(this);
+               prov->subscribers.insert(this);
        }
 
  public:
+       static const unsigned int DefaultPriority = 100;
+
        /** Constructor
         * @param mod Module subscribing
         * @param eventid Identifier of the event to subscribe to
         */
-       ModuleEventListener(Module* mod, const std::string& eventid)
+       ModuleEventListener(Module* mod, const std::string& eventid, unsigned int eventprio = DefaultPriority)
                : prov(mod, eventid)
+               , eventpriority(eventprio)
        {
                prov.SetCaptureHook(this);
                // If the dynamic_reference resolved at construction our capture handler wasn't called
@@ -108,18 +118,25 @@ class Events::ModuleEventListener : private dynamic_reference_base::CaptureHook
        ~ModuleEventListener()
        {
                if (prov)
-                       stdalgo::erase(prov->subscribers, this);
+                       prov->subscribers.erase(this);
        }
+
+       friend struct ModuleEventProvider::Comp;
 };
 
+inline bool Events::ModuleEventProvider::Comp::operator()(Events::ModuleEventListener* one, Events::ModuleEventListener* two) const
+{
+       return (one->eventpriority < two->eventpriority);
+}
+
 /**
  * Run the given hook provided by a module
  *
  * FOREACH_MOD_CUSTOM(accountevprov, AccountEventListener, OnAccountChange, MOD_RESULT, (user, newaccount))
  */
 #define FOREACH_MOD_CUSTOM(prov, listenerclass, func, params) do { \
-       const Events::ModuleEventProvider::SubscriberList& _handlers = (prov).GetSubscribers(); \
-       for (Events::ModuleEventProvider::SubscriberList::const_iterator _i = _handlers.begin(); _i != _handlers.end(); ++_i) \
+       const ::Events::ModuleEventProvider::SubscriberList& _handlers = (prov).GetSubscribers(); \
+       for (::Events::ModuleEventProvider::SubscriberList::const_iterator _i = _handlers.begin(); _i != _handlers.end(); ++_i) \
        { \
                listenerclass* _t = static_cast<listenerclass*>(*_i); \
                _t->func params ; \
@@ -135,8 +152,8 @@ class Events::ModuleEventListener : private dynamic_reference_base::CaptureHook
  */
 #define FIRST_MOD_RESULT_CUSTOM(prov, listenerclass, func, result, params) do { \
        result = MOD_RES_PASSTHRU; \
-       const Events::ModuleEventProvider::SubscriberList& _handlers = (prov).GetSubscribers(); \
-       for (Events::ModuleEventProvider::SubscriberList::const_iterator _i = _handlers.begin(); _i != _handlers.end(); ++_i) \
+       const ::Events::ModuleEventProvider::SubscriberList& _handlers = (prov).GetSubscribers(); \
+       for (::Events::ModuleEventProvider::SubscriberList::const_iterator _i = _handlers.begin(); _i != _handlers.end(); ++_i) \
        { \
                listenerclass* _t = static_cast<listenerclass*>(*_i); \
                result = _t->func params ; \
index 00093e52ba07fbbd33f0ef3d79fd87f7fd3b3fed..90ee6ca8d93b45d372b13345b98d32d5859246f4 100644 (file)
@@ -91,6 +91,7 @@ struct fakederef
 #include "filelogger.h"
 #include "message.h"
 #include "modules.h"
+#include "clientprotocol.h"
 #include "threadengine.h"
 #include "configreader.h"
 #include "inspstring.h"
@@ -193,6 +194,8 @@ class CoreExport InspIRCd
         */
        char ReadBuffer[65535];
 
+       ClientProtocol::RFCEvents rfcevents;
+
        /** Check we aren't running as root, and exit if we are
         * with exit code EXIT_STATUS_ROOT.
         */
@@ -565,6 +568,8 @@ class CoreExport InspIRCd
        {
                return this->ReadBuffer;
        }
+
+       ClientProtocol::RFCEvents& GetRFCEvents() { return rfcevents; }
 };
 
 ENTRYPOINT;
@@ -590,4 +595,18 @@ inline void stdalgo::culldeleter::operator()(classbase* item)
                ServerInstance->GlobalCulls.AddItem(item);
 }
 
+inline void Channel::Write(ClientProtocol::EventProvider& protoevprov, ClientProtocol::Message& msg, char status, const CUList& except_list)
+{
+       ClientProtocol::Event event(protoevprov, msg);
+       Write(event, status, except_list);
+}
+
+inline void LocalUser::Send(ClientProtocol::EventProvider& protoevprov, ClientProtocol::Message& msg)
+{
+       ClientProtocol::Event event(protoevprov, msg);
+       Send(event);
+}
+
 #include "numericbuilder.h"
+#include "clientprotocolmsg.h"
+#include "clientprotocolevent.h"
index fb9e7619f70b473a1be57902578f99808cab6952..1799e61192c9cde5260e5e6d9547fc2130663d16 100644 (file)
@@ -41,15 +41,22 @@ class CoreExport MessageDetails
        /* The original message as sent by the user. */
        const std::string originaltext;
 
+       /** IRCv3 message tags sent to the server by the user. */
+       const ClientProtocol::TagMap tags_in;
+
+       /** IRCv3 message tags sent out to users who get this message. */
+       ClientProtocol::TagMap tags_out;
+
        /** The message which will be sent to clients. */
        std::string text;
 
        /** The type of message. */
        const MessageType type;
 
-       MessageDetails(MessageType mt, const std::string& msg)
+       MessageDetails(MessageType mt, const std::string& msg, const ClientProtocol::TagMap& tags)
                : echooriginal(false)
                , originaltext(msg)
+               , tags_in(tags)
                , text(msg)
                , type(mt)
        {
index 4fa78bcacd0b8ef62a4178894167db45e4926ccd..ac23adc330b1303175fdfff2d88cca6c0ae209f9 100644 (file)
@@ -614,11 +614,6 @@ class CoreExport ModeParser : public fakederef<ModeParser>
         */
        ModeHandler::Id AllocateModeId(ModeType mt);
 
-       /** The string representing the last set of modes to be parsed.
-        * Use GetLastParse() to get this value, to be used for  display purposes.
-        */
-       std::string LastParse;
-
        /** Cached mode list for use in 004 numeric
         */
        TR1NS::array<std::string, 3> Cached004ModeList;
@@ -681,13 +676,6 @@ class CoreExport ModeParser : public fakederef<ModeParser>
        /** Gets the last mode change to be processed. */
        const Modes::ChangeList& GetLastChangeList() const { return LastChangeList; }
 
-       /** Get the last string to be processed, as it was sent to the user or channel.
-        * Use this to display a string you just sent to be parsed, as the actual output
-        * may be different to what you sent after it has been 'cleaned up' by the parser.
-        * @return Last parsed string, as seen by users.
-        */
-       const std::string& GetLastParse() const { return LastParse; }
-
        /** Add a mode to the mode parser.
         * Throws a ModuleException if the mode cannot be added.
         */
index 885c22900907527fe3c0b09b6c6fce88b3f11dc0..9ec105e73b5cadb97d174f51ebcd5495e729a4e7 100644 (file)
@@ -53,6 +53,23 @@ class Modes::ChangeList
  public:
        typedef std::vector<Change> List;
 
+       /** Add a new mode to be changed to this ChangeList
+        * @param change Mode change to add
+        */
+       void push(const Modes::Change& change)
+       {
+               items.push_back(change);
+       }
+
+       /** Insert multiple mode changes to the ChangeList
+        * @param first Iterator to the first change to insert
+        * @param last Iterator to the first change to not insert
+        */
+       void push(List::const_iterator first, List::const_iterator last)
+       {
+               items.insert(items.end(), first, last);
+       }
+
        /** Add a new mode to be changed to this ChangeList
         * @param mh Mode handler
         * @param adding True if this mode is being set, false if removed
index 4e5648512c0501b18c1dbe992d6852e45f08bb54..ba84c4ccc30a308a035637e2c03709ef764f91ac 100644 (file)
@@ -227,7 +227,7 @@ enum Implementation
        I_OnBuildNeighborList, I_OnGarbageCollect, I_OnSetConnectClass,
        I_OnUserMessage, I_OnPassCompare, I_OnNamesListItem, I_OnNumeric,
        I_OnPreRehash, I_OnModuleRehash, I_OnSendWhoLine, I_OnChangeIdent, I_OnSetUserIP,
-       I_OnServiceAdd, I_OnServiceDel,
+       I_OnServiceAdd, I_OnServiceDel, I_OnUserWrite,
        I_END
 };
 
@@ -561,9 +561,8 @@ class CoreExport Module : public classbase, public usecountbase
         * @param changelist The changed modes.
         * @param processflags Flags passed to ModeParser::Process(), see ModeParser::ModeProcessFlags
         * for the possible flags.
-        * @param output_mode Changed modes, including '+' and '-' characters, not including any parameters
         */
-       virtual void OnMode(User* user, User* usertarget, Channel* chantarget, const Modes::ChangeList& changelist, ModeParser::ModeProcessFlag processflags, const std::string& output_mode);
+       virtual void OnMode(User* user, User* usertarget, Channel* chantarget, const Modes::ChangeList& changelist, ModeParser::ModeProcessFlag processflags);
 
        /** Allows module data, sent via ProtoSendMetaData, to be decoded again by a receiving module.
         * Please see src/modules/m_swhois.cpp for a working example of how to use this method call.
@@ -953,6 +952,8 @@ class CoreExport Module : public classbase, public usecountbase
         * @param service ServiceProvider being unregistered.
         */
        virtual void OnServiceDel(ServiceProvider& service);
+
+       virtual ModResult OnUserWrite(LocalUser* user, ClientProtocol::Message& msg);
 };
 
 /** ModuleManager takes care of all things module-related
index 8299d14aeae7634eb6553266c09f1f7492714b22..6dcb9f3bc4d037d5a9055cfe7472824e8adcb190 100644 (file)
@@ -313,4 +313,23 @@ namespace Cap
                        return false;
                }
        };
+
+       class MessageBase : public ClientProtocol::Message
+       {
+        public:
+               MessageBase(const std::string& subcmd)
+                       : ClientProtocol::Message("CAP", ServerInstance->Config->ServerName)
+               {
+                       PushParamPlaceholder();
+                       PushParam(subcmd);
+               }
+
+               void SetUser(LocalUser* user)
+               {
+                       if (user->registered & REG_NICK)
+                               ReplaceParamRef(0, user->nick);
+                       else
+                               ReplaceParam(0, "*");
+               }
+       };
 }
index e03ee16fa061c3d69e1d1fd7bf2ff55fa1f75b9d..338abdeba25b6e1552a1c33d693e59088349de0a 100644 (file)
 
 #pragma once
 
+#include "modules/cap.h"
+
 namespace IRCv3
 {
        class WriteNeighborsWithCap;
+       template <typename T>
+       class CapTag;
 }
 
 class IRCv3::WriteNeighborsWithCap : public User::ForEachNeighborHandler
 {
        const Cap::Capability& cap;
-       const std::string& msg;
+       ClientProtocol::Event& protoev;
 
        void Execute(LocalUser* user) CXX11_OVERRIDE
        {
                if (cap.get(user))
-                       user->Write(msg);
+                       user->Send(protoev);
        }
 
  public:
-       WriteNeighborsWithCap(User* user, const std::string& message, const Cap::Capability& capability)
+       WriteNeighborsWithCap(User* user, ClientProtocol::Event& ev, const Cap::Capability& capability)
                : cap(capability)
-               , msg(message)
+               , protoev(ev)
        {
                user->ForEachNeighbor(*this, false);
        }
 };
+
+/** Base class for simple message tags.
+ * Message tags provided by classes derived from this class will be sent to clients that have negotiated
+ * a client capability, also managed by this class.
+ *
+ * Derived classes specify the name of the capability and the message tag and provide a public GetValue()
+ * method with the following signature: const std::string* GetValue(ClientProtocol::Message& msg).
+ * The returned value determines whether to attach the tag to the message. If it is NULL, the tag won't
+ * be attached. If it is non-NULL the tag will be attached with the value in the string. If the string is
+ * empty the tag is attached without a value.
+ *
+ * Providers inheriting from this class don't accept incoming tags by default.
+ *
+ * For more control, inherit from ClientProtocol::MessageTagProvider directly.
+ *
+ * Template parameter T is the derived class.
+ */
+template <typename T>
+class IRCv3::CapTag : public ClientProtocol::MessageTagProvider
+{
+       Cap::Capability cap;
+       const std::string tagname;
+
+       bool ShouldSendTag(LocalUser* user, const ClientProtocol::MessageTagData& tagdata) CXX11_OVERRIDE
+       {
+               return cap.get(user);
+       }
+
+       void OnClientProtocolPopulateTags(ClientProtocol::Message& msg) CXX11_OVERRIDE
+       {
+               T& tag = static_cast<T&>(*this);
+               const std::string* const val = tag.GetValue(msg);
+               if (val)
+                       msg.AddTag(tagname, this, *val);
+       }
+
+ public:
+       /** Constructor.
+        * @param mod Module that owns the tag.
+        * @param capname Name of the client capability.
+        * A client capability with this name will be created. It will be available to all clients and it won't
+        * have a value.
+        * See Cap::Capability for more info on client capabilities.
+        * @param Tagname Name of the message tag, to use in the protocol.
+        */
+       CapTag(Module* mod, const std::string& capname, const std::string& Tagname)
+               : ClientProtocol::MessageTagProvider(mod)
+               , cap(mod, capname)
+               , tagname(Tagname)
+       {
+       }
+};
index bb5e122623277a9daa60a6ecd13a82852db74ed1..d69f50bb244afd71537ac750c54d1c1dd4980a6f 100644 (file)
@@ -210,4 +210,86 @@ namespace stdalgo
        {
                return (std::find(cont.begin(), cont.end(), val) != cont.end());
        }
+
+       namespace string
+       {
+               /**
+                * Escape a string
+                * @param str String to escape
+                * @param out Output, must not be the same string as str
+                */
+               template <char from, char to, char esc>
+               inline void escape(const std::string& str, std::string& out)
+               {
+                       for (std::string::const_iterator i = str.begin(); i != str.end(); ++i)
+                       {
+                               char c = *i;
+                               if (c == esc)
+                                       out.append(2, esc);
+                               else
+                               {
+                                       if (c == from)
+                                       {
+                                               out.push_back(esc);
+                                               c = to;
+                                       }
+                                       out.push_back(c);
+                               }
+                       }
+               }
+
+               /**
+                * Escape a string using the backslash character as the escape character
+                * @param str String to escape
+                * @param out Output, must not be the same string as str
+                */
+               template <char from, char to>
+               inline void escape(const std::string& str, std::string& out)
+               {
+                       escape<from, to, '\\'>(str, out);
+               }
+
+               /**
+                * Unescape a string
+                * @param str String to unescape
+                * @param out Output, must not be the same string as str
+                * @return True if the string was unescaped, false if an invalid escape sequence is present in the input in which case out will contain a partially unescaped string
+                */
+               template<char from, char to, char esc>
+               inline bool unescape(const std::string& str, std::string& out)
+               {
+                       for (std::string::const_iterator i = str.begin(); i != str.end(); ++i)
+                       {
+                               char c = *i;
+                               if (c == '\\')
+                               {
+                                       ++i;
+                                       if (i == str.end())
+                                               return false;
+
+                                       char nextc = *i;
+                                       if (nextc == esc)
+                                               c = esc;
+                                       else if (nextc != to)
+                                               return false; // Invalid escape sequence
+                                       else
+                                               c = from;
+                               }
+                               out.push_back(c);
+                       }
+                       return true;
+               }
+
+               /**
+                * Unescape a string using the backslash character as the escape character
+                * @param str String to unescape
+                * @param out Output, must not be the same string as str
+                * @return True if the string was unescaped, false if an invalid escape sequence is present in the input in which case out will contain a partially unescaped string
+                */
+               template <char from, char to>
+               inline bool unescape(const std::string& str, std::string& out)
+               {
+                       return unescape<from, to, '\\'>(str, out);
+               }
+       }
 }
index 9a015d4450b44ca8f2b4e02c867b3af6c83b0a2a..20fc596be444ac29c0665da90af031d0019e0676 100644 (file)
@@ -49,6 +49,34 @@ class XLineFactory;
 struct ConnectClass;
 struct ModResult;
 
+namespace ClientProtocol
+{
+       class Event;
+       class EventProvider;
+       class Message;
+       class MessageTagProvider;
+       class Serializer;
+
+       typedef std::vector<Message*> MessageList;
+       typedef std::vector<std::string> ParamList;
+       typedef std::string SerializedMessage;
+
+       struct MessageTagData
+       {
+               MessageTagProvider* tagprov;
+               std::string value;
+               void* provdata;
+
+               MessageTagData(MessageTagProvider* prov, const std::string& val, void* data = NULL);
+       };
+
+       /** Map of message tag values and providers keyed by their name.
+        * Sorted in descending order to ensure tag names beginning with symbols (such as '+') come later when iterating
+        * the container than tags with a normal name.
+        */
+       typedef insp::flat_map<std::string, MessageTagData, std::greater<std::string> > TagMap;
+}
+
 #include "hashcomp.h"
 #include "base.h"
 
index 21a35645dabca16a45a021fe00eee4322e4f22a4..e8f5399e8854f24026b5accb85b50a102653edc2 100644 (file)
@@ -498,41 +498,10 @@ class CoreExport User : public Extensible
         */
        void UnOper();
 
-       /** Write text to this user, appending CR/LF. Works on local users only.
-        * @param text A std::string to send to the user
-        */
-       virtual void Write(const std::string &text);
-
-       /** Write text to this user, appending CR/LF.
-        * Works on local users only.
-        * @param text The format string for text to send to the user
-        * @param ... POD-type format arguments
-        */
-       virtual void Write(const char *text, ...) CUSTOM_PRINTF(2, 3);
-
-       /** Write text to this user, appending CR/LF and prepending :server.name
-        * Works on local users only.
-        * @param text A std::string to send to the user
-        */
-       void WriteServ(const std::string& text);
-
-       /** Write text to this user, appending CR/LF and prepending :server.name
-        * Works on local users only.
-        * @param text The format string for text to send to the user
-        * @param ... POD-type format arguments
-        */
-       void WriteServ(const char* text, ...) CUSTOM_PRINTF(2, 3);
-
-       /** Sends a command to this user.
-        * @param command The command to be sent.
-        * @param text The message to send.
-        */
-       void WriteCommand(const char* command, const std::string& text);
-
        /** Sends a server notice to this user.
         * @param text The contents of the message to send.
         */
-       void WriteNotice(const std::string& text) { this->WriteCommand("NOTICE", ":" + text); }
+       void WriteNotice(const std::string& text);
 
        /** Send a NOTICE message from the local server to the user.
         * @param text Text to send
@@ -643,30 +612,11 @@ class CoreExport User : public Extensible
                WriteNumeric(n);
        }
 
-       /** Write text to this user, appending CR/LF and prepending :nick!user\@host of the user provided in the first parameter.
-        * @param user The user to prepend the :nick!user\@host of
-        * @param text A std::string to send to the user
-        */
-       void WriteFrom(User *user, const std::string &text);
-
-       /** Write text to this user, appending CR/LF and prepending :nick!user\@host of the user provided in the first parameter.
-        * @param user The user to prepend the :nick!user\@host of
-        * @param text The format string for text to send to the user
-        * @param ... POD-type format arguments
-        */
-       void WriteFrom(User *user, const char* text, ...) CUSTOM_PRINTF(3, 4);
-
        /** Write to all users that can see this user (including this user in the list if include_self is true), appending CR/LF
-        * @param line A std::string to send to the users
+        * @param protoev Protocol event to send, may contain any number of messages.
         * @param include_self Should the message be sent back to the author?
         */
-       void WriteCommonRaw(const std::string &line, bool include_self = true);
-
-       /** Write to all users that can see this user (including this user in the list), appending CR/LF
-        * @param text The format string for text to send to the users
-        * @param ... POD-type format arguments
-        */
-       void WriteCommon(const char* text, ...) CUSTOM_PRINTF(2, 3);
+       void WriteCommonRaw(ClientProtocol::Event& protoev, bool include_self = true);
 
        /** Execute a function once for each local neighbor of this user. By default, the neighbors of a user are the users
         * who have at least one common channel with the user. Modules are allowed to alter the set of neighbors freely.
@@ -750,12 +700,32 @@ typedef unsigned int already_sent_t;
 
 class CoreExport LocalUser : public User, public insp::intrusive_list_node<LocalUser>
 {
+       /** Add a serialized message to the send queue of the user.
+        * @param serialized Bytes to add.
+        */
+       void Write(const ClientProtocol::SerializedMessage& serialized);
+
+       /** Send a protocol event to the user, consisting of one or more messages.
+        * @param protoev Event to send, may contain any number of messages.
+        * @param msglist Message list used temporarily internally to pass to hooks and store messages
+        * before Write().
+        */
+       void Send(ClientProtocol::Event& protoev, ClientProtocol::MessageList& msglist);
+
+       /** Message list, can be passed to the two parameter Send().
+        */
+       static ClientProtocol::MessageList sendmsglist;
+
  public:
        LocalUser(int fd, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server);
        CullResult cull() CXX11_OVERRIDE;
 
        UserIOHandler eh;
 
+       /** Serializer to use when communicating with the user
+        */
+       ClientProtocol::Serializer* serializer;
+
        /** Stats counter for bytes inbound
         */
        unsigned int bytes_in;
@@ -850,9 +820,6 @@ class CoreExport LocalUser : public User, public insp::intrusive_list_node<Local
 
        void SetClientIP(const irc::sockets::sockaddrs& sa, bool recheck_eline = true) CXX11_OVERRIDE;
 
-       void Write(const std::string& text) CXX11_OVERRIDE;
-       void Write(const char*, ...) CXX11_OVERRIDE CUSTOM_PRINTF(2, 3);
-
        /** Send a NOTICE message from the local server to the user.
         * The message will be sent even if the user is connected to a remote server.
         * @param text Text to send
@@ -890,6 +857,17 @@ class CoreExport LocalUser : public User, public insp::intrusive_list_node<Local
         * isn't registered.
         */
        void OverruleNick();
+
+       /** Send a protocol event to the user, consisting of one or more messages.
+        * @param protoev Event to send, may contain any number of messages.
+        */
+       void Send(ClientProtocol::Event& protoev);
+
+       /** Send a single message to the user.
+        * @param protoevprov Protocol event provider.
+        * @param msg Message to send.
+        */
+       void Send(ClientProtocol::EventProvider& protoevprov, ClientProtocol::Message& msg);
 };
 
 class RemoteUser : public User
index b293e7fad74a93bada1db9940b581aaf3d961aff..c9816db4b006795fc0dde9faef88ade67a6d07b7 100644 (file)
@@ -49,7 +49,8 @@ void Channel::SetTopic(User* u, const std::string& ntopic, time_t topicts, const
        if (this->topic != ntopic)
        {
                this->topic = ntopic;
-               this->WriteChannel(u, "TOPIC %s :%s", this->name.c_str(), this->topic.c_str());
+               ClientProtocol::Messages::Topic topicmsg(u, this, this->topic);
+               Write(ServerInstance->GetRFCEvents().topic, topicmsg);
        }
 
        // Always update setter and set time
@@ -287,18 +288,8 @@ Membership* Channel::ForceJoin(User* user, const std::string* privs, bool bursti
        CUList except_list;
        FOREACH_MOD(OnUserJoin, (memb, bursting, created_by_local, except_list));
 
-       this->WriteAllExcept(user, false, 0, except_list, "JOIN :%s", this->name.c_str());
-
-       /* Theyre not the first ones in here, make sure everyone else sees the modes we gave the user */
-       if ((GetUserCounter() > 1) && (!memb->modes.empty()))
-       {
-               std::string ms = memb->modes;
-               for(unsigned int i=0; i < memb->modes.length(); i++)
-                       ms.append(" ").append(user->nick);
-
-               except_list.insert(user);
-               this->WriteAllExcept(user, !ServerInstance->Config->CycleHostsFromUser, 0, except_list, "MODE %s +%s", this->name.c_str(), ms.c_str());
-       }
+       ClientProtocol::Events::Join joinevent(memb);
+       this->Write(joinevent, 0, except_list);
 
        FOREACH_MOD(OnPostJoin, (memb));
        return memb;
@@ -397,7 +388,8 @@ bool Channel::PartUser(User* user, std::string& reason)
        CUList except_list;
        FOREACH_MOD(OnUserPart, (memb, reason, except_list));
 
-       WriteAllExcept(user, false, 0, except_list, "PART %s%s%s", this->name.c_str(), reason.empty() ? "" : " :", reason.c_str());
+       ClientProtocol::Messages::Part partmsg(memb, reason);
+       Write(ServerInstance->GetRFCEvents().part, partmsg, 0, except_list);
 
        // Remove this channel from the user's chanlist
        user->chans.erase(memb);
@@ -413,73 +405,14 @@ void Channel::KickUser(User* src, const MemberMap::iterator& victimiter, const s
        CUList except_list;
        FOREACH_MOD(OnUserKick, (src, memb, reason, except_list));
 
-       User* victim = memb->user;
-       WriteAllExcept(src, false, 0, except_list, "KICK %s %s :%s", name.c_str(), victim->nick.c_str(), reason.c_str());
+       ClientProtocol::Messages::Kick kickmsg(src, memb, reason);
+       Write(ServerInstance->GetRFCEvents().kick, kickmsg, 0, except_list);
 
-       victim->chans.erase(memb);
+       memb->user->chans.erase(memb);
        this->DelUser(victimiter);
 }
 
-void Channel::WriteChannel(User* user, const char* text, ...)
-{
-       std::string textbuffer;
-       VAFORMAT(textbuffer, text, text);
-       this->WriteChannel(user, textbuffer);
-}
-
-void Channel::WriteChannel(User* user, const std::string &text)
-{
-       const std::string message = ":" + user->GetFullHost() + " " + text;
-
-       for (MemberMap::iterator i = userlist.begin(); i != userlist.end(); i++)
-       {
-               if (IS_LOCAL(i->first))
-                       i->first->Write(message);
-       }
-}
-
-void Channel::WriteChannelWithServ(const std::string& ServName, const char* text, ...)
-{
-       std::string textbuffer;
-       VAFORMAT(textbuffer, text, text);
-       this->WriteChannelWithServ(ServName, textbuffer);
-}
-
-void Channel::WriteChannelWithServ(const std::string& ServName, const std::string &text)
-{
-       const std::string message = ":" + (ServName.empty() ? ServerInstance->Config->ServerName : ServName) + " " + text;
-
-       for (MemberMap::iterator i = userlist.begin(); i != userlist.end(); i++)
-       {
-               if (IS_LOCAL(i->first))
-                       i->first->Write(message);
-       }
-}
-
-/* write formatted text from a source user to all users on a channel except
- * for the sender (for privmsg etc) */
-void Channel::WriteAllExceptSender(User* user, bool serversource, char status, const char* text, ...)
-{
-       std::string textbuffer;
-       VAFORMAT(textbuffer, text, text);
-       this->WriteAllExceptSender(user, serversource, status, textbuffer);
-}
-
-void Channel::WriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const char* text, ...)
-{
-       std::string textbuffer;
-       VAFORMAT(textbuffer, text, text);
-       textbuffer = ":" + (serversource ? ServerInstance->Config->ServerName : user->GetFullHost()) + " " + textbuffer;
-       this->RawWriteAllExcept(user, serversource, status, except_list, textbuffer);
-}
-
-void Channel::WriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const std::string &text)
-{
-       const std::string message = ":" + (serversource ? ServerInstance->Config->ServerName : user->GetFullHost()) + " " + text;
-       this->RawWriteAllExcept(user, serversource, status, except_list, message);
-}
-
-void Channel::RawWriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const std::string &out)
+void Channel::Write(ClientProtocol::Event& protoev, char status, const CUList& except_list)
 {
        unsigned int minrank = 0;
        if (status)
@@ -490,24 +423,18 @@ void Channel::RawWriteAllExcept(User* user, bool serversource, char status, CULi
        }
        for (MemberMap::iterator i = userlist.begin(); i != userlist.end(); i++)
        {
-               if (IS_LOCAL(i->first) && (except_list.find(i->first) == except_list.end()))
+               LocalUser* user = IS_LOCAL(i->first);
+               if ((user) && (!except_list.count(user)))
                {
                        /* User doesn't have the status we're after */
                        if (minrank && i->second->getRank() < minrank)
                                continue;
 
-                       i->first->Write(out);
+                       user->Send(protoev);
                }
        }
 }
 
-void Channel::WriteAllExceptSender(User* user, bool serversource, char status, const std::string& text)
-{
-       CUList except_list;
-       except_list.insert(user);
-       this->WriteAllExcept(user, serversource, status, except_list, std::string(text));
-}
-
 const char* Channel::ChanModes(bool showkey)
 {
        static std::string scratch;
@@ -545,9 +472,8 @@ const char* Channel::ChanModes(bool showkey)
 
 void Channel::WriteNotice(const std::string& text)
 {
-       std::string rawmsg = "NOTICE ";
-       rawmsg.append(this->name).append(" :").append(text);
-       WriteChannelWithServ(ServerInstance->Config->ServerName, rawmsg);
+       ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, this, text, MSG_NOTICE);
+       Write(ServerInstance->GetRFCEvents().privmsg, privmsg);
 }
 
 /* returns the status character for a given user on a channel, e.g. @ for op,
@@ -628,7 +554,10 @@ bool Membership::SetPrefix(PrefixMode* delta_mh, bool adding)
 
 void Membership::WriteNotice(const std::string& text) const
 {
-       std::string rawmsg = "NOTICE ";
-       rawmsg.append(chan->name).append(" :").append(text);
-       user->WriteServ(rawmsg);
+       LocalUser* const localuser = IS_LOCAL(user);
+       if (!localuser)
+               return;
+
+       ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, this->chan, text, MSG_NOTICE);
+       localuser->Send(ServerInstance->GetRFCEvents().privmsg, privmsg);
 }
diff --git a/src/clientprotocol.cpp b/src/clientprotocol.cpp
new file mode 100644 (file)
index 0000000..a732855
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ *   Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd.  InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+
+ClientProtocol::Serializer::Serializer(Module* mod, const char* Name)
+       : DataProvider(mod, std::string("serializer/") + Name)
+       , evprov(mod, "event/messagetag")
+{
+}
+
+bool ClientProtocol::Serializer::HandleTag(LocalUser* user, const std::string& tagname, std::string& tagvalue, TagMap& tags) const
+{
+       // Catch and block empty tags
+       if (tagname.empty())
+               return false;
+
+       const ::Events::ModuleEventProvider::SubscriberList& list = evprov.GetSubscribers();
+       for (::Events::ModuleEventProvider::SubscriberList::const_iterator i = list.begin(); i != list.end(); ++i)
+       {
+               MessageTagProvider* const tagprov = static_cast<MessageTagProvider*>(*i);
+               const ModResult res = tagprov->OnClientProtocolProcessTag(user, tagname, tagvalue);
+               if (res == MOD_RES_ALLOW)
+                       return tags.insert(std::make_pair(tagname, MessageTagData(tagprov, tagvalue))).second;
+               else if (res == MOD_RES_DENY)
+                       break;
+       }
+
+       // No module handles the tag but that's not an error
+       return true;
+}
+
+ClientProtocol::TagSelection ClientProtocol::Serializer::MakeTagWhitelist(LocalUser* user, const TagMap& tagmap) const
+{
+       TagSelection tagwl;
+       for (TagMap::const_iterator i = tagmap.begin(); i != tagmap.end(); ++i)
+       {
+               const MessageTagData& tagdata = i->second;
+               if (tagdata.tagprov->ShouldSendTag(user, tagdata))
+                       tagwl.Select(tagmap, i);
+       }
+       return tagwl;
+}
+
+const ClientProtocol::SerializedMessage& ClientProtocol::Serializer::SerializeForUser(LocalUser* user, Message& msg)
+{
+       if (!msg.msginit_done)
+       {
+               msg.msginit_done = true;
+               FOREACH_MOD_CUSTOM(evprov, MessageTagProvider, OnClientProtocolPopulateTags, (msg));
+       }
+       return msg.GetSerialized(Message::SerializedInfo(this, MakeTagWhitelist(user, msg.GetTags())));
+}
+
+const ClientProtocol::SerializedMessage& ClientProtocol::Message::GetSerialized(const SerializedInfo& serializeinfo) const
+{
+       // First check if the serialized line they're asking for is in the cache
+       for (SerializedList::const_iterator i = serlist.begin(); i != serlist.end(); ++i)
+       {
+               const SerializedInfo& curr = i->first;
+               if (curr == serializeinfo)
+                       return i->second;
+       }
+
+       // Not cached, generate it and put it in the cache for later use
+       serlist.push_back(std::make_pair(serializeinfo, serializeinfo.serializer->Serialize(*this, serializeinfo.tagwl)));
+       return serlist.back().second;
+}
+
+void ClientProtocol::Event::GetMessagesForUser(LocalUser* user, MessageList& messagelist)
+{
+       if (!eventinit_done)
+       {
+               eventinit_done = true;
+               FOREACH_MOD_CUSTOM(*event, EventHook, OnEventInit, (*this));
+       }
+
+       // Most of the time there's only a single message but in rare cases there are more
+       if (initialmsg)
+               messagelist.assign(1, initialmsg);
+       else
+               messagelist = *initialmsglist;
+
+       // Let modules modify the message list
+       ModResult res;
+       FIRST_MOD_RESULT_CUSTOM(*event, EventHook, OnPreEventSend, res, (user, *this, messagelist));
+       if (res == MOD_RES_DENY)
+               messagelist.clear();
+}
index c133c475e333c1fbe5a602dc6691b012813adad4..503630d537eaa5f0b409fcd73c76922237a94587 100644 (file)
@@ -93,7 +93,8 @@ bool CommandParser::LoopCall(User* user, Command* handler, const CommandBase::Pa
                                new_parameters[extra] = item;
                        }
 
-                       CmdResult result = handler->Handle(user, new_parameters);
+                       CommandBase::Params params(new_parameters, parameters.GetTags());
+                       CmdResult result = handler->Handle(user, params);
                        if (localuser)
                        {
                                // Run the OnPostCommand hook with the last parameter (original line) being empty
@@ -152,14 +153,16 @@ CmdResult CommandParser::CallHandler(const std::string& commandname, const Comma
                        {
                                if (cmd)
                                        *cmd = n->second;
-                               return n->second->Handle(user, parameters);
+
+                               ClientProtocol::TagMap tags;
+                               return n->second->Handle(user, CommandBase::Params(parameters, tags));
                        }
                }
        }
        return CMD_INVALID;
 }
 
-void CommandParser::ProcessCommand(LocalUser* user, std::string& command, Command::Params& command_p)
+void CommandParser::ProcessCommand(LocalUser* user, std::string& command, CommandBase::Params& command_p)
 {
        /* find the command, check it exists */
        Command* handler = GetHandler(command);
@@ -369,44 +372,14 @@ void Command::RegisterService()
 
 void CommandParser::ProcessBuffer(LocalUser* user, const std::string& buffer)
 {
-       size_t start = buffer.find_first_not_of(" ");
-       if (start == std::string::npos)
-       {
-               // Discourage the user from flooding the server.
-               user->CommandFloodPenalty += 2000;
+       ClientProtocol::ParseOutput parseoutput;
+       if (!user->serializer->Parse(user, buffer, parseoutput))
                return;
-       }
-
-       ServerInstance->Logs->Log("USERINPUT", LOG_RAWIO, "C[%s] I %s", user->uuid.c_str(), buffer.c_str());
-
-       irc::tokenstream tokens(buffer, start);
-       std::string command;
-       CommandBase::Params parameters;
-
-       // Get the command name. This will always exist because of the check
-       // at the start of the function.
-       tokens.GetMiddle(command);
 
-       // If this exists then the client sent a prefix as part of their
-       // message. Section 2.3 of RFC 1459 technically says we should only
-       // allow the nick of the client here but in practise everyone just
-       // ignores it so we will copy them.
-       if (command[0] == ':' && !tokens.GetMiddle(command))
-       {
-               // Discourage the user from flooding the server.
-               user->CommandFloodPenalty += 2000;
-               return;
-       }
-
-       // We upper-case the command name to ensure consistency internally.
+       std::string& command = parseoutput.cmd;
        std::transform(command.begin(), command.end(), command.begin(), ::toupper);
 
-       // Build the parameter map. We intentionally do not respect the RFC 1459
-       // thirteen parameter limit here.
-       std::string parameter;
-       while (tokens.GetTrailing(parameter))
-               parameters.push_back(parameter);
-
+       CommandBase::Params parameters(parseoutput.params, parseoutput.tags);
        ProcessCommand(user, command, parameters);
 }
 
@@ -425,7 +398,7 @@ CommandParser::CommandParser()
 {
 }
 
-std::string CommandParser::TranslateUIDs(const std::vector<TranslateType>& to, const std::vector<std::string>& source, bool prefix_final, CommandBase* custom_translator)
+std::string CommandParser::TranslateUIDs(const std::vector<TranslateType>& to, const CommandBase::Params& source, bool prefix_final, CommandBase* custom_translator)
 {
        std::vector<TranslateType>::const_iterator types = to.begin();
        std::string dest;
index 52d349f5cac46166b1e32f59c1649336955bea3d..6c7cb492afe9a6a8f4828a374b9fae33c9a1595c 100644 (file)
@@ -415,7 +415,6 @@ void ServerConfig::Fill()
        HideULineKills = security->getBool("hideulinekills");
        GenericOper = security->getBool("genericoper");
        SyntaxHints = options->getBool("syntaxhints");
-       CycleHostsFromUser = options->getBool("cyclehostsfromuser");
        FullHostInTopic = options->getBool("hostintopic");
        MaxTargets = security->getUInt("maxtargets", 20, 1, 31);
        DefaultModes = options->getString("defaultmodes", "not");
index 89a2f6b691aba152346164d388ebcbaa17111bb0..1b480aa20051481c1862cb585c2edae9810f7b5f 100644 (file)
@@ -114,10 +114,12 @@ CmdResult CommandInvite::Handle(User* user, const Params& parameters)
                        }
                }
 
-               if (IS_LOCAL(u))
+               LocalUser* const localtargetuser = IS_LOCAL(u);
+               if (localtargetuser)
                {
-                       invapi.Create(IS_LOCAL(u), c, timeout);
-                       u->WriteFrom(user,"INVITE %s :%s",u->nick.c_str(),c->name.c_str());
+                       invapi.Create(localtargetuser, c, timeout);
+                       ClientProtocol::Messages::Invite invitemsg(user, localtargetuser, c);
+                       localtargetuser->Send(ServerInstance->GetRFCEvents().invite, invitemsg);
                }
 
                if (IS_LOCAL(user))
@@ -156,7 +158,11 @@ CmdResult CommandInvite::Handle(User* user, const Params& parameters)
                FOREACH_MOD(OnUserInvite, (user, u, c, timeout, minrank, excepts));
 
                if (announceinvites != Invite::ANNOUNCE_NONE)
-                       c->WriteAllExcept(user, true, prefix, excepts, "NOTICE %s :*** %s invited %s into the channel", c->name.c_str(), user->nick.c_str(), u->nick.c_str());
+               {
+                       excepts.insert(user);
+                       ClientProtocol::Messages::Privmsg privmsg(ServerInstance->FakeClient, c, InspIRCd::Format("*** %s invited %s into the channel", user->nick.c_str(), u->nick.c_str()), MSG_NOTICE);
+                       c->Write(ServerInstance->GetRFCEvents().privmsg, privmsg, prefix, excepts);
+               }
        }
        else if (IS_LOCAL(user))
        {
index ccf4d1a6e1c64695b1ecf1a632054066036bbed5..4e49ba2b4a6ff1b41e095e3d82ec4bb89fc4ba5f 100644 (file)
 #include "invite.h"
 #include "listmode.h"
 
+namespace
+{
+/** Hook that sends a MODE after a JOIN if the user in the JOIN has some modes prefix set.
+ * This happens e.g. when modules such as operprefix explicitly set prefix modes on the joining
+ * user, or when a member with prefix modes does a host cycle.
+ */
+class JoinHook : public ClientProtocol::EventHook
+{
+       ClientProtocol::Messages::Mode modemsg;
+       Modes::ChangeList modechangelist;
+       const User* joininguser;
+
+ public:
+       /** If true, MODE changes after JOIN will be sourced from the user, rather than the server
+        */
+       bool modefromuser;
+
+       JoinHook(Module* mod)
+               : ClientProtocol::EventHook(mod, "JOIN")
+       {
+       }
+
+       void OnEventInit(const ClientProtocol::Event& ev) CXX11_OVERRIDE
+       {
+               const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev);
+               const Membership& memb = *join.GetMember();
+
+               modechangelist.clear();
+               for (std::string::const_iterator i = memb.modes.begin(); i != memb.modes.end(); ++i)
+               {
+                       PrefixMode* const pm = ServerInstance->Modes.FindPrefixMode(*i);
+                       if (!pm)
+                               continue; // Shouldn't happen
+                       modechangelist.push_add(pm, memb.user->nick);
+               }
+
+               if (modechangelist.empty())
+               {
+                       // Member got no modes on join
+                       joininguser = NULL;
+                       return;
+               }
+
+               joininguser = memb.user;
+
+               // Prepare a mode protocol event that we can append to the message list in OnPreEventSend()
+               modemsg.SetParams(memb.chan, NULL, modechangelist);
+               if (modefromuser)
+                       modemsg.SetSource(join);
+               else
+                       modemsg.SetSourceUser(ServerInstance->FakeClient);
+       }
+
+       ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE
+       {
+               // If joininguser is NULL then they didn't get any modes on join, skip.
+               // Also don't show their own modes to them, they get that in the NAMES list not via MODE.
+               if ((joininguser) && (user != joininguser))
+                       messagelist.push_back(&modemsg);
+               return MOD_RES_PASSTHRU;
+       }
+};
+
+}
+
 class CoreModChannel : public Module, public CheckExemption::EventListener
 {
        Invite::APIImpl invapi;
@@ -30,6 +95,8 @@ class CoreModChannel : public Module, public CheckExemption::EventListener
        CommandKick cmdkick;
        CommandNames cmdnames;
        CommandTopic cmdtopic;
+       Events::ModuleEventProvider evprov;
+       JoinHook joinhook;
 
        ModeChannelBan banmode;
        SimpleChannelModeHandler inviteonlymode;
@@ -62,6 +129,8 @@ class CoreModChannel : public Module, public CheckExemption::EventListener
                , cmdkick(this)
                , cmdnames(this)
                , cmdtopic(this)
+               , evprov(this, "event/channel")
+               , joinhook(this)
                , banmode(this)
                , inviteonlymode(this, "inviteonly", 'i')
                , keymode(this)
@@ -88,6 +157,8 @@ class CoreModChannel : public Module, public CheckExemption::EventListener
                                ServerInstance->Modules.Detach(events[i], this);
                }
 
+               joinhook.modefromuser = optionstag->getBool("cyclehostsfromuser");
+
                std::string current;
                irc::spacesepstream defaultstream(optionstag->getString("exemptchanops"));
                insp::flat_map<std::string, char> exempts;
index d10732952e6a8b1d549fefe5c1e2bd3e28127d1b..b25fe24076e23d66605ed14ce6c1461b722a42aa 100644 (file)
@@ -39,7 +39,8 @@ static void QuitAll()
 
 void DieRestart::SendError(const std::string& message)
 {
-       const std::string unregline = "ERROR :" + message;
+       ClientProtocol::Messages::Error errormsg(message);
+       ClientProtocol::Event errorevent(ServerInstance->GetRFCEvents().error, errormsg);
        const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
        for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i)
        {
@@ -51,7 +52,7 @@ void DieRestart::SendError(const std::string& message)
                else
                {
                        // Unregistered connections receive ERROR, not a NOTICE
-                       user->Write(unregline);
+                       user->Send(errorevent);
                }
        }
 }
index fd6729f53d429dd253da934db6e945c839522b5d..5572e5789f7f79014df847ef6637686bf3cdb70f 100644 (file)
 
 CommandKill::CommandKill(Module* parent)
        : Command(parent, "KILL", 2, 2)
+       , protoev(parent, name)
 {
        flags_needed = 'o';
        syntax = "<nickname> <reason>";
        TRANSLATE2(TR_CUSTOM, TR_CUSTOM);
 }
 
+class KillMessage : public ClientProtocol::Message
+{
+ public:
+       KillMessage(ClientProtocol::EventProvider& protoev, User* user, LocalUser* target, const std::string& text)
+               : ClientProtocol::Message("KILL", NULL)
+       {
+               if (ServerInstance->Config->HideKillsServer.empty())
+                       SetSourceUser(user);
+               else
+                       SetSource(ServerInstance->Config->HideKillsServer);
+
+               PushParamRef(target->nick);
+               PushParamRef(text);
+       }
+};
 
 /** Handle /KILL
  */
@@ -100,10 +116,10 @@ CmdResult CommandKill::Handle(User* user, const Params& parameters)
 
        if (IS_LOCAL(target))
        {
-               target->Write(":%s KILL %s :%s",
-                               ServerInstance->Config->HideKillsServer.empty() ? user->GetFullHost().c_str() : ServerInstance->Config->HideKillsServer.c_str(),
-                               target->nick.c_str(),
-                               parameters[1].c_str());
+               LocalUser* localu = IS_LOCAL(target);
+               KillMessage msg(protoev, user, localu, killreason);
+               ClientProtocol::Event killevent(protoev, msg);
+               localu->Send(killevent);
 
                this->lastuuid.clear();
        }
index b069c34b2a270f65f62f3c2d0e9529aa227e6be9..db8c4161c8672ee9253587fc868cf5f660b8d4dd 100644 (file)
@@ -60,6 +60,7 @@ class CommandKill : public Command
 {
        std::string lastuuid;
        std::string killreason;
+       ClientProtocol::EventProvider protoev;
 
  public:
        /** Constructor for kill.
@@ -67,8 +68,8 @@ class CommandKill : public Command
        CommandKill(Module* parent);
 
        /** Handle command.
-        * @param parameters The parameters to the command
-        * @param user The user issuing the command
+        * @param user User issuing the command
+        * @param parameters Parameters to the command
         * @return A value from CmdResult to indicate command success or failure.
         */
        CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE;
@@ -87,8 +88,8 @@ class CommandOper : public SplitCommand
        CommandOper(Module* parent);
 
        /** Handle command.
-        * @param parameters The parameters to the command
-        * @param user The user issuing the command
+        * @param user User issuing the command
+        * @param parameters Parameters to the command
         * @return A value from CmdResult to indicate command success or failure.
         */
        CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE;
@@ -104,8 +105,8 @@ class CommandRehash : public Command
        CommandRehash(Module* parent);
 
        /** Handle command.
-        * @param parameters The parameters to the command
-        * @param user The user issuing the command
+        * @param user User issuing the command
+        * @param parameters Parameters to the command
         * @return A value from CmdResult to indicate command success or failure.
         */
        CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE;
@@ -121,8 +122,8 @@ class CommandRestart : public Command
        CommandRestart(Module* parent);
 
        /** Handle command.
-        * @param parameters The parameters to the command
-        * @param user The user issuing the command
+        * @param user User issuing the command
+        * @param parameters Parameters to the command
         * @return A value from CmdResult to indicate command success or failure.
         */
        CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE;
index 6078f5297dcc2b5195a67091af0188e219ceaa53..2daeef3ad6636ac6d852b1b1df4596eb111c7c42 100644 (file)
 
 #include "inspircd.h"
 
-namespace
-{
-       const char* MessageTypeString[] = { "PRIVMSG", "NOTICE" };
-}
-
 class MessageCommandBase : public Command
 {
        ChanModeReference moderatedmode;
@@ -35,12 +30,13 @@ class MessageCommandBase : public Command
         * @param user User sending the message
         * @param msg The message to send
         * @param mt Type of the message (MSG_PRIVMSG or MSG_NOTICE)
+        * @param tags Message tags to include in the outgoing protocol message
         */
-       static void SendAll(User* user, const std::string& msg, MessageType mt);
+       static void SendAll(User* user, const std::string& msg, MessageType mt, const ClientProtocol::TagMap& tags);
 
  public:
        MessageCommandBase(Module* parent, MessageType mt)
-               : Command(parent, MessageTypeString[mt], 2, 2)
+               : Command(parent, ClientProtocol::Messages::Privmsg::CommandStrFromMsgType(mt), 2, 2)
                , moderatedmode(parent, "moderated")
                , noextmsgmode(parent, "noextmsg")
        {
@@ -52,7 +48,7 @@ class MessageCommandBase : public Command
         * @param user The user issuing the command
         * @return A value from CmdResult to indicate command success or failure.
         */
-       CmdResult HandleMessage(User* user, const CommandBase::Params& parameters, MessageType mt);
+       CmdResult HandleMessage(User* user, const Params& parameters, MessageType mt);
 
        RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE
        {
@@ -64,18 +60,22 @@ class MessageCommandBase : public Command
        }
 };
 
-void MessageCommandBase::SendAll(User* user, const std::string& msg, MessageType mt)
+void MessageCommandBase::SendAll(User* user, const std::string& msg, MessageType mt, const ClientProtocol::TagMap& tags)
 {
-       const std::string message = ":" + user->GetFullHost() + " " + MessageTypeString[mt] + " $* :" + msg;
+       ClientProtocol::Messages::Privmsg message(ClientProtocol::Messages::Privmsg::nocopy, user, "$*", msg, mt);
+       message.AddTags(tags);
+       message.SetSideEffect(true);
+       ClientProtocol::Event messageevent(ServerInstance->GetRFCEvents().privmsg, message);
+
        const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
        for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i)
        {
                if ((*i)->registered == REG_ALL)
-                       (*i)->Write(message);
+                       (*i)->Send(messageevent);
        }
 }
 
-CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Params& parameters, MessageType mt)
+CmdResult MessageCommandBase::HandleMessage(User* user, const Params& parameters, MessageType mt)
 {
        User *dest;
        Channel *chan;
@@ -94,7 +94,7 @@ CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Param
 
                std::string servername(parameters[0], 1);
                MessageTarget msgtarget(&servername);
-               MessageDetails msgdetails(mt, parameters[1]);
+               MessageDetails msgdetails(mt, parameters[1], parameters.GetTags());
 
                ModResult MOD_RESULT;
                FIRST_MOD_RESULT(OnUserPreMessage, MOD_RESULT, (user, msgtarget, msgdetails));
@@ -107,7 +107,7 @@ CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Param
                FOREACH_MOD(OnUserMessage, (user, msgtarget, msgdetails));
                if (InspIRCd::Match(ServerInstance->Config->ServerName, servername, NULL))
                {
-                       SendAll(user, msgdetails.text, mt);
+                       SendAll(user, msgdetails.text, mt, msgdetails.tags_out);
                }
                FOREACH_MOD(OnUserPostMessage, (user, msgtarget, msgdetails));
                return CMD_SUCCESS;
@@ -153,7 +153,7 @@ CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Param
                        }
 
                        MessageTarget msgtarget(chan, status);
-                       MessageDetails msgdetails(mt, parameters[1]);
+                       MessageDetails msgdetails(mt, parameters[1], parameters.GetTags());
                        msgdetails.exemptions.insert(user);
 
                        ModResult MOD_RESULT;
@@ -173,14 +173,10 @@ CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Param
 
                        FOREACH_MOD(OnUserMessage, (user, msgtarget, msgdetails));
 
-                       if (status)
-                       {
-                               chan->WriteAllExcept(user, false, status, msgdetails.exemptions, "%s %c%s :%s", MessageTypeString[mt], status, chan->name.c_str(), msgdetails.text.c_str());
-                       }
-                       else
-                       {
-                               chan->WriteAllExcept(user, false, status, msgdetails.exemptions, "%s %s :%s", MessageTypeString[mt], chan->name.c_str(), msgdetails.text.c_str());
-                       }
+                       ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, chan, msgdetails.text, msgdetails.type, msgtarget.status);
+                       privmsg.AddTags(msgdetails.tags_out);
+                       privmsg.SetSideEffect(true);
+                       chan->Write(ServerInstance->GetRFCEvents().privmsg, privmsg, msgtarget.status, msgdetails.exemptions);
 
                        FOREACH_MOD(OnUserPostMessage, (user, msgtarget, msgdetails));
                }
@@ -233,7 +229,8 @@ CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Param
                }
 
                MessageTarget msgtarget(dest);
-               MessageDetails msgdetails(mt, parameters[1]);
+               MessageDetails msgdetails(mt, parameters[1], parameters.GetTags());
+
 
                ModResult MOD_RESULT;
                FIRST_MOD_RESULT(OnUserPreMessage, MOD_RESULT, (user, msgtarget, msgdetails));
@@ -245,10 +242,14 @@ CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Param
 
                FOREACH_MOD(OnUserMessage, (user, msgtarget, msgdetails));
 
-               if (IS_LOCAL(dest))
+               LocalUser* const localtarget = IS_LOCAL(dest);
+               if (localtarget)
                {
                        // direct write, same server
-                       dest->WriteFrom(user, "%s %s :%s", MessageTypeString[mt], dest->nick.c_str(), msgdetails.text.c_str());
+                       ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, localtarget->nick, msgdetails.text, mt);
+                       privmsg.AddTags(msgdetails.tags_out);
+                       privmsg.SetSideEffect(true);
+                       localtarget->Send(ServerInstance->GetRFCEvents().privmsg, privmsg);
                }
 
                FOREACH_MOD(OnUserPostMessage, (user, msgtarget, msgdetails));
index 23be33af8d327ad9757bc37eef28f42a2d3117cc..383d574bfe3c077798d22c24116532cf36378e91 100644 (file)
 #include "modules/reload.h"
 
 static Events::ModuleEventProvider* reloadevprov;
+static ClientProtocol::Serializer* dummyserializer;
+
+class DummySerializer : public ClientProtocol::Serializer
+{
+       bool Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput) CXX11_OVERRIDE
+       {
+               return false;
+       }
+
+       ClientProtocol::SerializedMessage Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const CXX11_OVERRIDE
+       {
+               return ClientProtocol::SerializedMessage();
+       }
+
+ public:
+       DummySerializer(Module* mod)
+               : ClientProtocol::Serializer(mod, "dummy")
+       {
+       }
+};
 
 class CommandReloadmodule : public Command
 {
        Events::ModuleEventProvider evprov;
+       DummySerializer dummyser;
+
  public:
        /** Constructor for reloadmodule.
         */
        CommandReloadmodule(Module* parent)
                : Command(parent, "RELOADMODULE", 1)
                , evprov(parent, "event/reloadmodule")
+               , dummyser(parent)
        {
                reloadevprov = &evprov;
+               dummyserializer = &dummyser;
                flags_needed = 'o';
                syntax = "<modulename>";
        }
@@ -62,6 +86,7 @@ class DataKeeper
                {
                        ModeHandler* mh;
                        ExtensionItem* extitem;
+                       ClientProtocol::Serializer* serializer;
                };
 
                ProviderInfo(ModeHandler* mode)
@@ -75,6 +100,12 @@ class DataKeeper
                        , extitem(ei)
                {
                }
+
+               ProviderInfo(ClientProtocol::Serializer* ser)
+                       : itemname(ser->name)
+                       , serializer(ser)
+               {
+               }
        };
 
        struct InstanceData
@@ -143,7 +174,17 @@ class DataKeeper
        };
 
        // Data saved for each user
-       typedef OwnedModesExts UserData;
+       struct UserData : public OwnedModesExts
+       {
+               static const size_t UNUSED_INDEX = (size_t)-1;
+               size_t serializerindex;
+
+               UserData(User* user, size_t serializeridx)
+                       : OwnedModesExts(user->uuid)
+                       , serializerindex(serializeridx)
+               {
+               }
+       };
 
        /** Module being reloaded
         */
@@ -157,6 +198,10 @@ class DataKeeper
         */
        std::vector<ProviderInfo> handledexts;
 
+       /** Stores all serializers provided by the module
+        */
+       std::vector<ProviderInfo> handledserializers;
+
        /** Stores all of the module data related to users
         */
        std::vector<UserData> userdatalist;
@@ -172,6 +217,14 @@ class DataKeeper
        void SaveExtensions(Extensible* extensible, std::vector<InstanceData>& extdatalist);
        void SaveMemberData(Channel* chan, std::vector<ChanData::MemberData>& memberdatalist);
        static void SaveListModes(Channel* chan, ListModeBase* lm, size_t index, ModesExts& currdata);
+       size_t SaveSerializer(User* user);
+
+       /** Get the index of a ProviderInfo representing the serializer in the handledserializers list.
+        * If the serializer is not already in the list it is added.
+        * @param serializer Serializer to get an index to.
+        * @return Index of the ProviderInfo representing the serializer.
+        */
+       size_t GetSerializerIndex(ClientProtocol::Serializer* serializer);
 
        void CreateModeList(ModeType modetype);
        void DoSaveUsers();
@@ -186,6 +239,10 @@ class DataKeeper
         */
        void LinkModes(ModeType modetype);
 
+       /** Link previously saved serializer names to currently available Serializers
+        */
+       void LinkSerializers();
+
        void DoRestoreUsers();
        void DoRestoreChans();
        void DoRestoreModules();
@@ -213,6 +270,15 @@ class DataKeeper
         */
        void RestoreModes(const std::vector<InstanceData>& list, ModeType modetype, Modes::ChangeList& modechange);
 
+       /** Restore previously saved serializer on a User.
+        * Quit the user if the serializer cannot be restored.
+        * @param serializerindex Saved serializer index to restore.
+        * @param user User whose serializer to restore. If not local then calling this method is a no-op.
+        * @return True if the serializer didn't need restoring or was restored successfully.
+        * False if the serializer should have been restored but the required serializer is unavailable and the user was quit.
+        */
+       bool RestoreSerializer(size_t serializerindex, User* user);
+
        /** Restore all modes and extensions of all members on a channel
         * @param chan Channel whose members are being restored
         * @param memberdata Data to restore
@@ -262,16 +328,44 @@ void DataKeeper::DoSaveUsers()
                // Serialize all extensions attached to the User
                SaveExtensions(user, currdata.extlist);
 
+               // Save serializer name if applicable and get an index to it
+               size_t serializerindex = SaveSerializer(user);
+
                // Add to list if the user has any modes or extensions set that we are interested in, otherwise we don't
                // have to do anything with this user when restoring
-               if (!currdata.empty())
+               if ((!currdata.empty()) || (serializerindex != UserData::UNUSED_INDEX))
                {
-                       userdatalist.push_back(UserData(user->uuid));
+                       userdatalist.push_back(UserData(user, serializerindex));
                        userdatalist.back().swap(currdata);
                }
        }
 }
 
+size_t DataKeeper::GetSerializerIndex(ClientProtocol::Serializer* serializer)
+{
+       for (size_t i = 0; i < handledserializers.size(); i++)
+       {
+               if (handledserializers[i].serializer == serializer)
+                       return i;
+       }
+
+       handledserializers.push_back(ProviderInfo(serializer));
+       return handledserializers.size()-1;
+}
+
+size_t DataKeeper::SaveSerializer(User* user)
+{
+       LocalUser* const localuser = IS_LOCAL(user);
+       if ((!localuser) || (!localuser->serializer))
+               return UserData::UNUSED_INDEX;
+       if (localuser->serializer->creator != mod)
+               return UserData::UNUSED_INDEX;
+
+       const size_t serializerindex = GetSerializerIndex(localuser->serializer);
+       localuser->serializer = dummyserializer;
+       return serializerindex;
+}
+
 void DataKeeper::SaveExtensions(Extensible* extensible, std::vector<InstanceData>& extdata)
 {
        const Extensible::ExtensibleStore& setexts = extensible->GetExtList();
@@ -456,6 +550,16 @@ void DataKeeper::LinkExtensions()
        }
 }
 
+void DataKeeper::LinkSerializers()
+{
+       for (std::vector<ProviderInfo>::iterator i = handledserializers.begin(); i != handledserializers.end(); ++i)
+       {
+               ProviderInfo& item = *i;
+               item.serializer = ServerInstance->Modules.FindDataService<ClientProtocol::Serializer>(item.itemname);
+               VerifyServiceProvider(item.serializer, "Serializer");
+       }
+}
+
 void DataKeeper::Restore(Module* newmod)
 {
        this->mod = newmod;
@@ -464,6 +568,7 @@ void DataKeeper::Restore(Module* newmod)
        LinkExtensions();
        LinkModes(MODETYPE_USER);
        LinkModes(MODETYPE_CHANNEL);
+       LinkSerializers();
 
        // Restore
        DoRestoreUsers();
@@ -505,6 +610,30 @@ void DataKeeper::RestoreModes(const std::vector<InstanceData>& list, ModeType mo
        }
 }
 
+bool DataKeeper::RestoreSerializer(size_t serializerindex, User* user)
+{
+       if (serializerindex == UserData::UNUSED_INDEX)
+               return true;
+
+       // The following checks are redundant
+       LocalUser* const localuser = IS_LOCAL(user);
+       if (!localuser)
+               return true;
+       if (localuser->serializer != dummyserializer)
+               return true;
+
+       const ProviderInfo& provinfo = handledserializers[serializerindex];
+       if (!provinfo.serializer)
+       {
+               // Users cannot exist without a serializer
+               ServerInstance->Users.QuitUser(user, "Serializer lost in reload");
+               return false;
+       }
+
+       localuser->serializer = provinfo.serializer;
+       return true;
+}
+
 void DataKeeper::DoRestoreUsers()
 {
        ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Restoring user data");
@@ -520,6 +649,10 @@ void DataKeeper::DoRestoreUsers()
                        continue;
                }
 
+               // Attempt to restore serializer first, if it fails it's a fatal error and RestoreSerializer() quits them
+               if (!RestoreSerializer(userdata.serializerindex, user))
+                       continue;
+
                RestoreObj(userdata, user, MODETYPE_USER, modechange);
                ServerInstance->Modes.Process(ServerInstance->FakeClient, NULL, user, modechange, ModeParser::MODE_LOCALONLY);
                modechange.clear();
diff --git a/src/coremods/core_serialize_rfc.cpp b/src/coremods/core_serialize_rfc.cpp
new file mode 100644 (file)
index 0000000..afa914f
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ *   Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd.  InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+
+class RFCSerializer : public ClientProtocol::Serializer
+{
+       /** Maximum size of the message tags portion of the message, including the `@` and the trailing space characters.
+        */
+       static const std::string::size_type MAX_MESSAGE_TAG_LENGTH = 512;
+
+       static void SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line);
+
+ public:
+       RFCSerializer(Module* mod)
+               : ClientProtocol::Serializer(mod, "rfc")
+       {
+       }
+
+       bool Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput) CXX11_OVERRIDE;
+       ClientProtocol::SerializedMessage Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const CXX11_OVERRIDE;
+};
+
+bool RFCSerializer::Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput)
+{
+       size_t start = line.find_first_not_of(" ");
+       if (start == std::string::npos)
+       {
+               // Discourage the user from flooding the server.
+               user->CommandFloodPenalty += 2000;
+               return false;
+       }
+
+       ServerInstance->Logs->Log("USERINPUT", LOG_RAWIO, "C[%s] I %s", user->uuid.c_str(), line.c_str());
+
+       irc::tokenstream tokens(line, start);
+       std::string token;
+
+       // This will always exist because of the check at the start of the function.
+       tokens.GetMiddle(token);
+       if (token[0] == '@')
+       {
+               // Line begins with message tags, parse them.
+               std::string tagval;
+               irc::sepstream ss(token.substr(1), ';');
+               while (ss.GetToken(token))
+               {
+                       // Two or more tags with the same key must not be sent, but if a client violates that we accept
+                       // the first occurence of duplicate tags and ignore all later occurences.
+                       //
+                       // Another option is to reject the message entirely but there is no standard way of doing that.
+                       const std::string::size_type p = token.find('=');
+                       if (p != std::string::npos)
+                       {
+                               // Tag has a value
+                               tagval.assign(token, p+1, std::string::npos);
+                               token.erase(p);
+                       }
+                       else
+                               tagval.clear();
+
+                       HandleTag(user, token, tagval, parseoutput.tags);
+               }
+
+
+               // Try to read the prefix or command name.
+               if (!tokens.GetMiddle(token))
+               {
+                       // Discourage the user from flooding the server.
+                       user->CommandFloodPenalty += 2000;
+                       return false;
+               }
+       }
+
+       if (token[0] == ':')
+       {
+               // If this exists then the client sent a prefix as part of their
+               // message. Section 2.3 of RFC 1459 technically says we should only
+               // allow the nick of the client here but in practise everyone just
+               // ignores it so we will copy them.
+
+               // Try to read the command name.
+               if (!tokens.GetMiddle(token))
+               {
+                       // Discourage the user from flooding the server.
+                       user->CommandFloodPenalty += 2000;
+                       return false;
+               }
+       }
+
+       parseoutput.cmd.assign(token);
+
+       // Build the parameter map. We intentionally do not respect the RFC 1459
+       // thirteen parameter limit here.
+       while (tokens.GetTrailing(token))
+               parseoutput.params.push_back(token);
+
+       return true;
+}
+
+void RFCSerializer::SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line)
+{
+       char prefix = '@'; // First tag name is prefixed with a '@'
+       for (ClientProtocol::TagMap::const_iterator i = tags.begin(); i != tags.end(); ++i)
+       {
+               if (!tagwl.IsSelected(tags, i))
+                       continue;
+
+               const std::string::size_type prevsize = line.size();
+               line.push_back(prefix);
+               prefix = ';'; // Remaining tags are prefixed with ';'
+               line.append(i->first);
+               const std::string& val = i->second.value;
+               if (!val.empty())
+               {
+                       line.push_back('=');
+                       line.append(val);
+               }
+
+               // The tags part of the message mustn't grow longer than what is allowed by the spec. If it does,
+               // remove last tag and stop adding more tags.
+               //
+               // One is subtracted from the limit before comparing because there must be a ' ' char after the last tag
+               // which also counts towards the limit.
+               if (line.size() > MAX_MESSAGE_TAG_LENGTH-1)
+               {
+                       line.erase(prevsize);
+                       break;
+               }
+       }
+
+       if (!line.empty())
+               line.push_back(' ');
+}
+
+ClientProtocol::SerializedMessage RFCSerializer::Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const
+{
+       std::string line;
+       SerializeTags(msg.GetTags(), tagwl, line);
+
+       // Save position for length calculation later
+       const std::string::size_type rfcmsg_begin = line.size();
+
+       if (msg.GetSource())
+       {
+               line.push_back(':');
+               line.append(*msg.GetSource());
+               line.push_back(' ');
+       }
+       line.append(msg.GetCommand());
+
+       const ClientProtocol::Message::ParamList& params = msg.GetParams();
+       if (!params.empty())
+       {
+               for (ClientProtocol::Message::ParamList::const_iterator i = params.begin(); i != params.end()-1; ++i)
+               {
+                       const std::string& param = *i;
+                       line.push_back(' ');
+                       line.append(param);
+               }
+
+               line.append(" :", 2).append(params.back());
+       }
+
+       // Truncate if too long
+       std::string::size_type maxline = ServerInstance->Config->Limits.MaxLine - 2;
+       if (line.length() - rfcmsg_begin > maxline)
+               line.erase(rfcmsg_begin + maxline);
+
+       line.append("\r\n", 2);
+       return line;
+}
+
+class ModuleCoreRFCSerializer : public Module
+{
+       RFCSerializer rfcserializer;
+
+ public:
+       ModuleCoreRFCSerializer()
+               : rfcserializer(this)
+       {
+       }
+
+       void OnCleanup(ExtensionItem::ExtensibleType type, Extensible* item) CXX11_OVERRIDE
+       {
+               if (type != ExtensionItem::EXT_USER)
+                       return;
+
+               LocalUser* const user = IS_LOCAL(static_cast<User*>(item));
+               if ((user) && (user->serializer == &rfcserializer))
+                       ServerInstance->Users.QuitUser(user, "Protocol serializer module unloading");
+       }
+
+       void OnUserInit(LocalUser* user) CXX11_OVERRIDE
+       {
+               if (!user->serializer)
+                       user->serializer = &rfcserializer;
+       }
+
+       Version GetVersion()
+       {
+               return Version("RFC client protocol serializer and unserializer", VF_CORE|VF_VENDOR);
+       }
+};
+
+MODULE_INIT(ModuleCoreRFCSerializer)
index 6e4e547c16f148668b2018657336f27d7752611b..e4e7a5056d899b7adebad0ec3efafee9b4e6f73c 100644 (file)
@@ -57,13 +57,13 @@ class CommandPass : public SplitCommand
 
 /** Handle /PING.
  */
-class CommandPing : public Command
+class CommandPing : public SplitCommand
 {
  public:
        /** Constructor for ping.
         */
        CommandPing(Module* parent)
-               : Command(parent, "PING", 1, 2)
+               : SplitCommand(parent, "PING", 1, 2)
        {
                syntax = "<servername> [:<servername>]";
        }
@@ -73,9 +73,10 @@ class CommandPing : public Command
         * @param user The user issuing the command
         * @return A value from CmdResult to indicate command success or failure.
         */
-       CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE
+       CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE
        {
-               user->WriteServ("PONG %s :%s", ServerInstance->Config->ServerName.c_str(), parameters[0].c_str());
+               ClientProtocol::Messages::Pong pong(parameters[0]);
+               user->Send(ServerInstance->GetRFCEvents().pong, pong);
                return CMD_SUCCESS;
        }
 };
index 856fcea747293cf470c878a6ec5f0c44909128bf..26a00a47daa49de8b6bf9357d11a07a74bf0ae91 100644 (file)
@@ -25,6 +25,7 @@
 class CommandWallops : public Command
 {
        SimpleUserModeHandler wallopsmode;
+       ClientProtocol::EventProvider protoevprov;
 
  public:
        /** Constructor for wallops.
@@ -32,6 +33,7 @@ class CommandWallops : public Command
        CommandWallops(Module* parent)
                : Command(parent, "WALLOPS", 1, 1)
                , wallopsmode(parent, "wallops", 'w')
+               , protoevprov(parent, name)
        {
                flags_needed = 'o';
                syntax = "<any-text>";
@@ -52,15 +54,16 @@ class CommandWallops : public Command
 
 CmdResult CommandWallops::Handle(User* user, const Params& parameters)
 {
-       std::string wallop("WALLOPS :");
-       wallop.append(parameters[0]);
+       ClientProtocol::Message msg("WALLOPS", user);
+       msg.PushParamRef(parameters[0]);
+       ClientProtocol::Event wallopsevent(protoevprov, msg);
 
        const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
        for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i)
        {
-               User* t = *i;
-               if (t->IsModeSet(wallopsmode))
-                       t->WriteFrom(user, wallop);
+               LocalUser* curr = *i;
+               if (curr->IsModeSet(wallopsmode))
+                       curr->Send(wallopsevent);
        }
 
        return CMD_SUCCESS;
index bb27c718ef4202e69bfc78f499dd826423f63a3f..aa3fb961231739a593cc4de41798b79fb43be319 100644 (file)
@@ -271,6 +271,16 @@ InspIRCd::InspIRCd(int argc, char** argv) :
        srandom(TIME.tv_nsec ^ TIME.tv_sec);
 #endif
 
+       {
+               ServiceProvider* provs[] =
+               {
+                       &rfcevents.numeric, &rfcevents.join, &rfcevents.part, &rfcevents.kick, &rfcevents.quit, &rfcevents.nick,
+                       &rfcevents.mode, &rfcevents.topic, &rfcevents.privmsg, &rfcevents.invite, &rfcevents.ping, &rfcevents.pong,
+                       &rfcevents.error
+               };
+               Modules.AddServices(provs, sizeof(provs)/sizeof(provs[0]));
+       }
+
        struct option longopts[] =
        {
                { "nofork",     no_argument,            &do_nofork,     1       },
index 9d17f5be8b8eda5cae8a329fd550ef2d3d6d6b08..71fce24d82658e3935d58c77e7758f9a382ed7b3 100644 (file)
@@ -438,14 +438,9 @@ void ModeParser::Process(User* user, Channel* targetchannel, User* targetuser, M
 
 unsigned int ModeParser::ProcessSingle(User* user, Channel* targetchannel, User* targetuser, Modes::ChangeList& changelist, ModeProcessFlag flags, unsigned int beginindex)
 {
-       LastParse.clear();
        LastChangeList.clear();
 
        unsigned int modes_processed = 0;
-       std::string output_mode;
-       std::string output_parameters;
-
-       char output_pm = '\0'; // current output state, '+' or '-'
        Modes::ChangeList::List& list = changelist.getlist();
        for (Modes::ChangeList::List::iterator i = list.begin()+beginindex; i != list.end(); ++i)
        {
@@ -478,43 +473,30 @@ unsigned int ModeParser::ProcessSingle(User* user, Channel* targetchannel, User*
                if (ma != MODEACTION_ALLOW)
                        continue;
 
-               char needed_pm = item.adding ? '+' : '-';
-               if (needed_pm != output_pm)
-               {
-                       output_pm = needed_pm;
-                       output_mode.append(1, output_pm);
-               }
-               output_mode.push_back(mh->GetModeChar());
-
-               if (!item.param.empty())
-               {
-                       output_parameters.push_back(' ');
-                       output_parameters.append(item.param);
-               }
                LastChangeList.push(mh, item.adding, item.param);
 
-               if ((output_mode.length() + output_parameters.length() > 450)
-                               || (output_mode.length() > 100)
-                               || (LastChangeList.size() >= ServerInstance->Config->Limits.MaxModes))
+               if (LastChangeList.size() >= ServerInstance->Config->Limits.MaxModes)
                {
                        /* mode sequence is getting too long */
                        break;
                }
        }
 
-       if (!output_mode.empty())
+       if (!LastChangeList.empty())
        {
-               LastParse = targetchannel ? targetchannel->name : targetuser->nick;
-               LastParse.append(" ");
-               LastParse.append(output_mode);
-               LastParse.append(output_parameters);
-
+               ClientProtocol::Events::Mode modeevent(user, targetchannel, targetuser, LastChangeList);
                if (targetchannel)
-                       targetchannel->WriteChannel(user, "MODE " + LastParse);
+               {
+                       targetchannel->Write(modeevent);
+               }
                else
-                       targetuser->WriteFrom(user, "MODE " + LastParse);
+               {
+                       LocalUser* localtarget = IS_LOCAL(targetuser);
+                       if (localtarget)
+                               localtarget->Send(modeevent);
+               }
 
-               FOREACH_MOD(OnMode, (user, targetuser, targetchannel, LastChangeList, flags, output_mode));
+               FOREACH_MOD(OnMode, (user, targetuser, targetchannel, LastChangeList, flags));
        }
 
        return modes_processed;
index 8f2d874dcbc0d9e34ae3d470d0660a1ca025ce82..7912fb569ac962c59867c3bf51679e4cf1bc9e7b 100644 (file)
@@ -79,7 +79,7 @@ void          Module::OnUserPart(Membership*, std::string&, CUList&) { DetachEvent(I_OnU
 void           Module::OnPreRehash(User*, const std::string&) { DetachEvent(I_OnPreRehash); }
 void           Module::OnModuleRehash(User*, const std::string&) { DetachEvent(I_OnModuleRehash); }
 ModResult      Module::OnUserPreJoin(LocalUser*, Channel*, const std::string&, std::string&, const std::string&) { DetachEvent(I_OnUserPreJoin); return MOD_RES_PASSTHRU; }
-void           Module::OnMode(User*, User*, Channel*, const Modes::ChangeList&, ModeParser::ModeProcessFlag, const std::string&) { DetachEvent(I_OnMode); }
+void           Module::OnMode(User*, User*, Channel*, const Modes::ChangeList&, ModeParser::ModeProcessFlag) { DetachEvent(I_OnMode); }
 void           Module::OnOper(User*, const std::string&) { DetachEvent(I_OnOper); }
 void           Module::OnPostOper(User*, const std::string&, const std::string &) { DetachEvent(I_OnPostOper); }
 void           Module::OnPostDeoper(User*) { DetachEvent(I_OnPostDeoper); }
@@ -138,6 +138,7 @@ ModResult   Module::OnSendWhoLine(User*, const std::vector<std::string>&, User*, M
 void           Module::OnSetUserIP(LocalUser*) { DetachEvent(I_OnSetUserIP); }
 void           Module::OnServiceAdd(ServiceProvider&) { DetachEvent(I_OnServiceAdd); }
 void           Module::OnServiceDel(ServiceProvider&) { DetachEvent(I_OnServiceDel); }
+ModResult      Module::OnUserWrite(LocalUser*, ClientProtocol::Message&) { DetachEvent(I_OnUserWrite); return MOD_RES_PASSTHRU; }
 
 #ifdef INSPIRCD_ENABLE_TESTSUITE
 void           Module::OnRunTestSuite() { }
index 76ccc6ebc0a0ca0df8050a6d73f852f95887451d..75ab57e9495eaaabd44954f968bf9fdf1b7eb217 100644 (file)
@@ -129,7 +129,7 @@ class ModuleAlias : public Module
                return word;
        }
 
-       std::string CreateRFCMessage(const std::string& command, Command::Params& parameters)
+       std::string CreateRFCMessage(const std::string& command, CommandBase::Params& parameters)
        {
                std::string message(command);
                for (CommandBase::Params::const_iterator iter = parameters.begin(); iter != parameters.end();)
index 7acbd2fff856a11e0ea081dbb60faa17eb9bc553..8485f1d7a450a025bb0aa4ccb134f640e8ad7072 100644 (file)
@@ -32,6 +32,29 @@ class AuditoriumMode : public SimpleChannelModeHandler
        }
 };
 
+class ModuleAuditorium;
+
+namespace
+{
+
+/** Hook handler for join client protocol events.
+ * This allows us to block join protocol events completely, including all associated messages (e.g. MODE, away-notify AWAY).
+ * This is not the same as OnUserJoin() because that runs only when a real join happens but this runs also when a module
+ * such as delayjoin or hostcycle generates a join.
+ */
+class JoinHook : public ClientProtocol::EventHook
+{
+       ModuleAuditorium* const parentmod;
+       bool active;
+
+ public:
+       JoinHook(ModuleAuditorium* mod);
+       void OnEventInit(const ClientProtocol::Event& ev) CXX11_OVERRIDE;
+       ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE;
+};
+
+}
+
 class ModuleAuditorium : public Module
 {
        CheckExemption::EventProvider exemptionprov;
@@ -39,11 +62,13 @@ class ModuleAuditorium : public Module
        bool OpsVisible;
        bool OpsCanSee;
        bool OperCanSee;
+       JoinHook joinhook;
 
  public:
        ModuleAuditorium()
                : exemptionprov(this)
                , aum(this)
+               , joinhook(this)
        {
        }
 
@@ -115,11 +140,6 @@ class ModuleAuditorium : public Module
                }
        }
 
-       void OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) CXX11_OVERRIDE
-       {
-               BuildExcept(memb, excepts);
-       }
-
        void OnUserPart(Membership* memb, std::string &partmessage, CUList& excepts) CXX11_OVERRIDE
        {
                BuildExcept(memb, excepts);
@@ -165,4 +185,25 @@ class ModuleAuditorium : public Module
        }
 };
 
+JoinHook::JoinHook(ModuleAuditorium* mod)
+       : ClientProtocol::EventHook(mod, "JOIN", 10)
+       , parentmod(mod)
+{
+}
+
+void JoinHook::OnEventInit(const ClientProtocol::Event& ev)
+{
+       const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev);
+       active = !parentmod->IsVisible(join.GetMember());
+}
+
+ModResult JoinHook::OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist)
+{
+       if (!active)
+               return MOD_RES_PASSTHRU;
+
+       const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev);
+       return ((parentmod->CanSee(user, join.GetMember())) ? MOD_RES_PASSTHRU : MOD_RES_DENY);
+}
+
 MODULE_INIT(ModuleAuditorium)
index 80e70d3e57470d89ba883edde2ada430d47d55bd..922061757306b878c418dc3c23a322fd48c5c9be 100644 (file)
@@ -341,16 +341,35 @@ void Cap::ExtItem::unserialize(SerializeFormat format, Extensible* container, co
        managerimpl->HandleReq(user, caplist);
 }
 
+class CapMessage : public Cap::MessageBase
+{
+ public:
+       CapMessage(LocalUser* user, const std::string& subcmd, const std::string& result)
+               : Cap::MessageBase(subcmd)
+       {
+               SetUser(user);
+               PushParamRef(result);
+       }
+};
+
 class CommandCap : public SplitCommand
 {
        Events::ModuleEventProvider evprov;
        Cap::ManagerImpl manager;
+       ClientProtocol::EventProvider protoevprov;
 
-       static void DisplayResult(LocalUser* user, std::string& result)
+       void DisplayResult(LocalUser* user, const std::string& subcmd, std::string& result)
        {
                if (*result.rbegin() == ' ')
                        result.erase(result.end()-1);
-               user->WriteCommand("CAP", result);
+               DisplayResult2(user, subcmd, result);
+       }
+
+       void DisplayResult2(LocalUser* user, const std::string& subcmd, const std::string& result)
+       {
+               CapMessage msg(user, subcmd, result);
+               ClientProtocol::Event ev(protoevprov, msg);
+               user->Send(ev);
        }
 
  public:
@@ -360,6 +379,7 @@ class CommandCap : public SplitCommand
                : SplitCommand(mod, "CAP", 1)
                , evprov(mod, "event/cap")
                , manager(mod, evprov)
+               , protoevprov(mod, name)
                , holdext("cap_hold", ExtensionItem::EXT_USER, mod)
        {
                works_before_reg = true;
@@ -378,9 +398,8 @@ class CommandCap : public SplitCommand
                        if (parameters.size() < 2)
                                return CMD_FAILURE;
 
-                       std::string result = (manager.HandleReq(user, parameters[1]) ? "ACK :" : "NAK :");
-                       result.append(parameters[1]);
-                       user->WriteCommand("CAP", result);
+                       const std::string replysubcmd = (manager.HandleReq(user, parameters[1]) ? "ACK" : "NAK");
+                       DisplayResult2(user, replysubcmd, parameters[1]);
                }
                else if (subcommand == "END")
                {
@@ -392,16 +411,16 @@ class CommandCap : public SplitCommand
                        if ((is_ls) && (parameters.size() > 1) && (parameters[1] == "302"))
                                manager.Set302Protocol(user);
 
-                       std::string result = subcommand + " :";
+                       std::string result;
                        // Show values only if supports v3.2 and doing LS
                        manager.HandleList(result, user, is_ls, ((is_ls) && (manager.GetProtocol(user) != Cap::CAP_LEGACY)));
-                       DisplayResult(user, result);
+                       DisplayResult(user, subcommand, result);
                }
                else if ((subcommand == "CLEAR") && (manager.GetProtocol(user) == Cap::CAP_LEGACY))
                {
-                       std::string result = "ACK :";
+                       std::string result;
                        manager.HandleClear(user, result);
-                       DisplayResult(user, result);
+                       DisplayResult(user, "ACK", result);
                }
                else
                {
index 0817311260e7a990f53c1562bff6bcb967133e57..0c39453465b88a6265884ad1f90c580924b79fb2 100644 (file)
 struct HistoryItem
 {
        time_t ts;
-       std::string line;
-       HistoryItem(const std::string& Line) : ts(ServerInstance->Time()), line(Line) {}
+       std::string text;
+       std::string sourcemask;
+
+       HistoryItem(User* source, const std::string& Text)
+               : ts(ServerInstance->Time())
+               , text(Text)
+               , sourcemask(source->GetFullHost())
+       {
+       }
 };
 
 struct HistoryList
@@ -136,8 +143,7 @@ class ModuleChanHistory : public Module
                        HistoryList* list = m.ext.get(c);
                        if (list)
                        {
-                               const std::string line = ":" + user->GetFullHost() + " PRIVMSG " + c->name + " :" + details.text;
-                               list->lines.push_back(HistoryItem(line));
+                               list->lines.push_back(HistoryItem(user, details.text));
                                if (list->lines.size() > list->maxlen)
                                        list->lines.pop_front();
                        }
@@ -146,7 +152,8 @@ class ModuleChanHistory : public Module
 
        void OnPostJoin(Membership* memb) CXX11_OVERRIDE
        {
-               if (IS_REMOTE(memb->user))
+               LocalUser* localuser = IS_LOCAL(memb->user);
+               if (!localuser)
                        return;
 
                if (memb->user->IsModeSet(botmode) && !dobots)
@@ -169,8 +176,12 @@ class ModuleChanHistory : public Module
 
                for(std::deque<HistoryItem>::iterator i = list->lines.begin(); i != list->lines.end(); ++i)
                {
-                       if (i->ts >= mintime)
-                               memb->user->Write(i->line);
+                       const HistoryItem& item = *i;
+                       if (item.ts >= mintime)
+                       {
+                               ClientProtocol::Messages::Privmsg msg(ClientProtocol::Messages::Privmsg::nocopy, item.sourcemask, memb->chan, item.text);
+                               localuser->Send(ServerInstance->GetRFCEvents().privmsg, msg);
+                       }
                }
        }
 
index f618a539cc098e920acd51042b1d1fca02ad4595..85e7ca2eb7b9d9eb4900f47db02fb7b5d088d690 100644 (file)
@@ -70,7 +70,8 @@ class ModuleChanLog : public Module
                        Channel *c = ServerInstance->FindChan(it->second);
                        if (c)
                        {
-                               c->WriteChannelWithServ(ServerInstance->Config->ServerName, "PRIVMSG %s :%s", c->name.c_str(), snotice.c_str());
+                               ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->Config->ServerName, c, snotice);
+                               c->Write(ServerInstance->GetRFCEvents().privmsg, privmsg);
                                ServerInstance->PI->SendMessage(c, 0, snotice);
                        }
                }
index c277759d174400649a2cac31721fc221ac30769f..b9ff085c3ebdc488894e4db053b3ef64cddceb98 100644 (file)
@@ -313,7 +313,13 @@ class ModuleCloaking : public Module
                if (u->IsModeSet(cu) && !cu.active)
                {
                        u->SetMode(cu, false);
-                       u->WriteCommand("MODE", "-" + ConvToStr(cu.GetModeChar()));
+
+                       if (!IS_LOCAL(u))
+                               return;
+                       Modes::ChangeList modechangelist;
+                       modechangelist.push_remove(&cu);
+                       ClientProtocol::Events::Mode modeevent(ServerInstance->FakeClient, NULL, u, modechangelist);
+                       static_cast<LocalUser*>(u)->Send(modeevent);
                }
                cu.active = false;
        }
index b4441c88cf61bf9876f3fc18e7846e85f089edd0..f2e9590c81ece0598613f0c73e28360d5cf0d2df 100644 (file)
@@ -46,8 +46,10 @@ class ModuleWaitPong : public Module
        ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE
        {
                std::string pingrpl = ServerInstance->GenRandomStr(10);
-
-               user->Write("PING :%s", pingrpl.c_str());
+               {
+                       ClientProtocol::Messages::Ping pingmsg(pingrpl);
+                       user->Send(ServerInstance->GetRFCEvents().ping, pingmsg);
+               }
 
                if(sendsnotice)
                        user->WriteNotice("*** If you are having problems connecting due to ping timeouts, please type /quote PONG " + pingrpl + " or /raw PONG " + pingrpl + " now.");
index f9cd837d7a23bd66dbcf27c11dda9727838d28c3..7c557eb35a0abdf9c31c15c59b53f7ef3749d322 100644 (file)
@@ -33,14 +33,50 @@ class DelayJoinMode : public ModeHandler
        ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding) CXX11_OVERRIDE;
 };
 
+
+namespace
+{
+
+/** Hook handler for join client protocol events.
+ * This allows us to block join protocol events completely, including all associated messages (e.g. MODE, away-notify AWAY).
+ * This is not the same as OnUserJoin() because that runs only when a real join happens but this runs also when a module
+ * such as hostcycle generates a join.
+ */
+class JoinHook : public ClientProtocol::EventHook
+{
+       const LocalIntExt& unjoined;
+
+ public:
+       JoinHook(Module* mod, const LocalIntExt& unjoinedref)
+               : ClientProtocol::EventHook(mod, "JOIN", 10)
+               , unjoined(unjoinedref)
+       {
+       }
+
+       ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE
+       {
+               const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev);
+               const User* const u = join.GetMember()->user;
+               if ((unjoined.get(u)) && (u != user))
+                       return MOD_RES_DENY;
+               return MOD_RES_PASSTHRU;
+       }
+};
+
+}
+
 class ModuleDelayJoin : public Module
 {
        DelayJoinMode djm;
+       void RevealUser(User* user, Channel* chan);
  public:
        LocalIntExt unjoined;
+       JoinHook joinhook;
+
        ModuleDelayJoin()
                : djm(this)
                , unjoined("delayjoin", ExtensionItem::EXT_MEMBERSHIP, this)
+               , joinhook(this, unjoined)
        {
        }
 
@@ -68,7 +104,7 @@ ModeAction DelayJoinMode::OnModeChange(User* source, User* dest, Channel* channe
                 * they remain permanently invisible on this channel!
                 */
                MessageTarget msgtarget(channel, 0);
-               MessageDetails msgdetails(MSG_PRIVMSG, "");
+               MessageDetails msgdetails(MSG_PRIVMSG, "", ClientProtocol::TagMap());
                const Channel::MemberMap& users = channel->GetUsers();
                for (Channel::MemberMap::const_iterator n = users.begin(); n != users.end(); ++n)
                {
@@ -111,10 +147,7 @@ static void populate(CUList& except, Membership* memb)
 void ModuleDelayJoin::OnUserJoin(Membership* memb, bool sync, bool created, CUList& except)
 {
        if (memb->chan->IsModeSet(djm))
-       {
                unjoined.set(memb, 1);
-               populate(except, memb);
-       }
 }
 
 void ModuleDelayJoin::OnUserPart(Membership* memb, std::string &partmessage, CUList& except)
@@ -147,20 +180,20 @@ void ModuleDelayJoin::OnUserMessage(User* user, const MessageTarget& target, con
                return;
 
        Channel* channel = target.Get<Channel>();
+       RevealUser(user, channel);
+}
 
-       Membership* memb = channel->GetUser(user);
+void ModuleDelayJoin::RevealUser(User* user, Channel* chan)
+{
+       Membership* memb = chan->GetUser(user);
        if (!memb || !unjoined.set(memb, 0))
                return;
 
        /* Display the join to everyone else (the user who joined got it earlier) */
-       channel->WriteAllExceptSender(user, false, 0, "JOIN %s", channel->name.c_str());
-
-       std::string ms = memb->modes;
-       for(unsigned int i=0; i < memb->modes.length(); i++)
-               ms.append(" ").append(user->nick);
-
-       if (ms.length() > 0)
-               channel->WriteAllExceptSender(user, false, 0, "MODE %s +%s", channel->name.c_str(), ms.c_str());
+       CUList except_list;
+       except_list.insert(user);
+       ClientProtocol::Events::Join joinevent(memb);
+       chan->Write(joinevent, 0, except_list);
 }
 
 /* make the user visible if he receives any mode change */
@@ -182,9 +215,7 @@ ModResult ModuleDelayJoin::OnRawMode(User* user, Channel* channel, ModeHandler*
        if (!dest)
                return MOD_RES_PASSTHRU;
 
-       Membership* memb = channel->GetUser(dest);
-       if (memb && unjoined.set(memb, 0))
-               channel->WriteAllExceptSender(dest, false, 0, "JOIN %s", channel->name.c_str());
+       RevealUser(dest, channel);
        return MOD_RES_PASSTHRU;
 }
 
index 0f7405dcc8b83f45ca93428cffeadc97c3f343c0..a3c81df6e04cbbd49bc75a631f2dca6eeb7a288e 100644 (file)
 class ModuleHostCycle : public Module
 {
        Cap::Reference chghostcap;
+       const std::string quitmsghost;
+       const std::string quitmsgident;
 
        /** Send fake quit/join/mode messages for host or ident cycle.
         */
-       void DoHostCycle(User* user, const std::string& newident, const std::string& newhost, const char* quitmsg)
+       void DoHostCycle(User* user, const std::string& newident, const std::string& newhost, const std::string& reason)
        {
-               // GetFullHost() returns the original data at the time this function is called
-               const std::string quitline = ":" + user->GetFullHost() + " QUIT :" + quitmsg;
+               // The user has the original ident/host at the time this function is called
+               ClientProtocol::Messages::Quit quitmsg(user, reason);
+               ClientProtocol::Event quitevent(ServerInstance->GetRFCEvents().quit, quitmsg);
 
                already_sent_t silent_id = ServerInstance->Users.NextAlreadySentId();
                already_sent_t seen_id = ServerInstance->Users.NextAlreadySentId();
@@ -50,7 +53,7 @@ class ModuleHostCycle : public Module
                                if (i->second)
                                {
                                        u->already_sent = seen_id;
-                                       u->Write(quitline);
+                                       u->Send(quitevent);
                                }
                                else
                                {
@@ -65,17 +68,8 @@ class ModuleHostCycle : public Module
                {
                        Membership* memb = *i;
                        Channel* c = memb->chan;
-                       const std::string joinline = ":" + newfullhost + " JOIN " + c->name;
-                       std::string modeline;
 
-                       if (!memb->modes.empty())
-                       {
-                               modeline = ":" + (ServerInstance->Config->CycleHostsFromUser ? newfullhost : ServerInstance->Config->ServerName)
-                                       + " MODE " + c->name + " +" + memb->modes;
-
-                               for (size_t j = 0; j < memb->modes.length(); j++)
-                                       modeline.append(" ").append(user->nick);
-                       }
+                       ClientProtocol::Events::Join joinevent(memb, newfullhost);
 
                        const Channel::MemberMap& ulist = c->GetUsers();
                        for (Channel::MemberMap::const_iterator j = ulist.begin(); j != ulist.end(); ++j)
@@ -90,13 +84,11 @@ class ModuleHostCycle : public Module
 
                                if (u->already_sent != seen_id)
                                {
-                                       u->Write(quitline);
+                                       u->Send(quitevent);
                                        u->already_sent = seen_id;
                                }
 
-                               u->Write(joinline);
-                               if (!memb->modes.empty())
-                                       u->Write(modeline);
+                               u->Send(joinevent);
                        }
                }
        }
@@ -104,17 +96,19 @@ class ModuleHostCycle : public Module
  public:
        ModuleHostCycle()
                : chghostcap(this, "chghost")
+               , quitmsghost("Changing host")
+               , quitmsgident("Changing ident")
        {
        }
 
        void OnChangeIdent(User* user, const std::string& newident) CXX11_OVERRIDE
        {
-               DoHostCycle(user, newident, user->GetDisplayedHost(), "Changing ident");
+               DoHostCycle(user, newident, user->GetDisplayedHost(), quitmsgident);
        }
 
        void OnChangeHost(User* user, const std::string& newhost) CXX11_OVERRIDE
        {
-               DoHostCycle(user, user->ident, newhost, "Changing host");
+               DoHostCycle(user, user->ident, newhost, quitmsghost);
        }
 
        Version GetVersion() CXX11_OVERRIDE
index 92e8a088150402e13168d716a9e85ff169969404..14b1cf8a161ee46ca601fd4a74352fb5348f4c93 100644 (file)
 #include "modules/cap.h"
 #include "modules/ircv3.h"
 
+class AwayMessage : public ClientProtocol::Message
+{
+ public:
+       AwayMessage(User* user)
+               : ClientProtocol::Message("AWAY", user)
+       {
+               SetParams(user, user->awaymsg);
+       }
+
+       AwayMessage()
+               : ClientProtocol::Message("AWAY")
+       {
+       }
+
+       void SetParams(User* user, const std::string& awaymsg)
+       {
+               // Going away: 1 parameter which is the away reason
+               // Back from away: no parameter
+               if (!awaymsg.empty())
+                       PushParam(awaymsg);
+       }
+};
+
+class JoinHook : public ClientProtocol::EventHook
+{
+       ClientProtocol::Events::Join extendedjoinmsg;
+
+ public:
+       const std::string asterisk;
+       ClientProtocol::EventProvider awayprotoev;
+       AwayMessage awaymsg;
+       Cap::Capability extendedjoincap;
+       Cap::Capability awaycap;
+
+       JoinHook(Module* mod)
+               : ClientProtocol::EventHook(mod, "JOIN")
+               , asterisk(1, '*')
+               , awayprotoev(mod, "AWAY")
+               , extendedjoincap(mod, "extended-join")
+               , awaycap(mod, "away-notify")
+       {
+       }
+
+       void OnEventInit(const ClientProtocol::Event& ev) CXX11_OVERRIDE
+       {
+               const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev);
+
+               // An extended join has two extra parameters:
+               // First the account name of the joining user or an asterisk if the user is not logged in.
+               // The second parameter is the realname of the joining user.
+
+               Membership* const memb = join.GetMember();
+               const std::string* account = &asterisk;
+               const AccountExtItem* const accountext = GetAccountExtItem();
+               if (accountext)
+               {
+                       const std::string* accountname = accountext->get(memb->user);
+                       if (accountname)
+                               account = accountname;
+               }
+
+               extendedjoinmsg.ClearParams();
+               extendedjoinmsg.SetSource(join);
+               extendedjoinmsg.PushParamRef(memb->chan->name);
+               extendedjoinmsg.PushParamRef(*account);
+               extendedjoinmsg.PushParamRef(memb->user->GetRealName());
+
+               awaymsg.ClearParams();
+               if ((memb->user->IsAway()) && (awaycap.IsActive()))
+               {
+                       awaymsg.SetSource(join);
+                       awaymsg.SetParams(memb->user, memb->user->awaymsg);
+               }
+       }
+
+       ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE
+       {
+               if (extendedjoincap.get(user))
+                       messagelist.front() = &extendedjoinmsg;
+
+               if ((!awaymsg.GetParams().empty()) && (awaycap.get(user)))
+                       messagelist.push_back(&awaymsg);
+
+               return MOD_RES_PASSTHRU;
+       }
+};
+
 class ModuleIRCv3
        : public Module
        , public AccountEventListener
        , public Away::EventListener
 {
        Cap::Capability cap_accountnotify;
-       Cap::Capability cap_awaynotify;
-       Cap::Capability cap_extendedjoin;
+       JoinHook joinhook;
 
-       CUList last_excepts;
+       ClientProtocol::EventProvider accountprotoev;
 
  public:
        ModuleIRCv3()
                : AccountEventListener(this)
                , Away::EventListener(this)
                , cap_accountnotify(this, "account-notify")
-               , cap_awaynotify(this, "away-notify")
-               , cap_extendedjoin(this, "extended-join")
+               , joinhook(this)
+               , accountprotoev(this, "ACCOUNT")
        {
        }
 
@@ -47,141 +133,41 @@ class ModuleIRCv3
        {
                ConfigTag* conf = ServerInstance->Config->ConfValue("ircv3");
                cap_accountnotify.SetActive(conf->getBool("accountnotify", true));
-               cap_awaynotify.SetActive(conf->getBool("awaynotify", true));
-               cap_extendedjoin.SetActive(conf->getBool("extendedjoin", true));
+               joinhook.awaycap.SetActive(conf->getBool("awaynotify", true));
+               joinhook.extendedjoincap.SetActive(conf->getBool("extendedjoin", true));
        }
 
        void OnAccountChange(User* user, const std::string& newaccount) CXX11_OVERRIDE
        {
-               // :nick!user@host ACCOUNT account
-               // or
-               // :nick!user@host ACCOUNT *
-               std::string line = ":" + user->GetFullHost() + " ACCOUNT ";
-               if (newaccount.empty())
-                       line += "*";
-               else
-                       line += newaccount;
-
-               IRCv3::WriteNeighborsWithCap(user, line, cap_accountnotify);
-       }
-
-       void OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) CXX11_OVERRIDE
-       {
-               // Remember who is not going to see the JOIN because of other modules
-               if ((cap_awaynotify.IsActive()) && (memb->user->IsAway()))
-                       last_excepts = excepts;
-
-               if (!cap_extendedjoin.IsActive())
-                       return;
-
-               /*
-                * Send extended joins to clients who have the extended-join capability.
-                * An extended join looks like this:
-                *
-                * :nick!user@host JOIN #chan account :realname
-                *
-                * account is the joining user's account if he's logged in, otherwise it's an asterisk (*).
-                */
-
-               std::string line;
-               std::string mode;
-
-               const Channel::MemberMap& userlist = memb->chan->GetUsers();
-               for (Channel::MemberMap::const_iterator it = userlist.begin(); it != userlist.end(); ++it)
-               {
-                       // Send the extended join line if the current member is local, has the extended-join cap and isn't excepted
-                       User* member = IS_LOCAL(it->first);
-                       if ((member) && (cap_extendedjoin.get(member)) && (excepts.find(member) == excepts.end()))
-                       {
-                               // Construct the lines we're going to send if we haven't constructed them already
-                               if (line.empty())
-                               {
-                                       bool has_account = false;
-                                       line = ":" + memb->user->GetFullHost() + " JOIN " + memb->chan->name + " ";
-                                       const AccountExtItem* accountext = GetAccountExtItem();
-                                       if (accountext)
-                                       {
-                                               std::string* accountname;
-                                               accountname = accountext->get(memb->user);
-                                               if (accountname)
-                                               {
-                                                       line += *accountname;
-                                                       has_account = true;
-                                               }
-                                       }
-
-                                       if (!has_account)
-                                               line += "*";
-
-                                       line += " :" + memb->user->GetRealName();
-
-                                       // If the joining user received privileges from another module then we must send them as well,
-                                       // since silencing the normal join means the MODE will be silenced as well
-                                       if (!memb->modes.empty())
-                                       {
-                                               const std::string& modefrom = ServerInstance->Config->CycleHostsFromUser ? memb->user->GetFullHost() : ServerInstance->Config->ServerName;
-                                               mode = ":" + modefrom + " MODE " + memb->chan->name + " +" + memb->modes;
-
-                                               for (unsigned int i = 0; i < memb->modes.length(); i++)
-                                                       mode += " " + memb->user->nick;
-                                       }
-                               }
-
-                               // Write the JOIN and the MODE, if any
-                               member->Write(line);
-                               if ((!mode.empty()) && (member != memb->user))
-                                       member->Write(mode);
-
-                               // Prevent the core from sending the JOIN and MODE to this user
-                               excepts.insert(it->first);
-                       }
-               }
+               // Logged in: 1 parameter which is the account name
+               // Logged out: 1 parameter which is a "*"
+               ClientProtocol::Message msg("ACCOUNT", user);
+               const std::string& param = (newaccount.empty() ? joinhook.asterisk : newaccount);
+               msg.PushParamRef(param);
+               ClientProtocol::Event accountevent(accountprotoev, msg);
+               IRCv3::WriteNeighborsWithCap(user, accountevent, cap_accountnotify);
        }
 
        void OnUserAway(User* user) CXX11_OVERRIDE
        {
-               if (!cap_awaynotify.IsActive())
+               if (!joinhook.awaycap.IsActive())
                        return;
 
                // Going away: n!u@h AWAY :reason
-               const std::string line = ":" + user->GetFullHost() + " AWAY :" + user->awaymsg;
-               IRCv3::WriteNeighborsWithCap(user, line, cap_awaynotify);
+               AwayMessage msg(user);
+               ClientProtocol::Event awayevent(joinhook.awayprotoev, msg);
+               IRCv3::WriteNeighborsWithCap(user, awayevent, joinhook.awaycap);
        }
 
        void OnUserBack(User* user) CXX11_OVERRIDE
        {
-               if (!cap_awaynotify.IsActive())
+               if (!joinhook.awaycap.IsActive())
                        return;
 
                // Back from away: n!u@h AWAY
-               const std::string line = ":" + user->GetFullHost() + " AWAY";
-               IRCv3::WriteNeighborsWithCap(user, line, cap_awaynotify);
-       }
-
-       void OnPostJoin(Membership *memb) CXX11_OVERRIDE
-       {
-               if ((!cap_awaynotify.IsActive()) || (!memb->user->IsAway()))
-                       return;
-
-               std::string line = ":" + memb->user->GetFullHost() + " AWAY :" + memb->user->awaymsg;
-
-               const Channel::MemberMap& userlist = memb->chan->GetUsers();
-               for (Channel::MemberMap::const_iterator it = userlist.begin(); it != userlist.end(); ++it)
-               {
-                       // Send the away notify line if the current member is local, has the away-notify cap and isn't excepted
-                       User* member = IS_LOCAL(it->first);
-                       if ((member) && (cap_awaynotify.get(member)) && (last_excepts.find(member) == last_excepts.end()) && (it->second != memb))
-                       {
-                               member->Write(line);
-                       }
-               }
-
-               last_excepts.clear();
-       }
-
-       void Prioritize() CXX11_OVERRIDE
-       {
-               ServerInstance->Modules->SetPriority(this, I_OnUserJoin, PRIORITY_LAST);
+               AwayMessage msg(user);
+               ClientProtocol::Event awayevent(joinhook.awayprotoev, msg);
+               IRCv3::WriteNeighborsWithCap(user, awayevent, joinhook.awaycap);
        }
 
        Version GetVersion() CXX11_OVERRIDE
index 93c30df123ce018e28cc4f3aa73ebf104e65ea41..757b0858f70371ca5c9e66ae6c54971d5afeb79c 100644 (file)
@@ -46,19 +46,53 @@ class CapNotify : public Cap::Capability
        }
 };
 
+class CapNotifyMessage : public Cap::MessageBase
+{
+ public:
+       CapNotifyMessage(bool add, const std::string& capname)
+               : Cap::MessageBase((add ? "NEW" : "DEL"))
+       {
+               PushParamRef(capname);
+       }
+};
+
+class CapNotifyValueMessage : public Cap::MessageBase
+{
+       std::string s;
+       const std::string::size_type pos;
+
+ public:
+       CapNotifyValueMessage(const std::string& capname)
+               : Cap::MessageBase("NEW")
+               , s(capname)
+               , pos(s.size()+1)
+       {
+               s.push_back('=');
+               PushParamRef(s);
+       }
+
+       void SetCapValue(const std::string& capvalue)
+       {
+               s.erase(pos);
+               s.append(capvalue);
+               InvalidateCache();
+       }
+};
+
 class ModuleIRCv3CapNotify : public Module, public Cap::EventListener, public ReloadModule::EventListener
 {
        CapNotify capnotify;
        std::string reloadedmod;
        std::vector<std::string> reloadedcaps;
+       ClientProtocol::EventProvider protoev;
 
        void Send(const std::string& capname, Cap::Capability* cap, bool add)
        {
-               std::string msg = (add ? "NEW :" : "DEL :");
-               msg.append(capname);
-               std::string msgwithval = msg;
-               msgwithval.push_back('=');
-               std::string::size_type msgpos = msgwithval.size();
+               CapNotifyMessage msg(add, capname);
+               CapNotifyValueMessage msgwithval(capname);
+
+               ClientProtocol::Event event(protoev, msg);
+               ClientProtocol::Event eventwithval(protoev, msgwithval);
 
                const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
                for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i)
@@ -73,13 +107,14 @@ class ModuleIRCv3CapNotify : public Module, public Cap::EventListener, public Re
                                const std::string* capvalue = cap->GetValue(user);
                                if ((capvalue) && (!capvalue->empty()))
                                {
-                                       msgwithval.append(*capvalue);
-                                       user->WriteCommand("CAP", msgwithval);
-                                       msgwithval.erase(msgpos);
+                                       msgwithval.SetUser(user);
+                                       msgwithval.SetCapValue(*capvalue);
+                                       user->Send(eventwithval);
                                        continue;
                                }
                        }
-                       user->WriteCommand("CAP", msg);
+                       msg.SetUser(user);
+                       user->Send(event);
                }
        }
 
@@ -88,6 +123,7 @@ class ModuleIRCv3CapNotify : public Module, public Cap::EventListener, public Re
                : Cap::EventListener(this)
                , ReloadModule::EventListener(this)
                , capnotify(this)
+               , protoev(this, "CAP_NOTIFY")
        {
        }
 
index 0a9e055b4d32a089de0c52f773331cc2fa251f42..aa53612cb60533113d3ed83e967a3bec95baedf3 100644 (file)
 class ModuleIRCv3ChgHost : public Module
 {
        Cap::Capability cap;
+       ClientProtocol::EventProvider protoevprov;
 
        void DoChgHost(User* user, const std::string& ident, const std::string& host)
        {
-               std::string line(1, ':');
-               line.append(user->GetFullHost()).append(" CHGHOST ").append(ident).append(1, ' ').append(host);
-               IRCv3::WriteNeighborsWithCap(user, line, cap);
+               ClientProtocol::Message msg("CHGHOST", user);
+               msg.PushParamRef(ident);
+               msg.PushParamRef(host);
+               ClientProtocol::Event protoev(protoevprov, msg);
+               IRCv3::WriteNeighborsWithCap(user, protoev, cap);
        }
 
  public:
        ModuleIRCv3ChgHost()
                : cap(this, "chghost")
+               , protoevprov(this, "CHGHOST")
        {
        }
 
index 056b02194cb46f30e45a894d27f35b9a1e55ae75..702552ea7c62a05bab7c732afbab1885c475fc6f 100644 (file)
@@ -21,8 +21,6 @@
 #include "inspircd.h"
 #include "modules/cap.h"
 
-static const char* MessageTypeStringSp[] = { "PRIVMSG ", "NOTICE " };
-
 class ModuleIRCv3EchoMessage : public Module
 {
        Cap::Capability cap;
@@ -38,27 +36,31 @@ class ModuleIRCv3EchoMessage : public Module
                if (!cap.get(user))
                        return;
 
-               std::string msg = MessageTypeStringSp[details.type];
+               // Caps are only set on local users
+               LocalUser* const localuser = static_cast<LocalUser*>(user);
+
+               const std::string& text = details.echooriginal ? details.originaltext : details.text;
                if (target.type == MessageTarget::TYPE_USER)
                {
                        User* destuser = target.Get<User>();
-                       msg.append(destuser->nick);
+                       ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, destuser, text, details.type);
+                       privmsg.AddTags(details.tags_in);
+                       localuser->Send(ServerInstance->GetRFCEvents().privmsg, privmsg);
                }
                else if (target.type == MessageTarget::TYPE_CHANNEL)
                {
-                       if (target.status)
-                               msg.push_back(target.status);
-
                        Channel* chan = target.Get<Channel>();
-                       msg.append(chan->name);
+                       ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, chan, text, details.type, target.status);
+                       privmsg.AddTags(details.tags_in);
+                       localuser->Send(ServerInstance->GetRFCEvents().privmsg, privmsg);
                }
                else
                {
                        const std::string* servername = target.Get<std::string>();
-                       msg.append(*servername);
+                       ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, *servername, text, details.type);
+                       privmsg.AddTags(details.tags_in);
+                       localuser->Send(ServerInstance->GetRFCEvents().privmsg, privmsg);
                }
-               msg.append(" :").append(details.echooriginal ? details.originaltext : details.text);
-               user->WriteFrom(user, msg);
        }
 
        void OnUserMessageBlocked(User* user, const MessageTarget& target, const MessageDetails& details) CXX11_OVERRIDE
index 3783ff33c48149e8494c984e1fabee9052dfc79c..bcb6f51d548f9a538e4dd2f4cdc0e92cbbd37f31 100644 (file)
@@ -32,8 +32,8 @@ class ModuleIRCv3InviteNotify : public Module
 
        void OnUserInvite(User* source, User* dest, Channel* chan, time_t expiry, unsigned int notifyrank, CUList& notifyexcepts) CXX11_OVERRIDE
        {
-               std::string msg = "INVITE ";
-               msg.append(dest->nick).append(1, ' ').append(chan->name);
+               ClientProtocol::Messages::Invite invitemsg(source, dest, chan);
+               ClientProtocol::Event inviteevent(ServerInstance->GetRFCEvents().invite, invitemsg);
                const Channel::MemberMap& users = chan->GetUsers();
                for (Channel::MemberMap::const_iterator i = users.begin(); i != users.end(); ++i)
                {
@@ -47,8 +47,10 @@ class ModuleIRCv3InviteNotify : public Module
                        if (memb->getRank() < notifyrank)
                                continue;
 
+                       // Caps are only set on local users
+                       LocalUser* const localuser = static_cast<LocalUser*>(user);
                        // Send and add the user to the exceptions so they won't get the NOTICE invite announcement message
-                       user->WriteFrom(source, msg);
+                       localuser->Send(inviteevent);
                        notifyexcepts.insert(user);
                }
        }
index a0a8455a8ebd76b929bcda25e4df4d29d93d23e8..1b66e01a474b32a7e30f52705e93aaa2447378d0 100644 (file)
@@ -81,7 +81,13 @@ class CommandKnock : public Command
                        c->WriteNotice(InspIRCd::Format("User %s is KNOCKing on %s (%s)", user->nick.c_str(), c->name.c_str(), parameters[1].c_str()));
 
                if (sendnumeric)
-                       c->WriteChannelWithServ(ServerInstance->Config->ServerName, "710 %s %s %s :is KNOCKing: %s", c->name.c_str(), c->name.c_str(), user->GetFullHost().c_str(), parameters[1].c_str());
+               {
+                       Numeric::Numeric numeric(710);
+                       numeric.push(c->name).push(user->GetFullHost()).push("is KNOCKing: " + parameters[1]);
+
+                       ClientProtocol::Messages::Numeric numericmsg(numeric, c->name);
+                       c->Write(ServerInstance->GetRFCEvents().numeric, numericmsg);
+               }
 
                user->WriteNotice("KNOCKing on " + c->name);
                return CMD_SUCCESS;
index 094b37744e322c057fd0428b1da0ae70ead22067..cde5b00d7878b9cc0da9e985881e4ea8d0062190 100644 (file)
@@ -48,7 +48,8 @@ class LDAPOperBase : public LDAPInterface
                CommandBase::Params params;
                params.push_back(opername);
                params.push_back(password);
-               oper_command->Handle(user, params);
+               ClientProtocol::TagMap tags;
+               oper_command->Handle(user, CommandBase::Params(params, tags));
        }
 
        void Fallback()
index e3f8624fc4ee247748ddd677d4479f87115c6545..08d3533cd120dfe5cc2c920e67a408acf7f48b3a 100644 (file)
@@ -91,8 +91,8 @@ class ModulePassForward : public Module
                }
 
                std::string tmp;
-               FormatStr(tmp,forwardmsg, user);
-               user->WriteServ(tmp);
+               FormatStr(tmp, forwardmsg, user);
+               ServerInstance->Parser.ProcessBuffer(user, tmp);
 
                tmp.clear();
                FormatStr(tmp,forwardcmd, user);
index 195b767ab462e4c47c828d6bf22cddc224415ac8..8f28a7e9cbee8825e7952973415e4f56beb01bad 100644 (file)
@@ -26,6 +26,8 @@
  */
 class CommandSamode : public Command
 {
+       bool logged;
+
  public:
        bool active;
        CommandSamode(Module* Creator) : Command(Creator,"SAMODE", 2)
@@ -55,24 +57,29 @@ class CommandSamode : public Command
                Modes::ChangeList emptychangelist;
                ServerInstance->Modes->ProcessSingle(ServerInstance->FakeClient, NULL, ServerInstance->FakeClient, emptychangelist);
 
+               logged = false;
                this->active = true;
-               CmdResult result = ServerInstance->Parser.CallHandler("MODE", parameters, user);
+               ServerInstance->Parser.CallHandler("MODE", parameters, user);
                this->active = false;
 
-               if (result == CMD_SUCCESS)
+               if (!logged)
                {
-                       // If lastparse is empty and the MODE command handler returned CMD_SUCCESS then
-                       // the client queried the list of a listmode (e.g. /SAMODE #chan b), which was
-                       // handled internally by the MODE command handler.
+                       // If we haven't logged anything yet then the client queried the list of a listmode
+                       // (e.g. /SAMODE #chan b), which was handled internally by the MODE command handler.
                        //
-                       // Viewing the modes of a user or a channel can also result in CMD_SUCCESS, but
+                       // Viewing the modes of a user or a channel could also result in this, but
                        // that is not possible with /SAMODE because we require at least 2 parameters.
-                       const std::string& lastparse = ServerInstance->Modes.GetLastParse();
-                       ServerInstance->SNO->WriteGlobalSno('a', user->nick + " used SAMODE: " + (lastparse.empty() ? stdalgo::string::join(parameters) : lastparse));
+                       LogUsage(user, stdalgo::string::join(parameters));
                }
 
                return CMD_SUCCESS;
        }
+
+       void LogUsage(const User* user, const std::string& text)
+       {
+               logged = true;
+               ServerInstance->SNO->WriteGlobalSno('a', user->nick + " used SAMODE: " + text);
+       }
 };
 
 class ModuleSaMode : public Module
@@ -96,6 +103,25 @@ class ModuleSaMode : public Module
                return MOD_RES_PASSTHRU;
        }
 
+       void OnMode(User* user, User* destuser, Channel* destchan, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags) CXX11_OVERRIDE
+       {
+               if (!cmd.active)
+                       return;
+
+               std::string logtext = (destuser ? destuser->nick : destchan->name);
+               logtext.push_back(' ');
+               logtext += ClientProtocol::Messages::Mode::ToModeLetters(modes);
+
+               for (Modes::ChangeList::List::const_iterator i = modes.getlist().begin(); i != modes.getlist().end(); ++i)
+               {
+                       const Modes::Change& item = *i;
+                       if (!item.param.empty())
+                               logtext.append(1, ' ').append(item.param);
+               }
+
+               cmd.LogUsage(user, logtext);
+       }
+
        void Prioritize() CXX11_OVERRIDE
        {
                Module *override = ServerInstance->Modules->Find("m_override.so");
index d37e1c90f8a0f93f3a50ddf891d627352b95c06d..480f8f6db0d0ebc2ee559a80b705ae77417e6f8c 100644 (file)
@@ -145,7 +145,7 @@ static Events::ModuleEventProvider* saslevprov;
 
 static void SendSASL(LocalUser* user, const std::string& agent, char mode, const std::vector<std::string>& parameters)
 {
-       CommandBase::Params params(parameters.size() + 3);
+       CommandBase::Params params;
        params.push_back(user->uuid);
        params.push_back(agent);
        params.push_back(ConvToStr(mode));
@@ -157,6 +157,8 @@ static void SendSASL(LocalUser* user, const std::string& agent, char mode, const
        }
 }
 
+static ClientProtocol::EventProvider* g_protoev;
+
 /**
  * Tracks SASL authentication state like charybdis does. --nenolod
  */
@@ -223,7 +225,15 @@ class SaslAuthenticator
                                return this->state;
 
                        if (msg[2] == "C")
-                               this->user->Write("AUTHENTICATE %s", msg[3].c_str());
+                       {
+                               ClientProtocol::Message authmsg("AUTHENTICATE");
+                               authmsg.PushParamRef(msg[3]);
+
+                               ClientProtocol::Event authevent(*g_protoev, authmsg);
+                               LocalUser* const localuser = IS_LOCAL(user);
+                               if (localuser)
+                                       localuser->Send(authevent);
+                       }
                        else if (msg[2] == "D")
                        {
                                this->state = SASL_DONE;
@@ -377,6 +387,7 @@ class ModuleSASL : public Module
        CommandAuthenticate auth;
        CommandSASL sasl;
        Events::ModuleEventProvider sasleventprov;
+       ClientProtocol::EventProvider protoev;
 
  public:
        ModuleSASL()
@@ -386,8 +397,10 @@ class ModuleSASL : public Module
                , auth(this, authExt, cap)
                , sasl(this, authExt)
                , sasleventprov(this, "event/sasl")
+               , protoev(this, auth.name)
        {
                saslevprov = &sasleventprov;
+               g_protoev = &protoev;
        }
 
        void init() CXX11_OVERRIDE
index 565aaf78b97de3d60baa0f3f9abf52326df3c602..99c54514007fcd615d1c39556e3e2d38b741d5a7 100644 (file)
@@ -63,13 +63,14 @@ class CommandShowFile : public Command
 
                        user->WriteRemoteNumeric(endnumeric, endtext.c_str());
                }
-               else
+               else if (IS_LOCAL(user))
                {
-                       const char* msgcmd = (method == SF_MSG ? "PRIVMSG" : "NOTICE");
+                       LocalUser* const localuser = IS_LOCAL(user);
                        for (file_cache::const_iterator i = contents.begin(); i != contents.end(); ++i)
                        {
                                const std::string& line = *i;
-                               user->WriteCommand(msgcmd, ":" + line);
+                               ClientProtocol::Messages::Privmsg msg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, localuser, line, ((method == SF_MSG) ? MSG_PRIVMSG : MSG_NOTICE));
+                               localuser->Send(ServerInstance->GetRFCEvents().privmsg, msg);
                        }
                }
                return CMD_SUCCESS;
index 3c90891156153fe5a9ca25dd4f41a4305e5f13d6..0ff180a8338abfcf9ef6c6831471146fa885348f 100644 (file)
@@ -731,7 +731,7 @@ void ModuleSpanningTree::OnUserBack(User* user)
                CommandAway::Builder(user).Broadcast();
 }
 
-void ModuleSpanningTree::OnMode(User* source, User* u, Channel* c, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags, const std::string& output_mode)
+void ModuleSpanningTree::OnMode(User* source, User* u, Channel* c, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags)
 {
        if (processflags & ModeParser::MODE_LOCALONLY)
                return;
@@ -743,7 +743,7 @@ void ModuleSpanningTree::OnMode(User* source, User* u, Channel* c, const Modes::
 
                CmdBuilder params(source, "MODE");
                params.push(u->uuid);
-               params.push(output_mode);
+               params.push(ClientProtocol::Messages::Mode::ToModeLetters(modes));
                params.push_raw(Translate::ModeChangeListToParams(modes.getlist()));
                params.Broadcast();
        }
@@ -752,7 +752,7 @@ void ModuleSpanningTree::OnMode(User* source, User* u, Channel* c, const Modes::
                CmdBuilder params(source, "FMODE");
                params.push(c->name);
                params.push_int(c->age);
-               params.push(output_mode);
+               params.push(ClientProtocol::Messages::Mode::ToModeLetters(modes));
                params.push_raw(Translate::ModeChangeListToParams(modes.getlist()));
                params.Broadcast();
        }
index 4a65f1c37850f56b36ffb7ae46f46ca6820146cb..60f819e9c21bf0f6214127aef25bbd1ab83396de 100644 (file)
@@ -172,7 +172,7 @@ class ModuleSpanningTree
        void OnLoadModule(Module* mod) CXX11_OVERRIDE;
        void OnUnloadModule(Module* mod) CXX11_OVERRIDE;
        ModResult OnAcceptConnection(int newsock, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE;
-       void OnMode(User* source, User* u, Channel* c, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags, const std::string& output_mode) CXX11_OVERRIDE;
+       void OnMode(User* source, User* u, Channel* c, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags) CXX11_OVERRIDE;
        CullResult cull() CXX11_OVERRIDE;
        ~ModuleSpanningTree();
        Version GetVersion() CXX11_OVERRIDE;
index 3bc1fc71a0042cffcdf3bfda82703cb914988e6d..513fa6dbf9768082cad4dc8826bb540d1f16eeea 100644 (file)
@@ -343,7 +343,8 @@ void TreeSocket::ProcessConnectedLine(std::string& prefix, std::string& command,
                res = scmd->Handle(who, params);
        else
        {
-               res = cmd->Handle(who, params);
+               ClientProtocol::TagMap tags;
+               res = cmd->Handle(who, CommandBase::Params(params, tags));
                if (res == CMD_INVALID)
                        throw ProtocolException("Error in command handler");
        }
index 2b298f66212b648e09d89f7c460ab97d8214ccd6..da538caef63876d441663302b872efaf8b27a920 100644 (file)
@@ -143,7 +143,8 @@ class OperQuery : public SQL::Query
                                return;
 
                        // Now handle /OPER.
-                       oper_command->Handle(user, params);
+                       ClientProtocol::TagMap tags;
+                       oper_command->Handle(user, CommandBase::Params(params, tags));
                }
                else
                {
index ffb84a44fee1f77631a23c35e7b77fa554e1ca0a..058028f61aa96997da25da5ae3f19778164e4472 100644 (file)
@@ -113,7 +113,6 @@ class CommandTban : public Command
                        return CMD_FAILURE;
                }
 
-               CUList tmp;
                T.mask = mask;
                T.expire = expire + (IS_REMOTE(user) ? 5 : 0);
                T.chan = channel;
@@ -124,7 +123,8 @@ class CommandTban : public Command
                PrefixMode* mh = ServerInstance->Modes->FindPrefixMode('h');
                char pfxchar = (mh && mh->name == "halfop") ? mh->GetPrefix() : '@';
 
-               channel->WriteAllExcept(ServerInstance->FakeClient, true, pfxchar, tmp, "NOTICE %s :%s", channel->name.c_str(), addban.c_str());
+               ClientProtocol::Messages::Privmsg notice(ServerInstance->FakeClient, channel, addban, MSG_NOTICE);
+               channel->Write(ServerInstance->GetRFCEvents().privmsg, notice, pfxchar);
                ServerInstance->PI->SendChannelNotice(channel, pfxchar, addban);
                return CMD_SUCCESS;
        }
@@ -210,13 +210,13 @@ class ModuleTimedBans : public Module
                        std::string mask = i->mask;
                        Channel* cr = i->chan;
                        {
-                               CUList empty;
                                const std::string expiry = "*** Timed ban on " + cr->name + " expired.";
                                // If halfop is loaded, send notice to halfops and above, otherwise send to ops and above
                                PrefixMode* mh = ServerInstance->Modes->FindPrefixMode('h');
                                char pfxchar = (mh && mh->name == "halfop") ? mh->GetPrefix() : '@';
 
-                               cr->WriteAllExcept(ServerInstance->FakeClient, true, pfxchar, empty, "NOTICE %s :%s", cr->name.c_str(), expiry.c_str());
+                               ClientProtocol::Messages::Privmsg notice(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, cr, expiry, MSG_NOTICE);
+                               cr->Write(ServerInstance->GetRFCEvents().privmsg, notice, pfxchar);
                                ServerInstance->PI->SendChannelNotice(cr, pfxchar, expiry);
 
                                Modes::ChangeList setban;
index 02c030a42434b1c1414deebe2705e8e05532e432..7466f385b565ec3895879590bca90b91c9357e61 100644 (file)
@@ -28,21 +28,23 @@ namespace
 {
        class WriteCommonQuit : public User::ForEachNeighborHandler
        {
-               std::string line;
-               std::string operline;
+               ClientProtocol::Messages::Quit quitmsg;
+               ClientProtocol::Event quitevent;
+               ClientProtocol::Messages::Quit operquitmsg;
+               ClientProtocol::Event operquitevent;
 
                void Execute(LocalUser* user) CXX11_OVERRIDE
                {
-                       user->Write(user->IsOper() ? operline : line);
+                       user->Send(user->IsOper() ? operquitevent : quitevent);
                }
 
         public:
                WriteCommonQuit(User* user, const std::string& msg, const std::string& opermsg)
-                       : line(":" + user->GetFullHost() + " QUIT :")
-                       , operline(line)
+                       : quitmsg(user, msg)
+                       , quitevent(ServerInstance->GetRFCEvents().quit, quitmsg)
+                       , operquitmsg(user, opermsg)
+                       , operquitevent(ServerInstance->GetRFCEvents().quit, operquitmsg)
                {
-                       line += msg;
-                       operline += opermsg;
                        user->ForEachNeighbor(*this, false);
                }
        };
@@ -177,7 +179,12 @@ void UserManager::QuitUser(User* user, const std::string& quitreason, const std:
        user->quitting = true;
 
        ServerInstance->Logs->Log("USERS", LOG_DEBUG, "QuitUser: %s=%s '%s'", user->uuid.c_str(), user->nick.c_str(), quitreason.c_str());
-       user->Write("ERROR :Closing link: (%s@%s) [%s]", user->ident.c_str(), user->GetRealHost().c_str(), operreason ? operreason->c_str() : quitreason.c_str());
+       LocalUser* const localuser = IS_LOCAL(user);
+       if (localuser)
+       {
+               ClientProtocol::Messages::Error errormsg(InspIRCd::Format("Closing link: (%s@%s) [%s]", user->ident.c_str(), user->GetRealHost().c_str(), operreason ? operreason->c_str() : quitreason.c_str()));
+               localuser->Send(ServerInstance->GetRFCEvents().error, errormsg);
+       }
 
        std::string reason;
        reason.assign(quitreason, 0, ServerInstance->Config->Limits.MaxQuit);
@@ -264,12 +271,13 @@ void UserManager::ServerNoticeAll(const char* text, ...)
 {
        std::string message;
        VAFORMAT(message, text, text);
-       message = "NOTICE $" + ServerInstance->Config->ServerName + " :" + message;
+       ClientProtocol::Messages::Privmsg msg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, ServerInstance->Config->ServerName, message, MSG_NOTICE);
+       ClientProtocol::Event msgevent(ServerInstance->GetRFCEvents().privmsg, msg);
 
        for (LocalList::const_iterator i = local_users.begin(); i != local_users.end(); ++i)
        {
-               User* t = *i;
-               t->WriteServ(message);
+               LocalUser* user = *i;
+               user->Send(msgevent);
        }
 }
 
@@ -320,8 +328,8 @@ void UserManager::DoBackgroundUserStuff()
                                                this->QuitUser(curr, message);
                                                continue;
                                        }
-
-                                       curr->Write("PING :" + ServerInstance->Config->ServerName);
+                                       ClientProtocol::Messages::Ping ping;
+                                       curr->Send(ServerInstance->GetRFCEvents().ping, ping);
                                        curr->lastping = 0;
                                        curr->nping = ServerInstance->Time() + curr->MyClass->GetPingTime();
                                }
index 737a3fa5ce320cb97b2fde2b6187eceb548bddbd..e05ef1853a3a435a9a93153fcdc929edfbc7a916 100644 (file)
@@ -26,6 +26,8 @@
 #include "inspircd.h"
 #include "xline.h"
 
+ClientProtocol::MessageList LocalUser::sendmsglist;
+
 bool User::IsNoticeMaskSet(unsigned char sm)
 {
        if (!isalpha(sm))
@@ -87,6 +89,7 @@ User::User(const std::string& uid, Server* srv, UserType type)
 LocalUser::LocalUser(int myfd, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* servaddr)
        : User(ServerInstance->UIDGen.GetUID(), ServerInstance->FakeClient->server, USERTYPE_LOCAL)
        , eh(this)
+       , serializer(NULL)
        , bytes_in(0)
        , bytes_out(0)
        , cmds_in(0)
@@ -359,7 +362,16 @@ void User::Oper(OperInfo* info)
 
        this->SetMode(opermh, true);
        this->oper = info;
-       this->WriteCommand("MODE", "+o");
+
+       LocalUser* localuser = IS_LOCAL(this);
+       if (localuser)
+       {
+               Modes::ChangeList changelist;
+               changelist.push_add(opermh);
+               ClientProtocol::Events::Mode modemsg(ServerInstance->FakeClient, NULL, localuser, changelist);
+               localuser->Send(modemsg);
+       }
+
        FOREACH_MOD(OnOper, (this, info->name));
 
        std::string opername;
@@ -384,7 +396,7 @@ void User::Oper(OperInfo* info)
        ServerInstance->Users->all_opers.push_back(this);
 
        // Expand permissions from config for faster lookup
-       if (IS_LOCAL(this))
+       if (localuser)
                oper->init();
 
        FOREACH_MOD(OnPostOper, (this, oper->name, opername));
@@ -573,7 +585,10 @@ void LocalUser::FullConnect()
                ServerInstance->Parser.CallHandler(command, parameters, this);
 
        if (ServerInstance->Config->RawLog)
-               WriteServ("PRIVMSG %s :*** Raw I/O logging is enabled on this server. All messages, passwords, and commands are being recorded.", nick.c_str());
+       {
+               ClientProtocol::Messages::Privmsg rawlogmsg(ServerInstance->FakeClient, this, "*** Raw I/O logging is enabled on this server. All messages, passwords, and commands are being recorded.");
+               this->Send(ServerInstance->GetRFCEvents().privmsg, rawlogmsg);
+       }
 
        /*
         * We don't set REG_ALL until triggering OnUserConnect, so some module events don't spew out stuff
@@ -651,7 +666,11 @@ bool User::ChangeNick(const std::string& newnick, time_t newts)
        }
 
        if (this->registered == REG_ALL)
-               this->WriteCommon("NICK %s", newnick.c_str());
+       {
+               ClientProtocol::Messages::Nick nickmsg(this, newnick);
+               ClientProtocol::Event nickevent(ServerInstance->GetRFCEvents().nick, nickmsg);
+               this->WriteCommonRaw(nickevent, true);
+       }
        const std::string oldnick = nick;
        nick = newnick;
 
@@ -667,7 +686,10 @@ bool User::ChangeNick(const std::string& newnick, time_t newts)
 
 void LocalUser::OverruleNick()
 {
-       this->WriteFrom(this, "NICK %s", this->uuid.c_str());
+       {
+               ClientProtocol::Messages::Nick nickmsg(this, this->uuid);
+               this->Send(ServerInstance->GetRFCEvents().nick, nickmsg);
+       }
        this->WriteNumeric(ERR_NICKNAMEINUSE, this->nick, "Nickname overruled.");
 
        // Clear the bit before calling ChangeNick() to make it NOT run the OnUserPostNick() hook
@@ -763,35 +785,24 @@ void LocalUser::SetClientIP(const irc::sockets::sockaddrs& sa, bool recheck_elin
        }
 }
 
-static std::string wide_newline("\r\n");
-
-void User::Write(const std::string& text)
-{
-}
-
-void User::Write(const char *text, ...)
-{
-}
-
-void LocalUser::Write(const std::string& text)
+void LocalUser::Write(const ClientProtocol::SerializedMessage& text)
 {
        if (!SocketEngine::BoundsCheckFd(&eh))
                return;
 
-       // The maximum size of an IRC message minus the terminating CR+LF.
-       const size_t maxmessage = ServerInstance->Config->Limits.MaxLine - 2;
-       if (text.length() > maxmessage)
+       if (ServerInstance->Config->RawLog)
        {
-               // This should happen rarely or never. Crop the string at MaxLine and try again.
-               std::string try_again(text, 0, maxmessage);
-               Write(try_again);
-               return;
-       }
+               if (text.empty())
+                       return;
+
+               std::string::size_type nlpos = text.find_first_of("\r\n", 0, 2);
+               if (nlpos == std::string::npos)
+                       nlpos = text.length(); // TODO is this ok, test it
 
-       ServerInstance->Logs->Log("USEROUTPUT", LOG_RAWIO, "C[%s] O %s", uuid.c_str(), text.c_str());
+               ServerInstance->Logs->Log("USEROUTPUT", LOG_RAWIO, "C[%s] O %.*s", uuid.c_str(), (int) nlpos, text.c_str());
+       }
 
        eh.AddWriteBuf(text);
-       eh.AddWriteBuf(wide_newline);
 
        const size_t bytessent = text.length() + 2;
        ServerInstance->stats.Sent += bytessent;
@@ -799,53 +810,47 @@ void LocalUser::Write(const std::string& text)
        this->cmds_out++;
 }
 
-/** Write()
- */
-void LocalUser::Write(const char *text, ...)
-{
-       std::string textbuffer;
-       VAFORMAT(textbuffer, text, text);
-       this->Write(textbuffer);
-}
-
-void User::WriteServ(const std::string& text)
-{
-       this->Write(":%s %s",ServerInstance->Config->ServerName.c_str(),text.c_str());
-}
-
-/** WriteServ()
- *  Same as Write(), except `text' is prefixed with `:server.name '.
- */
-void User::WriteServ(const char* text, ...)
+void LocalUser::Send(ClientProtocol::Event& protoev)
 {
-       std::string textbuffer;
-       VAFORMAT(textbuffer, text, text);
-       this->WriteServ(textbuffer);
-}
+       if (!serializer)
+               return;
 
-void User::WriteCommand(const char* command, const std::string& text)
-{
-       this->WriteServ(command + (this->registered & REG_NICK ? " " + this->nick : " *") + " " + text);
+       // In the most common case a static LocalUser field, sendmsglist, is passed to the event to be
+       // populated. The list is cleared before returning.
+       // To handle re-enters, if sendmsglist is non-empty upon entering the method then a temporary
+       // list is used instead of the static one.
+       if (sendmsglist.empty())
+       {
+               Send(protoev, sendmsglist);
+               sendmsglist.clear();
+       }
+       else
+       {
+               ClientProtocol::MessageList msglist;
+               Send(protoev, msglist);
+       }
 }
 
-namespace
+void LocalUser::Send(ClientProtocol::Event& protoev, ClientProtocol::MessageList& msglist)
 {
-       std::string BuildNumeric(const std::string& source, User* targetuser, unsigned int num, const Command::Params& params)
+       // Modules can personalize the messages sent per user for the event
+       protoev.GetMessagesForUser(this, msglist);
+       for (ClientProtocol::MessageList::const_iterator i = msglist.begin(); i != msglist.end(); ++i)
        {
-               const char* const target = (targetuser->registered & REG_NICK ? targetuser->nick.c_str() : "*");
-               std::string raw = InspIRCd::Format(":%s %03u %s", source.c_str(), num, target);
-               if (!params.empty())
-               {
-                       for (std::vector<std::string>::const_iterator i = params.begin(); i != params.end()-1; ++i)
-                               raw.append(1, ' ').append(*i);
-                       raw.append(" :").append(params.back());
-               }
-               return raw;
+               ClientProtocol::Message& curr = **i;
+               ModResult res;
+               FIRST_MOD_RESULT(OnUserWrite, res, (this, curr));
+               if (res != MOD_RES_DENY)
+                       Write(serializer->SerializeForUser(this, curr));
        }
 }
 
 void User::WriteNumeric(const Numeric::Numeric& numeric)
 {
+       LocalUser* const localuser = IS_LOCAL(this);
+       if (!localuser)
+               return;
+
        ModResult MOD_RESULT;
 
        FIRST_MOD_RESULT(OnNumeric, MOD_RESULT, (this, numeric));
@@ -853,24 +858,8 @@ void User::WriteNumeric(const Numeric::Numeric& numeric)
        if (MOD_RESULT == MOD_RES_DENY)
                return;
 
-       const std::string& servername = (numeric.GetServer() ? numeric.GetServer()->GetName() : ServerInstance->Config->ServerName);
-       this->Write(BuildNumeric(servername, this, numeric.GetNumeric(), numeric.GetParams()));
-}
-
-void User::WriteFrom(User *user, const std::string &text)
-{
-       const std::string message = ":" + user->GetFullHost() + " " + text;
-       this->Write(message);
-}
-
-
-/* write text from an originating user to originating user */
-
-void User::WriteFrom(User *user, const char* text, ...)
-{
-       std::string textbuffer;
-       VAFORMAT(textbuffer, text, text);
-       this->WriteFrom(user, textbuffer);
+       ClientProtocol::Messages::Numeric numericmsg(numeric, localuser);
+       localuser->Send(ServerInstance->GetRFCEvents().numeric, numericmsg);
 }
 
 void User::WriteRemoteNotice(const std::string& text)
@@ -887,32 +876,24 @@ namespace
 {
        class WriteCommonRawHandler : public User::ForEachNeighborHandler
        {
-               const std::string& msg;
+               ClientProtocol::Event& ev;
 
                void Execute(LocalUser* user) CXX11_OVERRIDE
                {
-                       user->Write(msg);
+                       user->Send(ev);
                }
 
         public:
-               WriteCommonRawHandler(const std::string& message)
-                       : msg(message)
+               WriteCommonRawHandler(ClientProtocol::Event& protoev)
+                       : ev(protoev)
                {
                }
        };
 }
 
-void User::WriteCommon(const char* text, ...)
+void User::WriteCommonRaw(ClientProtocol::Event& protoev, bool include_self)
 {
-       std::string textbuffer;
-       VAFORMAT(textbuffer, text, text);
-       textbuffer = ":" + this->GetFullHost() + " " + textbuffer;
-       this->WriteCommonRaw(textbuffer, true);
-}
-
-void User::WriteCommonRaw(const std::string &line, bool include_self)
-{
-       WriteCommonRawHandler handler(line);
+       WriteCommonRawHandler handler(protoev);
        ForEachNeighbor(handler, include_self);
 }
 
@@ -1203,6 +1184,16 @@ void User::PurgeEmptyChannels()
        this->UnOper();
 }
 
+void User::WriteNotice(const std::string& text)
+{
+       LocalUser* const localuser = IS_LOCAL(this);
+       if (!localuser)
+               return;
+
+       ClientProtocol::Messages::Privmsg msg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, localuser, text, MSG_NOTICE);
+       localuser->Send(ServerInstance->GetRFCEvents().privmsg, msg);
+}
+
 const std::string& FakeUser::GetFullHost()
 {
        if (!ServerInstance->Config->HideServer.empty())