File Coverage

blib/lib/EBook/MOBI/MobiPerl/EXTH.pm
Criterion Covered Total %
statement 71 182 39.0
branch 9 54 16.6
condition 1 9 11.1
subroutine 9 15 60.0
pod 0 12 0.0
total 90 272 33.0


line stmt bran cond sub pod time code
1             package EBook::MOBI::MobiPerl::EXTH;
2              
3             # Copyright (C) 2007 Tommy Persson, tpe@ida.liu.se
4             #
5             # MobiPerl/EXTH.pm, Copyright (C) 2007 Tommy Persson, tpe@ida.liu.se
6             #
7             # This program is free software: you can redistribute it and/or modify
8             # it under the terms of the GNU General Public License as published by
9             # the Free Software Foundation, either version 2 of the License, or
10             # (at your option) any later version.
11             #
12             # This program is distributed in the hope that it will be useful,
13             # but WITHOUT ANY WARRANTY; without even the implied warranty of
14             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15             # GNU General Public License for more details.
16             #
17             # You should have received a copy of the GNU General Public License
18             # along with this program. If not, see .
19              
20 9     9   54 use FindBin qw($RealBin);
  9         18  
  9         968  
21 9     9   50 use lib "$RealBin";
  9         71  
  9         56  
22              
23 9     9   1180 use strict;
  9         19  
  9         19715  
