File Coverage

blib/lib/FTN/Pkt.pm
Criterion Covered Total %
statement 59 175 33.7
branch 2 62 3.2
condition 0 18 0.0
subroutine 18 32 56.2
pod 4 4 100.0
total 83 291 28.5


line stmt bran cond sub pod time code
1             package FTN::Pkt;
2              
3 1     1   23790 use strict;
  1         3  
  1         40  
4 1     1   5 use warnings;
  1         2  
  1         55  
5             require 5.6.0;
6             our $VERSION = "1.02";
7              
8             package FTN::Pkt::utils;
9              
10 1     1   5 use strict;
  1         6  
  1         25  
11 1     1   13 use warnings;
  1         2  
  1         36  
12             require Exporter;
13 1     1   5 use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS);
  1         1  
  1         108  
14              
15             BEGIN
16             {
17 1     1   20 @ISA = qw(Exporter);
18 1         5 %EXPORT_TAGS = (utils => [qw(parse_addr datetime trunk trunkzero hextime my_sleep)]);
19 1         47 Exporter::export_ok_tags('utils');
20 1         21 1;
21             }
22              
23 1     1   845 use POSIX qw(strftime);
  1         17869  
  1         9  
24 1     1   15724 use Time::HiRes qw(usleep gettimeofday);
  1         8964  
  1         8  
25              
26             my $PRECISION = 0.1;
27              
28             #========================================================
29              
30             # Here is some auxiliary functions. Not for client use.
31              
32             #========================================================
33              
34             sub parse_addr($)
35             {
36 0     0   0 my $addr = shift;
37 0 0       0 return (undef, undef, undef, undef) unless $addr;
38 0 0       0 $addr .= ".0" unless $addr =~ /\.\d+$/;
39 0         0 my @result = $addr =~ /(\d)\:(\d+)\/(\d+)\.(\d+)/;
40 0         0 return @result;
41             }
42              
43             #========================================================
44              
45             sub datetime(;$)
46             {
47 0     0   0 my $tm = shift;
48 0   0     0 $tm ||= time();
49 0         0 my @MON = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
50 0         0 my @curtime = localtime($tm);
51 0         0 return strftime("%d ", @curtime).$MON[$curtime[4]].strftime(" %y %H:%M:%S", @curtime);
52             }
53              
54             #========================================================
55              
56             sub trunk($$)
57             {
58 0     0   0 my ($str, $len) = @_;
59 0 0 0     0 if (length($str) > $len && $len > 0){
60 0         0 $str = substr($str, 0, $len);
61             }
62 0         0 return $str;
63             }
64              
65             #========================================================
66              
67             sub trunkzero($$)
68             {
69 0     0   0 return trunk($_[0], $_[1]) . "\0";
70             }
71              
72             #========================================================
73              
74             sub hextime()
75             {
76 0     0   0 my $msec = int(gettimeofday() / $PRECISION) % 0xffffffff;
77 0         0 return sprintf("%08x", $msec);
78             }
79              
80             #========================================================
81              
82             sub my_sleep()
83             {
84 0     0   0 usleep($PRECISION*1000000*1.1);
85             }
86              
87             #========================================================
88              
89             package FTN::Msg;
90              
91 1     1   3937 use strict;
  1         256  
  1         70  
92 1     1   7 use warnings;
  1         2  
  1         78  
93              
94             import FTN::Pkt::utils qw(:utils);
95              
96              
97             #========================================================
98              
99 1         8 use fields qw(fromaddr toaddr fromname toname tearline origin subj text area msgid reply
100 1     1   1417 topkt frompkt pid tid date);
  1         2749  
