File Coverage

blib/lib/Protocol/IRC.pm
Criterion Covered Total %
statement 138 146 94.5
branch 70 92 76.0
condition 14 20 70.0
subroutine 21 25 84.0
pod 16 20 80.0
total 259 303 85.4


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2010-2014 -- leonerd@leonerd.org.uk
5              
6             package Protocol::IRC;
7              
8 15     15   191531 use strict;
  15         32  
  15         490  
9 15     15   70 use warnings;
  15         25  
  15         591  
10              
11             our $VERSION = '0.10';
12              
13 15     15   126 use Carp;
  15         29  
  15         1419  
14              
15 15     15   9706 use Protocol::IRC::Message;
  15         37  
  15         33926  
16              
17             # This should be mixed in MI-style
18              
19             =head1 NAME
20              
21             C - IRC protocol handling
22              
23             =head1 DESCRIPTION
24              
25             This mix-in class provides a base layer of IRC message handling logic. It
26             allows reading of IRC messages from a string buffer and dispatching them to
27             handler methods on its instance.
28              
29             L provides an extension to this logic that may be more
30             convenient for IRC client implementations.
31              
32             =head1 MESSAGE HANDLING
33              
34             Every incoming message causes a sequence of message handling to occur. First,
35             the message is parsed, and a hash of data about it is created; this is called
36             the hints hash. The message and this hash are then passed down a sequence of
37             potential handlers.
38              
39             Each handler indicates by return value, whether it considers the message to
40             have been handled. Processing of the message is not interrupted the first time
41             a handler declares to have handled a message. Instead, the hints hash is marked
42             to say it has been handled. Later handlers can still inspect the message or its
43             hints, using this information to decide if they wish to take further action.
44              
45             A message with a command of C will try handlers in following places:
46              
47             =over 4
48              
49             =item 1.
50              
51             A method called C
52              
53             $irc->on_message_COMMAND( $message, \%hints )
54              
55             =item 2.
56              
57             A method called C
58              
59             $irc->on_message( 'COMMAND', $message, \%hints )
60              
61             =back
62              
63             For server numeric replies, if the numeric reply has a known name, it will be
64             attempted first at its known name, before falling back to the numeric if it
65             was not handled. Unrecognised numerics will be attempted only at their numeric
66             value.
67              
68             Because of the wide variety of messages in IRC involving various types of data
69             the message handling specific cases for certain types of message, including
70             adding extra hints hash items, or invoking extra message handler stages. These
71             details are noted here.
72              
73             Many of these messages create new events; called synthesized messages. These
74             are messages created by the C object itself, to better
75             represent some of the details derived from the primary ones from the server.
76             These events all take lower-case command names, rather than capitals, and will
77             have a C key in the hints hash, set to a true value. These are
78             dispatched and handled identically to regular primary events, detailed above.
79              
80             If any handler of the synthesized message returns true, then this marks the
81             primary message handled as well.
82              
83             If a message is received that has a gating disposition, extra processing is
84             applied to it before the processing above. The effect on its gate is given as
85             a string (one of C, C, C) to handlers in the following
86             places:
87              
88             =over 4
89              
90             =item 1.
91              
92             A method called C
93              
94             $irc->on_message_gate_EFFECT_GATE( $message, \%hints )
95              
96             =item 2.
97              
98             A method called C
99              
100             $irc->on_message_gate_EFFECT( 'GATE', $message, \%hints )
101              
102             =item 3.
103              
104             A method called C
105              
106             $irc->on_message_gate( 'EFFECT', 'GATE', $message, \%hints )
107              
108             =back
109              
110             =head2 Message Hints
111              
112             When messages arrive they are passed to the appropriate message handling
113             method, which the implementation may define. As well as the message, a hash
114             of extra information derived from or relating to the message is also given.
115              
116             The following keys will be present in any message hint hash:
117              
118             =over 8
119              
120             =item handled => BOOL
121              
122             Initially false. Will be set to true the first time a handler returns a true
123             value.
124              
125             =item prefix_nick => STRING
126              
127             =item prefix_user => STRING
128              
129             =item prefix_host => STRING
130              
131             Values split from the message prefix; see the C
132             C method.
133              
134             =item prefix_name => STRING
135              
136             Usually the prefix nick, or the hostname in case the nick isn't defined
137             (usually on server messages).
138              
139             =item prefix_is_me => BOOL
140              
141             True if the nick mentioned in the prefix refers to this connection.
142              
143             =back
144              
145             Added to this set, will be all the values returned by the message's
146             C method. Some of these values may cause yet more values to be
147             generated.
148              
149             If the message type defines a C:
150              
151             =over 8
152              
153             =item * target_type => STRING
154              
155             Either C or C, as returned by C.
156              
157             =item * target_is_me => BOOL
158              
159             True if the target name is a user and refers to this connection.
160              
161             =back
162              
163             Finally, any key whose name ends in C<_nick> or C<_name> will have a
164             corresponding key added with C<_folded> suffixed on its name, containing the
165             value casefolded using C. This is for the convenience of string
166             comparisons, hash keys, etc..
167              
168             =cut
169              
170             =head1 METHODS
171              
172             =cut
173              
174             =head2 $irc->on_read( $buffer )
175              
176             Informs the protocol implementation that more bytes have been read from the
177             peer. This method will modify the C<$buffer> directly, and remove from it the
178             prefix of bytes it has consumed. Any bytes remaining should be stored by the
179             caller for next time.
180              
181             Any messages found in the buffer will be passed, in sequence, to the
182             C method.
183              
184             =cut
185              
186             sub on_read
187             {
188 63     63 1 12115 my $self = shift;
189             # buffer in $_[0]
190              
191 63         532 while( $_[0] =~ s/^(.*)\x0d\x0a// ) {
192 64         171 my $line = $1;
193             # Ignore blank lines
194 64 100       174 next if !length $line;
195              
196 62         292 my $message = Protocol::IRC::Message->new_from_line( $line );
197              
198 61         287 my $command = $message->command_name;
199              
200 61         204 my ( $prefix_nick, $prefix_user, $prefix_host ) = $message->prefix_split;
201              
202 61 100 100     465 my $hints = {
203             handled => 0,
204              
205             prefix_nick => $prefix_nick,
206             prefix_user => $prefix_user,
207             prefix_host => $prefix_host,
208             # Most of the time this will be "nick", except for special messages from the server
209             prefix_name => defined $prefix_nick ? $prefix_nick : $prefix_host,
210             prefix_is_me => defined $prefix_nick && $self->is_nick_me( $prefix_nick ),
211             };
212              
213 61 100       251 if( my $named_args = $message->named_args ) {
214 53         341 $hints->{$_} = $named_args->{$_} for keys %$named_args;
215             }
216              
217 61 100 100     341 if( defined $hints->{text} and my $encoder = $self->encoder ) {
218 2         34 $hints->{text} = $encoder->decode( $hints->{text} );
219             }
220              
221 61 100       217 if( defined( my $target_name = $hints->{target_name} ) ) {
222 25         55 $hints->{target_is_me} = $self->is_nick_me( $target_name );
223              
224 25         69 my $target_type = $self->classify_name( $target_name );
225 25         54 $hints->{target_type} = $target_type;
226             }
227              
228 61         125 my $prepare_method = "prepare_hints_$command";
229 61 100       580 $self->$prepare_method( $message, $hints ) if $self->can( $prepare_method );
230              
231 61 100       258 foreach my $k ( grep { m/_nick$/ or m/_name$/ } keys %$hints ) {
  545         7259  
232 154         385 $hints->{"${k}_folded"} = $self->casefold_name( $hints->{$k} );
233             }
234              
235 61 100       245 if( my $disp = $message->gate_disposition ) {
236 15         61 my ( $type, $gate ) = $disp =~ m/^([-+!])(.*)$/;
237 15 0       35 my $effect = ( $type eq "-" ? "more" :
    50          
    100          
238             $type eq "+" ? "done" :
239             $type eq "!" ? "fail" : die "TODO" );
240              
241 15 50       50 $self->invoke( "on_message_gate_${effect}_$gate", $message, $hints ) and $hints->{handled} = 1;
242 15 50       38 $self->invoke( "on_message_gate_$effect", $gate, $message, $hints ) and $hints->{handled} = 1;
243 15 50       29 $self->invoke( "on_message_gate", $effect, $gate, $message, $hints ) and $hints->{handled} = 1;
244             }
245              
246 61 100       297 $self->invoke( "on_message_$command", $message, $hints ) and $hints->{handled} = 1;
247 61 100       232 $self->invoke( "on_message", $command, $message, $hints ) and $hints->{handled} = 1;
248              
249 61 100 100     927 if( !$hints->{handled} and $message->command ne $command ) { # numerics
250 4         12 my $numeric = $message->command;
251 4 50       25 $self->invoke( "on_message_$numeric", $message, $hints ) and $hints->{handled} = 1;
252 4 50       45 $self->invoke( "on_message", $numeric, $message, $hints ) and $hints->{handled} = 1;
253             }
254             }
255             }
256              
257             =head2 $irc->send_message( $message )
258              
259             Sends a message to the peer from the given C
260             object.
261              
262             =head2 $irc->send_message( $command, $prefix, @args )
263              
264             Sends a message to the peer directly from the given arguments.
265              
266             =cut
267              
268             sub send_message
269             {
270 22     22 1 222 my $self = shift;
271              
272 22         28 my $message;
273              
274 22 50       67 if( @_ == 1 ) {
275 0         0 $message = shift;
276             }
277             else {
278 22         49 my ( $command, $prefix, @args ) = @_;
279              
280 22 100       108 if( my $encoder = $self->encoder ) {
281 1         6 my $argnames = Protocol::IRC::Message->arg_names( $command );
282              
283 1 50       5 if( defined( my $i = $argnames->{text} ) ) {
284 1 50       13 $args[$i] = $encoder->encode( $args[$i] ) if defined $args[$i];
285             }
286             }
287              
288 22         111 $message = Protocol::IRC::Message->new( $command, $prefix, @args );
289             }
290              
291 22         102 $self->write( $message->stream_to_line . "\x0d\x0a" );
292             }
293              
294             =head2 $irc->send_ctcp( $prefix, $target, $verb, $argstr )
295              
296             Shortcut to sending a CTCP message. Sends a PRIVMSG to the given target,
297             containing the given verb and argument string.
298              
299             =cut
300              
301             sub send_ctcp
302             {
303 1     1 1 2 my $self = shift;
304 1         2 my ( $prefix, $target, $verb, $argstr ) = @_;
305              
306 1         8 $self->send_message( "PRIVMSG", undef, $target, "\001$verb $argstr\001" );
307             }
308              
309             =head2 $irc->send_ctcprely( $prefix, $target, $verb, $argstr )
310              
311             Shortcut to sending a CTCP reply. As C but using a NOTICE instead.
312              
313             =cut
314              
315             sub send_ctcpreply
316             {
317 1     1 0 2 my $self = shift;
318 1         4 my ( $prefix, $target, $verb, $argstr ) = @_;
319              
320 1         7 $self->send_message( "NOTICE", undef, $target, "\001$verb $argstr\001" );
321             }
322              
323             =head1 ISUPPORT-DRIVEN UTILITIES
324              
325             The following methods are controlled by the server information given in the
326             C settings. They use the C required method to query the
327             information required.
328              
329             =cut
330              
331             =head2 $name_folded = $irc->casefold_name( $name )
332              
333             Returns the C<$name>, folded in case according to the server's C
334             C. Such a folded name will compare using C according to whether the
335             server would consider it the same name.
336              
337             Useful for use in hash keys or similar.
338              
339             =cut
340              
341             sub casefold_name
342             {
343 306     306 1 473 my $self = shift;
344 306         369 my ( $nick ) = @_;
345              
346 306 100       669 return undef unless defined $nick;
347              
348 271   100     728 my $mapping = lc( $self->isupport( "CASEMAPPING" ) || "" );
349              
350             # Squash the 'capital' [\] into lowercase {|}
351 271 100       1074 $nick =~ tr/[\\]/{|}/ if $mapping ne "ascii";
352              
353             # Most RFC 1459 implementations also squash ^ to ~, even though the RFC
354             # didn't mention it
355 271 100       579 $nick =~ tr/^/~/ unless $mapping eq "strict-rfc1459";
356              
357 271         1113 return lc $nick;
358             }
359              
360             =head2 $cmp = $irc->cmp_prefix_flags( $lhs, $rhs )
361              
362             Compares two channel occupant prefix flags, and returns a signed integer to
363             indicate which of them has higher priviledge, according to the server's
364             ISUPPORT declaration. Suitable for use in a C function or similar.
365              
366             =cut
367              
368             sub cmp_prefix_flags
369             {
370 4     4 1 599 my $self = shift;
371 4         7 my ( $lhs, $rhs ) = @_;
372              
373 4 50 33     26 return undef unless defined $lhs and defined $rhs;
374              
375             # As a special case, compare emptystring as being lower than voice
376 4 50 33     12 return 0 if $lhs eq "" and $rhs eq "";
377 4 50       11 return 1 if $rhs eq "";
378 4 50       37 return -1 if $lhs eq "";
379              
380 4         12 my $PREFIX_FLAGS = $self->isupport( 'prefix_flags' );
381              
382 4 50       24 ( my $lhs_index = index $PREFIX_FLAGS, $lhs ) > -1 or return undef;
383 4 100       14 ( my $rhs_index = index $PREFIX_FLAGS, $rhs ) > -1 or return undef;
384              
385             # IRC puts these in greatest-first, so we need to swap the ordering here
386 3         12 return $rhs_index <=> $lhs_index;
387             }
388              
389             =head2 $cmp = $irc->cmp_prefix_modes( $lhs, $rhs )
390              
391             Similar to C, but compares channel occupant C command
392             flags.
393              
394             =cut
395              
396             sub cmp_prefix_modes
397             {
398 4     4 1 8 my $self = shift;
399 4         8 my ( $lhs, $rhs ) = @_;
400              
401 4 50 33     23 return undef unless defined $lhs and defined $rhs;
402              
403 4         10 my $PREFIX_MODES = $self->isupport( "prefix_modes" );
404              
405 4 50       23 ( my $lhs_index = index $PREFIX_MODES, $lhs ) > -1 or return undef;
406 4 100       15 ( my $rhs_index = index $PREFIX_MODES, $rhs ) > -1 or return undef;
407              
408             # IRC puts these in greatest-first, so we need to swap the ordering here
409 3         14 return $rhs_index <=> $lhs_index;
410             }
411              
412             =head2 $flag = $irc->prefix_mode2flag( $mode )
413              
414             Converts a channel occupant C flag (such as C) into a name prefix
415             flag (such as C<@>).
416              
417             =cut
418              
419             sub prefix_mode2flag
420             {
421 7     7 1 3882 my $self = shift;
422 7         12 my ( $mode ) = @_;
423              
424 7         25 return $self->isupport( 'prefix_map_m2f' )->{$mode};
425             }
426              
427             =head2 $mode = $irc->prefix_flag2mode( $flag )
428              
429             The inverse of C.
430              
431             =cut
432              
433             sub prefix_flag2mode
434             {
435 2     2 1 552 my $self = shift;
436 2         5 my ( $flag ) = @_;
437              
438 2         9 return $self->isupport( 'prefix_map_f2m' )->{$flag};
439             }
440              
441             =head2 $classification = $irc->classify_name( $name )
442              
443             Returns C if the given name matches the pattern of names allowed for
444             channels according to the server's C C. Returns C
445             if not.
446              
447             =cut
448              
449             sub classify_name
450             {
451 37     37 1 46 my $self = shift;
452 37         56 my ( $name ) = @_;
453              
454 37 100       89 return "channel" if $name =~ $self->isupport( "channame_re" );
455 11         61 return "user"; # TODO: Perhaps we can be a bit stricter - only check for valid nick chars?
456             }
457              
458             =head2 $me = $irc->is_nick_me( $nick )
459              
460             Returns true if the given nick refers to that in use by the connection.
461              
462             =cut
463              
464             sub is_nick_me
465             {
466 64     64 1 184 my $self = shift;
467 64         79 my ( $nick ) = @_;
468              
469 64         138 return $self->casefold_name( $nick ) eq $self->nick_folded;
470             }
471              
472             =head1 INTERNAL MESSAGE HANDLING
473              
474             The following messages are handled internally by C.
475              
476             =cut
477              
478             =head2 PING
479              
480             C messages are automatically replied to with C.
481              
482             =cut
483              
484             sub on_message_PING
485             {
486 0     0 0 0 my $self = shift;
487 0         0 my ( $message, $hints ) = @_;
488              
489 0         0 $self->send_message( "PONG", undef, $message->named_args->{text} );
490              
491 0         0 return 1;
492             }
493              
494             =head2 NOTICE and PRIVMSG
495              
496             Because C and C are so similar, they are handled together by
497             synthesized events called C, C and C. Depending on the
498             contents of the text, and whether it was supplied in a C or a
499             C, one of these three events will be created.
500              
501             In all cases, the hints hash will contain a C key being true or
502             false, depending on whether the original messages was a C or a
503             C, a C key containing the message target name, a
504             case-folded version of the name in a C key, and a
505             classification of the target type in a C key.
506              
507             For the C target type, it will contain a boolean in C to
508             indicate if the target of the message is the user represented by this
509             connection.
510              
511             For the C target type, it will contain a C key
512             containing the channel message restriction, if present.
513              
514             For normal C messages, it will contain a key C containing the
515             actual message text.
516              
517             For either CTCP message type, it will contain keys C and
518             C with the parsed message. The C will contain the first
519             space-separated token, and C will be a string containing the rest
520             of the line, otherwise unmodified. This type of message is also subject to a
521             special stage of handler dispatch, involving the CTCP verb string. For
522             messages with C as the verb, the following are tried. C may stand
523             for either C or C.
524              
525             =over 4
526              
527             =item 1.
528              
529             A method called C
530              
531             $irc->on_message_CTCP_VERB( $message, \%hints )
532              
533             =item 2.
534              
535             A method called C
536              
537             $irc->on_message_CTCP( 'VERB', $message, \%hintss )
538              
539             =item 3.
540              
541             A method called C
542              
543             $irc->on_message( 'CTCP VERB', $message, \%hints )
544              
545             =back
546              
547             =cut
548              
549             sub on_message_NOTICE
550             {
551 3     3 0 7 my $self = shift;
552 3         8 my ( $message, $hints ) = @_;
553 3         20 return $self->_on_message_text( $message, $hints, 1 );
554             }
555              
556             sub on_message_PRIVMSG
557             {
558 7     7 0 9 my $self = shift;
559 7         11 my ( $message, $hints ) = @_;
560 7         38 return $self->_on_message_text( $message, $hints, 0 );
561             }
562              
563             sub _on_message_text
564             {
565 10     10   16 my $self = shift;
566 10         20 my ( $message, $hints, $is_notice ) = @_;
567              
568 10         128 my %hints = (
569             %$hints,
570             synthesized => 1,
571             is_notice => $is_notice,
572             );
573              
574             # TODO: In client->server messages this might be a comma-separated list
575 10         31 my $target = delete $hints{targets};
576              
577 10         32 my $prefixflag_re = $self->isupport( 'prefixflag_re' );
578              
579 10         40 my $restriction = "";
580 10         132 while( $target =~ m/^$prefixflag_re/ ) {
581 1         12 $restriction .= substr( $target, 0, 1, "" );
582             }
583              
584 10         21 $hints{target_name} = $target;
585 10         26 $hints{target_name_folded} = $self->casefold_name( $target );
586              
587 10         41 my $type = $hints{target_type} = $self->classify_name( $target );
588              
589 10 100       93 if( $type eq "channel" ) {
    50          
590 5         14 $hints{restriction} = $restriction;
591 5         20 $hints{target_is_me} = '';
592             }
593             elsif( $type eq "user" ) {
594             # TODO: user messages probably can't have restrictions. What to do
595             # if we got one?
596 5         10 $hints{target_is_me} = $self->is_nick_me( $target );
597             }
598              
599 10         22 my $text = $hints->{text};
600              
601 10 100       57 if( $text =~ m/^\x01(.*)\x01$/ ) {
602 2         9 ( my $verb, $text ) = split( m/ /, $1, 2 );
603 2         6 $hints{ctcp_verb} = $verb;
604 2         5 $hints{ctcp_args} = $text;
605              
606 2 100       7 my $ctcptype = $is_notice ? "ctcpreply" : "ctcp";
607              
608 2 50       8 $self->invoke( "on_message_${ctcptype}_$verb", $message, \%hints ) and $hints{handled} = 1;
609 2 50       8 $self->invoke( "on_message_${ctcptype}", $verb, $message, \%hints ) and $hints{handled} = 1;
610 2 50       7 $self->invoke( "on_message", "$ctcptype $verb", $message, \%hints ) and $hints{handled} = 1;
611             }
612             else {
613 8         14 $hints{text} = $text;
614              
615 8 100       36 $self->invoke( "on_message_text", $message, \%hints ) and $hints{handled} = 1;
616 8 100       31 $self->invoke( "on_message", "text", $message, \%hints ) and $hints{handled} = 1;
617             }
618              
619 10         134 return $hints{handled};
620             }
621              
622             =head1 REQUIRED METHODS
623              
624             =cut
625              
626             =head2 $irc->write( $string )
627              
628             Requests the byte string to be sent to the peer
629              
630             =cut
631              
632 0     0 1 0 sub write { croak "Attemped to invoke abstract ->write on " . ref $_[0] }
633              
634             =head2 $encoder = $irc->encoder
635              
636             Optional. If supplied, returns an L object used to encode or decode
637             the bytes appearing in a C field of a message. If set, all text strings
638             will be returned, and should be given, as Unicode strings. They will be
639             encoded or decoded using this object.
640              
641             =cut
642              
643 20     20 1 88 sub encoder { undef }
644              
645             =head2 $result = $irc->invoke( $name, @args )
646              
647             Optional. If provided, invokes the message handling routine called C<$name>
648             with the given arguments. A default implementation is provided which simply
649             attempts to invoke a method of the given name, or return false if no method
650             of that name exists.
651              
652             If an implementation does override this method, care should be taken to ensure
653             that methods are tested for and invoked if present, in addition to any other
654             work the method wishes to perform, as this is the basis by which derived
655             message handling works.
656              
657             =cut
658              
659             sub invoke
660             {
661 194     194 1 306 my $self = shift;
662 194         357 my ( $name, @args ) = @_;
663 194 100       1170 return unless $self->can( $name );
664 100         303 return $self->$name( @args );
665             }
666              
667             =head2 $value = $irc->isupport( $field )
668              
669             Should return the value of the given C field.
670              
671             As well as the all-capitals server-supplied fields, the following fields may
672             be requested. Their names are all lowercase and contain underscores, to
673             distinguish them from server-supplied fields.
674              
675             =over 8
676              
677             =item prefix_modes => STRING
678              
679             The mode characters from C (e.g. C)
680              
681             =item prefix_flags => STRING
682              
683             The flag characters from C (e.g. C<@%+>)
684              
685             =item prefixflag_re => Regexp
686              
687             A precompiled regexp that matches any of the prefix flags
688              
689             =item prefix_map_m2f => HASH
690              
691             A map from mode characters to flag characters
692              
693             =item prefix_map_f2m => HASH
694              
695             A map from flag characters to mode characters
696              
697             =item chanmodes_list => ARRAY
698              
699             A 4-element array containing the split portions of C;
700              
701             [ $listmodes, $argmodes, $argsetmodes, $boolmodes ]
702              
703             =item channame_re => Regexp
704              
705             A precompiled regexp that matches any string beginning with a channel prefix
706             character in C.
707              
708             =back
709              
710             =cut
711              
712 0     0 1 0 sub isupport { croak "Attempted to invoke abstract ->isupport on " . ref $_[0] }
713              
714             =head2 $nick = $irc->nick
715              
716             Should return the current nick in use by the connection.
717              
718             =head2 $nick_folded = $irc->nick_folded
719              
720             Optional. If supplied, should return the current nick as case-folded by the
721             C method. If not provided, this will be performed by
722             case-folding the result from C.
723              
724             =cut
725              
726 0     0 1 0 sub nick { croak "Attempted to invoke abstract ->nick on " . ref $_[0] }
727 55     55 1 168 sub nick_folded { $_[0]->casefold_name( $_[0]->nick ) }
728              
729             =head1 AUTHOR
730              
731             Paul Evans
732              
733             =cut
734              
735             0x55AA;