2 * InspIRCd -- Internet Relay Chat Daemon
4 * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com>
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.
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
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/>.
22 class RFCSerializer : public ClientProtocol::Serializer
24 /** Maximum size of the message tags portion of the message, including the `@` and the trailing space characters.
26 static const std::string::size_type MAX_MESSAGE_TAG_LENGTH = 512;
28 static void SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line);
31 RFCSerializer(Module* mod)
32 : ClientProtocol::Serializer(mod, "rfc")
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;
40 bool RFCSerializer::Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput)
42 size_t start = line.find_first_not_of(" ");
43 if (start == std::string::npos)
45 // Discourage the user from flooding the server.
46 user->CommandFloodPenalty += 2000;
50 ServerInstance->Logs->Log("USERINPUT", LOG_RAWIO, "C[%s] I %s", user->uuid.c_str(), line.c_str());
52 irc::tokenstream tokens(line, start);
55 // This will always exist because of the check at the start of the function.
56 tokens.GetMiddle(token);
59 // Line begins with message tags, parse them.
61 irc::sepstream ss(token.substr(1), ';');
62 while (ss.GetToken(token))
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.
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)
72 tagval.assign(token, p+1, std::string::npos);
78 HandleTag(user, token, tagval, parseoutput.tags);
82 // Try to read the prefix or command name.
83 if (!tokens.GetMiddle(token))
85 // Discourage the user from flooding the server.
86 user->CommandFloodPenalty += 2000;
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.
98 // Try to read the command name.
99 if (!tokens.GetMiddle(token))
101 // Discourage the user from flooding the server.
102 user->CommandFloodPenalty += 2000;
107 parseoutput.cmd.assign(token);
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);
117 void RFCSerializer::SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line)
119 char prefix = '@'; // First tag name is prefixed with a '@'
120 for (ClientProtocol::TagMap::const_iterator i = tags.begin(); i != tags.end(); ++i)
122 if (!tagwl.IsSelected(tags, i))
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;
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.
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)
143 line.erase(prevsize);
152 ClientProtocol::SerializedMessage RFCSerializer::Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const
155 SerializeTags(msg.GetTags(), tagwl, line);
157 // Save position for length calculation later
158 const std::string::size_type rfcmsg_begin = line.size();
163 line.append(*msg.GetSource());
166 line.append(msg.GetCommand());
168 const ClientProtocol::Message::ParamList& params = msg.GetParams();
171 for (ClientProtocol::Message::ParamList::const_iterator i = params.begin(); i != params.end()-1; ++i)
173 const std::string& param = *i;
178 line.append(" :", 2).append(params.back());
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);
186 line.append("\r\n", 2);
190 class ModuleCoreRFCSerializer : public Module
192 RFCSerializer rfcserializer;
195 ModuleCoreRFCSerializer()
196 : rfcserializer(this)
200 void OnCleanup(ExtensionItem::ExtensibleType type, Extensible* item) CXX11_OVERRIDE
202 if (type != ExtensionItem::EXT_USER)
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");
210 void OnUserInit(LocalUser* user) CXX11_OVERRIDE
212 if (!user->serializer)
213 user->serializer = &rfcserializer;
218 return Version("RFC client protocol serializer and unserializer", VF_CORE|VF_VENDOR);
222 MODULE_INIT(ModuleCoreRFCSerializer)