blob: 5ce4db366613ea1c39334ec71c9af73ec0c04c12 [file] [log] [blame]
# 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::BugUrl;
use 5.10.1;
use strict;
use warnings;
use parent qw(Bugzilla::Object);
use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::Constants;
use Bugzilla::Hook;
use URI;
use URI::QueryParam;
###############################
#### Initialization ####
###############################
use constant DB_TABLE => 'bug_see_also';
use constant NAME_FIELD => 'value';
use constant LIST_ORDER => 'id';
# See Also is tracked in bugs_activity.
use constant AUDIT_CREATES => 0;
use constant AUDIT_UPDATES => 0;
use constant AUDIT_REMOVES => 0;
use constant DB_COLUMNS => qw(
id
bug_id
value
class
);
# This must be strings with the names of the validations,
# instead of coderefs, because subclasses override these
# validators with their own.
use constant VALIDATORS => {
value => '_check_value',
bug_id => '_check_bug_id',
class => \&_check_class,
};
# This is the order we go through all of subclasses and
# pick the first one that should handle the url. New
# subclasses should be added at the end of the list.
use constant SUB_CLASSES => qw(
Bugzilla::BugUrl::Bugzilla::Local
Bugzilla::BugUrl::Bugzilla
Bugzilla::BugUrl::Launchpad
Bugzilla::BugUrl::Google
Bugzilla::BugUrl::Chromium
Bugzilla::BugUrl::Debian
Bugzilla::BugUrl::JIRA
Bugzilla::BugUrl::Trac
Bugzilla::BugUrl::MantisBT
Bugzilla::BugUrl::SourceForge
Bugzilla::BugUrl::GitHub
);
###############################
#### Accessors ######
###############################
sub class { return $_[0]->{class} }
sub bug_id { return $_[0]->{bug_id} }
###############################
#### Methods ####
###############################
sub new {
my $class = shift;
my $param = shift;
if (ref $param) {
my $bug_id = $param->{bug_id};
my $name = $param->{name} || $param->{value};
if (!defined $bug_id) {
ThrowCodeError('bad_arg',
{ argument => 'bug_id',
function => "${class}::new" });
}
if (!defined $name) {
ThrowCodeError('bad_arg',
{ argument => 'name',
function => "${class}::new" });
}
my $condition = 'bug_id = ? AND value = ?';
my @values = ($bug_id, $name);
$param = { condition => $condition, values => \@values };
}
unshift @_, $param;
return $class->SUPER::new(@_);
}
sub _do_list_select {
my $class = shift;
my $objects = $class->SUPER::_do_list_select(@_);
foreach my $object (@$objects) {
eval "use " . $object->class;
# If the class cannot be loaded, then we build a generic object.
bless $object, ($@ ? 'Bugzilla::BugUrl' : $object->class);
}
return $objects
}
# This is an abstract method. It must be overridden
# in every subclass.
sub should_handle {
my ($class, $input) = @_;
ThrowCodeError('unknown_method',
{ method => "${class}::should_handle" });
}
sub class_for {
my ($class, $value) = @_;
my @sub_classes = $class->SUB_CLASSES;
Bugzilla::Hook::process("bug_url_sub_classes",
{ sub_classes => \@sub_classes });
my $uri = URI->new($value);
foreach my $subclass (@sub_classes) {
eval "use $subclass";
die $@ if $@;
return wantarray ? ($subclass, $uri) : $subclass
if $subclass->should_handle($uri);
}
ThrowUserError('bug_url_invalid', { url => $value });
}
sub _check_class {
my ($class, $subclass) = @_;
eval "use $subclass"; die $@ if $@;
return $subclass;
}
sub _check_bug_id {
my ($class, $bug_id) = @_;
my $bug;
if (blessed $bug_id) {
# We got a bug object passed in, use it
$bug = $bug_id;
$bug->check_is_visible;
}
else {
# We got a bug id passed in, check it and get the bug object
$bug = Bugzilla::Bug->check({ id => $bug_id });
}
return $bug->id;
}
sub _check_value {
my ($class, $uri) = @_;
my $value = $uri->as_string;
if (!$value) {
ThrowCodeError('param_required',
{ function => 'add_see_also', param => '$value' });
}
# We assume that the URL is an HTTP URL if there is no (something)://
# in front.
if (!$uri->scheme) {
# This works better than setting $uri->scheme('http'), because
# that creates URLs like "http:domain.com" and doesn't properly
# differentiate the path from the domain.
$uri = new URI("http://$value");
}
elsif ($uri->scheme ne 'http' && $uri->scheme ne 'https') {
ThrowUserError('bug_url_invalid', { url => $value, reason => 'http' });
}
# This stops the following edge cases from being accepted:
# * show_bug.cgi?id=1
# * /show_bug.cgi?id=1
# * http:///show_bug.cgi?id=1
if (!$uri->authority or $uri->path !~ m{/}) {
ThrowUserError('bug_url_invalid',
{ url => $value, reason => 'path_only' });
}
if (length($uri->path) > MAX_BUG_URL_LENGTH) {
ThrowUserError('bug_url_too_long', { url => $uri->path });
}
return $uri;
}
1;
=head1 B<Methods in need of POD>
=over
=item should_handle
=item class_for
=item class
=item bug_id
=back