UserAgent.pm 8.79 KB
Newer Older
1 2 3
# 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/.
4
#
5 6
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

package Bugzilla::UserAgent;

use strict;
use base 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
50
    qr/\(.*(?:iPod|iPad|iPhone).*\)/ => ["ARM"],
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
    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"],
103
    qr/\(.*Android.*\)/ => ["Android"],
104 105
    # Windows
    qr/\(.*Windows XP.*\)/ => ["Windows XP"],
106
    qr/\(.*Windows NT 6\.4.*\)/ => ["Windows 10"],
107
    qr/\(.*Windows NT 6\.3.*\)/ => ["Windows 8.1"],
108
    qr/\(.*Windows NT 6\.2.*\)/ => ["Windows 8"],
109 110 111 112 113 114 115 116 117 118 119 120 121 122
    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
123 124 125 126 127
    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"],
128
    qr/\(.*(?:iPod|iPad|iPhone).*\)/ => ["iOS"],
129
    qr/\(.*Mac OS X (?:|Mach-O |\()10.8.*\)/ => ["Mac OS X 10.8"],
130
    qr/\(.*Mac OS X (?:|Mach-O |\()10.7.*\)/ => ["Mac OS X 10.7"],
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
    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 {
178
    my $userAgent = $ENV{'HTTP_USER_AGENT'} || '';
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
    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