File Coverage

blib/lib/Protocol/IRC/Message.pm
Criterion Covered Total %
statement 124 126 98.4
branch 61 68 89.7
condition 11 12 91.6
subroutine 20 20 100.0
pod 16 16 100.0
total 232 242 95.8


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, 2008-2016 -- leonerd@leonerd.org.uk
5              
6             package Protocol::IRC::Message 0.13;
7              
8 16     16   251550 use v5.14;
  16         76  
9 16     16   72 use warnings;
  16         33  
  16         417  
10              
11 16     16   78 use Carp;
  16         26  
  16         14176  
12             our @CARP_NOT = qw( Net::Async::IRC );
13              
14             =head1 NAME
15              
16             C - encapsulates a single IRC message
17              
18             =head1 SYNOPSIS
19              
20             use Protocol::IRC::Message;
21              
22             my $hello = Protocol::IRC::Message->new(
23             "PRIVMSG",
24             undef,
25             "World",
26             "Hello, world!"
27             );
28              
29             printf "The command is %s and the final argument is %s\n",
30             $hello->command, $hello->arg( -1 );
31              
32             =head1 DESCRIPTION
33              
34             An object in this class represents a single IRC message, either received from
35             or to be sent to the server. These objects are immutable once constructed, but
36             provide a variety of methods to access the contained information.
37              
38             This class also understands IRCv3 message tags.
39              
40             =cut
41              
42             =head1 CONSTRUCTOR
43              
44             =cut
45              
46             =head2 new_from_line
47              
48             $message = Protocol::IRC::Message->new_from_line( $line )
49              
50             Returns a new C object, constructed by parsing the
51             given IRC line. Most typically used to create a new object to represent a
52             message received from the server.
53              
54             =cut
55              
56             sub new_from_line
57             {
58 70     70 1 5202 my $class = shift;
59 70         118 my ( $line ) = @_;
60              
61 70         91 my %tags;
62 70 100       294 if( $line =~ s/^\@([^ ]+) +// ) {
63 1         4 foreach ( split m/;/, $1 ) {
64 3 100       11 if( m/^([^=]+)=(.*)$/ ) {
65 2         6 $tags{$1} = $2;
66             }
67             else {
68 1         2 $tags{$_} = undef;
69             }
70             }
71             }
72              
73 70         92 my $prefix;
74 70 100       328 if( $line =~ s/^:([^ ]+) +// ) {
75 63         127 $prefix = $1;
76             }
77              
78 70         265 my ( $mid, $final ) = split( m/ +:/, $line, 2 );
79 70         250 my @args = split( m/ +/, $mid );
80              
81 70 100       178 push @args, $final if defined $final;
82              
83 70         111 my $command = shift @args;
84              
85 70         194 return $class->new_with_tags( $command, \%tags, $prefix, @args );
86             }
87              
88             =head2 new
89              
90             $message = Protocol::IRC::Message->new( $command, $prefix, @args )
91              
92             Returns a new C object, intialised from the given
93             components. Most typically used to create a new object to send to the server
94             using C. The message will contain no IRCv3 tags.
95              
96             =cut
97              
98             sub new
99             {
100 25     25 1 3361 my $class = shift;
101 25         98 return $class->new_with_tags( $_[0], {}, $_[1], @_[2..$#_] );
102             }
103              
104             =head2 new_from_named_args
105              
106             $message = Protocol::IRC::Message->new_from_named_args( $command, %args )
107              
108             Returns a new C object, initialised from the given
109             named arguments. The argument names must match those required by the given
110             command.
111              
112             =cut
113              
114             sub new_from_named_args
115             {
116 11     11 1 1350 my $class = shift;
117 11         27 my ( $command, %args ) = @_;
118              
119 11         26 my $argnames = $class->arg_names( $command );
120              
121 11         14 my @args;
122              
123 11         28 foreach my $name ( keys %$argnames ) {
124 21         29 my $idx = $argnames->{$name};
125              
126             # Clients don't get to set prefix nick
127             # TODO: servers do
128 21 100       56 next if $idx eq "pn";
129              
130 20 50       33 defined( my $value = $args{$name} ) or
131             croak "$command requires a named argument of '$name'";
132              
133 20 50       77 if( $idx =~ m/^\d+$/ ) {
134 20         40 $args[$idx] = $args{$name};
135             }
136             else {
137 0         0 die "TODO: not sure what to do with argname idx $idx\n";
138             }
139             }
140              
141 11         29 return $class->new( $command, undef, @args );
142             }
143              
144             =head2 new_with_tags
145              
146             $mesage = Protocol::IRC::Message->new_with_tags( $command, \%tags, $prefix, @args )
147              
148             Returns a new C object, as with C but also
149             containing the given IRCv3 tags.
150              
151             =cut
152              
153             sub new_with_tags
154             {
155 98     98 1 1205 my $class = shift;
156 98         257 my ( $command, $tags, $prefix, @args ) = @_;
157              
158             # IRC is case-insensitive for commands, but we'd like them in uppercase
159             # to keep things simpler
160 98         191 $command = uc $command;
161              
162             # Less strict checking than RFC 2812 because a lot of servers lately seem
163             # to be more flexible than that.
164              
165 98 100 100     609 $command =~ m/^[A-Z]+$/ or $command =~ m/^\d\d\d$/ or
166             croak "Command must be just letters or three digits";
167              
168 97         260 foreach my $key ( keys %$tags ) {
169 6 100       87 $key =~ m{^[a-zA-Z0-9./-]+$} or
170             croak "Tag key '$key' is invalid";
171              
172 5         9 my $value = $tags->{$key};
173 5 100 100     89 defined $value and $value =~ m{[ ;]} and
174             croak "Tag value '$value' for key '$key' is invalid";
175             }
176              
177 95 100       181 if( defined $prefix ) {
178 65 100       227 $prefix =~ m/[ \t\x0d\x0a]/ and
179             croak "Prefix must not contain whitespace";
180             }
181              
182 94         245 foreach ( @args[0 .. $#args-1] ) { # Not the final
183 136 100       295 defined or croak "Argument must be defined";
184 135 100       349 m/[ \t\x0d\x0a]/ and
185             croak "Argument must not contain whitespace";
186             }
187              
188 92 100       177 if( @args ) {
189 85 100       222 defined $args[-1] or croak "Final argument must be defined";
190 84 100       250 $args[-1] =~ m/[\x0d\x0a]/ and croak "Final argument must not contain a linefeed";
191             }
192              
193 90         319 my $self = {
194             command => $command,
195             prefix => $prefix,
196             args => \@args,
197             tags => { %$tags },
198             };
199              
200 90         413 return bless $self, $class;
201             }
202              
203             =head1 METHODS
204              
205             =cut
206              
207             =head2 STRING
208              
209             $str = $message->STRING
210              
211             $str = "$message"
212              
213             Returns a string representing the message, suitable for use in a debugging
214             message or similar. I: This is not the same as the IRC wire form, to
215             send to the IRC server; for that see C.
216              
217             =cut
218              
219 16     16   16188 use overload '""' => "STRING";
  16         12949  
  16         86  
220             sub STRING
221             {
222 2     2 1 3545 my $self = shift;
223 2         4 my $class = ref $self;
224             return $class . "[" .
225             ( defined $self->{prefix} ? "prefix=$self->{prefix}," : "" ) .
226             "cmd=$self->{command}," .
227 2 50       50 "args=(" . join( ",", @{ $self->{args} } ) . ")]";
  2         171  
228             }
229              
230             =head2 command
231              
232             $command = $message->command
233              
234             Returns the command name or numeric stored in the message object.
235              
236             =cut
237              
238             sub command
239             {
240 187     187 1 4897 my $self = shift;
241 187         863 return $self->{command};
242             }
243              
244             =head2 command_name
245              
246             $name = $message->command_name
247              
248             For named commands, returns the command name directly. For server numeric
249             replies, returns the name of the numeric.
250              
251             =cut
252              
253             my %NUMERIC_NAMES;
254              
255             sub command_name
256             {
257 64     64 1 99 my $self = shift;
258 64   66     108 return $NUMERIC_NAMES{ $self->command } || $self->command;
259             }
260              
261             =head2 tags
262              
263             $tags = $message->tags
264              
265             Returns a HASH reference containing IRCv3 message tags. This is a reference to
266             the hash stored directly by the object itself, so the caller should be careful
267             not to modify it.
268              
269             =cut
270              
271             sub tags
272             {
273 2     2 1 7 my $self = shift;
274             return $self->{tags}
275 2         7 }
276              
277             =head2 prefix
278              
279             $prefix = $message->prefix
280              
281             Returns the line prefix stored in the object, or the empty string if one was
282             not supplied.
283              
284             =cut
285              
286             sub prefix
287             {
288 87     87 1 134 my $self = shift;
289 87 100       288 return defined $self->{prefix} ? $self->{prefix} : "";
290             }
291              
292             =head2 prefix_split
293              
294             ( $nick, $ident, $host ) = $message->prefix_split
295              
296             Splits the prefix into its nick, ident and host components. If the prefix
297             contains only a hostname (such as the server name), the first two components
298             will be returned as C.
299              
300             =cut
301              
302             sub prefix_split
303             {
304 56     56 1 90 my $self = shift;
305              
306 56         123 my $prefix = $self->prefix;
307              
308 56 100       294 return ( $1, $2, $3 ) if $prefix =~ m/^(.*?)!(.*?)@(.*)$/;
309              
310             # $prefix doesn't split into nick!ident@host so presume host only
311 25         75 return ( undef, undef, $prefix );
312             }
313              
314             =head2 arg
315              
316             $arg = $message->arg( $index )
317              
318             Returns the argument at the given index. Uses normal perl array indexing, so
319             negative indices work as expected.
320              
321             =cut
322              
323             sub arg
324             {
325 114     114 1 131 my $self = shift;
326 114         167 my ( $index ) = @_;
327 114         214 return $self->{args}[$index];
328             }
329              
330             =head2 args
331              
332             @args = $message->args
333              
334             Returns a list containing all the message arguments.
335              
336             =cut
337              
338             sub args
339             {
340 30     30 1 49 my $self = shift;
341 30         34 return @{$self->{args}};
  30         122  
342             }
343              
344             =head2 stream_to_line
345              
346             $line = $message->stream_to_line
347              
348             Returns a string suitable for sending the message to the IRC server.
349              
350             =cut
351              
352             sub stream_to_line
353             {
354 26     26 1 48 my $self = shift;
355              
356 26         50 my $line = "";
357              
358 26 100       45 if( keys %{ $self->{tags} } ) {
  26         153  
359 1         2 my $tags = $self->{tags};
360 1 50       3 $line .= "\@" . join( ";", map { defined $tags->{$_} ? "$_=$tags->{$_}" : $_ } keys %$tags ) . " ";
  1         8  
361             }
362              
363 26 100       62 if( defined $self->{prefix} ) {
364 2         7 $line .= ":$self->{prefix} ";
365             }
366              
367 26         70 $line .= $self->{command};
368              
369 26         34 foreach ( @{$self->{args}} ) {
  26         56  
370 40 100 100     148 if( m/ / or m/^:/ ) {
371 15         35 $line .= " :$_";
372             }
373             else {
374 25         56 $line .= " $_";
375             }
376             }
377              
378 26         103 return $line;
379             }
380              
381             # Argument naming information
382              
383             # This hash holds HASH refs giving the names of the positional arguments of
384             # any message. The hash keys store the argument names, and the values store
385             # an argument index, the string "pn" meaning prefix nick, or "$n~$m" meaning
386             # an index range. Endpoint can be absent.
387              
388             my %ARG_NAMES = (
389             INVITE => { inviter_nick => "pn",
390             invited_nick => 0,
391             target_name => 1 },
392             KICK => { kicker_nick => "pn",
393             target_name => 0,
394             kicked_nick => 1,
395             text => 2 },
396             MODE => { target_name => 0,
397             modechars => 1,
398             modeargs => "2.." },
399             NICK => { old_nick => "pn",
400             new_nick => 0 },
401             NOTICE => { targets => 0,
402             text => 1 },
403             PING => { text => 0 },
404             PONG => { text => 0 },
405             QUIT => { text => 0 },
406             PART => { target_name => 0,
407             text => 1 },
408             PRIVMSG => { targets => 0,
409             text => 1 },
410             TOPIC => { target_name => 0,
411             text => 1 },
412             );
413              
414             # Misc. named commands
415             $ARG_NAMES{$_} = { target_name => 0 } for qw(
416             LIST NAMES WHO WHOIS WHOWAS
417             );
418              
419             # TODO: 472 ERR_UNKNOWNMODE: :is unknown mode char to me for
420             # How to parse this one??
421              
422             =head2 arg_names
423              
424             $names = $message->arg_names
425              
426             Returns a HASH reference giving details on how to parse named arguments for
427             the command given in this message.
428              
429             This will be a hash whose keys give the names of the arguments, and the values
430             of these keys indicate how that argument is derived from the simple positional
431             arguments.
432              
433             Normally this method is only called internally by the C method,
434             but is documented here for the benefit of completeness, and in case extension
435             modules wish to define parsing of new message types.
436              
437             Each value should be one of the following:
438              
439             =over 4
440              
441             =item * String literal C
442              
443             The value is a string, the nickname given in the message prefix
444              
445             =item * NUMBER..NUMBER
446              
447             The value is an ARRAY ref, containing a list of all the numbered arguments
448             between the (inclusive) given limits. Either or both limits may be negative;
449             they will count backwards from the end.
450              
451             =item * NUMBER
452              
453             The value is the argument at that numeric index. May be negative to count
454             backwards from the end.
455              
456             =item * NUMBER@
457              
458             The value is the argument at that numeric index as for C, except that
459             the result will be split on spaces and stored in an ARRAY ref.
460              
461             =back
462              
463             =head2 arg_names (class method)
464              
465             $names = Protocol::IRC::Message->arg_names( $command )
466              
467             This method may also be invoked as a class method by passing in the command
468             name or numeric. This allows inspection of what arguments would be required
469             or returned before a message object itself is constructed.
470              
471             =cut
472              
473             sub arg_names
474             {
475 77     77 1 640 my $command;
476              
477 77 100       155 if( ref $_[0] ) {
478 64         85 my $self = shift;
479 64         120 $command = $self->{command};
480             }
481             else {
482 13         20 my $class = shift; # ignore
483 13         22 ( $command ) = @_;
484 13 50       28 defined $command or croak 'Usage: '.__PACKAGE__.'->arg_names($command)';
485             }
486              
487 77         203 return $ARG_NAMES{$command};
488             }
489              
490             =head2 named_args
491              
492             $args = $message->named_args
493              
494             Parses arguments in the message according to the specification given by the
495             C method. Returns a hash of parsed arguments.
496              
497             TODO: More complete documentation on the exact arg names/values per message
498             type.
499              
500             =cut
501              
502             sub named_args
503             {
504 58     58 1 82 my $self = shift;
505              
506 58 100       97 my $argnames = $self->arg_names or return;
507              
508 55         70 my %named_args;
509 55         144 foreach my $name ( keys %$argnames ) {
510 128         179 my $argindex = $argnames->{$name};
511              
512 128         132 my $value;
513 128 100       521 if( $argindex eq "pn" ) {
    100          
    100          
    50          
514 2         4 ( $value, undef, undef ) = $self->prefix_split;
515             }
516             elsif( $argindex =~ m/^(-?\d+)?\.\.(-?\d+)?$/ ) {
517 17         47 my ( $start, $end ) = ( $1, $2 );
518 17         37 my @args = $self->args;
519              
520 17 50       38 defined $start or $start = 0;
521 17 100       33 defined $end or $end = $#args;
522              
523 17 100       32 $end += @args if $end < 0;
524              
525 17         59 $value = [ splice( @args, $start, $end-$start+1 ) ];
526             }
527             elsif( $argindex =~ m/^-?\d+$/ ) {
528 105         188 $value = $self->arg( $argindex );
529             }
530             elsif( $argindex =~ m/^(-?\d+)\@$/ ) {
531 4         9 $value = [ split ' ', $self->arg( $1 ) ];
532             }
533             else {
534 0         0 die "Unrecognised argument specification $argindex";
535             }
536              
537 128         256 $named_args{$name} = $value;
538             }
539              
540 55         179 return \%named_args;
541             }
542              
543             =head2 gate_disposition
544              
545             $disp = $message->gate_disposition
546              
547             Returns the "gating disposition" of the message. This defines how a reply
548             message from the server combines with other messages in response of a command
549             sent by the client. The disposition is either C, or a string consisting
550             of a type symbol and a gate name. If defined, the symbol defines what effect
551             it has on the gate name.
552              
553             =over 4
554              
555             =item -GATE
556              
557             Adds more information to the response for that gate, but doesn't yet complete
558             it.
559              
560             =item +GATE
561              
562             Completes the gate with a successful result.
563              
564             =item *GATE
565              
566             Completes the gate with a successful result, but only if the nick in the
567             message prefix relates to the connection it is received on.
568              
569             =item !GATE
570              
571             Completes the gate with a failure result.
572              
573             =back
574              
575             =cut
576              
577             my %GATE_DISPOSITIONS;
578              
579             sub gate_disposition
580             {
581 52     52 1 78 my $self = shift;
582 52         183 return $GATE_DISPOSITIONS{ $self->command };
583             }
584              
585             =head1 AUTHOR
586              
587             Paul Evans
588              
589             =cut
590              
591             local $_;
592             while( ) {
593             chomp;
594             m/^\s*#/ and next; # ignore comments
595              
596             my ( $cmdname, $args, $gating ) = split m/\s*\|\s*/, $_ or next;
597             my ( $cmd, $name ) = split m/=/, $cmdname;
598              
599             my $index = 0;
600             my %args = map {
601             if( m/^(.*)=(.*)$/ ) {
602             $index = $1;
603             ( $2 => $1 )
604             }
605             else {
606             ( $_ => ++$index );
607             }
608             } split m/,/, $args;
609              
610             $NUMERIC_NAMES{$cmd} = $name;
611             $ARG_NAMES{$cmd} = \%args;
612             $GATE_DISPOSITIONS{$cmd} = $gating if defined $gating;
613             }
614             close DATA;
615              
616             0x55AA;
617              
618             # And now the actual numeric definitions, given in columns
619             # number=NAME | argname,argname,argname
620              
621             # arg may be position=argname
622              
623             # See also
624             # http://www.alien.net.au/irc/irc2numerics.html
625              
626             __DATA__