File Coverage

blib/lib/HTTP/BrowserDetect.pm
Criterion Covered Total %
statement 930 1027 90.5
branch 773 884 87.4
condition 339 423 80.1
subroutine 76 79 96.2
pod 46 47 97.8
total 2164 2460 87.9


line stmt bran cond sub pod time code
1 5     5   72346 use strict;
  5         29  
  5         145  
2 5     5   27 use warnings;
  5         8  
  5         119  
3              
4 5     5   105 use 5.006;
  5         16  
5              
6             package HTTP::BrowserDetect;
7              
8             our $VERSION = '3.38';
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 7247     7247 1 5257260 my ( $class, $user_agent ) = @_;
489              
490 7247         15176 my $self = {};
491 7247         15451 bless $self, $class;
492              
493 7247 100       21715 unless ( defined $user_agent ) {
494 2         6 $user_agent = $ENV{'HTTP_USER_AGENT'};
495             }
496              
497 7247 100       23432 $self->{user_agent} = defined $user_agent ? $user_agent : q{};
498 7247         22671 $self->_init_core;
499              
500 7247         71544 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   39 no strict 'refs';
  5         18  
  5         627  
508             *{$test} = sub {
509 25672     25672   1774949 my ($self) = @_;
510 25672   100     96975 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   36 no strict 'refs';
  5         10  
  5         521  
520             *{$test} = sub {
521 50632     50632   8367630 my ($self) = @_;
522 50632 100       128379 $self->_init_os() unless $self->{os_tests};
523 50632   100     179318 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   44 no strict 'refs';
  5         21  
  5         431  
530             *{$test} = sub {
531 61584     61584   5513786 my ($self) = @_;
532 61584   100     245486 return $self->{browser_tests}->{$test} || 0;
533             };
534             }
535              
536             foreach my $test (@OLD_ROBOT_TESTS) {
537             ## no critic (TestingAndDebugging::ProhibitNoStrict)
538 5     5   32 no strict 'refs';
  5         12  
  5         622  
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 30674     30674   4199719 my ($self) = @_;
546 30674 100       74731 $self->_init_robots() unless $self->{robot_tests};
547 30674   66     112379 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         11  
  5         551  
557             *{$test} = sub {
558 24362     24362   4011634 my ($self) = @_;
559 24362 100       62823 $self->_init_version() unless $self->{version_tests};
560 24362   100     88362 return $self->{version_tests}->{$test} || 0;
561             };
562             }
563              
564             foreach my $test (@DEVICE_TESTS) {
565             ## no critic (TestingAndDebugging::ProhibitNoStrict)
566 5     5   34 no strict 'refs';
  5         8  
  5         79811  
567             *{$test} = sub {
568 22335     22335   2274103 my ($self) = @_;
569 22335 100       63207 $self->_init_device() unless $self->{device_tests};
570 22335   100     106463 return $self->{device_tests}->{$test} || 0;
571             };
572             }
573              
574             sub user_agent {
575 1     1 1 7 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         11 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 7247     7247   14517 my ( $self, $new_ua ) = @_;
630              
631 7247         21263 my $ua = lc $self->{user_agent};
632              
633             # any UA via Google Translate gets this appended
634 7247         17465 $ua =~ s{,gzip\(gfe\)\z}{};
635              
636             # These get filled in immediately
637 7247         15478 $self->{tests} = {};
638 7247         15164 $self->{browser_tests} = {};
639              
640 7247         13612 my $tests = $self->{tests};
641 7247         13810 my $browser_tests = $self->{browser_tests};
642 7247         11923 my $browser = undef;
643 7247         11808 my $browser_string = undef;
644              
645             # Detect engine
646 7247         13025 $self->{engine_version} = undef;
647              
648 7247 100 100     73689 if ( $ua =~ m{edge/([\d\.]+)} ) {
    100 66        
    100          
    100          
    100          
    100          
649 30         90 $tests->{edgehtml} = 1;
650 30         92 $self->{engine_version} = $1;
651             }
652             elsif ( $ua =~ /trident\/([\w\.\d]*)/ ) {
653 944         2356 $tests->{trident} = 1;
654 944         2946 $self->{engine_version} = $1;
655             }
656             elsif ( index( $ua, 'gecko' ) != -1 && index( $ua, 'like gecko' ) == -1 )
657             {
658 732         1889 $tests->{gecko} = 1;
659 732 100       4843 if ( $ua =~ /\([^)]*rv:([\w.\d]*)/ ) {
660 660         2402 $self->{engine_version} = $1;
661             }
662             }
663             elsif ( $ua =~ m{applewebkit/([\d.\+]+)} && not $tests->{edgehtml} ) {
664 2628         7022 $tests->{webkit} = 1;
665 2628         8248 $self->{engine_version} = $1;
666             }
667             elsif ( $ua =~ m{presto/([\d.]+)} ) {
668 138         355 $tests->{presto} = 1;
669 138         422 $self->{engine_version} = $1;
670             }
671             elsif ( $ua =~ m{khtml/([\d.]+)} ) {
672 12         34 $tests->{khtml} = 1;
673 12         37 $self->{engine_version} = $1;
674             }
675              
676             # Detect browser
677              
678 7247 100 100     176967 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         30 $browser = 'galeon';
683 12         30 $browser_tests->{galeon} = 1;
684             }
685             elsif ( index( $ua, 'epiphany' ) != -1 ) {
686              
687             # Needs to go above firefox + mozilla
688              
689 6         15 $browser = 'epiphany';
690 6         15 $browser_tests->{epiphany} = 1;
691             }
692             elsif ( $ua =~ m{(?:edg|edga|edgios)/[\d.]+} ) {
693 24         53 $browser = 'edge';
694 24         53 $browser_string = 'Edge';
695              
696 24         54 $browser_tests->{edge} = 1;
697             }
698             elsif ( $ua =~ m{edge/[\d.]+} ) {
699 30         57 $browser = 'edge';
700 30         61 $browser_string = 'Edge';
701              
702 30         60 $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         1461 $browser = 'firefox';
718 636 100       2762 $browser_string = ucfirst( $1 eq 'fxios' ? $browser : $1 );
719              
720 636         1694 $browser_tests->{$1} = 1;
721 636         1183 $browser_tests->{firefox} = 1;
722             }
723             elsif ( $self->{user_agent} =~ m{ \[ FB (?: AN | _IAB ) }x ) {
724 78   50     471 $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         289 $browser = lc $browser_string;
737 78         209 $browser =~ tr/ /_/;
738 78         172 $tests->{meta_app} = 1;
739             }
740             elsif ( $ua =~ m{opera|opr\/} ) {
741              
742             # Browser is Opera
743              
744 264         600 $browser = 'opera';
745 264         549 $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         3635 $browser = 'ie';
754 1730         3532 $browser_tests->{ie} = 1;
755              
756 1730 100 100     9023 if ( index( $ua, 'aol' ) != -1
757             || index( $ua, 'america online browser' ) != -1 ) {
758 42         71 $browser_string = 'AOL Browser';
759 42         87 $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         30 $browser = 'instagram';
781 12         31 $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         31 $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         160 $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         15 $browser = 'samsung';
801 6         15 $browser_tests->{$browser} = 1;
802             }
803             elsif ( index( $ua, 'ucbrowser' ) != -1 ) {
804              
805             # Has to go above Safari, Mozilla and Chrome
806              
807 336         700 $browser = 'ucbrowser';
808 336         756 $browser_tests->{$browser} = 1;
809             }
810             elsif ( 0 < index $self->{user_agent}, ' YaBrowser/' ) {
811 36         81 $browser = 'yandex_browser';
812 36         86 $browser_tests->{$browser} = 1;
813             }
814             elsif (index( $ua, 'chrome/' ) != -1
815             || index( $ua, 'crios' ) != -1 ) {
816              
817             # Browser is Chrome
818              
819 1344         3002 $browser = 'chrome';
820 1344         2690 $browser_tests->{chrome} = 1;
821              
822 1344 100       4025 if ( index( $ua, 'chromium' ) != -1 ) {
823 12         23 $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         102 $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         2643 $browser_tests->{safari} = 1;
839 960         1868 $browser = 'safari';
840 960 100 100     4415 if ( index( $ua, ' mobile safari/' ) != -1
841             || index( $ua, 'mobilesafari' ) != -1 ) {
842 276         527 $browser_string = 'Mobile Safari';
843 276         533 $browser_tests->{mobile_safari} = 1;
844             }
845 960 100       3110 if ( index( $ua, 'puffin' ) != -1 ) {
846 18         40 $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         14 $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         107 $browser = 'netfront';
876 42         107 $browser_tests->{$browser} = 1;
877             }
878             elsif ( index( $ua, 'browsex' ) != -1 ) {
879 6         16 $browser = 'browsex';
880 6         16 $browser_tests->{$browser} = 1;
881             }
882             elsif ( index( $ua, 'polaris' ) != -1 ) {
883 18         44 $browser = 'polaris';
884 18         48 $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         426 $browser = 'mozilla';
895 168 100 100     984 if ( index( $ua, 'netscape' ) != -1
    100          
896             || !$tests->{gecko} ) {
897 108         231 $browser = 'netscape';
898             }
899             elsif ( index( $ua, 'seamonkey' ) != -1 ) {
900 6         154 $browser = 'seamonkey';
901             }
902 168         469 $browser_tests->{$browser} = 1;
903 168         316 $browser_tests->{netscape} = 1;
904 168         321 $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         28 $browser = 'staroffice';
910 12         33 $browser_tests->{$browser} = 1;
911             }
912             elsif ( index( $ua, 'icab' ) != -1 ) {
913 6         14 $browser = 'icab';
914 6         20 $browser_tests->{$browser} = 1;
915             }
916             elsif ( index( $ua, 'lotus-notes' ) != -1 ) {
917 6         16 $browser = 'lotusnotes';
918 6         18 $browser_tests->{$browser} = 1;
919             }
920             elsif ( index( $ua, 'konqueror' ) != -1 ) {
921 24         60 $browser = 'konqueror';
922 24         59 $browser_tests->{$browser} = 1;
923             }
924             elsif ( index( $ua, 'lynx' ) != -1 ) {
925 6         15 $browser = 'lynx';
926 6         17 $browser_tests->{$browser} = 1;
927             }
928             elsif ( index( $ua, 'elinks' ) != -1 ) {
929 6         15 $browser = 'elinks';
930 6         15 $browser_tests->{$browser} = 1;
931             }
932             elsif ( index( $ua, 'links' ) != -1 ) {
933 12         26 $browser = 'links';
934 12         31 $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         14 $browser = 'emacs';
942 6         15 $browser_tests->{$browser} = 1;
943             }
944             elsif ( index( $ua, 'obigo' ) != -1 ) {
945 30         67 $browser = 'obigo';
946 30         87 $browser_tests->{$browser} = 1;
947             }
948             elsif ( index( $ua, 'teleca' ) != -1 ) {
949 54         125 $browser = 'obigo';
950 54         82 $browser_string = 'Teleca';
951 54         137 $browser_tests->{$browser} = 1;
952             }
953             elsif ( index( $ua, 'libcurl' ) != -1 || $ua =~ /^curl/ ) {
954 36         88 $browser = 'curl'; # Test gets set during robot check
955             }
956             elsif ( index( $ua, 'puf/' ) != -1 ) {
957 6         16 $browser = 'puf'; # Test gets set during robot check
958             }
959             elsif ( index( $ua, 'applecoremedia/' ) != -1 ) {
960 6         127 $browser = 'applecoremedia';
961 6         23 $browser_tests->{$browser} = 1;
962             }
963             elsif ( index( $ua, 'androiddownloadmanager' ) != -1 ) {
964 6         17 $browser = 'adm';
965 6         16 $browser_tests->{$browser} = 1;
966             }
967             elsif ( index( $ua, 'dalvik' ) != -1 ) {
968 12         28 $browser = 'dalvik';
969 12         28 $browser_tests->{$browser} = 1;
970             }
971             elsif ( index( $ua, 'apple-pubsub' ) != -1 ) {
972 6         32 $browser = 'pubsub';
973 6         23 $browser_tests->{$browser} = 1;
974             }
975             elsif ( index( $ua, 'imagesearcherpro' ) != -1 ) {
976 6         14 $browser = 'imagesearcherpro';
977 6         14 $browser_tests->{$browser} = 1;
978             }
979              
980 7247         15125 $self->{browser} = $browser;
981 7247 100 100     31741 $self->{browser_string} = $browser_string || $BROWSER_NAMES{$browser}
982             if defined $browser;
983              
984             # Other random tests
985              
986 7247 100       18789 $tests->{x11} = 1 if index( $ua, 'x11' ) != -1;
987 7247 100       17998 $tests->{dotnet} = 1 if index( $ua, '.net clr' ) != -1;
988              
989 7247 100       15813 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         35 $self->{browser} = 'realplayer';
998 18         30 $self->{browser_string} = 'RealPlayer';
999 18         35 $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         37 $self->{realplayer_version} = undef;
1005              
1006 18 100       96 if ( $ua =~ /realplayer\/([\d+\.]+)/ ) {
    50          
1007 12         35 $self->{realplayer_version} = $1;
1008             ( $self->{major}, $self->{minor} )
1009 12         44 = split( /\./, $self->{realplayer_version} );
1010 12 50       46 $self->{minor} = ".$self->{minor}" if defined $self->{minor};
1011             }
1012             elsif ( $ua =~ /realplayer\s(\w+)/ ) {
1013 6         124 $self->{realplayer_version} = $1;
1014             }
1015             }
1016              
1017 7247 100       18507 if ( index( $ua, '(r1 ' ) != -1 ) {
1018              
1019             # Realplayer plugin -- don't override browser but do set property
1020 18         41 $browser_tests->{realplayer} = 1;
1021             }
1022              
1023             # Details: https://developer.chrome.com/multidevice/user-agent#webview_user_agent
1024 7247 100 100     16541 if ( ( $self->android && index( $ua, '; wv)' ) > 0 )
      100        
      100        
      100        
1025             || ( $self->chrome && $self->android && $self->browser_major >= 30 ) )
1026             {
1027 480         1259 $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   232 my ( $self, @specs ) = @_;
1036 78         188 for my $spec (@specs) {
1037 156         241 my ( $ret, @tests ) = @{$spec};
  156         385  
1038 156         338 for my $m (@tests) {
1039 264 100 66     1312 return $ret if !ref $m && 0 < index $self->{user_agent}, $m;
1040 186 50 33     519 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 8787     8787   15112 my $self = shift;
1083              
1084 8787         21848 my $ua = lc $self->{user_agent};
1085 8787         15032 my $tests = $self->{tests};
1086 8787         13628 my $browser_tests = $self->{browser_tests};
1087              
1088 8787         23201 my $robot_tests = $self->{robot_tests} = {};
1089 8787         21915 my $id;
1090             my $r;
1091              
1092 8787         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 8787 100 66     347145 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         38 $r = 'lwp';
1098 18         44 $robot_tests->{lib} = 1;
1099 18 50       58 $robot_fragment = (
1100             ( index( $ua, 'libwww-perl' ) != -1 ) ? 'libwww-perl' : 'lwp-' );
1101             }
1102             elsif ( index( $ua, 'slurp' ) != -1 ) {
1103 12         23 $r = 'slurp';
1104 12         28 $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         16 $r = 'msnmobile';
1112 6         16 $robot_tests->{msn} = 1;
1113 6         9 $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         11 $r = 'msn';
1122 6         12 $robot_fragment = 'msnbot';
1123             }
1124             elsif (index( $ua, 'binglocalsearch' ) != -1
1125             || index( $ua, 'bingbot' ) != -1
1126             || index( $ua, 'bingpreview' ) != -1 ) {
1127 30         61 $r = 'bingbot';
1128 30         74 $robot_tests->{bingbot} = 1;
1129 30         55 $robot_fragment = 'bingbot';
1130             }
1131             elsif ( index( $ua, 'microsoft office existence discovery' ) != -1 ) {
1132 6         13 $r = 'msoffice';
1133 6         11 $robot_fragment = 'office';
1134             }
1135             elsif ( index( $ua, 'ahrefsbot' ) != -1 ) {
1136 6         14 $r = 'ahrefs';
1137             }
1138             elsif ( index( $ua, 'altavista' ) != -1 ) {
1139 0         0 $r = 'altavista';
1140             }
1141             elsif ( index( $ua, 'apache-httpclient' ) != -1 ) {
1142 12         26 $r = 'apache';
1143             }
1144             elsif ( $ua =~ m{\( *\) *\{ *\: *\; *} ) {
1145              
1146             # Shellcode for spawning a process, i.e. (){:;} with some kind of whitespace interleaved
1147 6         23 $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         48 $r = 'baidu';
1155             }
1156             elsif ( index( $ua, 'libcurl' ) != -1 || $ua =~ /^curl/ ) {
1157 36         73 $r = 'curl';
1158 36         90 $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         12 $r = 'getright';
1165             }
1166             elsif ( index( $ua, 'adsbot-google' ) != -1 ) {
1167 12         26 $r = 'googleadsbot';
1168 12         28 $robot_tests->{google} = 1;
1169 12         22 $robot_fragment = 'adsbot-google';
1170             }
1171             elsif ( index( $ua, 'mediapartners-google' ) != -1 ) {
1172 6         12 $r = 'googleadsense';
1173 6         17 $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         15 $robot_tests->{google} = 1;
1179 6         13 $robot_fragment = 'favicon';
1180             }
1181             elsif ( index( $ua, 'googlebot-image' ) != -1 ) {
1182 6         14 $r = 'googlebotimage';
1183 6         14 $robot_tests->{google} = 1;
1184 6         12 $robot_fragment = 'googlebot-image';
1185             }
1186             elsif ( index( $ua, 'googlebot-news' ) != -1 ) {
1187 6         12 $r = 'googlebotnews';
1188 6         16 $robot_tests->{google} = 1;
1189 6         12 $robot_fragment = 'googlebot-news';
1190             }
1191             elsif ( index( $ua, 'googlebot-video' ) != -1 ) {
1192 6         17 $r = 'googlebotvideo';
1193 6         15 $robot_tests->{google} = 1;
1194 6         14 $robot_fragment = 'googlebot-video';
1195             }
1196             elsif ( index( $ua, 'googlebot-mobile' ) != -1 ) {
1197 18         47 $r = 'googlemobile';
1198 18         48 $robot_tests->{google} = 1;
1199 18         43 $robot_fragment = 'googlebot-mobile';
1200             }
1201             elsif ( index( $ua, 'googlebot' ) != -1 ) {
1202 36         85 $r = 'google';
1203             }
1204             elsif ( $ua =~ m{go.*package http} ) {
1205 6         14 $r = 'golib';
1206 6         17 $robot_tests->{lib} = 1;
1207 6         14 $robot_fragment = 'package';
1208             }
1209             elsif ( $ua =~ m{^http_request} ) {
1210 6         16 $r = 'phplib';
1211 6         17 $robot_tests->{lib} = 1;
1212 6         11 $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         9 $r = 'indy';
1220 6         18 $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         30 $r = 'linkchecker';
1235             }
1236             elsif ( index( $ua, 'lycos' ) != -1 ) {
1237 0         0 $r = 'lycos';
1238             }
1239             elsif ( index( $ua, 'mechanize' ) != -1 ) {
1240 12         27 $r = 'rubylib';
1241 12         29 $robot_tests->{lib} = 1;
1242 12         22 $robot_fragment = 'mechanize';
1243             }
1244             elsif ( index( $ua, 'mj12bot/' ) != -1 ) {
1245 6         18 $r = 'mj12bot';
1246             }
1247             elsif ( index( $ua, 'nutch' ) != -1 ) {
1248 18         40 $r = 'nutch';
1249             }
1250             elsif ( index( $ua, 'puf/' ) != -1 ) {
1251 6         16 $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         16 $r = 'specialarchiver';
1259 6         15 $robot_fragment = 'special_archiver';
1260             }
1261             elsif ( index( $ua, 'wget' ) == 0 ) {
1262 12         30 $r = 'wget';
1263             }
1264             elsif ( index( $ua, 'yandexbot' ) != -1 ) {
1265 6         14 $r = 'yandex';
1266             }
1267             elsif ( index( $ua, 'yandeximages' ) != -1 ) {
1268 6         16 $r = 'yandeximages';
1269             }
1270             elsif ( index( $ua, 'headlesschrome' ) != -1 ) {
1271 6         28 $r = 'headlesschrome';
1272             }
1273             elsif ( $ua =~ m{^java} && !$self->{browser} ) {
1274 42         99 $r = 'java';
1275 42         92 $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         12 $robot_fragment = 'jakarta';
1286             }
1287             elsif ( index( $ua, 'google-http-java-client' ) != -1 ) {
1288 6         16 $r = 'java';
1289 6         16 $robot_tests->{lib} = 1;
1290 6         14 $robot_fragment = 'google';
1291             }
1292             elsif ( index( $ua, 'researchscan.comsys.rwth-aachen.de' ) != -1 ) {
1293 6         16 $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 8343         22169 for my $set (@ROBOT_TESTS) {
1303 249498         388671 my $match = lc $set->[0];
1304              
1305 249498 100       561868 if ( index( $ua, lc($match) ) != -1 ) {
1306 48         114 $id = $set->[1];
1307 48         92 $r = $id;
1308 48         99 $robot_fragment = lc $match;
1309 48         135 last TEST;
1310             }
1311             }
1312             }
1313              
1314 8787 100 100     46799 if ( $browser_tests->{applecoremedia}
      100        
1315             || $browser_tests->{dalvik}
1316             || $browser_tests->{adm} ) {
1317 28         68 $robot_tests->{lib} = 1;
1318             }
1319              
1320 8787 100       52116 if ($r) {
    50          
    100          
    100          
1321              
1322             # Got a named robot
1323 492         1026 $robot_tests->{$r} = 1;
1324 492 100       1195 if ( !$id ) {
1325 444         1096 $id = $ROBOT_IDS{$r};
1326             }
1327              
1328 492 50       1217 if ( !exists $robot_tests->{robot_id} ) {
1329 492         965 $robot_tests->{robot_id} = $id;
1330             }
1331              
1332             # This isn't all keyed on ids (yet)
1333 492   33     1913 $self->{robot_string} = $ROBOT_NAMES{$id} || $ROBOT_NAMES{$r};
1334 492         967 $robot_tests->{robot} = $r;
1335 492 100       1123 $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         103 $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         1122 $self->{robot_string} = $1;
1353 246         2575 $self->{robot_string} =~ s/^ *(.+?)[ \;\(\)]*?( *\/[\d\.]+ *)?$/$1/;
1354 246         701 $robot_fragment = $1;
1355 246         666 $robot_tests->{robot} = 'unknown';
1356             }
1357             else {
1358             # See if we have a simple fragment
1359             FRAGMENT:
1360 8007         15713 for my $fragment (@ROBOT_FRAGMENTS) {
1361 211078 100       362391 if ( $ROBOT_FRAGMENT_EXCEPTIONS{$fragment} ) {
1362 7965         11847 for my $exception (
1363 7965 50       21402 @{ $ROBOT_FRAGMENT_EXCEPTIONS{$fragment} || [] } ) {
1364 7965 100       20552 if ( index( $ua, $exception ) != -1 ) {
1365 7         26 next FRAGMENT;
1366             }
1367             }
1368             }
1369              
1370 211071 100       428422 if ( index( $ua, $fragment ) != -1 ) {
1371 311         715 $robot_fragment = $fragment;
1372 311         848 $robot_tests->{robot} = 'unknown';
1373 311         707 last;
1374             }
1375             }
1376             }
1377              
1378 8787 100 100     23627 if ( exists $robot_tests->{robot} && $robot_tests->{robot} eq 'unknown' )
1379             {
1380 599         1303 $robot_tests->{robot_id} = 'unknown';
1381             }
1382              
1383 8787 100       18805 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       73464 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         3561 my $full_string = $1;
1396 1019         5886 $full_string =~ s/ *$//; # Trim whitespace at end
1397 1019 100 100     6189 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         117 $full_string = $1;
1411             }
1412              
1413             # Figure out robot version based on the string
1414 1019 100 66     10906 if ( $full_string
1415             and $full_string =~ s/[\/ \.v]*(\d+)(\.\d+)?([\.\w]*)$// ) {
1416 654         3066 $self->{robot_version} = [ $1, $2, $3 ];
1417             }
1418             else {
1419 365         972 $self->{robot_version} = undef;
1420             }
1421              
1422             # Set robot_string, if we don't already have an explictly set
1423             # one
1424 1019 100       3221 if ( !defined $self->{robot_string} ) {
1425 353         998 $self->{robot_string} = $full_string;
1426             }
1427             }
1428             }
1429              
1430 8787 100       22587 if ( !exists( $self->{robot_version} ) ) {
1431 6210         15497 $self->{robot_version} = undef;
1432             }
1433             }
1434              
1435             ### OS tests, only run on demand
1436              
1437             sub _init_os {
1438 7242     7242   12040 my $self = shift;
1439              
1440 7242         11492 my $tests = $self->{tests};
1441 7242         11531 my $browser_tests = $self->{browser_tests};
1442 7242         16959 my $ua = lc $self->{user_agent};
1443              
1444 7242         16683 my $os_tests = $self->{os_tests} = {};
1445 7242         12471 my $os = undef;
1446 7242         10877 my $os_string = undef;
1447              
1448             # Windows
1449              
1450 7242 50       20914 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 7242 100       16772 if ( index( $ua, 'win' ) != -1 ) {
1457 2964 100 66     47835 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         35 $os_tests->{win16} = 1;
1461 12         24 $os_tests->{win3x} = 1;
1462 12         23 $os = 'windows';
1463 12 100       39 if ( index( $ua, 'windows 3.1' ) != -1 ) {
1464 6         10 $os_tests->{win31} = 1;
1465 6         13 $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         131 $os = 'windows';
1474 72         110 $os_string = 'Win95';
1475 72         208 $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         53 $os = 'windows';
1482 30         53 $os_string = 'WinME';
1483 30         89 $os_tests->{winme} = $os_tests->{win32} = 1;
1484             }
1485             elsif (index( $ua, 'win98' ) != -1
1486             || index( $ua, 'windows 98' ) != -1 ) {
1487 24         54 $os = 'windows';
1488 24         39 $os_string = 'Win98';
1489 24         74 $os_tests->{win98} = $os_tests->{win32} = 1;
1490             }
1491             elsif ( index( $ua, 'windows 2000' ) != -1 ) {
1492 12         26 $os = 'windows';
1493 12         19 $os_string = 'Win2k';
1494 12         38 $os_tests->{win2k} = $os_tests->{winnt} = $os_tests->{win32} = 1;
1495             }
1496             elsif ( index( $ua, 'windows ce' ) != -1 ) {
1497 12         28 $os = 'windows';
1498 12         20 $os_string = 'WinCE';
1499 12         33 $os_tests->{wince} = 1;
1500             }
1501             elsif ( index( $ua, 'windows phone' ) != -1 ) {
1502 54         108 $os = 'winphone';
1503 54         128 $os_tests->{winphone} = 1;
1504              
1505 54 100       286 if ( index( $ua, 'windows phone os 7.0' ) != -1 ) {
    100          
    100          
    100          
    50          
1506 6         14 $os_tests->{winphone7} = 1;
1507             }
1508             elsif ( index( $ua, 'windows phone os 7.5' ) != -1 ) {
1509 12         28 $os_tests->{winphone7_5} = 1;
1510             }
1511             elsif ( index( $ua, 'windows phone 8.0' ) != -1 ) {
1512 12         26 $os_tests->{winphone8} = 1;
1513             }
1514             elsif ( index( $ua, 'windows phone 8.1' ) != -1 ) {
1515 18         39 $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 7242 100       17961 if ( index( $ua, 'nt' ) != -1 ) {
1524 3804 100 66     37440 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         173 $os = 'windows';
1527 90         160 $os_string = 'Win2k';
1528 90         278 $os_tests->{win2k} = $os_tests->{winnt} = $os_tests->{win32} = 1;
1529             }
1530             elsif ( index( $ua, 'nt 5.1' ) != -1 ) {
1531 984         1764 $os = 'windows';
1532 984         1538 $os_string = 'WinXP';
1533 984         2847 $os_tests->{winxp} = $os_tests->{winnt} = $os_tests->{win32} = 1;
1534             }
1535             elsif ( index( $ua, 'nt 5.2' ) != -1 ) {
1536 126         217 $os = 'windows';
1537 126         200 $os_string = 'Win2k3';
1538 126         361 $os_tests->{win2k3} = $os_tests->{winnt} = $os_tests->{win32} = 1;
1539             }
1540             elsif ( index( $ua, 'nt 6.0' ) != -1 ) {
1541 204         399 $os = 'windows';
1542 204         334 $os_string = 'WinVista';
1543             $os_tests->{winvista} = $os_tests->{winnt} = $os_tests->{win32}
1544 204         654 = 1;
1545             }
1546             elsif ( index( $ua, 'nt 6.1' ) != -1 ) {
1547 966         1929 $os = 'windows';
1548 966         1507 $os_string = 'Win7';
1549 966         2910 $os_tests->{win7} = $os_tests->{winnt} = $os_tests->{win32} = 1;
1550             }
1551             elsif ( index( $ua, 'nt 6.2' ) != -1 ) {
1552 96         194 $os = 'windows';
1553 96         158 $os_string = 'Win8.0';
1554             $os_tests->{win8_0} = $os_tests->{win8} = $os_tests->{winnt}
1555 96         334 = $os_tests->{win32} = 1;
1556             }
1557             elsif ( index( $ua, 'nt 6.3' ) != -1 ) {
1558 78         157 $os = 'windows';
1559 78         153 $os_string = 'Win8.1';
1560             $os_tests->{win8_1} = $os_tests->{win8} = $os_tests->{winnt}
1561 78         269 = $os_tests->{win32} = 1;
1562             }
1563             elsif ( index( $ua, 'nt 10.0' ) != -1 ) {
1564 36         75 $os = 'windows';
1565 36         49 $os_string = 'Win10.0';
1566             $os_tests->{win10_0} = $os_tests->{win10} = $os_tests->{winnt}
1567 36         116 = $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         100 $os = 'windows';
1574 54         88 $os_string = 'WinNT';
1575 54         154 $os_tests->{winnt} = $os_tests->{win32} = 1;
1576             }
1577             }
1578              
1579 7242 100 100     117445 if ($os) {
    100 100        
    100 100        
    100 100        
    100 66        
    100 33        
    100 33        
    100 33        
    100 33        
    50 33        
    50 33        
    50 33        
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    100          
    100          
    50          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
1580              
1581             # windows, set through some path above
1582 2850         5914 $os_tests->{windows} = 1;
1583 2850 50       7437 $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         1064 $os_tests->{mac} = 1;
1589 462 100       1108 if ( index( $ua, 'mac os x' ) != -1 ) {
1590 438         726 $os = 'macosx';
1591 438         851 $os_tests->{$os} = 1;
1592             }
1593             else {
1594 24         47 $os = 'mac';
1595             }
1596 462 50 33     3051 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         264 $os_tests->{macppc} = 1;
1601             }
1602             }
1603             elsif (index( $ua, 'ipod' ) != -1
1604             || index( $ua, 'iphone' ) != -1
1605             || index( $ua, 'ipad' ) != -1 ) {
1606              
1607             # iOS
1608 420         748 $os = 'ios';
1609 420         1028 $os_tests->{$os} = 1;
1610             }
1611             elsif ( index( $ua, 'android' ) != -1 ) {
1612              
1613             # Android
1614 1008         1871 $os = 'android'; # Test gets set in the device testing
1615             }
1616             elsif ( index( $ua, 'inux' ) != -1 ) {
1617              
1618             # Linux
1619 498         875 $os = 'linux';
1620 498         1316 $os_tests->{linux} = $os_tests->{unix} = 1;
1621             }
1622             elsif ( $tests->{x11} && index( $ua, 'cros' ) != -1 ) {
1623              
1624             # ChromeOS
1625 42         108 $os = 'chromeos';
1626 42         97 $os_tests->{chromeos} = 1;
1627             }
1628             ## Long series of unlikely OSs
1629             elsif ( index( $ua, 'amiga' ) != -1 ) {
1630 6         10 $os = 'amiga';
1631 6         16 $os_tests->{$os} = 1;
1632             }
1633             elsif ( index( $ua, 'os/2' ) != -1 ) {
1634 6         14 $os = 'os2';
1635 6         15 $os_tests->{$os} = 1;
1636             }
1637             elsif ( index( $ua, 'solaris' ) != -1 ) {
1638 6         14 $os = 'unix';
1639 6         8 $os_string = 'Solaris';
1640 6         20 $os_tests->{sun} = $os_tests->{unix} = 1;
1641             }
1642             elsif ( index( $ua, 'samsung' ) == -1 && index( $ua, 'sun' ) != -1 ) {
1643 0         0 $os = 'unix';
1644 0         0 $os_string = 'SunOS';
1645 0         0 $os_tests->{sun} = $os_tests->{unix} = 1;
1646 0 0       0 $os_tests->{suni86} = 1 if index( $ua, 'i86' ) != -1;
1647 0 0       0 $os_tests->{sun4} = 1 if index( $ua, 'sunos 4' ) != -1;
1648 0 0       0 $os_tests->{sun5} = 1 if index( $ua, 'sunos 5' ) != -1;
1649             }
1650             elsif ( index( $ua, 'irix' ) != -1 ) {
1651 0         0 $os = 'unix';
1652 0         0 $os_string = 'Irix';
1653 0         0 $os_tests->{irix} = $os_tests->{unix} = 1;
1654 0 0       0 $os_tests->{irix5} = 1 if ( index( $ua, 'irix5' ) != -1 );
1655 0 0       0 $os_tests->{irix6} = 1 if ( index( $ua, 'irix6' ) != -1 );
1656             }
1657             elsif ( index( $ua, 'hp-ux' ) != -1 ) {
1658 0         0 $os = 'unix';
1659 0         0 $os_string = 'HP-UX';
1660 0         0 $os_tests->{hpux} = $os_tests->{unix} = 1;
1661 0 0       0 $os_tests->{hpux9} = 1 if index( $ua, '09.' ) != -1;
1662 0 0       0 $os_tests->{hpux10} = 1 if index( $ua, '10.' ) != -1;
1663             }
1664             elsif ( index( $ua, 'aix' ) != -1 ) {
1665 0         0 $os = 'unix';
1666 0         0 $os_string = 'AIX';
1667 0         0 $os_tests->{aix} = $os_tests->{unix} = 1;
1668 0 0       0 $os_tests->{aix1} = 1 if ( index( $ua, 'aix 1' ) != -1 );
1669 0 0       0 $os_tests->{aix2} = 1 if ( index( $ua, 'aix 2' ) != -1 );
1670 0 0       0 $os_tests->{aix3} = 1 if ( index( $ua, 'aix 3' ) != -1 );
1671 0 0       0 $os_tests->{aix4} = 1 if ( index( $ua, 'aix 4' ) != -1 );
1672             }
1673             elsif ( $ua =~ m{\bsco\b} || index( $ua, 'unix_sv' ) != -1 ) {
1674 0         0 $os = 'unix';
1675 0         0 $os_string = 'SCO Unix';
1676 0         0 $os_tests->{sco} = $os_tests->{unix} = 1;
1677             }
1678             elsif ( index( $ua, 'unix_system_v' ) != -1 ) {
1679 0         0 $os = 'unix';
1680 0         0 $os_string = 'System V Unix';
1681 0         0 $os_tests->{unixware} = $os_tests->{unix} = 1;
1682             }
1683             elsif ( $ua =~ m{\bncr\b} ) {
1684 0         0 $os = 'unix';
1685 0         0 $os_string = 'NCR Unix';
1686 0         0 $os_tests->{mpras} = $os_tests->{unix} = 1;
1687             }
1688             elsif ( index( $ua, 'reliantunix' ) != -1 ) {
1689 0         0 $os = 'unix';
1690 0         0 $os_string = 'Reliant Unix';
1691 0         0 $os_tests->{reliant} = $os_tests->{unix} = 1;
1692             }
1693             elsif (index( $ua, 'dec' ) != -1
1694             || index( $ua, 'osf1' ) != -1
1695             || index( $ua, 'declpha' ) != -1
1696             || index( $ua, 'alphaserver' ) != -1
1697             || index( $ua, 'ultrix' ) != -1
1698             || index( $ua, 'alphastation' ) != -1 ) {
1699 0         0 $os = 'unix';
1700 0         0 $os_tests->{dec} = $os_tests->{unix} = 1;
1701             }
1702             elsif ( index( $ua, 'sinix' ) != -1 ) {
1703 0         0 $os = 'unix';
1704 0         0 $os_tests->{sinix} = $os_tests->{unix} = 1;
1705             }
1706             elsif ( index( $ua, 'bsd' ) != -1 ) {
1707 30         70 $os = 'unix';
1708 30 50       257 if ( $self->{user_agent} =~ m{(\w*bsd\w*)}i ) {
1709 30         100 $os_string = $1;
1710             }
1711 30         94 $os_tests->{bsd} = $os_tests->{unix} = 1;
1712 30 50       111 $os_tests->{freebsd} = 1 if index( $ua, 'freebsd' ) != -1;
1713             }
1714             elsif ( $tests->{x11} ) {
1715              
1716             # Some Unix we didn't identify
1717 12         33 $os = 'unix';
1718 12         34 $os_tests->{unix} = 1;
1719             }
1720             elsif ( index( $ua, 'vax' ) != -1 || index( $ua, 'openvms' ) != -1 ) {
1721              
1722 0         0 $os = 'vms';
1723 0         0 $os_tests->{vms} = 1;
1724             }
1725             elsif ( index( $ua, 'bb10' ) != -1 ) {
1726 12         27 $os = 'bb10';
1727 12         28 $os_tests->{bb10} = 1;
1728             }
1729             elsif ( index( $ua, 'rim tablet os' ) != -1 ) {
1730 6         13 $os = 'rimtabletos';
1731 6         16 $os_tests->{rimtabletos} = 1;
1732             }
1733             elsif ( index( $ua, 'playstation 3' ) != -1 ) {
1734 6         14 $os = 'ps3gameos';
1735 6         17 $os_tests->{ps3gameos} = 1;
1736             }
1737             elsif ( index( $ua, 'playstation portable' ) != -1 ) {
1738 6         14 $os = 'pspgameos';
1739 6         15 $os_tests->{pspgameos} = 1;
1740             }
1741             elsif ( index( $ua, 'windows' ) != -1 ) {
1742              
1743             # Windows again, the super generic version
1744 54         169 $os_tests->{windows} = 1;
1745             }
1746             elsif ( index( $ua, 'win32' ) != -1 ) {
1747 18         62 $os_tests->{win32} = $os_tests->{windows} = 1;
1748             }
1749             elsif ( $self->{user_agent} =~ m{(brew)|(\bbmp\b)}i ) {
1750 144         347 $os = 'brew';
1751 144 100       473 if ($1) {
1752 102         183 $os_string = 'Brew';
1753             }
1754             else {
1755 42         71 $os_string = 'Brew MP';
1756             }
1757 144         374 $os_tests->{brew} = 1;
1758             }
1759             else {
1760 1656         3564 $os = undef;
1761             }
1762              
1763             # To deal with FirefoxOS we seem to have to load-on-demand devices
1764             # also, by calling ->mobile and ->tablet. We have to be careful;
1765             # if we ever created a loop back from _init_devices to _init_os
1766             # we'd run forever.
1767 7242 100 100     19623 if ( !$os
      66        
      100        
      100        
1768             && $browser_tests->{firefox}
1769             && index( $ua, 'fennec' ) == -1
1770             && ( $self->mobile || $self->tablet ) ) {
1771 12         27 $os = 'firefoxos';
1772 12         31 $os_tests->{firefoxos} = 1;
1773             }
1774              
1775 7242         15856 $self->{os} = $os;
1776 7242 100 100     22901 if ( $os and !$os_string ) {
1777 2550         5748 $os_string = $OS_NAMES{$os};
1778             }
1779 7242         18371 $self->{os_string} = $os_string;
1780             }
1781              
1782             sub _init_os_version {
1783 2574     2574   5118 my ($self) = @_;
1784              
1785 2574         6407 my $os = $self->os;
1786 2574         7013 my $os_string = $self->os_string;
1787 2574         8276 my $ua = lc $self->{user_agent};
1788              
1789 2574         4436 my $os_version = undef;
1790              
1791 2574 100       14816 if ( !defined $os ) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
1792              
1793             # Nothing is going to work if we have no OS. Skip everything.
1794             }
1795             elsif ( $os eq 'winphone' ) {
1796 22 50       198 if ( $ua =~ m{windows phone (?:os )?(\d+)(\.?\d*)([\.\d]*)} ) {
1797 22         115 $os_version = [ $1, $2, $3 ];
1798             }
1799             }
1800             elsif ( $os eq 'macosx' ) {
1801 194 100       1324 if ( $ua =~ m{os x (\d+)[\._](\d+)[\._]?(\d*)} ) {
1802 155 100       1073 $os_version = [ $1, ".$2", length($3) ? ".$3" : q{} ];
1803             }
1804             }
1805             elsif ( $os eq 'ios' ) {
1806 185 100       1394 if ( $ua =~ m{ os (\d+)[\._ ](\d+)[\._ ]?(\d*)} ) {
1807 184 100       1309 $os_version = [ $1, ".$2", length($3) ? ".$3" : q{} ];
1808             }
1809             }
1810             elsif ( $os eq 'chromeos' ) {
1811 20 50       186 if ( $ua =~ m{ cros \S* (\d+)(\.?\d*)([\.\d]*)} ) {
1812 20         92 $os_version = [ $1, $2, $3 ];
1813             }
1814             }
1815             elsif ( $os eq 'android' ) {
1816 479 100       3838 if ( $ua =~ m{android (\d+)(\.?\d*)([\w\-\.]*)[\;\)]} ) {
1817 464         2128 $os_version = [ $1, $2, $3 ];
1818             }
1819             }
1820             elsif ( $os eq 'firefoxos' ) {
1821 5 50       42 if ( $ua =~ m{firefox/(\d+)(\.?\d*)([\.\d]*)} ) {
1822 5         26 $os_version = [ $1, $2, $3 ];
1823             }
1824             }
1825             elsif ( $os eq 'brew' ) {
1826 68 100       644 if ( $ua =~ m{(brew|\bbmp) (\d+)(\.?\d*)([\.\d]*)} ) {
1827 47         239 $os_version = [ $2, $3, $4 ];
1828             }
1829             }
1830              
1831             # Set the version. It might be set to undef, in which case we know
1832             # not to go through this next time.
1833 2574         6436 $self->{os_version} = $os_version;
1834             }
1835              
1836             ### Version determination, only run on demand
1837              
1838             sub _init_version {
1839 7201     7201   11923 my ($self) = @_;
1840              
1841 7201         20922 my $ua = lc $self->{user_agent};
1842 7201         12360 my $tests = $self->{tests};
1843 7201         10729 my $browser_tests = $self->{browser_tests};
1844 7201         11511 my $browser = $self->{browser};
1845              
1846 7201         14930 $self->{version_tests} = {};
1847 7201         11683 my $version_tests = $self->{version_tests};
1848              
1849 7201         12218 my ( $major, $minor, $beta );
1850              
1851             ### First figure out version numbers. First, we test if we're
1852             ### using a browser that needs some special method to determine
1853             ### the version.
1854              
1855 7201 100 100     94843 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          
1856              
1857             # Opera has a 'compatible; ' section, but lies sometimes. It needs
1858             # special handling.
1859              
1860             # http://dev.opera.com/articles/view/opera-ua-string-changes/
1861             # http://my.opera.com/community/openweb/idopera/
1862             # Opera/9.80 (S60; SymbOS; Opera Mobi/320; U; sv) Presto/2.4.15 Version/10.00
1863             # 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
1864              
1865 264 100       3138 if ( $ua =~ m{\AOpera.*\sVersion/(\d*)\.(\d*)\z}i ) {
    100          
    50          
1866 90         296 $major = $1;
1867 90         199 $minor = $2;
1868             }
1869             elsif ( $ua =~ m{\bOPR/(\d+)\.(\d+)}i ) {
1870 48         168 $major = $1;
1871 48         105 $minor = $2;
1872             }
1873             elsif ( $ua =~ m{Opera[ /](\d+).(\d+)}i ) {
1874 126         363 $major = $1;
1875 126         268 $minor = $2;
1876             }
1877             }
1878             elsif ( $ua
1879             =~ m{\b compatible; \s* [\w\-]* [/\s] ( [0-9]+ ) (?: .([0-9]+) (\S*) )? ;}x
1880             ) {
1881             # MSIE and some others use a 'compatible' format
1882 1883         7969 ( $major, $minor, $beta ) = ( $1, $2, $3 );
1883             }
1884             elsif ( !$browser ) {
1885              
1886             # Nothing else is going to work if $browser isn't defined; skip the
1887             # specific approaches and go straight to the generic ones.
1888             }
1889             elsif ( $browser_tests->{edge} ) {
1890 24         119 ( $major, $minor, $beta )
1891             = $ua =~ m{Edge/(\d+)(?:\.(\d+))?([\.\d]+)?}i;
1892 24 50       325 ( $major, $minor, $beta )
1893             = $ua =~ m{(?:Edg|EdgA|EdgiOS)/(\d+)(?:\.(\d+))?([\.\d]+)?}i
1894             unless defined $major;
1895             }
1896             elsif ( $browser_tests->{safari} ) {
1897              
1898             # Safari Version
1899              
1900 940 100       6592 if (
    100          
1901             0
1902             && $ua =~ m{ # Disabled for bug compatibility
1903             version/
1904             ( \d+ ) # Major version number is everything before first dot
1905             \. # First dot
1906             ( \d+ )? # Minor version number follows dot
1907             }x
1908             ) {
1909             # Safari starting with version 3.0 provides its own public version
1910             ( $major, $minor ) = ( $1, $2, undef );
1911             }
1912 0         0 elsif ( $ua =~ m{ safari/ ( \d+ (?: \.\d+ )* ) }x ) {
1913 875 50       5416 if ( my ( $safari_build, $safari_minor ) = split /\./, $1 ) {
1914 875         2840 $major = int( $safari_build / 100 );
1915 875         1806 $minor = int( $safari_build % 100 );
1916 875 100       2678 $beta = ".$safari_minor" if defined $safari_minor;
1917             }
1918             }
1919             elsif ( $ua =~ m{applewebkit\/([\d\.]{1,})}xi ) {
1920 53 50       351 if ( my ( $safari_build, $safari_minor ) = split /\./, $1 ) {
1921 53         187 $major = int( $safari_build / 100 );
1922 53         132 $minor = int( $safari_build % 100 );
1923 53 100       207 $beta = ".$safari_minor" if $safari_minor;
1924             }
1925             }
1926             }
1927             elsif ( $browser_tests->{fxios} ) {
1928 6         51 ( $major, $minor ) = $ua =~ m{ \b fxios/ (\d+) [.] (\d+) }x;
1929             }
1930             elsif ( $tests->{meta_app} ) {
1931              
1932             # init. in order to avoid guessing downstream
1933 78         279 ( $major, $minor, $beta ) = (q{}) x 3;
1934              
1935             # get version only from FBAV/ part
1936 78 100       515 if ( $ua =~ m{ \b fbav/ ([^;]*) }x ) {
1937 60         343 ( $major, $minor, $beta ) = split /[.]/, $1, 3;
1938 60 50       185 if ($beta) {
1939              
1940             # "minor" forcibly gets a "." prepended at the end of _init_version
1941             # while "beta" does not - yet it is documented to include the "."
1942 60         165 $beta = q{.} . $beta;
1943             }
1944             }
1945             }
1946             elsif ( $browser_tests->{instagram} ) {
1947 12         44 ( $major, $minor, $beta ) = (q{}) x 3; # don't guess downstream
1948 12 50       96 if ( $self->{user_agent} =~ m{ \b Instagram [ ]+ ([0-9.]+) [ ] }x ) {
1949 12         62 ( $major, $minor, $beta ) = split /[.]/, $1, 3;
1950 12 50       37 if ($beta) {
1951 12         35 $beta = q{.} . $beta;
1952             }
1953             }
1954             }
1955             elsif ( $browser_tests->{ie} ) {
1956              
1957             # MSIE
1958              
1959 100 100       775 if ( $ua =~ m{\b msie \s ( [0-9\.]+ ) (?: [a-z]+ [a-z0-9]* )? ;}x ) {
    100          
1960              
1961             # Internet Explorer
1962 18         93 ( $major, $minor, $beta ) = split /\./, $1;
1963             }
1964             elsif ( $ua =~ m{\b rv: ( [0-9\.]+ ) \b}x ) {
1965              
1966             # MSIE masking as Gecko really well ;)
1967 71         353 ( $major, $minor, $beta ) = split /\./, $1;
1968             }
1969             }
1970             elsif ( $browser eq 'n3ds' ) {
1971 6 50       50 if ( $ua =~ m{Nintendo 3DS;.*\sVersion/(\d*)\.(\d*)}i ) {
1972 6         17 $major = $1;
1973 6         16 $minor = $2;
1974             }
1975             }
1976             elsif ( $browser eq 'browsex' ) {
1977 6 50       53 if ( $ua =~ m{BrowseX \((\d+)\.(\d+)([\d.]*)}i ) {
1978 6         17 $major = $1;
1979 6         14 $minor = $2;
1980 6         13 $beta = $3;
1981             }
1982             }
1983             elsif ( $ua =~ m{netscape6/(\d+)\.(\d+)([\d.]*)} ) {
1984              
1985             # Other cases get handled below, we just need this to skip the '6'
1986 6         23 $major = $1;
1987 6         15 $minor = $2;
1988 6         13 $beta = $3;
1989             }
1990             elsif ( $browser eq 'brave' ) {
1991              
1992             # Note: since 0.7.10, Brave has changed the branding
1993             # of GitHub's 'Electron' (http://electron.atom.io/) to 'Brave'.
1994             # This means the browser string has both 'brave/' (the browser)
1995             # and 'Brave/' (re-branded Electron) in it.
1996             # The generic section below looks at $self->{browser_string}, which is 'Brave'
1997             # (Electron) and not $self->{browser} which is 'brave'.
1998             # Caveat parser.
1999 12 50       92 if ( $ua =~ m{brave/(\d+)\.(\d+)([\d.]*)} ) {
2000 12         34 $major = $1;
2001 12         26 $minor = $2;
2002 12         25 $beta = $3;
2003             }
2004             }
2005             elsif ($browser eq 'chrome'
2006             && $ua =~ m{crios/(\d+)\.(\d+)([\d.]*)} ) {
2007 18         57 $major = $1;
2008 18         34 $minor = $2;
2009 18         187 $beta = $3;
2010             }
2011             elsif ($browser eq 'pubsub'
2012             && $ua =~ m{apple-pubsub/(\d+)\.?(\d+)?([\d.]*)} ) {
2013 6         21 $major = $1;
2014 6         15 $minor = $2;
2015 6         11 $beta = $3;
2016             }
2017             elsif ($browser eq 'obigo'
2018             && $self->{user_agent} =~ m{(obigo[\w\-]*|teleca)[\/ ]\w(\d+)(\w*)}i )
2019             {
2020 78         268 $major = $2;
2021 78         160 $minor = q{};
2022 78         166 $beta = $3;
2023             }
2024             elsif ($browser eq 'polaris'
2025             && $ua =~ m{polaris[ \/](\d+)\.?(\d+)?([\d\.]*)} ) {
2026 6         21 $major = $1;
2027 6         14 $minor = $2;
2028 6         11 $beta = $3;
2029             }
2030             elsif ($browser eq 'ucbrowser'
2031             && $ua =~ m{ucbrowser[\/ ]*(\d+)\.?(\d+)?([\d\.]*)} ) {
2032 336         1049 $major = $1;
2033 336         672 $minor = $2;
2034 336         704 $beta = $3;
2035             }
2036             elsif ( $browser eq 'samsung' && $ua =~ m{samsungbrowser/(\d+)\.(\d+)\s} )
2037             {
2038 6         19 $major = $1;
2039 6         15 $minor = $2;
2040             }
2041             elsif ( $browser_tests->{yandex_browser} ) {
2042 36         142 ( $major, $minor, $beta ) = (q{}) x 3; # don't guess downstream
2043 36 50       279 if ( $self->{user_agent} =~ m{ \b YaBrowser / ([0-9.]+) [ ] }x ) {
2044 36         197 ( $major, $minor, $beta ) = split /[.]/, $1, 3;
2045 36 50       106 if ($beta) {
2046 36         105 $beta = q{.} . $beta;
2047             }
2048             }
2049             }
2050              
2051             # If we didn't match a browser-specific test, we look for
2052             # '$browser/x.y.z'
2053 7201 100 100     23211 if ( !defined $major && defined $self->{browser_string} ) {
2054 2440         8001 my $version_index = index( $ua, lc "$self->{browser_string}/" );
2055 2440 100       5903 if ( $version_index != -1 ) {
2056 2243         6177 my $version_str
2057             = substr( $ua, $version_index + length($browser) );
2058 2243 100       13883 if ( $version_str =~ m{/(\d+)\.(\d+)([\w.]*)} ) {
2059 2232         5702 $major = $1;
2060 2232         4407 $minor = $2;
2061 2232         5018 $beta = $3;
2062             }
2063             }
2064             }
2065              
2066             # If that didn't work, we try 'Version/x.y.z'
2067 7201 100       16275 if ( !defined $major ) {
2068 1169 100       3359 if ( $ua =~ m{version/(\d+)\.(\d+)([\w.]*)} ) {
2069 24         71 $major = $1;
2070 24         53 $minor = $2;
2071 24         50 $beta = $3;
2072             }
2073             }
2074              
2075             # If that didn't work, we start guessing. Just grab
2076             # anything after a word and a slash.
2077 7201 100       15989 if ( !defined $major ) {
2078              
2079 1145         7638 ( $major, $minor, $beta ) = (
2080             $ua =~ m{
2081             \S+ # Greedily catch anything leading up to forward slash.
2082             \/ # Version starts with a slash
2083             [A-Za-z]* # Eat any letters before the major version
2084             ( [0-9]+ ) # Major version number is everything before the first dot
2085             \. # The first dot
2086             ([\d]* ) # Minor version number is every digit after the first dot
2087             # Throw away remaining numbers and dots
2088             ( [^\s]* ) # Beta version string is up to next space
2089             }x
2090             );
2091             }
2092              
2093             # If that didn't work, try even more generic.
2094 7201 100       15025 if ( !defined $major ) {
2095              
2096 324 50       1190 if ( $ua =~ /[A-Za-z]+\/(\d+)\;/ ) {
2097 0         0 $major = $1;
2098 0         0 $minor = 0;
2099             }
2100             }
2101              
2102             # If that didn't work, give up.
2103 7201 100       14891 $major = 0 if !$major;
2104 7201 100       13797 $minor = 0 if !$minor;
2105 7201 100 100     24388 $beta = undef if ( defined $beta && $beta eq q{} );
2106              
2107             # Now set version tests
2108              
2109 7201 100       15994 if ( $browser_tests->{netscape} ) {
2110              
2111             # Netscape browsers
2112 168 100       540 $version_tests->{nav2} = 1 if $major == 2;
2113 168 100       480 $version_tests->{nav3} = 1 if $major == 3;
2114 168 100       434 $version_tests->{nav4} = 1 if $major == 4;
2115 168 100       550 $version_tests->{nav4up} = 1 if $major >= 4;
2116 168 100 100     538 $version_tests->{nav45} = 1 if $major == 4 && $minor == 5;
2117 168 100 100     866 $version_tests->{nav45up} = 1
      100        
2118             if ( $major == 4 && ".$minor" >= .5 )
2119             || $major >= 5;
2120 168 100 100     591 $version_tests->{navgold} = 1
2121             if defined $beta && ( index( $beta, 'gold' ) != -1 );
2122 168 100 100     590 $version_tests->{nav6} = 1
2123             if ( $major == 5 || $major == 6 ); # go figure
2124 168 100       438 $version_tests->{nav6up} = 1 if $major >= 5;
2125              
2126 168 100       428 if ( $browser eq 'seamonkey' ) {
2127              
2128             # Ugh, seamonkey versions started back at 1.
2129 6         13 $version_tests->{nav2} = 0;
2130 6         14 $version_tests->{nav4up} = 1;
2131 6         16 $version_tests->{nav45up} = 1;
2132 6         13 $version_tests->{nav6} = 1;
2133 6         18 $version_tests->{nav6up} = 1;
2134             }
2135             }
2136              
2137 7201 100       16009 if ( $browser_tests->{ie} ) {
2138 1708 100       4465 $version_tests->{ie3} = 1 if ( $major == 3 );
2139 1708 100       3717 $version_tests->{ie4} = 1 if ( $major == 4 );
2140 1708 100       4889 $version_tests->{ie4up} = 1 if ( $major >= 4 );
2141 1708 100       3851 $version_tests->{ie5} = 1 if ( $major == 5 );
2142 1708 100       4101 $version_tests->{ie5up} = 1 if ( $major >= 5 );
2143 1708 100 100     4359 $version_tests->{ie55} = 1 if ( $major == 5 && $minor == 5 );
2144 1708 100 100     10320 $version_tests->{ie55up} = 1 if ( ".$minor" >= .5 || $major >= 6 );
2145 1708 100       4253 $version_tests->{ie6} = 1 if ( $major == 6 );
2146 1708 100       3588 $version_tests->{ie7} = 1 if ( $major == 7 );
2147 1708 100       3293 $version_tests->{ie8} = 1 if ( $major == 8 );
2148 1708 100       3288 $version_tests->{ie9} = 1 if ( $major == 9 );
2149 1708 100       3463 $version_tests->{ie10} = 1 if ( $major == 10 );
2150 1708 100       3323 $version_tests->{ie11} = 1 if ( $major == 11 );
2151              
2152             $version_tests->{ie_compat_mode}
2153             = ( $version_tests->{ie7}
2154             && $tests->{trident}
2155 1708   100     5038 && defined $self->engine_version
2156             && $self->engine_version >= 4 );
2157             }
2158              
2159 7201 100       15322 if ( $browser_tests->{aol} ) {
2160             $version_tests->{aol3} = 1
2161             if ( index( $ua, 'aol 3.0' ) != -1
2162 41 100 66     238 || $version_tests->{ie3} );
2163             $version_tests->{aol4} = 1
2164             if ( index( $ua, 'aol 4.0' ) != -1 )
2165 41 50 33     196 || $version_tests->{ie4};
2166 41 50       121 $version_tests->{aol5} = 1 if index( $ua, 'aol 5.0' ) != -1;
2167 41 100       111 $version_tests->{aol6} = 1 if index( $ua, 'aol 6.0' ) != -1;
2168 41 50       113 $version_tests->{aoltv} = 1 if index( $ua, 'navio' ) != -1;
2169             }
2170              
2171 7201 100       15955 if ( $browser_tests->{opera} ) {
2172 264 100 66     1407 $version_tests->{opera3} = 1
2173             if index( $ua, 'opera 3' ) != -1 || index( $ua, 'opera/3' ) != -1;
2174 264 50 66     1222 $version_tests->{opera4} = 1
      33        
2175             if ( index( $ua, 'opera 4' ) != -1 )
2176             || ( index( $ua, 'opera/4' ) != -1
2177             && ( index( $ua, 'nintendo dsi' ) == -1 ) );
2178 264 50 33     1132 $version_tests->{opera5} = 1
2179             if ( index( $ua, 'opera 5' ) != -1 )
2180             || ( index( $ua, 'opera/5' ) != -1 );
2181 264 100 66     1119 $version_tests->{opera6} = 1
2182             if ( index( $ua, 'opera 6' ) != -1 )
2183             || ( index( $ua, 'opera/6' ) != -1 );
2184 264 100 100     1074 $version_tests->{opera7} = 1
2185             if ( index( $ua, 'opera 7' ) != -1 )
2186             || ( index( $ua, 'opera/7' ) != -1 );
2187              
2188             }
2189              
2190 7201         15938 $minor = ".$minor";
2191              
2192 7201         18780 $self->{major} = $major;
2193 7201         17111 $self->{minor} = $minor;
2194 7201         18423 $self->{beta} = $beta;
2195             }
2196              
2197             ### Device tests, only run on demand
2198              
2199             sub _init_device {
2200 8311     8311   14205 my ($self) = @_;
2201              
2202 8311         19848 my $ua = lc $self->{user_agent};
2203 8311         13597 my $browser_tests = $self->{browser_tests};
2204 8311         13277 my $tests = $self->{tests};
2205              
2206 8311         12858 my ( $device, $device_string );
2207 8311         19514 my $device_tests = $self->{device_tests} = {};
2208              
2209 8311 100 100     270640 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        
2210 54         119 $device = 'winphone';
2211              
2212             # Test is set in _init_os()
2213             }
2214             elsif (index( $ua, 'android' ) != -1
2215             || index( $ua, 'silk-accelerated' ) != -1 ) {
2216              
2217             # Silk-accelerated indicates a 1st generation Kindle Fire,
2218             # which may not have other indications of being an Android
2219             # device.
2220 1014         1765 $device = 'android';
2221 1014         2313 $device_tests->{$device} = 1;
2222             }
2223             elsif (index( $ua, 'blackberry' ) != -1
2224             || index( $ua, 'bb10' ) != -1
2225             || index( $ua, 'rim tablet os' ) != -1 ) {
2226 42         75 $device = 'blackberry';
2227 42         111 $device_tests->{$device} = 1;
2228             }
2229             elsif ( index( $ua, 'ipod' ) != -1 ) {
2230 18         32 $device = 'ipod';
2231 18         44 $device_tests->{$device} = 1;
2232             }
2233             elsif ( index( $ua, 'ipad' ) != -1 ) {
2234 162         306 $device = 'ipad';
2235 162         401 $device_tests->{$device} = 1;
2236             }
2237             elsif ( index( $ua, 'iphone' ) != -1 ) {
2238 240         413 $device = 'iphone';
2239 240         573 $device_tests->{$device} = 1;
2240             }
2241             elsif ( index( $ua, 'webos' ) != -1 ) {
2242 6         12 $device = 'webos';
2243 6         19 $device_tests->{$device} = 1;
2244             }
2245             elsif ( index( $ua, 'kindle' ) != -1 ) {
2246 12         25 $device = 'kindle';
2247 12         28 $device_tests->{$device} = 1;
2248             }
2249             elsif ( index( $ua, 'audrey' ) != -1 ) {
2250 0         0 $device = 'audrey';
2251 0         0 $device_tests->{$device} = 1;
2252             }
2253             elsif ( index( $ua, 'i-opener' ) != -1 ) {
2254 0         0 $device = 'iopener';
2255 0         0 $device_tests->{$device} = 1;
2256             }
2257             elsif ( index( $ua, 'avantgo' ) != -1 ) {
2258 0         0 $device = 'avantgo';
2259 0         0 $device_tests->{$device} = 1;
2260 0         0 $device_tests->{palm} = 1;
2261             }
2262             elsif ( index( $ua, 'palmos' ) != -1 ) {
2263 0         0 $device = 'palm';
2264 0         0 $device_tests->{$device} = 1;
2265             }
2266             elsif ( index( $ua, 'playstation 3' ) != -1 ) {
2267 6         15 $device = 'ps3';
2268 6         16 $device_tests->{$device} = 1;
2269             }
2270             elsif ( index( $ua, 'playstation portable' ) != -1 ) {
2271 6         10 $device = 'psp';
2272 6         16 $device_tests->{$device} = 1;
2273             }
2274             elsif ( index( $ua, 'nintendo dsi' ) != -1 ) {
2275 6         13 $device = 'dsi';
2276 6         22 $device_tests->{$device} = 1;
2277             }
2278             elsif ( index( $ua, 'nintendo 3ds' ) != -1 ) {
2279 6         14 $device = 'n3ds';
2280 6         16 $device_tests->{$device} = 1;
2281             }
2282             elsif (
2283             $browser_tests->{obigo}
2284             || $browser_tests->{ucbrowser}
2285             || index( $ua, 'up.browser' ) != -1
2286             || ( index( $ua, 'nokia' ) != -1
2287             && index( $ua, 'windows phone' ) == -1 )
2288             || index( $ua, 'alcatel' ) != -1
2289             || $ua =~ m{\bbrew\b}
2290             || $ua =~ m{\bbmp\b}
2291             || index( $ua, 'ericsson' ) != -1
2292             || index( $ua, 'sie-' ) == 0
2293             || index( $ua, 'wmlib' ) != -1
2294             || index( $ua, ' wap' ) != -1
2295             || index( $ua, 'wap ' ) != -1
2296             || index( $ua, 'wap/' ) != -1
2297             || index( $ua, '-wap' ) != -1
2298             || index( $ua, 'wap-' ) != -1
2299             || index( $ua, 'wap' ) == 0
2300             || index( $ua, 'wapper' ) != -1
2301             || index( $ua, 'zetor' ) != -1
2302             ) {
2303 582         1205 $device = 'wap';
2304 582         1339 $device_tests->{$device} = 1;
2305             }
2306              
2307             $device_tests->{tablet} = (
2308             index( $ua, 'ipad' ) != -1
2309             || ( $browser_tests->{ie}
2310             && index( $ua, 'windows phone' ) == -1
2311             && index( $ua, 'arm' ) != -1 )
2312             || ( index( $ua, 'android' ) != -1
2313             && index( $ua, 'mobile' ) == -1
2314             && index( $ua, 'safari' ) != -1 )
2315 8311   66     304450 || ( $browser_tests->{firefox} && index( $ua, 'tablet' ) != -1 )
2316             || index( $ua, 'an10bg3' ) != -1
2317             || index( $ua, 'an10bg3dt' ) != -1
2318             || index( $ua, 'an10g2' ) != -1
2319             || index( $ua, 'an7bg3' ) != -1
2320             || index( $ua, 'an7dg3' ) != -1
2321             || index( $ua, 'an7dg3childpad' ) != -1
2322             || index( $ua, 'an7dg3st' ) != -1
2323             || index( $ua, 'an7fg3' ) != -1
2324             || index( $ua, 'an7g3' ) != -1
2325             || index( $ua, 'an8cg3' ) != -1
2326             || index( $ua, 'an8g3' ) != -1
2327             || index( $ua, 'an9g3' ) != -1
2328             || index( $ua, 'flyer' ) != -1
2329             || index( $ua, 'hp-tablet' ) != -1
2330             || index( $ua, 'jetstream' ) != -1
2331             || index( $ua, 'kindle' ) != -1
2332             || index( $ua, 'novo7' ) != -1
2333             || index( $ua, 'opera tablet' ) != -1
2334             || index( $ua, 'rim tablet' ) != -1
2335             || index( $ua, 'transformer' ) != -1
2336             || index( $ua, 'xoom' ) != -1
2337             );
2338              
2339 8311 100       19932 if ( !$device_tests->{tablet} ) {
2340             $device_tests->{mobile} = (
2341             ( $browser_tests->{firefox} && index( $ua, 'mobile' ) != -1 )
2342             || ( $browser_tests->{ie}
2343             && index( $ua, 'windows phone' ) == -1
2344             && index( $ua, 'arm' ) != -1 )
2345             || index( $ua, 'windows phone' ) != -1
2346             || index( $ua, 'up.browser' ) != -1
2347             || index( $ua, 'nokia' ) != -1
2348             || index( $ua, 'alcatel' ) != -1
2349             || index( $ua, 'ericsson' ) != -1
2350             || index( $ua, 'sie-' ) == 0
2351             || index( $ua, 'wmlib' ) != -1
2352             || index( $ua, ' wap' ) != -1
2353             || index( $ua, 'wap ' ) != -1
2354             || index( $ua, 'wap/' ) != -1
2355             || index( $ua, '-wap' ) != -1
2356             || index( $ua, 'wap-' ) != -1
2357             || index( $ua, 'wap' ) == 0
2358             || index( $ua, 'wapper' ) != -1
2359             || index( $ua, 'blackberry' ) != -1
2360             || index( $ua, 'mobile' ) != -1
2361             || index( $ua, 'palm' ) != -1
2362             || index( $ua, 'smartphone' ) != -1
2363             || index( $ua, 'windows ce' ) != -1
2364             || index( $ua, 'palmsource' ) != -1
2365             || index( $ua, 'iphone' ) != -1
2366             || index( $ua, 'ipod' ) != -1
2367             || index( $ua, 'ipad' ) != -1
2368             || ( index( $ua, 'opera mini' ) != -1
2369             && index( $ua, 'tablet' ) == -1 )
2370             || index( $ua, 'htc_' ) != -1
2371             || index( $ua, 'symbian' ) != -1
2372             || index( $ua, 'webos' ) != -1
2373             || index( $ua, 'samsung' ) != -1
2374             || index( $ua, 'zetor' ) != -1
2375             || index( $ua, 'android' ) != -1
2376             || index( $ua, 'symbos' ) != -1
2377             || index( $ua, 'opera mobi' ) != -1
2378             || index( $ua, 'fennec' ) != -1
2379             || $ua =~ m{\bbrew\b}
2380             || index( $ua, 'obigo' ) != -1
2381             || index( $ua, 'teleca' ) != -1
2382             || index( $ua, 'polaris' ) != -1
2383             || index( $ua, 'opera tablet' ) != -1
2384             || index( $ua, 'rim tablet' ) != -1
2385             || ( index( $ua, 'bb10' ) != -1
2386             && index( $ua, 'mobile' ) != -1 )
2387             || $device_tests->{psp}
2388             || $device_tests->{dsi}
2389 7970   100     402307 || $device_tests->{'n3ds'}
2390             );
2391             }
2392              
2393 8311 100 100     194420 if ( $browser_tests->{ucbrowser}
    100 100        
    100 100        
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
2394             && $self->{user_agent}
2395             =~ m{ucweb/2.0\s*\(([^\;\)]*\;){3,4}\s*([^\;\)]*?)\s*\)}i ) {
2396 276         966 $device_string = $2;
2397             }
2398             elsif ( $ua =~ /^(\bmot-[^ \/]+)/ ) {
2399 12         52 $device_string = substr $self->{user_agent}, 0, length $1;
2400 12         51 $device_string =~ s/^MOT-/Motorola /i;
2401             }
2402             elsif ( ( $browser_tests->{obigo} || index( $ua, 'brew' ) != -1 )
2403             && $self->{user_agent} =~ m{\d+x\d+ ([\d\w\- ]+?)( \S+\/\S+)*$}i ) {
2404 108         359 $device_string = $1;
2405             }
2406             elsif (
2407             $ua =~ /windows phone os [^\)]+ iemobile\/[^;]+; ([^;]+; [^;\)]+)/g )
2408             {
2409             # windows phone 7.x
2410             $device_string = substr $self->{user_agent},
2411 18         100 pos($ua) - length $1, length $1;
2412 18         75 $device_string =~ s/; / /;
2413             }
2414             elsif ( $ua
2415             =~ /windows phone [^\)]+ iemobile\/[^;]+; arm; touch; ([^;]+; [^;\)]+)/g
2416             ) {
2417             # windows phone 8.0
2418             $device_string = substr $self->{user_agent},
2419 12         57 pos($ua) - length $1, length $1;
2420 12         45 $device_string =~ s/; / /;
2421             }
2422             elsif (
2423             $ua =~ /windows phone 8[^\)]+ iemobile\/[^;]+; ([^;]+; [^;\)]+)/g ) {
2424              
2425             # windows phone 8.1
2426             $device_string = substr $self->{user_agent},
2427 18         91 pos($ua) - length $1, length $1;
2428 18         73 $device_string =~ s/; / /;
2429             }
2430             elsif ( $ua =~ /bb10; ([^;\)]+)/g ) {
2431             $device_string = 'BlackBerry ' . substr $self->{user_agent},
2432 12         67 pos($ua) - length $1, length $1;
2433 12         36 $device_string =~ s/Kbd/Q10/;
2434             }
2435             elsif ( $ua =~ /blackberry ([\w.]+)/ ) {
2436 6         25 $device_string = "BlackBerry $1";
2437             }
2438             elsif ( $ua =~ /blackberry(\d+)\// ) {
2439 18         71 $device_string = "BlackBerry $1";
2440             }
2441             elsif ( $ua =~ /silk-accelerated/ ) {
2442              
2443             # Only first generation Kindle Fires have that string
2444 12         29 $device_string = 'Kindle Fire';
2445 12         28 $device_tests->{kindlefire} = 1;
2446             }
2447             elsif ( $self->{user_agent} =~ /android .*\; ([^;]*) build/i ) {
2448 942         3153 my $model = $1;
2449 942 100 100     4632 if ( $model =~ m{^KF} || $model =~ m{kindle fire}i ) {
    100          
2450              
2451             # We might hit this even if tablet() is false, if we have
2452             # a Kindle Fire masquerading as a mobile device.
2453 54         117 $device_string = 'Kindle Fire';
2454 54         137 $device_tests->{kindlefire} = 1;
2455             }
2456             elsif ( $device_tests->{tablet} ) {
2457 78         259 $device_string = "Android tablet ($model)";
2458             }
2459             else {
2460 810         2521 $device_string = "Android ($model)";
2461             }
2462             }
2463             elsif ( $self->{user_agent}
2464             =~ /\b((alcatel|huawei|lg|nokia|samsung|sonyericsson)[\w\-]*)\//i ) {
2465 114         430 $device_string = $1;
2466             }
2467             elsif ( $self->{user_agent} =~ /CrKey/ ) {
2468 6         23 $device = 'chromecast';
2469 6         10 $device_string = 'Chromecast';
2470             }
2471             elsif ($device) {
2472 606         1788 $device_string = $DEVICE_NAMES{$device};
2473             }
2474             else {
2475 6151         13024 $device_string = undef;
2476             }
2477              
2478 8311 100       14488 if ($device) {
2479 2160         7398 $self->{device} = $device;
2480             }
2481             else {
2482             $self->{device}
2483 6151         16911 = undef; # Means we cache the fact that we found nothing
2484             }
2485              
2486 8311 100       22813 if ($device_string) {
2487 2154         5962 $self->{device_string} = $device_string;
2488             }
2489             }
2490              
2491             ### Now a big block of public accessors for tests and information
2492              
2493             sub browser {
2494 1726     1726 1 912734 my ($self) = @_;
2495 1726 50       5664 return undef unless defined $self->{user_agent};
2496 1726         7624 return $self->{browser};
2497             }
2498              
2499             sub browser_string {
2500 1695     1695 1 837757 my ($self) = @_;
2501 1695 50       5782 return undef unless defined $self->{user_agent};
2502 1695         6955 return $self->{browser_string};
2503             }
2504              
2505             sub robot {
2506 2254     2254 1 2396541 my $self = shift;
2507              
2508 2254 100       11427 $self->_init_robots unless exists( $self->{robot_string} );
2509 2254         8742 return $self->{robot_tests}->{robot};
2510             }
2511              
2512             sub robot_string {
2513 934     934 1 142616 my $self = shift;
2514              
2515 934 100       3875 $self->_init_robots unless exists( $self->{robot_string} );
2516 934         3238 return $self->{robot_string};
2517             }
2518              
2519             sub robot_name {
2520 140     140 0 101120 my $self = shift;
2521 140         447 return $self->robot_string;
2522             }
2523              
2524             sub robot_id {
2525 546     546 1 1696 my $self = shift;
2526             return
2527             $self->{robot_tests}->{robot_id} ? $self->{robot_tests}->{robot_id}
2528 546 0       2128 : $self->robot ? $ROBOT_IDS{ $self->robot }
    50          
2529             : undef;
2530             }
2531              
2532             sub _robot_version {
2533 415     415   788 my ($self) = @_;
2534 415 50       1244 $self->_init_robots unless exists( $self->{robot_string} );
2535 415 50       1170 if ( $self->{robot_version} ) {
2536 415         650 return @{ $self->{robot_version} };
  415         1988  
2537             }
2538             else {
2539 0         0 return ( undef, undef, undef );
2540             }
2541             }
2542              
2543             sub robot_version {
2544 106     106 1 74337 my ($self) = @_;
2545 106         339 my ( $major, $minor, $beta ) = $self->_robot_version;
2546 106 50       425 if ( defined $major ) {
2547 106 100       268 if ( defined $minor ) {
2548 105         640 return "$major$minor";
2549             }
2550             else {
2551 1         7 return $major;
2552             }
2553             }
2554             else {
2555 0         0 return undef;
2556             }
2557             }
2558              
2559             sub robot_major {
2560 102     102 1 57043 my ($self) = @_;
2561 102         364 my ( $major, $minor, $beta ) = $self->_robot_version;
2562 102         521 return $major;
2563             }
2564              
2565             sub robot_minor {
2566 101     101 1 53482 my ($self) = @_;
2567 101         334 my ( $major, $minor, $beta ) = $self->_robot_version;
2568 101         495 return $minor;
2569             }
2570              
2571             sub robot_beta {
2572 106     106 1 77887 my ($self) = @_;
2573 106         341 my ( $major, $minor, $beta ) = $self->_robot_version;
2574 106         556 return $beta;
2575             }
2576              
2577             sub os {
2578 4225     4225 1 776080 my ($self) = @_;
2579              
2580 4225 50       11496 return undef unless defined $self->{user_agent};
2581 4225 100       12609 $self->_init_os unless $self->{os_tests};
2582 4225         11424 return $self->{os};
2583             }
2584              
2585             sub os_string {
2586 4199     4199 1 759439 my ($self) = @_;
2587              
2588 4199 50       11357 return undef unless defined $self->{user_agent};
2589 4199 100       11207 $self->_init_os unless $self->{os_tests};
2590 4199         11057 return $self->{os_string};
2591             }
2592              
2593             sub _os_version {
2594 4011     4011   6986 my ($self) = @_;
2595 4011 100       14297 $self->_init_os_version if !exists( $self->{os_version} );
2596 4011 100       9752 if ( $self->{os_version} ) {
2597 1947         3147 return @{ $self->{os_version} };
  1947         9341  
2598             }
2599             else {
2600 2064         6329 return ( undef, undef, undef );
2601             }
2602             }
2603              
2604             sub os_version {
2605 998     998 1 302384 my ($self) = @_;
2606 998         3276 my ( $major, $minor, $beta ) = $self->_os_version;
2607 998 100       4753 return defined $major ? "$major$minor" : undef;
2608             }
2609              
2610             sub os_major {
2611 950     950 1 286607 my ($self) = @_;
2612 950         2915 my ( $major, $minor, $beta ) = $self->_os_version;
2613 950         3068 return $major;
2614             }
2615              
2616             sub os_minor {
2617 1029     1029 1 269086 my ($self) = @_;
2618 1029         3274 my ( $major, $minor, $beta ) = $self->_os_version;
2619 1029         3335 return $minor;
2620             }
2621              
2622             sub os_beta {
2623 1034     1034 1 334854 my ($self) = @_;
2624 1034         3505 my ( $major, $minor, $beta ) = $self->_os_version;
2625 1034         3985 return $beta;
2626             }
2627              
2628             sub _realplayer_version {
2629 0     0   0 my ($self) = @_;
2630              
2631 0 0       0 $self->_init_version unless $self->{version_tests};
2632 0   0     0 return $self->{realplayer_version} || 0;
2633             }
2634              
2635             sub realplayer_browser {
2636 740     740 1 106373 my ($self) = @_;
2637 740   100     4806 return defined( $self->{browser} ) && $self->{browser} eq 'realplayer';
2638             }
2639              
2640             sub gecko_version {
2641 684     684 1 90447 my ($self) = @_;
2642              
2643 684 100       2049 if ( $self->gecko ) {
2644 74         270 return $self->{engine_version};
2645             }
2646             else {
2647 610         1629 return undef;
2648             }
2649             }
2650              
2651             sub version {
2652 893     893 1 217008 my ($self) = @_;
2653 893 100       3673 $self->_init_version() unless $self->{version_tests};
2654              
2655 893 50       4741 return defined $self->{major} ? "$self->{major}$self->{minor}" : undef;
2656             }
2657              
2658             sub major {
2659 1174     1174 1 213164 my ($self) = @_;
2660 1174 100       3948 $self->_init_version() unless $self->{version_tests};
2661              
2662 1174         2760 my ($version) = $self->{major};
2663 1174         3652 return $version;
2664             }
2665              
2666             sub minor {
2667 964     964 1 216472 my ($self) = @_;
2668 964 100       3897 $self->_init_version() unless $self->{version_tests};
2669              
2670 964         3019 my ($version) = $self->{minor};
2671 964         2929 return $version;
2672             }
2673              
2674             sub public_version {
2675 883     883 1 209823 my ($self) = @_;
2676 883         3235 my ( $major, $minor ) = $self->_public;
2677              
2678 883   50     2782 $minor ||= q{};
2679 883 50       4490 return defined $major ? "$major$minor" : undef;
2680             }
2681              
2682             sub public_major {
2683 836     836 1 196202 my ($self) = @_;
2684 836         2785 my ( $major, $minor ) = $self->_public;
2685              
2686 836         2847 return $major;
2687             }
2688              
2689             sub public_minor {
2690 934     934 1 192449 my ($self) = @_;
2691 934         3132 my ( $major, $minor ) = $self->_public;
2692              
2693 934         3453 return $minor;
2694             }
2695              
2696             sub public_beta {
2697 0     0 1 0 my ($self) = @_;
2698 0         0 my ( $major, $minor, $beta ) = $self->_public;
2699              
2700 0         0 return $beta;
2701             }
2702              
2703             sub browser_version {
2704 22     22 1 16524 my ($self) = @_;
2705 22         81 my ( $major, $minor ) = $self->_public;
2706 22   100     105 $minor ||= q{};
2707              
2708 22 100       175 return defined $major ? "$major$minor" : undef;
2709             }
2710              
2711             sub browser_major {
2712 1451     1451 1 673467 my ($self) = @_;
2713 1451         4464 my ( $major, $minor ) = $self->_public;
2714              
2715 1451         8166 return $major;
2716             }
2717              
2718             sub browser_minor {
2719 911     911 1 655662 my ($self) = @_;
2720 911         2851 my ( $major, $minor ) = $self->_public;
2721              
2722 911         4592 return $minor;
2723             }
2724              
2725             sub browser_beta {
2726 369     369 1 278191 my ($self) = @_;
2727 369         1206 my ( $major, $minor, $beta ) = $self->_public;
2728              
2729 369         2100 return $beta;
2730             }
2731              
2732             sub _public {
2733 5406     5406   10697 my ($self) = @_;
2734              
2735             # Return Public version of Safari. See RT #48727.
2736 5406 100       13083 if ( $self->safari ) {
2737 657         2454 my $ua = lc $self->{user_agent};
2738              
2739             # Safari starting with version 3.0 provides its own public version
2740 657 100       4255 if (
2741             $ua =~ m{
2742             version/
2743             ( \d+ ) # Major version number is everything before first dot
2744             ( \. \d+ )? # Minor version number is first dot and following digits
2745             }x
2746             ) {
2747 473         2981 return ( $1, $2, undef );
2748             }
2749              
2750             # Safari before version 3.0 had only build numbers; use a lookup table
2751             # provided by Apple to convert to version numbers
2752              
2753 184 100       1217 if ( $ua =~ m{ safari/ ( \d+ (?: \.\d+ )* ) }x ) {
2754 152         463 my $build = $1;
2755 152         341 my $version = $safari_build_to_version{$build};
2756 152 100       392 unless ($version) {
2757              
2758             # if exact build -> version mapping doesn't exist, find next
2759             # lower build
2760              
2761 111         1037 for my $maybe_build (
2762 8941         14672 sort { $self->_cmp_versions( $b, $a ) }
2763             keys %safari_build_to_version
2764             ) {
2765 510 100       1012 $version = $safari_build_to_version{$maybe_build}, last
2766             if $self->_cmp_versions( $build, $maybe_build ) >= 0;
2767             }
2768              
2769             # Special case for specific worm that uses a malformed user agent
2770 111 100       613 return ( '1', '.2', undef ) if $ua =~ m{safari/12x};
2771             }
2772              
2773 148 100       407 return ( undef, undef, undef ) unless defined $version;
2774 141         550 my ( $major, $minor ) = split /\./, $version;
2775 141         272 my $beta;
2776 141 100       503 $minor =~ s/(\D.*)// and $beta = $1;
2777 141         344 $minor = ( '.' . $minor );
2778 141 100       815 return ( $major, $minor, ( $beta ? 1 : undef ) );
2779             }
2780             }
2781              
2782 4781 100       15802 $self->_init_version() unless $self->{version_tests};
2783 4781         19940 return ( $self->{major}, $self->{minor}, $self->{beta} );
2784             }
2785              
2786             sub _cmp_versions {
2787 9451     9451   15517 my ( $self, $a, $b ) = @_;
2788              
2789 9451         17733 my @a = split /\./, $a;
2790 9451         15234 my @b = split /\./, $b;
2791              
2792 9451         16429 while (@b) {
2793 10920 100 100     37713 return -1 if @a == 0 || $a[0] < $b[0];
2794 5537 100 66     19855 return 1 if @b == 0 || $b[0] < $a[0];
2795 1783         2449 shift @a;
2796 1783         3411 shift @b;
2797             }
2798              
2799 314         679 return @a <=> @b;
2800             }
2801              
2802             sub engine {
2803 1584     1584 1 743703 my ($self) = @_;
2804              
2805             return
2806 1584 100       4862 !$self->engine_string ? undef
    100          
2807             : $self->engine_string eq 'MSIE' ? 'ie'
2808             : lc( $self->engine_string );
2809             }
2810              
2811             sub engine_string {
2812 5671     5671 1 679240 my ($self) = @_;
2813              
2814 5671 100       12406 if ( $self->gecko ) {
2815 750         3236 return 'Gecko';
2816             }
2817              
2818 4921 100       10913 if ( $self->trident ) {
2819 1016         4374 return 'Trident';
2820             }
2821              
2822 3905 100       8299 if ( $self->ie ) {
2823 576         2715 return 'MSIE';
2824             }
2825              
2826 3329 100       7658 if ( $self->edgelegacy ) {
2827 31         142 return 'EdgeHTML';
2828             }
2829              
2830 3298 100       7039 if ( $self->webkit ) {
2831 2727         12969 return 'WebKit';
2832             }
2833              
2834 571 100       1501 if ( $self->presto ) {
2835 147         697 return 'Presto';
2836             }
2837              
2838 424 100       1077 if ( $self->netfront ) {
2839 45         199 return 'NetFront';
2840             }
2841              
2842 379 100       920 if ( $self->khtml ) {
2843 11         53 return 'KHTML';
2844             }
2845              
2846 368         1066 return undef;
2847             }
2848              
2849             sub engine_version {
2850 1574     1574 1 526365 my ($self) = @_;
2851              
2852             return $self->{engine_version}
2853 1574 100 100     18394 && $self->{engine_version} =~ m{^(\d+(\.\d+)?)} ? $1 : undef;
2854             }
2855              
2856             sub engine_major {
2857 2224     2224 1 618544 my ($self) = @_;
2858              
2859             return $self->{engine_version}
2860 2224 100 100     20076 && $self->{engine_version} =~ m{^(\d+)} ? $1 : undef;
2861             }
2862              
2863             sub engine_minor {
2864 2112     2112 1 574960 my ($self) = @_;
2865              
2866             return $self->{engine_version}
2867 2112 100 100     20023 && $self->{engine_version} =~ m{^\d+(\.\d+)} ? $1 : undef;
2868             }
2869              
2870             sub engine_beta {
2871 1424     1424 1 614612 my ($self) = @_;
2872              
2873             return $self->{engine_version}
2874 1424 100 100     17352 && $self->{engine_version} =~ m{^\d+\.\d+([\.\d\+]*)} ? $1 : undef;
2875             }
2876              
2877             sub beta {
2878 732     732 1 98976 my ($self) = @_;
2879              
2880 732 100       2872 $self->_init_version unless $self->{version_tests};
2881              
2882 732         2735 my ($version) = $self->{beta};
2883 732         1993 return $version;
2884             }
2885              
2886             sub language {
2887 1133     1133 1 350615 my ($self) = @_;
2888              
2889 1133         3338 my $parsed = $self->_language_country();
2890 1133         4932 return $parsed->{'language'};
2891             }
2892              
2893             sub country {
2894 719     719 1 96711 my ($self) = @_;
2895              
2896 719         2042 my $parsed = $self->_language_country();
2897 719         2607 return $parsed->{'country'};
2898             }
2899              
2900             sub device {
2901 4166     4166 1 803794 my ($self) = @_;
2902              
2903 4166 50       10762 $self->_init_device if !exists( $self->{device} );
2904 4166         11984 return $self->{device};
2905             }
2906              
2907             sub device_string {
2908 1837     1837 1 361151 my ($self) = @_;
2909              
2910 1837 100       7800 $self->_init_device if !exists( $self->{device_string} );
2911 1837         6721 return $self->{device_string};
2912             }
2913              
2914             sub device_name {
2915 682     682 1 91306 my ($self) = @_;
2916 682         1916 return $self->device_string;
2917             }
2918              
2919             sub _language_country {
2920 1852     1852   3370 my ($self) = @_;
2921              
2922 1852 100       4172 if ( $self->safari ) {
2923 267 100 66     859 if ( $self->major == 1
2924             && $self->{user_agent} =~ m/\s ( [a-z]{2} ) \)/xms ) {
2925 8         59 return { language => uc $1 };
2926             }
2927 259 100       1600 if ( $self->{user_agent} =~ m/\s ([a-z]{2})-([A-Za-z]{2})/xms ) {
2928 149         1049 return { language => uc $1, country => uc $2 };
2929             }
2930             }
2931              
2932 1695 100 100     5041 if ( $self->aol
2933             && $self->{user_agent} =~ m/;([A-Z]{2})_([A-Z]{2})\)/ ) {
2934 3         21 return { language => $1, country => $2 };
2935             }
2936              
2937 1692 100 100     4955 if ( $self->meta_app
2938             && $self->{user_agent}
2939             =~ m{ ;FBLC/ ([a-z]{2}) (?: [_-] ([A-Z]{2}) )? }x ) {
2940 12 100       115 return { language => uc $1, $2 ? ( country => $2 ) : () };
2941             }
2942              
2943 1680 100 66     4609 if ( $self->instagram
2944             && $self->{user_agent}
2945             =~ m{ (?: [(] | ;[ ] ) ([a-z]{2}) [_-] ([A-Z]{2}) [;)] }x ) {
2946 4 50       43 return { language => uc $1, $2 ? ( country => $2 ) : () };
2947             }
2948              
2949 1676 100       8385 if ( $self->{user_agent} =~ m/\b([a-z]{2})-([A-Za-z]{2})\b/xms ) {
2950 338         2529 return { language => uc $1, country => uc $2 };
2951             }
2952              
2953 1338 100       4287 if ( $self->{user_agent} =~ m/\[([a-z]{2})\]/xms ) {
2954 28         173 return { language => uc $1 };
2955             }
2956              
2957 1310 100       7326 if ( $self->{user_agent} =~ m/\(([^)]+)\)/xms ) {
2958 1156         6006 my @parts = split( /;/, $1 );
2959 1156         2879 foreach my $part (@parts) {
2960              
2961             # 'wv' for WebView is not language code. Details here: https://developer.chrome.com/multidevice/user-agent#webview_user_agent
2962 4850 100 66     16465 if ( $part =~ /^\s*([a-z]{2})\s*$/
      100        
2963             && !( $self->webview && $1 eq 'wv' ) ) {
2964 144         979 return { language => uc $1 };
2965             }
2966             }
2967             }
2968              
2969 1166         4572 return { language => undef, country => undef };
2970             }
2971              
2972             sub browser_properties {
2973 1938     1938 1 528683 my ($self) = @_;
2974              
2975 1938         3969 my @browser_properties;
2976              
2977 1938         4546 my ( $test, $value );
2978              
2979 1938         4131 while ( ( $test, $value ) = each %{ $self->{tests} } ) {
  3709         15578  
2980 1771 50       5667 push @browser_properties, $test if $value;
2981             }
2982 1938         4096 while ( ( $test, $value ) = each %{ $self->{browser_tests} } ) {
  3677         12169  
2983 1739 100       5537 push @browser_properties, $test if $value;
2984             }
2985              
2986 1938 50       5435 $self->_init_device unless $self->{device_tests};
2987 1938 100       6357 $self->_init_os unless $self->{os_tests};
2988 1938 100       6631 $self->_init_robots unless $self->{robot_tests};
2989 1938 100       5512 $self->_init_version unless $self->{version_tests};
2990              
2991 1938         3540 while ( ( $test, $value ) = each %{ $self->{device_tests} } ) {
  6296         19358  
2992 4358 100       9991 push @browser_properties, $test if $value;
2993             }
2994 1938         3688 while ( ( $test, $value ) = each %{ $self->{os_tests} } ) {
  5794         15813  
2995 3856 50       8903 push @browser_properties, $test if $value;
2996             }
2997 1938         3620 while ( ( $test, $value ) = each %{ $self->{robot_tests} } ) {
  2709         9188  
2998 771 50       1890 push @browser_properties, $test if $value;
2999             }
3000 1938         3783 while ( ( $test, $value ) = each %{ $self->{version_tests} } ) {
  4371         12096  
3001 2433 100       6947 push @browser_properties, $test if $value;
3002             }
3003              
3004             # devices are a property too but it's not stored in %tests
3005             # so I explicitly test for it and add it
3006 1938 100       6182 push @browser_properties, 'device' if ( $self->device() );
3007              
3008 1938         11647 @browser_properties = sort @browser_properties;
3009 1938         7301 return @browser_properties;
3010             }
3011              
3012             sub lib {
3013 28     28 1 20656 my $self = shift;
3014 28 100       112 $self->_init_robots() unless $self->{robot_tests};
3015 28         114 return $self->{robot_tests}->{lib};
3016             }
3017              
3018             sub all_robot_ids {
3019 2     2 1 112 my $self = shift;
3020 2         47 return keys %ROBOT_NAMES;
3021             }
3022              
3023             # The list of U2F supported browsers is expected to increase:
3024             # https://www.bit-tech.net/news/tech/software/w3c-adopts-fidos-webauthn-standard/1/
3025              
3026             sub u2f {
3027 0     0 1 0 my $self = shift;
3028              
3029             # Chrome version 41 and up can U2F
3030 0 0 0     0 return 1
      0        
3031             if $self->chrome
3032             && $self->browser_major
3033             && $self->browser_major >= 41;
3034              
3035             # Opera versions 40 and >= 42 can U2F
3036 0 0 0     0 return 1
      0        
      0        
3037             if $self->opera
3038             && $self->browser_major
3039             && ( $self->browser_major == 40
3040             || $self->browser_major >= 42 );
3041              
3042 0         0 return undef;
3043             }
3044              
3045             # These method are only used by the test suite.
3046             sub _all_tests {
3047 1     1   6703425 return @ALL_TESTS;
3048             }
3049              
3050             sub _robot_names {
3051 1     1   84 return %ROBOT_NAMES;
3052             }
3053              
3054             sub _robot_tests {
3055 2     2   7098186 return @ROBOT_TESTS;
3056             }
3057              
3058             sub _robot_ids {
3059 1     1   28 return %ROBOT_IDS;
3060             }
3061              
3062             1;
3063              
3064             # ABSTRACT: Determine Web browser, version, and platform from an HTTP user agent string
3065              
3066             __END__