File Coverage

blib/lib/Net/IPP/IPPAttribute.pm
Criterion Covered Total %
statement 69 162 42.5
branch 38 86 44.1
condition 11 33 33.3
subroutine 9 13 69.2
pod 0 5 0.0
total 127 299 42.4


line stmt bran cond sub pod time code
1             ###
2             # Copyright (c) 2004 Matthias Hilbig
3             # All rights reserved.
4             #
5             # This program is free software; you may redistribute it and/or modify it
6             # under the same terms as Perl itself.
7             #
8              
9             package Net::IPP::IPPAttribute;
10              
11 2     2   11 use strict;
  2         4  
  2         154  
12 2     2   11 use warnings;
  2         4  
  2         95  
13              
14 2     2   12 use Carp;
  2         4  
  2         165  
15              
16 2     2   10 use Net::IPP::IPP qw(:all);
  2         4  
  2         10981  
17              
18             require Exporter;
19             our @ISA = ("Exporter");
20             our @EXPORT_OK = qw(encodeAttribute decodeAttribute);
21             our %EXPORT_TAGS = ( 'all' => \@EXPORT_OK );
22              
23             # this variable is set to 1 by IPPRequest.pm to turn HP Bugfixing on
24             #
25             # one of the HP printers encodes the values of NAME_WITH_LANGUAGE and
26             # TEXT_WITH_LANGUAGE types wrong:
27             #
28             # rfc conform encoding:
29             # val_length[lang_length[lang]name_length[name]]
30             #
31             # HP uses instead:
32             # lang_length[lang]name_length[name]
33             #
34              
35             our $HP_BUGFIX = 0;
36              
37             #
38             # Hash which associates attribute names with default IPP type.
39             # This default type can be overwritten with hash notation:
40             #
41             # "requesting-user-name" => { &TYPE => &NAME_WITH_LANGUAGE,
42             # &VALUE => "de, root" }
43             #
44             # TODO: enter all attributes that can be used in IPP requests
45             my %attributeTypes = (
46              
47             # operation attributes belong into the operation group
48              
49             "attributes-charset" => &CHARSET,
50             "attributes-natural-language" => &NATURAL_LANGUAGE,
51             "printer-uri" => &URI,
52             "which-jobs" => &KEYWORD,
53             "job-uri" => &URI,
54             "job-id" => &INTEGER,
55             "requesting-user-name" => &NAME_WITHOUT_LANGUAGE,
56             "document-format" => &MIME_MEDIA_TYPE,
57             "document-name" => &NAME_WITHOUT_LANGUAGE,
58             "requested-attributes" => &KEYWORD,
59             "limit" => &INTEGER,
60             "printer-info" => &TEXT_WITHOUT_LANGUAGE,
61             "printer-location" => &TEXT_WITHOUT_LANGUAGE,
62             "printer-type" => &ENUM,
63            
64             # job-template-attributes
65             "job-priority" => &INTEGER,
66             "job-hold-until" => &KEYWORD,
67             "job-sheets" => &KEYWORD,
68             "multiple-document-handling" => &KEYWORD,
69             "copies" => &INTEGER,
70             "finishings" => &ENUM,
71             "page-ranges" => &RANGE_OF_INTEGER,
72             "sides" => &KEYWORD,
73             "number-up" => &INTEGER,
74             "orientation-requested" => &ENUM,
75             "media" => &KEYWORD,
76             "media-ready" => &KEYWORD,
77             "printer-resolution" => &RESOLUTION,
78             "print-quality" => &ENUM,
79              
80             );
81              
82             ###
83             # Encode attribute to bytes.
84             #
85             # Parameters: $name - name of attribute
86             # $value - value of attribute
87             #
88             # Return: byte encoded attribute
89             #
90             sub encodeAttribute($$) {
91 0     0 0 0 my $name = shift;
92 0         0 my $value = shift;
93            
94 0         0 my $type;
95 0 0       0 if (ref($value) eq "HASH") {
96             #if value is hashref, overwrite default IPP type
97 0 0       0 if (exists($value->{&TYPE})) {
98 0         0 $type = $value->{&TYPE};
99             }
100 0 0       0 if (!exists($value->{&VALUE})) {
101 0         0 confess "Could not find value in Hash.\n";
102             } else {
103 0         0 $value = $value->{&VALUE};
104             }
105             }
106            
107 0 0       0 if (!$type) {
108 0 0       0 if (exists($attributeTypes{$name})) {
109 0         0 $type = $attributeTypes{$name};
110             } else {
111             # look if template attribute and then use type of base type
112 0         0 my $base;
113 0 0       0 if ($name =~ /^(.*)\-(default|supported)$/) {
114 0         0 $base = $1;
115             }
116 0 0       0 if (exists($attributeTypes{$base})) {
117 0         0 $type = $attributeTypes{$name};
118             } else {
119 0         0 confess "Error: Unknown attribute $name used in request.";
120             }
121             }
122            
123            
124             }
125              
126 0         0 my $bytes = "";
127            
128 0 0       0 if (ref($value) eq "ARRAY") {
129             #if value is arrayref encode isSet
130            
131 0         0 my $size = scalar(@{$value});
  0         0  
132 0         0 for (my $i = 0; $i < $size; $i++) {
133 0         0 $bytes .= pack("C", $type);
134 0         0 my $tValue = transformValue($type, $name, $value->[$i], 0);
135 0 0       0 if ($i == 0) {
136 0         0 $bytes .= pack("n/a*n/a*", $name, $tValue);
137             } else {
138 0         0 $bytes .= pack("nn/a*",0,$tValue);
139             }
140             }
141             } else {
142             #normal encoding
143 0         0 $bytes .= pack("C", $type);
144 0         0 $value = transformValue($type, $name, $value, 0);
145 0         0 $bytes .= pack("n/a*n/a*", $name, $value);
146             }
147              
148 0         0 return $bytes;
149             }
150              
151             ###
152             # Transforms attribute value, two modes are available: encoding and decoding
153             #
154             # Parameter: $type - IPP type to use
155             # $value - value to transform
156             # $decode - 1 for decoding, 0 for encoding
157             #
158             # Return: transformed value
159             #
160             sub transformValue($$$$) {
161 36     36 0 10089 my $type = shift;
162 36         48 my $key = shift;
163 36         51 my $value = shift;
164 36         101 my $decode = shift;
165            
166 36 50 33     979 if ($type == &TEXT_WITHOUT_LANGUAGE
    100 66        
    50 33        
    100 33        
    100 33        
    100 33        
    100 33        
    100 100        
    50 0        
    0          
    0          
167             || $type == &NAME_WITHOUT_LANGUAGE) {
168             #RFC: textWithoutLanguage, LOCALIZED-STRING.
169             #RFC: nameWithoutLanguage
170 0         0 return $value;
171             } elsif ($type == &TEXT_WITH_LANGUAGE
172             || $type == &NAME_WITH_LANGUAGE) {
173             #RFC: textWithLanguage OCTET-STRING consisting of 4 fields:
174             #RFC: a. a SIGNED-SHORT which is the number of
175             #RFC: octets in the following field
176             #RFC: b. a value of type natural-language,
177             #RFC: c. a SIGNED-SHORT which is the number of
178             #RFC: octets in the following field,
179             #RFC: d. a value of type textWithoutLanguage.
180             #RFC: The length of a textWithLanguage value MUST be
181             #RFC: 4 + the value of field a + the value of field c.
182 6 100       15 if ($decode) {
183 3 50       7 if ($IPPAttribute::HP_BUGFIX) {
184 0         0 return $value;
185             } else {
186 3         19 my ($language, $text) = unpack("n/a*n/a*", $value);
187 3         16 return "$language, $text";
188             }
189             } else {
190             #TODO: test if HP needs bugfix also for encoding
191 3         19 $value =~ /^\s*([^,]*?)\s*,\s*([^,]*?)\s*$/;
192 3         21 return pack("n/a*n/a*", $1, $2);
193             }
194             } elsif ($type == &CHARSET
195             || $type == &NATURAL_LANGUAGE
196             || $type == &MIME_MEDIA_TYPE
197             || $type == &KEYWORD
198             || $type == &URI
199             || $type == &URI_SCHEME) {
200             #RFC: charset, US-ASCII-STRING.
201             #RFC: naturalLanguage,
202             #RFC: mimeMediaType,
203             #RFC: keyword, uri, and
204             #RFC: uriScheme
205 0         0 return $value;
206             } elsif ($type == &BOOLEAN) {
207             #RFC: boolean SIGNED-BYTE where 0x00 is 'false' and 0x01 is
208             #RFC: 'true'.
209 10 100       18 if ($decode) {
210 5         18 return unpack("c", $value);
211             } else {
212 5 100       10 if ($value) {
213 3         11 return "\01";
214             } else {
215 2         6 return "\00";
216             }
217             }
218             } elsif ($type == &INTEGER
219             || $type == &ENUM) {
220             #RFC: integer and enum a SIGNED-INTEGER.
221 6 100       10 if ($decode) {
222 3         12 return unpack("N", $value);
223             } else {
224 3         18 return pack("N", $value);
225             }
226             } elsif ($type == &DATE_TIME) {
227             #RFC: dateTime OCTET-STRING consisting of eleven octets whose
228             #RFC: contents are defined by "DateAndTime" in RFC
229             #RFC: 1903 [RFC1903].
230 6 100       45 if ($decode) {
231 3         21 my ($year, $month, $day, $hour, $minute, $seconds, $deciSeconds, $direction, $utcHourDiff, $utcMinuteDiff)
232             = unpack("nCCCCCCaCC", $value);
233 3         21 return "$month-$day-$year,$hour:$minute:$seconds.$deciSeconds,$direction$utcHourDiff:$utcMinuteDiff";
234             } else {
235 3 100       32 if ($value =~ /^\s*(\d+)\s*-\s*(\d+)\s*-\s*(\d+)\s*,\s*(\d+)\s*:\s*(\d+)\s*:\s*(\d+)\s*.\s*(\d+)\s*,\s*([\-\+])\s*(\d+)\s*:\s*(\d+)\s*$/) {
236 2         27 return pack("nCCCCCCaCC", $3, $1, $2, $4, $5, $6, $7, $8, $9, $10);
237             } else {
238 1         8493 carp("Unable to parse date: $value");
239 1         15 return "\00" x 8 . "+" . "\00\00";
240             }
241             }
242             } elsif ($type == &RESOLUTION) {
243             #RFC: resolution OCTET-STRING consisting of nine octets of 2
244             #RFC: SIGNED-INTEGERs followed by a SIGNED-BYTE. The
245             #RFC: first SIGNED-INTEGER contains the value of
246             #RFC: cross feed direction resolution. The second
247             #RFC: SIGNED-INTEGER contains the value of feed
248             #RFC: direction resolution. The SIGNED-BYTE contains
249             #RFC: the units
250             # unit: 3 = dots per inch
251             # 4 = dots per cm
252 4 100       10 if ($decode) {
253 2         6 my ($crossFeedResolution, $feedResolution, $unit) = unpack("NNc", $value);
254 2         3 my $unitText;
255 2 100       8 if ($unit == 3) {
    50          
256 1         2 $unitText = "dpi";
257             } elsif ($unit == 4) {
258 1         3 $unitText = "dpc";
259             } else {
260 0         0 carp ("Unknown Unit value: $unit");
261 0         0 $unitText = $unit;
262             }
263 2         11 return "$crossFeedResolution, $feedResolution $unitText";
264             } else {
265 2         13 my ($crossFeedResolution, $feedResolution, $unitText) =
266             $value =~ /^\s*(\d+)\s*,\s*(\d+)\s*(\w+)\s*$/;
267 2         4 my $unit;
268 2 100       8 if ($unitText eq "dpi") {
    50          
269 1         3 $unit = 3;
270             } elsif ($unitText eq "dpc") {
271 1         2 $unit = 4;
272             } else {
273 0         0 carp ("Unknown Unit: $unitText using dpi instead.");
274 0         0 $unit = 3;
275             }
276 2         15 return pack("NNc", $crossFeedResolution, $feedResolution, $unit);
277             }
278             } elsif ($type == &RANGE_OF_INTEGER) {
279             #RFC: rangeOfInteger Eight octets consisting of 2 SIGNED-INTEGERs.
280             #RFC: The first SIGNED-INTEGER contains the lower
281             #RFC: bound and the second SIGNED-INTEGER contains
282             #RFC: the upper bound.
283 2 100       7 if ($decode) {
284 1         4 my ($lowerBound, $upperBound) = unpack("NN", $value);
285 1         6 return "$lowerBound:$upperBound";
286             } else {
287 1         7 my ($lowerBound, $upperBound) =
288             $value =~ /^\s*(\d+)\s*:\s*(\d+)\s*$/;
289 1         6 return pack("NN", $lowerBound, $upperBound);
290             }
291             } elsif ($type == &OCTET_STRING) {
292             #RFC: octetString OCTET-STRING
293 2         6 return $value;
294             } elsif ($type == &BEG_COLLECTION) {
295 0 0         if ($key) {
296 0           carp "WARNING: Collection Syntax not supported. Attribute \"$key\" will have invalid value.\n";
297             }
298             } elsif ($type == &END_COLLECTION
299             || $type == &MEMBER_ATTR_NAME) {
300 0           return $value;
301             } else {
302 0           carp "Unknown Value type ", sprintf("%#lx",$type) , " for key \"$key\". Performing no transformation.";
303 0           return $value;
304             }
305             }
306              
307             ###
308             # print warning if the key does not consist of word symbols and -, as
309             # then something went probably wrong.
310             #
311             # Parameter: $key - attribute key to test
312             #
313             sub testKey($) {
314 0     0 0   my $key = shift;
315 0 0         if (not $key =~ /^[\w\-]*$/) {
316 0           carp ("Probably wrong attribute key: $key\n");
317             }
318             }
319              
320             ###
321             # test if response is RFC conform: if lengths of key or value is
322             # longer than remaining bytes, something went wrong while decoding.
323             #
324             # As there are (hopefully :-)) no bugs in the decoding functions, the response
325             # is not RFC conform.
326             #
327             # TODO: maybe implement length check for different attribute types:
328             # maximum lengths of the different types:
329             # 'textWithLanguage <= 1023 AND 'naturalLanguage' <= 63
330             # 'textWithoutLanguage' <= 1023
331             # 'nameWithLanguage' <= 255 AND 'naturalLanguage' <= 63
332             # 'nameWithoutLanguage' <= 255
333             # 'keyword' <= 255
334             # 'enum' = 4
335             # 'uri' <= 1023
336             # 'uriScheme' <= 63
337             # 'charset' <= 63
338             # 'naturalLanguage' <= 63
339             # 'mimeMediaType' <= 255
340             # 'octetString' <= 1023
341             # 'boolean' = 1
342             # 'integer' = 4
343             # 'rangeOfInteger' = 8
344             # 'dateTime' = 11
345             # 'resolution' = 9
346             # '1setOf X'
347             #
348             sub testLengths($$) {
349 2     2   2460 use bytes;
  2         23  
  2         11  
350            
351 0     0 0   my $bytes = shift;
352 0           my $offset = shift;
353              
354 0           my $keyLength = unpack("n", substr($bytes, $offset, 2));
355            
356 0 0         if ($offset + 2 + $keyLength > length($bytes)) {
357 0           my $dump = bytesToString($bytes);
358 0           print STDERR "---IPP RESPONSE DUMP (current offset: $offset):---\n$dump\n";
359 0           confess("ERROR: IPP response is not RFC conform.");
360             }
361            
362 0           my $valueLength = unpack("n", substr($bytes, $offset + 2 + $keyLength, 2));
363            
364 0 0         if ($offset + 4 + $keyLength + $valueLength > length($bytes)) {
365 0           my $dump = bytesToString($bytes);
366 0           print STDERR "---IPP RESPONSE DUMP (current offset: $offset):\n---$dump\n";
367 0           confess("ERROR: IPP response is not RFC conform.");
368             }
369             }
370              
371             ###
372             # Decode next attribute from IPP Response
373             #
374             # Parameters: $bytes - IPP Response
375             # $offsetref - reference to current position in IPP Response
376             # $type - type of attribute
377             # $group - reference to group into which to insert the attribute
378             #
379              
380             my $previousKey; # used for 1setOf values
381              
382             sub decodeAttribute($$$$) {
383 0     0 0   my $bytes = shift;
384 0           my $offsetref = shift;
385 0           my $type = shift;
386 0           my $group = shift;
387              
388 0           my $data;
389 2     2   630 { use bytes;
  2         5  
  2         17  
  0            
390 0           $data = substr($bytes, $$offsetref);
391             }
392            
393 0           my ($key, $value, $addValue);
394            
395             #TODO: novalue
396            
397             # HP BUG!!!!
398 0 0 0       if ($IPPAttribute::HP_BUGFIX && ($type == &TEXT_WITH_LANGUAGE
      0        
399             || $type == &NAME_WITH_LANGUAGE)) {
400 0           ($key, $value, $addValue) = unpack("n/a* n/a* n/a*", $data);
401            
402 0           testKey($key);
403            
404 2     2   165 { use bytes;
  2         5  
  2         7  
  0            
405 0           $$offsetref += 6 + length($key) + length($value) + length($addValue);
406             }
407            
408 0           $value .= ", " . $addValue;
409             } else {
410            
411 0           testLengths($bytes, $$offsetref);
412            
413 0           ($key, $value) = unpack("n/a* n/a*", $data);
414            
415 0           testKey($key);
416            
417 2     2   243 { use bytes;
  2         5  
  2         7  
  0            
418 0           $$offsetref += 4 + length($key) + length($value);
419             }
420             }
421            
422             #for attribute autodetection:
423 0 0         if (&DEBUG) {
424 0 0         if (!exists($attributeTypes{$key})) {
    0          
425 0           print "Unknown attribute in response:\n";
426 0           print "\"$key\" => $type\n";
427             } elsif($attributeTypes{$key} != $type) {
428 0           print "Attribute has unexpected type (instead of ",$attributeTypes{$key},"):\n";
429 0           print "\"$key\" => $type\n";
430             }
431             }
432            
433 0           $value = transformValue($type, $key, $value, 1);
434            
435             # if key empty, attribute is 1setOf
436 0 0         if (!$key) {
437 0 0         if (!ref($group->{$previousKey})) {
438 0           my $arrayref = [$group->{$previousKey}];
439 0           $group->{$previousKey} = $arrayref;
440             }
441 0           push @{$group->{$previousKey}}, $value;
  0            
442             } else {
443 0           $group->{$key} = $value;
444 0           $previousKey = $key;
445             }
446             }
447              
448             1;