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