File Coverage

blib/lib/MIME/Detect.pm
Criterion Covered Total %
statement 182 193 94.3
branch 39 54 72.2
condition 14 18 77.7
subroutine 23 24 95.8
pod 5 11 45.4
total 263 300 87.6


line stmt bran cond sub pod time code
1             package MIME::Detect;
2 4     4   661879 use Moo 2;
  4         45223  
  4         28  
3 4     4   7707 use Filter::signatures;
  4         97357  
  4         27  
4 4     4   160 use feature 'signatures';
  4         8  
  4         124  
5 4     4   22 no warnings 'experimental::signatures';
  4         8  
  4         132  
6 4     4   22 use Carp qw(croak);
  4         6  
  4         181  
7 4     4   2717 use XML::LibXML;
  4         150791  
  4         31  
8 4     4   2495 use MIME::Detect::Type;
  4         14  
  4         6839  
9              
10             our $VERSION = '0.10';
11              
12             =head1 NAME
13              
14             MIME::Detect - MIME file type identification
15              
16             =head1 SYNOPSIS
17              
18             my $mime = MIME::Detect->new();
19              
20             for my $file (@ARGV) {
21             print sprintf "%s: %s\n", $file, $_->mime_type
22             for $mime->mime_types($file);
23             };
24              
25             =head1 METHODS
26              
27             =head2 C<< MIME::Detect->new( ... ) >>
28              
29             my $mime = MIME::Detect->new();
30              
31             Creates a new instance and reads the database distributed with this module.
32              
33             my $mime = MIME::Detect->new(
34             files => [
35             '/usr/share/freedesktop.org/mimeinfo.xml',
36             't/mimeinfo.xml',
37             ],
38             );
39              
40             =cut
41              
42 3     3 0 26 sub BUILD( $self, $args ) {
  3         9  
  3         6  
  3         5  
43 3 100       9 my %db_args = map { exists( $args->{$_} )? ($_ => $args->{$_}) : () } (qw(xml files));
  6         29  
44 3         18 $self->read_database( %db_args );
45             }
46              
47             has 'typeclass' => (
48             is => 'ro',
49             default => 'MIME::Detect::Type',
50             );
51              
52             has 'types' => (
53             is => 'rw',
54             default => sub { [] },
55             );
56              
57             # References into @types
58             has 'known_types' => (
59             is => 'rw',
60             default => sub { {} },
61             );
62              
63             # The XPath context we use
64             has 'xpc' => (
65             is => 'lazy',
66             default => sub {
67             my $XPC = XML::LibXML::XPathContext->new;
68             $XPC->registerNs('x', 'http://www.freedesktop.org/standards/shared-mime-info');
69             $XPC
70             },
71             );
72              
73             =head2 C<< $mime->read_database %options >>
74              
75             $mime->read_database(
76             xml => MIME::Detect::FreeDesktopOrgDB->get_xml,
77             files => [
78             'mymime/mymime.xml',
79             '/usr/share/freedesktop.org/mime.xml',
80             ],
81             );
82              
83             If you want rules in addition to the default
84             database included with the distribution, you can load the rules from another file.
85             Passing in multiple filenames will join the multiple
86             databases. Duplicate file type definitions will not be detected
87             and will be returned as duplicates.
88              
89             The rules will be sorted according to the priority specified in the database
90             file(s).
91              
92             By default, the XML database stored alongside
93             L
94             will be loaded after all custom files have been loaded.
95             To pass in a different fallback database, either pass in a reference
96             to the XML string or the name of a package that has an C subroutine.
97              
98             To prevent loading the default database, pass undef
99             for the C key.
100              
101             =cut
102              
103 3     3 1 6 sub read_database( $self, %options ) {
  3         7  
  3         8  
  3         7  
104 3   100     26 $options{ files } ||= [];
105 3 50       11 if( ! exists $options{ xml }) {
106 3         10 $options{ xml } = 'MIME::Detect::FreeDesktopOrgDB';
107             };
108            
109 3 50 33     22 if( $options{ xml } and not ref $options{ xml }) {
110             # Load the class name
111 3 50       239 if( !eval "require $options{ xml }; 1") {
112 0         0 croak $@;
113             };
114 3         43 $options{ xml } = $options{ xml }->get_xml;
115             };
116            
117             my @types = map {
118 4 50       67 my @args = ref $_ eq 'SCALAR' ? (string => $_) :
    100          
119             ref $_ ? (IO => $_) :
120             (location => $_);
121 4         42 my $doc = XML::LibXML->load_xml(
122             no_network => 1,
123             load_ext_dtd => 0,
124             @args
125             );
126 4         361177 $self->_parse_types($doc);
127 3         17 } @{$options{ files }}, $options{ xml };
  3         22  
128 3         16141 $self->reparse(@types);
129             }
130              
131 4     4   12 sub _parse_types( $self, $document ) {
  4         10  
  4         11  
  4         8  
132 4         221 map { $self->fragment_to_type( $_ ) }
  2321         21707  
133             $self->xpc->findnodes('/x:mime-info/x:mime-type',$document);
134             }
135              
136 3     3 0 12 sub reparse($self, @types) {
  3         11  
  3         364  
  3         10  
137 3   100     54 @types = sort { ($b->priority || 50 ) <=> ($a->priority || 50 ) }
  8339   100     21656  
138             @types;
139 3         29 $self->types(\@types);
140              
141             # Build the map from mime_type to object
142 3         7 my %mime_map;
143 3         14 for my $t (@types) {
144 2321         5929 $mime_map{ $t->mime_type } = $t;
145 2321         2481 for my $a (@{$t->aliases}) {
  2321         4680  
146 798   66     2836 $mime_map{ $a } ||= $t;
147             };
148             };
149 3         18 $self->known_types(\%mime_map);
150              
151             # Now, upgrade the strings to objects:
152 3         12 my $m = $self->known_types;
153 3         8 for my $t (@types) {
154 2321         3400 my $s = $t->superclass;
155 2321 100       4262 if( $s ) {
156 1128 50       1766 if( my $sc = $m->{ $s } ) {
157 1128         1691 $t->superclass( $sc );
158             } else {
159 0         0 warn sprintf "No superclass found for '%s' used by '%s'",
160             $s,
161             $t->mime_type;
162             };
163             };
164             };
165             };
166              
167 2321     2321 0 3027 sub fragment_to_type( $self, $frag ) {
  2321         2890  
  2321         2722  
  2321         2633  
168 2321         4744 my $mime_type = $frag->getAttribute('type');
169 2321         60265 my $comment = $self->xpc->findnodes('./x:comment', $frag);
170 2321         254029 my @globs = map { $_->getAttribute('pattern')} $self->xpc->findnodes('./x:glob', $frag);
  3059         101398  
171 2321         38714 (my $superclass) = $self->xpc->findnodes('./x:sub-class-of',$frag);
172 2321 100       154774 $superclass = $superclass->getAttribute('type')
173             if $superclass;
174              
175 2321         35975 my @aliases = map { $_->getAttribute('type') } $self->xpc->findnodes('./x:alias',$frag);
  798         27033  
176              
177 2321         122118 (my $magic) = $self->xpc->findnodes('./x:magic', $frag);
178 2321         98506 my( $priority, @rules );
179 2321 100       5437 if( $magic ) {
180 1241         6567 $priority = $magic->getAttribute('priority');
181 1241 50       12169 $priority = 50 if !defined $priority;
182 1241         2771 @rules = grep { $_->nodeType != 3 } # exclude text nodes
  5739         23457  
183             $magic->childNodes;
184 1241         4640 for my $rule (@rules) {
185 2249         12914 $rule = $self->parse_rule( $rule );
186             };
187             };
188              
189 2321         58310 $self->typeclass->new(
190             aliases => \@aliases,
191             priority => $priority,
192             mime_type => $mime_type,
193             comment => $comment,
194             superclass => $superclass,
195             rules => \@rules,
196             globs => \@globs,
197             );
198             }
199              
200 3011     3011 0 3518 sub parse_rule( $self, $rule ) {
  3011         3615  
  3011         3345  
  3011         3203  
201 3011         5061 my $value = $rule->getAttribute('value');
202 3011         25715 my $offset = $rule->getAttribute('offset');
203 3011         24045 my $type = $rule->getAttribute('type');
204              
205 3011         22590 my @and = map { $self->parse_rule( $_ ) } grep { $_->nodeType != 3 } $rule->childNodes;
  762         1422  
  2160         8701  
206 3011 100       16031 my $and = @and ? \@and : undef;
207              
208             return {
209 3011         19860 value => $value,
210             offset => $offset,
211             type => $type,
212             and => $and,
213             };
214             }
215              
216             =head2 C<< $mime->mime_types >>
217              
218             my @types = $mime->mime_types( 'some/file' );
219             for( @types ) {
220             print $type->mime_type, "\n";
221             };
222              
223             Returns the list of MIME types according to their priority.
224             The first type is the most likely. The returned objects
225             are of type L.
226              
227             =cut
228              
229 2     2 1 932 sub mime_types( $self, $file ) {
  2         6  
  2         7  
  2         4  
230 2 100       17 if( ! ref $file) {
231 1 50       140 open my $fh, '<', $file
232             or croak "Couldn't read '$file': $!";
233 1         8 binmode $fh;
234 1         5 $file = $fh;
235             };
236 2         25 my $buffer = MIME::Detect::Buffer->new(fh => $file);
237 2         3006 $buffer->request(0,4096); # should be enough for most checks
238              
239 2         4 my @candidates;
240 2         5 my $m = $self->known_types;
241              
242             # Already sorted by priority
243 2         4 my @types = @{ $self->{types} };
  2         122  
244              
245             # Let's just hope we don't have infinite subtype loops in the XML file
246 2         13 for my $k (@types) {
247 1548 50       2775 my $t = ref $k ? $k : $m->{ $k };
248 1548 100       2595 if( $t->matches($buffer) ) {
249             #warn sprintf "*** found '%s'", $t->mime_type;
250 5         38 push @candidates, $m->{$t->mime_type};
251             };
252             };
253              
254 2         237 @candidates;
255             }
256              
257             =head2 C<< $mime->mime_type >>
258              
259             my $type = $mime->mime_type( 'some/file' );
260             print $type->mime_type, "\n"
261             if $type;
262              
263             Returns the most likely type of a file as L. Returns
264             C if no file type can be determined.
265              
266             =cut
267              
268 0     0 1 0 sub mime_type( $self, $file ) {
  0         0  
  0         0  
  0         0  
269 0         0 ($self->mime_types($file))[0]
270             }
271              
272             =head2 C<< $mime->mime_types_from_name >>
273              
274             my $type = $mime->mime_types_from_name( 'some/file.ext' );
275             print $type->mime_type, "\n"
276             if $type;
277              
278             Returns the list of MIME types for a file name based on the extension
279             according to their priority.
280             The first type is the most likely. The returned objects
281             are of type L.
282              
283             =cut
284              
285 3     3 1 6 sub mime_types_from_name( $self, $file ) {
  3         5  
  3         6  
  3         6  
286 3         4 my @candidates;
287 3         12 my $m = $self->known_types;
288              
289             # Already sorted by priority
290 3         4 my @types = @{ $self->{types} };
  3         198  
291              
292             # Let's just hope we don't have infinite subtype loops in the XML file
293 3         10 for my $k (@types) {
294 2319 50       4963 my $t = ref $k ? $k : $m->{ $k };
295 2319 100       4463 if( $t->valid_extension($file) ) {
296             #warn sprintf "*** found '%s'", $t->mime_type;
297 4         33 push @candidates, $m->{$t->mime_type};
298             };
299             };
300              
301 3         195 @candidates;
302             }
303              
304             =head2 C<< $mime->mime_type_from_name >>
305              
306             my $type = $mime->mime_type_from_name( 'some/file.ext' );
307             print $type->mime_type, "\n"
308             if $type;
309              
310             Returns the most likely type of a file name as L. Returns
311             C if no file type can be determined.
312              
313             =cut
314              
315 3     3 1 1753 sub mime_type_from_name( $self, $file ) {
  3         6  
  3         7  
  3         5  
316 3         14 ($self->mime_types_from_name($file))[0]
317             }
318              
319             package MIME::Detect::Buffer;
320 4     4   47 use Moo 2;
  4         79  
  4         31  
