summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/WebService/Server/REST.pm')
-rw-r--r--Bugzilla/WebService/Server/REST.pm781
1 files changed, 396 insertions, 385 deletions
diff --git a/Bugzilla/WebService/Server/REST.pm b/Bugzilla/WebService/Server/REST.pm
index 8450a7a28..6a5e889a8 100644
--- a/Bugzilla/WebService/Server/REST.pm
+++ b/Bugzilla/WebService/Server/REST.pm
@@ -40,134 +40,135 @@ use MIME::Base64 qw(decode_base64);
###########################
sub handle {
- my ($self) = @_;
-
- # Determine how the data should be represented. We do this early so
- # errors will also be returned with the proper content type.
- # If no accept header was sent or the content types specified were not
- # matched, we default to the first type in the whitelist.
- $self->content_type($self->_best_content_type(REST_CONTENT_TYPE_WHITELIST()));
-
- # Using current path information, decide which class/method to
- # use to serve the request. Throw error if no resource was found
- # unless we were looking for OPTIONS
- if (!$self->_find_resource($self->cgi->path_info)) {
- if ($self->request->method eq 'OPTIONS'
- && $self->bz_rest_options)
- {
- my $response = $self->response_header(STATUS_OK, "");
- my $options_string = join(', ', @{ $self->bz_rest_options });
- $response->header('Allow' => $options_string,
- 'Access-Control-Allow-Methods' => $options_string);
- return $self->response($response);
- }
-
- ThrowUserError("rest_invalid_resource",
- { path => $self->cgi->path_info,
- method => $self->request->method });
+ my ($self) = @_;
+
+ # Determine how the data should be represented. We do this early so
+ # errors will also be returned with the proper content type.
+ # If no accept header was sent or the content types specified were not
+ # matched, we default to the first type in the whitelist.
+ $self->content_type($self->_best_content_type(REST_CONTENT_TYPE_WHITELIST()));
+
+ # Using current path information, decide which class/method to
+ # use to serve the request. Throw error if no resource was found
+ # unless we were looking for OPTIONS
+ if (!$self->_find_resource($self->cgi->path_info)) {
+ if ($self->request->method eq 'OPTIONS' && $self->bz_rest_options) {
+ my $response = $self->response_header(STATUS_OK, "");
+ my $options_string = join(', ', @{$self->bz_rest_options});
+ $response->header(
+ 'Allow' => $options_string,
+ 'Access-Control-Allow-Methods' => $options_string
+ );
+ return $self->response($response);
}
- # Dispatch to the proper module
- my $class = $self->bz_class_name;
- my ($path) = $class =~ /::([^:]+)$/;
- $self->path_info($path);
- delete $self->{dispatch_path};
- $self->dispatch({ $path => $class });
+ ThrowUserError("rest_invalid_resource",
+ {path => $self->cgi->path_info, method => $self->request->method});
+ }
- my $params = $self->_retrieve_json_params;
+ # Dispatch to the proper module
+ my $class = $self->bz_class_name;
+ my ($path) = $class =~ /::([^:]+)$/;
+ $self->path_info($path);
+ delete $self->{dispatch_path};
+ $self->dispatch({$path => $class});
- fix_credentials($params);
+ my $params = $self->_retrieve_json_params;
- # Fix includes/excludes for each call
- rest_include_exclude($params);
+ fix_credentials($params);
- # Set callback name if exists
- $self->_bz_callback($params->{'callback'}) if $params->{'callback'};
+ # Fix includes/excludes for each call
+ rest_include_exclude($params);
- Bugzilla->input_params($params);
+ # Set callback name if exists
+ $self->_bz_callback($params->{'callback'}) if $params->{'callback'};
- # Set the JSON version to 1.1 and the id to the current urlbase
- # also set up the correct handler method
- my $obj = {
- version => '1.1',
- id => correct_urlbase(),
- method => $self->bz_method_name,
- params => $params
- };
+ Bugzilla->input_params($params);
- # Execute the handler
- my $result = $self->_handle($obj);
+ # Set the JSON version to 1.1 and the id to the current urlbase
+ # also set up the correct handler method
+ my $obj = {
+ version => '1.1',
+ id => correct_urlbase(),
+ method => $self->bz_method_name,
+ params => $params
+ };
- if (!$self->error_response_header) {
- return $self->response(
- $self->response_header($self->bz_success_code || STATUS_OK, $result));
- }
+ # Execute the handler
+ my $result = $self->_handle($obj);
+
+ if (!$self->error_response_header) {
+ return $self->response(
+ $self->response_header($self->bz_success_code || STATUS_OK, $result));
+ }
- $self->response($self->error_response_header);
+ $self->response($self->error_response_header);
}
sub response {
- my ($self, $response) = @_;
-
- # If we have thrown an error, the 'error' key will exist
- # otherwise we use 'result'. JSONRPC returns other data
- # along with the result/error such as version and id which
- # we will strip off for REST calls.
- my $content = $response->content;
- my $json_data = {};
- if ($content) {
- $json_data = $self->json->decode($content);
- }
-
- my $result = {};
- if (exists $json_data->{error}) {
- $result = $json_data->{error};
- $result->{error} = $self->type('boolean', 1);
- $result->{documentation} = REST_DOC;
- delete $result->{'name'}; # Remove JSONRPCError
- }
- elsif (exists $json_data->{result}) {
- $result = $json_data->{result};
- }
-
- # The result needs to be a valid JSON data structure
- # and not a undefined or scalar value.
- if (!ref $result
- || blessed($result)
- || (ref $result ne 'HASH' && ref $result ne 'ARRAY'))
- {
- $result = { result => $result };
- }
-
- Bugzilla::Hook::process('webservice_rest_response',
- { rpc => $self, result => \$result, response => $response });
-
- # Access Control
- $response->header("Access-Control-Allow-Origin", "*");
- $response->header("Access-Control-Allow-Headers", "origin, content-type, accept, x-requested-with");
-
- # ETag support
- my $etag = $self->bz_etag;
- $self->bz_etag($result) if !$etag;
-
- # If accessing through web browser, then display in readable format
- if ($self->content_type eq 'text/html') {
- $result = $self->json->pretty->canonical->allow_nonref->encode($result);
-
- my $template = Bugzilla->template;
- $content = "";
- $template->process("rest.html.tmpl", { result => $result }, \$content)
- || ThrowTemplateError($template->error());
-
- $response->content_type('text/html');
- }
- else {
- $content = $self->json->encode($result);
- }
-
- $response->content($content);
-
- $self->SUPER::response($response);
+ my ($self, $response) = @_;
+
+ # If we have thrown an error, the 'error' key will exist
+ # otherwise we use 'result'. JSONRPC returns other data
+ # along with the result/error such as version and id which
+ # we will strip off for REST calls.
+ my $content = $response->content;
+ my $json_data = {};
+ if ($content) {
+ $json_data = $self->json->decode($content);
+ }
+
+ my $result = {};
+ if (exists $json_data->{error}) {
+ $result = $json_data->{error};
+ $result->{error} = $self->type('boolean', 1);
+ $result->{documentation}
+ = Bugzilla->params->{'urlbase'} . 'docs/en/html/api/index.html';
+ delete $result->{'name'}; # Remove JSONRPCError
+ }
+ elsif (exists $json_data->{result}) {
+ $result = $json_data->{result};
+ }
+
+ # The result needs to be a valid JSON data structure
+ # and not a undefined or scalar value.
+ if ( !ref $result
+ || blessed($result)
+ || (ref $result ne 'HASH' && ref $result ne 'ARRAY'))
+ {
+ $result = {result => $result};
+ }
+
+ Bugzilla::Hook::process('webservice_rest_response',
+ {rpc => $self, result => \$result, response => $response});
+
+ # Access Control
+ $response->header("Access-Control-Allow-Origin", "*");
+ $response->header("Access-Control-Allow-Headers",
+ "origin, content-type, accept, x-requested-with");
+
+ # ETag support
+ my $etag = $self->bz_etag;
+ $self->bz_etag($result) if !$etag;
+
+ # If accessing through web browser, then display in readable format
+ if ($self->content_type eq 'text/html') {
+ $result = $self->json->pretty->canonical->allow_nonref->encode($result);
+
+ my $template = Bugzilla->template;
+ $content = "";
+ $template->process("rest.html.tmpl", {result => $result}, \$content)
+ || ThrowTemplateError($template->error());
+
+ $response->content_type('text/html');
+ }
+ else {
+ $content = $self->json->encode($result);
+ }
+
+ $response->content($content);
+
+ $self->SUPER::response($response);
}
#######################################
@@ -175,36 +176,40 @@ sub response {
#######################################
sub handle_login {
- my $self = shift;
-
- # If we're being called using GET, we don't allow cookie-based or Env
- # login, because GET requests can be done cross-domain, and we don't
- # want private data showing up on another site unless the user
- # explicitly gives that site their username and password. (This is
- # particularly important for JSONP, which would allow a remote site
- # to use private data without the user's knowledge, unless we had this
- # protection in place.) We do allow this for GET /login as we need to
- # for Bugzilla::Auth::Persist::Cookie to create a login cookie that we
- # can also use for Bugzilla_token support. This is OK as it requires
- # a login and password to be supplied and will fail if they are not
- # valid for the user.
- if (!grep($_ eq $self->request->method, ('POST', 'PUT'))
- && !($self->bz_class_name eq 'Bugzilla::WebService::User'
- && $self->bz_method_name eq 'login'))
- {
- # XXX There's no particularly good way for us to get a parameter
- # to Bugzilla->login at this point, so we pass this information
- # around using request_cache, which is a bit of a hack. The
- # implementation of it is in Bugzilla::Auth::Login::Stack.
- Bugzilla->request_cache->{'auth_no_automatic_login'} = 1;
- }
-
- my $class = $self->bz_class_name;
- my $method = $self->bz_method_name;
- my $full_method = $class . "." . $method;
-
- # Bypass JSONRPC::handle_login
- Bugzilla::WebService::Server->handle_login($class, $method, $full_method);
+ my $self = shift;
+
+ # If we're being called using GET, we don't allow cookie-based or Env
+ # login, because GET requests can be done cross-domain, and we don't
+ # want private data showing up on another site unless the user
+ # explicitly gives that site their username and password. (This is
+ # particularly important for JSONP, which would allow a remote site
+ # to use private data without the user's knowledge, unless we had this
+ # protection in place.) We do allow this for GET /login as we need to
+ # for Bugzilla::Auth::Persist::Cookie to create a login cookie that we
+ # can also use for Bugzilla_token support. This is OK as it requires
+ # a login and password to be supplied and will fail if they are not
+ # valid for the user.
+ if (
+ !grep($_ eq $self->request->method, ('POST', 'PUT'))
+ && !(
+ $self->bz_class_name eq 'Bugzilla::WebService::User'
+ && $self->bz_method_name eq 'login'
+ )
+ )
+ {
+ # XXX There's no particularly good way for us to get a parameter
+ # to Bugzilla->login at this point, so we pass this information
+ # around using request_cache, which is a bit of a hack. The
+ # implementation of it is in Bugzilla::Auth::Login::Stack.
+ Bugzilla->request_cache->{'auth_no_automatic_login'} = 1;
+ }
+
+ my $class = $self->bz_class_name;
+ my $method = $self->bz_method_name;
+ my $full_method = $class . "." . $method;
+
+ # Bypass JSONRPC::handle_login
+ Bugzilla::WebService::Server->handle_login($class, $method, $full_method);
}
############################
@@ -214,79 +219,78 @@ sub handle_login {
# We do not want to run Bugzilla::WebService::Server::JSONRPC->_find_prodedure
# as it determines the method name differently.
sub _find_procedure {
- my $self = shift;
- if ($self->isa('JSON::RPC::Server::CGI')) {
- return JSON::RPC::Server::_find_procedure($self, @_);
- }
- else {
- return JSON::RPC::Legacy::Server::_find_procedure($self, @_);
- }
+ my $self = shift;
+ if ($self->isa('JSON::RPC::Server::CGI')) {
+ return JSON::RPC::Server::_find_procedure($self, @_);
+ }
+ else {
+ return JSON::RPC::Legacy::Server::_find_procedure($self, @_);
+ }
}
sub _argument_type_check {
- my $self = shift;
- my $params;
-
- if ($self->isa('JSON::RPC::Server::CGI')) {
- $params = JSON::RPC::Server::_argument_type_check($self, @_);
+ my $self = shift;
+ my $params;
+
+ if ($self->isa('JSON::RPC::Server::CGI')) {
+ $params = JSON::RPC::Server::_argument_type_check($self, @_);
+ }
+ else {
+ $params = JSON::RPC::Legacy::Server::_argument_type_check($self, @_);
+ }
+
+ # JSON-RPC 1.0 requires all parameters to be passed as an array, so
+ # we just pull out the first item and assume it's an object.
+ my $params_is_array;
+ if (ref $params eq 'ARRAY') {
+ $params = $params->[0];
+ $params_is_array = 1;
+ }
+
+ taint_data($params);
+
+ # Now, convert dateTime fields on input.
+ my $method = $self->bz_method_name;
+ my $pkg = $self->{dispatch_path}->{$self->path_info};
+ my @date_fields = @{$pkg->DATE_FIELDS->{$method} || []};
+ foreach my $field (@date_fields) {
+ if (defined $params->{$field}) {
+ my $value = $params->{$field};
+ if (ref $value eq 'ARRAY') {
+ $params->{$field} = [map { $self->datetime_format_inbound($_) } @$value];
+ }
+ else {
+ $params->{$field} = $self->datetime_format_inbound($value);
+ }
}
- else {
- $params = JSON::RPC::Legacy::Server::_argument_type_check($self, @_);
+ }
+ my @base64_fields = @{$pkg->BASE64_FIELDS->{$method} || []};
+ foreach my $field (@base64_fields) {
+ if (defined $params->{$field}) {
+ $params->{$field} = decode_base64($params->{$field});
}
+ }
- # JSON-RPC 1.0 requires all parameters to be passed as an array, so
- # we just pull out the first item and assume it's an object.
- my $params_is_array;
- if (ref $params eq 'ARRAY') {
- $params = $params->[0];
- $params_is_array = 1;
- }
+ # This is the best time to do login checks.
+ $self->handle_login();
- taint_data($params);
-
- # Now, convert dateTime fields on input.
- my $method = $self->bz_method_name;
- my $pkg = $self->{dispatch_path}->{$self->path_info};
- my @date_fields = @{ $pkg->DATE_FIELDS->{$method} || [] };
- foreach my $field (@date_fields) {
- if (defined $params->{$field}) {
- my $value = $params->{$field};
- if (ref $value eq 'ARRAY') {
- $params->{$field} =
- [ map { $self->datetime_format_inbound($_) } @$value ];
- }
- else {
- $params->{$field} = $self->datetime_format_inbound($value);
- }
- }
- }
- my @base64_fields = @{ $pkg->BASE64_FIELDS->{$method} || [] };
- foreach my $field (@base64_fields) {
- if (defined $params->{$field}) {
- $params->{$field} = decode_base64($params->{$field});
- }
- }
+ # Bugzilla::WebService packages call internal methods like
+ # $self->_some_private_method. So we have to inherit from
+ # that class as well as this Server class.
+ my $new_class = ref($self) . '::' . $pkg;
+ my $isa_string = 'our @ISA = qw(' . ref($self) . " $pkg)";
+ eval "package $new_class;$isa_string;";
+ bless $self, $new_class;
- # This is the best time to do login checks.
- $self->handle_login();
+ # Allow extensions to modify the params post login
+ Bugzilla::Hook::process('webservice_rest_request',
+ {rpc => $self, params => $params});
- # Bugzilla::WebService packages call internal methods like
- # $self->_some_private_method. So we have to inherit from
- # that class as well as this Server class.
- my $new_class = ref($self) . '::' . $pkg;
- my $isa_string = 'our @ISA = qw(' . ref($self) . " $pkg)";
- eval "package $new_class;$isa_string;";
- bless $self, $new_class;
+ if ($params_is_array) {
+ $params = [$params];
+ }
- # Allow extensions to modify the params post login
- Bugzilla::Hook::process('webservice_rest_request',
- { rpc => $self, params => $params });
-
- if ($params_is_array) {
- $params = [$params];
- }
-
- return $params;
+ return $params;
}
###################
@@ -294,46 +298,46 @@ sub _argument_type_check {
###################
sub bz_method_name {
- my ($self, $method) = @_;
- $self->{_bz_method_name} = $method if $method;
- return $self->{_bz_method_name};
+ my ($self, $method) = @_;
+ $self->{_bz_method_name} = $method if $method;
+ return $self->{_bz_method_name};
}
sub bz_class_name {
- my ($self, $class) = @_;
- $self->{_bz_class_name} = $class if $class;
- return $self->{_bz_class_name};
+ my ($self, $class) = @_;
+ $self->{_bz_class_name} = $class if $class;
+ return $self->{_bz_class_name};
}
sub bz_success_code {
- my ($self, $value) = @_;
- $self->{_bz_success_code} = $value if $value;
- return $self->{_bz_success_code};
+ my ($self, $value) = @_;
+ $self->{_bz_success_code} = $value if $value;
+ return $self->{_bz_success_code};
}
sub bz_rest_params {
- my ($self, $params) = @_;
- $self->{_bz_rest_params} = $params if $params;
- return $self->{_bz_rest_params};
+ my ($self, $params) = @_;
+ $self->{_bz_rest_params} = $params if $params;
+ return $self->{_bz_rest_params};
}
sub bz_rest_options {
- my ($self, $options) = @_;
- $self->{_bz_rest_options} = $options if $options;
- return $self->{_bz_rest_options};
+ my ($self, $options) = @_;
+ $self->{_bz_rest_options} = $options if $options;
+ return $self->{_bz_rest_options};
}
sub rest_include_exclude {
- my ($params) = @_;
+ my ($params) = @_;
- if ($params->{'include_fields'} && !ref $params->{'include_fields'}) {
- $params->{'include_fields'} = [ split(/[\s+,]/, $params->{'include_fields'}) ];
- }
- if ($params->{'exclude_fields'} && !ref $params->{'exclude_fields'}) {
- $params->{'exclude_fields'} = [ split(/[\s+,]/, $params->{'exclude_fields'}) ];
- }
+ if ($params->{'include_fields'} && !ref $params->{'include_fields'}) {
+ $params->{'include_fields'} = [split(/[\s+,]/, $params->{'include_fields'})];
+ }
+ if ($params->{'exclude_fields'} && !ref $params->{'exclude_fields'}) {
+ $params->{'exclude_fields'} = [split(/[\s+,]/, $params->{'exclude_fields'})];
+ }
- return $params;
+ return $params;
}
##########################
@@ -341,184 +345,191 @@ sub rest_include_exclude {
##########################
sub _retrieve_json_params {
- my $self = shift;
-
- # Make a copy of the current input_params rather than edit directly
- my $params = {};
- %{$params} = %{ Bugzilla->input_params };
-
- # First add any parameters we were able to pull out of the path
- # based on the resource regexp and combine with the normal URL
- # parameters.
- if (my $rest_params = $self->bz_rest_params) {
- foreach my $param (keys %$rest_params) {
- # If the param does not already exist or if the
- # rest param is a single value, add it to the
- # global params.
- if (!exists $params->{$param} || !ref $rest_params->{$param}) {
- $params->{$param} = $rest_params->{$param};
- }
- # If rest_param is a list then add any extra values to the list
- elsif (ref $rest_params->{$param}) {
- my @extra_values = ref $params->{$param}
- ? @{ $params->{$param} }
- : ($params->{$param});
- $params->{$param}
- = [ uniq (@{ $rest_params->{$param} }, @extra_values) ];
- }
- }
+ my $self = shift;
+
+ # Make a copy of the current input_params rather than edit directly
+ my $params = {};
+ %{$params} = %{Bugzilla->input_params};
+
+ # First add any parameters we were able to pull out of the path
+ # based on the resource regexp and combine with the normal URL
+ # parameters.
+ if (my $rest_params = $self->bz_rest_params) {
+ foreach my $param (keys %$rest_params) {
+
+ # If the param does not already exist or if the
+ # rest param is a single value, add it to the
+ # global params.
+ if (!exists $params->{$param} || !ref $rest_params->{$param}) {
+ $params->{$param} = $rest_params->{$param};
+ }
+
+ # If rest_param is a list then add any extra values to the list
+ elsif (ref $rest_params->{$param}) {
+ my @extra_values
+ = ref $params->{$param} ? @{$params->{$param}} : ($params->{$param});
+ $params->{$param} = [uniq(@{$rest_params->{$param}}, @extra_values)];
+ }
+ }
+ }
+
+ # Any parameters passed in in the body of a non-GET request will override
+ # any parameters pull from the url path. Otherwise non-unique keys are
+ # combined.
+ if ($self->request->method ne 'GET') {
+ my $extra_params = {};
+
+ # We do this manually because CGI.pm doesn't understand JSON strings.
+ my $json = delete $params->{'POSTDATA'} || delete $params->{'PUTDATA'};
+ if ($json) {
+ eval { $extra_params = $self->json->decode($json); };
+ if ($@) {
+ ThrowUserError('json_rpc_invalid_params', {err_msg => $@});
+ }
}
- # Any parameters passed in in the body of a non-GET request will override
- # any parameters pull from the url path. Otherwise non-unique keys are
- # combined.
- if ($self->request->method ne 'GET') {
- my $extra_params = {};
- # We do this manually because CGI.pm doesn't understand JSON strings.
- my $json = delete $params->{'POSTDATA'} || delete $params->{'PUTDATA'};
- if ($json) {
- eval { $extra_params = $self->json->decode($json); };
- if ($@) {
- ThrowUserError('json_rpc_invalid_params', { err_msg => $@ });
- }
- }
-
- # Allow parameters in the query string if request was non-GET.
- # Note: parameters in query string body override any matching
- # parameters in the request body.
- foreach my $param ($self->cgi->url_param()) {
- $extra_params->{$param} = $self->cgi->url_param($param);
- }
-
- %{$params} = (%{$params}, %{$extra_params}) if %{$extra_params};
+ # Allow parameters in the query string if request was non-GET.
+ # Note: parameters in query string body override any matching
+ # parameters in the request body.
+ foreach my $param ($self->cgi->url_param()) {
+ $extra_params->{$param} = $self->cgi->url_param($param);
}
- return $params;
+ %{$params} = (%{$params}, %{$extra_params}) if %{$extra_params};
+ }
+
+ return $params;
}
sub _find_resource {
- my ($self, $path) = @_;
-
- # Load in the WebService module from the dispatch map and then call
- # $module->rest_resources to get the resources array ref.
- my $resources = {};
- foreach my $module (values %{ $self->{dispatch_path} }) {
- eval("require $module") || die $@;
- next if !$module->can('rest_resources');
- $resources->{$module} = $module->rest_resources;
- }
-
- Bugzilla::Hook::process('webservice_rest_resources',
- { rpc => $self, resources => $resources });
-
- # Use the resources hash from each module loaded earlier to determine
- # which handler to use based on a regex match of the CGI path.
- # Also any matches found in the regex will be passed in later to the
- # handler for possible use.
- my $request_method = $self->request->method;
-
- my (@matches, $handler_found, $handler_method, $handler_class);
- foreach my $class (keys %{ $resources }) {
- # The resource data for each module needs to be
- # an array ref with an even number of elements
- # to work correctly.
- next if (ref $resources->{$class} ne 'ARRAY'
- || scalar @{ $resources->{$class} } % 2 != 0);
-
- while (my $regex = shift @{ $resources->{$class} }) {
- my $options_data = shift @{ $resources->{$class} };
- next if ref $options_data ne 'HASH';
-
- if (@matches = ($path =~ $regex)) {
- # If a specific path is accompanied by a OPTIONS request
- # method, the user is asking for a list of possible request
- # methods for a specific path.
- $self->bz_rest_options([ keys %{ $options_data } ]);
-
- if ($options_data->{$request_method}) {
- my $resource_data = $options_data->{$request_method};
- $self->bz_class_name($class);
-
- # The method key/value can be a simple scalar method name
- # or a anonymous subroutine so we execute it here.
- my $method = ref $resource_data->{method} eq 'CODE'
- ? $resource_data->{method}->($self)
- : $resource_data->{method};
- $self->bz_method_name($method);
-
- # Pull out any parameters parsed from the URL path
- # and store them for use by the method.
- if ($resource_data->{params}) {
- $self->bz_rest_params($resource_data->{params}->(@matches));
- }
-
- # If a special success code is needed for this particular
- # method, then store it for later when generating response.
- if ($resource_data->{success_code}) {
- $self->bz_success_code($resource_data->{success_code});
- }
- $handler_found = 1;
- }
- }
- last if $handler_found;
+ my ($self, $path) = @_;
+
+ # Load in the WebService module from the dispatch map and then call
+ # $module->rest_resources to get the resources array ref.
+ my $resources = {};
+ foreach my $module (values %{$self->{dispatch_path}}) {
+ eval("require $module") || die $@;
+ next if !$module->can('rest_resources');
+ $resources->{$module} = $module->rest_resources;
+ }
+
+ Bugzilla::Hook::process('webservice_rest_resources',
+ {rpc => $self, resources => $resources});
+
+ # Use the resources hash from each module loaded earlier to determine
+ # which handler to use based on a regex match of the CGI path.
+ # Also any matches found in the regex will be passed in later to the
+ # handler for possible use.
+ my $request_method = $self->request->method;
+
+ my (@matches, $handler_found, $handler_method, $handler_class);
+ foreach my $class (keys %{$resources}) {
+
+ # The resource data for each module needs to be
+ # an array ref with an even number of elements
+ # to work correctly.
+ next
+ if (ref $resources->{$class} ne 'ARRAY'
+ || scalar @{$resources->{$class}} % 2 != 0);
+
+ while (my $regex = shift @{$resources->{$class}}) {
+ my $options_data = shift @{$resources->{$class}};
+ next if ref $options_data ne 'HASH';
+
+ if (@matches = ($path =~ $regex)) {
+
+ # If a specific path is accompanied by a OPTIONS request
+ # method, the user is asking for a list of possible request
+ # methods for a specific path.
+ $self->bz_rest_options([keys %{$options_data}]);
+
+ if ($options_data->{$request_method}) {
+ my $resource_data = $options_data->{$request_method};
+ $self->bz_class_name($class);
+
+ # The method key/value can be a simple scalar method name
+ # or a anonymous subroutine so we execute it here.
+ my $method
+ = ref $resource_data->{method} eq 'CODE'
+ ? $resource_data->{method}->($self)
+ : $resource_data->{method};
+ $self->bz_method_name($method);
+
+ # Pull out any parameters parsed from the URL path
+ # and store them for use by the method.
+ if ($resource_data->{params}) {
+ $self->bz_rest_params($resource_data->{params}->(@matches));
+ }
+
+ # If a special success code is needed for this particular
+ # method, then store it for later when generating response.
+ if ($resource_data->{success_code}) {
+ $self->bz_success_code($resource_data->{success_code});
+ }
+ $handler_found = 1;
}
- last if $handler_found;
+ }
+ last if $handler_found;
}
+ last if $handler_found;
+ }
- return $handler_found;
+ return $handler_found;
}
sub _best_content_type {
- my ($self, @types) = @_;
- return ($self->_simple_content_negotiation(@types))[0] || '*/*';
+ my ($self, @types) = @_;
+ return ($self->_simple_content_negotiation(@types))[0] || '*/*';
}
sub _simple_content_negotiation {
- my ($self, @types) = @_;
- my @accept_types = $self->_get_content_prefs();
- # Return the types as-is if no accept header sent, since sorting will be a no-op.
- if (!@accept_types) {
- return @types;
- }
- my $score = sub { $self->_score_type(shift, @accept_types) };
- return sort {$score->($b) <=> $score->($a)} @types;
+ my ($self, @types) = @_;
+ my @accept_types = $self->_get_content_prefs();
+
+ # Return the types as-is if no accept header sent, since sorting will be a no-op.
+ if (!@accept_types) {
+ return @types;
+ }
+ my $score = sub { $self->_score_type(shift, @accept_types) };
+ return sort { $score->($b) <=> $score->($a) } @types;
}
sub _score_type {
- my ($self, $type, @accept_types) = @_;
- my $score = scalar(@accept_types);
- for my $accept_type (@accept_types) {
- return $score if $type eq $accept_type;
- $score--;
- }
- return 0;
+ my ($self, $type, @accept_types) = @_;
+ my $score = scalar(@accept_types);
+ for my $accept_type (@accept_types) {
+ return $score if $type eq $accept_type;
+ $score--;
+ }
+ return 0;
}
sub _get_content_prefs {
- my $self = shift;
- my $default_weight = 1;
- my @prefs;
-
- # Parse the Accept header, and save type name, score, and position.
- my @accept_types = split /,/, $self->cgi->http('accept') || '';
- my $order = 0;
- for my $accept_type (@accept_types) {
- my ($weight) = ($accept_type =~ /q=(\d\.\d+|\d+)/);
- my ($name) = ($accept_type =~ m#(\S+/[^;]+)#);
- next unless $name;
- push @prefs, { name => $name, order => $order++};
- if (defined $weight) {
- $prefs[-1]->{score} = $weight;
- } else {
- $prefs[-1]->{score} = $default_weight;
- $default_weight -= 0.001;
- }
+ my $self = shift;
+ my $default_weight = 1;
+ my @prefs;
+
+ # Parse the Accept header, and save type name, score, and position.
+ my @accept_types = split /,/, $self->cgi->http('accept') || '';
+ my $order = 0;
+ for my $accept_type (@accept_types) {
+ my ($weight) = ($accept_type =~ /q=(\d\.\d+|\d+)/);
+ my ($name) = ($accept_type =~ m#(\S+/[^;]+)#);
+ next unless $name;
+ push @prefs, {name => $name, order => $order++};
+ if (defined $weight) {
+ $prefs[-1]->{score} = $weight;
+ }
+ else {
+ $prefs[-1]->{score} = $default_weight;
+ $default_weight -= 0.001;
}
+ }
- # Sort the types by score, subscore by order, and pull out just the name
- @prefs = map {$_->{name}} sort {$b->{score} <=> $a->{score} ||
- $a->{order} <=> $b->{order}} @prefs;
- return @prefs;
+ # Sort the types by score, subscore by order, and pull out just the name
+ @prefs = map { $_->{name} }
+ sort { $b->{score} <=> $a->{score} || $a->{order} <=> $b->{order} } @prefs;
+ return @prefs;
}
1;
@@ -646,7 +657,7 @@ the client.
=item B<handle_login>
This method determines the proper WebService all to make based on class
-and method name determined earlier. Then calls L<Bugzilla::WebService::Server::handle_login>
+and method name determined earlier. Then calls L<Bugzilla::WebService::Server/handle_login>
which will attempt to authenticate the client.
=item B<bz_method_name>