File Coverage

blib/lib/Image/ExifTool/RSRC.pm
Criterion Covered Total %
statement 69 99 69.7
branch 28 76 36.8
condition 12 51 23.5
subroutine 4 4 100.0
pod 0 1 0.0
total 113 231 48.9


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: RSRC.pm
3             #
4             # Description: Read Mac OS Resource information
5             #
6             # Revisions: 2010/03/17 - P. Harvey Created
7             #
8             # References: 1) http://developer.apple.com/legacy/mac/library/documentation/mac/MoreToolbox/MoreToolbox-99.html
9             #------------------------------------------------------------------------------
10              
11             package Image::ExifTool::RSRC;
12              
13 2     2   16 use strict;
  2         6  
  2         70  
14 2     2   12 use vars qw($VERSION);
  2         5  
  2         92  
15 2     2   12 use Image::ExifTool qw(:DataAccess :Utils);
  2         5  
  2         2776  
16              
17             $VERSION = '1.09';
18              
19             sub ProcessRSRC($$);
20              
21             # Information decoded from Mac OS resources
22             %Image::ExifTool::RSRC::Main = (
23             GROUPS => { 2 => 'Document' },
24             PROCESS_PROC => \&ProcessRSRC,
25             NOTES => q{
26             Tags extracted from Mac OS resource files, DFONT files and "._" sidecar
27             files. These tags may also be extracted from the resource fork of any file
28             in OS X, either by adding "/..namedfork/rsrc" to the filename to process the
29             resource fork alone, or by using the L (-ee) option to process
30             the resource fork as a sub-document of the main file. When writing,
31             ExifTool preserves the Mac OS resource fork by default, but it may deleted
32             with C<-rsrc:all=> on the command line.
33             },
34             '8BIM' => {
35             Name => 'PhotoshopInfo',
36             SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::Main' },
37             },
38             'sfnt' => {
39             Name => 'Font',
40             SubDirectory => { TagTable => 'Image::ExifTool::Font::Name' },
41             },
42             # my samples of postscript-type DFONT files have a POST resource
43             # with ID 0x1f5 and the same format as a PostScript file
44             'POST_0x01f5' => {
45             Name => 'PostscriptFont',
46             SubDirectory => { TagTable => 'Image::ExifTool::PostScript::Main' },
47             },
48             'usro_0x0000' => 'OpenWithApplication',
49             'vers_0x0001' => 'ApplicationVersion',
50             'STR _0xbff3' => 'ApplicationMissingMsg',
51             'STR _0xbff4' => 'CreatorApplication',
52             # the following written by Photoshop
53             # (ref http://www.adobe.ca/devnet/photoshop/psir/ps_image_resources.pdf)
54             'STR#_0x0080' => 'Keywords',
55             'TEXT_0x0080' => 'Description',
56             # don't extract PICT's because the clip region isn't set properly
57             # in the PICT resource for some reason. Also, a dummy 512-byte
58             # header would have to be added to create a valid PICT file.
59             # 'PICT' => { Name => 'PreviewPICT', Binary => 1 },
60             );
61              
62             #------------------------------------------------------------------------------
63             # Read information from a Mac resource file (ref 1)
64             # Inputs: 0) ExifTool ref, 1) dirInfo ref
65             # Returns: 1 on success, 0 if this wasn't a valid resource file
66             sub ProcessRSRC($$)
67             {
68 2     2 0 5 my ($et, $dirInfo) = @_;
69 2         5 my $raf = $$dirInfo{RAF};
70 2         4 my ($hdr, $map, $buff, $i, $j);
71              
72             # allow access with data reference
73 2 100       14 $raf or $raf = new File::RandomAccess($$dirInfo{DataPt});
74              
75             # attempt to validate the format as thoroughly as practical
76 2 50       9 return 0 unless $raf->Read($hdr, 30) == 30;
77 2         14 my ($datOff, $mapOff, $datLen, $mapLen) = unpack('N*', $hdr);
78 2 50       9 return 0 unless $raf->Seek(0, 2);
79 2         12 my $fLen = $raf->Tell();
80 2 50 33     15 return 0 if $datOff < 0x10 or $datOff + $datLen > $fLen;
81 2 50 33     17 return 0 if $mapOff < 0x10 or $mapOff + $mapLen > $fLen or $mapLen < 30;
      33        
82 2 50 66     13 return 0 if $datOff < $mapOff and $datOff + $datLen > $mapOff;
83 2 50 33     8 return 0 if $mapOff < $datOff and $mapOff + $mapLen > $datOff;
84              
85             # read the resource map
86 2 50 33     6 $raf->Seek($mapOff, 0) and $raf->Read($map, $mapLen) == $mapLen or return 0;
87 2         11 SetByteOrder('MM');
88 2         10 my $typeOff = Get16u(\$map, 24);
89 2         6 my $nameOff = Get16u(\$map, 26);
90 2         5 my $numTypes = (Get16u(\$map, 28) + 1) & 0xffff;
91              
92             # validate offsets in the resource map
93 2 50 33     13 return 0 if $typeOff < 28 or $nameOff < 30;
94              
95 2 50       17 $et->SetFileType('RSRC') unless $$et{IN_RESOURCE};
96 2         8 my $verbose = $et->Options('Verbose');
97 2         8 my $tagTablePtr = GetTagTable('Image::ExifTool::RSRC::Main');
98 2         12 $et->VerboseDir('RSRC', $numTypes);
99              
100             # parse resource type list
101 2         6 for ($i=0; $i<$numTypes; ++$i) {
102 2         6 my $off = $typeOff + 2 + 8 * $i; # offset of entry in type list
103 2 50       7 last if $off + 8 > $mapLen;
104 2         10 my $resType = substr($map,$off,4); # resource type
105 2         8 my $resNum = Get16u(\$map,$off+4); # number of resources - 1
106 2         11 my $refOff = Get16u(\$map,$off+6) + $typeOff; # offset to first resource reference
107             # loop through all resources
108 2         10 for ($j=0; $j<=$resNum; ++$j) {
109 2         4 my $roff = $refOff + 12 * $j;
110 2 50       6 last if $roff + 12 > $mapLen;
111             # read only the 24-bit resource data offset
112 2         5 my $id = Get16u(\$map,$roff);
113 2         11 my $resOff = (Get32u(\$map,$roff+4) & 0x00ffffff) + $datOff;
114 2         14 my $resNameOff = Get16u(\$map,$roff+2) + $nameOff + $mapOff;
115 2         6 my ($tag, $val, $valLen);
116 2         5 my $tagInfo = $$tagTablePtr{$resType};
117 2 100       8 if ($tagInfo) {
118 1         2 $tag = $resType;
119             } else {
120 1         13 $tag = sprintf('%s_0x%.4x', $resType, $id);
121 1         5 $tagInfo = $$tagTablePtr{$tag};
122             }
123             # read the resource data if necessary
124 2 50 33     9 if ($tagInfo or $verbose) {
125 2 50 33     12 unless ($raf->Seek($resOff, 0) and $raf->Read($buff, 4) == 4 and
      33        
      33        
126             ($valLen = unpack('N', $buff)) < 100000000 and # arbitrary size limit (100MB)
127             $raf->Read($val, $valLen) == $valLen)
128             {
129 0         0 $et->Warn("Error reading $resType resource");
130 0         0 next;
131             }
132             }
133 2 50       10 if ($verbose) {
134 0         0 my ($resName, $nameLen);
135 0 0 0     0 $resName = '' unless $raf->Seek($resNameOff, 0) and $raf->Read($buff, 1) and
      0        
      0        
136             ($nameLen = ord $buff) != 0 and $raf->Read($resName, $nameLen) == $nameLen;
137 0         0 $et->VPrint(0,sprintf("%s resource ID 0x%.4x (offset 0x%.4x, $valLen bytes, name='%s'):\n",
138             $resType, $id, $resOff, $resName));
139 0         0 $et->VerboseDump(\$val);
140             }
141 2 50       7 next unless $tagInfo;
142 2 100 0     8 if ($resType eq 'vers') {
    50 0        
    0 0        
    0          
    0          
    0          
    0          
    0          
143             # parse the 'vers' resource to get the long version string
144 1 50       5 next unless $valLen > 8;
145             # long version string is after short version
146 1         7 my $p = 7 + Get8u(\$val, 6);
147 1 50       5 next if $p >= $valLen;
148 1         5 my $vlen = Get8u(\$val, $p++);
149 1 50       8 next if $p + $vlen > $valLen;
150 1         36 my $tagTablePtr = GetTagTable('Image::ExifTool::RSRC::Main');
151 1         7 $val = $et->Decode(substr($val, $p, $vlen), 'MacRoman');
152             } elsif ($resType eq 'sfnt') {
153             # parse the OTF font block
154 1 50       5 $raf->Seek($resOff + 4, 0) or next;
155 1         3 $$dirInfo{Base} = $resOff + 4;
156 1         7 require Image::ExifTool::Font;
157 1 50       6 unless (Image::ExifTool::Font::ProcessOTF($et, $dirInfo)) {
158 0         0 $et->Warn('Unrecognized sfnt resource format');
159             }
160             # assume this is a DFONT file unless processing the rsrc fork
161 1 50       10 $et->OverrideFileType('DFONT') unless $$et{DOC_NUM};
162 1         11 next;
163             } elsif ($resType eq '8BIM') {
164 0         0 my $ttPtr = GetTagTable('Image::ExifTool::Photoshop::Main');
165 0         0 $et->HandleTag($ttPtr, $id, $val,
166             DataPt => \$val,
167             DataPos => $resOff + 4,
168             Size => $valLen,
169             Start => 0,
170             Parent => 'RSRC',
171             );
172 0         0 next;
173             } elsif ($resType eq 'STR ' and $valLen > 1) {
174             # extract Pascal string
175 0         0 my $len = ord $val;
176 0 0       0 next unless $valLen >= $len + 1;
177 0         0 $val = substr($val, 1, $len);
178             } elsif ($resType eq 'usro' and $valLen > 4) {
179 0         0 my $len = unpack('N', $val);
180 0 0       0 next unless $valLen >= $len + 4;
181 0         0 ($val = substr($val, 4, $len)) =~ s/\0.*//g; # truncate at null
182             } elsif ($resType eq 'STR#' and $valLen > 2) {
183             # extract list of strings (ref http://simtech.sourceforge.net/tech/strings.html)
184 0         0 my $num = unpack('n', $val);
185 0 0       0 next if $num & 0xf000; # (ignore special-format STR# resources)
186 0         0 my ($i, @vals);
187 0         0 my $pos = 2;
188 0         0 for ($i=0; $i<$num; ++$i) {
189 0 0       0 last if $pos >= $valLen;
190 0         0 my $len = ord substr($val, $pos++, 1);
191 0 0       0 last if $pos + $len > $valLen;
192 0         0 push @vals, substr($val, $pos, $len);
193 0         0 $pos += $len;
194             }
195 0         0 $val = \@vals;
196             } elsif ($resType eq 'POST') {
197             # assume this is a DFONT file unless processing the rsrc fork
198 0 0       0 $et->OverrideFileType('DFONT') unless $$et{DOC_NUM};
199 0         0 $val = substr $val, 2;
200             } elsif ($resType ne 'TEXT') {
201 0         0 next;
202             }
203 1         5 $et->HandleTag($tagTablePtr, $tag, $val);
204             }
205             }
206 2         11 return 1;
207             }
208              
209             1; # end
210              
211             __END__