File Coverage

blib/lib/Net/BEEP/Lite/Message.pm
Criterion Covered Total %
statement 171 172 99.4
branch 63 72 87.5
condition 16 24 66.6
subroutine 23 23 100.0
pod 13 14 92.8
total 286 305 93.7


line stmt bran cond sub pod time code
1             # $Id: Message.pm,v 1.9 2004/04/22 20:45:32 davidb Exp $
2             #
3             # Copyright (C) 2003 Verisign, Inc.
4             #
5             # This library is free software; you can redistribute it and/or
6             # modify it under the terms of the GNU Lesser General Public
7             # License as published by the Free Software Foundation; either
8             # version 2.1 of the License, or (at your option) any later version.
9             #
10             # This library is distributed in the hope that it will be useful,
11             # but WITHOUT ANY WARRANTY; without even the implied warranty of
12             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13             # Lesser General Public License for more details.
14             #
15             # You should have received a copy of the GNU Lesser General Public
16             # License along with this library; if not, write to the Free Software
17             # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18             # USA
19              
20             package Net::BEEP::Lite::Message;
21              
22             =head1 NAME
23              
24             Net::BEEP::Lite::Message
25              
26             =head1 SYNOPSIS
27              
28             use Net::BEEP::Lite::Message;
29              
30             my $message = Net::BEEP::Lite::Message->new
31             ( Frame => $frame );
32              
33             $message->add_frame($next_frame);
34              
35             my $message2 = new Net::BEEP::Lite::Message
36             ( Type => 'MSG',
37             Channel => 3,
38             Content => $content,
39             ContentType => 'application/xml' );
40              
41             for my $frame ($message2->next_frame($seqno, $max_size)) {
42             # ... send the frame
43             }
44              
45             =head1 DESCRIPTON
46              
47             This class represents a BEEP message, the basic unit of data transport
48             at the user level. It contains both a reference to the session that
49             it was received on (or will be sent by), and content. It contains
50             methods to construct and deconstruct the message into frames, the
51             actual base unit of transport.
52              
53             This class is expected to be used in user code by both clients and
54             servers.
55              
56             =cut
57              
58 5     5   60676 use Carp;
  5         12  
  5         392  
59              
60 5     5   3800 use Net::BEEP::Lite::Frame;
  5         15  
  5         168  
61              
62 5     5   43 use strict;
  5         29  
  5         154  
63 5     5   25 use warnings;
  5         8  
  5         14065  
