File Coverage

blib/lib/HTTP/BrowserDetect.pm
Criterion Covered Total %
statement 896 993 90.2
branch 744 848 87.7
condition 330 409 80.6
subroutine 75 78 96.1
pod 46 47 97.8
total 2091 2375 88.0


line stmt bran cond sub pod time code
1 5     5   68252 use strict;
  5         29  
  5         147  
2 5     5   24 use warnings;
  5         9  
  5         139  
3              
4 5     5   114 use 5.006;
  5         13  
5              
6             package HTTP::BrowserDetect;
7              
8             our $VERSION = '3.37';
9              
10             # Operating Systems
11             our @OS_TESTS = qw(
12             windows mac os2
13             unix linux vms
14             bsd amiga brew
15             bb10 rimtabletos
16             chromeos ios
17             firefoxos
18             );
19              
20             # More precise Windows
21             our @WINDOWS_TESTS = qw(
22             win16 win3x win31
23             win95 win98 winnt
24             winme win32 win2k
25             winxp win2k3 winvista
26             win7 win8 win8_0
27             win8_1 win10 win10_0
28             wince winphone winphone7
29             winphone7_5 winphone8 winphone8_1
30             winphone10
31             );
32              
33             # More precise Mac
34             our @MAC_TESTS = qw(
35             macosx mac68k macppc
36             );
37              
38             # More precise Unix
39             our @UNIX_TESTS = qw(
40             sun sun4 sun5
41             suni86 irix irix5
42             irix6 hpux hpux9
43             hpux10 aix aix1
44             aix2 aix3 aix4
45             sco unixware mpras
46             reliant dec sinix
47             );
48              
49             # More precise BSDs
50             our @BSD_TESTS = qw(
51             freebsd
52             );
53              
54             # Gaming devices
55             our @GAMING_TESTS = qw(
56             ps3gameos pspgameos
57             );
58              
59             # Device related tests
60             our @DEVICE_TESTS = qw(
61             android audrey blackberry dsi iopener ipad
62             iphone ipod kindle kindlefire n3ds palm ps3 psp wap webos
63             mobile tablet
64             );
65              
66             # Browsers
67             our @BROWSER_TESTS = qw(
68             mosaic netscape firefox
69             chrome safari ie
70             opera lynx links
71             elinks neoplanet neoplanet2
72             avantgo emacs mozilla
73             konqueror realplayer netfront
74             mobile_safari obigo aol
75             lotusnotes staroffice icab
76             webtv browsex silk
77             applecoremedia galeon seamonkey
78             epiphany ucbrowser dalvik
79             edge pubsub adm
80             brave imagesearcherpro polaris
81             edgelegacy samsung
82             );
83              
84             our @IE_TESTS = qw(
85             ie3 ie4 ie4up
86             ie5 ie5up ie55
87             ie55up ie6 ie7
88             ie8 ie9 ie10
89             ie11
90             ie_compat_mode
91             );
92              
93             our @OPERA_TESTS = qw(
94             opera3 opera4 opera5
95             opera6 opera7
96             );
97              
98             our @AOL_TESTS = qw(
99             aol3 aol4
100             aol5 aol6
101             );
102              
103             our @NETSCAPE_TESTS = qw(
104             nav2 nav3 nav4
105             nav4up nav45 nav45up
106             nav6 nav6up navgold
107             );
108              
109             # Firefox variants
110             our @FIREFOX_TESTS = qw(
111             firebird iceweasel phoenix
112             namoroka
113             fxios
114             );
115              
116             # Engine tests
117             our @ENGINE_TESTS = qw(
118             gecko trident webkit
119             presto khtml edgehtml
120             );
121              
122             # These bot names get turned into methods. Crazy, right? (I don't even think
123             # this is documented anymore.) We'll leave this in place for back compat, but
124             # we won't add any new methods moving forward.
125              
126             my @OLD_ROBOT_TESTS = qw(
127             ahrefs
128             altavista
129             apache
130             askjeeves
131             baidu
132             bingbot
133             curl
134             facebook
135             getright
136             golib
137             google
138             googleadsbot
139             googleadsense
140             googlebotimage
141             googlebotnews
142             googlebotvideo
143             googlefavicon
144             googlemobile
145             indy
146             infoseek
147             ipsagent
148             java
149             linkchecker
150             linkexchange
151             lwp
152             lycos
153             malware
154             mj12bot
155             msn
156             msnmobile
157             msoffice
158             nutch
159             phplib
160             puf
161             rubylib
162             slurp
163             specialarchiver
164             wget
165             yahoo
166             yandex
167             yandeximages
168             headlesschrome
169             );
170              
171             our @ROBOT_TESTS = (
172             [ 'Applebot', 'apple' ],
173             [ 'baiduspider', 'baidu' ],
174             [ 'bitlybot', 'bitly' ],
175             [ 'developers.google.com//web/snippet', 'google-plus-snippet' ],
176             [ 'Discordbot', 'discordbot' ],
177             [ 'embedly', 'embedly' ],
178             [ 'facebookexternalhit', 'facebook' ],
179             [ 'flipboard', 'flipboard' ],
180             [ 'Google Page Speed', 'google-page-speed' ],
181             [ 'ltx71', 'ltx71' ],
182             [ 'linkedinbot', 'linkedin' ],
183             [ 'nuzzel', 'nuzzel' ],
184             [ 'outbrain', 'outbrain' ],
185             [ 'pinterest/0.', 'pinterest' ],
186             [ 'pinterestbot', 'pinterest' ],
187             [ 'Pro-Sitemaps', 'pro-sitemaps' ],
188             [ 'quora link preview', 'quora-link-preview' ],
189             [ 'Qwantify', 'qwantify' ],
190             [ 'redditbot', 'reddit', ],
191             [ 'researchscan', 'researchscan' ],
192             [ 'rogerbot', 'rogerbot' ],
193             [ 'ShowyouBot', 'showyou' ],
194             [ 'SkypeUriPreview', 'skype-uri-preview' ],
195             [ 'slackbot', 'slack' ],
196             [ 'Swiftbot', 'swiftbot' ],
197             [ 'tumblr', 'tumblr' ],
198             [ 'twitterbot', 'twitter' ],
199             [ 'vkShare', 'vkshare' ],
200             [ 'W3C_Validator', 'w3c-validator' ],
201             [ 'WhatsApp', 'whatsapp' ],
202             );
203              
204             our @MISC_TESTS = qw(
205             dotnet x11
206             webview
207             );
208              
209             our @ALL_TESTS = (
210             @OS_TESTS, @WINDOWS_TESTS,
211             @MAC_TESTS, @UNIX_TESTS,
212             @BSD_TESTS, @GAMING_TESTS,
213             @DEVICE_TESTS, @BROWSER_TESTS,
214             @IE_TESTS, @OPERA_TESTS,
215             @AOL_TESTS, @NETSCAPE_TESTS,
216             @FIREFOX_TESTS, @ENGINE_TESTS,
217             @OLD_ROBOT_TESTS, @MISC_TESTS,
218             );
219              
220             # https://support.google.com/webmasters/answer/1061943?hl=en
221              
222             my %ROBOT_NAMES = (
223             ahrefs => 'Ahrefs',
224             altavista => 'AltaVista',
225             'apache-http-client' => 'Apache HttpClient',
226             apple => 'Apple',
227             'archive-org' => 'Internet Archive',
228             askjeeves => 'AskJeeves',
229             baidu => 'Baidu',
230             baiduspider => 'Baidu Spider',
231             bingbot => 'Bingbot',
232             bitly => 'Bitly',
233             curl => 'curl',
234             discordbot => 'Discord',
235             embedly => 'Embedly',
236             facebook => 'Facebook',
237             facebookexternalhit => 'Facebook',
238             flipboard => 'Flipboard',
239             getright => 'GetRight',
240             golib => 'Go language http library',
241             google => 'Googlebot',
242             'google-adsbot' => 'Google AdsBot',
243             'google-adsense' => 'Google AdSense',
244             'googlebot' => 'Googlebot',
245             'googlebot-image' => 'Googlebot Images',
246             'googlebot-mobile' => 'Googlebot Mobile',
247             'googlebot-news' => 'Googlebot News',
248             'googlebot-video' => 'Googlebot Video',
249             'google-favicon' => 'Google Favicon',
250             'google-mobile' => 'Googlebot Mobile',
251             'google-page-speed' => 'Google Page Speed',
252             'google-plus-snippet' => 'Google+ Snippet',
253             'indy-library' => 'Indy Library',
254             infoseek => 'InfoSeek',
255             java => 'Java',
256             'libwww-perl' => 'LWP::UserAgent',
257             linkchecker => 'LinkChecker',
258             linkedin => 'LinkedIn',
259             linkexchange => 'LinkExchange',
260             ltx71 => 'ltx71',
261             lycos => 'Lycos',
262             malware => 'Malware / hack attempt',
263             'microsoft-office' => 'Microsoft Office',
264             mj12bot => 'Majestic-12 DSearch',
265             msn => 'MSN',
266             'msn-mobile' => 'MSN Mobile',
267             nutch => 'Apache Nutch',
268             nuzzel => 'Nuzzel',
269             outbrain => 'Outbrain',
270             phplib => 'PHP http library',
271             pinterest => 'Pinterest',
272             'pro-sitemaps' => 'Pro Sitemap Service',
273             puf => 'puf',
274             'quora-link-preview' => 'Quora Link Preview',
275             qwantify => 'Qwantify',
276             researchscan => 'Researchscan RWTH Aachen',
277             reddit => 'Reddit',
278             robot => 'robot',
279             rogerbot => 'Moz',
280             'ruby-http-library' => 'Ruby http library',
281             showyou => 'Showyou',
282             'skype-uri-preview' => 'Skype URI Preview',
283             slack => 'slack',
284             swiftbot => 'Swiftbot',
285             tumblr => 'Tumblr',
286             twitter => 'Twitter',
287             unknown => 'Unknown Bot',
288             'verisign-ips-agent' => 'Verisign ips-agent',
289             vkshare => 'VK Share',
290             'w3c-validator' => 'W3C Validator',
291             wget => 'Wget',
292             whatsapp => 'WhatsApp',
293             yahoo => 'Yahoo',
294             'yahoo-slurp' => 'Yahoo! Slurp',
295             yandex => 'Yandex',
296             'yandex-images' => 'YandexImages',
297             'headless-chrome' => 'HeadlessChrome',
298             );
299              
300             my %ROBOT_IDS = (
301             ahrefs => 'ahrefs',
302             altavista => 'altavista',
303             apache => 'apache-http-client',
304             askjeeves => 'askjeeves',
305             baidu => 'baidu',
306             baiduspider => 'baidu',
307             bingbot => 'bingbot',
308             curl => 'curl',
309             facebook => 'facebook',
310             getright => 'getright',
311             golib => 'golib',
312             google => 'googlebot',
313             googleadsbot => 'google-adsbot',
314             googleadsense => 'google-adsense',
315             googlebotimage => 'googlebot-image',
316             googlebotnews => 'googlebot-news',
317             googlebotvideo => 'googlebot-video',
318             googlefavicon => 'google-favicon',
319             googlemobile => 'googlebot-mobile',
320             indy => 'indy-library',
321             infoseek => 'infoseek',
322             ipsagent => 'verisign-ips-agent',
323             java => 'java',
324             linkchecker => 'linkchecker',
325             linkexchange => 'linkexchange',
326             ltx71 => 'ltx71',
327             lwp => 'libwww-perl',
328             lycos => 'lycos',
329             malware => 'malware',
330             mj12bot => 'mj12bot',
331             msn => 'msn',
332             msnmobile => 'msn-mobile',
333             msoffice => 'microsoft-office',
334             nutch => 'nutch',
335             phplib => 'phplib',
336             puf => 'puf',
337             robot => 'robot',
338             rubylib => 'ruby-http-library',
339             researchscan => 'researchscan',
340             slurp => 'yahoo-slurp',
341             specialarchiver => 'archive-org',
342             wget => 'wget',
343             yahoo => 'yahoo',
344             yandex => 'yandex',
345             yandeximages => 'yandex-images',
346             headlesschrome => 'headless-chrome',
347             );
348              
349             my %BROWSER_NAMES = (
350             adm => 'Android Download Manager',
351             aol => 'AOL Browser',
352             applecoremedia => 'AppleCoreMedia',
353             blackberry => 'BlackBerry',
354             brave => 'Brave',
355             browsex => 'BrowseX',
356             chrome => 'Chrome',
357             curl => 'curl',
358             dalvik => 'Dalvik',
359             dsi => 'Nintendo DSi',
360             edge => 'Edge',
361             elinks => 'ELinks',
362             epiphany => 'Epiphany',
363             firefox => 'Firefox',
364             galeon => 'Galeon',
365             icab => 'iCab',
366             iceweasel => 'IceWeasel',
367             ie => 'MSIE',
368             imagesearcherpro => 'ImageSearcherPro',
369             konqueror => 'Konqueror',
370             links => 'Links',
371             lotusnotes => 'Lotus Notes',
372             lynx => 'Lynx',
373             mobile_safari => 'Mobile Safari',
374             mosaic => 'Mosaic',
375             mozilla => 'Mozilla',
376             n3ds => 'Nintendo 3DS',
377             netfront => 'NetFront',
378             netscape => 'Netscape',
379             obigo => 'Obigo',
380             opera => 'Opera',
381             polaris => 'Polaris',
382             pubsub => 'Safari RSS Reader',
383             puf => 'puf',
384             realplayer => 'RealPlayer',
385             safari => 'Safari',
386             samsung => 'Samsung',
387             seamonkey => 'SeaMonkey',
388             silk => 'Silk',
389             staroffice => 'StarOffice',
390             ucbrowser => 'UCBrowser',
391             webtv => 'WebTV',
392             );
393              
394             # Device names
395             my %DEVICE_NAMES = (
396             android => 'Android',
397             audrey => 'Audrey',
398             blackberry => 'BlackBerry',
399             dsi => 'Nintendo DSi',
400             iopener => 'iopener',
401             ipad => 'iPad',
402             iphone => 'iPhone',
403             ipod => 'iPod',
404             kindle => 'Amazon Kindle',
405             n3ds => 'Nintendo 3DS',
406             palm => 'Palm',
407             ps3 => 'Sony PlayStation 3',
408             psp => 'Sony PlayStation Portable',
409             wap => 'WAP capable phone',
410             webos => 'webOS',
411             );
412              
413             my %OS_NAMES = (
414             amiga => 'Amiga',
415             android => 'Android',
416             bb10 => 'BlackBerry 10',
417             brew => 'Brew',
418             chromeos => 'Chrome OS',
419             firefoxos => 'Firefox OS',
420             ios => 'iOS',
421             linux => 'Linux',
422             mac => 'Mac',
423             macosx => 'Mac OS X',
424             os2 => 'OS/2',
425             ps3gameos => 'Playstation 3 GameOS',
426             pspgameos => 'Playstation Portable GameOS',
427             rimtabletos => 'RIM Tablet OS',
428             unix => 'Unix',
429             vms => 'VMS',
430             win2k => 'Win2k',
431             win2k3 => 'Win2k3',
432             win3x => 'Win3x',
433             win7 => 'Win7',
434             win8 => 'Win8',
435             win8_0 => 'Win8.0',
436             win8_1 => 'Win8.1',
437             win10 => 'Win10',
438             win10_0 => 'Win10.0',
439             win95 => 'Win95',
440             win98 => 'Win98',
441             winme => 'WinME',
442             winnt => 'WinNT',
443             winphone => 'Windows Phone',
444             winvista => 'WinVista',
445             winxp => 'WinXP',
446             );
447              
448             # Safari build -> version map for versions prior to 3.0
449             # (since then, version appears in the user-agent string)
450              
451             my %safari_build_to_version = qw(
452             48 0.8
453             51 0.8.1
454             60 0.8.2
455             73 0.9
456             74 1.0b2v74
457             85 1.0
458             85.7 1.0.2
459             85.8 1.0.3
460             100 1.1
461             100.1 1.1.1
462             125 1.2
463             125.1 1.2.1
464             125.7 1.2.2
465             125.9 1.2.3
466             125.11 1.2.4
467             312 1.3
468             312.3 1.3.1
469             312.5 1.3.2
470             412 2.0
471             412.5 2.0.1
472             416.12 2.0.2
473             417.8 2.0.3
474             419.3 2.0.4
475             );
476              
477             #######################################################################################################
478             # BROWSER OBJECT
479              
480             my $default = undef;
481              
482             sub new {
483 7151     7151 1 4369497 my ( $class, $user_agent ) = @_;
484              
485 7151         16064 my $self = {};
486 7151         14413 bless $self, $class;
487              
488 7151 100       17605 unless ( defined $user_agent ) {
489 2         5 $user_agent = $ENV{'HTTP_USER_AGENT'};
490             }
491              
492 7151 100       20281 $self->{user_agent} = defined $user_agent ? $user_agent : q{};
493 7151         18636 $self->_init_core;
494              
495 7151         59588 return $self;
496             }
497              
498             ### Accessors for computed-on-demand test attributes
499              
500             foreach my $test ( @ENGINE_TESTS, @MISC_TESTS ) {
501             ## no critic (TestingAndDebugging::ProhibitNoStrict)
502 5     5   39 no strict 'refs';
  5         16  
  5         574  
503             *{$test} = sub {
504 22987     22987   1430169 my ($self) = @_;
505 22987   100     73634 return $self->{tests}->{$test} || 0;
506             };
507             }
508              
509             foreach my $test (
510             @OS_TESTS, @WINDOWS_TESTS, @MAC_TESTS, @UNIX_TESTS,
511             @BSD_TESTS, @GAMING_TESTS
512             ) {
513             ## no critic (TestingAndDebugging::ProhibitNoStrict)
514 5     5   33 no strict 'refs';
  5         9  
  5         472  
515             *{$test} = sub {
516 50566     50566   7168792 my ($self) = @_;
517 50566 100       113555 $self->_init_os() unless $self->{os_tests};
518 50566   100     157169 return $self->{os_tests}->{$test} || 0;
519             };
520             }
521              
522             foreach my $test ( @BROWSER_TESTS, @FIREFOX_TESTS ) {
523             ## no critic (TestingAndDebugging::ProhibitNoStrict)
524 5     5   30 no strict 'refs';
  5         10  
  5         476  
525             *{$test} = sub {
526 58115     58115   4512160 my ($self) = @_;
527 58115   100     192410 return $self->{browser_tests}->{$test} || 0;
528             };
529             }
530              
531             foreach my $test (@OLD_ROBOT_TESTS) {
532             ## no critic (TestingAndDebugging::ProhibitNoStrict)
533 5     5   31 no strict 'refs';
  5         7  
  5         510  
534              
535             # For the 'robot' test, we return undef instead of 0 if it's
536             # false, to match os() and browser() and the like.
537             my $false_result = ( $test eq 'robot' ? undef : 0 );
538              
539             *{$test} = sub {
540 30595     30595   3575249 my ($self) = @_;
541 30595 100       65384 $self->_init_robots() unless $self->{robot_tests};
542 30595   66     98636 return $self->{robot_tests}->{$test} || $false_result;
543             };
544             }
545              
546             foreach my $test (
547             @NETSCAPE_TESTS, @IE_TESTS, @AOL_TESTS,
548             @OPERA_TESTS
549             ) {
550             ## no critic (TestingAndDebugging::ProhibitNoStrict)
551 5     5   40 no strict 'refs';
  5         33  
  5         452  
552             *{$test} = sub {
553 24330     24330   3461321 my ($self) = @_;
554 24330 100       53733 $self->_init_version() unless $self->{version_tests};
555 24330   100     79515 return $self->{version_tests}->{$test} || 0;
556             };
557             }
558              
559             foreach my $test (@DEVICE_TESTS) {
560             ## no critic (TestingAndDebugging::ProhibitNoStrict)
561 5     5   41 no strict 'refs';
  5         17  
  5         66530  
562             *{$test} = sub {
563 22190     22190   1905089 my ($self) = @_;
564 22190 100       61652 $self->_init_device() unless $self->{device_tests};
565 22190   100     92257 return $self->{device_tests}->{$test} || 0;
566             };
567             }
568              
569             sub user_agent {
570 1     1 1 7 my ( $self, $user_agent ) = @_;
571 1 50       8 if ( defined($user_agent) ) {
572 0         0 die
573             'Calling HTTP::BrowserDetect::user_agent() with an argument is no longer allowed; please use new().';
574             }
575             else {
576 1         20 return $self->{user_agent};
577             }
578             }
579              
580             ### Code for initializing various pieces of the object. Since it would
581             ### be needlessly slow if we examined every user agent for every piece
582             ### of information we might possibly need, we only initialize things
583             ### when they're actually queried about. Specifically:
584             ###
585             ### _init_core() sets up:
586             ### $self->{browser_tests}
587             ### $self->{browser}
588             ### $self->{browser_string}
589             ### $self->{tests}
590             ### $self->{engine_version}
591             ### $self->{realplayer_version}
592             ###
593             ### _init_version() sets up:
594             ### $self->{version_tests}
595             ### $self->{major}
596             ### $self->{minor}
597             ### $self->{beta}
598             ### $self->{realplayer_version}
599             ###
600             ### _init_os() sets up:
601             ### $self->{os}
602             ### $self->{os_string}
603             ### $self->{os_tests}
604             ### $self->{os_version}
605             ###
606             ### _init_os_version() sets up:
607             ### $self->{os_version}
608             ###
609             ### _init_device() sets up:
610             ### $self->{device_tests}
611             ### $self->{device}
612             ### $self->{device_string}
613             ###
614             ### _init_robots() sets up:
615             ### $self->{robot}
616             ### $self->{robot_tests}
617             ### $self->{robot_string}
618             ### $self->{robot_version}
619             ###
620              
621             # Private method -- Set up the basics (browser and misc attributes)
622             # for a new user-agent string
623             sub _init_core {
624 7151     7151   13037 my ( $self, $new_ua ) = @_;
625              
626 7151         21443 my $ua = lc $self->{user_agent};
627              
628             # any UA via Google Translate gets this appended
629 7151         15380 $ua =~ s{,gzip\(gfe\)\z}{};
630              
631             # These get filled in immediately
632 7151         13543 $self->{tests} = {};
633 7151         13619 $self->{browser_tests} = {};
634              
635 7151         13165 my $tests = $self->{tests};
636 7151         14061 my $browser_tests = $self->{browser_tests};
637 7151         10531 my $browser = undef;
638 7151         9318 my $browser_string = undef;
639              
640             # Detect engine
641 7151         11415 $self->{engine_version} = undef;
642              
643 7151 100 100     73926 if ( $ua =~ m{edge/([\d\.]+)} ) {
    100 66        
    100          
    100          
    100          
    100          
644 30         65 $tests->{edgehtml} = 1;
645 30         72 $self->{engine_version} = $1;
646             }
647             elsif ( $ua =~ /trident\/([\w\.\d]*)/ ) {
648 944         2256 $tests->{trident} = 1;
649 944         2841 $self->{engine_version} = $1;
650             }
651             elsif ( index( $ua, 'gecko' ) != -1 && index( $ua, 'like gecko' ) == -1 )
652             {
653 732         1480 $tests->{gecko} = 1;
654 732 100       4029 if ( $ua =~ /\([^)]*rv:([\w.\d]*)/ ) {
655 660         1872 $self->{engine_version} = $1;
656             }
657             }
658             elsif ( $ua =~ m{applewebkit/([\d.\+]+)} && not $tests->{edgehtml} ) {
659 2532         6213 $tests->{webkit} = 1;
660 2532         7219 $self->{engine_version} = $1;
661             }
662             elsif ( $ua =~ m{presto/([\d.]+)} ) {
663 138         515 $tests->{presto} = 1;
664 138         424 $self->{engine_version} = $1;
665             }
666             elsif ( $ua =~ m{khtml/([\d.]+)} ) {
667 12         27 $tests->{khtml} = 1;
668 12         36 $self->{engine_version} = $1;
669             }
670              
671             # Detect browser
672              
673 7151 100 100     162329 if ( index( $ua, 'galeon' ) != -1 ) {
    100 100        
    100 66        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100          
    100          
    100          
    50          
    50          
    100          
    50          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
674              
675             # Needs to go above firefox
676              
677 12         19 $browser = 'galeon';
678 12         22 $browser_tests->{galeon} = 1;
679             }
680             elsif ( index( $ua, 'epiphany' ) != -1 ) {
681              
682             # Needs to go above firefox + mozilla
683              
684 6         9 $browser = 'epiphany';
685 6         15 $browser_tests->{epiphany} = 1;
686             }
687             elsif ( $ua =~ m{(?:edg|edga|edgios)/[\d.]+} ) {
688 24         44 $browser = 'edge';
689 24         38 $browser_string = 'Edge';
690              
691 24         45 $browser_tests->{edge} = 1;
692             }
693             elsif ( $ua =~ m{edge/[\d.]+} ) {
694 30         52 $browser = 'edge';
695 30         39 $browser_string = 'Edge';
696              
697 30         56 $browser_tests->{edgelegacy} = 1;
698             }
699             elsif (
700             $ua =~ m{
701             (firebird|iceweasel|phoenix|namoroka|firefox|fxios)
702             \/
703             ( [^.]* ) # Major version number is everything before first dot
704             \. # The first dot
705             ( [\d]* ) # Minor version nnumber is digits after first dot
706             }xo
707             && index( $ua, 'not firefox' ) == -1
708             ) # Hack for Yahoo Slurp
709             {
710             # Browser is Firefox, possibly under an alternate name
711              
712 636         1077 $browser = 'firefox';
713 636 100       2033 $browser_string = ucfirst( $1 eq 'fxios' ? $browser : $1 );
714              
715 636         1316 $browser_tests->{$1} = 1;
716 636         887 $browser_tests->{firefox} = 1;
717             }
718             elsif ( $ua =~ m{opera|opr\/} ) {
719              
720             # Browser is Opera
721              
722 264         499 $browser = 'opera';
723 264         636 $browser_tests->{opera} = 1;
724             }
725             elsif ($tests->{trident}
726             || index( $ua, 'msie' ) != -1
727             || index( $ua, 'microsoft internet explorer' ) != -1 ) {
728              
729             # Browser is MSIE (possibly AOL branded)
730              
731 1730         3552 $browser = 'ie';
732 1730         3400 $browser_tests->{ie} = 1;
733              
734 1730 100 100     10072 if ( index( $ua, 'aol' ) != -1
735             || index( $ua, 'america online browser' ) != -1 ) {
736 42         69 $browser_string = 'AOL Browser';
737 42         81 $browser_tests->{aol} = 1;
738             }
739              
740             # Disabled for now -- need to figure out how to deal with version numbers
741             #elsif ( index ( $ua, 'acoobrowser' ) != -1 ) {
742             # $browser_string = 'Acoo Browser';
743             #}
744             #elsif ( index( $ua, 'avant' ) != -1 ) {
745             # $browser_string = 'Avant Browser';
746             #}
747             #elsif ( index( $ua, 'crazy browser' ) != -1 ) {
748             # $browser_string = 'Crazy Browser';
749             #}
750             #elsif ( index( $ua, 'deepnet explorer' ) != -1 ) {
751             # $browser_string = 'Deepnet Explorer';
752             #}
753             #elsif ( index( $ua, 'maxthon' ) != -1 ) {
754             # $browser_string = 'Maxthon';
755             #}
756             }
757             elsif ( index( $ua, 'brave' ) != -1 ) {
758              
759             # Has to go above Chrome, it includes 'like Chrome/'
760              
761 12         22 $browser = 'brave';
762 12         26 $browser_tests->{$browser} = 1;
763             }
764             elsif ( index( $ua, 'silk' ) != -1 ) {
765              
766             # Has to go above Chrome, it includes 'like Chrome/'
767              
768 72         127 $browser = 'silk';
769 72         141 $browser_tests->{$browser} = 1;
770             }
771             elsif ( index( $ua, 'samsungbrowser' ) != -1 ) {
772              
773             # Has to go above Chrome, it includes 'Chrome/'
774 6         11 $browser = 'samsung';
775 6         12 $browser_tests->{$browser} = 1;
776             }
777             elsif ( index( $ua, 'ucbrowser' ) != -1 ) {
778              
779             # Has to go above Safari, Mozilla and Chrome
780              
781 336         659 $browser = 'ucbrowser';
782 336         654 $browser_tests->{$browser} = 1;
783             }
784             elsif (index( $ua, 'chrome/' ) != -1
785             || index( $ua, 'crios' ) != -1 ) {
786              
787             # Browser is Chrome
788              
789 1362         2426 $browser = 'chrome';
790 1362         2075 $browser_tests->{chrome} = 1;
791              
792 1362 100       3224 if ( index( $ua, 'chromium' ) != -1 ) {
793 12         16 $browser_string = 'Chromium';
794             }
795             }
796             elsif (index( $ua, 'blackberry' ) != -1
797             || index( $ua, 'bb10' ) != -1
798             || index( $ua, 'rim tablet os' ) != -1 ) {
799              
800             # Has to go above Safari
801 42         71 $browser = 'blackberry'; # test gets set during device check
802             }
803             elsif (( index( $ua, 'safari' ) != -1 )
804             || ( index( $ua, 'applewebkit' ) != -1 ) ) {
805              
806             # Browser is Safari
807              
808 972         1954 $browser_tests->{safari} = 1;
809 972         1440 $browser = 'safari';
810 972 100 100     4073 if ( index( $ua, ' mobile safari/' ) != -1
811             || index( $ua, 'mobilesafari' ) != -1 ) {
812 276         414 $browser_string = 'Mobile Safari';
813 276         426 $browser_tests->{mobile_safari} = 1;
814             }
815 972 100       2313 if ( index( $ua, 'puffin' ) != -1 ) {
816 18         30 $browser_string = 'Puffin';
817             }
818             }
819             elsif ( index( $ua, 'neoplanet' ) != -1 ) {
820              
821             # Browser is Neoplanet
822              
823 0         0 $browser = 'ie';
824 0         0 $browser_tests->{$browser} = 1;
825 0         0 $browser_tests->{neoplanet} = 1;
826 0 0       0 $browser_tests->{neoplanet2} = 1 if ( index( $ua, '2.' ) != -1 );
827             }
828              
829             ## The following browsers all need to be tested for *before*
830             ## Mozilla, otherwise we'll think they are Mozilla because they
831             ## look very much like it.
832             elsif ( index( $ua, 'webtv' ) != -1 ) {
833 0         0 $browser = 'webtv';
834 0         0 $browser_tests->{$browser} = 1;
835             }
836             elsif ( index( $ua, 'nintendo 3ds' ) != -1 ) {
837 6         12 $browser = 'n3ds'; # Test gets set during device check
838             }
839             elsif ( index( $ua, 'nintendo dsi' ) != -1 ) {
840 0         0 $browser = 'dsi'; # Test gets set during device check
841             }
842             elsif (index( $ua, 'playstation 3' ) != -1
843             || index( $ua, 'playstation portable' ) != -1
844             || index( $ua, 'netfront' ) != -1 ) {
845 42         81 $browser = 'netfront';
846 42         87 $browser_tests->{$browser} = 1;
847             }
848             elsif ( index( $ua, 'browsex' ) != -1 ) {
849 6         16 $browser = 'browsex';
850 6         13 $browser_tests->{$browser} = 1;
851             }
852             elsif ( index( $ua, 'polaris' ) != -1 ) {
853 18         37 $browser = 'polaris';
854 18         38 $browser_tests->{$browser} = 1;
855             }
856              
857             ## At this point if it looks like Mozilla but we haven't found
858             ## anything else for it to be, it's probably Mozilla.
859             elsif (index( $ua, 'mozilla' ) != -1
860             && index( $ua, 'compatible' ) == -1 ) {
861              
862             # Browser is a Gecko-powered Netscape (i.e. Mozilla) version
863              
864 168         336 $browser = 'mozilla';
865 168 100 100     1154 if ( index( $ua, 'netscape' ) != -1
    100          
866             || !$tests->{gecko} ) {
867 108         189 $browser = 'netscape';
868             }
869             elsif ( index( $ua, 'seamonkey' ) != -1 ) {
870 6         10 $browser = 'seamonkey';
871             }
872 168         345 $browser_tests->{$browser} = 1;
873 168         274 $browser_tests->{netscape} = 1;
874 168         284 $browser_tests->{mozilla} = ( $tests->{gecko} );
875             }
876              
877             ## Long series of unlikely browsers (ones that don't look like Mozilla)
878             elsif ( index( $ua, 'staroffice' ) != -1 ) {
879 12         27 $browser = 'staroffice';
880 12         35 $browser_tests->{$browser} = 1;
881             }
882             elsif ( index( $ua, 'icab' ) != -1 ) {
883 6         9 $browser = 'icab';
884 6         99 $browser_tests->{$browser} = 1;
885             }
886             elsif ( index( $ua, 'lotus-notes' ) != -1 ) {
887 6         16 $browser = 'lotusnotes';
888 6         16 $browser_tests->{$browser} = 1;
889             }
890             elsif ( index( $ua, 'konqueror' ) != -1 ) {
891 24         63 $browser = 'konqueror';
892 24         53 $browser_tests->{$browser} = 1;
893             }
894             elsif ( index( $ua, 'lynx' ) != -1 ) {
895 6         15 $browser = 'lynx';
896 6         15 $browser_tests->{$browser} = 1;
897             }
898             elsif ( index( $ua, 'elinks' ) != -1 ) {
899 6         14 $browser = 'elinks';
900 6         15 $browser_tests->{$browser} = 1;
901             }
902             elsif ( index( $ua, 'links' ) != -1 ) {
903 12         29 $browser = 'links';
904 12         38 $browser_tests->{$browser} = 1;
905             }
906             elsif ( index( $ua, 'mosaic' ) != -1 ) {
907 0         0 $browser = 'mosaic';
908 0         0 $browser_tests->{$browser} = 1;
909             }
910             elsif ( index( $ua, 'emacs' ) != -1 ) {
911 6         12 $browser = 'emacs';
912 6         14 $browser_tests->{$browser} = 1;
913             }
914             elsif ( index( $ua, 'obigo' ) != -1 ) {
915 30         61 $browser = 'obigo';
916 30         72 $browser_tests->{$browser} = 1;
917             }
918             elsif ( index( $ua, 'teleca' ) != -1 ) {
919 54         118 $browser = 'obigo';
920 54         98 $browser_string = 'Teleca';
921 54         134 $browser_tests->{$browser} = 1;
922             }
923             elsif ( index( $ua, 'libcurl' ) != -1 || $ua =~ /^curl/ ) {
924 36         82 $browser = 'curl'; # Test gets set during robot check
925             }
926             elsif ( index( $ua, 'puf/' ) != -1 ) {
927 6         14 $browser = 'puf'; # Test gets set during robot check
928             }
929             elsif ( index( $ua, 'applecoremedia/' ) != -1 ) {
930 6         19 $browser = 'applecoremedia';
931 6         15 $browser_tests->{$browser} = 1;
932             }
933             elsif ( index( $ua, 'androiddownloadmanager' ) != -1 ) {
934 6         11 $browser = 'adm';
935 6         12 $browser_tests->{$browser} = 1;
936             }
937             elsif ( index( $ua, 'dalvik' ) != -1 ) {
938 12         24 $browser = 'dalvik';
939 12         26 $browser_tests->{$browser} = 1;
940             }
941             elsif ( index( $ua, 'apple-pubsub' ) != -1 ) {
942 6         11 $browser = 'pubsub';
943 6         17 $browser_tests->{$browser} = 1;
944             }
945             elsif ( index( $ua, 'imagesearcherpro' ) != -1 ) {
946 6         13 $browser = 'imagesearcherpro';
947 6         17 $browser_tests->{$browser} = 1;
948             }
949              
950 7151         13829 $self->{browser} = $browser;
951 7151 100 100     27076 $self->{browser_string} = $browser_string || $BROWSER_NAMES{$browser}
952             if defined $browser;
953              
954             # Other random tests
955              
956 7151 100       17155 $tests->{x11} = 1 if index( $ua, 'x11' ) != -1;
957 7151 100       16111 $tests->{dotnet} = 1 if index( $ua, '.net clr' ) != -1;
958              
959 7151 100       14990 if ( index( $ua, 'realplayer' ) != -1 ) {
960              
961             # Hack for Realplayer -- fix the version and 'real' browser
962              
963 18         72 $self->_init_version; # Set appropriate tests for whatever the 'real'
964             # browser is.
965              
966             # Now set the browser to Realplayer.
967 18         37 $self->{browser} = 'realplayer';
968 18         34 $self->{browser_string} = 'RealPlayer';
969 18         35 $browser_tests->{realplayer} = 1;
970              
971             # Now override the version with the Realplayer version (but leave
972             # alone the tests we already set, which might have been based on the
973             # 'real' browser's version).
974 18         40 $self->{realplayer_version} = undef;
975              
976 18 100       98 if ( $ua =~ /realplayer\/([\d+\.]+)/ ) {
    50          
977 12         37 $self->{realplayer_version} = $1;
978             ( $self->{major}, $self->{minor} )
979 12         47 = split( /\./, $self->{realplayer_version} );
980 12 50       59 $self->{minor} = ".$self->{minor}" if defined $self->{minor};
981             }
982             elsif ( $ua =~ /realplayer\s(\w+)/ ) {
983 6         21 $self->{realplayer_version} = $1;
984             }
985             }
986              
987 7151 100       16949 if ( index( $ua, '(r1 ' ) != -1 ) {
988              
989             # Realplayer plugin -- don't override browser but do set property
990 18         54 $browser_tests->{realplayer} = 1;
991             }
992              
993             # Details: https://developer.chrome.com/multidevice/user-agent#webview_user_agent
994 7151 100 100     16566 if ( ( $self->android && index( $ua, '; wv)' ) > 0 )
      100        
      100        
      100        
995             || ( $self->chrome && $self->android && $self->browser_major >= 30 ) )
996             {
997 456         980 $tests->{webview} = 1;
998             }
999              
1000             }
1001              
1002             # Any of these fragments within a user-agent generally indicates it's
1003             # a robot.
1004             my @ROBOT_FRAGMENTS = qw(
1005             agent
1006             analy
1007             appender
1008             babya
1009             checker
1010             bot
1011             copy
1012             crawl
1013             explorador
1014             fetch
1015             find
1016             ia_archive
1017             index
1018             netcraft
1019             reap
1020             resolver
1021             sleuth
1022             scan
1023             service
1024             spider
1025             thumbtack-thunderdome
1026             tiscali
1027             validator
1028             verif
1029             webcapture
1030             worm
1031             zyborg
1032             );
1033              
1034             my %ROBOT_FRAGMENT_EXCEPTIONS = (
1035             bot => ['cubot'],
1036             );
1037              
1038             sub _init_robots {
1039 8652     8652   12003 my $self = shift;
1040              
1041 8652         18781 my $ua = lc $self->{user_agent};
1042 8652         13159 my $tests = $self->{tests};
1043 8652         13452 my $browser_tests = $self->{browser_tests};
1044              
1045 8652         19575 my $robot_tests = $self->{robot_tests} = {};
1046 8652         18183 my $id;
1047             my $r;
1048              
1049 8652         0 my $robot_fragment; # The text that indicates it's a robot (we'll
1050             # use this later to detect robot version, and
1051             # maybe robot_string)
1052              
1053 8652 100 66     324703 if ( index( $ua, 'libwww-perl' ) != -1 || index( $ua, 'lwp-' ) != -1 ) {
    100 100        
    100 100        
    100 100        
    50 100        
    100 66        
    100          
    100          
    100          
    50          
    100          
    100          
    50          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    50          
    100          
    50          
    100          
    50          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    100          
1054 18         36 $r = 'lwp';
1055 18         35 $robot_tests->{lib} = 1;
1056 18 50       52 $robot_fragment = (
1057             ( index( $ua, 'libwww-perl' ) != -1 ) ? 'libwww-perl' : 'lwp-' );
1058             }
1059             elsif ( index( $ua, 'slurp' ) != -1 ) {
1060 12         22 $r = 'slurp';
1061 12         34 $robot_tests->{yahoo} = 1;
1062             }
1063             elsif (index( $ua, 'yahoo' ) != -1
1064             && index( $ua, 'jp.co.yahoo' ) == -1 ) {
1065 6         12 $r = 'yahoo';
1066             }
1067             elsif ( index( $ua, 'msnbot-mobile' ) != -1 ) {
1068 6         16 $r = 'msnmobile';
1069 6         12 $robot_tests->{msn} = 1;
1070 6         11 $robot_fragment = 'msnbot';
1071             }
1072             elsif ( index( $ua, 'bingbot-mobile' ) != -1 ) {
1073 0         0 $r = 'bingbot';
1074 0         0 $robot_tests->{bingbot} = 1;
1075 0         0 $robot_fragment = 'bingbot';
1076             }
1077             elsif ( index( $ua, 'msnbot' ) != -1 ) {
1078 6         13 $r = 'msn';
1079 6         11 $robot_fragment = 'msnbot';
1080             }
1081             elsif (index( $ua, 'binglocalsearch' ) != -1
1082             || index( $ua, 'bingbot' ) != -1
1083             || index( $ua, 'bingpreview' ) != -1 ) {
1084 30         59 $r = 'bingbot';
1085 30         66 $robot_tests->{bingbot} = 1;
1086 30         56 $robot_fragment = 'bingbot';
1087             }
1088             elsif ( index( $ua, 'microsoft office existence discovery' ) != -1 ) {
1089 6         12 $r = 'msoffice';
1090 6         12 $robot_fragment = 'office';
1091             }
1092             elsif ( index( $ua, 'ahrefsbot' ) != -1 ) {
1093 6         17 $r = 'ahrefs';
1094             }
1095             elsif ( index( $ua, 'altavista' ) != -1 ) {
1096 0         0 $r = 'altavista';
1097             }
1098             elsif ( index( $ua, 'apache-httpclient' ) != -1 ) {
1099 12         21 $r = 'apache';
1100             }
1101             elsif ( $ua =~ m{\( *\) *\{ *\: *\; *} ) {
1102              
1103             # Shellcode for spawning a process, i.e. (){:;} with some kind of whitespace interleaved
1104 6         13 $r = 'malware';
1105             }
1106             elsif ( index( $ua, 'ask jeeves/teoma' ) != -1 ) {
1107 0         0 $r = 'askjeeves';
1108 0         0 $robot_fragment = 'teoma';
1109             }
1110             elsif ( index( $ua, 'baiduspider' ) != -1 ) {
1111 18         31 $r = 'baidu';
1112             }
1113             elsif ( index( $ua, 'libcurl' ) != -1 || $ua =~ /^curl/ ) {
1114 36         66 $r = 'curl';
1115 36         71 $robot_tests->{lib} = 1;
1116             }
1117             elsif ( index( $ua, 'facebookexternalhit' ) != -1 ) {
1118 6         12 $r = 'facebook';
1119             }
1120             elsif ( index( $ua, 'getright' ) != -1 ) {
1121 6         17 $r = 'getright';
1122             }
1123             elsif ( index( $ua, 'adsbot-google' ) != -1 ) {
1124 12         24 $r = 'googleadsbot';
1125 12         22 $robot_tests->{google} = 1;
1126 12         19 $robot_fragment = 'adsbot-google';
1127             }
1128             elsif ( index( $ua, 'mediapartners-google' ) != -1 ) {
1129 6         11 $r = 'googleadsense';
1130 6         14 $robot_tests->{google} = 1;
1131 6         12 $robot_fragment = 'mediapartners-google';
1132             }
1133             elsif ( index( $ua, 'google favicon' ) != -1 ) {
1134 6         13 $r = 'googlefavicon';
1135 6         13 $robot_tests->{google} = 1;
1136 6         11 $robot_fragment = 'favicon';
1137             }
1138             elsif ( index( $ua, 'googlebot-image' ) != -1 ) {
1139 6         54 $r = 'googlebotimage';
1140 6         17 $robot_tests->{google} = 1;
1141 6         14 $robot_fragment = 'googlebot-image';
1142             }
1143             elsif ( index( $ua, 'googlebot-news' ) != -1 ) {
1144 6         12 $r = 'googlebotnews';
1145 6         19 $robot_tests->{google} = 1;
1146 6         15 $robot_fragment = 'googlebot-news';
1147             }
1148             elsif ( index( $ua, 'googlebot-video' ) != -1 ) {
1149 6         16 $r = 'googlebotvideo';
1150 6         15 $robot_tests->{google} = 1;
1151 6         13 $robot_fragment = 'googlebot-video';
1152             }
1153             elsif ( index( $ua, 'googlebot-mobile' ) != -1 ) {
1154 18         38 $r = 'googlemobile';
1155 18         41 $robot_tests->{google} = 1;
1156 18         31 $robot_fragment = 'googlebot-mobile';
1157             }
1158             elsif ( index( $ua, 'googlebot' ) != -1 ) {
1159 36         72 $r = 'google';
1160             }
1161             elsif ( $ua =~ m{go.*package http} ) {
1162 6         13 $r = 'golib';
1163 6         15 $robot_tests->{lib} = 1;
1164 6         13 $robot_fragment = 'package';
1165             }
1166             elsif ( $ua =~ m{^http_request} ) {
1167 6         16 $r = 'phplib';
1168 6         17 $robot_tests->{lib} = 1;
1169 6         14 $robot_fragment = 'http_request';
1170             }
1171             elsif ( $ua =~ m{^http_request} ) {
1172 0         0 $r = 'phplib';
1173 0         0 $robot_tests->{lib} = 1;
1174             }
1175             elsif ( index( $ua, 'indy library' ) != -1 ) {
1176 6         14 $r = 'indy';
1177 6         17 $robot_tests->{lib} = 1;
1178             }
1179             elsif ( index( $ua, 'infoseek' ) != -1 ) {
1180 0         0 $r = 'infoseek';
1181             }
1182             elsif ( index( $ua, 'ips-agent' ) != -1 ) {
1183 6         11 $r = 'ipsagent';
1184 6         9 $robot_fragment = 'ips-agent';
1185             }
1186             elsif ( index( $ua, 'lecodechecker' ) != -1 ) {
1187 0         0 $r = 'linkexchange';
1188 0         0 $robot_fragment = 'lecodechecker';
1189             }
1190             elsif ( index( $ua, 'linkchecker' ) != -1 ) {
1191 11         28 $r = 'linkchecker';
1192             }
1193             elsif ( index( $ua, 'lycos' ) != -1 ) {
1194 0         0 $r = 'lycos';
1195             }
1196             elsif ( index( $ua, 'mechanize' ) != -1 ) {
1197 12         24 $r = 'rubylib';
1198 12         27 $robot_tests->{lib} = 1;
1199 12         20 $robot_fragment = 'mechanize';
1200             }
1201             elsif ( index( $ua, 'mj12bot/' ) != -1 ) {
1202 6         16 $r = 'mj12bot';
1203             }
1204             elsif ( index( $ua, 'nutch' ) != -1 ) {
1205 18         34 $r = 'nutch';
1206             }
1207             elsif ( index( $ua, 'puf/' ) != -1 ) {
1208 6         15 $r = 'puf';
1209 6         17 $robot_tests->{lib} = 1;
1210             }
1211             elsif ( index( $ua, 'scooter' ) != -1 ) {
1212 0         0 $r = 'scooter';
1213             }
1214             elsif ( index( $ua, 'special_archiver' ) != -1 ) {
1215 6         16 $r = 'specialarchiver';
1216 6         14 $robot_fragment = 'special_archiver';
1217             }
1218             elsif ( index( $ua, 'wget' ) == 0 ) {
1219 12         22 $r = 'wget';
1220             }
1221             elsif ( index( $ua, 'yandexbot' ) != -1 ) {
1222 6         17 $r = 'yandex';
1223             }
1224             elsif ( index( $ua, 'yandeximages' ) != -1 ) {
1225 6         15 $r = 'yandeximages';
1226             }
1227             elsif ( index( $ua, 'headlesschrome' ) != -1 ) {
1228 6         17 $r = 'headlesschrome';
1229             }
1230             elsif ( $ua =~ m{^java} && !$self->{browser} ) {
1231 42         89 $r = 'java';
1232 42         108 $robot_tests->{lib} = 1;
1233             }
1234             elsif ( index( $ua, 'jdk' ) != -1 ) {
1235 0         0 $r = 'java';
1236 0         0 $robot_tests->{lib} = 1;
1237 0         0 $robot_fragment = 'jdk';
1238             }
1239             elsif ( index( $ua, 'jakarta commons-httpclient' ) != -1 ) {
1240 6         15 $r = 'java';
1241 6         17 $robot_tests->{lib} = 1;
1242 6         13 $robot_fragment = 'jakarta';
1243             }
1244             elsif ( index( $ua, 'google-http-java-client' ) != -1 ) {
1245 6         17 $r = 'java';
1246 6         15 $robot_tests->{lib} = 1;
1247 6         12 $robot_fragment = 'google';
1248             }
1249             elsif ( index( $ua, 'researchscan.comsys.rwth-aachen.de' ) != -1 ) {
1250 6         16 $r = 'researchscan';
1251             }
1252              
1253             # These @ROBOT_TESTS were added in 3.15. Some of them may need more
1254             # individualized treatment, but get them identified as bots for now.
1255              
1256             # XXX
1257             else {
1258             TEST:
1259 8209         20992 for my $set (@ROBOT_TESTS) {
1260 245478         321798 my $match = lc $set->[0];
1261              
1262 245478 100       454680 if ( index( $ua, lc($match) ) != -1 ) {
1263 48         77 $id = $set->[1];
1264 48         101 $r = $id;
1265 48         93 $robot_fragment = lc $match;
1266 48         121 last TEST;
1267             }
1268             }
1269             }
1270              
1271 8652 100 100     41210 if ( $browser_tests->{applecoremedia}
      100        
1272             || $browser_tests->{dalvik}
1273             || $browser_tests->{adm} ) {
1274 29         67 $robot_tests->{lib} = 1;
1275             }
1276              
1277 8652 100       47229 if ($r) {
    50          
    100          
    100          
1278              
1279             # Got a named robot
1280 491         931 $robot_tests->{$r} = 1;
1281 491 100       920 if ( !$id ) {
1282 443         964 $id = $ROBOT_IDS{$r};
1283             }
1284              
1285 491 50       944 if ( !exists $robot_tests->{robot_id} ) {
1286 491         834 $robot_tests->{robot_id} = $id;
1287             }
1288              
1289             # This isn't all keyed on ids (yet)
1290 491   33     1638 $self->{robot_string} = $ROBOT_NAMES{$id} || $ROBOT_NAMES{$r};
1291 491         783 $robot_tests->{robot} = $r;
1292 491 100       1038 $robot_fragment = $r if !defined $robot_fragment;
1293             }
1294             elsif ( $ua =~ /seek (?! mo (?: toolbar )? \s+ \d+\.\d+ )/x ) {
1295              
1296             # Store the fragment for later, to determine full name
1297 0         0 $robot_fragment = 'seek';
1298 0         0 $robot_tests->{robot} = 'unknown';
1299             }
1300             elsif ( $ua =~ /search (?! [\w\s]* toolbar \b | bar \b | erpro \b )/x ) {
1301              
1302             # Store the fragment for later, to determine full name
1303 42         91 $robot_fragment = 'search';
1304 42         107 $robot_tests->{robot} = 'unknown';
1305             }
1306             elsif ( $self->{user_agent} =~ /([\w \/\.\-]+)[ \;\(\)]*\+https?\:/i ) {
1307              
1308             # Something followed by +http
1309 245         1016 $self->{robot_string} = $1;
1310 245         2203 $self->{robot_string} =~ s/^ *(.+?)[ \;\(\)]*?( *\/[\d\.]+ *)?$/$1/;
1311 245         632 $robot_fragment = $1;
1312 245         564 $robot_tests->{robot} = 'unknown';
1313             }
1314             else {
1315             # See if we have a simple fragment
1316             FRAGMENT:
1317 7874         14805 for my $fragment (@ROBOT_FRAGMENTS) {
1318 207487 100       299250 if ( $ROBOT_FRAGMENT_EXCEPTIONS{$fragment} ) {
1319 7832         10723 for my $exception (
1320 7832 50       20019 @{ $ROBOT_FRAGMENT_EXCEPTIONS{$fragment} || [] } ) {
1321 7832 100       19420 if ( index( $ua, $exception ) != -1 ) {
1322 7         18 next FRAGMENT;
1323             }
1324             }
1325             }
1326              
1327 207480 100       356838 if ( index( $ua, $fragment ) != -1 ) {
1328 311         557 $robot_fragment = $fragment;
1329 311         629 $robot_tests->{robot} = 'unknown';
1330 311         581 last;
1331             }
1332             }
1333             }
1334              
1335 8652 100 100     24687 if ( exists $robot_tests->{robot} && $robot_tests->{robot} eq 'unknown' )
1336             {
1337 598         1320 $robot_tests->{robot_id} = 'unknown';
1338             }
1339              
1340 8652 100       16474 if ( defined $robot_fragment ) {
1341              
1342             # Examine what surrounds the fragment; that leads us to the
1343             # version and the string (if we haven't explicitly set one).
1344              
1345 1089 100       61277 if (
1346             $self->{user_agent} =~ m{\s* # Beginning whitespace
1347             ([\w .:,\-\@\/]* # Words before fragment
1348             $robot_fragment # Match the fragment
1349             [\w .:,\-\@\/]*) # Words after fragment
1350             }ix
1351             ) {
1352 1017         3420 my $full_string = $1;
1353 1017         5170 $full_string =~ s/ *$//; # Trim whitespace at end
1354 1017 100 100     4960 if (
      66        
1355             $self->{user_agent} eq $full_string
1356             && $self->{user_agent} =~ m{\/.*\/}
1357             && $self->{user_agent} =~ m{
1358             ([\w]* # Words before fragment
1359             $robot_fragment # Match the fragment
1360             (\/[\d\.]+)? # Version
1361             [\w]*) # Beta stuff
1362             }ix
1363             ) {
1364             # We matched the whole string, but it seems to
1365             # make more sense as whitespace-separated
1366             # 'thing/ver' tokens
1367 36         108 $full_string = $1;
1368             }
1369              
1370             # Figure out robot version based on the string
1371 1017 100 66     9139 if ( $full_string
1372             and $full_string =~ s/[\/ \.v]*(\d+)(\.\d+)?([\.\w]*)$// ) {
1373 652         2847 $self->{robot_version} = [ $1, $2, $3 ];
1374             }
1375             else {
1376 365         852 $self->{robot_version} = undef;
1377             }
1378              
1379             # Set robot_string, if we don't already have an explictly set
1380             # one
1381 1017 100       2751 if ( !defined $self->{robot_string} ) {
1382 353         848 $self->{robot_string} = $full_string;
1383             }
1384             }
1385             }
1386              
1387 8652 100       22998 if ( !exists( $self->{robot_version} ) ) {
1388 6117         13258 $self->{robot_version} = undef;
1389             }
1390             }
1391              
1392             ### OS tests, only run on demand
1393              
1394             sub _init_os {
1395 7146     7146   10867 my $self = shift;
1396              
1397 7146         10712 my $tests = $self->{tests};
1398 7146         10916 my $browser_tests = $self->{browser_tests};
1399 7146         14632 my $ua = lc $self->{user_agent};
1400              
1401 7146         14958 my $os_tests = $self->{os_tests} = {};
1402 7146         9478 my $os = undef;
1403 7146         8674 my $os_string = undef;
1404              
1405             # Windows
1406              
1407 7146 50       19300 if ( index( $ua, '16bit' ) != -1 ) {
1408 0         0 $os = 'windows';
1409 0         0 $os_string = '16-bit Windows';
1410 0         0 $os_tests->{win16} = $os_tests->{windows} = 1;
1411             }
1412              
1413 7146 100       17194 if ( index( $ua, 'win' ) != -1 ) {
1414 2958 100 66     52042 if ( index( $ua, 'win16' ) != -1
    100 66        
    100 100        
    100 100        
    100 100        
    100          
    100          
1415             || index( $ua, 'windows 3' ) != -1
1416             || index( $ua, 'windows 16-bit' ) != -1 ) {
1417 12         28 $os_tests->{win16} = 1;
1418 12         31 $os_tests->{win3x} = 1;
1419 12         22 $os = 'windows';
1420 12 100       37 if ( index( $ua, 'windows 3.1' ) != -1 ) {
1421 6         14 $os_tests->{win31} = 1;
1422 6         14 $os_string = 'Win3x'; # FIXME bug compatibility
1423             }
1424             else {
1425 6         13 $os_string = 'Win3x';
1426             }
1427             }
1428             elsif (index( $ua, 'win95' ) != -1
1429             || index( $ua, 'windows 95' ) != -1 ) {
1430 72         137 $os = 'windows';
1431 72         100 $os_string = 'Win95';
1432 72         208 $os_tests->{win95} = $os_tests->{win32} = 1;
1433             }
1434             elsif (
1435             index( $ua, 'win 9x 4.90' ) != -1 # whatever
1436             || index( $ua, 'windows me' ) != -1
1437             ) {
1438 30         46 $os = 'windows';
1439 30         35 $os_string = 'WinME';
1440 30         63 $os_tests->{winme} = $os_tests->{win32} = 1;
1441             }
1442             elsif (index( $ua, 'win98' ) != -1
1443             || index( $ua, 'windows 98' ) != -1 ) {
1444 24         46 $os = 'windows';
1445 24         33 $os_string = 'Win98';
1446 24         58 $os_tests->{win98} = $os_tests->{win32} = 1;
1447             }
1448             elsif ( index( $ua, 'windows 2000' ) != -1 ) {
1449 12         24 $os = 'windows';
1450 12         18 $os_string = 'Win2k';
1451 12         28 $os_tests->{win2k} = $os_tests->{winnt} = $os_tests->{win32} = 1;
1452             }
1453             elsif ( index( $ua, 'windows ce' ) != -1 ) {
1454 12         25 $os = 'windows';
1455 12         20 $os_string = 'WinCE';
1456 12         32 $os_tests->{wince} = 1;
1457             }
1458             elsif ( index( $ua, 'windows phone' ) != -1 ) {
1459 54         111 $os = 'winphone';
1460 54         113 $os_tests->{winphone} = 1;
1461              
1462 54 100       255 if ( index( $ua, 'windows phone os 7.0' ) != -1 ) {
    100          
    100          
    100          
    50          
1463 6         10 $os_tests->{winphone7} = 1;
1464             }
1465             elsif ( index( $ua, 'windows phone os 7.5' ) != -1 ) {
1466 12         32 $os_tests->{winphone7_5} = 1;
1467             }
1468             elsif ( index( $ua, 'windows phone 8.0' ) != -1 ) {
1469 12         22 $os_tests->{winphone8} = 1;
1470             }
1471             elsif ( index( $ua, 'windows phone 8.1' ) != -1 ) {
1472 18         32 $os_tests->{winphone8_1} = 1;
1473             }
1474             elsif ( index( $ua, 'windows phone 10.0' ) != -1 ) {
1475 6         15 $os_tests->{winphone10} = 1;
1476             }
1477             }
1478             }
1479              
1480 7146 100       16074 if ( index( $ua, 'nt' ) != -1 ) {
1481 3798 100 66     32582 if ( index( $ua, 'nt 5.0' ) != -1
    100 100        
    100 100        
    100 66        
    100          
    100          
    100          
    100          
    100          
1482             || index( $ua, 'nt5' ) != -1 ) {
1483 90         128 $os = 'windows';
1484 90         113 $os_string = 'Win2k';
1485 90         221 $os_tests->{win2k} = $os_tests->{winnt} = $os_tests->{win32} = 1;
1486             }
1487             elsif ( index( $ua, 'nt 5.1' ) != -1 ) {
1488 984         1777 $os = 'windows';
1489 984         1741 $os_string = 'WinXP';
1490 984         2999 $os_tests->{winxp} = $os_tests->{winnt} = $os_tests->{win32} = 1;
1491             }
1492             elsif ( index( $ua, 'nt 5.2' ) != -1 ) {
1493 126         222 $os = 'windows';
1494 126         209 $os_string = 'Win2k3';
1495 126         373 $os_tests->{win2k3} = $os_tests->{winnt} = $os_tests->{win32} = 1;
1496             }
1497             elsif ( index( $ua, 'nt 6.0' ) != -1 ) {
1498 204         353 $os = 'windows';
1499 204         321 $os_string = 'WinVista';
1500             $os_tests->{winvista} = $os_tests->{winnt} = $os_tests->{win32}
1501 204         582 = 1;
1502             }
1503             elsif ( index( $ua, 'nt 6.1' ) != -1 ) {
1504 966         1588 $os = 'windows';
1505 966         1350 $os_string = 'Win7';
1506 966         2581 $os_tests->{win7} = $os_tests->{winnt} = $os_tests->{win32} = 1;
1507             }
1508             elsif ( index( $ua, 'nt 6.2' ) != -1 ) {
1509 96         147 $os = 'windows';
1510 96         136 $os_string = 'Win8.0';
1511             $os_tests->{win8_0} = $os_tests->{win8} = $os_tests->{winnt}
1512 96         279 = $os_tests->{win32} = 1;
1513             }
1514             elsif ( index( $ua, 'nt 6.3' ) != -1 ) {
1515 78         115 $os = 'windows';
1516 78         100 $os_string = 'Win8.1';
1517             $os_tests->{win8_1} = $os_tests->{win8} = $os_tests->{winnt}
1518 78         193 = $os_tests->{win32} = 1;
1519             }
1520             elsif ( index( $ua, 'nt 10.0' ) != -1 ) {
1521 30         52 $os = 'windows';
1522 30         38 $os_string = 'Win10.0';
1523             $os_tests->{win10_0} = $os_tests->{win10} = $os_tests->{winnt}
1524 30         103 = $os_tests->{win32} = 1;
1525             }
1526             elsif (index( $ua, 'winnt' ) != -1
1527             || index( $ua, 'windows nt' ) != -1
1528             || index( $ua, 'nt4' ) != -1
1529             || index( $ua, 'nt3' ) != -1 ) {
1530 54         102 $os = 'windows';
1531 54         72 $os_string = 'WinNT';
1532 54         137 $os_tests->{winnt} = $os_tests->{win32} = 1;
1533             }
1534             }
1535              
1536 7146 100 100     103173 if ($os) {
    100 100        
    100 100        
    100 100        
    100 66        
    100 33        
    100 33        
    100 33        
    100 33        
    50 33        
    50 33        
    50 33        
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    100          
    100          
    50          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
1537              
1538             # windows, set through some path above
1539 2844         5079 $os_tests->{windows} = 1;
1540 2844 50       7533 $os_tests->{win32} = 1 if index( $ua, 'win32' ) != -1;
1541             }
1542             elsif ( index( $ua, 'macintosh' ) != -1 || index( $ua, 'mac_' ) != -1 ) {
1543              
1544             # Mac operating systems
1545 462         878 $os_tests->{mac} = 1;
1546 462 100       879 if ( index( $ua, 'mac os x' ) != -1 ) {
1547 438         587 $os = 'macosx';
1548 438         697 $os_tests->{$os} = 1;
1549             }
1550             else {
1551 24         40 $os = 'mac';
1552             }
1553 462 50 33     2949 if ( index( $ua, '68k' ) != -1 || index( $ua, '68000' ) != -1 ) {
    100 100        
1554 0         0 $os_tests->{mac68k} = 1;
1555             }
1556             elsif ( index( $ua, 'ppc' ) != -1 || index( $ua, 'powerpc' ) != -1 ) {
1557 126         189 $os_tests->{macppc} = 1;
1558             }
1559             }
1560             elsif (index( $ua, 'ipod' ) != -1
1561             || index( $ua, 'iphone' ) != -1
1562             || index( $ua, 'ipad' ) != -1 ) {
1563              
1564             # iOS
1565 378         639 $os = 'ios';
1566 378         811 $os_tests->{$os} = 1;
1567             }
1568             elsif ( index( $ua, 'android' ) != -1 ) {
1569              
1570             # Android
1571 966         1458 $os = 'android'; # Test gets set in the device testing
1572             }
1573             elsif ( index( $ua, 'inux' ) != -1 ) {
1574              
1575             # Linux
1576 492         718 $os = 'linux';
1577 492         1048 $os_tests->{linux} = $os_tests->{unix} = 1;
1578             }
1579             elsif ( $tests->{x11} && index( $ua, 'cros' ) != -1 ) {
1580              
1581             # ChromeOS
1582 42         69 $os = 'chromeos';
1583 42         97 $os_tests->{chromeos} = 1;
1584             }
1585             ## Long series of unlikely OSs
1586             elsif ( index( $ua, 'amiga' ) != -1 ) {
1587 6         10 $os = 'amiga';
1588 6         86 $os_tests->{$os} = 1;
1589             }
1590             elsif ( index( $ua, 'os/2' ) != -1 ) {
1591 6         14 $os = 'os2';
1592 6         17 $os_tests->{$os} = 1;
1593             }
1594             elsif ( index( $ua, 'solaris' ) != -1 ) {
1595 6         13 $os = 'unix';
1596 6         10 $os_string = 'Solaris';
1597 6         16 $os_tests->{sun} = $os_tests->{unix} = 1;
1598             }
1599             elsif ( index( $ua, 'samsung' ) == -1 && index( $ua, 'sun' ) != -1 ) {
1600 0         0 $os = 'unix';
1601 0         0 $os_string = 'SunOS';
1602 0         0 $os_tests->{sun} = $os_tests->{unix} = 1;
1603 0 0       0 $os_tests->{suni86} = 1 if index( $ua, 'i86' ) != -1;
1604 0 0       0 $os_tests->{sun4} = 1 if index( $ua, 'sunos 4' ) != -1;
1605 0 0       0 $os_tests->{sun5} = 1 if index( $ua, 'sunos 5' ) != -1;
1606             }
1607             elsif ( index( $ua, 'irix' ) != -1 ) {
1608 0         0 $os = 'unix';
1609 0         0 $os_string = 'Irix';
1610 0         0 $os_tests->{irix} = $os_tests->{unix} = 1;
1611 0 0       0 $os_tests->{irix5} = 1 if ( index( $ua, 'irix5' ) != -1 );
1612 0 0       0 $os_tests->{irix6} = 1 if ( index( $ua, 'irix6' ) != -1 );
1613             }
1614             elsif ( index( $ua, 'hp-ux' ) != -1 ) {
1615 0         0 $os = 'unix';
1616 0         0 $os_string = 'HP-UX';
1617 0         0 $os_tests->{hpux} = $os_tests->{unix} = 1;
1618 0 0       0 $os_tests->{hpux9} = 1 if index( $ua, '09.' ) != -1;
1619 0 0       0 $os_tests->{hpux10} = 1 if index( $ua, '10.' ) != -1;
1620             }
1621             elsif ( index( $ua, 'aix' ) != -1 ) {
1622 0         0 $os = 'unix';
1623 0         0 $os_string = 'AIX';
1624 0         0 $os_tests->{aix} = $os_tests->{unix} = 1;
1625 0 0       0 $os_tests->{aix1} = 1 if ( index( $ua, 'aix 1' ) != -1 );
1626 0 0       0 $os_tests->{aix2} = 1 if ( index( $ua, 'aix 2' ) != -1 );
1627 0 0       0 $os_tests->{aix3} = 1 if ( index( $ua, 'aix 3' ) != -1 );
1628 0 0       0 $os_tests->{aix4} = 1 if ( index( $ua, 'aix 4' ) != -1 );
1629             }
1630             elsif ( $ua =~ m{\bsco\b} || index( $ua, 'unix_sv' ) != -1 ) {
1631 0         0 $os = 'unix';
1632 0         0 $os_string = 'SCO Unix';
1633 0         0 $os_tests->{sco} = $os_tests->{unix} = 1;
1634             }
1635             elsif ( index( $ua, 'unix_system_v' ) != -1 ) {
1636 0         0 $os = 'unix';
1637 0         0 $os_string = 'System V Unix';
1638 0         0 $os_tests->{unixware} = $os_tests->{unix} = 1;
1639             }
1640             elsif ( $ua =~ m{\bncr\b} ) {
1641 0         0 $os = 'unix';
1642 0         0 $os_string = 'NCR Unix';
1643 0         0 $os_tests->{mpras} = $os_tests->{unix} = 1;
1644             }
1645             elsif ( index( $ua, 'reliantunix' ) != -1 ) {
1646 0         0 $os = 'unix';
1647 0         0 $os_string = 'Reliant Unix';
1648 0         0 $os_tests->{reliant} = $os_tests->{unix} = 1;
1649             }
1650             elsif (index( $ua, 'dec' ) != -1
1651             || index( $ua, 'osf1' ) != -1
1652             || index( $ua, 'declpha' ) != -1
1653             || index( $ua, 'alphaserver' ) != -1
1654             || index( $ua, 'ultrix' ) != -1
1655             || index( $ua, 'alphastation' ) != -1 ) {
1656 0         0 $os = 'unix';
1657 0         0 $os_tests->{dec} = $os_tests->{unix} = 1;
1658             }
1659             elsif ( index( $ua, 'sinix' ) != -1 ) {
1660 0         0 $os = 'unix';
1661 0         0 $os_tests->{sinix} = $os_tests->{unix} = 1;
1662             }
1663             elsif ( index( $ua, 'bsd' ) != -1 ) {
1664 30         58 $os = 'unix';
1665 30 50       211 if ( $self->{user_agent} =~ m{(\w*bsd\w*)}i ) {
1666 30         81 $os_string = $1;
1667             }
1668 30         72 $os_tests->{bsd} = $os_tests->{unix} = 1;
1669 30 50       85 $os_tests->{freebsd} = 1 if index( $ua, 'freebsd' ) != -1;
1670             }
1671             elsif ( $tests->{x11} ) {
1672              
1673             # Some Unix we didn't identify
1674 12         42 $os = 'unix';
1675 12         32 $os_tests->{unix} = 1;
1676             }
1677             elsif ( index( $ua, 'vax' ) != -1 || index( $ua, 'openvms' ) != -1 ) {
1678              
1679 0         0 $os = 'vms';
1680 0         0 $os_tests->{vms} = 1;
1681             }
1682             elsif ( index( $ua, 'bb10' ) != -1 ) {
1683 12         23 $os = 'bb10';
1684 12         24 $os_tests->{bb10} = 1;
1685             }
1686             elsif ( index( $ua, 'rim tablet os' ) != -1 ) {
1687 6         12 $os = 'rimtabletos';
1688 6         11 $os_tests->{rimtabletos} = 1;
1689             }
1690             elsif ( index( $ua, 'playstation 3' ) != -1 ) {
1691 6         12 $os = 'ps3gameos';
1692 6         14 $os_tests->{ps3gameos} = 1;
1693             }
1694             elsif ( index( $ua, 'playstation portable' ) != -1 ) {
1695 6         9 $os = 'pspgameos';
1696 6         15 $os_tests->{pspgameos} = 1;
1697             }
1698             elsif ( index( $ua, 'windows' ) != -1 ) {
1699              
1700             # Windows again, the super generic version
1701 54         145 $os_tests->{windows} = 1;
1702             }
1703             elsif ( index( $ua, 'win32' ) != -1 ) {
1704 18         53 $os_tests->{win32} = $os_tests->{windows} = 1;
1705             }
1706             elsif ( $self->{user_agent} =~ m{(brew)|(\bbmp\b)}i ) {
1707 144         313 $os = 'brew';
1708 144 100       465 if ($1) {
1709 102         164 $os_string = 'Brew';
1710             }
1711             else {
1712 42         70 $os_string = 'Brew MP';
1713             }
1714 144         313 $os_tests->{brew} = 1;
1715             }
1716             else {
1717 1656         3157 $os = undef;
1718             }
1719              
1720             # To deal with FirefoxOS we seem to have to load-on-demand devices
1721             # also, by calling ->mobile and ->tablet. We have to be careful;
1722             # if we ever created a loop back from _init_devices to _init_os
1723             # we'd run forever.
1724 7146 100 100     16558 if ( !$os
      66        
      100        
      100        
1725             && $browser_tests->{firefox}
1726             && index( $ua, 'fennec' ) == -1
1727             && ( $self->mobile || $self->tablet ) ) {
1728 12         17 $os = 'firefoxos';
1729 12         24 $os_tests->{firefoxos} = 1;
1730             }
1731              
1732 7146         13397 $self->{os} = $os;
1733 7146 100 100     21335 if ( $os and !$os_string ) {
1734 2460         4156 $os_string = $OS_NAMES{$os};
1735             }
1736 7146         17097 $self->{os_string} = $os_string;
1737             }
1738              
1739             sub _init_os_version {
1740 2527     2527   3717 my ($self) = @_;
1741              
1742 2527         5619 my $os = $self->os;
1743 2527         5343 my $os_string = $self->os_string;
1744 2527         5902 my $ua = lc $self->{user_agent};
1745              
1746 2527         3518 my $os_version = undef;
1747              
1748 2527 100       13518 if ( !defined $os ) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
1749              
1750             # Nothing is going to work if we have no OS. Skip everything.
1751             }
1752             elsif ( $os eq 'winphone' ) {
1753 24 50       186 if ( $ua =~ m{windows phone (?:os )?(\d+)(\.?\d*)([\.\d]*)} ) {
1754 24         96 $os_version = [ $1, $2, $3 ];
1755             }
1756             }
1757             elsif ( $os eq 'macosx' ) {
1758 190 100       1109 if ( $ua =~ m{os x (\d+)[\._](\d+)[\._]?(\d*)} ) {
1759 158 100       844 $os_version = [ $1, ".$2", length($3) ? ".$3" : q{} ];
1760             }
1761             }
1762             elsif ( $os eq 'ios' ) {
1763 171 100       1110 if ( $ua =~ m{ os (\d+)[\._ ](\d+)[\._ ]?(\d*)} ) {
1764 170 100       1025 $os_version = [ $1, ".$2", length($3) ? ".$3" : q{} ];
1765             }
1766             }
1767             elsif ( $os eq 'chromeos' ) {
1768 22 50       140 if ( $ua =~ m{ cros \S* (\d+)(\.?\d*)([\.\d]*)} ) {
1769 22         76 $os_version = [ $1, $2, $3 ];
1770             }
1771             }
1772             elsif ( $os eq 'android' ) {
1773 454 100       2763 if ( $ua =~ m{android (\d+)(\.?\d*)([\w\-\.]*)[\;\)]} ) {
1774 442         1673 $os_version = [ $1, $2, $3 ];
1775             }
1776             }
1777             elsif ( $os eq 'firefoxos' ) {
1778 6 50       43 if ( $ua =~ m{firefox/(\d+)(\.?\d*)([\.\d]*)} ) {
1779 6         24 $os_version = [ $1, $2, $3 ];
1780             }
1781             }
1782             elsif ( $os eq 'brew' ) {
1783 71 100       576 if ( $ua =~ m{(brew|\bbmp) (\d+)(\.?\d*)([\.\d]*)} ) {
1784 55         264 $os_version = [ $2, $3, $4 ];
1785             }
1786             }
1787              
1788             # Set the version. It might be set to undef, in which case we know
1789             # not to go through this next time.
1790 2527         5169 $self->{os_version} = $os_version;
1791             }
1792              
1793             ### Version determination, only run on demand
1794              
1795             sub _init_version {
1796 7108     7108   10443 my ($self) = @_;
1797              
1798 7108         17999 my $ua = lc $self->{user_agent};
1799 7108         10731 my $tests = $self->{tests};
1800 7108         9820 my $browser_tests = $self->{browser_tests};
1801 7108         10744 my $browser = $self->{browser};
1802              
1803 7108         12609 $self->{version_tests} = {};
1804 7108         11341 my $version_tests = $self->{version_tests};
1805              
1806 7108         10959 my ( $major, $minor, $beta );
1807              
1808             ### First figure out version numbers. First, we test if we're
1809             ### using a browser that needs some special method to determine
1810             ### the version.
1811              
1812 7108 100 100     81737 if ( defined $browser && $browser eq 'opera' ) {
    100 100        
    100 66        
    100 100        
    100 66        
    100 66        
    100 66        
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
1813              
1814             # Opera has a 'compatible; ' section, but lies sometimes. It needs
1815             # special handling.
1816              
1817             # http://dev.opera.com/articles/view/opera-ua-string-changes/
1818             # http://my.opera.com/community/openweb/idopera/
1819             # Opera/9.80 (S60; SymbOS; Opera Mobi/320; U; sv) Presto/2.4.15 Version/10.00
1820             # Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.52 Safari/537.36 OPR/15.0.1147.100
1821              
1822 264 100       2847 if ( $ua =~ m{\AOpera.*\sVersion/(\d*)\.(\d*)\z}i ) {
    100          
    50          
1823 90         248 $major = $1;
1824 90         208 $minor = $2;
1825             }
1826             elsif ( $ua =~ m{\bOPR/(\d+)\.(\d+)}i ) {
1827 48         122 $major = $1;
1828 48         95 $minor = $2;
1829             }
1830             elsif ( $ua =~ m{Opera[ /](\d+).(\d+)}i ) {
1831 126         337 $major = $1;
1832 126         252 $minor = $2;
1833             }
1834             }
1835             elsif ( $ua
1836             =~ m{\b compatible; \s* [\w\-]* [/\s] ( [0-9]+ ) (?: .([0-9]+) (\S*) )? ;}x
1837             ) {
1838             # MSIE and some others use a 'compatible' format
1839 1883         8567 ( $major, $minor, $beta ) = ( $1, $2, $3 );
1840             }
1841             elsif ( !$browser ) {
1842              
1843             # Nothing else is going to work if $browser isn't defined; skip the
1844             # specific approaches and go straight to the generic ones.
1845             }
1846             elsif ( $browser_tests->{edge} ) {
1847 24         88 ( $major, $minor, $beta )
1848             = $ua =~ m{Edge/(\d+)(?:\.(\d+))?([\.\d]+)?}i;
1849 24 50       256 ( $major, $minor, $beta )
1850             = $ua =~ m{(?:Edg|EdgA|EdgiOS)/(\d+)(?:\.(\d+))?([\.\d]+)?}i
1851             unless defined $major;
1852             }
1853             elsif ( $browser_tests->{safari} ) {
1854              
1855             # Safari Version
1856              
1857 952 100       5776 if (
    100          
1858             0
1859             && $ua =~ m{ # Disabled for bug compatibility
1860             version/
1861             ( \d+ ) # Major version number is everything before first dot
1862             \. # First dot
1863             ( \d+ )? # Minor version number follows dot
1864             }x
1865             ) {
1866             # Safari starting with version 3.0 provides its own public version
1867             ( $major, $minor ) = ( $1, $2, undef );
1868             }
1869 0         0 elsif ( $ua =~ m{ safari/ ( \d+ (?: \.\d+ )* ) }x ) {
1870 874 50       4270 if ( my ( $safari_build, $safari_minor ) = split /\./, $1 ) {
1871 874         2495 $major = int( $safari_build / 100 );
1872 874         1545 $minor = int( $safari_build % 100 );
1873 874 100       2201 $beta = ".$safari_minor" if defined $safari_minor;
1874             }
1875             }
1876             elsif ( $ua =~ m{applewebkit\/([\d\.]{1,})}xi ) {
1877 66 50       370 if ( my ( $safari_build, $safari_minor ) = split /\./, $1 ) {
1878 66         202 $major = int( $safari_build / 100 );
1879 66         128 $minor = int( $safari_build % 100 );
1880 66 100       190 $beta = ".$safari_minor" if $safari_minor;
1881             }
1882             }
1883             }
1884             elsif ( $browser_tests->{fxios} ) {
1885 6         45 ( $major, $minor ) = $ua =~ m{ \b fxios/ (\d+) [.] (\d+) }x;
1886             }
1887             elsif ( $browser_tests->{ie} ) {
1888              
1889             # MSIE
1890              
1891 100 100       647 if ( $ua =~ m{\b msie \s ( [0-9\.]+ ) (?: [a-z]+ [a-z0-9]* )? ;}x ) {
    100          
1892              
1893             # Internet Explorer
1894 18         85 ( $major, $minor, $beta ) = split /\./, $1;
1895             }
1896             elsif ( $ua =~ m{\b rv: ( [0-9\.]+ ) \b}x ) {
1897              
1898             # MSIE masking as Gecko really well ;)
1899 71         324 ( $major, $minor, $beta ) = split /\./, $1;
1900             }
1901             }
1902             elsif ( $browser eq 'n3ds' ) {
1903 6 50       42 if ( $ua =~ m{Nintendo 3DS;.*\sVersion/(\d*)\.(\d*)}i ) {
1904 6         15 $major = $1;
1905 6         11 $minor = $2;
1906             }
1907             }
1908             elsif ( $browser eq 'browsex' ) {
1909 6 50       37 if ( $ua =~ m{BrowseX \((\d+)\.(\d+)([\d.]*)}i ) {
1910 6         19 $major = $1;
1911 6         10 $minor = $2;
1912 6         11 $beta = $3;
1913             }
1914             }
1915             elsif ( $ua =~ m{netscape6/(\d+)\.(\d+)([\d.]*)} ) {
1916              
1917             # Other cases get handled below, we just need this to skip the '6'
1918 6         16 $major = $1;
1919 6         10 $minor = $2;
1920 6         12 $beta = $3;
1921             }
1922             elsif ( $browser eq 'brave' ) {
1923              
1924             # Note: since 0.7.10, Brave has changed the branding
1925             # of GitHub's 'Electron' (http://electron.atom.io/) to 'Brave'.
1926             # This means the browser string has both 'brave/' (the browser)
1927             # and 'Brave/' (re-branded Electron) in it.
1928             # The generic section below looks at $self->{browser_string}, which is 'Brave'
1929             # (Electron) and not $self->{browser} which is 'brave'.
1930             # Caveat parser.
1931 12 50       76 if ( $ua =~ m{brave/(\d+)\.(\d+)([\d.]*)} ) {
1932 12         31 $major = $1;
1933 12         20 $minor = $2;
1934 12         21 $beta = $3;
1935             }
1936             }
1937             elsif ($browser eq 'chrome'
1938             && $ua =~ m{crios/(\d+)\.(\d+)([\d.]*)} ) {
1939 18         62 $major = $1;
1940 18         37 $minor = $2;
1941 18         43 $beta = $3;
1942             }
1943             elsif ($browser eq 'pubsub'
1944             && $ua =~ m{apple-pubsub/(\d+)\.?(\d+)?([\d.]*)} ) {
1945 6         15 $major = $1;
1946 6         11 $minor = $2;
1947 6         10 $beta = $3;
1948             }
1949             elsif ($browser eq 'obigo'
1950             && $self->{user_agent} =~ m{(obigo[\w\-]*|teleca)[\/ ]\w(\d+)(\w*)}i )
1951             {
1952 78         263 $major = $2;
1953 78         156 $minor = q{};
1954 78         163 $beta = $3;
1955             }
1956             elsif ($browser eq 'polaris'
1957             && $ua =~ m{polaris[ \/](\d+)\.?(\d+)?([\d\.]*)} ) {
1958 6         16 $major = $1;
1959 6         12 $minor = $2;
1960 6         11 $beta = $3;
1961             }
1962             elsif ($browser eq 'ucbrowser'
1963             && $ua =~ m{ucbrowser[\/ ]*(\d+)\.?(\d+)?([\d\.]*)} ) {
1964 335         917 $major = $1;
1965 335         605 $minor = $2;
1966 335         724 $beta = $3;
1967             }
1968             elsif ( $browser eq 'samsung' && $ua =~ m{samsungbrowser/(\d+)\.(\d+)\s} )
1969             {
1970 6         18 $major = $1;
1971 6         10 $minor = $2;
1972             }
1973              
1974             # If we didn't match a browser-specific test, we look for
1975             # '$browser/x.y.z'
1976 7108 100 100     21230 if ( !defined $major && defined $self->{browser_string} ) {
1977 2462         7087 my $version_index = index( $ua, lc "$self->{browser_string}/" );
1978 2462 100       4561 if ( $version_index != -1 ) {
1979 2265         5073 my $version_str
1980             = substr( $ua, $version_index + length($browser) );
1981 2265 100       11248 if ( $version_str =~ m{/(\d+)\.(\d+)([\w.]*)} ) {
1982 2253         5160 $major = $1;
1983 2253         3474 $minor = $2;
1984 2253         4220 $beta = $3;
1985             }
1986             }
1987             }
1988              
1989             # If that didn't work, we try 'Version/x.y.z'
1990 7108 100       15283 if ( !defined $major ) {
1991 1170 100       2960 if ( $ua =~ m{version/(\d+)\.(\d+)([\w.]*)} ) {
1992 24         72 $major = $1;
1993 24         40 $minor = $2;
1994 24         42 $beta = $3;
1995             }
1996             }
1997              
1998             # If that didn't work, we start guessing. Just grab
1999             # anything after a word and a slash.
2000 7108 100       11647 if ( !defined $major ) {
2001              
2002 1146         6557 ( $major, $minor, $beta ) = (
2003             $ua =~ m{
2004             \S+ # Greedily catch anything leading up to forward slash.
2005             \/ # Version starts with a slash
2006             [A-Za-z]* # Eat any letters before the major version
2007             ( [0-9]+ ) # Major version number is everything before the first dot
2008             \. # The first dot
2009             ([\d]* ) # Minor version number is every digit after the first dot
2010             # Throw away remaining numbers and dots
2011             ( [^\s]* ) # Beta version string is up to next space
2012             }x
2013             );
2014             }
2015              
2016             # If that didn't work, try even more generic.
2017 7108 100       14253 if ( !defined $major ) {
2018              
2019 324 50       1010 if ( $ua =~ /[A-Za-z]+\/(\d+)\;/ ) {
2020 0         0 $major = $1;
2021 0         0 $minor = 0;
2022             }
2023             }
2024              
2025             # If that didn't work, give up.
2026 7108 100       12567 $major = 0 if !$major;
2027 7108 100       12005 $minor = 0 if !$minor;
2028 7108 100 100     22535 $beta = undef if ( defined $beta && $beta eq q{} );
2029              
2030             # Now set version tests
2031              
2032 7108 100       15094 if ( $browser_tests->{netscape} ) {
2033              
2034             # Netscape browsers
2035 168 100       1483 $version_tests->{nav2} = 1 if $major == 2;
2036 168 100       381 $version_tests->{nav3} = 1 if $major == 3;
2037 168 100       369 $version_tests->{nav4} = 1 if $major == 4;
2038 168 100       448 $version_tests->{nav4up} = 1 if $major >= 4;
2039 168 100 100     505 $version_tests->{nav45} = 1 if $major == 4 && $minor == 5;
2040 168 100 100     771 $version_tests->{nav45up} = 1
      100        
2041             if ( $major == 4 && ".$minor" >= .5 )
2042             || $major >= 5;
2043 168 100 100     424 $version_tests->{navgold} = 1
2044             if defined $beta && ( index( $beta, 'gold' ) != -1 );
2045 168 100 100     521 $version_tests->{nav6} = 1
2046             if ( $major == 5 || $major == 6 ); # go figure
2047 168 100       373 $version_tests->{nav6up} = 1 if $major >= 5;
2048              
2049 168 100       353 if ( $browser eq 'seamonkey' ) {
2050              
2051             # Ugh, seamonkey versions started back at 1.
2052 6         11 $version_tests->{nav2} = 0;
2053 6         10 $version_tests->{nav4up} = 1;
2054 6         11 $version_tests->{nav45up} = 1;
2055 6         10 $version_tests->{nav6} = 1;
2056 6         13 $version_tests->{nav6up} = 1;
2057             }
2058             }
2059              
2060 7108 100       13157 if ( $browser_tests->{ie} ) {
2061 1708 100       4980 $version_tests->{ie3} = 1 if ( $major == 3 );
2062 1708 100       4282 $version_tests->{ie4} = 1 if ( $major == 4 );
2063 1708 100       5769 $version_tests->{ie4up} = 1 if ( $major >= 4 );
2064 1708 100       3581 $version_tests->{ie5} = 1 if ( $major == 5 );
2065 1708 100       4151 $version_tests->{ie5up} = 1 if ( $major >= 5 );
2066 1708 100 100     4262 $version_tests->{ie55} = 1 if ( $major == 5 && $minor == 5 );
2067 1708 100 100     10616 $version_tests->{ie55up} = 1 if ( ".$minor" >= .5 || $major >= 6 );
2068 1708 100       4162 $version_tests->{ie6} = 1 if ( $major == 6 );
2069 1708 100       3480 $version_tests->{ie7} = 1 if ( $major == 7 );
2070 1708 100       3911 $version_tests->{ie8} = 1 if ( $major == 8 );
2071 1708 100       3604 $version_tests->{ie9} = 1 if ( $major == 9 );
2072 1708 100       3262 $version_tests->{ie10} = 1 if ( $major == 10 );
2073 1708 100       3897 $version_tests->{ie11} = 1 if ( $major == 11 );
2074              
2075             $version_tests->{ie_compat_mode}
2076             = ( $version_tests->{ie7}
2077             && $tests->{trident}
2078 1708   100     5210 && defined $self->engine_version
2079             && $self->engine_version >= 4 );
2080             }
2081              
2082 7108 100       15620 if ( $browser_tests->{aol} ) {
2083             $version_tests->{aol3} = 1
2084             if ( index( $ua, 'aol 3.0' ) != -1
2085 41 100 66     221 || $version_tests->{ie3} );
2086             $version_tests->{aol4} = 1
2087             if ( index( $ua, 'aol 4.0' ) != -1 )
2088 41 50 33     192 || $version_tests->{ie4};
2089 41 50       116 $version_tests->{aol5} = 1 if index( $ua, 'aol 5.0' ) != -1;
2090 41 100       107 $version_tests->{aol6} = 1 if index( $ua, 'aol 6.0' ) != -1;
2091 41 50       105 $version_tests->{aoltv} = 1 if index( $ua, 'navio' ) != -1;
2092             }
2093              
2094 7108 100       13687 if ( $browser_tests->{opera} ) {
2095 264 100 66     1686 $version_tests->{opera3} = 1
2096             if index( $ua, 'opera 3' ) != -1 || index( $ua, 'opera/3' ) != -1;
2097 264 50 66     1466 $version_tests->{opera4} = 1
      33        
2098             if ( index( $ua, 'opera 4' ) != -1 )
2099             || ( index( $ua, 'opera/4' ) != -1
2100             && ( index( $ua, 'nintendo dsi' ) == -1 ) );
2101 264 50 33     1340 $version_tests->{opera5} = 1
2102             if ( index( $ua, 'opera 5' ) != -1 )
2103             || ( index( $ua, 'opera/5' ) != -1 );
2104 264 100 66     1445 $version_tests->{opera6} = 1
2105             if ( index( $ua, 'opera 6' ) != -1 )
2106             || ( index( $ua, 'opera/6' ) != -1 );
2107 264 100 100     1313 $version_tests->{opera7} = 1
2108             if ( index( $ua, 'opera 7' ) != -1 )
2109             || ( index( $ua, 'opera/7' ) != -1 );
2110              
2111             }
2112              
2113 7108         14105 $minor = ".$minor";
2114              
2115 7108         16564 $self->{major} = $major;
2116 7108         14439 $self->{minor} = $minor;
2117 7108         14783 $self->{beta} = $beta;
2118             }
2119              
2120             ### Device tests, only run on demand
2121              
2122             sub _init_device {
2123 8222     8222   13324 my ($self) = @_;
2124              
2125 8222         18238 my $ua = lc $self->{user_agent};
2126 8222         12127 my $browser_tests = $self->{browser_tests};
2127 8222         11986 my $tests = $self->{tests};
2128              
2129 8222         10318 my ( $device, $device_string );
2130 8222         15971 my $device_tests = $self->{device_tests} = {};
2131              
2132 8222 100 100     258523 if ( index( $ua, 'windows phone' ) != -1 ) {
    100 100        
    100 100        
    100 100        
    100 100        
    100 66        
    100 100        
    100 66        
    50 100        
    50 66        
    50 100        
    50 66        
    100 66        
    100 66        
    100 66        
    100 100        
    100 66        
      66        
      66        
      66        
      66        
2133 54         82 $device = 'winphone';
2134              
2135             # Test is set in _init_os()
2136             }
2137             elsif (index( $ua, 'android' ) != -1
2138             || index( $ua, 'silk-accelerated' ) != -1 ) {
2139              
2140             # Silk-accelerated indicates a 1st generation Kindle Fire,
2141             # which may not have other indications of being an Android
2142             # device.
2143 972         1352 $device = 'android';
2144 972         1697 $device_tests->{$device} = 1;
2145             }
2146             elsif (index( $ua, 'blackberry' ) != -1
2147             || index( $ua, 'bb10' ) != -1
2148             || index( $ua, 'rim tablet os' ) != -1 ) {
2149 42         64 $device = 'blackberry';
2150 42         77 $device_tests->{$device} = 1;
2151             }
2152             elsif ( index( $ua, 'ipod' ) != -1 ) {
2153 18         25 $device = 'ipod';
2154 18         40 $device_tests->{$device} = 1;
2155             }
2156             elsif ( index( $ua, 'ipad' ) != -1 ) {
2157 156         348 $device = 'ipad';
2158 156         457 $device_tests->{$device} = 1;
2159             }
2160             elsif ( index( $ua, 'iphone' ) != -1 ) {
2161 204         309 $device = 'iphone';
2162 204         377 $device_tests->{$device} = 1;
2163             }
2164             elsif ( index( $ua, 'webos' ) != -1 ) {
2165 6         33 $device = 'webos';
2166 6         16 $device_tests->{$device} = 1;
2167             }
2168             elsif ( index( $ua, 'kindle' ) != -1 ) {
2169 12         21 $device = 'kindle';
2170 12         25 $device_tests->{$device} = 1;
2171             }
2172             elsif ( index( $ua, 'audrey' ) != -1 ) {
2173 0         0 $device = 'audrey';
2174 0         0 $device_tests->{$device} = 1;
2175             }
2176             elsif ( index( $ua, 'i-opener' ) != -1 ) {
2177 0         0 $device = 'iopener';
2178 0         0 $device_tests->{$device} = 1;
2179             }
2180             elsif ( index( $ua, 'avantgo' ) != -1 ) {
2181 0         0 $device = 'avantgo';
2182 0         0 $device_tests->{$device} = 1;
2183 0         0 $device_tests->{palm} = 1;
2184             }
2185             elsif ( index( $ua, 'palmos' ) != -1 ) {
2186 0         0 $device = 'palm';
2187 0         0 $device_tests->{$device} = 1;
2188             }
2189             elsif ( index( $ua, 'playstation 3' ) != -1 ) {
2190 6         11 $device = 'ps3';
2191 6         12 $device_tests->{$device} = 1;
2192             }
2193             elsif ( index( $ua, 'playstation portable' ) != -1 ) {
2194 6         9 $device = 'psp';
2195 6         13 $device_tests->{$device} = 1;
2196             }
2197             elsif ( index( $ua, 'nintendo dsi' ) != -1 ) {
2198 6         14 $device = 'dsi';
2199 6         18 $device_tests->{$device} = 1;
2200             }
2201             elsif ( index( $ua, 'nintendo 3ds' ) != -1 ) {
2202 6         11 $device = 'n3ds';
2203 6         14 $device_tests->{$device} = 1;
2204             }
2205             elsif (
2206             $browser_tests->{obigo}
2207             || $browser_tests->{ucbrowser}
2208             || index( $ua, 'up.browser' ) != -1
2209             || ( index( $ua, 'nokia' ) != -1
2210             && index( $ua, 'windows phone' ) == -1 )
2211             || index( $ua, 'alcatel' ) != -1
2212             || $ua =~ m{\bbrew\b}
2213             || $ua =~ m{\bbmp\b}
2214             || index( $ua, 'ericsson' ) != -1
2215             || index( $ua, 'sie-' ) == 0
2216             || index( $ua, 'wmlib' ) != -1
2217             || index( $ua, ' wap' ) != -1
2218             || index( $ua, 'wap ' ) != -1
2219             || index( $ua, 'wap/' ) != -1
2220             || index( $ua, '-wap' ) != -1
2221             || index( $ua, 'wap-' ) != -1
2222             || index( $ua, 'wap' ) == 0
2223             || index( $ua, 'wapper' ) != -1
2224             || index( $ua, 'zetor' ) != -1
2225             ) {
2226 582         1317 $device = 'wap';
2227 582         1302 $device_tests->{$device} = 1;
2228             }
2229              
2230             $device_tests->{tablet} = (
2231             index( $ua, 'ipad' ) != -1
2232             || ( $browser_tests->{ie}
2233             && index( $ua, 'windows phone' ) == -1
2234             && index( $ua, 'arm' ) != -1 )
2235             || ( index( $ua, 'android' ) != -1
2236             && index( $ua, 'mobile' ) == -1
2237             && index( $ua, 'safari' ) != -1 )
2238 8222   66     251828 || ( $browser_tests->{firefox} && index( $ua, 'tablet' ) != -1 )
2239             || index( $ua, 'an10bg3' ) != -1
2240             || index( $ua, 'an10bg3dt' ) != -1
2241             || index( $ua, 'an10g2' ) != -1
2242             || index( $ua, 'an7bg3' ) != -1
2243             || index( $ua, 'an7dg3' ) != -1
2244             || index( $ua, 'an7dg3childpad' ) != -1
2245             || index( $ua, 'an7dg3st' ) != -1
2246             || index( $ua, 'an7fg3' ) != -1
2247             || index( $ua, 'an7g3' ) != -1
2248             || index( $ua, 'an8cg3' ) != -1
2249             || index( $ua, 'an8g3' ) != -1
2250             || index( $ua, 'an9g3' ) != -1
2251             || index( $ua, 'flyer' ) != -1
2252             || index( $ua, 'hp-tablet' ) != -1
2253             || index( $ua, 'jetstream' ) != -1
2254             || index( $ua, 'kindle' ) != -1
2255             || index( $ua, 'novo7' ) != -1
2256             || index( $ua, 'opera tablet' ) != -1
2257             || index( $ua, 'rim tablet' ) != -1
2258             || index( $ua, 'transformer' ) != -1
2259             || index( $ua, 'xoom' ) != -1
2260             );
2261              
2262 8222 100       18613 if ( !$device_tests->{tablet} ) {
2263             $device_tests->{mobile} = (
2264             ( $browser_tests->{firefox} && index( $ua, 'mobile' ) != -1 )
2265             || ( $browser_tests->{ie}
2266             && index( $ua, 'windows phone' ) == -1
2267             && index( $ua, 'arm' ) != -1 )
2268             || index( $ua, 'windows phone' ) != -1
2269             || index( $ua, 'up.browser' ) != -1
2270             || index( $ua, 'nokia' ) != -1
2271             || index( $ua, 'alcatel' ) != -1
2272             || index( $ua, 'ericsson' ) != -1
2273             || index( $ua, 'sie-' ) == 0
2274             || index( $ua, 'wmlib' ) != -1
2275             || index( $ua, ' wap' ) != -1
2276             || index( $ua, 'wap ' ) != -1
2277             || index( $ua, 'wap/' ) != -1
2278             || index( $ua, '-wap' ) != -1
2279             || index( $ua, 'wap-' ) != -1
2280             || index( $ua, 'wap' ) == 0
2281             || index( $ua, 'wapper' ) != -1
2282             || index( $ua, 'blackberry' ) != -1
2283             || index( $ua, 'mobile' ) != -1
2284             || index( $ua, 'palm' ) != -1
2285             || index( $ua, 'smartphone' ) != -1
2286             || index( $ua, 'windows ce' ) != -1
2287             || index( $ua, 'palmsource' ) != -1
2288             || index( $ua, 'iphone' ) != -1
2289             || index( $ua, 'ipod' ) != -1
2290             || index( $ua, 'ipad' ) != -1
2291             || ( index( $ua, 'opera mini' ) != -1
2292             && index( $ua, 'tablet' ) == -1 )
2293             || index( $ua, 'htc_' ) != -1
2294             || index( $ua, 'symbian' ) != -1
2295             || index( $ua, 'webos' ) != -1
2296             || index( $ua, 'samsung' ) != -1
2297             || index( $ua, 'zetor' ) != -1
2298             || index( $ua, 'android' ) != -1
2299             || index( $ua, 'symbos' ) != -1
2300             || index( $ua, 'opera mobi' ) != -1
2301             || index( $ua, 'fennec' ) != -1
2302             || $ua =~ m{\bbrew\b}
2303             || index( $ua, 'obigo' ) != -1
2304             || index( $ua, 'teleca' ) != -1
2305             || index( $ua, 'polaris' ) != -1
2306             || index( $ua, 'opera tablet' ) != -1
2307             || index( $ua, 'rim tablet' ) != -1
2308             || ( index( $ua, 'bb10' ) != -1
2309             && index( $ua, 'mobile' ) != -1 )
2310             || $device_tests->{psp}
2311             || $device_tests->{dsi}
2312 7891   100     347855 || $device_tests->{'n3ds'}
2313             );
2314             }
2315              
2316 8222 100 100     172782 if ( $browser_tests->{ucbrowser}
    100 100        
    100 100        
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
2317             && $self->{user_agent}
2318             =~ m{ucweb/2.0\s*\(([^\;\)]*\;){3,4}\s*([^\;\)]*?)\s*\)}i ) {
2319 276         909 $device_string = $2;
2320             }
2321             elsif ( $ua =~ /^(\bmot-[^ \/]+)/ ) {
2322 12         56 $device_string = substr $self->{user_agent}, 0, length $1;
2323 12         102 $device_string =~ s/^MOT-/Motorola /i;
2324             }
2325             elsif ( ( $browser_tests->{obigo} || index( $ua, 'brew' ) != -1 )
2326             && $self->{user_agent} =~ m{\d+x\d+ ([\d\w\- ]+?)( \S+\/\S+)*$}i ) {
2327 108         358 $device_string = $1;
2328             }
2329             elsif (
2330             $ua =~ /windows phone os [^\)]+ iemobile\/[^;]+; ([^;]+; [^;\)]+)/g )
2331             {
2332             # windows phone 7.x
2333             $device_string = substr $self->{user_agent},
2334 18         101 pos($ua) - length $1, length $1;
2335 18         73 $device_string =~ s/; / /;
2336             }
2337             elsif ( $ua
2338             =~ /windows phone [^\)]+ iemobile\/[^;]+; arm; touch; ([^;]+; [^;\)]+)/g
2339             ) {
2340             # windows phone 8.0
2341             $device_string = substr $self->{user_agent},
2342 12         52 pos($ua) - length $1, length $1;
2343 12         37 $device_string =~ s/; / /;
2344             }
2345             elsif (
2346             $ua =~ /windows phone 8[^\)]+ iemobile\/[^;]+; ([^;]+; [^;\)]+)/g ) {
2347              
2348             # windows phone 8.1
2349             $device_string = substr $self->{user_agent},
2350 18         77 pos($ua) - length $1, length $1;
2351 18         56 $device_string =~ s/; / /;
2352             }
2353             elsif ( $ua =~ /bb10; ([^;\)]+)/g ) {
2354             $device_string = 'BlackBerry ' . substr $self->{user_agent},
2355 12         54 pos($ua) - length $1, length $1;
2356 12         29 $device_string =~ s/Kbd/Q10/;
2357             }
2358             elsif ( $ua =~ /blackberry ([\w.]+)/ ) {
2359 6         21 $device_string = "BlackBerry $1";
2360             }
2361             elsif ( $ua =~ /blackberry(\d+)\// ) {
2362 18         60 $device_string = "BlackBerry $1";
2363             }
2364             elsif ( $ua =~ /silk-accelerated/ ) {
2365              
2366             # Only first generation Kindle Fires have that string
2367 12         21 $device_string = 'Kindle Fire';
2368 12         19 $device_tests->{kindlefire} = 1;
2369             }
2370             elsif ( $self->{user_agent} =~ /android .*\; ([^;]*) build/i ) {
2371 906         2393 my $model = $1;
2372 906 100 100     3209 if ( $model =~ m{^KF} || $model =~ m{kindle fire}i ) {
    100          
2373              
2374             # We might hit this even if tablet() is false, if we have
2375             # a Kindle Fire masquerading as a mobile device.
2376 54         82 $device_string = 'Kindle Fire';
2377 54         96 $device_tests->{kindlefire} = 1;
2378             }
2379             elsif ( $device_tests->{tablet} ) {
2380 78         213 $device_string = "Android tablet ($model)";
2381             }
2382             else {
2383 774         1840 $device_string = "Android ($model)";
2384             }
2385             }
2386             elsif ( $self->{user_agent}
2387             =~ /\b((alcatel|huawei|lg|nokia|samsung|sonyericsson)[\w\-]*)\//i ) {
2388 114         367 $device_string = $1;
2389             }
2390             elsif ( $self->{user_agent} =~ /CrKey/ ) {
2391 6         14 $device = 'chromecast';
2392 6         8 $device_string = 'Chromecast';
2393             }
2394             elsif ($device) {
2395 558         1428 $device_string = $DEVICE_NAMES{$device};
2396             }
2397             else {
2398 6146         11060 $device_string = undef;
2399             }
2400              
2401 8222 100       13174 if ($device) {
2402 2076         5954 $self->{device} = $device;
2403             }
2404             else {
2405             $self->{device}
2406 6146         14405 = undef; # Means we cache the fact that we found nothing
2407             }
2408              
2409 8222 100       19148 if ($device_string) {
2410 2070         5054 $self->{device_string} = $device_string;
2411             }
2412             }
2413              
2414             ### Now a big block of public accessors for tests and information
2415              
2416             sub browser {
2417 1705     1705 1 734578 my ($self) = @_;
2418 1705 50       4846 return undef unless defined $self->{user_agent};
2419 1705         7011 return $self->{browser};
2420             }
2421              
2422             sub browser_string {
2423 1673     1673 1 707256 my ($self) = @_;
2424 1673 50       4301 return undef unless defined $self->{user_agent};
2425 1673         5671 return $self->{browser_string};
2426             }
2427              
2428             sub robot {
2429 2234     2234 1 1972665 my $self = shift;
2430              
2431 2234 100       9143 $self->_init_robots unless exists( $self->{robot_string} );
2432 2234         7568 return $self->{robot_tests}->{robot};
2433             }
2434              
2435             sub robot_string {
2436 888     888 1 120157 my $self = shift;
2437              
2438 888 100       3197 $self->_init_robots unless exists( $self->{robot_string} );
2439 888         2260 return $self->{robot_string};
2440             }
2441              
2442             sub robot_name {
2443 140     140 0 85139 my $self = shift;
2444 140         421 return $self->robot_string;
2445             }
2446              
2447             sub robot_id {
2448 546     546 1 1409 my $self = shift;
2449             return
2450             $self->{robot_tests}->{robot_id} ? $self->{robot_tests}->{robot_id}
2451 546 0       1765 : $self->robot ? $ROBOT_IDS{ $self->robot }
    50          
2452             : undef;
2453             }
2454              
2455             sub _robot_version {
2456 415     415   647 my ($self) = @_;
2457 415 50       1073 $self->_init_robots unless exists( $self->{robot_string} );
2458 415 50       883 if ( $self->{robot_version} ) {
2459 415         540 return @{ $self->{robot_version} };
  415         1680  
2460             }
2461             else {
2462 0         0 return ( undef, undef, undef );
2463             }
2464             }
2465              
2466             sub robot_version {
2467 106     106 1 62717 my ($self) = @_;
2468 106         243 my ( $major, $minor, $beta ) = $self->_robot_version;
2469 106 50       293 if ( defined $major ) {
2470 106 100       215 if ( defined $minor ) {
2471 105         538 return "$major$minor";
2472             }
2473             else {
2474 1         5 return $major;
2475             }
2476             }
2477             else {
2478 0         0 return undef;
2479             }
2480             }
2481              
2482             sub robot_major {
2483 102     102 1 49174 my ($self) = @_;
2484 102         254 my ( $major, $minor, $beta ) = $self->_robot_version;
2485 102         410 return $major;
2486             }
2487              
2488             sub robot_minor {
2489 101     101 1 45576 my ($self) = @_;
2490 101         245 my ( $major, $minor, $beta ) = $self->_robot_version;
2491 101         418 return $minor;
2492             }
2493              
2494             sub robot_beta {
2495 106     106 1 68288 my ($self) = @_;
2496 106         351 my ( $major, $minor, $beta ) = $self->_robot_version;
2497 106         440 return $beta;
2498             }
2499              
2500             sub os {
2501 4148     4148 1 619882 my ($self) = @_;
2502              
2503 4148 50       9388 return undef unless defined $self->{user_agent};
2504 4148 100       10460 $self->_init_os unless $self->{os_tests};
2505 4148         9442 return $self->{os};
2506             }
2507              
2508             sub os_string {
2509 4101     4101 1 612182 my ($self) = @_;
2510              
2511 4101 50       8943 return undef unless defined $self->{user_agent};
2512 4101 100       8151 $self->_init_os unless $self->{os_tests};
2513 4101         8458 return $self->{os_string};
2514             }
2515              
2516             sub _os_version {
2517 3978     3978   6623 my ($self) = @_;
2518 3978 100       13003 $self->_init_os_version if !exists( $self->{os_version} );
2519 3978 100       7016 if ( $self->{os_version} ) {
2520 1912         2550 return @{ $self->{os_version} };
  1912         7005  
2521             }
2522             else {
2523 2066         4992 return ( undef, undef, undef );
2524             }
2525             }
2526              
2527             sub os_version {
2528 1004     1004 1 248357 my ($self) = @_;
2529 1004         2366 my ( $major, $minor, $beta ) = $self->_os_version;
2530 1004 100       3569 return defined $major ? "$major$minor" : undef;
2531             }
2532              
2533             sub os_major {
2534 922     922 1 221273 my ($self) = @_;
2535 922         2217 my ( $major, $minor, $beta ) = $self->_os_version;
2536 922         2814 return $major;
2537             }
2538              
2539             sub os_minor {
2540 1034     1034 1 215688 my ($self) = @_;
2541 1034         3391 my ( $major, $minor, $beta ) = $self->_os_version;
2542 1034         2656 return $minor;
2543             }
2544              
2545             sub os_beta {
2546 1018     1018 1 267337 my ($self) = @_;
2547 1018         2520 my ( $major, $minor, $beta ) = $self->_os_version;
2548 1018         2751 return $beta;
2549             }
2550              
2551             sub _realplayer_version {
2552 0     0   0 my ($self) = @_;
2553              
2554 0 0       0 $self->_init_version unless $self->{version_tests};
2555 0   0     0 return $self->{realplayer_version} || 0;
2556             }
2557              
2558             sub realplayer_browser {
2559 752     752 1 88997 my ($self) = @_;
2560 752   100     3580 return defined( $self->{browser} ) && $self->{browser} eq 'realplayer';
2561             }
2562              
2563             sub gecko_version {
2564 710     710 1 81150 my ($self) = @_;
2565              
2566 710 100       1844 if ( $self->gecko ) {
2567 76         217 return $self->{engine_version};
2568             }
2569             else {
2570 634         1464 return undef;
2571             }
2572             }
2573              
2574             sub version {
2575 948     948 1 192865 my ($self) = @_;
2576 948 100       2777 $self->_init_version() unless $self->{version_tests};
2577              
2578 948 50       3854 return defined $self->{major} ? "$self->{major}$self->{minor}" : undef;
2579             }
2580              
2581             sub major {
2582 1150     1150 1 165505 my ($self) = @_;
2583 1150 100       2961 $self->_init_version() unless $self->{version_tests};
2584              
2585 1150         2290 my ($version) = $self->{major};
2586 1150         2993 return $version;
2587             }
2588              
2589             sub minor {
2590 969     969 1 192546 my ($self) = @_;
2591 969 100       2709 $self->_init_version() unless $self->{version_tests};
2592              
2593 969         1734 my ($version) = $self->{minor};
2594 969         2402 return $version;
2595             }
2596              
2597             sub public_version {
2598 919     919 1 178563 my ($self) = @_;
2599 919         2483 my ( $major, $minor ) = $self->_public;
2600              
2601 919   50     2254 $minor ||= q{};
2602 919 50       3336 return defined $major ? "$major$minor" : undef;
2603             }
2604              
2605             sub public_major {
2606 855     855 1 165266 my ($self) = @_;
2607 855         2312 my ( $major, $minor ) = $self->_public;
2608              
2609 855         2277 return $major;
2610             }
2611              
2612             sub public_minor {
2613 904     904 1 149974 my ($self) = @_;
2614 904         2329 my ( $major, $minor ) = $self->_public;
2615              
2616 904         2392 return $minor;
2617             }
2618              
2619             sub public_beta {
2620 0     0 1 0 my ($self) = @_;
2621 0         0 my ( $major, $minor, $beta ) = $self->_public;
2622              
2623 0         0 return $beta;
2624             }
2625              
2626             sub browser_version {
2627 6     6 1 3650 my ($self) = @_;
2628 6         19 my ( $major, $minor ) = $self->_public;
2629 6   100     22 $minor ||= q{};
2630              
2631 6 100       38 return defined $major ? "$major$minor" : undef;
2632             }
2633              
2634             sub browser_major {
2635 1441     1441 1 550421 my ($self) = @_;
2636 1441         3544 my ( $major, $minor ) = $self->_public;
2637              
2638 1441         7429 return $major;
2639             }
2640              
2641             sub browser_minor {
2642 895     895 1 535704 my ($self) = @_;
2643 895         2284 my ( $major, $minor ) = $self->_public;
2644              
2645 895         3610 return $minor;
2646             }
2647              
2648             sub browser_beta {
2649 353     353 1 220749 my ($self) = @_;
2650 353         955 my ( $major, $minor, $beta ) = $self->_public;
2651              
2652 353         1635 return $beta;
2653             }
2654              
2655             sub _public {
2656 5373     5373   8501 my ($self) = @_;
2657              
2658             # Return Public version of Safari. See RT #48727.
2659 5373 100       10531 if ( $self->safari ) {
2660 650         1946 my $ua = lc $self->{user_agent};
2661              
2662             # Safari starting with version 3.0 provides its own public version
2663 650 100       3428 if (
2664             $ua =~ m{
2665             version/
2666             ( \d+ ) # Major version number is everything before first dot
2667             ( \. \d+ )? # Minor version number is first dot and following digits
2668             }x
2669             ) {
2670 455         2128 return ( $1, $2, undef );
2671             }
2672              
2673             # Safari before version 3.0 had only build numbers; use a lookup table
2674             # provided by Apple to convert to version numbers
2675              
2676 195 100       942 if ( $ua =~ m{ safari/ ( \d+ (?: \.\d+ )* ) }x ) {
2677 146         388 my $build = $1;
2678 146         260 my $version = $safari_build_to_version{$build};
2679 146 100       330 unless ($version) {
2680              
2681             # if exact build -> version mapping doesn't exist, find next
2682             # lower build
2683              
2684 101         746 for my $maybe_build (
2685 7427         9760 sort { $self->_cmp_versions( $b, $a ) }
2686             keys %safari_build_to_version
2687             ) {
2688 461 100       700 $version = $safari_build_to_version{$maybe_build}, last
2689             if $self->_cmp_versions( $build, $maybe_build ) >= 0;
2690             }
2691              
2692             # Special case for specific worm that uses a malformed user agent
2693 101 100       411 return ( '1', '.2', undef ) if $ua =~ m{safari/12x};
2694             }
2695              
2696 140 100       295 return ( undef, undef, undef ) unless defined $version;
2697 137         399 my ( $major, $minor ) = split /\./, $version;
2698 137         223 my $beta;
2699 137 100       409 $minor =~ s/(\D.*)// and $beta = $1;
2700 137         267 $minor = ( '.' . $minor );
2701 137 100       554 return ( $major, $minor, ( $beta ? 1 : undef ) );
2702             }
2703             }
2704              
2705 4772 100       14678 $self->_init_version() unless $self->{version_tests};
2706 4772         16315 return ( $self->{major}, $self->{minor}, $self->{beta} );
2707             }
2708              
2709             sub _cmp_versions {
2710 7888     7888   10626 my ( $self, $a, $b ) = @_;
2711              
2712 7888         11701 my @a = split /\./, $a;
2713 7888         10191 my @b = split /\./, $b;
2714              
2715 7888         11443 while (@b) {
2716 9019 100 100     25355 return -1 if @a == 0 || $a[0] < $b[0];
2717 4713 100 66     13017 return 1 if @b == 0 || $b[0] < $a[0];
2718 1440         1645 shift @a;
2719 1440         2284 shift @b;
2720             }
2721              
2722 309         532 return @a <=> @b;
2723             }
2724              
2725             sub engine {
2726 1553     1553 1 601406 my ($self) = @_;
2727              
2728             return
2729 1553 100       3649 !$self->engine_string ? undef
    100          
2730             : $self->engine_string eq 'MSIE' ? 'ie'
2731             : lc( $self->engine_string );
2732             }
2733              
2734             sub engine_string {
2735 5538     5538 1 544361 my ($self) = @_;
2736              
2737 5538 100       10904 if ( $self->gecko ) {
2738 740         2359 return 'Gecko';
2739             }
2740              
2741 4798 100       8822 if ( $self->trident ) {
2742 978         3810 return 'Trident';
2743             }
2744              
2745 3820 100       6439 if ( $self->ie ) {
2746 558         2227 return 'MSIE';
2747             }
2748              
2749 3262 100       5417 if ( $self->edgelegacy ) {
2750 32         107 return 'EdgeHTML';
2751             }
2752              
2753 3230 100       5490 if ( $self->webkit ) {
2754 2632         8935 return 'WebKit';
2755             }
2756              
2757 598 100       1318 if ( $self->presto ) {
2758 140         504 return 'Presto';
2759             }
2760              
2761 458 100       865 if ( $self->netfront ) {
2762 53         171 return 'NetFront';
2763             }
2764              
2765 405 100       830 if ( $self->khtml ) {
2766 16         58 return 'KHTML';
2767             }
2768              
2769 389         936 return undef;
2770             }
2771              
2772             sub engine_version {
2773 1578     1578 1 447959 my ($self) = @_;
2774              
2775             return $self->{engine_version}
2776 1578 100 100     15372 && $self->{engine_version} =~ m{^(\d+(\.\d+)?)} ? $1 : undef;
2777             }
2778              
2779             sub engine_major {
2780 2190     2190 1 526204 my ($self) = @_;
2781              
2782             return $self->{engine_version}
2783 2190 100 100     16510 && $self->{engine_version} =~ m{^(\d+)} ? $1 : undef;
2784             }
2785              
2786             sub engine_minor {
2787 2093     2093 1 466230 my ($self) = @_;
2788              
2789             return $self->{engine_version}
2790 2093 100 100     16369 && $self->{engine_version} =~ m{^\d+(\.\d+)} ? $1 : undef;
2791             }
2792              
2793             sub engine_beta {
2794 1405     1405 1 500220 my ($self) = @_;
2795              
2796             return $self->{engine_version}
2797 1405 100 100     14197 && $self->{engine_version} =~ m{^\d+\.\d+([\.\d\+]*)} ? $1 : undef;
2798             }
2799              
2800             sub beta {
2801 735     735 1 89186 my ($self) = @_;
2802              
2803 735 100       2161 $self->_init_version unless $self->{version_tests};
2804              
2805 735         1449 my ($version) = $self->{beta};
2806 735         1411 return $version;
2807             }
2808              
2809             sub language {
2810 1100     1100 1 291204 my ($self) = @_;
2811              
2812 1100         2647 my $parsed = $self->_language_country();
2813 1100         3623 return $parsed->{'language'};
2814             }
2815              
2816             sub country {
2817 708     708 1 81917 my ($self) = @_;
2818              
2819 708         1799 my $parsed = $self->_language_country();
2820 708         2069 return $parsed->{'country'};
2821             }
2822              
2823             sub device {
2824 4129     4129 1 657197 my ($self) = @_;
2825              
2826 4129 50       9737 $self->_init_device if !exists( $self->{device} );
2827 4129         9598 return $self->{device};
2828             }
2829              
2830             sub device_string {
2831 1834     1834 1 298754 my ($self) = @_;
2832              
2833 1834 100       6202 $self->_init_device if !exists( $self->{device_string} );
2834 1834         4797 return $self->{device_string};
2835             }
2836              
2837             sub device_name {
2838 684     684 1 77459 my ($self) = @_;
2839 684         1514 return $self->device_string;
2840             }
2841              
2842             sub _language_country {
2843 1808     1808   3137 my ($self) = @_;
2844              
2845 1808 100       4068 if ( $self->safari ) {
2846 255 100 66     569 if ( $self->major == 1
2847             && $self->{user_agent} =~ m/\s ( [a-z]{2} ) \)/xms ) {
2848 8         39 return { language => uc $1 };
2849             }
2850 247 100       1230 if ( $self->{user_agent} =~ m/\s ([a-z]{2})-([A-Za-z]{2})/xms ) {
2851 147         862 return { language => uc $1, country => uc $2 };
2852             }
2853             }
2854              
2855 1653 100 100     3826 if ( $self->aol
2856             && $self->{user_agent} =~ m/;([A-Z]{2})_([A-Z]{2})\)/ ) {
2857 3         19 return { language => $1, country => $2 };
2858             }
2859              
2860 1650 100       7771 if ( $self->{user_agent} =~ m/\b([a-z]{2})-([A-Za-z]{2})\b/xms ) {
2861 342         1915 return { language => uc $1, country => uc $2 };
2862             }
2863              
2864 1308 100       3765 if ( $self->{user_agent} =~ m/\[([a-z]{2})\]/xms ) {
2865 23         121 return { language => uc $1 };
2866             }
2867              
2868 1285 100       6593 if ( $self->{user_agent} =~ m/\(([^)]+)\)/xms ) {
2869 1141         5321 my @parts = split( /;/, $1 );
2870 1141         2612 foreach my $part (@parts) {
2871              
2872             # 'wv' for WebView is not language code. Details here: https://developer.chrome.com/multidevice/user-agent#webview_user_agent
2873 4859 100 66     12751 if ( $part =~ /^\s*([a-z]{2})\s*$/
      100        
2874             && !( $self->webview && $1 eq 'wv' ) ) {
2875 147         769 return { language => uc $1 };
2876             }
2877             }
2878             }
2879              
2880 1138         3972 return { language => undef, country => undef };
2881             }
2882              
2883             sub browser_properties {
2884 1926     1926 1 441250 my ($self) = @_;
2885              
2886 1926         3363 my @browser_properties;
2887              
2888 1926         2897 my ( $test, $value );
2889              
2890 1926         3031 while ( ( $test, $value ) = each %{ $self->{tests} } ) {
  3664         12144  
2891 1738 50       4598 push @browser_properties, $test if $value;
2892             }
2893 1926         3290 while ( ( $test, $value ) = each %{ $self->{browser_tests} } ) {
  3680         9709  
2894 1754 100       4457 push @browser_properties, $test if $value;
2895             }
2896              
2897 1926 50       4239 $self->_init_device unless $self->{device_tests};
2898 1926 100       5120 $self->_init_os unless $self->{os_tests};
2899 1926 100       4846 $self->_init_robots unless $self->{robot_tests};
2900 1926 100       4334 $self->_init_version unless $self->{version_tests};
2901              
2902 1926         2831 while ( ( $test, $value ) = each %{ $self->{device_tests} } ) {
  6237         14744  
2903 4311 100       8276 push @browser_properties, $test if $value;
2904             }
2905 1926         2841 while ( ( $test, $value ) = each %{ $self->{os_tests} } ) {
  5779         12698  
2906 3853 50       7219 push @browser_properties, $test if $value;
2907             }
2908 1926         2912 while ( ( $test, $value ) = each %{ $self->{robot_tests} } ) {
  2688         6670  
2909 762 50       1509 push @browser_properties, $test if $value;
2910             }
2911 1926         2869 while ( ( $test, $value ) = each %{ $self->{version_tests} } ) {
  4357         9539  
2912 2431 100       4569 push @browser_properties, $test if $value;
2913             }
2914              
2915             # devices are a property too but it's not stored in %tests
2916             # so I explicitly test for it and add it
2917 1926 100       4979 push @browser_properties, 'device' if ( $self->device() );
2918              
2919 1926         8548 @browser_properties = sort @browser_properties;
2920 1926         6197 return @browser_properties;
2921             }
2922              
2923             sub lib {
2924 28     28 1 17278 my $self = shift;
2925 28 100       103 $self->_init_robots() unless $self->{robot_tests};
2926 28         112 return $self->{robot_tests}->{lib};
2927             }
2928              
2929             sub all_robot_ids {
2930 2     2 1 69 my $self = shift;
2931 2         32 return keys %ROBOT_NAMES;
2932             }
2933              
2934             # The list of U2F supported browsers is expected to increase:
2935             # https://www.bit-tech.net/news/tech/software/w3c-adopts-fidos-webauthn-standard/1/
2936              
2937             sub u2f {
2938 0     0 1 0 my $self = shift;
2939              
2940             # Chrome version 41 and up can U2F
2941 0 0 0     0 return 1
      0        
2942             if $self->chrome
2943             && $self->browser_major
2944             && $self->browser_major >= 41;
2945              
2946             # Opera versions 40 and >= 42 can U2F
2947 0 0 0     0 return 1
      0        
      0        
2948             if $self->opera
2949             && $self->browser_major
2950             && ( $self->browser_major == 40
2951             || $self->browser_major >= 42 );
2952              
2953 0         0 return undef;
2954             }
2955              
2956             # These method are only used by the test suite.
2957             sub _all_tests {
2958 1     1   5415703 return @ALL_TESTS;
2959             }
2960              
2961             sub _robot_names {
2962 1     1   41 return %ROBOT_NAMES;
2963             }
2964              
2965             sub _robot_tests {
2966 2     2   5594763 return @ROBOT_TESTS;
2967             }
2968              
2969             sub _robot_ids {
2970 1     1   20 return %ROBOT_IDS;
2971             }
2972              
2973             1;
2974              
2975             # ABSTRACT: Determine Web browser, version, and platform from an HTTP user agent string
2976              
2977             __END__