321 4     4   1429 use Filter::signatures;
  4         16  
  4         55  
322 4     4   188 use feature 'signatures';
  4         9  
  4         221  
323 4     4   25 no warnings 'experimental::signatures';
  4         7  
  4         184  
324 4     4   27 use Fcntl 'SEEK_SET';
  4         9  
  4         1948  
325              
326             has 'offset' => (
327             is => 'rw',
328             default => 0,
329             );
330              
331             has 'buffer' => (
332             is => 'rw',
333             default => undef,
334             );
335              
336             has 'fh' => (
337             is => 'ro',
338             );
339              
340 2925     2925 0 3234 sub length($self) {
  2925         3203  
  2925         2931  
341 2925 100       8486 length $self->buffer || 0
342             };
343              
344 1503     1503 0 1682 sub request($self,$offset,$length) {
  1503         1652  
  1503         1810  
  1503         1617  
  1503         1540  
345 1503         1894 my $fh = $self->fh;
346              
347 1503 100       2843 if( $offset =~ m/^(\d+):(\d+)$/) {
348 227         408 $offset = $1;
349 227         405 $length += $2;
350             };
351              
352             #warn sprintf "At %d to %d (%d), want %d to %d (%d)",
353             # $self->offset, $self->offset+$self->length, $self->length,
354             # $offset, $offset+$length, $length;
355 1503 100 100     3526 if( $offset < $self->offset
356             or $self->offset+$self->length < $offset+$length ) {
357             # We need to refill the buffer
358 477         534 my $buffer;
359 477         513 my $updated = 0;
360 477 50       718 if (ref $fh eq 'GLOB') {
361 477 50       1369 if( seek($fh, $offset, SEEK_SET)) {
362 477         1185 read($fh, $buffer, $length);
363 477         627 $updated = 1;
364             };
365             } else {
366             # let's hope you have ->seek and ->read:
367 0 0       0 if( $fh->seek($offset, SEEK_SET) ) {
368 0         0 $fh->read($buffer, $length);
369 0         0 $updated = 1;
370             };
371             }
372            
373             # Setting all three in one go would be more object-oriented ;)
374 477 50       729 if( $updated ) {
375 477         929 $self->offset($offset);
376 477         829 $self->buffer($buffer);
377             };
378             };
379              
380 1503 100 66     3505 if( $offset >= $self->offset
    50          
381             and $self->offset+$self->length >= $offset+$length ) {
382 1116         3312 substr $self->buffer, $offset-$self->offset, $length
383             } elsif( $offset >= $self->offset ) {
384 387         1005 substr $self->buffer, $offset-$self->offset
385             } else {
386 0           return ''
387             };
388             }
389              
390             1;
391              
392             =head1 SEE ALSO
393              
394             L - the website
395             where the XML file is distributed
396              
397             L - module to read your locally installed and converted MIME database
398              
399             L - if you can install C and the appropriate C files
400              
401             L - if you have the appropriate C files
402              
403             L - if you have the appropriate C files but want more speed
404              
405             L - inlines its database, unsupported since 2004?
406              
407             L - if you're only interested in determining whether
408             a file is an image or not
409              
410             L - for extension-based detection
411              
412             =head1 REPOSITORY
413              
414             The public repository of this module is
415             L.
416              
417             =head1 SUPPORT
418              
419             The public support forum of this module is
420             L.
421              
422             =head1 BUG TRACKER
423              
424             Please report bugs in this module via the RT CPAN bug queue at
425             L
426             or via mail to L.
427              
428             =head1 AUTHOR
429              
430             Max Maischein C
431              
432             =head1 COPYRIGHT (c)
433              
434             Copyright 2015-2018 by Max Maischein C.
435              
436             =head1 LICENSE
437              
438             This module is released under the same terms as Perl itself.
439              
440             =cut