101              
102             #========================================================
103              
104             sub new
105             {
106 0     0   0 my FTN::Msg $self = shift;
107 0 0       0 $self = fields::new($self) unless ref $self;
108 0         0 $self->update(@_);
109 0         0 return $self;
110             }
111              
112             #========================================================
113              
114             sub update
115             {
116 0     0   0 my FTN::Msg $self = shift;
117 0         0 my %params = @_;
118 0         0 foreach(keys %params){
119 0         0 $self->{$_} = $params{$_};
120             }
121             }
122              
123             #========================================================
124              
125             sub make_msgid(;$)
126             {
127 0     0   0 my FTN::Msg $self = shift;
128 0         0 my $msgid = shift;
129 0 0       0 unless ($msgid){
130 0         0 $msgid = hextime();
131 0         0 my_sleep();
132             }
133 0 0       0 die "make_msgid: unknown fromaddr" unless $self->{fromaddr};
134 0         0 return ($self->{msgid} = "$self->{fromaddr} $msgid");
135             }
136              
137             #========================================================
138              
139             sub as_string()
140             {
141 0     0   0 my FTN::Msg $self = shift;
142 0         0 my $res = "\n";
143 0         0 foreach(qw (fromname fromaddr toname toaddr frompkt topkt frompkt area msgid reply pid subj)) {
144 0 0 0     0 $res .= "$_ : $self->{$_}\n" if exists $self->{$_} and defined $self->{$_};
145             }
146 0 0 0     0 $res .= '-' x 72 . "\n$self->{text}\n".'-' x 72 ."\n"
147             if exists $self->{text} and defined $self->{text};
148 0         0 foreach(qw (tearline origin)) {
149 0 0 0     0 $res .= "$_ : $self->{$_}\n" if exists $self->{$_} and defined $self->{$_};
150             }
151 0         0 return $res;
152             }
153              
154             #========================================================
155             #
156             # Internal method. Returns binary representation of the message inside the packet.
157              
158             sub _packed()
159             {
160 0     0   0 my FTN::Msg $self = shift;
161 0         0 my ($fromzone, $fromnet, $fromnode, $frompoint) = parse_addr($self->{fromaddr});
162 0         0 my ($tozone, $tonet, $tonode, $topoint) = parse_addr($self->{toaddr});
163 0         0 my ($pfromzone, $pfromnet, $pfromnode, $pfrompoint) = parse_addr($self->{frompkt});
164 0         0 my ($ptozone, $ptonet, $ptonode, $ptopoint) = parse_addr($self->{topkt});
165 0         0 my $template = "v7a20";
166 0 0       0 $self->make_msgid() unless ($self->{msgid});
167 0         0 my $result = pack $template, 2, $pfromnode, $ptonode, $pfromnet, $ptonet,
168             0, 0, datetime($self->{date});
169 0 0       0 $result .= trunkzero(($self->{toname} ? $self->{toname} : "All"), 35);
170 0         0 $result .= trunkzero($self->{fromname}, 35);
171 0 0       0 $result .= trunkzero(($self->{subj} ? $self->{subj} : ""), 71);
172 0         0 my $msgtail = "\x0";
173 0 0       0 if ($self->{area}){
174 0         0 $result .= "AREA:".$self->{area}."\xd";
175 0         0 $msgtail = "SEEN-BY: $pfromnet/$pfromnode\x0d\x01PATH: $pfromnet/$pfromnode\x0d\x00";
176             # -------------->
177             }else{
178 0         0 $result .= "\x01INTL $tozone:$tonet/$tonode $fromzone:$fromnet/$fromnode\xd";
179 0 0       0 $result .= "\x01FMPT $frompoint\xd" if $frompoint != 0;
180 0 0       0 $result .= "\x01TOPT $topoint\xd" if $topoint != 0;
181             }
182 0 0       0 $result .= "\x01REPLY: $self->{reply}\x0d" if $self->{reply};
183 0         0 $result .= "\x01MSGID: $self->{msgid}\x0d";
184 0         0 $result .= "\x01CHRS: CP866 2\x0d";
185 0 0       0 $result .= "\x01PID: $self->{pid}\x0d" if $self->{pid};
186 0 0       0 $result .= sprintf("\x01TID: FTN::Pkt %s\x0d", $FTN::Pkt::VERSION) if $self->{tid};
187 0 0       0 $result .= "\x01Posted: ".datetime()."\x0d" if $self->{date};
188 0         0 my $text = $self->{text};
189 0         0 $text =~ s/\n/\xd/sg;
190 0         0 $result .= $text;
191 0 0       0 $result .= "\x0d--- ".($self->{tearline} ? $self->{tearline} : "")."\xd";
192 0         0 my $origin = " * Origin: ";
193 0 0       0 my $origtext = ($self->{origin} ? $self->{origin} : "");
194 0         0 my $origtail = " (".$self->{fromaddr}.")\xd";
195 0         0 my $origtxln = 79 - length ($origin.$origtail);
196 0         0 $origtext = trunk($origtext, $origtxln);
197 0         0 $origin .= $origtext .= $origtail;
198 0 0 0     0 $result .= $origin if ($self->{origin} || $self->{area});
199 0         0 $result .= $msgtail;
200 0         0 return $result;
201             }
202              
203             #========================================================
204              
205             package FTN::Pkt;
206 1     1   1462 use strict;
  1         2  
  1         36  
