File Coverage

blib/lib/MIME/Detect.pm
Criterion Covered Total %
statement 175 186 94.0
branch 39 54 72.2
condition 13 18 72.2
subroutine 21 22 95.4
pod 5 11 45.4
total 253 291 86.9


line stmt bran cond sub pod time code
1             package MIME::Detect;
2 4     4   389926 use 5.020;
  4         20  
3 4     4   2491 use Moo 2;
  4         36447  
  4         29  
4 4     4   9183 use experimental 'signatures';
  4         18509  
  4         30  
5 4     4   909 use Carp qw(croak);
  4         16  
  4         299  
6 4     4   3062 use XML::LibXML;
  4         282184  
  4         25  
7 4     4   2998 use MIME::Detect::Type;
  4         24  
  4         10729  
8              
9             our $VERSION = '0.12';
10              
11             =head1 NAME
12              
13             MIME::Detect - MIME file type identification
14              
15             =head1 SYNOPSIS
16              
17             my $mime = MIME::Detect->new();
18              
19             for my $file (@ARGV) {
20             print sprintf "%s: %s\n", $file, $_->mime_type
21             for $mime->mime_types($file);
22             };
23              
24             =head1 METHODS
25              
26             =head2 C<< MIME::Detect->new( ... ) >>
27              
28             my $mime = MIME::Detect->new();
29              
30             Creates a new instance and reads the database distributed with this module.
31              
32             my $mime = MIME::Detect->new(
33             files => [
34             '/usr/share/freedesktop.org/mimeinfo.xml',
35             't/mimeinfo.xml',
36             ],
37             );
38              
39             =cut
40              
41 3     3 0 20 sub BUILD( $self, $args ) {
  3         7  
  3         5  
  3         6  
42 3 100       7 my %db_args = map { exists( $args->{$_} )? ($_ => $args->{$_}) : () } (qw(xml files));
  6         25  
43 3         17 $self->read_database( %db_args );
44             }
45              
46             has 'typeclass' => (
47             is => 'ro',
48             default => 'MIME::Detect::Type',
49             );
50              
51             has 'types' => (
52             is => 'rw',
53             default => sub { [] },
54             );
55              
56             # References into @types
57             has 'known_types' => (
58             is => 'rw',
59             default => sub { {} },
60             );
61              
62             # The XPath context we use
63             has 'xpc' => (
64             is => 'lazy',
65             default => sub {
66             my $XPC = XML::LibXML::XPathContext->new;
67             $XPC->registerNs('x', 'http://www.freedesktop.org/standards/shared-mime-info');
68             $XPC
69             },
70             );
71              
72             =head2 C<< $mime->read_database %options >>
73              
74             $mime->read_database(
75             xml => MIME::Detect::FreeDesktopOrgDB->get_xml,
76             files => [
77             'mymime/mymime.xml',
78             '/usr/share/freedesktop.org/mime.xml',
79             ],
80             );
81              
82             If you want rules in addition to the default
83             database included with the distribution, you can load the rules from another file.
84             Passing in multiple filenames will join the multiple
85             databases. Duplicate file type definitions will not be detected
86             and will be returned as duplicates.
87              
88             The rules will be sorted according to the priority specified in the database
89             file(s).
90              
91             By default, the XML database stored alongside
92             L
93             will be loaded after all custom files have been loaded.
94             To pass in a different fallback database, either pass in a reference
95             to the XML string or the name of a package that has an C subroutine.
96              
97             To prevent loading the default database, pass undef
98             for the C key.
99              
100             =cut
101              
102 3     3 1 6 sub read_database( $self, %options ) {
  3         6  
  3         6  
  3         4  
103 3   100     21 $options{ files } ||= [];
104 3 50       9 if( ! exists $options{ xml }) {
105 3         9 $options{ xml } = 'MIME::Detect::FreeDesktopOrgDB';
106             };
107 3 50 33     21 if( $options{ xml } and not ref $options{ xml }) {
108             # Load the class name
109 3 50       351 if( !eval "require $options{ xml }; 1") {
110 0         0 croak $@;
111             };
112 3         65 $options{ xml } = $options{ xml }->get_xml;
113             };
114              
115             my @types = map {
116 4 50       52 my @args = ref $_ eq 'SCALAR' ? (string => $_) :
    100          
117             ref $_ ? (IO => $_) :
118             (location => $_);
119 4         46 my $doc = XML::LibXML->load_xml(
120             no_network => 1,
121             load_ext_dtd => 0,
122             @args
123             );
124 4         62928 $self->_parse_types($doc);
125 3         14 } @{$options{ files }}, $options{ xml };
  3         19  
126 3         15888 $self->reparse(@types);
127             }
128              
129 4     4   10 sub _parse_types( $self, $document ) {
  4         8  
  4         9  
  4         7  
130 4         224 map { $self->fragment_to_type( $_ ) }
  2726         26799  
131             $self->xpc->findnodes('/x:mime-info/x:mime-type',$document);
132             }
133              
134 3     3 0 8 sub reparse($self, @types) {
  3         6  
  3         306  
  3         88  
135 3   100     64 @types = sort { ($b->priority || 50 ) <=> ($a->priority || 50 ) }
  9978   100     26778  
136             @types;
137 3         31 $self->types(\@types);
138              
139             # Build the map from mime_type to object
140 3         7 my %mime_map;
141 3         10 for my $t (@types) {
142 2726         6989 $mime_map{ $t->mime_type } = $t;
143 2726         2933 for my $a (@{$t->aliases}) {
  2726         5097  
144 969   33     3227 $mime_map{ $a } ||= $t;
145             };
146             };
147 3         24 $self->known_types(\%mime_map);
148              
149             # Now, upgrade the strings to objects:
150 3         10 my $m = $self->known_types;
151 3         8 for my $t (@types) {
152 2726         4399 my $s = $t->superclass;
153 2726 100       4291 if( $s ) {
154 1413 50       2136 if( my $sc = $m->{ $s } ) {
155 1413         2755 $t->superclass( $sc );
156             } else {
157 0         0 warn sprintf "No superclass found for '%s' used by '%s'",
158             $s,
159             $t->mime_type;
160             };
161             };
162             };
163             };
164              
165 2726     2726 0 3607 sub fragment_to_type( $self, $frag ) {
  2726         3500  
  2726         3280  
  2726         3176  
166 2726         6107 my $mime_type = $frag->getAttribute('type');
167 2726         84601 my $comment = $self->xpc->findnodes('./x:comment', $frag);
168 2726         185119 my @globs = map { $_->getAttribute('pattern')} $self->xpc->findnodes('./x:glob', $frag);
  3677         106024  
169 2726         39875 (my $superclass) = $self->xpc->findnodes('./x:sub-class-of',$frag);
170 2726 100       183348 $superclass = $superclass->getAttribute('type')
171             if $superclass;
172              
173 2726         44917 my @aliases = map { $_->getAttribute('type') } $self->xpc->findnodes('./x:alias',$frag);
  969         38229  
174              
175 2726         144464 (my $magic) = $self->xpc->findnodes('./x:magic', $frag);
176 2726         114386 my( $priority, @rules );
177 2726 100       7160 if( $magic ) {
178 1481         8565 $priority = $magic->getAttribute('priority');
179 1481 50       15574 $priority = 50 if !defined $priority;
180 1481         3577 @rules = grep { $_->nodeType == XML_ELEMENT_NODE } # exclude text nodes and other stuff
  6861         30614  
181             $magic->childNodes;
182 1481         6518 for my $rule (@rules) {
183 2588         15020 $rule = $self->parse_rule( $rule );
184             };
185             };
186              
187 2726         79300 $self->typeclass->new(
188             aliases => \@aliases,
189             priority => $priority,
190             mime_type => $mime_type,
191             comment => $comment,
192             superclass => $superclass,
193             rules => \@rules,
194             globs => \@globs,
195             );
196             }
197              
198 3506     3506 0 4552 sub parse_rule( $self, $rule ) {
  3506         4239  
  3506         4058  
  3506         3959  
199 3506         6295 my $value = $rule->getAttribute('value');
200 3506         29952 my $offset = $rule->getAttribute('offset');
201 3506         25609 my $type = $rule->getAttribute('type');
202              
203 918         1731 my @and = map { $self->parse_rule( $_ ) }
204 3506         25290 grep { $_->nodeType == XML_ELEMENT_NODE } $rule->childNodes;
  2658         10257  
205 3506 100       19475 my $and = @and ? \@and : undef;
206              
207             return {
208 3506         26174 value => $value,
209             offset => $offset,
210             type => $type,
211             and => $and,
212             };
213             }
214              
215             =head2 C<< $mime->mime_types >>
216              
217             my @types = $mime->mime_types( 'some/file' );
218             for( @types ) {
219             print $type->mime_type, "\n";
220             };
221              
222             Returns the list of MIME types according to their priority.
223             The first type is the most likely. The returned objects
224             are of type L.
225              
226             =cut
227              
228 3     3 1 537 sub mime_types( $self, $file ) {
  3         9  
  3         9  
  3         6  
229 3 100       30 if( ! ref $file) {
230 1 50       113 open my $fh, '<', $file
231             or croak "Couldn't read '$file': $!";
232 1         7 binmode $fh;
233 1         3 $file = $fh;
234             };
235 3         77 my $buffer = MIME::Detect::Buffer->new(fh => $file);
236 3         2898 $buffer->request(0,4096); # should be enough for most checks
237              
238 3         6 my @candidates;
239 3         11 my $m = $self->known_types;
240              
241             # Already sorted by priority
242 3         6 my @types = @{ $self->{types} };
  3         268  
243              
244             # Let's just hope we don't have infinite subtype loops in the XML file
245 3         23 for my $k (@types) {
246 2726 50       4275 my $t = ref $k ? $k : $m->{ $k };
247 2726 100       4359 if( $t->matches($buffer) ) {
248             #warn sprintf "*** found '%s'", $t->mime_type;
249 6         36 push @candidates, $m->{$t->mime_type};
250             };
251             };
252              
253 3         405 @candidates;
254             }
255              
256             =head2 C<< $mime->mime_type >>
257              
258             my $type = $mime->mime_type( 'some/file' );
259             print $type->mime_type, "\n"
260             if $type;
261              
262             Returns the most likely type of a file as L. Returns
263             C if no file type can be determined.
264              
265             =cut
266              
267 0     0 1 0 sub mime_type( $self, $file ) {
  0         0  
  0         0  
  0         0  
268 0         0 ($self->mime_types($file))[0]
269             }
270              
271             =head2 C<< $mime->mime_types_from_name >>
272              
273             my $type = $mime->mime_types_from_name( 'some/file.ext' );
274             print $type->mime_type, "\n"
275             if $type;
276              
277             Returns the list of MIME types for a file name based on the extension
278             according to their priority.
279             The first type is the most likely. The returned objects
280             are of type L.
281              
282             =cut
283              
284 3     3 1 6 sub mime_types_from_name( $self, $file ) {
  3         5  
  3         6  
  3         4  
285 3         6 my @candidates;
286 3         18 my $m = $self->known_types;
287              
288             # Already sorted by priority
289 3         4 my @types = @{ $self->{types} };
  3         241  
290              
291             # Let's just hope we don't have infinite subtype loops in the XML file
292 3         9 for my $k (@types) {
293 2724 50       4239 my $t = ref $k ? $k : $m->{ $k };
294 2724 100       4134 if( $t->valid_extension($file) ) {
295             #warn sprintf "*** found '%s'", $t->mime_type;
296 4         33 push @candidates, $m->{$t->mime_type};
297             };
298             };
299              
300 3         305 @candidates;
301             }
302              
303             =head2 C<< $mime->mime_type_from_name >>
304              
305             my $type = $mime->mime_type_from_name( 'some/file.ext' );
306             print $type->mime_type, "\n"
307             if $type;
308              
309             Returns the most likely type of a file name as L. Returns
310             C if no file type can be determined.
311              
312             =cut
313              
314 3     3 1 2003 sub mime_type_from_name( $self, $file ) {
  3         7  
  3         7  
  3         4  
315 3         15 ($self->mime_types_from_name($file))[0]
316             }
317              
318             package MIME::Detect::Buffer;
319 4     4   44 use Moo 2;
  4         139  
  4         34  
