BugUrl.pm 5.12 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

package Bugzilla::BugUrl;
9 10

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

14
use parent qw(Bugzilla::Object);
15 16 17 18

use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::Constants;
19
use Bugzilla::Hook;
20

21
use URI;
22 23 24 25 26 27 28 29 30
use URI::QueryParam;

###############################
####    Initialization     ####
###############################

use constant DB_TABLE   => 'bug_see_also';
use constant NAME_FIELD => 'value';
use constant LIST_ORDER => 'id';
31

32 33 34 35
# See Also is tracked in bugs_activity.
use constant AUDIT_CREATES => 0;
use constant AUDIT_UPDATES => 0;
use constant AUDIT_REMOVES => 0;
36 37

use constant DB_COLUMNS => qw(
38 39 40 41
  id
  bug_id
  value
  class
42 43 44 45 46 47
);

# This must be strings with the names of the validations,
# instead of coderefs, because subclasses override these
# validators with their own.
use constant VALIDATORS => {
48 49 50
  value  => '_check_value',
  bug_id => '_check_bug_id',
  class  => \&_check_class,
51 52 53 54 55 56
};

# 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(
57 58 59 60 61 62 63 64 65 66
  Bugzilla::BugUrl::Bugzilla::Local
  Bugzilla::BugUrl::Bugzilla
  Bugzilla::BugUrl::Launchpad
  Bugzilla::BugUrl::Google
  Bugzilla::BugUrl::Debian
  Bugzilla::BugUrl::JIRA
  Bugzilla::BugUrl::Trac
  Bugzilla::BugUrl::MantisBT
  Bugzilla::BugUrl::SourceForge
  Bugzilla::BugUrl::GitHub
67 68
  Bugzilla::BugUrl::RequestTracker
  Bugzilla::BugUrl::Redmine
69 70
);

71 72 73 74
###############################
####      Accessors      ######
###############################

75
sub class  { return $_[0]->{class} }
76 77
sub bug_id { return $_[0]->{bug_id} }

78 79 80 81 82
###############################
####        Methods        ####
###############################

sub new {
83 84 85 86 87
  my $class = shift;
  my $param = shift;

  if (ref $param) {
    my $bug_id = $param->{bug_id};
88
    my $name   = $param->{name} || $param->{value};
89 90 91 92 93
    if (!defined $bug_id) {
      ThrowCodeError('bad_arg', {argument => 'bug_id', function => "${class}::new"});
    }
    if (!defined $name) {
      ThrowCodeError('bad_arg', {argument => 'name', function => "${class}::new"});
94 95
    }

96
    my $condition = 'bug_id = ? AND value = ?';
97
    my @values    = ($bug_id, $name);
98 99 100 101 102
    $param = {condition => $condition, values => \@values};
  }

  unshift @_, $param;
  return $class->SUPER::new(@_);
103 104
}

105
sub _do_list_select {
106 107
  my $class   = shift;
  my $objects = $class->SUPER::_do_list_select(@_);
108

109 110 111 112 113 114
  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);
  }
115

116
  return $objects;
117 118
}

119 120 121
# This is an abstract method. It must be overridden
# in every subclass.
sub should_handle {
122 123
  my ($class, $input) = @_;
  ThrowCodeError('unknown_method', {method => "${class}::should_handle"});
124 125 126
}

sub class_for {
127
  my ($class, $value) = @_;
128

129 130 131 132 133 134 135 136 137 138 139 140
  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});
141 142
}

143
sub _check_class {
144 145 146 147
  my ($class, $subclass) = @_;
  eval "use $subclass";
  die $@ if $@;
  return $subclass;
148 149
}

150
sub _check_bug_id {
151
  my ($class, $bug_id) = @_;
152

153 154 155 156 157 158 159 160 161 162 163
  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});
  }
164

165
  return $bug->id;
166 167 168
}

sub _check_value {
169 170 171 172 173 174 175 176 177 178 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
  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;
204 205 206
}

1;
207 208 209 210 211 212 213 214 215 216 217 218 219 220

=head1 B<Methods in need of POD>

=over

=item should_handle

=item class_for

=item class

=item bug_id

=back