]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/coremods/core_serialize_rfc.cpp
Implement IRCv3 message tag support.
[user/henk/code/inspircd.git] / src / coremods / core_serialize_rfc.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com>
5  *
6  * This file is part of InspIRCd.  InspIRCd is free software: you can
7  * redistribute it and/or modify it under the terms of the GNU General Public
8  * License as published by the Free Software Foundation, version 2.
9  *
10  * This program is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
13  * details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19
20 #include "inspircd.h"
21
22 class RFCSerializer : public ClientProtocol::Serializer
23 {
24         /** Maximum size of the message tags portion of the message, including the `@` and the trailing space characters.
25          */
26         static const std::string::size_type MAX_MESSAGE_TAG_LENGTH = 512;
27
28         static void SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line);
29
30  public:
31         RFCSerializer(Module* mod)
32                 : ClientProtocol::Serializer(mod, "rfc")
33         {
34         }
35
36         bool Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput) CXX11_OVERRIDE;
37         ClientProtocol::SerializedMessage Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const CXX11_OVERRIDE;
38 };
39
40 bool RFCSerializer::Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput)
41 {
42         size_t start = line.find_first_not_of(" ");
43         if (start == std::string::npos)
44         {
45                 // Discourage the user from flooding the server.
46                 user->CommandFloodPenalty += 2000;
47                 return false;
48         }
49
50         ServerInstance->Logs->Log("USERINPUT", LOG_RAWIO, "C[%s] I %s", user->uuid.c_str(), line.c_str());
51
52         irc::tokenstream tokens(line, start);
53         std::string token;
54
55         // This will always exist because of the check at the start of the function.
56         tokens.GetMiddle(token);
57         if (token[0] == '@')
58         {
59                 // Line begins with message tags, parse them.
60                 std::string tagval;
61                 irc::sepstream ss(token.substr(1), ';');
62                 while (ss.GetToken(token))
63                 {
64                         // Two or more tags with the same key must not be sent, but if a client violates that we accept
65                         // the first occurence of duplicate tags and ignore all later occurences.
66                         //
67                         // Another option is to reject the message entirely but there is no standard way of doing that.
68                         const std::string::size_type p = token.find('=');
69                         if (p != std::string::npos)
70                         {
71                                 // Tag has a value
72                                 tagval.assign(token, p+1, std::string::npos);
73                                 token.erase(p);
74                         }
75                         else
76                                 tagval.clear();
77
78                         HandleTag(user, token, tagval, parseoutput.tags);
79                 }
80
81
82                 // Try to read the prefix or command name.
83                 if (!tokens.GetMiddle(token))
84                 {
85                         // Discourage the user from flooding the server.
86                         user->CommandFloodPenalty += 2000;
87                         return false;
88                 }
89         }
90
91         if (token[0] == ':')
92         {
93                 // If this exists then the client sent a prefix as part of their
94                 // message. Section 2.3 of RFC 1459 technically says we should only
95                 // allow the nick of the client here but in practise everyone just
96                 // ignores it so we will copy them.
97
98                 // Try to read the command name.
99                 if (!tokens.GetMiddle(token))
100                 {
101                         // Discourage the user from flooding the server.
102                         user->CommandFloodPenalty += 2000;
103                         return false;
104                 }
105         }
106
107         parseoutput.cmd.assign(token);
108
109         // Build the parameter map. We intentionally do not respect the RFC 1459
110         // thirteen parameter limit here.
111         while (tokens.GetTrailing(token))
112                 parseoutput.params.push_back(token);
113
114         return true;
115 }
116
117 void RFCSerializer::SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line)
118 {
119         char prefix = '@'; // First tag name is prefixed with a '@'
120         for (ClientProtocol::TagMap::const_iterator i = tags.begin(); i != tags.end(); ++i)
121         {
122                 if (!tagwl.IsSelected(tags, i))
123                         continue;
124
125                 const std::string::size_type prevsize = line.size();
126                 line.push_back(prefix);
127                 prefix = ';'; // Remaining tags are prefixed with ';'
128                 line.append(i->first);
129                 const std::string& val = i->second.value;
130                 if (!val.empty())
131                 {
132                         line.push_back('=');
133                         line.append(val);
134                 }
135
136                 // The tags part of the message mustn't grow longer than what is allowed by the spec. If it does,
137                 // remove last tag and stop adding more tags.
138                 //
139                 // One is subtracted from the limit before comparing because there must be a ' ' char after the last tag
140                 // which also counts towards the limit.
141                 if (line.size() > MAX_MESSAGE_TAG_LENGTH-1)
142                 {
143                         line.erase(prevsize);
144                         break;
145                 }
146         }
147
148         if (!line.empty())
149                 line.push_back(' ');
150 }
151
152 ClientProtocol::SerializedMessage RFCSerializer::Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const
153 {
154         std::string line;
155         SerializeTags(msg.GetTags(), tagwl, line);
156
157         // Save position for length calculation later
158         const std::string::size_type rfcmsg_begin = line.size();
159
160         if (msg.GetSource())
161         {
162                 line.push_back(':');
163                 line.append(*msg.GetSource());
164                 line.push_back(' ');
165         }
166         line.append(msg.GetCommand());
167
168         const ClientProtocol::Message::ParamList& params = msg.GetParams();
169         if (!params.empty())
170         {
171                 for (ClientProtocol::Message::ParamList::const_iterator i = params.begin(); i != params.end()-1; ++i)
172                 {
173                         const std::string& param = *i;
174                         line.push_back(' ');
175                         line.append(param);
176                 }
177
178                 line.append(" :", 2).append(params.back());
179         }
180
181         // Truncate if too long
182         std::string::size_type maxline = ServerInstance->Config->Limits.MaxLine - 2;
183         if (line.length() - rfcmsg_begin > maxline)
184                 line.erase(rfcmsg_begin + maxline);
185
186         line.append("\r\n", 2);
187         return line;
188 }
189
190 class ModuleCoreRFCSerializer : public Module
191 {
192         RFCSerializer rfcserializer;
193
194  public:
195         ModuleCoreRFCSerializer()
196                 : rfcserializer(this)
197         {
198         }
199
200         void OnCleanup(ExtensionItem::ExtensibleType type, Extensible* item) CXX11_OVERRIDE
201         {
202                 if (type != ExtensionItem::EXT_USER)
203                         return;
204
205                 LocalUser* const user = IS_LOCAL(static_cast<User*>(item));
206                 if ((user) && (user->serializer == &rfcserializer))
207                         ServerInstance->Users.QuitUser(user, "Protocol serializer module unloading");
208         }
209
210         void OnUserInit(LocalUser* user) CXX11_OVERRIDE
211         {
212                 if (!user->serializer)
213                         user->serializer = &rfcserializer;
214         }
215
216         Version GetVersion()
217         {
218                 return Version("RFC client protocol serializer and unserializer", VF_CORE|VF_VENDOR);
219         }
220 };
221
222 MODULE_INIT(ModuleCoreRFCSerializer)