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