207 1     1   5 use warnings;
  1         2  
  1         66  
208              
209             import FTN::Pkt::utils qw(:utils);
210              
211 1     1   1598 use FTN::Utils::OS_features;
  1         2  
  1         139  
212 1     1   5 use Carp qw(croak);
  1         2  
  1         328  
213              
214              
215             #========================================================
216              
217 1     1   6 use fields qw(fromaddr toaddr password inbound _msgs);
  1         1  
  1         8  
218              
219             #========================================================
220              
221             # fromaddr, toaddr, password, inbound
222             # msgs
223              
224             sub new {
225              
226 1     1 1 12 my FTN::Pkt $self = shift;
227 1 50       11 $self = fields::new($self) unless ref $self;
228 1         4059 $self->update(@_);
229 1         3 $self->{_msgs} = [];
230 1         4 return $self;
231             }
232              
233             #========================================================
234              
235             sub update
236             {
237 1     1 1 3 my FTN::Pkt $self = shift;
238 1         7 my %params = @_;
239 1 50       5 if(exists $params{_msgs}){
240 0         0 croak "FATAL: can't update '_msgs' directly!";
241             }
242 1         8 foreach(keys %params){
243 4         13 $self->{$_} = $params{$_};
244             }
245             }
246              
247             #========================================================
248              
249             sub add_msg($)
250             {
251 0     0 1   my FTN::Pkt $self = shift;
252 0           my $msg = shift;
253 0           push @{$self->{_msgs}}, $msg;
  0            
254             }
255              
256             #========================================================
257             #
258             # Internal method. Returns binary representation of the packet.
259              
260             sub _packed()
261             {
262 0     0     my FTN::Pkt $self = shift;
263 0           my ($fromzone, $fromnet, $fromnode, $frompoint) = parse_addr($self->{fromaddr});
264 0           my ($tozone, $tonet, $tonode, $topoint) = parse_addr($self->{toaddr});
265 0           my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
266              
267 0           my $template = "v13a8v10V";
268 0 0         my $result = pack $template, $fromnode, $tonode,
269             $year+1900, $mon, $mday, $hour, $min, $sec, 0, 2,
270             $fromnet, $tonet, 0x7766,
271             $self->{password} ? $self->{password} : "",
272             $fromzone, $tozone, 0, 0x100, 0x7766, 1,
273             $fromzone, $tozone, $frompoint, $topoint, 0;
274 0           foreach my $msg(@{$self->{_msgs}}){
  0            
275 0           $msg->update(frompkt => $self->{fromaddr}, topkt => $self->{toaddr});
276 0           $result .= $msg->_packed();
277             }
278 0           $result .= "\x00\x00";
279 0           return $result;
280             }
281              
282              
283             #========================================================
284              
285             sub write_pkt()
286             {
287 0     0 1   my FTN::Pkt $self = shift;
288 0           my $regexp = "${dir_separator}\$";
289 0 0         $self->{inbound} .= $dir_separator unless $self->{inbound} =~ /$regexp/;
290 0           my $filename = $self->{inbound}.hextime() .".tmp";
291 0           my $newname = $filename;
292 0           $newname =~ s/tmp$/pkt/;
293 0           my @repl = split //, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
294 0           for(my $i = 0; -e $filename; $i++){
295 0 0         if($i >= scalar @repl){die "can't make unique tmp name";}
  0            
296 0           substr($filename, -12, 1) = $repl[$i];
297             }
298 0 0         open(PKT, ">", $filename) or die "can't open $filename : $!";
299 0 0         binmode PKT if $needs_binmode;
300 0           print PKT $self->_packed();
301 0           close PKT;
302 0           for(my $i = 0; -e $newname; $i++){
303 0 0         if($i >= scalar @repl){die "can't make unique pkt name";}
  0            
304 0           substr($newname, -12, 1) = $repl[$i];
305             }
306 0 0         rename $filename, $newname or die "can't rename $filename -> $newname : $!";
307 0           return $newname;
308             }
309              
310             #========================================================
311              
312             1;
313              
314             =head1 NAME
315              
316             FTN::Pkt - a module to make FTN-style mail packets
317              
318             =head1 SYNOPSIS
319            
320             my $pkt = new FTN::Pkt (
321             fromaddr => '2:9999/999.128',
322             toaddr => '2:9999/999',
323             password => 'password',
324             inbound => '/var/spool/fido/inbound'
325             );
326             my $msg = new FTN::Msg(
327             fromname => 'Vassily Poupkine',
328             toname => 'Poupa Vassilyev',
329             subj => 'Hello',
330             text => "Hi, Poupa!\n\nHow do you do?\n\n--\nVassily",
331             fromaddr => '2:9999/999.128',
332             origin => 'My cool origin',
333             tearline => '/usr/bin/perl',
334             area => 'poupa.local',
335             reply => '2:9999/999.1 fedcba987',
336             date => 1210918822, # unixtime format
337             pid => 'Super-Duper Editor v0.01',
338             tid => 1
339             );
340             $pkt->add_msg($msg);
341             $pkt->write_pkt();
342              
343             =head1 DESCRIPTION
344              
345             This module can be used to make FTN-style mail packets. Either echomail or netmail are supported.
346             You can specify @REPLY cludge. @MSGID may be auto-generated or specified manually.
347              
348             If C present then message treated as echomail. Othervise it becomes netmail (C required).
349              
350             =head1 FTN::Msg methods
351              
352             =over 8
353              
354             =item C
355              
356             A constructor. Some initialization parameters can be passed via C<%hash>.
357             Possible ones are:
358             C.
359              
360             All parameters are text but C is boolean. If I then @TID cludge will be added to message.
361              
362             =item C
363              
364             Changes the message. See C for parameters allowed.
365              
366             =item C
367              
368             Generates @MSGID, sets it inside the message and and returns it. Possible parameter is only second part of @MSGID, without source address.
369             If parameter omitted then all @MSGID parts will be auto-generated. Auto-generation method use I as basis,
370             so don't allow more than one process to generate @MSGIDs in the same time.
371              
372             =item C
373              
374             Returns string representation of message. For debugging.
375              
376             =back
377              
378             =head1 FTN::Pkt methods
379              
380             =over 8
381              
382             =item C
383              
384             A constructor. Some initialization parameters can be passed via C<%hash>.
385             Possible ones are: C
386              
387             =item C
388              
389             Changes the packet. See C for parameters allowed.
390              
391             =item C
392              
393             Adds a message to the packet. C<$msg> must be a reference to C object.
394              
395             =item C
396              
397             Writes the packet to a disk into C directory. Filename is auto-generated.
398             Don't allow more than one process to write at the same time. Returns resulting filename.
399              
400             =back
401              
402             =head1 LIMITATIONS
403              
404             CP866 codepage is hardcoded.
405              
406             =head1 REQUIREMENTS
407              
408             FTN::OS_features module required (included in this package).
409              
410             Supported platforms: UNIX and Win32 have been tested. All others may work or not.
411              
412             =head1 COPYRIGHT
413              
414             Copyright 2008 Dmitry V. Kolvakh
415              
416             This library is free software.
417             You may copy or redistribute it under the same terms as Perl itself.
418              
419             =head1 AUTHOR
420              
421             Dmitry V. Kolvakh aka Keu
422              
423             2:5054/89@FIDOnet
424              
425             =cut