1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
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 protoeventprov Protocol event provider the event is an instance of.
*/
Event(EventProvider& protoeventprov)
: event(&protoeventprov)
, initialmsg(NULL)
, initialmsglist(NULL)
, eventinit_done(false)
{
}
/** Constructor.
* @param protoeventprov 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 OnPopulateTags(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 tagvalue 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 OnProcessTag(User* 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)
{
}
|