244 lines
8.9 KiB
Perl
244 lines
8.9 KiB
Perl
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
#
|
|
# This Source Code Form is "Incompatible With Secondary Licenses", as
|
|
# defined by the Mozilla Public License, v. 2.0.
|
|
|
|
package Bugzilla::UserAgent;
|
|
|
|
use 5.10.1;
|
|
use strict;
|
|
use warnings;
|
|
|
|
use parent qw(Exporter);
|
|
our @EXPORT = qw(detect_platform detect_op_sys);
|
|
|
|
use Bugzilla::Field;
|
|
use List::MoreUtils qw(natatime);
|
|
|
|
use constant DEFAULT_VALUE => 'Other';
|
|
|
|
use constant PLATFORMS_MAP => (
|
|
# PowerPC
|
|
qr/\(.*PowerPC.*\)/i => ["PowerPC", "Macintosh"],
|
|
# AMD64, Intel x86_64
|
|
qr/\(.*[ix0-9]86 (?:on |\()x86_64.*\)/ => ["IA32", "x86", "PC"],
|
|
qr/\(.*amd64.*\)/ => ["AMD64", "x86_64", "PC"],
|
|
qr/\(.*x86_64.*\)/ => ["AMD64", "x86_64", "PC"],
|
|
# Intel IA64
|
|
qr/\(.*IA64.*\)/ => ["IA64", "PC"],
|
|
# Intel x86
|
|
qr/\(.*Intel.*\)/ => ["IA32", "x86", "PC"],
|
|
qr/\(.*[ix0-9]86.*\)/ => ["IA32", "x86", "PC"],
|
|
# Versions of Windows that only run on Intel x86
|
|
qr/\(.*Win(?:dows |)[39M].*\)/ => ["IA32", "x86", "PC"],
|
|
qr/\(.*Win(?:dows |)16.*\)/ => ["IA32", "x86", "PC"],
|
|
# Sparc
|
|
qr/\(.*sparc.*\)/ => ["Sparc", "Sun"],
|
|
qr/\(.*sun4.*\)/ => ["Sparc", "Sun"],
|
|
# Alpha
|
|
qr/\(.*AXP.*\)/i => ["Alpha", "DEC"],
|
|
qr/\(.*[ _]Alpha.\D/i => ["Alpha", "DEC"],
|
|
qr/\(.*[ _]Alpha\)/i => ["Alpha", "DEC"],
|
|
# MIPS
|
|
qr/\(.*IRIX.*\)/i => ["MIPS", "SGI"],
|
|
qr/\(.*MIPS.*\)/i => ["MIPS", "SGI"],
|
|
# 68k
|
|
qr/\(.*68K.*\)/ => ["68k", "Macintosh"],
|
|
qr/\(.*680[x0]0.*\)/ => ["68k", "Macintosh"],
|
|
# HP
|
|
qr/\(.*9000.*\)/ => ["PA-RISC", "HP"],
|
|
# ARM
|
|
qr/\(.*(?:iPod|iPad|iPhone).*\)/ => ["ARM"],
|
|
qr/\(.*ARM.*\)/ => ["ARM", "PocketPC"],
|
|
# PocketPC intentionally before PowerPC
|
|
qr/\(.*Windows CE.*PPC.*\)/ => ["ARM", "PocketPC"],
|
|
# PowerPC
|
|
qr/\(.*PPC.*\)/ => ["PowerPC", "Macintosh"],
|
|
qr/\(.*AIX.*\)/ => ["PowerPC", "Macintosh"],
|
|
# Stereotypical and broken
|
|
qr/\(.*Windows CE.*\)/ => ["ARM", "PocketPC"],
|
|
qr/\(.*Macintosh.*\)/ => ["68k", "Macintosh"],
|
|
qr/\(.*Mac OS [89].*\)/ => ["68k", "Macintosh"],
|
|
qr/\(.*WOW64.*\)/ => ["x86_64"],
|
|
qr/\(.*Win64.*\)/ => ["IA64"],
|
|
qr/\(Win.*\)/ => ["IA32", "x86", "PC"],
|
|
qr/\(.*Win(?:dows[ -])NT.*\)/ => ["IA32", "x86", "PC"],
|
|
qr/\(.*OSF.*\)/ => ["Alpha", "DEC"],
|
|
qr/\(.*HP-?UX.*\)/i => ["PA-RISC", "HP"],
|
|
qr/\(.*IRIX.*\)/i => ["MIPS", "SGI"],
|
|
qr/\(.*(SunOS|Solaris).*\)/ => ["Sparc", "Sun"],
|
|
# Braindead old browsers who didn't follow convention:
|
|
qr/Amiga/ => ["68k", "Macintosh"],
|
|
qr/WinMosaic/ => ["IA32", "x86", "PC"],
|
|
);
|
|
|
|
use constant OS_MAP => (
|
|
# Sun
|
|
qr/\(.*Solaris.*\)/ => ["Solaris"],
|
|
qr/\(.*SunOS 5.11.*\)/ => [("OpenSolaris", "Opensolaris", "Solaris 11")],
|
|
qr/\(.*SunOS 5.10.*\)/ => ["Solaris 10"],
|
|
qr/\(.*SunOS 5.9.*\)/ => ["Solaris 9"],
|
|
qr/\(.*SunOS 5.8.*\)/ => ["Solaris 8"],
|
|
qr/\(.*SunOS 5.7.*\)/ => ["Solaris 7"],
|
|
qr/\(.*SunOS 5.6.*\)/ => ["Solaris 6"],
|
|
qr/\(.*SunOS 5.5.*\)/ => ["Solaris 5"],
|
|
qr/\(.*SunOS 5.*\)/ => ["Solaris"],
|
|
qr/\(.*SunOS.*sun4u.*\)/ => ["Solaris"],
|
|
qr/\(.*SunOS.*i86pc.*\)/ => ["Solaris"],
|
|
qr/\(.*SunOS.*\)/ => ["SunOS"],
|
|
# BSD
|
|
qr/\(.*BSD\/(?:OS|386).*\)/ => ["BSDI"],
|
|
qr/\(.*FreeBSD.*\)/ => ["FreeBSD"],
|
|
qr/\(.*OpenBSD.*\)/ => ["OpenBSD"],
|
|
qr/\(.*NetBSD.*\)/ => ["NetBSD"],
|
|
# Misc POSIX
|
|
qr/\(.*IRIX.*\)/ => ["IRIX"],
|
|
qr/\(.*OSF.*\)/ => ["OSF/1"],
|
|
qr/\(.*Linux.*\)/ => ["Linux"],
|
|
qr/\(.*BeOS.*\)/ => ["BeOS"],
|
|
qr/\(.*AIX.*\)/ => ["AIX"],
|
|
qr/\(.*OS\/2.*\)/ => ["OS/2"],
|
|
qr/\(.*QNX.*\)/ => ["Neutrino"],
|
|
qr/\(.*VMS.*\)/ => ["OpenVMS"],
|
|
qr/\(.*HP-?UX.*\)/ => ["HP-UX"],
|
|
qr/\(.*Android.*\)/ => ["Android"],
|
|
# Windows
|
|
qr/\(.*Windows XP.*\)/ => ["Windows XP"],
|
|
qr/\(.*Windows NT 10\.0.*\)/ => ["Windows 10"],
|
|
qr/\(.*Windows NT 6\.4.*\)/ => ["Windows 10"],
|
|
qr/\(.*Windows NT 6\.3.*\)/ => ["Windows 8.1"],
|
|
qr/\(.*Windows NT 6\.2.*\)/ => ["Windows 8"],
|
|
qr/\(.*Windows NT 6\.1.*\)/ => ["Windows 7"],
|
|
qr/\(.*Windows NT 6\.0.*\)/ => ["Windows Vista"],
|
|
qr/\(.*Windows NT 5\.2.*\)/ => ["Windows Server 2003"],
|
|
qr/\(.*Windows NT 5\.1.*\)/ => ["Windows XP"],
|
|
qr/\(.*Windows 2000.*\)/ => ["Windows 2000"],
|
|
qr/\(.*Windows NT 5.*\)/ => ["Windows 2000"],
|
|
qr/\(.*Win.*9[8x].*4\.9.*\)/ => ["Windows ME"],
|
|
qr/\(.*Win(?:dows |)M[Ee].*\)/ => ["Windows ME"],
|
|
qr/\(.*Win(?:dows |)98.*\)/ => ["Windows 98"],
|
|
qr/\(.*Win(?:dows |)95.*\)/ => ["Windows 95"],
|
|
qr/\(.*Win(?:dows |)16.*\)/ => ["Windows 3.1"],
|
|
qr/\(.*Win(?:dows[ -]|)NT.*\)/ => ["Windows NT"],
|
|
qr/\(.*Windows.*NT.*\)/ => ["Windows NT"],
|
|
# OS X
|
|
qr/\(.*(?:iPad|iPhone).*OS 7.*\)/ => ["iOS 7"],
|
|
qr/\(.*(?:iPad|iPhone).*OS 6.*\)/ => ["iOS 6"],
|
|
qr/\(.*(?:iPad|iPhone).*OS 5.*\)/ => ["iOS 5"],
|
|
qr/\(.*(?:iPad|iPhone).*OS 4.*\)/ => ["iOS 4"],
|
|
qr/\(.*(?:iPad|iPhone).*OS 3.*\)/ => ["iOS 3"],
|
|
qr/\(.*(?:iPod|iPad|iPhone).*\)/ => ["iOS"],
|
|
qr/\(.*Mac OS X (?:|Mach-O |\()10.8.*\)/ => ["Mac OS X 10.8"],
|
|
qr/\(.*Mac OS X (?:|Mach-O |\()10.7.*\)/ => ["Mac OS X 10.7"],
|
|
qr/\(.*Mac OS X (?:|Mach-O |\()10.6.*\)/ => ["Mac OS X 10.6"],
|
|
qr/\(.*Mac OS X (?:|Mach-O |\()10.5.*\)/ => ["Mac OS X 10.5"],
|
|
qr/\(.*Mac OS X (?:|Mach-O |\()10.4.*\)/ => ["Mac OS X 10.4"],
|
|
qr/\(.*Mac OS X (?:|Mach-O |\()10.3.*\)/ => ["Mac OS X 10.3"],
|
|
qr/\(.*Mac OS X (?:|Mach-O |\()10.2.*\)/ => ["Mac OS X 10.2"],
|
|
qr/\(.*Mac OS X (?:|Mach-O |\()10.1.*\)/ => ["Mac OS X 10.1"],
|
|
# Unfortunately, OS X 10.4 was the first to support Intel. This is fallback
|
|
# support because some browsers refused to include the OS Version.
|
|
qr/\(.*Intel.*Mac OS X.*\)/ => ["Mac OS X 10.4"],
|
|
# OS X 10.3 is the most likely default version of PowerPC Macs
|
|
# OS X 10.0 is more for configurations which didn't setup 10.x versions
|
|
qr/\(.*Mac OS X.*\)/ => [("Mac OS X 10.3", "Mac OS X 10.0", "Mac OS X")],
|
|
qr/\(.*Mac OS 9.*\)/ => [("Mac System 9.x", "Mac System 9.0")],
|
|
qr/\(.*Mac OS 8\.6.*\)/ => [("Mac System 8.6", "Mac System 8.5")],
|
|
qr/\(.*Mac OS 8\.5.*\)/ => ["Mac System 8.5"],
|
|
qr/\(.*Mac OS 8\.1.*\)/ => [("Mac System 8.1", "Mac System 8.0")],
|
|
qr/\(.*Mac OS 8\.0.*\)/ => ["Mac System 8.0"],
|
|
qr/\(.*Mac OS 8[^.].*\)/ => ["Mac System 8.0"],
|
|
qr/\(.*Mac OS 8.*\)/ => ["Mac System 8.6"],
|
|
qr/\(.*Darwin.*\)/ => [("Mac OS X 10.0", "Mac OS X")],
|
|
# Silly
|
|
qr/\(.*Mac.*PowerPC.*\)/ => ["Mac System 9.x"],
|
|
qr/\(.*Mac.*PPC.*\)/ => ["Mac System 9.x"],
|
|
qr/\(.*Mac.*68k.*\)/ => ["Mac System 8.0"],
|
|
# Evil
|
|
qr/Amiga/i => ["Other"],
|
|
qr/WinMosaic/ => ["Windows 95"],
|
|
qr/\(.*32bit.*\)/ => ["Windows 95"],
|
|
qr/\(.*16bit.*\)/ => ["Windows 3.1"],
|
|
qr/\(.*PowerPC.*\)/ => ["Mac System 9.x"],
|
|
qr/\(.*PPC.*\)/ => ["Mac System 9.x"],
|
|
qr/\(.*68K.*\)/ => ["Mac System 8.0"],
|
|
);
|
|
|
|
sub detect_platform {
|
|
my $userAgent = $ENV{'HTTP_USER_AGENT'};
|
|
my @detected;
|
|
my $iterator = natatime(2, PLATFORMS_MAP);
|
|
while (my($re, $ra) = $iterator->()) {
|
|
if ($userAgent =~ $re) {
|
|
push @detected, @$ra;
|
|
}
|
|
}
|
|
return _pick_valid_field_value('rep_platform', @detected);
|
|
}
|
|
|
|
sub detect_op_sys {
|
|
my $userAgent = $ENV{'HTTP_USER_AGENT'} || '';
|
|
my @detected;
|
|
my $iterator = natatime(2, OS_MAP);
|
|
while (my($re, $ra) = $iterator->()) {
|
|
if ($userAgent =~ $re) {
|
|
push @detected, @$ra;
|
|
}
|
|
}
|
|
push(@detected, "Windows") if grep(/^Windows /, @detected);
|
|
push(@detected, "Mac OS") if grep(/^Mac /, @detected);
|
|
return _pick_valid_field_value('op_sys', @detected);
|
|
}
|
|
|
|
# Takes the name of a field and a list of possible values for that field.
|
|
# Returns the first value in the list that is actually a valid value for that
|
|
# field.
|
|
# Returns 'Other' if none of the values match.
|
|
sub _pick_valid_field_value {
|
|
my ($field, @values) = @_;
|
|
foreach my $value (@values) {
|
|
return $value if check_field($field, $value, undef, 1);
|
|
}
|
|
return DEFAULT_VALUE;
|
|
}
|
|
|
|
1;
|
|
|
|
__END__
|
|
|
|
=head1 NAME
|
|
|
|
Bugzilla::UserAgent - UserAgent utilities for Bugzilla
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
use Bugzilla::UserAgent;
|
|
printf "platform: %s op-sys: %s\n", detect_platform(), detect_op_sys();
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
The functions exported by this module all return information derived from the
|
|
remote client's user agent.
|
|
|
|
=head1 FUNCTIONS
|
|
|
|
=over 4
|
|
|
|
=item C<detect_platform>
|
|
|
|
This function attempts to detect the remote client's platform from the
|
|
presented user-agent. If a suitable value on the I<platform> field is found,
|
|
that field value will be returned. If no suitable value is detected,
|
|
C<detect_platform> returns I<Other>.
|
|
|
|
=item C<detect_op_sys>
|
|
|
|
This function attempts to detect the remote client's operating system from the
|
|
presented user-agent. If a suitable value on the I<op_sys> field is found, that
|
|
field value will be returned. If no suitable value is detected,
|
|
C<detect_op_sys> returns I<Other>.
|
|
|
|
=back
|