File Coverage

blib/lib/HTTP/BrowserDetect.pm
Criterion Covered Total %
statement 930 1027 90.5
branch 773 884 87.4
condition 333 417 79.8
subroutine 76 79 96.2
pod 46 47 97.8
total 2158 2454 87.9


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