24              
25             our $VERSION = 2011.11.26;
26              
27             # 400-499 application binary
28             # 500-599 application string
29              
30             my %typename_to_type = ("drm_server_id" => 1,
31             "drm_commerce_id" => 2,
32             "drm_ebookbase_book_id" => 3,
33             "author" => 100,
34             "publisher" => 101,
35             "imprint" => 102,
36             "description" => 103,
37             "isbn" => 104,
38             "subject" => 105,
39             "publishingdate" => 106,
40             "review" => 107,
41             "contributor" => 108,
42             "rights" => 109,
43             "subjectcode" => 110,
44             "type" => 111,
45             "source" => 112,
46             "asin" => 113,
47             "versionnumber" => 114,
48             "sample" => 115,
49             "startreading" => 116,
50             "coveroffset" => 201,
51             "thumboffset" => 202,
52             "hasfakecover" => 203,
53             "204" => 204,
54             "205" => 205,
55             "206" => 206,
56             "207" => 207,
57             "clippinglimit" => 401, # varies in size 1 or 4 seend
58             "publisherlimit" => 402,
59             "403" => 403,
60             "ttsflag" => 404,
61             "cdetype" => 501,
62             "lastupdatetime" => 502,
63             "updatedtitle" => 503,
64             );
65              
66             my %type_to_desc = (1 => "drm_server_id",
67             2 => "drm_commerce_id",
68             3 => "drm_ebookbase_book_id",
69             100 => "Author",
70             101 => "Publisher",
71             102 => "Imprint",
72             103 => "Description",
73             104 => "ISBN",
74             105 => "Subject",
75             106 => "PublishingDate",
76             107 => "Review",
77             108 => "Contributor",
78             109 => "Rights",
79             110 => "SubjectCode",
80             111 => "Type",
81             112 => "Source",
82             113 => "ASIN",
83             114 => "VersionNumber",
84             115 => "Sample",
85             116 => "StartReading",
86             201 => "CoverOffset",
87             202 => "ThumbOffset",
88             203 => "hasFakeCover",
89             401 => "ClippingLimit",
90             402 => "PublisherLimit",
91             404 => "TTSFlag",
92             501 => "CDEContentType",
93             502 => "LastUpdateTime",
94             503 => "UpdatedTitle",
95             504 => "cDEContentKey",
96             );
97              
98             my %binary_data = (114 => 1,
99             115 => 1,
100             201 => 1,
101             202 => 1,
102             203 => 1,
103             204 => 1,
104             205 => 1,
105             206 => 1,
106             207 => 1,
107             300 => 1,
108             401 => 1,
109             403 => 1,
110             404 => 1,
111             );
112              
113             my %format = (114 => 4,
114             201 => 4,
115             202 => 4,
116             203 => 4,
117             204 => 4,
118             205 => 4,
119             206 => 4,
120             207 => 4,
121             403 => 1);
122              
123              
124              
125              
126             sub new {
127 6     6 0 90 my $this = shift;
128 6         10 my $data = shift;
129 6   33     115 my $class = ref($this) || $this;
130 6         38 my $obj = bless {
131             TYPE => [],
132             DATA => [],
133             @_
134             }, $class;
135 6 50       17 $obj->initialize_from_data ($data) if defined $data;
136 6         19 return $obj;
137             }
138              
139             sub get_string {
140 0     0 0 0 my $self = shift;
141 0         0 my @type = @{$self->{TYPE}};
  0         0  
142 0         0 my @data = @{$self->{DATA}};
  0         0  
143 0         0 my $res = "";
144 0         0 foreach my $i (0..$#type) {
145 0         0 my $type = $type[$i];
146 0         0 my $data = $data[$i];
147 0         0 my $typedesc = $type;
148 0 0       0 if (defined $type_to_desc{$type}) {
149 0         0 $typedesc = $type_to_desc{$type};
150 0 0       0 if (defined $binary_data{$type}) {
151 0         0 $res .= $typedesc . " - " . "not printable" . "\n";
152             } else {
153 0         0 $res .= $typedesc . " - " . $data . "\n";
154             }
155             }
156             }
157 0         0 return $res;
158             }
159              
160             sub add {
161 6     6 0 9 my $self = shift;
162 6         9 my $typename = shift;
163 6         10 my $data = shift;
164 6         14 my $type = $self->get_type ($typename);
165 6 50       23 if (is_binary_data ($type)) {
166 0         0 my $hex = MobiPerl::Util::iso2hex ($data);
167             #print STDERR "EXTH add: $typename - $type - ", int($data), " - $hex\n";
168             } else {
169             #print STDERR "EXTH add: $typename - $type - $data\n";
170             }
171 6 50       13 if ($type) {
172 6         8 push @{$self->{TYPE}}, $type;
  6         15  
173 6         9 push @{$self->{DATA}}, $data;
  6         16  
174             } else {
175 0         0 print STDERR "WARNING: $typename is not defined as an EXTH type\n";
176             }
177 6         14 return $type;
178             }
179              
180             sub delete {
181 0     0 0 0 my $self = shift;
182 0         0 my $typename = shift;
183 0         0 my $delexthindex = shift;
184 0         0 my $type = $self->get_type ($typename);
185             #print STDERR "EXTH delete: $typename - $type - $delexthindex\n";
186 0 0       0 if ($type) {
187 0         0 my @type = @{$self->{TYPE}};
  0         0  
188 0         0 my @data = @{$self->{DATA}};
  0         0  
189 0         0 @{$self->{TYPE}} = ();
  0         0  
190 0         0 @{$self->{DATA}} = ();
  0         0  
191 0         0 my $index = 0;
192 0         0 foreach my $i (0..$#type) {
193             ## print STDERR "TYPE: $type[$i]\n";
194 0 0       0 if ($type[$i] == $type) {
195 0         0 $index++;
196             ## print STDERR "INDEX: $index\n";
197             }
198 0 0 0     0 if ($type[$i] == $type and
      0        
199             ($delexthindex == 0 or $delexthindex == $index)) {
200 0 0       0 if (is_binary_data ($type[$i])) {
201 0         0 my $hex = MobiPerl::Util::iso2hex ($data[$i]);
202             #print STDERR "DELETING $type[$i]: ", int($data[$i]), " - $hex\n";
203             } else {
204             #print STDERR "DELETING $type[$i]: $data[$i]\n";
205             }
206             } else {
207 0         0 push @{$self->{TYPE}}, $type[$i];
  0         0  
208 0         0 push @{$self->{DATA}}, $data[$i];
  0         0  
209             }
210             }
211             } else {
212 0         0 print STDERR "WARNING: $typename is not defined as an EXTH type\n";
213             }
214             }
215              
216             sub get_type {
217 12     12 0 17 my $self = shift;
218 12         16 my $typename = shift;
219 12         19 my $res = 0;
220             ### print STDERR "EXTH: GETTYPE: $typename\n";
221 12 50       31 if (defined $typename_to_type{$typename}) {
222 12         22 $res = $typename_to_type{$typename};
223             } else {
224 0 0       0 if ($typename =~ /^\d+$/) {
225 0         0 $res = $typename;
226             }
227             }
228 12         22 return $res;
229             }
230              
231             sub set {
232 6     6 0 9 my $self = shift;
233 6         9 my $typename = shift;
234 6         10 my $data = shift;
235 6         18 my $type = $self->get_type ($typename);
236 6         25 my $hex = EBook::MOBI::MobiPerl::Util::iso2hex ($data);
237             #print STDERR "EXTH setting data: $typename - $type - $data - $hex\n";
238 6 50       19 if ($type) {
239 6         8 my @type = @{$self->{TYPE}};
  6         23  
240 6         8 my @data = @{$self->{DATA}};
  6         13  
241 6         10 my $found = 0;
242 6         28 foreach my $i (0..$#type) {
243 0 0       0 if ($type[$i] == $type) {
244             #print STDERR "EXTH replacing data: $type - $data - $hex\n";
245 0         0 $self->{TYPE}->[$i] = $type;
246 0         0 $self->{DATA}->[$i] = $data;
247 0         0 $found = 1;
248 0         0 last;
249             }
250             }
251 6 50       15 if (not $found) {
252 6         26 $self->add ($typename, $data);
253             }
254             }
255 6         26 return $type;
256             }
257              
258             sub initialize_from_data {
259 0     0 0 0 my $self = shift;
260 0         0 my $data = shift;
261 0         0 my ($doctype, $len, $n_items) = unpack ("a4NN", $data);
262             ## print "EXTH doctype: $doctype\n";
263             ## print "EXTH length: $len\n";
264             ## print "EXTH n_items: $n_items\n";
265 0         0 my $pos = 12;
266 0         0 foreach (1..$n_items) {
267 0         0 my ($type, $size) = unpack ("NN", substr ($data, $pos));
268 0         0 $pos += 8;
269 0         0 my $contlen = $size-8;
270 0         0 my ($content) = unpack ("a$contlen", substr ($data, $pos));
271 0 0       0 if (defined $format{$type}) {
272 0         0 my $len = $format{$type};
273             ## print STDERR "TYPE:$type:$len\n";
274 0 0       0 if ($len == 4) {
275 0         0 ($content) = unpack ("N", substr ($data, $pos));
276             ## print STDERR "CONT:$content\n";
277             }
278 0 0       0 if ($len == 1) {
279 0         0 ($content) = unpack ("C", substr ($data, $pos));
280             ## print STDERR "CONT:$content\n";
281             }
282             }
283 0         0 push @{$self->{TYPE}}, $type;
  0         0  
284 0         0 push @{$self->{DATA}}, $content;
  0         0  
285 0         0 $pos += $contlen;
286             }
287 0 0       0 if ($self->get_data () ne substr ($data, 0, $len)) {
288 0         0 print STDERR "ERROR: generated EXTH does not match original\n";
289 0         0 my $s1 = $self->get_data ();
290 0         0 my $s0 = substr ($data, 0, $len);
291 0         0 foreach my $i (0..length ($s0)-1) {
292 0 0       0 if (substr ($s0, $i, 1) ne substr ($s1, $i, 1)) {
293 0         0 my $c0 = substr ($s0, $i, 1);
294 0         0 my $c1 = substr ($s1, $i, 1);
295 0         0 $c0 = MobiPerl::Util::iso2hex ($c0);
296 0         0 $c1 = MobiPerl::Util::iso2hex ($c1);
297 0         0 print STDERR "MISMATCH POS:$i:$c0:$c1\n";
298             }
299             }
300             }
301             # open EXTH0, ">exth0";
302             # print EXTH0 substr ($data, 0, $len);
303             # open EXTH1, ">exth1";
304             # print EXTH1 $self->get_data ();
305             }
306              
307             sub get_data {
308 6     6 0 9 my $self = shift;
309 6         9 my @type = @{$self->{TYPE}};
  6         17  
310 6         8 my @data = @{$self->{DATA}};
  6         17  
311 6         19 my $exth = pack ("a*", "EXTH");
312 6         11 my $content = "";
313 6         9 my $n_items = 0;
314 6         15 foreach my $i (0..$#type) {
315 6         12 my $type = $type[$i];
316 6         18 my $data = $data[$i];
317 6 50       15 next unless defined $data; # remove type...
318 6 50       19 if (defined $format{$type}) {
319 0         0 my $len = $format{$type};
320 0 0       0 if ($len == 4) {
321 0         0 $content .= pack ("NNN", $type, $len+8, $data);
322             }
323 0 0       0 if ($len == 1) {
324 0         0 $content .= pack ("NNC", $type, $len+8, $data);
325             }
326             } else {
327 6         29 $content .= pack ("NNa*", $type, length ($data)+8, $data);
328             }
329 6         15 $n_items++;
330             }
331             #
332             # Maybe fill up to even 4...
333             #
334              
335 6         25 my $comp = length ($content) % 4;
336 6 50       22 if ($comp) {
337 6         13 foreach ($comp .. 3) {
338 6         17 $content .= pack ("C", 0);
339             }
340             }
341 6         29 $exth .= pack ("NN", length ($content)+12, $n_items);
342 6         10 $exth .= $content;
343 6         62 return $exth;
344             }
345              
346             sub get_cover_offset {
347 0     0 0 0 my $self = shift;
348 0         0 my @type = @{$self->{TYPE}};
  0         0  
349 0         0 my @data = @{$self->{DATA}};
  0         0  
350             # pdurrant: 0 is a valid cover offset, so return -1 if not found
351 0         0 my $res = -1;
352             # my $res = 0;
353 0         0 foreach my $i (0..$#type) {
354 0 0       0 if ($type[$i] == 201) {
355             ## print STDERR "TYPE: $type[$i] - $data[$i]\n";
356             ## ($res) = unpack ("N", $data[$i]);
357 0         0 $res = $data[$i];
358             ## print STDERR "RES: $res\n";
359             }
360             }
361 0         0 return $res;
362             }
363              
364             sub get_thumb_offset {
365 0     0 0 0 my $self = shift;
366 0         0 my @type = @{$self->{TYPE}};
  0         0  
367 0         0 my @data = @{$self->{DATA}};
  0         0  
368             # pdurrant: 0 is a valid cover offset, so return -1 if not found
369 0         0 my $res = -1;
370             # my $res = 0;
371 0         0 foreach my $i (0..$#type) {
372 0 0       0 if ($type[$i] == 202) {
373 0         0 $res = $data[$i];
374             }
375             }
376 0         0 return $res;
377             }
378              
379             #
380             # Non object methods
381             #
382              
383             sub get_description {
384 0     0 0 0 my $type = shift;
385 0         0 my $res = $type;
386 0 0       0 if (defined $type_to_desc{$type}) {
387 0         0 $res = $type_to_desc{$type};
388             }
389 0         0 return $res;
390             }
391              
392             sub is_binary_data {
393 6     6 0 10 my $type = shift;
394 6         24 return $binary_data{$type};
395             }
396              
397             return 1;
398              
399              
400             __END__