File Coverage

blib/lib/Parse/HTTP/UserAgent/Base/Parsers.pm
Criterion Covered Total %
statement 618 668 92.5
branch 266 334 79.6
condition 157 241 65.1
subroutine 30 30 100.0
pod n/a
total 1071 1273 84.1


line stmt bran cond sub pod time code
1             package Parse::HTTP::UserAgent::Base::Parsers;
2             $Parse::HTTP::UserAgent::Base::Parsers::VERSION = '0.43';
3 4     4   141734 use strict;
  4         7  
  4         153  
4 4     4   18 use warnings;
  4         6  
  4         238  
5 4     4   397 use Parse::HTTP::UserAgent::Constants qw(:all);
  4         7  
  4         34293  
6              
7             sub _extract_dotnet {
8 172     172   528 my($self, @args) = @_;
9 172 50       398 my @raw = map { ref($_) eq 'ARRAY' ? @{$_} : $_ } grep { $_ } @args;
  190         516  
  190         656  
  344         777  
10 172         344 my(@extras,@dotnet);
11              
12 172         413 foreach my $e ( @raw ) {
13 662 100       2351 if ( my @match = $e =~ RE_DOTNET ) {
14 186         351 push @dotnet, $match[0];
15 186         431 next;
16             }
17 476 100       1816 if ( $e =~ RE_WINDOWS_OS ) {
18 180 100 66     1116 if ( $1 && $1 ne '64' ) {
19             # Maxthon stupidity: multiple OS definitions
20 178   66     862 $self->[UA_OS] ||= $e;
21 178         413 next;
22             }
23             }
24 298         636 push @extras, $e;
25             }
26              
27 172         940 return [@extras], [@dotnet];
28             }
29              
30             sub _fix_opera {
31 48     48   74 my $self = shift;
32 48 100       171 return 1 if ! $self->[UA_EXTRAS];
33 26         41 my @buf;
34 26         43 foreach my $e ( @{ $self->[UA_EXTRAS] } ) {
  26         72  
35 56 100       180 if ( $e =~ RE_OPERA_MINI ) {
36 12         38 $self->[UA_ORIGINAL_NAME] = $1;
37 12         25 $self->[UA_ORIGINAL_VERSION] = $2;
38 12         19 $self->[UA_MOBILE] = 1;
39 12         24 next;
40             }
41 44         91 push @buf, $e;
42             }
43 26         99 $self->_fix_os_lang;
44 26         81 $self->_fix_windows_nt('skip_os');
45 26 100       116 $self->[UA_EXTRAS] = @buf ? [ @buf ] : undef;
46 26         103 return 1;
47             }
48              
49             sub _fix_generic {
50 60     60   190 my($self, $os_ref, $name_ref, $v_ref, $e_ref) = @_;
51 60 100 100     104 if ( ${$v_ref} && ${$v_ref} !~ RE_DIGIT) {
  60         196  
  56         311  
52 2         6 ${$name_ref} .= q{ } . ${$v_ref};
  2         6  
  2         6  
53 2         6 ${$v_ref} = undef;
  2         5  
54             }
55              
56 60 100 100     136 if ( ${$os_ref} && ${$os_ref} =~ RE_HTTP ) {
  60         200  
  48         195  
57 8         17 ${$os_ref} =~ s{ \A \+ }{}xms;
  8         36  
58 8         20 push @{ $e_ref }, ${$os_ref};
  8         15  
  8         20  
59 8         18 ${$os_ref} = undef;
  8         20  
60             }
61 60         141 return;
62             }
63              
64             sub _parse_maxthon {
65 44     44   133 my($self, $moz, $thing, $extra, @others) = @_;
66 44   33     162 my $is_30 = $extra
67             && $extra->[0]
68             && index( $extra->[0], 'AppleWebKit' ) != NO_IMATCH;
69 44         124 my($maxthon, $msie, @buf);
70              
71 44 100       97 if ( $is_30 ) {
72             # yay, new nonsense with the new version
73 10         20 my @new;
74 10         38 for my $i (0..$#others) {
75 20 100       55 if ( index( $others[$i], 'Maxthon') != NO_IMATCH ) {
76 10         32 @new = split m{\s+}xms, $others[$i];
77 10         49 $maxthon = shift @new;
78 10   50     28 $extra ||= [];
79 10         14 unshift @{ $extra }, shift @new;
  10         33  
80 10         27 $others[$i] = '';
81 10         21 last;
82             }
83             }
84 10         22 @others = grep { $_ } @others, @new;
  28         61  
85 10         45 $self->_parse_safari( $moz, $thing, $extra, @others );
86 10         24 $self->[UA_NAME] = 'Maxthon';
87             }
88             else {
89 34         83 my @omap = grep { $_ } map { split RE_SC_WS_MULTI, $_ } @others;
  0         0  
  0         0  
90              
91 34         60 foreach my $e ( @omap, @{$thing} ) { # $extra -> junk
  34         83  
92 298 100       682 if ( index(uc $e, 'MAXTHON') != NO_IMATCH ) {
93 36         85 $maxthon = $e;
94 36         72 next;
95             }
96 262 100       546 if ( index(uc $e, 'MSIE' ) != NO_IMATCH ) {
97             # Maxthon stupidity: multiple MSIE strings
98 46   66     184 $msie ||= $e;
99 46         122 next;
100             }
101 216         454 push @buf, $e;
102             }
103             }
104              
105 44 50       125 if ( ! $maxthon ) {
106 0         0 warn ERROR_MAXTHON_VERSION . "\n";
107 0         0 $self->[UA_UNKNOWN] = 1;
108 0         0 return;
109             }
110              
111 44 100       113 if ( $is_30 ) {
112 10 100       23 if ( $self->[UA_LANG] ) {
113 4   50     9 push @{ $self->[UA_EXTRAS] ||= [] }, $self->[UA_LANG];
  4         20  
114 4         10 $self->[UA_LANG] = undef;
115             }
116             }
117             else {
118 34 50       76 if ( ! $msie ) {
119 0         0 warn ERROR_MAXTHON_MSIE . "\n";
120 0         0 $self->[UA_UNKNOWN] = 1;
121 0         0 return;
122             }
123             $self->_parse_msie(
124 34         212 $moz, [ undef, @buf ], undef, split RE_WHITESPACE, $msie
125             );
126             }
127              
128 44 100       387 my(undef, $mv) = split $is_30 ? RE_SLASH : RE_WHITESPACE, $maxthon;
129             my $v = $mv ? $mv
130             : $maxthon ? '1.0'
131 44 50       137 : do { warn ERROR_MAXTHON_VERSION . "\n"; 0 }
  0 100       0  
  0         0  
132             ;
133              
134 44         91 $self->[UA_ORIGINAL_VERSION] = $v;
135 44         78 $self->[UA_ORIGINAL_NAME] = 'Maxthon';
136 44         83 $self->[UA_PARSER] = 'maxthon';
137 44         176 return 1;
138             }
139              
140             sub _parse_msie {
141 170     170   504 my($self, $moz, $thing, $extra, $name, $version) = @_;
142 170         286 my $junk = shift @{ $thing }; # already used
  170         361  
143              
144 170         544 my($extras,$dotnet) = $self->_extract_dotnet( $thing, $extra );
145              
146 170 100 100     303 if ( @{$extras} == 2 && index( $extras->[1], 'Lunascape' ) != NO_IMATCH ) {
  170         565  
147 2         6 ($name, $version) = split RE_CHAR_SLASH_WS, pop @{ $extras };
  2         16  
148             }
149              
150 170         397 $self->[UA_NAME] = $name;
151 170         360 $self->[UA_VERSION_RAW] = $version;
152 170 100       253 $self->[UA_DOTNET] = [ @{ $dotnet } ] if @{$dotnet};
  76         227  
  170         424  
153              
154 170 100 100     687 if ( $extras->[0] && $extras->[0] eq 'Mac_PowerPC' ) {
155 6         13 $self->[UA_OS] = shift @{ $extras };
  6         16  
156             }
157              
158 170         365 my $real_version;
159             my @buf;
160 170         279 foreach my $e ( @{ $extras } ) {
  170         359  
161 288 100       659 if ( index( $e, 'Trident/' ) != NO_IMATCH ) {
162 44         191 my($tk_name, $tk_version) = split m{[/]}xms, $e, 2;
163 44         136 $self->[UA_TOOLKIT] = [ $tk_name, $tk_version ];
164 44 50 33     224 if ( $tk_name eq 'Trident' && $tk_version ) {
165 44 100 100     262 if ( $tk_version eq '7.0' && $self->[UA_VERSION_RAW] ne '11.0' ) {
    100 100        
166             # more stupidity (compat mode)
167 2         10 $self->[UA_ORIGINAL_NAME] = 'MSIE';
168 2         6 $self->[UA_ORIGINAL_VERSION] = 11;
169             }
170             elsif ( $tk_version eq '6.0' && $self->[UA_VERSION_RAW] ne '10.0') {
171             # more stupidity (compat mode)
172 4         12 $self->[UA_ORIGINAL_NAME] = 'MSIE';
173 4         11 $self->[UA_ORIGINAL_VERSION] = 10;
174             }
175             else {
176             # must be the real version or some other stupidity
177             }
178             }
179 44         104 next;
180             }
181 244         535 push @buf, $e;
182             }
183              
184             my @extras =
185             map {
186 230         708 my $thing = $self->trim( $_ );
187             lc($thing) eq 'touch'
188 230 100       830 ? do {
189 4         11 $self->[UA_TOUCH] = 1;
190 4         10 $self->[UA_MOBILE] = 1;
191 4         14 ();
192             }
193             : $thing
194             ;
195             }
196 170         380 grep { $_ !~ m{ \s+ compatible \z }xms }
  244         668  
197             @buf
198             ;
199              
200 170 100       504 $self->[UA_EXTRAS] = @extras ? [ @extras ] : undef;
201 170         414 $self->[UA_PARSER] = 'msie';
202              
203 170         850 return 1;
204             }
205              
206             sub _parse_msie_11 {
207 12     12   39 my($self, $moz, $thing, $extra) = @_;
208              
209 12 100       45 if ( ref $extra eq 'ARRAY' ) {
210             # remove junk
211 10 100       19 @{$extra} = grep { $_ ne 'like' && $_ ne 'Gecko' } @{ $extra };
  10         31  
  20         85  
  10         27  
212             }
213             else {
214 2         5 $extra = [];
215             }
216              
217 12         21 my($version);
218 12         38 while ( my $e = shift @{ $thing } ) {
  68         171  
219 56 100       150 if ( index($e, 'rv:' ) != NO_IMATCH ) {
220 12         59 $version = (split m{rv:}xms, $e )[1] ;
221 12         32 next;
222             }
223 44         85 push @{ $extra }, $e;
  44         135  
224             }
225              
226 12 50       49 $self->_parse_msie( undef, $thing, $extra, 'MSIE', $version) || return;
227              
228 12 50 66     45 if ( $self->[UA_TOUCH] && $self->[UA_EXTRAS] ) {
229             # version 10+
230             my @extras = map {
231             $_ eq 'ARM'
232 2 50       8 ? do {
233 2         5 $self->[UA_DEVICE] = $_;
234             ()
235 2         5 }
236             : $_
237 2         5 } @{ $self->[UA_EXTRAS] };
  2         7  
238 2 50       9 $self->[UA_EXTRAS] = @extras ? [ @extras ] : undef;
239             }
240              
241 12         25 $self->[UA_PARSER] = 'msie11';
242 12         66 return 1;
243             }
244              
245             sub _parse_firefox {
246 48     48   147 my($self, @args) = @_;
247 48         212 $self->_parse_mozilla_family( @args );
248 48         112 my $e = $self->[UA_EXTRAS];
249              
250 48 100 66     173 if ( ref $e eq 'ARRAY'
      100        
251 46         293 && @{ $e } > 0
252             && index( lc $e->[-1], 'fennec' ) != NO_IMATCH
253             ) {
254 10         58 $self->_fix_fennec( $e );
255             }
256              
257 48         114 $self->[UA_NAME] = 'Firefox';
258              
259 48         162 return 1;
260             }
261              
262             sub _parse_ff_suspect {
263 2     2   6 my($self, $moz, $thing, $extra, @others) = @_;
264             # fool the moz parser
265 2         5 unshift @{ $extra }, '';
  2         6  
266              
267 2         9 $self->_parse_mozilla_family( $moz, $thing, $extra, @others );
268              
269 2         4 $self->[UA_PARSER] = 'ff_suspect';
270              
271 2         8 return 1;
272             }
273              
274             sub _fix_fennec {
275 10     10   31 my($self, $e) = @_;
276 10         18 my($name, $version) = split RE_SLASH, pop @{ $e };
  10         45  
277 10         30 $self->[UA_ORIGINAL_NAME] = $name;
278 10         23 $self->[UA_ORIGINAL_VERSION] = $version;
279 10         23 $self->[UA_MOBILE] = 1;
280 10 50       66 return if ! $self->[UA_LANG];
281              
282 0 0       0 if ( lc $self->[UA_LANG] eq 'tablet' ) {
    0          
283 0         0 $self->[UA_TABLET] = 1;
284 0         0 $self->[UA_LANG] = undef;
285             }
286             elsif ( index( $self->[UA_LANG], q{ } ) != NO_IMATCH ) {
287 0   0     0 push @{ $self->[UA_EXTRAS] ||= [] }, $self->[UA_LANG];
  0         0  
288 0         0 $self->[UA_LANG] = undef;
289             }
290             else {
291             # Do nothing
292             }
293              
294 0         0 return;
295             }
296              
297             sub _parse_safari {
298 66     66   246 my($self, $moz, $thing, $extra, @others) = @_;
299 66   66     375 my $ipad = $thing && lc( $thing->[0] || q{} ) eq 'ipad';
300 66         213 my($version, @junk) = split RE_WHITESPACE, pop @others;
301 66   66     296 my $ep = $version &&
302             index( lc($version), 'epiphany' ) != NO_IMATCH;
303 66         288 my($junkv, $vx) = split RE_SLASH, $version;
304              
305 66 100       198 if ( $ipad ) {
306 12         22 shift @{ $thing }; # remove iPad
  12         32  
307 12 100 66     58 if ( $junkv && $junkv eq 'Mobile' ) {
308 4         18 unshift @junk, join q{/}, $junkv, $vx;
309 4         10 $vx = undef;
310             }
311 12         28 $self->[UA_MOBILE] = 1;
312 12         24 $self->[UA_TABLET] = 1;
313             }
314              
315 66 100       249 $self->[UA_NAME] = $ep ? 'Epiphany'
    100          
316             : $ipad ? 'iPad'
317             : 'Safari';
318 66         133 $self->[UA_VERSION_RAW] = $vx;
319 66 50       160 $self->[UA_TOOLKIT] = $extra ? [ split RE_SLASH, shift @{ $extra } ] : [];
  66         255  
320 66 100 66     359 if ( $thing->[-1] && length($thing->[LAST_ELEMENT]) <= 5 ) {
321             # todo: $self->_is_lang_field($junk)
322             # in here or in _post_parse()
323 36         57 $self->[UA_LANG] = pop @{ $thing };
  36         92  
324             }
325             $self->[UA_OS] = @{$thing} && length $thing->[LAST_ELEMENT] > 1
326 64         140 ? pop @{ $thing }
327 66 100 66     113 : shift @{ $thing }
  2         9  
328             ;
329 66 50 33     316 if ( $self->[UA_OS] && lc $self->[UA_OS] eq 'macintosh' ) {
330 0         0 $self->[UA_OS] = $self->[UA_LANG];
331 0         0 $self->[UA_LANG] = undef;
332             }
333              
334 66 100 100     258 if ( $thing->[0] && lc $thing->[0] eq 'iphone' ) {
335 6         13 $self->[UA_MOBILE] = 1;
336 6         9 $self->[UA_DEVICE] = shift @{$thing};
  6         14  
337 6         11 my $check_os = $thing->[LAST_ELEMENT];
338              
339 6 100 100     29 if ( $check_os && index( $check_os, 'Mac OS X' ) != NO_IMATCH ) {
340 2 50       9 if ( $self->[UA_OS] ) {
341 2   50     5 push @{ $self->[UA_EXTRAS] ||= [] }, $self->[UA_OS];
  2         13  
342             }
343 2         5 $self->[UA_OS] = pop @{ $thing };
  2         4  
344             # Another oddity: tk as "AppleWebKit/en_SG"
345 2 50 33     13 if ( ! $self->[UA_LANG] && $self->[UA_TOOLKIT] ) {
346 2         4 my $v = $self->[UA_TOOLKIT][TK_ORIGINAL_VERSION];
347 2 50 33     18 if ( $v && $v =~ m< [a-zA-Z]{2}_[a-zA-Z]{2} >xms ) {
348 2         6 $self->[UA_LANG] = $v;
349 2         7 $self->[UA_TOOLKIT][TK_ORIGINAL_VERSION] = undef;
350             }
351             }
352             }
353             }
354              
355 66         137 my @extras;
356 66         111 push @extras, @{$thing}, @others;
  66         176  
357              
358 66 50 33     332 if ( $self->[UA_OS] && length($self->[UA_OS]) == 1 ) {
359 0         0 push @extras, $self->[UA_OS];
360 0         0 $self->[UA_OS] = undef;
361             }
362              
363 66 100 100     424 if ( $self->[UA_LANG] && $self->[UA_LANG] !~ m{[a-zA-Z]+}xmsg ) {
364             # some junk like "6.0" -- more stupidity
365 2         8 push @extras, $self->[UA_LANG];
366 2         5 $self->[UA_LANG] = undef;
367             }
368              
369 66 100       183 push @extras, @junk if @junk;
370 66 50       193 push @extras, @{$extra} if $extra;
  66         120  
371              
372 66 50       240 $self->[UA_EXTRAS] = @extras ? [ @extras ] : undef;
373              
374 66         244 return 1;
375             }
376              
377             sub _parse_chrome {
378 20     20   58 my($self, $moz, $thing, $extra, @others) = @_;
379 20         37 my $chx = pop @others;
380 20         74 my($chrome, $safari, @rest) = split RE_WHITESPACE, $chx;
381 20         34 my $opera;
382 20 100 66     68 if ( $rest[0] && index( $rest[0], 'OPR/', 0) != NO_IMATCH ) {
383 4         10 $opera = shift @rest;
384 4 50       16 if ( ref $extra eq 'ARRAY' ) {
385 4         7 unshift @{ $extra }, $chrome;
  4         14  
386             }
387 4         9 push @others, @rest, $safari;
388             }
389             else {
390 16         30 push @others, $safari;
391             }
392 20         74 $self->_parse_safari($moz, $thing, $extra, @others);
393 20   66     81 my($name, $version) = split RE_SLASH, $opera || $chrome;
394 20 100       52 $self->[UA_NAME] = $opera ? 'Opera' : $name;
395 20         31 $self->[UA_VERSION_RAW] = $version;
396 20         90 return 1;
397             }
398              
399             sub _parse_android {
400 42     42   119 my($self, $moz, $thing, $extra, @others) = @_;
401 42         61 (undef, @{$self}[UA_STRENGTH, UA_OS, UA_LANG, UA_DEVICE]) = @{ $thing };
  42         133  
  42         92  
402 42 50 66     138 if ( ! $extra
      33        
403             && $others[0]
404             && index( $others[0], 'AppleWebKit' ) != NO_IMATCH
405             ) {
406 2         6 $extra = [ shift @others ];
407 2         5 $self->[UA_PARSER] = 'android:paren_fixer';
408             }
409 42 50       220 $self->[UA_TOOLKIT] = [ split RE_SLASH, $extra->[0] ] if $extra;
410 42         87 my(@extras, $is_phone);
411              
412 42         77 my @junkions = map { split m{\s+}xms } @others;
  84         255  
413 42         87 foreach my $junk ( @junkions ) {
414 258 100       677 if ( $junk eq 'Mobile' ) {
415 38         65 $is_phone = 1;
416 38         56 next;
417             }
418 220 100       408 if ( index( $junk, 'Version' ) != NO_IMATCH ) {
419 42         125 my(undef, $v) = split RE_SLASH, $junk;
420 42         89 $self->[UA_VERSION_RAW] = $v; # looks_like_number?
421 42         82 next;
422             }
423 178         338 push @extras, $junk;
424             }
425              
426 42 50       108 if ( $self->[UA_DEVICE] ) {
427 42         105 my @build = split RE_WHITESPACE, $self->[UA_DEVICE];
428 42         61 my @btest;
429 42   33     170 while ( @build && index($build[-1], 'Build') == NO_IMATCH ) {
430 0         0 unshift @btest, pop @build;
431             }
432 42 50       136 unshift @btest, pop @build if @build;
433 42 100       155 my $device = @build ? join ' ', @build : undef;
434 42         89 my $build = shift @btest;
435              
436 42 100 66     209 if ( $device && $build ) {
437 40         152 $build =~ s{ Build/ }{}xms;
438 40   50     100 my $os = $self->[UA_OS] || 'Android';
439 40         68 $self->[UA_DEVICE] = $device;
440 40         84 $self->[UA_OS] = "$os ($build)";
441 40 50       118 if ( @btest ) {
442 0         0 $self->[UA_TOOLKIT] = [ split RE_SLASH, $btest[0] ];
443             }
444             }
445             }
446              
447 42 50 33     210 if ( @extras >= 3 && $extras[0] && $extras[0] eq 'KHTML,') {
      33        
448 42         105 unshift @extras, join ' ', map { shift @extras } 1..3;
  126         279  
449             }
450              
451 42         84 my @extras_final = grep { $_ } @extras;
  94         189  
452              
453 42         81 $self->[UA_NAME] = 'Android';
454 42         61 $self->[UA_MOBILE] = 1;
455 42 100       102 $self->[UA_TABLET] = $is_phone ? undef : 1;
456 42 50       593 $self->[UA_EXTRAS] = @extras_final ? [ @extras_final ] : undef;
457              
458 42         169 return 1;
459             }
460              
461             sub _parse_opera_pre {
462             # opera 5,9
463 36     36   104 my($self, $moz, $thing, $extra) = @_;
464             my $ffaker = @{$thing} && index($thing->[LAST_ELEMENT], 'rv:') != NO_IMATCH
465 36 100 100     60 ? pop @{$thing}
  2         8  
466             : 0;
467 36         134 my($name, $version) = split RE_SLASH, $moz;
468 36 100       124 return if $name ne 'Opera';
469 34         84 $self->[UA_NAME] = $name;
470 34         66 $self->[UA_VERSION_RAW] = $version;
471 34         53 my $lang;
472              
473 34 100       89 if ( $extra ) {
474             # opera changed version string to workaround lame browser sniffers
475             # http://dev.opera.com/articles/view/opera-ua-string-changes/
476 22   66     40 my $swap = @{$extra}
477             && index($extra->[LAST_ELEMENT], 'Version/') != NO_IMATCH;
478 22 100       54 ($lang = $swap ? shift @{$extra} : pop @{$extra}) =~ tr/[]//d;
  10         35  
  12         132  
479 22 100       121 if ( $swap ) {
480 10         39 my $vjunk = pop @{$extra};
  10         25  
481 10 50       54 $self->[UA_VERSION_RAW] = ( split RE_SLASH, $vjunk )[1] if $vjunk;
482             }
483             }
484              
485 34 100 33     88 $lang ||= pop @{$thing} if $ffaker;
  2         9  
486              
487 34   100     165 my $tk_parsed_as_lang = ! $self->[UA_TOOLKIT]
488             && $self->_numify( $version ) >= OPERA9
489             && $lang
490             && length( $lang ) > OPERA_TK_LENGTH;
491              
492 34 100       90 if ( $tk_parsed_as_lang ) {
493 16         66 $self->[UA_TOOLKIT] = [ split RE_SLASH, $lang ];
494 16 50       44 ($lang = pop @{$thing}) =~ tr/[]//d if $extra;
  16         44  
495             }
496              
497 34         74 $self->[UA_LANG] = $lang;
498              
499 34 100 66     49 if ( @{$thing} && $self->_is_strength( $thing->[LAST_ELEMENT] ) ) {
  34         173  
500 24         39 $self->[UA_STRENGTH] = pop @{ $thing };
  24         56  
501 24         41 $self->[UA_OS] = shift @{ $thing };
  24         49  
502             }
503             else {
504 10         17 $self->[UA_OS] = pop @{ $thing };
  10         28  
505             }
506              
507 34 100       60 my @extras = ( @{ $thing }, ( $extra ? @{$extra} : () ) );
  34         195  
  22         58  
508              
509 34 100       93 $self->[UA_EXTRAS] = @extras ? [ @extras ] : undef;
510              
511 34         102 return $self->_fix_opera;
512             }
513              
514             sub _parse_opera_post {
515             # opera 5,6,7
516 14     14   47 my($self, $moz, $thing, $extra, $compatible) = @_;
517 14 100       42 shift @{ $thing } if $compatible;
  8         19  
518 14         26 $self->[UA_NAME] = shift @{$extra};
  14         36  
519 14         27 $self->[UA_VERSION_RAW] = shift @{$extra};
  14         30  
520 14   100     23 ($self->[UA_LANG] = shift @{$extra} || q{}) =~ tr/[]//d;
521              
522 14 100 66     42 if ( @{$thing} && $self->_is_strength( $thing->[LAST_ELEMENT] ) ) {
  14         65  
523 6         13 $self->[UA_STRENGTH] = pop @{ $thing };
  6         12  
524 6         11 $self->[UA_OS] = shift @{ $thing };
  6         14  
525             }
526             else {
527 8         14 $self->[UA_OS] = pop @{ $thing };
  8         20  
528             }
529              
530 14 50       30 my @extras = ( @{ $thing }, ( $extra ? @{$extra} : () ) );
  14         34  
  14         59  
531 14 100       41 $self->[UA_EXTRAS] = @extras ? [ @extras ] : undef;
532 14         100 return $self->_fix_opera;
533             }
534              
535             sub _parse_mozilla_family {
536 100     100   274 my($self, $moz, $thing, $extra, @others) = @_;
537             # firefox variation or just mozilla itself
538 100 100       479 my($name, $version) = split RE_SLASH, defined $extra->[1] ? $extra->[1]
539             : $moz
540             ;
541 100 50       250 if ( $version ) {
542 100         228 $extra->[1] = '';
543             }
544 100         238 $self->[UA_NAME] = $name;
545 100         211 $self->[UA_VERSION_RAW] = $version;
546             $self->[UA_TOOLKIT] = $extra->[0]
547 100 100       223 ? [ split RE_SLASH, shift @{ $extra } ]
  98         437  
548             : undef
549             ;
550              
551 100 100 66     247 if ( @{$thing} && index($thing->[LAST_ELEMENT], 'rv:') != NO_IMATCH ) {
  100         507  
552 90         159 $self->[UA_MOZILLA] = pop @{ $thing };
  90         197  
553 90         143 my $len_thing = @{ $thing };
  90         186  
554 90 50       287 if ( $len_thing == 3 ) {
    100          
555 0         0 $self->[UA_OS] = shift @{ $thing };
  0         0  
556 0 0 0     0 if ( $self->[UA_OS] && $self->[UA_OS] eq 'Macintosh' ) {
557 0         0 $self->[UA_OS] = shift @{ $thing };
  0         0  
558             }
559 0 0       0 $self->[UA_LANG] = pop @{ $thing } if @{ $thing };
  0         0  
  0         0  
560             }
561             elsif ( $len_thing <= 2 ) {
562 24 100 100     241 if ( $thing->[0] eq 'X11'
    100 100        
563             || index( $thing->[-1], 'Intel' ) != NO_IMATCH
564             ) {
565 12 100       70 if ( index( lc $thing->[-1], 'linux arm') != NO_IMATCH ) {
566 2         8 $self->[UA_DEVICE] = pop @{ $thing };
  2         7  
567 2         9 $self->[UA_OS] = 'Linux'; # Android? huh?
568             }
569             else {
570 10         21 $self->[UA_OS] = pop @{ $thing };
  10         88  
571             }
572             }
573             elsif (
574             index( lc $thing->[0], 'android' ) != NO_IMATCH
575             || index( lc $thing->[0], 'maemo' ) != NO_IMATCH
576             ) {
577             # mobile? tablet?
578 6         18 $self->[UA_OS] = shift @{ $thing };
  6         20  
579 6         14 $self->[UA_DEVICE] = shift @{ $thing };
  6         16  
580 6 100       29 if ( lc $self->[UA_DEVICE] eq 'tablet' ) {
581 2         9 $self->[UA_TABLET] = 1;
582             }
583             }
584             else {
585 6 100       24 if ( $len_thing > 1 ) {
586 4 100       16 if ( $thing->[-1] ne 'WOW64' ) {
587 2         3 $self->[UA_LANG] = pop @{ $thing };
  2         7  
588             }
589             }
590             else {
591 2         4 $self->[UA_OS] = pop @{ $thing };
  2         8  
592             }
593             }
594             }
595             else {
596              
597 66         118 $self->[UA_LANG] = pop @{ $thing };
  66         151  
598 66         105 $self->[UA_OS] = pop @{ $thing };
  66         145  
599             }
600             }
601              
602 368         792 my @extras = grep { $_ }
603 100         283 @{ $thing },
604             @others,
605 100 50       228 $extra ? @{ $extra } : (),
  100         233  
606             ;
607              
608 100 100       333 $self->[UA_EXTRAS] = @extras ? [ @extras ] : undef;
609              
610 100         266 return 1;
611             }
612              
613             sub _parse_gecko {
614 50     50   154 my($self, $moz, $thing, $extra, @others) = @_;
615 50         207 $self->_parse_mozilla_family($moz, $thing, $extra, @others);
616              
617             # we got some name & version
618 50 50 33     267 if ( $self->[UA_NAME] && $self->[UA_VERSION_RAW] ) {
619             # Change SeaMonkey too?
620 50         102 my $before = $self->[UA_NAME];
621 50 100       142 $self->[UA_NAME] = 'Netscape' if $self->[UA_NAME] eq 'Netscape6';
622 50 100       142 $self->[UA_NAME] = 'Mozilla' if $self->[UA_NAME] eq 'Beonex';
623 50         96 $self->[UA_PARSER] = 'mozilla_family:generic';
624 50         77 my @buf;
625              
626 50         71 foreach my $e ( @{ $self->[UA_EXTRAS] } ) {
  50         163  
627 130 50       283 next if ! $e;
628 130 100       308 if ( my $s = $self->_is_strength($e) ) {
629 46         105 $self->[UA_STRENGTH] = $s;
630 46         106 next;
631             }
632 84 100       238 if ( $e =~ RE_IX86 ) {
633 4         19 my($os,$lang) = split RE_COMMA, $e;
634 4 50       17 $self->[UA_OS] = $os if $os;
635 4 100       18 $self->[UA_LANG] = $self->trim($lang) if $lang;
636 4         10 next;
637             }
638 80 100 100     310 if ( ! $self->[UA_OS] && $e =~ m{ Win(?:NT|dows) }xmsi ) {
639 6         19 $self->[UA_OS] = $e;
640 6         17 next;
641             }
642 74 100       223 if ( $e =~ RE_TWO_LETTER_LANG ) {
643 2         7 $self->[UA_LANG] = $e;
644 2         7 next;
645             }
646 72 100       177 if ( $e =~ RE_EPIPHANY_GECKO ) {
647 2         9 $self->[UA_NAME] = $before = $1;
648 2         6 $self->[UA_VERSION_RAW] = $2;
649             }
650 72         193 push @buf, $e;
651             }
652              
653 50 50       184 $self->[UA_EXTRAS] = @buf ? [ @buf ] : undef;
654 50 100       163 $self->[UA_ORIGINAL_NAME] = $before if $before ne $self->[UA_NAME];
655 50         204 $self->_fix_windows_nt;
656 50         317 return 1 ;
657             }
658              
659 0 0 0     0 if ( ref $self->[UA_TOOLKIT] eq 'ARRAY' && $self->[UA_TOOLKIT][TK_NAME] eq 'Gecko' ) {
660 0         0 ($self->[UA_NAME], $self->[UA_VERSION_RAW]) = split RE_SLASH, $moz;
661 0 0 0     0 if ( $self->[UA_NAME] && $self->[UA_VERSION_RAW] ) {
662 0         0 $self->[UA_PARSER] = 'mozilla_family:gecko';
663 0         0 return 1;
664             }
665             }
666              
667 0         0 return;
668             }
669              
670             sub _fix_os_lang {
671 26     26   49 my $self = shift;
672 26 100 66     104 if ( $self->[UA_OS] && length $self->[UA_OS] == 2 ) {
673 8         22 $self->[UA_LANG] = $self->[UA_OS];
674 8         17 $self->[UA_OS] = undef;
675             }
676 26         46 return;
677             }
678              
679             sub _fix_windows_nt {
680 76     76   158 my $self = shift;
681 76         161 my $skip_os = shift; # ie os can be undef
682 76   100     211 my $os = $self->[UA_OS] || q{};
683 76 100 100     823 return if ( ! $os && ! $skip_os )
      100        
      100        
      66        
      66        
      66        
684             || ( $os ne 'windows' && ! $skip_os )
685             || ref $self->[UA_EXTRAS] ne 'ARRAY'
686             || ! $self->[UA_EXTRAS][0]
687             || $self->[UA_EXTRAS][0] !~ m{ NT\s?(\d.*?) \z }xmsi
688             ;
689 6         22 $self->[UA_EXTRAS][0] = $self->[UA_OS]; # restore
690 6         34 $self->[UA_OS] = "Windows NT $1"; # fix
691 6         14 return;
692             }
693              
694             sub _parse_netscape {
695 26     26   62 my($self, $moz, $thing) = @_;
696 26         86 my($mozx, $junk) = split RE_WHITESPACE, $moz;
697 26         98 my(undef, $version) = split RE_SLASH , $mozx;
698 26         47 my @buf;
699 26         42 foreach my $e ( @{ $thing } ) {
  26         68  
700 64 100       149 if ( my $s = $self->_is_strength($e) ) {
701 26         61 $self->[UA_STRENGTH] = $s;
702 26         66 next;
703             }
704 38         95 push @buf, $e;
705             }
706 26         53 $self->[UA_VERSION_RAW] = $version;
707 26 100       91 $self->[UA_OS] = $buf[0] eq 'X11' ? pop @buf : shift @buf;
708 26         55 $self->[UA_NAME] = 'Netscape';
709 26 100       118 $self->[UA_EXTRAS] = @buf ? [ @buf ] : undef;
710 26 100       59 if ( $junk ) {
711 12         91 $junk =~ s{ \[ (.+?) \] .* \z}{$1}xms;
712 12 50       41 $self->[UA_LANG] = $junk if $junk;
713             }
714 26         48 $self->[UA_PARSER] = 'netscape';
715 26         186 return 1;
716             }
717              
718             sub _generic_moz_thing {
719 50     50   160 my($self, $moz, $t, $extra, $compatible, @others) = @_;
720 50 100       79 return if ! @{ $t };
  50         158  
721 30         234 my($mname, $mversion, @rest) = split RE_CHAR_SLASH_WS, $moz;
722 30 100 100     166 return if $mname eq 'Mozilla' || $mname eq 'Emacs-W3';
723              
724 24 100       85 if ( index( $mname, 'Nokia' ) != NO_IMATCH ) {
725 6         36 my($device, $num, $os, $series, @junk) = split m{[\s]+}xms,
726             $self->[UA_STRING_ORIGINAL];
727 6 50 33     70 if ( $device
      66        
      66        
      66        
728             && $num
729             && $os
730             && $series
731             && index( $os, 'SymbianOS' ) != NO_IMATCH
732             ) {
733 4         45 return $self->_parse_symbian(
734             join ';', $os, "$series $device", join(q{ }, @junk, $num)
735             );
736             }
737             }
738              
739 20         49 $self->[UA_NAME] = $mname;
740 20   100     80 $self->[UA_VERSION_RAW] = $mversion || ( $mname eq 'Links' ? shift @{$t} : 0 );
741             $self->[UA_OS] = @rest ? join(q{ }, @rest)
742 20 100 66     168 : $t->[0] && $t->[0] !~ RE_DIGIT_DOT_DIGIT ? shift @{$t}
  10 100       31  
743             : undef;
744 20 100       42 my @extras = (@{$t}, $extra ? @{$extra} : (), @others );
  20         69  
  2         7  
745              
746 20         102 $self->_fix_generic(
747             \$self->[UA_OS], \$self->[UA_NAME], \$self->[UA_VERSION_RAW], \@extras
748             );
749              
750 20 100       96 $self->[UA_EXTRAS] = @extras ? [ @extras ] : undef;
751 20         44 $self->[UA_GENERIC] = 1;
752 20         46 $self->[UA_PARSER] = 'generic_moz_thing';
753              
754 20         200 return 1;
755             }
756              
757             sub _generic_name_version {
758 110     110   271 my($self, $moz, $thing, $extra, $compatible, @others) = @_;
759 110   33     307 my $ok = $moz && ! @{$thing} && ! $extra && ! $compatible && ! @others;
760 110 100       528 return if not $ok;
761              
762 36         108 my @moz = split RE_WHITESPACE, $moz;
763 36 100       104 if ( @moz == 1 ) {
764 18         66 my($name, $version) = split RE_SLASH, $moz;
765 18 50 33     73 if ($name && $version) {
766 18         41 $self->[UA_NAME] = $name;
767 18         35 $self->[UA_VERSION_RAW] = $version;
768 18         104 $self->[UA_GENERIC] = 1;
769 18         36 $self->[UA_PARSER] = 'generic_name_version';
770 18         137 return 1;
771             }
772             }
773 18         91 return;
774             }
775              
776             sub _generic_compatible {
777 92     92   248 my($self, $moz, $thing, $extra, $compatible, @others) = @_;
778 92         151 my @orig_thing = @{ $thing }; # see edge case below
  92         255  
779              
780 92 100 66     504 return if ! ( $compatible && @{$thing} );
  44         128  
781              
782 44         296 my($mname, $mversion) = split RE_CHAR_SLASH_WS, $moz;
783             my($name, $version) = $mname eq 'Mozilla'
784 44 100       132 ? split( RE_CHAR_SLASH_WS, shift @{ $thing } )
  40         179  
785             : ($mname, $mversion)
786             ;
787 44 100 100     348 shift @{$thing} if $thing->[0] &&
  4   100     11  
788             ( $thing->[0] eq $name || $thing->[0] eq $moz);
789 44         78 my $os = shift @{$thing};
  44         94  
790 44         80 my $lang = pop @{$thing};
  44         86  
791 44         83 my @extras;
792              
793 44 100       147 if ( $name eq 'MSIE') {
794 4 50       43 if ( $self->_is_generic_bogus_ie( $extra ) ) {
    100          
795             # edge case
796 0         0 my($n, $v) = split RE_WHITESPACE, shift @orig_thing;
797 0         0 my $e = [ split RE_SC_WS, join q{ }, @{ $extra } ];
  0         0  
798 0         0 my $t = \@orig_thing;
799 0         0 push @{ $e }, grep { $_ } map { split RE_SC_WS, $_ } @others;
  0         0  
  0         0  
  0         0  
800 0         0 $self->_parse_msie( $moz, $thing, $e, $n, $v );
801 0         0 return 1;
802             }
803             elsif ( $extra ) { # Sleipnir?
804 2         6 ($name, $version) = split RE_SLASH, pop @{$extra};
  2         10  
805 2         28 my($extras,$dotnet) = $self->_extract_dotnet( $thing, $extra );
806 2 50       6 $self->[UA_DOTNET] = [ @{$dotnet} ] if @{$dotnet};
  2         7  
  2         9  
807 2         6 @extras = (@{ $extras }, @others);
  2         8  
808             }
809             else {
810 2 50       20 return if index($moz, q{ }) != NO_IMATCH; # WebTV
811             }
812             }
813              
814 42 100       114 @extras = (@{$thing}, $extra ? @{$extra} : (), @others ) if ! @extras;
  40 100       106  
  16         54  
815              
816 42 100 100     162 if ( $lang && index( $lang, 'MSIE ') != NO_IMATCH ) {
817 2         18 return $self->_parse_msie(
818             $moz,
819             [],
820             [$os, "$name/$version", @extras], # junk
821             split( m{[\s]+}xms, $lang, 2 ), # name, version
822             );
823             }
824              
825 40         193 $self->_fix_generic( \$os, \$name, \$version, \@extras );
826              
827 40         98 $self->[UA_NAME] = $name;
828 40   100     155 $self->[UA_VERSION_RAW] = $version || 0;
829 40         80 $self->[UA_OS] = $os;
830 40         81 $self->[UA_LANG] = $lang;
831 40 100       164 $self->[UA_EXTRAS] = @extras ? [ @extras ] : undef;
832 40         82 $self->[UA_GENERIC] = 1;
833 40         73 $self->[UA_PARSER] = 'generic_compatible';
834              
835 40         433 return 1;
836             }
837              
838             sub _parse_emacs {
839 4     4   14 my($self, $moz, $thing, $extra, $compatible, @others) = @_;
840 4         13 my @moz = split RE_WHITESPACE, $moz;
841 4         10 my $emacs = shift @moz;
842 4         12 my($name, $version) = split RE_SLASH, $emacs;
843 4         11 $self->[UA_NAME] = $name;
844 4   50     12 $self->[UA_VERSION_RAW] = $version || 0;
845 4         6 $self->[UA_OS] = shift @{ $thing };
  4         10  
846 4 50       22 $self->[UA_OS] = $self->trim( $self->[UA_OS] ) if $self->[UA_OS];
847 4         8 my @rest = ( @{ $thing }, @moz );
  4         13  
848 4 50 33     14 push @rest, @{ $extra } if $extra && ref $extra eq 'ARRAY';
  0         0  
849 4 50       10 push @rest, ( map { split RE_SC_WS, $_ } @others ) if @others;
  0         0  
850 4         11 my @extras = grep { $_ } map { $self->trim( $_ ) } @rest;
  10         22  
  10         22  
851 4 50       16 $self->[UA_EXTRAS] = @extras ? [ @extras ] : undef;
852 4         8 $self->[UA_PARSER] = 'emacs';
853 4         26 return 1;
854             }
855              
856             sub _parse_moz_only {
857 20     20   41 my $self = shift;
858 20         42 my($moz) = @_;
859 20         55 my @parts = split RE_WHITESPACE, $moz;
860 20         40 my $id = shift @parts;
861 20         69 my($name, $version) = split RE_SLASH, $id;
862              
863 20 100       64 if ( index( $name, 'Symbian' ) != NO_IMATCH ) {
864 4         14 return $self->_parse_symbian( $moz );
865             }
866              
867 16 100 66     73 if ( $name eq 'Mozilla' && @parts ) {
868 2         6 ($name, $version) = split RE_SLASH, shift @parts;
869 2 50 33     13 return if ! $name || ! $version;
870             }
871              
872 16         35 $self->[UA_NAME] = $name;
873 16   50     49 $self->[UA_VERSION_RAW] = $version || 0;
874 16 100       50 $self->[UA_EXTRAS] = @parts ? [ @parts ] : undef;
875 16         31 $self->[UA_PARSER] = 'moz_only';
876 16 50       42 $self->[UA_ROBOT] = 1 if ! $self->[UA_VERSION_RAW];
877              
878 16         85 return 1;
879             }
880              
881             sub _parse_symbian {
882 8     8   17 my($self, $raw) = @_;
883 8         56 my($os, $series_device, @rest) = split m{[;]\s?}xms, $raw;
884              
885 8 50 33     38 return if ! $os || ! $series_device;
886              
887 8         23 my($series, $device) = split m{[\s]+}xms, $series_device;
888              
889 8 50       19 return if ! $device;
890              
891 8         17 my @extras = map { split m{[\s]+}xms, $_ } @rest;
  12         37  
892              
893 8         22 @{ $self }[ UA_NAME, UA_VERSION_RAW ] = split RE_SLASH, $series, 2;
  8         22  
894 8         16 $self->[UA_OS] = $os;
895 8         31 $self->[UA_DEVICE] = $device;
896 8 50       27 $self->[UA_EXTRAS] = @extras ? [ @extras ] : undef;
897 8         15 $self->[UA_MOBILE] = 1;
898 8         17 $self->[UA_PARSER] = 'symbian';
899              
900 8         72 return 1;
901             }
902              
903             sub _parse_hotjava {
904 2     2   7 my($self, $moz, $thing, $extra, $compatible, @others) = @_;
905 2         6 my $parsable = shift @{ $thing };
  2         5  
906 2         8 my($name, $version) = split RE_SLASH, $moz;
907 2         7 $self->[UA_NAME] = 'HotJava';
908 2   50     7 $self->[UA_VERSION_RAW] = $version || 0;
909 2 50       7 if ( $parsable ) {
910 2         10 my @parts = split m{[\[\]]}xms, $parsable;
911 2 50       8 if ( @parts > 2 ) {
912 2         6 @parts = map { $self->trim( $_ ) } @parts;
  6         14  
913 2         7 $self->[UA_OS] = pop @parts;
914 2         5 $self->[UA_LANG] = pop @parts;
915 2 50       12 $self->[UA_EXTRAS] = @parts ? [ @parts ] : undef;
916             }
917             }
918 2         31 return 1;
919             }
920              
921             sub _parse_docomo {
922 2     2   10 my($self, $moz, $thing, $extra, $compatible, @others) = @_;
923 2 50 33     19 if ( $thing->[0] && index(lc $thing->[0], 'googlebot-mobile') != NO_IMATCH ) {
924 2         25 my($name, $version) = split RE_SLASH, shift @{ $thing };
  2         12  
925 2         8 $self->[UA_NAME] = $name;
926 2         6 $self->[UA_VERSION_RAW] = $version;
927 2 50       4 $self->[UA_EXTRAS] = @{ $thing } > 0 ? [ @{ $thing } ] : undef;
  2         11  
  2         6  
928 2         6 $self->[UA_MOBILE] = 1;
929 2         5 $self->[UA_ROBOT] = 1;
930 2         6 $self->[UA_PARSER] = 'docomo';
931 2         18 return 1;
932             }
933             #$self->[UA_PARSER] = 'docomo';
934             #require Data::Dumper;warn "DoCoMo unsupported: ".Data::Dumper::Dumper( [ $moz, $thing, $extra, $compatible, \@others ] );
935 0           return;
936             }
937              
938             1;
939              
940             __END__