64              
65             =head1 CONSTRUCTOR
66              
67             =over 4
68              
69             =item new( I )
70              
71             This is the main constructor. It takes a named parameter list as its
72             argument. The following parameters are recognized:
73              
74             =over 4
75              
76             =item Session
77              
78             A reference to the session that the message was received by or will be sent by.
79              
80             =item Type
81              
82             The message type (e.g., "MSG", "RPY", "ERR", etc.)
83              
84             =item Msgno
85              
86             The message number. This is generally fetched from the session, or,
87             for replies, from the message being replied to. This should only be
88             set for replies. 'MSG's should be set by the session on sending it.
89              
90             =item Channel
91              
92             The channel number.
93              
94             =item Payload
95              
96             The message payload (including the MIME header(s)). Either this or
97             "Content" and "ContentType" MUST be supplied.
98              
99             =item Content
100              
101             The message content (not including the MIME headers).
102              
103             =item ContentType
104              
105             The message content type. This will be added as a MIME header when
106             forming the payload. If not supplied, the default content type is
107             'application/octet-stream'.
108              
109             =item ContentEncoding
110              
111             The content encoding. This will be added as a MIME header when
112             forming the payload, if supplied.
113              
114             =item Frame
115              
116             A frame to form the basis (or entire) message. Generally, this is
117             supplied on its own.
118              
119             =item Debug
120              
121             Emit debug messages.
122              
123             =back
124              
125             =back
126              
127             =cut
128              
129             sub new {
130 11     11 1 980 my $this = shift;
131 11   33     57 my $class = ref($this) || $this;
132 11         58 my %args = @_;
133              
134 11         15 my $self = {};
135 11         32 bless $self, $class;
136              
137             # ANSNO is only set for ANS message, but we would like it to be a
138             # defined hash element in either case.
139 11         30 $self->{ansno} = undef;
140              
141 11         18 $self->{debug} = 0;
142 11         21 $self->{trace} = 0;
143             # this is used by next_frame()
144 11         19 $self->{frame_offset} = 0;
145 11         19 $self->{generated_first_frame} = 0;
146              
147 11         22 $self->{payload} = $self->{content} = "";
148              
149 11         38 for (keys %args) {
150 35         45 my $val = $args{$_};
151              
152 35 100       98 /^Type$/i and do {
153 7         27 $self->type(uc $val);
154 7         14 next;
155             };
156 28 100       66 /^Msgno$/i and do {
157 4         15 $self->msgno($val);
158 4         6 next;
159             };
160 24 100       60 /^Ansno$/i and do {
161 1         4 $self->ansno($val);
162 1         1 next;
163             };
164 23 100       51 /^Channel$/i and do {
165 7         21 $self->{channel_number} = $val;
166 7         29 next;
167             };
168 16 100       33 /^Payload$/i and do {
169 1         2 $self->{payload} = $val;
170 1         2 next;
171             };
172 15 100       45 /^Content$/i and do {
173 5         10 $self->{content} = $val;
174 5         9 next;
175             };
176 10 100       28 /^Content.?Type$/i and do {
177 2         4 $self->{content_type} = $val;
178 2         4 next;
179             };
180 8 100       20 /^Content.?Encoding$/i and do {
181 1         3 $self->{content_encoding} = $val;
182 1         3 next;
183             };
184 7 100       24 /^Frame$/i and do {
185 3         13 $self->{type} = $val->type();
186 3         11 $self->{msgno} = $val->msgno();
187 3         13 $self->{ansno} = $val->ansno();
188 3         12 $self->{channel_number} = $val->channel_number();
189 3         10 $self->{payload} = $val->payload();
190 3         9 next;
191             };
192 4 100       12 /^Debug$/i and do {
193 2         3 $self->{debug} = $val;
194 2         11 next;
195             };
196 2 50       16 /^Trace$/i and do {
197 2         3 $self->{trace} = $val;
198 2         4 next;
199             };
200             }
201              
202 11         44 $self;
203             }
204              
205             =head1 METHODS
206              
207             =over 4
208              
209             =item type([$val])
210              
211             Returns the type of the message (e.g., "MSG", "RPY", etc.). Updates
212             the type to $val if provided.
213              
214             =cut
215              
216             sub type {
217 18     18 1 34 my $self = shift;
218 18         23 my $val = shift;
219              
220 18 100       51 $self->{type} = $val if $val;
221 18         55 $self->{type};
222             }
223              
224             =item msgno([$val])
225              
226             Returns (or sets) the message number of the message.
227              
228             =cut
229              
230             sub msgno {
231 24     24 1 34 my $self = shift;
232 24         28 my $val = shift;
233              
234 24 100       78 $self->{msgno} = $val if defined $val;
235 24         81 $self->{msgno};
236             }
237              
238              
239             sub ansno {
240 9     9 0 51 my $self = shift;
241 9         13 my $val = shift;
242              
243 9 100       22 $self->{ansno} = $val if defined $val;
244 9         26 $self->{ansno};
245             }
246              
247             =item size()
248              
249             Returns the size of the payload of the message.
250              
251             =cut
252              
253             sub size {
254 4     4 1 7 my $self = shift;
255              
256 4         10 length($self->payload());
257             }
258              
259             =item channel_number([$va])
260              
261             Returns or sets the channel number of the message.
262              
263             =cut
264              
265             sub channel_number {
266 19     19 1 29 my $self = shift;
267 19         24 my $val = shift;
268              
269 19 50       43 $self->{channel_number} = $val if defined $val;
270 19         82 $self->{channel_number};
271             }
272              
273             =item payload()
274              
275             Returns the payload of the message, forming it from the content,
276             content type, and content encoding, if necessary.
277              
278             =cut
279              
280             sub payload {
281 23     23 1 62 my $self = shift;
282              
283 23         47 $self->_content_payload_transfer();
284 23         72 $self->{payload};
285             }
286              
287             =item content_type()
288              
289             Returns the content type of the message (either set or parsed from the
290             payload).
291              
292             =cut
293              
294             sub content_type {
295 2     2 1 7 my $self = shift;
296 2         4 $self->_content_payload_transfer();
297              
298 2 100       16 $self->{content_type} || 'application/octet-stream';
299             }
300              
301             =item content_encoding()
302              
303             Returns the content encoding of the message (if one where set or
304             detected from the payload).
305              
306             =cut
307              
308             sub content_encoding {
309 2     2 1 5 my $self = shift;
310 2         5 $self->_content_payload_transfer();
311              
312 2 100       15 $self->{content_encoding} || 'binary';
313             }
314              
315             =item content()
316              
317             Returns the content of the message (the payload minus MIME headers).
318             It calculates the content from the payload, if necessary.
319              
320             =cut
321              
322             sub content {
323 4     4 1 78 my $self = shift;
324 4         8 $self->_content_payload_transfer();
325              
326 4         14 $self->{content};
327             }
328              
329             =item _content_payload_transfer()
330              
331             This will force the translation between content and payload.
332             Currently this can only be done once, but then again, this class
333             doesn't support changing either of them through the API. If you do
334             so, be sure to set the other to undef so that this routine will work.
335              
336             =cut
337              
338             sub _content_payload_transfer {
339 31     31   40 my $self = shift;
340              
341 31 100 100     228 if (! $self->{content} and $self->{payload}) {
    100 100        
342 4         13 $self->_decode_mime();
343             }
344             elsif (! $self->{payload} and $self->{content}) {
345 5         16 $self->_encode_mime();
346             }
347             }
348              
349             =item _decode_mime()
350              
351             Parse the payload into content, content type, and content encoding.
352             This is normally called automatically.
353              
354             =cut
355              
356             sub _decode_mime {
357 4     4   7 my $self = shift;
358              
359 4         10 my $payload = $self->{payload};
360              
361 4         10 my ($content, @headers) = _decode_mime_entity($payload);
362              
363 4         10 $self->{content} = $content;
364              
365 4         13 for my $header (@headers) {
366 2 50       13 next if not $header =~ /^(\S+):\s*(\S.*$)/;
367 2 100       24 if ($1 eq 'Content-Type') {
    50          
368 1         4 $self->{content_type} = $2;
369             } elsif ($1 eq 'Content-Transfer-Encoding') {
370 1         6 $self->{content_encoding} = $2;
371             }
372             }
373             }
374              
375             =item _encode_mime()
376              
377             Calculate the payload from the set content, content type, and content
378             encoding. This is normally called automatically.
379              
380             =cut
381              
382             sub _encode_mime {
383 5     5   8 my $self = shift;
384              
385 5         6 my @headers;
386 5         8 my $ct = $self->{content_type};
387 5 100 66     29 if ($ct and $ct ne 'application/octet-stream') {
388 2         5 push @headers, "Content-Type: $ct";
389             }
390 5         9 my $ce = $self->{content_encoding};
391 5 100 66     18 if ($ce and $ce ne "binary") {
392 1         3 push @headers, "Content-Transfer-Encoding: $ce";
393             }
394              
395 5         19 my $payload = _encode_mime_entity($self->{content}, @headers);
396              
397 5         15 $self->{payload} = $payload;
398             }
399              
400             =item add_frame($frame)
401              
402             Add a frame to an existing message. This is used to assemble a
403             message from multiple frames. For now, this method doesn't really
404             check that the additional frames really belong to the message.
405              
406             =cut
407              
408             sub add_frame {
409 3     3 1 11 my $self = shift;
410 3         6 my $frame = shift;
411             # TODO: check to see if this frame matches the message.
412              
413 3 50 33     57 if (!$self->{payload} and $self->{content}) {
414 0         0 $self->payload(); # force the payload to be constructed.
415             }
416             # we want to force the content to be constructed from the payload
417             # after this.
418 3         7 $self->{content} = undef;
419              
420 3         12 my ($content, @headers) = _decode_mime_entity($frame->payload());
421 3 50       19 $self->{payload} .= $content if $content;
422             }
423              
424             =item has_more_frames()
425              
426             Return true if there are more frames to be generated from this message.
427              
428             =cut
429              
430             sub has_more_frames {
431 4     4 1 554 my $self = shift;
432              
433 4 100       20 return 1 if not $self->{generated_first_frame};
434              
435 3         8 my $remainder = length($self->payload()) - $self->{frame_offset};
436              
437 3 100       19 $remainder > 0 ? 1 : 0;
438             }
439              
440             =item next_frame($seqno, $max_size)
441              
442             Returns the "next" frame in the message, based on given maximum size.
443             This method will split the message into multiple frames if the maximum
444             size forces it to. This will return undef when the entire message has
445             been rendered into frames. See the reset_frames() method if you wish
446             to convert the same message into frames multiple times.
447              
448             =cut
449              
450             sub next_frame {
451 10     10 1 55 my $self = shift;
452 10         16 my $seqno = shift;
453 10         12 my $max_size = shift;
454              
455 10         33 my $chno = $self->channel_number();
456              
457 10 50       24 confess "msgno was not set before next_frame()"
458             if (not defined $self->msgno());
459              
460 10 50       25 croak "maximum size of zero for message: type = ", $self->type(),
461             " msgno = ", $self->msgno(), " chno = $chno\n" if $max_size == 0;
462              
463 10         25 my $payload = $self->payload();
464 10         25 my $remainder = length($payload) - $self->{frame_offset};
465              
466 10 100 100     46 return undef if ($self->{generated_first_frame} and $remainder <= 0);
467              
468 8         19 $self->{generated_first_frame} = 1;
469              
470 8         12 my $more;
471             my $local_payload;
472              
473 8 100       21 if ($remainder > $max_size) {
474 4 50       13 print STDERR "***** fragmenting message.\n" if $self->{debug};
475 4         14 $local_payload = substr($payload, $self->{frame_offset}, $max_size);
476 4         9 $more = '*';
477             } else {
478 4         10 $local_payload = substr($payload, $self->{frame_offset});
479 4         6 $more = '.';
480             }
481              
482 8         52 my $frame = Net::BEEP::Lite::Frame->new
483             (Type => $self->type(),
484             Msgno => $self->msgno(),
485             Ansno => $self->ansno(),
486             More => $more,
487             Seqno => $seqno,
488             Channel => $self->channel_number(),
489             Payload => $local_payload);
490              
491 8         18 $self->{frame_offset} += length($local_payload);
492              
493 8         24 $frame;
494             }
495              
496             =item reset_frames()
497              
498             This will reset the counter used by next_frame(). Use this if you want
499             to start calculating frames from the beginning more than once.
500              
501             =cut
502              
503             sub reset_frames {
504 3     3 1 506 my $self = shift;
505              
506 3         7 $self->{frame_offset} = 0;
507 3         8 $self->{generated_first_frame} = 0;
508             }
509              
510             sub _decode_mime_entity {
511 7     7   12 my $block = shift;
512              
513 7         9 my @headers;
514              
515             # FIXME: this routine really sucks. We need to find a more reliable
516             # method.
517              
518             # first make sure that this looks like a MIME message at all:
519 7 100 33     41 if (not $block or not $block =~ /^Content-Type:/im) {
520 6         31 return ($block, @headers);
521             }
522              
523 1         6 my @lines = split(/\r\n/, $block);
524 1         2 while (1) {
525 3         16 my $line = shift @lines;
526 3         6 chomp $line;
527 3 100       9 last if not $line;
528 2         3 push @headers, $line;
529             }
530              
531 1         4 my $content = join("\r\n", @lines);
532              
533 1         6 ($content, @headers);
534             }
535              
536             sub _encode_mime_entity {
537 5     5   8 my $content = shift;
538 5         7 my @headers = @_;
539              
540 5 100       17 return $content if (not @headers);
541              
542 2         5 my $res = "";
543 2         3 for my $header (@headers) {
544 3         6 chomp $header;
545 3         8 $res .= $header . "\r\n";
546             }
547 2         3 $res .= "\r\n";
548 2         3 $res .= $content;
549              
550 2         4 $res;
551             }
552              
553             =pod
554              
555             =back
556              
557             =head1 SEE ALSO
558              
559             =over 4
560              
561             =item L
562              
563             =item L
564              
565             =back
566              
567             =cut
568              
569             1;