3 # Copyright (c) 1995 Graham Barr <Graham.Barr@tiuk.ti.com>. All rights
4 # reserved. This program is free software; you can redistribute it and/or
5 # modify it under the same terms as Perl itself.
11 Net::NNTP - NNTP Client class
17 $nntp = Net::NNTP->new("some.host.name");
22 C<Net::NNTP> is a class implementing a simple NNTP client in Perl as described
23 in RFC977. C<Net::NNTP> inherits its communication methods from C<Net::Cmd>
29 =item new ( [ HOST ] [, OPTIONS ])
31 This is the constructor for a new Net::NNTP object. C<HOST> is the
32 name of the remote host to which a NNTP connection is required. If not
33 given two environment variables are checked, first C<NNTPSERVER> then
34 C<NEWSHOST>, if neither are set C<news> is used.
36 C<OPTIONS> are passed in a hash like fasion, using key and value pairs.
39 B<Timeout> - Maximum time, in seconds, to wait for a response from the
40 NNTP server, a value of zero will cause all IO operations to block.
43 B<Debug> - Enable the printing of debugging information to STDERR
49 Unless otherwise stated all methods return either a I<true> or I<false>
50 value, with I<true> meaning that the operation was a success. When a method
51 states that it returns a value, falure will be returned as I<undef> or an
56 =item article ( [ MSGID|MSGNUM ] )
58 Retreive the header, a blank line, then the body (text) of the
61 If no arguments are passed then the current aricle in the current
62 newsgroup is returned.
64 C<MSGNUM> is a numeric id of an article in the
65 current newsgroup, and will change the current article pointer.
66 C<MSGID> is the message id of an article as
67 shown in that article's header. It is anticipated that the client
68 will obtain the C<MSGID> from a list provided by the C<newnews>
69 command, from references contained within another article, or from
70 the message-id provided in the response to some other commands.
72 Returns a reference to an array containing the article.
74 =item body ( [ MSGID|MSGNUM ] )
76 Retreive the body (text) of the specified article.
78 Takes the same arguments as C<article>
80 Returns a reference to an array containing the body of the article.
82 =item head ( [ MSGID|MSGNUM ] )
84 Retreive the header of the specified article.
86 Takes the same arguments as C<article>
88 Returns a reference to an array containing the header of the article.
90 =item nntpstat ( [ MSGID|MSGNUM ] )
92 The C<nntpstat> command is similar to the C<article> command except that no
93 text is returned. When selecting by message number within a group,
94 the C<nntpstat> command serves to set the "current article pointer" without
97 Using the C<nntpstat> command to
98 select by message-id is valid but of questionable value, since a
99 selection by message-id does B<not> alter the "current article pointer".
101 Returns the message-id of the "current article".
103 =item group ( [ GROUP ] )
105 Set and/or get the current group. If C<GROUP> is not given then information
106 is returned on the current group.
108 In a scalar context it returns the group name.
110 In an array context the return value is a list containing, the number
111 of articles in the group, the number of the first article, the number
112 of the last article and the group name.
114 =item ihave ( MSGID [, MESSAGE ])
116 The C<ihave> command informs the server that the client has an article
117 whose id is C<MSGID>. If the server desires a copy of that
118 article, and C<MESSAGE> has been given the it will be sent.
120 Returns I<true> if the server desires the article and C<MESSAGE> was
121 successfully sent,if specified.
123 If C<MESSAGE> is not specified then the message must be sent using the
124 C<datasend> and C<dataend> methods from L<Net::Cmd>
126 C<MESSAGE> can be either an array of lines or a reference to an array.
130 Set the "current article pointer" to the previous article in the current
133 Returns the message-id of the article.
137 Returns the date on the remote server. This date will be in a UNIX time
138 format (seconds since 1970)
142 C<postok> will return I<true> if the servers initial response indicated
143 that it will allow posting.
145 =item authinfo ( USER, PASS )
149 Obtain information about all the active newsgroups. The results is a reference
150 to a hash where the key is a group name and each value is a reference to an
151 array. The elements in this array are:- the first article number in the group,
152 the last article number in the group and any information flags about the group.
154 =item newgroups ( SINCE [, DISTRIBUTIONS ])
156 C<SINCE> is a time value and C<DISTRIBUTIONS> is either a distribution
157 pattern or a reference to a list of distribution patterns.
158 The result is the same as C<list>, but the
159 groups return will be limited to those created after C<SINCE> and, if
160 specified, in one of the distribution areas in C<DISTRIBUTIONS>.
162 =item newnews ( SINCE [, GROUPS [, DISTRIBUTIONS ]])
164 C<SINCE> is a time value. C<GROUPS> is either a group pattern or a reference
165 to a list of group patterns. C<DISTRIBUTIONS> is either a distribution
166 pattern or a reference to a list of distribution patterns.
168 Returns a reference to a list which contains the message-ids of all news posted
169 after C<SINCE>, that are in a groups which matched C<GROUPS> and a
170 distribution which matches C<DISTRIBUTIONS>.
174 Set the "current article pointer" to the next article in the current
177 Returns the message-id of the article.
179 =item post ( [ MESSAGE ] )
181 Post a new article to the news server. If C<MESSAGE> is specified and posting
182 is allowed then the message will be sent.
184 If C<MESSAGE> is not specified then the message must be sent using the
185 C<datasend> and C<dataend> methods from L<Net::Cmd>
187 C<MESSAGE> can be either an array of lines or a reference to an array.
191 Tell the remote server that I am not a user client, but probably another
196 Quit the remote server and close the socket connection.
200 =head2 Extension methods
202 These methods use commands that are not part of the RFC977 documentation. Some
203 servers may not support all of them.
207 =item newsgroups ( [ PATTERN ] )
209 Returns a reference to a hash where the keys are all the group names which
210 match C<PATTERN>, or all of the groups if no pattern is specified, and
211 each value contains the description text for the group.
213 =item distributions ()
215 Returns a reference to a hash where the keys are all the possible
216 distribution names and the values are the distribution descriptions.
218 =item subscriptions ()
220 Returns a reference to a list which contains a list of groups which
221 are reccomended for a new user to subscribe to.
223 =item overview_fmt ()
225 Returns a reference to an array which contain the names of the fields returnd
228 =item active_times ()
230 Returns a reference to a hash where the keys are the group names and each
231 value is a reference to an array containg the time the groups was created
232 and an identifier, possibly an Email address, of the creator.
234 =item active ( [ PATTERN ] )
236 Similar to C<list> but only active groups that match the pattern are returned.
237 C<PATTERN> can be a group pattern.
239 =item xgtitle ( PATTERN )
241 Returns a reference to a hash where the keys are all the group names which
242 match C<PATTERN> and each value is the description text for the group.
244 =item xhdr ( HEADER, MESSAGE-RANGE )
246 Obtain the header field C<HEADER> for all the messages specified.
248 Returns a reference to a hash where the keys are the message numbers and
249 each value contains the header for that message.
251 =item xover ( MESSAGE-RANGE )
253 Returns a reference to a hash where the keys are the message numbers and each
254 value is a reference to an array which contains the overview fields for that
255 message. The names of these fields can be obtained by calling C<overview_fmt>.
257 =item xpath ( MESSAGE-ID )
259 Returns the path name to the file on the server which contains the specified
262 =item xpat ( HEADER, PATTERN, MESSAGE-RANGE)
264 The result is the same as C<xhdr> except the is will be restricted to
265 headers that match C<PATTERN>
277 The following NNTP command are unsupported by the package, and there are
291 C<MESSAGE-RANGE> is either a single message-id, a single mesage number, or
294 If C<MESSAGE-RANGE> is two message numbers and the second number in a
295 range is less than or equal to the first then the range represents all
296 messages in the group after the first message number.
300 The C<NNTP> protocol uses the C<WILDMAT> format for patterns.
301 The WILDMAT format was first developed by Rich Salz based on
302 the format used in the UNIX "find" command to articulate
303 file names. It was developed to provide a uniform mechanism
304 for matching patterns in the same manner that the UNIX shell
307 Patterns are implicitly anchored at the
308 beginning and end of each string when testing for a match.
310 There are five pattern matching operations other than a strict
311 one-to-one match between the pattern and the source to be
314 The first is an asterisk C<*> to match any sequence of zero or more
317 The second is a question mark C<?> to match any single character. The
318 third specifies a specific set of characters.
320 The set is specified as a list of characters, or as a range of characters
321 where the beginning and end of the range are separated by a minus (or dash)
322 character, or as any combination of lists and ranges. The dash can
323 also be included in the set as a character it if is the beginning
324 or end of the set. This set is enclosed in square brackets. The
325 close square bracket C<]> may be used in a set if it is the first
326 character in the set.
328 The fourth operation is the same as the
329 logical not of the third operation and is specified the same
330 way as the third with the addition of a caret character C<^> at
331 the beginning of the test string just inside the open square
334 The final operation uses the backslash character to
335 invalidate the special meaning of the a open square bracket C<[>,
336 the asterisk, backslash or the question mark. Two backslashes in
337 sequence will result in the evaluation of the backslash as a
338 character with no special meaning.
346 matches any single character other than a close square
347 bracket or a minus sign/dash.
351 matches any string that ends with the string "bdc"
352 including the string "bdc" (without quotes).
356 matches any single printable alphanumeric ASCII character.
360 matches any four character string which begins
361 with a and ends with d.
373 Graham Barr <Graham.Barr@tiuk.ti.com>
381 Copyright (c) 1995 Graham Barr. All rights reserved. This program is free
382 software; you can redistribute it and/or modify it under the same terms
388 use vars qw(@ISA $VERSION $debug);
393 $VERSION = sprintf("%d.%02d", q$Revision: 2.5 $ =~ /(\d+)\.(\d+)/);
394 @ISA = qw(Net::Cmd IO::Socket::INET);
399 my $type = ref($self) || $self;
400 my $host = shift if @_ % 2;
403 $host ||= $ENV{NNTPSERVER} || $ENV{NEWSHOST} || "news";
405 my $obj = $type->SUPER::new(PeerAddr => $host,
406 PeerPort => $arg{Port} || 'nntp(119)',
408 Timeout => defined $arg{Timeout}
413 ${*$obj}{'net_nntp_host'} = $host;
416 $obj->debug(exists $arg{Debug} ? $arg{Debug} : undef);
418 unless ($obj->response() == CMD_OK)
425 ${*$obj}{'net_nntp_post'} = $c >= 200 && $c <= 209 ? 1 : 0;
436 if(($nntp->code == 350 && $text =~ /^(\S+)/)
437 || ($text =~ /^(authinfo\s+pass)/io))
447 @_ == 1 or croak 'usage: $nntp->postok()';
449 ${*$nntp}{'net_nntp_post'} || 0;
454 @_ == 1 || @_ == 2 or croak 'usage: $nntp->article( MSGID )';
458 ? $nntp->read_until_dot()
464 @_ == 3 or croak 'usage: $nntp->authinfo( USER, PASS )';
465 my($nntp,$user,$pass) = @_;
467 $nntp->_AUTHINFO("USER",$user) == CMD_MORE
468 && $nntp->_AUTHINFO("PASS",$pass) == CMD_OK;
473 @_ == 3 or croak 'usage: $nntp->authinfo( USER, PASS )';
474 my($nntp,$user,$pass) = @_;
476 $nntp->_AUTHINFO('SIMPLE') == CMD_MORE
477 && $nntp->command($user,$pass)->response == CMD_OK;
482 @_ == 1 || @_ == 2 or croak 'usage: $nntp->body( [ MSGID ] )';
486 ? $nntp->read_until_dot()
492 @_ == 1 || @_ == 2 or croak 'usage: $nntp->head( [ MSGID ] )';
496 ? $nntp->read_until_dot()
502 @_ == 1 || @_ == 2 or croak 'usage: $nntp->nntpstat( [ MSGID ] )';
505 $nntp->_STAT(@_) && $nntp->message =~ /(<[^>]+>)/o
513 @_ == 1 || @_ == 2 or croak 'usage: $nntp->group( [ GROUP ] )';
515 my $grp = ${*$nntp}{'net_nntp_group'} || undef;
518 unless(@_ || wantarray);
522 return wantarray ? () : undef
523 unless $nntp->_GROUP($newgrp || $grp || "")
524 && $nntp->message =~ /(\d+)\s+(\d+)\s+(\d+)\s+(\S+)/;
526 my($count,$first,$last,$group) = ($1,$2,$3,$4);
528 # group may be replied as '(current group)'
529 $group = ${*$nntp}{'net_nntp_group'}
532 ${*$nntp}{'net_nntp_group'} = $group;
535 ? ($count,$first,$last,$group)
541 @_ == 1 or croak 'usage: $nntp->help()';
545 ? $nntp->read_until_dot
551 @_ >= 2 or croak 'usage: $nntp->ihave( MESSAGE-ID [, MESSAGE ])';
555 $nntp->_IHAVE($mid) && $nntp->datasend(@_)
556 ? @_ == 0 || $nntp->dataend
562 @_ == 1 or croak 'usage: $nntp->last()';
565 $nntp->_LAST && $nntp->message =~ /(<[^>]+>)/o
572 @_ == 1 or croak 'usage: $nntp->list()';
582 @_ >= 2 or croak 'usage: $nntp->newgroups( SINCE [, DISTRIBUTIONS ])';
584 my $time = _timestr(shift);
585 my $dist = shift || "";
587 $dist = join(",", @{$dist})
590 $nntp->_NEWGROUPS($time,$dist)
597 @_ >= 3 or croak 'usage: $nntp->newnews( SINCE [, GROUPS [, DISTRIBUTIONS ]])';
599 my $time = _timestr(shift);
600 my $grp = @_ ? shift : $nntp->group;
601 my $dist = shift || "";
604 $grp = join(",", @{$grp})
607 $dist = join(",", @{$dist})
610 $nntp->_NEWNEWS($grp,$time,$dist)
611 ? $nntp->_articlelist
617 @_ == 1 or croak 'usage: $nntp->next()';
620 $nntp->_NEXT && $nntp->message =~ /(<[^>]+>)/o
627 @_ >= 1 or croak 'usage: $nntp->post( [ MESSAGE ] )';
630 $nntp->_POST() && $nntp->datasend(@_)
631 ? @_ == 0 || $nntp->dataend
637 @_ == 1 or croak 'usage: $nntp->quit()';
640 $nntp->_QUIT && $nntp->SUPER::close;
645 @_ == 1 or croak 'usage: $nntp->slave()';
652 ## The following methods are not implemented by all servers
657 @_ == 1 || @_ == 2 or croak 'usage: $nntp->active( [ PATTERN ] )';
660 $nntp->_LIST('ACTIVE',@_)
667 @_ == 1 or croak 'usage: $nntp->active_times()';
670 $nntp->_LIST('ACTIVE.TIMES')
677 @_ == 1 or croak 'usage: $nntp->distributions()';
680 $nntp->_LIST('DISTRIBUTIONS')
681 ? $nntp->_description
685 sub distribution_patterns
687 @_ == 1 or croak 'usage: $nntp->distributions()';
693 $nntp->_LIST('DISTRIB.PATS') && ($arr = $nntp->read_until_dot)
694 ? [grep { /^\d/ && (chomp, $_ = [ split /:/ ]) } @$arr]
700 @_ == 1 || @_ == 2 or croak 'usage: $nntp->newsgroups( [ PATTERN ] )';
703 $nntp->_LIST('NEWSGROUPS',@_)
704 ? $nntp->_description
710 @_ == 1 or croak 'usage: $nntp->overview_fmt()';
713 $nntp->_LIST('OVERVIEW.FMT')
714 ? $nntp->_articlelist
720 @_ == 1 or croak 'usage: $nntp->subscriptions()';
723 $nntp->_LIST('SUBSCRIPTIONS')
724 ? $nntp->_articlelist
730 @_ == 1 || @_ == 2 or croak 'usage: $nntp->listgroup( [ GROUP ] )';
733 $nntp->_LISTGROUP(@_)
734 ? $nntp->_articlelist
740 @_ == 1 or croak 'usage: $nntp->reader()';
743 $nntp->_MODE('READER');
748 @_ == 1 || @_ == 2 or croak 'usage: $nntp->xgtitle( [ PATTERN ] )';
752 ? $nntp->_description
758 @_ >= 2 && @_ <= 4 or croak 'usage: $nntp->xhdr( HEADER, [ MESSAGE-ID | MESSAGE_NUM [, MESSAGE-NUM ]] )';
759 my($nntp,$hdr,$first) = splice(@_,0,3);
769 if(defined $last && $last > $first);
772 $nntp->_XHDR($hdr, $arg)
773 ? $nntp->_description
779 @_ == 2 || @_ == 3 or croak 'usage: $nntp->xover( RANGE )';
780 my($nntp,$first) = splice(@_,0,2);
789 if(defined $last && $last > $first);
799 @_ == 4 || @_ == 5 or croak '$nntp->xpat( HEADER, PATTERN, RANGE )';
800 my($nntp,$hdr,$pat,$first) = splice(@_,0,4);
809 if(defined $last && $last > $first);
812 $pat = join(" ", @$pat)
815 $nntp->_XPAT($hdr,$arg,$pat)
816 ? $nntp->_description
822 @_ == 2 or croak 'usage: $nntp->xpath( MESSAGE-ID )';
826 unless $nntp->_XPATH($mid);
828 my $m; ($m = $nntp->message) =~ s/^\d+\s+//o;
829 my @p = split /\s+/, $m;
831 wantarray ? @p : $p[0];
836 @_ == 2 || @_ == 3 or croak 'usage: $nntp->xrover( RANGE )';
837 my($nntp,$first) = splice(@_,0,2);
847 if(defined $last && $last > $first);
857 @_ == 1 or croak 'usage: $nntp->date()';
860 $nntp->_DATE && $nntp->message =~ /(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/
861 ? timegm($6,$5,$4,$3,$2-1,$1)
867 ## Private subroutines
873 my @g = reverse((gmtime($time))[0..5]);
876 sprintf "%02d%02d%02d %02d%02d%02d GMT", @g;
882 my $arr = $nntp->read_until_dot or
890 my @a = split(/[\s\n]+/,$ln);
891 $hash->{$a[0]} = [ @a[1,2,3] ];
900 my $arr = $nntp->read_until_dot or
908 my @a = split(/[\t\n]/,$ln);
909 $hash->{$a[0]} = @a[1,2,3];
918 my $arr = $nntp->read_until_dot;
929 my $arr = $nntp->read_until_dot or
940 if $ln =~ s/^\s*(\S+)\s*//o;
951 sub _ARTICLE { shift->command('ARTICLE',@_)->response == CMD_OK }
952 sub _AUTHINFO { shift->command('AUTHINFO',@_)->response }
953 sub _BODY { shift->command('BODY',@_)->response == CMD_OK }
954 sub _DATE { shift->command('DATE')->response == CMD_INFO }
955 sub _GROUP { shift->command('GROUP',@_)->response == CMD_OK }
956 sub _HEAD { shift->command('HEAD',@_)->response == CMD_OK }
957 sub _HELP { shift->command('HELP',@_)->response == CMD_INFO }
958 sub _IHAVE { shift->command('IHAVE',@_)->response == CMD_MORE }
959 sub _LAST { shift->command('LAST')->response == CMD_OK }
960 sub _LIST { shift->command('LIST',@_)->response == CMD_OK }
961 sub _LISTGROUP { shift->command('LISTGROUP',@_)->response == CMD_OK }
962 sub _NEWGROUPS { shift->command('NEWGROUPS',@_)->response == CMD_OK }
963 sub _NEWNEWS { shift->command('NEWNEWS',@_)->response == CMD_OK }
964 sub _NEXT { shift->command('NEXT')->response == CMD_OK }
965 sub _POST { shift->command('POST',@_)->response == CMD_OK }
966 sub _QUIT { shift->command('QUIT',@_)->response == CMD_OK }
967 sub _SLAVE { shift->command('SLAVE',@_)->response == CMD_OK }
968 sub _STAT { shift->command('STAT',@_)->response == CMD_OK }
969 sub _MODE { shift->command('MODE',@_)->response == CMD_OK }
970 sub _XGTITLE { shift->command('XGTITLE',@_)->response == CMD_OK }
971 sub _XHDR { shift->command('XHDR',@_)->response == CMD_OK }
972 sub _XPAT { shift->command('XPAT',@_)->response == CMD_OK }
973 sub _XPATH { shift->command('XPATH',@_)->response == CMD_OK }
974 sub _XOVER { shift->command('XOVER',@_)->response == CMD_OK }
975 sub _XROVER { shift->command('XROVER',@_)->response == CMD_OK }
976 sub _XTHREAD { shift->unsupported }
977 sub _XSEARCH { shift->unsupported }
978 sub _XINDEX { shift->unsupported }
989 && defined fileno($nntp)
993 sub DESTROY { shift->close }