]> git.ipfire.org Git - thirdparty/bugzilla.git/blob - Bugzilla/BugUrl.pm
1e836ca1e8eabc2f2c0f059461069b286ec50a61
[thirdparty/bugzilla.git] / Bugzilla / BugUrl.pm
1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 #
5 # This Source Code Form is "Incompatible With Secondary Licenses", as
6 # 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 use Bugzilla::Util;
17 use Bugzilla::Error;
18 use Bugzilla::Constants;
19 use Bugzilla::Hook;
20
21 use URI;
22 use URI::QueryParam;
23
24 ###############################
25 #### Initialization ####
26 ###############################
27
28 use constant DB_TABLE => 'bug_see_also';
29 use constant NAME_FIELD => 'value';
30 use constant LIST_ORDER => 'id';
31 # See Also is tracked in bugs_activity.
32 use constant AUDIT_CREATES => 0;
33 use constant AUDIT_UPDATES => 0;
34 use constant AUDIT_REMOVES => 0;
35
36 use constant DB_COLUMNS => qw(
37 id
38 bug_id
39 value
40 class
41 );
42
43 # This must be strings with the names of the validations,
44 # instead of coderefs, because subclasses override these
45 # validators with their own.
46 use constant VALIDATORS => {
47 value => '_check_value',
48 bug_id => '_check_bug_id',
49 class => \&_check_class,
50 };
51
52 # This is the order we go through all of subclasses and
53 # pick the first one that should handle the url. New
54 # subclasses should be added at the end of the list.
55 use constant SUB_CLASSES => qw(
56 Bugzilla::BugUrl::Bugzilla::Local
57 Bugzilla::BugUrl::Bugzilla
58 Bugzilla::BugUrl::Launchpad
59 Bugzilla::BugUrl::Google
60 Bugzilla::BugUrl::Debian
61 Bugzilla::BugUrl::JIRA
62 Bugzilla::BugUrl::Trac
63 Bugzilla::BugUrl::MantisBT
64 Bugzilla::BugUrl::SourceForge
65 Bugzilla::BugUrl::GitHub
66 );
67
68 ###############################
69 #### Accessors ######
70 ###############################
71
72 sub class { return $_[0]->{class} }
73 sub bug_id { return $_[0]->{bug_id} }
74
75 ###############################
76 #### Methods ####
77 ###############################
78
79 sub new {
80 my $class = shift;
81 my $param = shift;
82
83 if (ref $param) {
84 my $bug_id = $param->{bug_id};
85 my $name = $param->{name} || $param->{value};
86 if (!defined $bug_id) {
87 ThrowCodeError('bad_arg',
88 { argument => 'bug_id',
89 function => "${class}::new" });
90 }
91 if (!defined $name) {
92 ThrowCodeError('bad_arg',
93 { argument => 'name',
94 function => "${class}::new" });
95 }
96
97 my $condition = 'bug_id = ? AND value = ?';
98 my @values = ($bug_id, $name);
99 $param = { condition => $condition, values => \@values };
100 }
101
102 unshift @_, $param;
103 return $class->SUPER::new(@_);
104 }
105
106 sub _do_list_select {
107 my $class = shift;
108 my $objects = $class->SUPER::_do_list_select(@_);
109
110 foreach my $object (@$objects) {
111 eval "use " . $object->class; die $@ if $@;
112 bless $object, $object->class;
113 }
114
115 return $objects
116 }
117
118 # This is an abstract method. It must be overridden
119 # in every subclass.
120 sub should_handle {
121 my ($class, $input) = @_;
122 ThrowCodeError('unknown_method',
123 { method => "${class}::should_handle" });
124 }
125
126 sub class_for {
127 my ($class, $value) = @_;
128
129 my @sub_classes = $class->SUB_CLASSES;
130 Bugzilla::Hook::process("bug_url_sub_classes",
131 { sub_classes => \@sub_classes });
132
133 my $uri = URI->new($value);
134 foreach my $subclass (@sub_classes) {
135 eval "use $subclass";
136 die $@ if $@;
137 return wantarray ? ($subclass, $uri) : $subclass
138 if $subclass->should_handle($uri);
139 }
140
141 ThrowUserError('bug_url_invalid', { url => $value });
142 }
143
144 sub _check_class {
145 my ($class, $subclass) = @_;
146 eval "use $subclass"; die $@ if $@;
147 return $subclass;
148 }
149
150 sub _check_bug_id {
151 my ($class, $bug_id) = @_;
152
153 my $bug;
154 if (blessed $bug_id) {
155 # We got a bug object passed in, use it
156 $bug = $bug_id;
157 $bug->check_is_visible;
158 }
159 else {
160 # We got a bug id passed in, check it and get the bug object
161 $bug = Bugzilla::Bug->check({ id => $bug_id });
162 }
163
164 return $bug->id;
165 }
166
167 sub _check_value {
168 my ($class, $uri) = @_;
169
170 my $value = $uri->as_string;
171
172 if (!$value) {
173 ThrowCodeError('param_required',
174 { function => 'add_see_also', param => '$value' });
175 }
176
177 # We assume that the URL is an HTTP URL if there is no (something)://
178 # in front.
179 if (!$uri->scheme) {
180 # This works better than setting $uri->scheme('http'), because
181 # that creates URLs like "http:domain.com" and doesn't properly
182 # differentiate the path from the domain.
183 $uri = new URI("http://$value");
184 }
185 elsif ($uri->scheme ne 'http' && $uri->scheme ne 'https') {
186 ThrowUserError('bug_url_invalid', { url => $value, reason => 'http' });
187 }
188
189 # This stops the following edge cases from being accepted:
190 # * show_bug.cgi?id=1
191 # * /show_bug.cgi?id=1
192 # * http:///show_bug.cgi?id=1
193 if (!$uri->authority or $uri->path !~ m{/}) {
194 ThrowUserError('bug_url_invalid',
195 { url => $value, reason => 'path_only' });
196 }
197
198 if (length($uri->path) > MAX_BUG_URL_LENGTH) {
199 ThrowUserError('bug_url_too_long', { url => $uri->path });
200 }
201
202 return $uri;
203 }
204
205 1;
206
207 =head1 B<Methods in need of POD>
208
209 =over
210
211 =item should_handle
212
213 =item class_for
214
215 =item class
216
217 =item bug_id
218
219 =back