File Coverage

blib/lib/Net/ICAP/Message.pm
Criterion Covered Total %
statement 384 433 88.6
branch 129 190 67.8
condition 47 87 54.0
subroutine 31 32 96.8
pod 12 12 100.0
total 603 754 79.9


line stmt bran cond sub pod time code
1             # Net::ICAP::Message -- Message object for ICAP
2             #
3             # (c) 2012, Arthur Corliss
4             #
5             # $Revision: 0.04 $
6             #
7             # This software is licensed under the same terms as Perl, itself.
8             # Please see http://dev.perl.org/licenses/ for more information.
9             #
10             #####################################################################
11              
12             #####################################################################
13             #
14             # Environment definitions
15             #
16             #####################################################################
17              
18             package Net::ICAP::Message;
19              
20 4     4   1113 use 5.008003;
  4         12  
21              
22 4     4   23 use strict;
  4         8  
  4         80  
23 4     4   19 use warnings;
  4         7  
  4         115  
24 4     4   21 use vars qw($VERSION @ISA @_properties @_methods);
  4         8  
  4         216  
25 4     4   1651 use Class::EHierarchy qw(:all);
  4         16175  
  4         484  
26 4     4   1566 use Paranoid::Debug;
  4         6979  
  4         190  
27 4     4   27 use Net::ICAP::Common qw(:std :debug);
  4         7  
  4         480  
28 4     4   1574 use HTTP::Date;
  4         10857  
  4         317  
29              
30             ($VERSION) = ( q$Revision: 0.04 $ =~ /(\d+(?:\.(\d+))+)/s );
31              
32             @ISA = qw(Class::EHierarchy);
33              
34 4     4   29 use constant DEF_CHUNK => 1024;
  4         9  
  4         13180  
