# 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 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::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; die $@ if $@; bless $object, $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 =over =item should_handle =item class_for =item class =item bug_id =back