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