320 4     4   1955 use feature 'signatures';
  4         33  
  4         244  
321 4     4   21 no warnings 'experimental::signatures';
  4         22  
  4         211  
322 4     4   25 use Fcntl 'SEEK_SET';
  4         8  
  4         3338  
323              
324             has 'offset' => (
325             is => 'rw',
326             default => 0,
327             );
328              
329             has 'buffer' => (
330             is => 'rw',
331             default => undef,
332             );
333              
334             has 'fh' => (
335             is => 'ro',
336             );
337              
338 5052     5052 0 5526 sub length($self) {
  5052         5433  
  5052         5220  
339 5052 100       14958 length $self->buffer || 0
340             };
341              
342 2593     2593 0 2948 sub request($self,$offset,$length) {
  2593         2857  
  2593         3059  
  2593         2717  
  2593         2656  
343 2593         3548 my $fh = $self->fh;
344              
345 2593 100       5018 if( $offset =~ m/^(\d+):(\d+)$/) {
346 361         659 $offset = $1;
347 361         673 $length += $2;
348             };
349              
350             #warn sprintf "At %d to %d (%d), want %d to %d (%d)",
351             # $self->offset, $self->offset+$self->length, $self->length,
352             # $offset, $offset+$length, $length;
353 2593 100 100     6511 if( $offset < $self->offset
354             or $self->offset+$self->length < $offset+$length ) {
355             # We need to refill the buffer
356 739         875 my $buffer;
357 739         853 my $updated = 0;
358 739 50       1138 if (ref $fh eq 'GLOB') {
359 739 50       1629 if( seek($fh, $offset, SEEK_SET)) {
360 739         1475 read($fh, $buffer, $length);
361 739         892 $updated = 1;
362             };
363             } else {
364             # let's hope you have ->seek and ->read:
365 0 0       0 if( $fh->seek($offset, SEEK_SET) ) {
366 0         0 $fh->read($buffer, $length);
367 0         0 $updated = 1;
368             };
369             }
370              
371             # Setting all three in one go would be more object-oriented ;)
372 739 50       1149 if( $updated ) {
373 739         1413 $self->offset($offset);
374 739         1234 $self->buffer($buffer);
375             };
376             };
377              
378 2593 100 66     6000 if( $offset >= $self->offset
    50          
379             and $self->offset+$self->length >= $offset+$length ) {
380 2017         5564 substr $self->buffer, $offset-$self->offset, $length
381             } elsif( $offset >= $self->offset ) {
382 576         1564 substr $self->buffer, $offset-$self->offset
383             } else {
384 0           return ''
385             };
386             }
387              
388             1;
389              
390             =head1 SEE ALSO
391              
392             L - the website
393             where the XML file is distributed
394              
395             L - module to read your locally installed and converted MIME database
396              
397             L - if you can install C and the appropriate C files
398              
399             L - if you have the appropriate C files
400              
401             L - if you have the appropriate C files but want more speed
402              
403             L - inlines its database, unsupported since 2004?
404              
405             L - if you're only interested in determining whether
406             a file is an image or not
407              
408             L - for extension-based detection
409              
410             =head1 REPOSITORY
411              
412             The public repository of this module is
413             L.
414              
415             =head1 SUPPORT
416              
417             The public support forum of this module is
418             L.
419              
420             =head1 BUG TRACKER
421              
422             Please report bugs in this module via the RT CPAN bug queue at
423             L
424             or via mail to L.
425              
426             =head1 AUTHOR
427              
428             Max Maischein C
429              
430             =head1 COPYRIGHT (c)
431              
432             Copyright 2015-2024 by Max Maischein C.
433              
434             =head1 LICENSE
435              
436             This module is released under the same terms as Perl itself.
437              
438             =cut