UserAgent.pm 9 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

package Bugzilla::UserAgent;

10
use 5.10.1;
11
use strict;
12
use warnings;
13

14
use base qw(Exporter);
15 16 17 18 19 20 21 22
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 => (
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 50 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

  # 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"],
90 91 92
);

use constant OS_MAP => (
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 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 178 179 180 181 182 183 184 185 186 187 188 189 190

  # 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"],
191 192 193
);

sub detect_platform {
194 195 196 197 198 199
  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;
200
    }
201 202
  }
  return _pick_valid_field_value('rep_platform', @detected);
203 204 205
}

sub detect_op_sys {
206 207 208 209 210 211
  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;
212
    }
213 214 215 216
  }
  push(@detected, "Windows") if grep(/^Windows /, @detected);
  push(@detected, "Mac OS")  if grep(/^Mac /,     @detected);
  return _pick_valid_field_value('op_sys', @detected);
217 218 219 220 221 222 223
}

# 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 {
224 225 226 227 228
  my ($field, @values) = @_;
  foreach my $value (@values) {
    return $value if check_field($field, $value, undef, 1);
  }
  return DEFAULT_VALUE;
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
}

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