35              
36             @_properties = (
37             [ CEH_RESTR | CEH_ARRAY, '_errors' ],
38             [ CEH_RESTR | CEH_SCALAR, '_time-out', 0 ],
39             [ CEH_RESTR | CEH_SCALAR, '_version', ICAP_VERSION ],
40             [ CEH_RESTR | CEH_SCALAR, '_start' ],
41             [ CEH_RESTR | CEH_HASH, '_headers' ],
42             [ CEH_RESTR | CEH_SCALAR, '_req-hdr' ],
43             [ CEH_RESTR | CEH_SCALAR, '_res-hdr' ],
44             [ CEH_RESTR | CEH_SCALAR, '_body' ],
45             [ CEH_RESTR | CEH_SCALAR, '_body_type' ],
46             [ CEH_RESTR | CEH_SCALAR, '_trailer' ],
47             [ CEH_RESTR | CEH_SCALAR, '_ieof', 0 ],
48             [ CEH_RESTR | CEH_CODE, '_chunked_cref' ],
49             );
50              
51             @_methods = (
52             [ CEH_RESTR, '_getLine' ],
53             [ CEH_RESTR, '_putLine' ],
54             [ CEH_RESTR, '_parseHeaders' ],
55             [ CEH_RESTR, '_genHeaders' ],
56             [ CEH_RESTR, '_readChunked' ],
57             [ CEH_RESTR, '_writeChunked' ],
58             [ CEH_RESTR, '_parseEncap' ],
59             [ CEH_RESTR, '_genEncap' ],
60             [ CEH_RESTR, '_validHeaders' ],
61             );
62              
63             #####################################################################
64             #
65             # Module code follows
66             #
67             #####################################################################
68              
69             sub _initialize ($;@) {
70              
71             # Purpose: Does nothing, base class
72             # Returns: Boolean
73             # Usage: $rv = $obj->_initialization;
74              
75 30     30   5388 my $obj = shift;
76 30         71 my %args = @_;
77 30         52 my $rv = 1;
78              
79 30         100 pdebug( 'entering w/%s and %s', ICAPDEBUG1, $obj, keys %args );
80 30         1127 pIn();
81              
82             # Set internal state if args were passed
83 30 100       268 $rv = $obj->version( $args{version} ) if exists $args{version};
84 3         18 $rv = $obj->setHeaders( %{ $args{headers} } )
85 30 100 66     105 if exists $args{headers} and $rv;
86 30 100 66     86 $rv = $obj->reqhdr( $args{reqhdr} ) if exists $args{reqhdr} and $rv;
87 30 50 33     85 $rv = $obj->reshdr( $args{reshdr} ) if exists $args{reshdr} and $rv;
88             $rv = $obj->body( @args{qw(body_type body)} )
89             if $rv
90             and exists $args{body_type}
91 30 50 33     141 and exists $args{body};
      66        
92 30 100 66     92 $rv = $obj->trailer( $args{trailer} ) if exists $args{trailer} and $rv;
93             $rv = $obj->set( '_chunked_cref', $args{chunked_handler} )
94 30 50 33     145 if $rv and exists $args{chunked_handler};
95              
96             # Set sigalarm handler
97 30 50       97 if ( exists $args{'time-out'} ) {
98 0         0 $obj->set( '_time-out', $args{'time-out'} );
99             $SIG{ALRM} = sub {
100 0     0   0 die pdebug( 'connection timed out', ICAPDEBUG1 ), "\n";
101 0         0 };
102             }
103              
104 30         83 pOut();
105 30         265 pdebug( 'leaving w/rv: %s', ICAPDEBUG1, $rv );
106              
107 30         958 return $rv;
108             }
109              
110             sub _validHeaders ($) {
111              
112             # Purpose: Returns a list of valid ICAP headers
113             # Returns: Array
114             # Usage: @val = $obj->_validHeaders;
115              
116 119     119   1066 my $obj = shift;
117              
118 119         551 return qw(Cache-Control Connection Date Expires
119             Pragma Trailer Upgrade Encapsulated);
120             }
121              
122             sub error ($;$) {
123              
124             # Purpose: Returns errors or logs a new one
125             # Returns: Array
126             # Usage: $obj->error($msg);
127             # Usage: @errors = $obj->error;
128              
129 55     55 1 116 my $obj = shift;
130 55         89 my $msg = shift;
131 55         98 my @rv;
132              
133 55 100       144 if ( defined $msg ) {
134 28         85 $obj->push( '_errors', $msg );
135 28         1225 pdebug( $msg, ICAPDEBUG1 );
136             }
137 55         651 @rv = $obj->get('_errors');
138              
139 55         3184 return @rv;
140             }
141              
142             sub ieof ($;$) {
143              
144             # Purpose: Gets/sets the ieof flag
145             # Returns: Boolean
146             # Usage: $rv = $obj->ieof;
147              
148 14     14 1 2022 my $obj = shift;
149 14         27 my ($val) = @_;
150 14         24 my $rv;
151              
152 14 100       51 $rv =
153             scalar @_
154             ? $obj->set( '_ieof', $val )
155             : $obj->get('_ieof');
156              
157 14         727 return $rv;
158             }
159              
160             sub reqhdr ($;$) {
161              
162             # Purpose: Gets/sets the request header
163             # Returns: Boolean/String
164             # Usage: $rv = $obj->reqhdr($text);
165             # Usage: $header = $obj->reqhdr;
166              
167 19     19 1 618 my $obj = shift;
168 19         40 my ($header) = @_;
169 19         28 my $rv;
170              
171 19 100       70 $rv =
172             scalar @_
173             ? $obj->set( '_req-hdr', $header )
174             : $obj->get('_req-hdr');
175              
176 19         942 return $rv;
177             }
178              
179             sub reshdr ($;$) {
180              
181             # Purpose: Gets/sets the response header
182             # Returns: Boolean/String
183             # Usage: $rv = $obj->reshdr($text);
184             # Usage: $header = $obj->reshdr;
185              
186 17     17 1 34 my $obj = shift;
187 17         32 my ($header) = @_;
188 17         27 my $rv;
189              
190 17 50       60 $rv =
191             scalar @_
192             ? $obj->set( '_res-hdr', $header )
193             : $obj->get('_res-hdr');
194              
195 17         774 return $rv;
196             }
197              
198             sub trailer ($;$) {
199              
200             # Purpose: Gets/sets the trailer
201             # Returns: Boolean/String
202             # Usage: $rv = $obj->trailer($text);
203             # Usage: $trailer = $obj->trailer;
204              
205 18     18 1 32 my $obj = shift;
206 18         39 my ($trailer) = @_;
207 18         27 my $rv;
208              
209 18 100       67 $rv =
210             scalar @_
211             ? $obj->set( '_trailer', $trailer )
212             : $obj->get('_trailer');
213              
214 18         874 return $rv;
215             }
216              
217             sub body ($;$$) {
218              
219             # Purpose: Gets/sets the body type and content
220             # Returns: Array
221             # Usage: ($type, $body) = $obj->body;
222             # Usage: $rv = $obj->body($type, $body);
223              
224 36     36 1 473 my $obj = shift;
225 36         70 my ( $type, $body ) = @_;
226 36         61 my $rv;
227              
228 36 100       76 if (@_) {
229 2   33     7 $rv = $obj->set( '_body_type', $type )
230             && $obj->set( '_body', $body );
231             } else {
232 34         88 $rv = [ $obj->get('_body_type'), $obj->get('_body') ];
233             }
234              
235 36 100       3237 return ref $rv eq 'ARRAY' ? @$rv : $rv;
236             }
237              
238             sub version ($;$) {
239              
240             # Purpose: Gets/sets version
241             # Returns: Boolean/String
242             # Usage: $rv = $obj->version($version);
243             # Usage: $method = $obj->version;
244              
245 25     25 1 56 my $obj = shift;
246 25         46 my $version = shift;
247 25         44 my $rv;
248              
249 25         77 pdebug( 'entering w/%s', ICAPDEBUG1, $version );
250 25         969 pIn();
251              
252 25 100       243 if ( defined $version ) {
253              
254             # Write mode
255 13 50       72 if ( $version eq ICAP_VERSION ) {
256 13         33 $rv = $obj->set( '_version', $version );
257             } else {
258 0         0 pdebug( 'invalid version passed: %s', ICAPDEBUG1, $version );
259 0         0 $rv = 0;
260             }
261              
262             } else {
263              
264             # Read mode
265 12         43 $rv = $obj->get('_version');
266             }
267              
268 25         1582 pOut();
269 25         196 pdebug( 'leaving w/rv: %s', ICAPDEBUG1, $rv );
270              
271 25         925 return $rv;
272             }
273              
274             sub _getLine ($$) {
275              
276             # Purpose: Retrieves input from passed string ref/object/file handle
277             # Returns: String
278             # Usage: $line = _getLine($fh);
279             # Usage: $line = _getLine(\$text);
280             # Usage: $line = _getLine($io_handle);
281              
282 290     290   2510 my $obj = shift;
283 290         425 my $ref = shift;
284 290         642 my $to = $obj->get('_time-out');
285 290         13289 my ( $line, @lines );
286              
287 290         734 pdebug( 'entering w/%s', ICAPDEBUG4, $ref );
288 290         9128 pIn();
289              
290 290         2161 eval {
291 290 50       646 alarm $to if $to;
292              
293 290 50       600 if ( defined $ref ) {
294 290 100       1125 if ( ref $ref eq 'SCALAR' ) {
    100          
    50          
295 5         47 ( $line, @lines ) = split /\r\n/s, $$ref;
296 5         25 $$ref = join "\r\n", @lines;
297 5         16 $line .= "\r\n";
298             } elsif ( ref $ref eq 'GLOB' ) {
299 5         26 $line = <$ref>;
300             } elsif ( $ref->isa('IO::Handle') ) {
301 280         4841 $line = $ref->getline;
302             } else {
303 0         0 $obj->error("don't know what to do with ref $ref");
304             }
305             } else {
306 0         0 $obj->error('undefined value passed for reference');
307             }
308              
309 290 50       6553 alarm 0 if $to;
310             };
311              
312 290 50 33     751 if ( $@ and $@ =~ /connection timed out/s ) {
313 0         0 $obj->error('connection timed out');
314 0         0 $line = undef;
315             }
316              
317 290         748 pOut();
318 290         2244 pdebug( 'leaving w/rv: %s', ICAPDEBUG4, $line );
319              
320 290         9755 return $line;
321             }
322              
323             sub _putLine ($$@) {
324              
325             # Purpose: Writes strings to passed string ref/object/file handle
326             # Returns: Boolean
327             # Usage: $rv = _putLine($fh, @lines);
328             # Usage: $rv = _putLine(\$text, @lines);
329             # Usage: $rv = _putLine($io_handle, @lines);
330              
331 71     71   604 my $obj = shift;
332 71         102 my $ref = shift;
333 71         177 my @lines = splice @_;
334 71         119 my $rv = 0;
335              
336 71         193 pdebug( 'entering w/%s, %s line(s)', ICAPDEBUG4, $ref, scalar @lines );
337 71         2603 pIn();
338              
339 71 50       578 if ( defined $ref ) {
340 71 50       159 if ( ref $ref eq 'SCALAR' ) {
    0          
    0          
341 71         103 $rv = 1;
342 71         213 $$ref .= join '', @lines;
343             } elsif ( ref $ref eq 'GLOB' ) {
344 0         0 $rv = print $ref join '', @lines;
345             } elsif ( $ref->isa('IO::Handle') ) {
346 0         0 $rv = $ref->print( join '', @lines );
347             } else {
348 0         0 $obj->error("don't know what to do with ref $ref");
349             }
350             } else {
351 0         0 $obj->error(
352             pdebug( 'undefined value passed for reference', ICAPDEBUG1 ) );
353             }
354              
355 71         196 pOut();
356 71         533 pdebug( 'leaving w/rv: %s', ICAPDEBUG4, $rv );
357              
358 71         2246 return $rv;
359             }
360              
361             sub _parseHeaders (@) {
362              
363             # Purpose: Parses lines of text to extract headers
364             # Returns: Hash
365             # Usage: %headers = _parseHeaders(@lines);
366              
367 27     27   303 my @lines = splice @_;
368 27         77 my ( $text, $line, $k, $v, %headers );
369              
370 27         86 pdebug( 'entering w/%s line(s) of text', ICAPDEBUG3, scalar @lines );
371 27         897 pIn();
372              
373 27 50       230 if ( scalar @lines ) {
374 27         104 $text = join "\r\n", @lines;
375              
376             # Fold header continuation lines
377 27         104 $text =~ s/\r\n\s+/ /sg;
378              
379             # Get new set of lines, each one a different header
380 27         125 @lines = split /\r\n/s, $text;
381              
382 27         69 foreach $line (@lines) {
383 121         588 ( $k, $v ) = ( $line =~ m/^(\S+):\s*(.*?)\s*$/s );
384 121 50 33     522 last unless defined $k and defined $v;
385 121 50       367 $headers{$k} = exists $headers{$k} ? "$headers{$k},$v" : $v;
386             }
387             }
388              
389 27         85 pOut();
390 27         252 pdebug( 'leaving w/%s headers', ICAPDEBUG3, scalar keys %headers );
391              
392 27         977 return %headers;
393             }
394              
395             sub _genHeaders ($) {
396              
397             # Purpose: Returns header block
398             # Returns: String
399             # Usage: $headers = $obj->_genHeaders;
400              
401 17     17   151 my $obj = shift;
402 17         49 my %headers = $obj->getHeaders;
403 17         906 my @valid = $obj->_validHeaders;
404 17         39 my $text = '';
405 17         30 my ( $h, $v );
406              
407 17         39 foreach $h (@valid) {
408 276 100       608 if ( exists $headers{$h} ) {
409 70         118 $v = $headers{$h};
410 70 50 33     405 $text .= "$h: $v\r\n" if defined $v and length $v;
411             }
412             }
413              
414 17         85 return $text;
415             }
416              
417             sub setHeaders ($@) {
418              
419             # Purpose: Sets all valid headers
420             # Returns: Boolean
421             # Usage: $rv = $obj->setHeaders(%headers);
422              
423 30     30 1 64 my $obj = shift;
424 30         99 my %headers = splice @_;
425 30         114 my @valid = $obj->_validHeaders;
426 30         68 my $rv = 1;
427 30         44 my $k;
428              
429 30         83 pdebug( 'entering', ICAPDEBUG2 );
430 30         560 pIn();
431              
432             # Validate headers
433 30         264 foreach $k ( keys %headers ) {
434 128 100 100     344 if ( $k =~ /^X-\w+[\w-]*/s or grep { $_ eq $k } @valid ) {
  2176         3613  
435              
436             # Chomp header value
437 100         227 $headers{$k} =~ s/\r\n$//s;
438              
439             } else {
440              
441 28         49 $rv = 0;
442 28         101 $obj->error("ignoring invalid header: $k");
443 28         66 delete $headers{$k};
444             }
445             }
446              
447             # Store anything left
448 30 50       147 $obj->set( '_headers', %headers )
449             if scalar keys %headers;
450              
451 30         1844 pOut();
452 30         239 pdebug( 'leaving w/rv: %s', ICAPDEBUG2, $rv );
453              
454 30         1075 return $rv;
455             }
456              
457             sub getHeaders ($) {
458              
459             # Purpose: Gets all headers
460             # Returns: Hash
461             # Usage: %headers = $obj->getHeaders;
462              
463 20     20 1 3272 my $obj = shift;
464 20         53 return $obj->get('_headers');
465             }
466              
467             sub header ($$;$) {
468              
469             # Purpose: Gets/sets the requested header
470             # Returns: Boolean/String
471             # Usage: $value = $obj->header($name);
472             # Usage: $rv = $obj->header($name, $value);
473              
474 72     72 1 1155 my $obj = shift;
475 72         152 my $header = shift;
476 72 100       189 my $v = @_ ? $_[0] : '(omitted)';
477 72         192 my @valid = $obj->_validHeaders;
478 72         152 my ( $value, $rv );
479              
480 72         207 pdebug( 'entering w/%s, %s, %s', ICAPDEBUG1, $obj, $header, $v );
481 72         2976 pIn();
482              
483 72 50 66     704 if ( $header =~ /^X-\w+[\w-]*/s or grep { $_ eq $header } @valid ) {
  1199         2067  
484              
485             # Valid header requested
486 72 100       160 if (@_) {
487              
488             # Write mode
489 10         19 $value = shift @_;
490 10 50       27 if ( defined $value ) {
491              
492             # Set mode
493 10         48 $obj->merge( '_headers', $header, $value );
494 10         549 $rv = 1;
495              
496             } else {
497              
498             # Delete mode
499 0         0 $obj->remove( '_headers', $header );
500 0         0 $rv = 1;
501             }
502              
503             } else {
504              
505             # Read mode
506 62         187 ($rv) = $obj->subset( '_headers', $header );
507             }
508             } else {
509 0         0 $obj->error("invalid header requested: $header");
510             }
511              
512 72         3077 pOut();
513 72         601 pdebug( 'leaving w/rv: %s', ICAPDEBUG1, $rv );
514              
515 72         2420 return $rv;
516             }
517              
518             sub _readChunked ($$) {
519              
520             # Purpose: Reads chunked-encoded text
521             # Returns: String
522             # Usage: $text = $obj->-readChunked($input);
523              
524 7     7   65 my $obj = shift;
525 7         11 my $input = shift;
526 7         20 my $cref = $obj->get('_chunked_cref');
527 7         318 my $text = '';
528 7         12 my ( $line, $chunk, $c, $ieof );
529              
530 7         23 pdebug( 'entering w/%s, %s', ICAPDEBUG2, $obj, $input );
531 7         258 pIn();
532              
533 7 50       61 if ( defined( $line = $obj->_getLine($input) ) ) {
534              
535             # Get initial chunk size
536 7         32 ($c) = ( $line =~ /^([0-9a-fA-F]+)\r\n$/s );
537 7         19 $c = hex $c;
538 7 50       18 $text = '' if defined $cref;
539              
540 7         19 OUTER: while ($c) {
541              
542             # Read lines until chunk size is met
543 7         13 $chunk = '';
544 7         21 while ( length $chunk <= $c ) {
545 9         32 $line = $obj->_getLine($input);
546              
547 9 50       46 unless ( defined $line ) {
548 0         0 $obj->error('ran out of text while reading chunks');
549 0         0 last OUTER;
550             }
551              
552 9         50 $chunk .= $line;
553             }
554              
555             # Trim line separator appended to chunk
556 7         33 $chunk =~ s/\r\n$//s;
557              
558             # Check for chunk size accuracy
559 7 50       26 if ( length $chunk == $c ) {
560              
561             # Save chunk
562 7         20 $text .= $chunk;
563 7 50       19 &$cref( $obj, $text ) if defined $cref;
564              
565             # Get next chunk size
566 7         25 $line = $obj->_getLine($input);
567 7 50       24 if ( defined $line ) {
568 7         35 ( $c, $ieof ) =
569             ( $line =~ /^([0-9a-fA-F]+)(; ieof)?\r\n$/s );
570 7         17 $c = hex $c;
571 7 50       31 $obj->ieof(1) if defined $ieof;
572             } else {
573 0         0 $c = 0;
574 0         0 $obj->error('missing next chunk header');
575             }
576              
577             } else {
578 0         0 $obj->error( "chunk size mismatch: expected $c "
579 0         0 . "recieved @{[ length $chunk ]}" );
580 0         0 last;
581             }
582             }
583             }
584              
585 7         22 pOut();
586 7         62 pdebug( 'leaving w/%s bytes of text', ICAPDEBUG2, length $text );
587              
588 7         226 return $text;
589             }
590              
591             sub _writeChunked ($) {
592              
593             # Purpose: Writes the body in chunked encoding
594             # Returns: String
595             # Usage: $chunked = $obj->_writeChunked;
596              
597 10     10   99 my $obj = shift;
598 10         30 my $body = $obj->get('_body');
599 10         459 my $cref = $obj->get('_chunked_cref');
600 10         428 my $rv = '';
601 10         18 my ( @segments, $r );
602              
603 10         33 pdebug( 'entering', ICAPDEBUG2 );
604 10         197 pIn();
605              
606 10 50       100 if ( defined $cref ) {
    50          
607 0         0 $body = &$cref($obj);
608 0 0 0     0 if ( defined $body and length $body ) {
    0          
609 0   0     0 while ( defined $body and length $body ) {
610 0         0 $rv .= sprintf "%x\r\n%s\r\n", length $_, $body;
611 0         0 $body = &$cref($obj);
612             }
613 0         0 $rv .= "0\r\n";
614             } elsif ( defined $body ) {
615 0         0 $rv .= "0\r\n";
616             }
617             } elsif ( defined $body ) {
618 10   66     68 while ( defined $body and length $body ) {
619 10         86 push @segments, substr $body, 0, DEF_CHUNK, '';
620             }
621 10         30 foreach (@segments) {
622 10         45 $rv .= sprintf "%x\r\n", length $_;
623              
624             # the following should probably be in the above sprintf,
625             # but I'm a little leery of some of the sprintf bugs in the
626             # past with binary data...
627 10         33 $rv .= "$_\r\n";
628             }
629 10 100       35 $rv .= $obj->ieof ? "0; ieof\r\n" : "0\r\n";
630             }
631              
632 10 50       31 $r = defined $rv ? "@{[ length $rv ]} characters" : 'undef';
  10         49  
633 10         37 pOut();
634 10         83 pdebug( 'leaving w/rv: %s', ICAPDEBUG2, $r );
635              
636 10         340 return $rv;
637             }
638              
639             sub _parseEncap ($$) {
640              
641             # Purpose: Parses message body as per rules in parseEncap
642             # Returns: Boolean
643             # Usage: $rv = $obj->parseEncap($input);
644              
645 12     12   112 my $obj = shift;
646 12         19 my $input = shift;
647 12         21 my $rv = 1;
648 12         33 my @ventitites = qw(rep-hdr req-hdr res-body req-body null-body opt-body);
649 12         27 my ( $encap, @entities, $t, $l, $n, $offset, $line, $text );
650              
651 12         34 pdebug( 'entering', ICAPDEBUG2 );
652 12         216 pIn();
653              
654 12         104 $encap = $obj->header('Encapsulated');
655 12 50       38 if ( defined $encap ) {
656 12         55 @entities = split /\s*,\s*/s, $encap;
657              
658             # Sanity tests:
659             #
660             # 1) there must be one (and only one) *-body tag as last entity
661 12         57 $n = scalar grep /^\w+-body=\d+$/s, @entities;
662 12 50       39 unless ( $n == 1 ) {
663 0         0 $rv = 0;
664 0         0 $obj->error(
665             "invalid number of body entities in Encapsulated: $encap");
666             }
667 12 50       50 unless ( $entities[$#entities] =~ /^\w+-body=\d+$/s ) {
668 0         0 $rv = 0;
669 0         0 $obj->error( 'last entity must be a body entities in '
670             . "Encapsulated: $encap" );
671             }
672              
673             # 2) only one req-hdr and/or resp-hdr allowed, but are optional
674 12         42 $n = scalar grep /^req-hdr=\d+$/s, @entities;
675 12 50       48 unless ( $n <= 1 ) {
676 0         0 $rv = 0;
677 0         0 $obj->error("too many req-hedr entities in Encapsulated: $encap");
678             }
679 12         35 $n = scalar grep /^res-hdr=\d+$/s, @entities;
680 12 50       33 unless ( $n <= 1 ) {
681 0         0 $rv = 0;
682 0         0 $obj->error("too many res-hedr entities in Encapsulated: $encap");
683             }
684              
685             # 3) offsets are monotonically increasing
686 12         21 $n = undef;
687 12         23 foreach ( map {m/=(\d+)$/s} @entities ) {
  24         86  
688 24 50 66     103 unless ( !defined $n or $_ > $n ) {
689 0         0 $rv = 0;
690 0         0 $obj->error( 'Encapsulated offsets aren\'t monotonically '
691             . "ordered: $encap" );
692 0         0 last;
693             }
694 24 100       59 unless ( defined $n ) {
695 12 50       41 unless ( $_ == 0 ) {
696 0         0 $rv = 0;
697 0         0 $obj->error( 'Encapsulated offsets don\'t start at '
698             . "0: $encap" );
699 0         0 last;
700             }
701             }
702 24         49 $n = $_;
703             }
704              
705             # 4) no unknown entity types
706 12 50       85 if ( scalar grep !m/^(?:re[qs]-hdr|(?:opt|null|re[qs])-body)=\d+$/s,
707             @entities ) {
708 0         0 $rv = 0;
709 0         0 $obj->error("invalid entities in Encapsulated: $encap");
710             }
711              
712             # Read data
713 12 50       35 if ($rv) {
714 12         17 $offset = 0;
715 12         32 while (@entities) {
716 24         84 ( $t, $l ) = split /=/s, shift @entities;
717 24         62 ( $line, $text ) = ( '', '' );
718              
719 24 100       89 if ( $t =~ /-hdr$/s ) {
    50          
720              
721             # Read headers
722 12         42 while ( defined( $line = $obj->_getLine($input) ) ) {
723 81 100       251 last if $line eq "\r\n";
724 69         306 $text .= $line;
725             }
726              
727             # Store the headers
728 12         45 $obj->set( "_$t", $text );
729              
730             } elsif ( $t =~ /-body$/s ) {
731 12 100       36 unless ( $t eq 'null-body' ) {
732 7         30 $text = $obj->_readChunked($input);
733 7         29 $obj->set( '_body', $text );
734 7         472 $obj->set( '_body_type', $t );
735             }
736             }
737              
738             # Check the intermediate length
739 24 100       1271 if (@entities) {
740 12         48 ($offset) = ( $entities[0] =~ /=(\d+)$/s );
741 12 100       48 $l = $l == 0 ? $offset - 2 : $offset - $l - 2;
742 12 50       57 unless ( length $text == $l ) {
743 0         0 $rv = 0;
744 0         0 $obj->error( "$t length mismatch: expected $l "
745             . 'characters, recieved '
746             . length $text );
747             }
748             }
749             }
750             }
751              
752             # Check for trailers for all message bodies
753 12 100       78 if ( grep /\b(?:res|req|opt)-body=/s, $encap ) {
754 7         27 $line = $obj->_getLine($input);
755 7 100 66     64 if ( defined $line and $line ne "\r\n" ) {
756 2         6 $text = $line;
757 2         9 while ( defined( $line = $obj->_getLine($input) ) ) {
758 4 100       17 last if $line eq "\r\n";
759 2         10 $text .= $line;
760             }
761 2         9 $obj->set( '_trailer', $text );
762             }
763             }
764              
765             } else {
766 0         0 pdebug( 'no Encapsulated header found', ICAPDEBUG2 );
767             }
768              
769 12         167 pOut();
770 12         99 pdebug( 'leaving w/rv: %s', ICAPDEBUG2, $rv );
771              
772 12         401 return $rv;
773             }
774              
775             sub _genEncap ($) {
776              
777             # Purpose: Returns a string Encapsulated value based on
778             # stored data
779             # Returns: String
780             # Usage: $encap = $obj->_genEncap;
781              
782 17     17   166 my $obj = shift;
783 17         32 my $encap = '';
784 17         28 my $offset = 0;
785 17         31 my ( $t, $tt );
786              
787 17         64 pdebug( 'entering', ICAPDEBUG2 );
788 17         307 pIn();
789              
790             # Check for req-hdr
791 17         146 $t = $obj->reqhdr;
792 17 100 66     92 if ( defined $t and length $t ) {
793 10         30 $encap = "req-hdr=$offset";
794 10         29 $offset += length($t) + 2;
795             }
796              
797             # Check for res-hdr
798 17         53 $t = $obj->reshdr;
799 17 100 66     77 if ( defined $t and length $t ) {
800 5 100       17 $encap .= ', ' if length $encap;
801 5         15 $encap .= "res-hdr=$offset";
802 5         16 $offset += length($t) + 2;
803             }
804              
805             # Check for body
806 17         51 ( $tt, $t ) = $obj->body;
807 17 100 66     136 if ( defined $tt and length $tt and defined $t and length $t ) {
      66        
      33        
808 10 50       38 $encap .= ', ' if length $encap;
809 10         30 $encap .= "$tt=$offset";
810             } else {
811 7 100       23 $encap .= ', ' if length $encap;
812 7         22 $encap .= "null-body=$offset";
813             }
814              
815 17         59 pOut();
816 17         141 pdebug( 'leaving w/rv: %s', ICAPDEBUG2, $encap );
817              
818 17         669 return $encap;
819             }
820              
821             sub parse ($$) {
822              
823             # Purpose: Reads request/response from input
824             # Returns: Boolean
825             # Usage: $rv = $obj->parse($input);
826              
827 27     27 1 431 my $obj = shift;
828 27         51 my $input = shift;
829 27         46 my ( $start, @headers, $line, $icap_msg );
830 27         44 my $rv = 0;
831              
832 27         81 pdebug( 'entering w/%s, %s', ICAPDEBUG1, $obj, $input );
833 27         966 pIn();
834              
835             # Purge internal state
836 27         258 $obj->empty('_errors');
837 27         1140 $obj->empty('_headers');
838 27         1069 $obj->set( '_start', undef );
839 27         1659 $obj->set( '_req-hdr', undef );
840 27         1628 $obj->set( '_res-hdr', undef );
841 27         1656 $obj->set( '_body', undef );
842 27         1657 $obj->set( '_trailer', undef );
843 27         1650 $obj->set( '_ieof', 0 );
844              
845             # Read the transaction
846 27         1692 while ( defined( $line = $obj->_getLine($input) ) ) {
847 175 100       547 last if $line eq "\r\n";
848 148         527 $icap_msg .= $line;
849             }
850              
851             # Process $icap_msg
852 27 50       92 if ( length $icap_msg ) {
853              
854             # Strip any trailing line terminations
855 27         114 $icap_msg =~ s/\r\n$//s;
856              
857             # Separate start line from headers
858 27         188 ( $start, @headers ) = split /\r\n/s, $icap_msg;
859              
860             # Store the start line, headers, and parse Encap data
861 27         108 $obj->set( '_start', $start );
862 27   66     1882 $rv = $obj->setHeaders( _parseHeaders(@headers) )
863             && $obj->_parseEncap($input);
864             }
865              
866 27         93 pOut();
867 27         222 pdebug( 'leaving w/rv: %s', ICAPDEBUG1, $rv );
868              
869 27         860 return $rv;
870             }
871              
872             sub generate ($$) {
873              
874             # Purpose: Writes output to the passed reference
875             # Returns: Boolean
876             # Usage: $rv = $obj->generate($ref);
877              
878 17     17 1 36 my $obj = shift;
879 17         28 my $ref = shift;
880 17         29 my $rv = 1;
881 17         31 my ( $d, $t, $tt, $l );
882              
883 17         53 pdebug( 'entering w/%s', ICAPDEBUG1, $ref );
884 17         565 pIn();
885              
886             # Update Date Header if missing
887 17         169 $d = $obj->header('Date');
888 17 100 66     135 $obj->header( 'Date', time2str(time) )
889             unless defined $d and length $d;
890              
891             # Print Start/Status line
892 17         51 $t = $obj->get('_start') . "\r\n";
893 17 50       895 $rv = defined $t and length $t ? $obj->_putLine( $ref, $t ) : 0;
    50          
894              
895             # Print ICAP headers
896 17 50       48 if ($rv) {
897 17         52 $obj->merge( qw(_headers Encapsulated), $obj->_genEncap );
898 17         849 $l = $t = $obj->_genHeaders . "\r\n";
899 17 50       109 $rv = defined $t and length $t ? $obj->_putLine( $ref, $t ) : 0;
    50          
900             }
901              
902             # Print req-hdr
903 17 50       44 if ($rv) {
904 17         60 $t = $obj->get('_req-hdr');
905 17 100 66     912 if ( defined $t and length $t ) {
906 10         46 while ( $t !~ /\r\n\r\n$/s ) { $t .= "\r\n" }
  10         56  
907 10         24 $l = $t;
908 10         35 $rv = $obj->_putLine( $ref, $t );
909             }
910             }
911              
912             # Print res-hdr
913 17 50       49 if ($rv) {
914 17         47 $t = $obj->get('_res-hdr');
915 17 100 66     819 if ( defined $t and length $t ) {
916 5         28 while ( $t !~ /\r\n\r\n$/s ) { $t .= "\r\n" }
  5         27  
917 5         13 $l = $t;
918 5         17 $rv = $obj->_putLine( $ref, $t );
919             }
920             }
921              
922             # Print body
923 17 50       46 if ($rv) {
924 17         42 ( $tt, $t ) = $obj->body;
925 17 100 66     99 if ( defined $t and length $t ) {
926 10         38 $l = $t = $obj->_writeChunked;
927 10         45 $rv = $obj->_putLine( $ref, $t );
928             }
929             }
930              
931             # Print trailer
932 17 50       47 if ($rv) {
933 17         84 $t = $obj->trailer;
934 17 100 66     67 if ( defined $t and length $t ) {
935 2         6 $l = $t;
936 2         9 $rv = $obj->_putLine( $ref, $t );
937             }
938             }
939              
940             # Print end of message termination
941 17 50       47 if ($rv) {
942 17         65 while ( $l !~ /\r\n\r\n/s ) {
943 10         22 $l .= "\r\n";
944 10         31 $rv = $obj->_putLine( $ref, "\r\n" );
945             }
946             }
947              
948 17         48 pOut();
949 17         137 pdebug( 'leaving w/rv: %s', ICAPDEBUG1, $rv );
950              
951 17         556 return $rv;
952             }
953              
954             1;
955              
956             __END__