aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/WebService/Server')
-rw-r--r--Bugzilla/WebService/Server/JSONRPC.pm696
-rw-r--r--Bugzilla/WebService/Server/REST.pm778
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/Bug.pm274
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm35
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/Bugzilla.pm46
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/Classification.pm27
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/Component.pm18
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/FlagType.pm67
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/Group.pm41
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/Product.pm78
-rw-r--r--Bugzilla/WebService/Server/REST/Resources/User.pm76
-rw-r--r--Bugzilla/WebService/Server/XMLRPC.pm491
12 files changed, 1304 insertions, 1323 deletions
diff --git a/Bugzilla/WebService/Server/JSONRPC.pm b/Bugzilla/WebService/Server/JSONRPC.pm
index 70b8fd96c..66640beb7 100644
--- a/Bugzilla/WebService/Server/JSONRPC.pm
+++ b/Bugzilla/WebService/Server/JSONRPC.pm
@@ -12,16 +12,17 @@ use strict;
use warnings;
use Bugzilla::WebService::Server;
-BEGIN {
- our @ISA = qw(Bugzilla::WebService::Server);
- if (eval { require JSON::RPC::Server::CGI }) {
- unshift(@ISA, 'JSON::RPC::Server::CGI');
- }
- else {
- require JSON::RPC::Legacy::Server::CGI;
- unshift(@ISA, 'JSON::RPC::Legacy::Server::CGI');
- }
+BEGIN {
+ our @ISA = qw(Bugzilla::WebService::Server);
+
+ if (eval { require JSON::RPC::Server::CGI }) {
+ unshift(@ISA, 'JSON::RPC::Server::CGI');
+ }
+ else {
+ require JSON::RPC::Legacy::Server::CGI;
+ unshift(@ISA, 'JSON::RPC::Legacy::Server::CGI');
+ }
}
use Bugzilla::Error;
@@ -38,79 +39,83 @@ use List::MoreUtils qw(none);
#####################################
sub new {
- my $class = shift;
- my $self = $class->SUPER::new(@_);
- Bugzilla->_json_server($self);
- $self->dispatch(WS_DISPATCH);
- $self->return_die_message(1);
- return $self;
+ my $class = shift;
+ my $self = $class->SUPER::new(@_);
+ Bugzilla->_json_server($self);
+ $self->dispatch(WS_DISPATCH);
+ $self->return_die_message(1);
+ return $self;
}
sub create_json_coder {
- my $self = shift;
- my $json = $self->SUPER::create_json_coder(@_);
- $json->allow_blessed(1);
- $json->convert_blessed(1);
- # This may seem a little backwards, but what this really means is
- # "don't convert our utf8 into byte strings, just leave it as a
- # utf8 string."
- $json->utf8(0) if Bugzilla->params->{'utf8'};
- return $json;
+ my $self = shift;
+ my $json = $self->SUPER::create_json_coder(@_);
+ $json->allow_blessed(1);
+ $json->convert_blessed(1);
+
+ # This may seem a little backwards, but what this really means is
+ # "don't convert our utf8 into byte strings, just leave it as a
+ # utf8 string."
+ $json->utf8(0) if Bugzilla->params->{'utf8'};
+ return $json;
}
# Override the JSON::RPC method to return our CGI object instead of theirs.
sub cgi { return Bugzilla->cgi; }
sub response_header {
- my $self = shift;
- # The HTTP body needs to be bytes (not a utf8 string) for recent
- # versions of HTTP::Message, but JSON::RPC::Server doesn't handle this
- # properly. $_[1] is the HTTP body content we're going to be sending.
- if (utf8::is_utf8($_[1])) {
- utf8::encode($_[1]);
- # Since we're going to just be sending raw bytes, we need to
- # set STDOUT to not expect utf8.
- disable_utf8();
- }
- return $self->SUPER::response_header(@_);
+ my $self = shift;
+
+ # The HTTP body needs to be bytes (not a utf8 string) for recent
+ # versions of HTTP::Message, but JSON::RPC::Server doesn't handle this
+ # properly. $_[1] is the HTTP body content we're going to be sending.
+ if (utf8::is_utf8($_[1])) {
+ utf8::encode($_[1]);
+
+ # Since we're going to just be sending raw bytes, we need to
+ # set STDOUT to not expect utf8.
+ disable_utf8();
+ }
+ return $self->SUPER::response_header(@_);
}
sub response {
- my ($self, $response) = @_;
- my $cgi = $self->cgi;
-
- # Implement JSONP.
- if (my $callback = $self->_bz_callback) {
- my $content = $response->content;
- # Prepend the JSONP response with /**/ in order to protect
- # against possible encoding attacks (e.g., affecting Flash).
- $response->content("/**/$callback($content)");
- }
-
- # Use $cgi->header properly instead of just printing text directly.
- # This fixes various problems, including sending Bugzilla's cookies
- # properly.
- my $headers = $response->headers;
- my @header_args;
- foreach my $name ($headers->header_field_names) {
- my @values = $headers->header($name);
- $name =~ s/-/_/g;
- foreach my $value (@values) {
- push(@header_args, "-$name", $value);
- }
- }
-
- # ETag support
- my $etag = $self->bz_etag;
- if ($etag && $cgi->check_etag($etag)) {
- push(@header_args, "-ETag", $etag);
- print $cgi->header(-status => '304 Not Modified', @header_args);
- }
- else {
- push(@header_args, "-ETag", $etag) if $etag;
- print $cgi->header(-status => $response->code, @header_args);
- print $response->content;
- }
+ my ($self, $response) = @_;
+ my $cgi = $self->cgi;
+
+ # Implement JSONP.
+ if (my $callback = $self->_bz_callback) {
+ my $content = $response->content;
+
+ # Prepend the JSONP response with /**/ in order to protect
+ # against possible encoding attacks (e.g., affecting Flash).
+ $response->content("/**/$callback($content)");
+ }
+
+ # Use $cgi->header properly instead of just printing text directly.
+ # This fixes various problems, including sending Bugzilla's cookies
+ # properly.
+ my $headers = $response->headers;
+ my @header_args;
+ foreach my $name ($headers->header_field_names) {
+ my @values = $headers->header($name);
+ $name =~ s/-/_/g;
+ foreach my $value (@values) {
+ push(@header_args, "-$name", $value);
+ }
+ }
+
+ # ETag support
+ my $etag = $self->bz_etag;
+ if ($etag && $cgi->check_etag($etag)) {
+ push(@header_args, "-ETag", $etag);
+ print $cgi->header(-status => '304 Not Modified', @header_args);
+ }
+ else {
+ push(@header_args, "-ETag", $etag) if $etag;
+ print $cgi->header(-status => $response->code, @header_args);
+ print $response->content;
+ }
}
# The JSON-RPC 1.1 GET specification is not so great--you can't specify
@@ -122,70 +127,69 @@ sub response {
# Base64 encoded, because that is ridiculous and obnoxious for JavaScript
# clients.
sub retrieve_json_from_get {
- my $self = shift;
- my $cgi = $self->cgi;
-
- my %input;
-
- # Both version and id must be set before any errors are thrown.
- if ($cgi->param('version')) {
- $self->version(scalar $cgi->param('version'));
- $input{version} = $cgi->param('version');
- }
- else {
- $self->version('1.0');
- }
-
- # The JSON-RPC 2.0 spec says that any request that omits an id doesn't
- # want a response. However, in an HTTP GET situation, it's stupid to
- # expect all clients to specify some id parameter just to get a response,
- # so we don't require it.
- my $id;
- if (defined $cgi->param('id')) {
- $id = $cgi->param('id');
- }
- # However, JSON::RPC does require that an id exist in most cases, in
- # order to throw proper errors. We use the installation's urlbase as
- # the id, in this case.
- else {
- $id = correct_urlbase();
- }
- # Setting _bz_request_id here is required in case we throw errors early,
- # before _handle.
- $self->{_bz_request_id} = $input{id} = $id;
-
- # _bz_callback can throw an error, so we have to set it here, after we're
- # ready to throw errors.
- $self->_bz_callback(scalar $cgi->param('callback'));
-
- if (!$cgi->param('method')) {
- ThrowUserError('json_rpc_get_method_required');
- }
- $input{method} = $cgi->param('method');
-
- my $params;
- if (defined $cgi->param('params')) {
- local $@;
- $params = eval {
- $self->json->decode(scalar $cgi->param('params'))
- };
- if ($@) {
- ThrowUserError('json_rpc_invalid_params',
- { params => scalar $cgi->param('params'),
- err_msg => $@ });
- }
- }
- elsif (!$self->version or $self->version ne '1.1') {
- $params = [];
- }
- else {
- $params = {};
- }
-
- $input{params} = $params;
-
- my $json = $self->json->encode(\%input);
- return $json;
+ my $self = shift;
+ my $cgi = $self->cgi;
+
+ my %input;
+
+ # Both version and id must be set before any errors are thrown.
+ if ($cgi->param('version')) {
+ $self->version(scalar $cgi->param('version'));
+ $input{version} = $cgi->param('version');
+ }
+ else {
+ $self->version('1.0');
+ }
+
+ # The JSON-RPC 2.0 spec says that any request that omits an id doesn't
+ # want a response. However, in an HTTP GET situation, it's stupid to
+ # expect all clients to specify some id parameter just to get a response,
+ # so we don't require it.
+ my $id;
+ if (defined $cgi->param('id')) {
+ $id = $cgi->param('id');
+ }
+
+ # However, JSON::RPC does require that an id exist in most cases, in
+ # order to throw proper errors. We use the installation's urlbase as
+ # the id, in this case.
+ else {
+ $id = correct_urlbase();
+ }
+
+ # Setting _bz_request_id here is required in case we throw errors early,
+ # before _handle.
+ $self->{_bz_request_id} = $input{id} = $id;
+
+ # _bz_callback can throw an error, so we have to set it here, after we're
+ # ready to throw errors.
+ $self->_bz_callback(scalar $cgi->param('callback'));
+
+ if (!$cgi->param('method')) {
+ ThrowUserError('json_rpc_get_method_required');
+ }
+ $input{method} = $cgi->param('method');
+
+ my $params;
+ if (defined $cgi->param('params')) {
+ local $@;
+ $params = eval { $self->json->decode(scalar $cgi->param('params')) };
+ if ($@) {
+ ThrowUserError('json_rpc_invalid_params',
+ {params => scalar $cgi->param('params'), err_msg => $@});
+ }
+ }
+ elsif (!$self->version or $self->version ne '1.1') {
+ $params = [];
+ }
+ else {
+ $params = {};
+ }
+
+ $input{params} = $params;
+
+ my $json = $self->json->encode(\%input);
+ return $json;
}
#######################################
@@ -193,72 +197,76 @@ sub retrieve_json_from_get {
#######################################
sub type {
- my ($self, $type, $value) = @_;
-
- # This is the only type that does something special with undef.
- if ($type eq 'boolean') {
- return $value ? JSON::true : JSON::false;
- }
-
- return JSON::null if !defined $value;
-
- my $retval = $value;
-
- if ($type eq 'int') {
- $retval = int($value);
- }
- if ($type eq 'double') {
- $retval = 0.0 + $value;
- }
- elsif ($type eq 'string') {
- # Forces string context, so that JSON will make it a string.
- $retval = "$value";
- }
- elsif ($type eq 'dateTime') {
- # ISO-8601 "YYYYMMDDTHH:MM:SS" with a literal T
- $retval = $self->datetime_format_outbound($value);
- }
- elsif ($type eq 'base64') {
- utf8::encode($value) if utf8::is_utf8($value);
- $retval = encode_base64($value, '');
- }
- elsif ($type eq 'email' && Bugzilla->params->{'webservice_email_filter'}) {
- $retval = email_filter($value);
- }
-
- return $retval;
+ my ($self, $type, $value) = @_;
+
+ # This is the only type that does something special with undef.
+ if ($type eq 'boolean') {
+ return $value ? JSON::true : JSON::false;
+ }
+
+ return JSON::null if !defined $value;
+
+ my $retval = $value;
+
+ if ($type eq 'int') {
+ $retval = int($value);
+ }
+ if ($type eq 'double') {
+ $retval = 0.0 + $value;
+ }
+ elsif ($type eq 'string') {
+
+ # Forces string context, so that JSON will make it a string.
+ $retval = "$value";
+ }
+ elsif ($type eq 'dateTime') {
+
+ # ISO-8601 "YYYYMMDDTHH:MM:SS" with a literal T
+ $retval = $self->datetime_format_outbound($value);
+ }
+ elsif ($type eq 'base64') {
+ utf8::encode($value) if utf8::is_utf8($value);
+ $retval = encode_base64($value, '');
+ }
+ elsif ($type eq 'email' && Bugzilla->params->{'webservice_email_filter'}) {
+ $retval = email_filter($value);
+ }
+
+ return $retval;
}
sub datetime_format_outbound {
- my $self = shift;
- # YUI expects ISO8601 in UTC time; including TZ specifier
- return $self->SUPER::datetime_format_outbound(@_) . 'Z';
+ my $self = shift;
+
+ # YUI expects ISO8601 in UTC time; including TZ specifier
+ return $self->SUPER::datetime_format_outbound(@_) . 'Z';
}
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.)
- if ($self->request->method ne 'POST') {
- # 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 $path = $self->path_info;
- my $class = $self->{dispatch_path}->{$path};
- my $full_method = $self->_bz_method_name;
- $full_method =~ /^\S+\.(\S+)/;
- my $method = $1;
- $self->SUPER::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.)
+ if ($self->request->method ne 'POST') {
+
+ # 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 $path = $self->path_info;
+ my $class = $self->{dispatch_path}->{$path};
+ my $full_method = $self->_bz_method_name;
+ $full_method =~ /^\S+\.(\S+)/;
+ my $method = $1;
+ $self->SUPER::handle_login($class, $method, $full_method);
}
######################################
@@ -267,165 +275,165 @@ sub handle_login {
# Store the ID of the current call, because Bugzilla::Error will need it.
sub _handle {
- my $self = shift;
- my ($obj) = @_;
- $self->{_bz_request_id} = $obj->{id};
+ my $self = shift;
+ my ($obj) = @_;
+ $self->{_bz_request_id} = $obj->{id};
- my $result = $self->SUPER::_handle(@_);
+ my $result = $self->SUPER::_handle(@_);
- # Set the ETag if not already set in the webservice methods.
- my $etag = $self->bz_etag;
- if (!$etag && ref $result) {
- my $data = $self->json->decode($result)->{'result'};
- $self->bz_etag($data);
- }
+ # Set the ETag if not already set in the webservice methods.
+ my $etag = $self->bz_etag;
+ if (!$etag && ref $result) {
+ my $data = $self->json->decode($result)->{'result'};
+ $self->bz_etag($data);
+ }
- return $result;
+ return $result;
}
# Make all error messages returned by JSON::RPC go into the 100000
# range, and bring down all our errors into the normal range.
sub _error {
- my ($self, $id, $code) = (shift, shift, shift);
- # All JSON::RPC errors are less than 1000.
- if ($code < 1000) {
- $code += 100000;
- }
- # Bugzilla::Error adds 100,000 to all *our* errors, so
- # we know they came from us.
- elsif ($code > 100000) {
- $code -= 100000;
- }
-
- # We can't just set $_[1] because it's not always settable,
- # in JSON::RPC::Server.
- unshift(@_, $id, $code);
- my $json = $self->SUPER::_error(@_);
-
- # We want to always send the JSON-RPC 1.1 error format, although
- # If we're not in JSON-RPC 1.1, we don't need the silly "name" parameter.
- if (!$self->version or $self->version ne '1.1') {
- my $object = $self->json->decode($json);
- my $message = $object->{error};
- # Just assure that future versions of JSON::RPC don't change the
- # JSON-RPC 1.0 error format.
- if (!ref $message) {
- $object->{error} = {
- code => $code,
- message => $message,
- };
- $json = $self->json->encode($object);
- }
- }
- return $json;
+ my ($self, $id, $code) = (shift, shift, shift);
+
+ # All JSON::RPC errors are less than 1000.
+ if ($code < 1000) {
+ $code += 100000;
+ }
+
+ # Bugzilla::Error adds 100,000 to all *our* errors, so
+ # we know they came from us.
+ elsif ($code > 100000) {
+ $code -= 100000;
+ }
+
+ # We can't just set $_[1] because it's not always settable,
+ # in JSON::RPC::Server.
+ unshift(@_, $id, $code);
+ my $json = $self->SUPER::_error(@_);
+
+ # We want to always send the JSON-RPC 1.1 error format, although
+ # If we're not in JSON-RPC 1.1, we don't need the silly "name" parameter.
+ if (!$self->version or $self->version ne '1.1') {
+ my $object = $self->json->decode($json);
+ my $message = $object->{error};
+
+ # Just assure that future versions of JSON::RPC don't change the
+ # JSON-RPC 1.0 error format.
+ if (!ref $message) {
+ $object->{error} = {code => $code, message => $message,};
+ $json = $self->json->encode($object);
+ }
+ }
+ return $json;
}
# This handles dispatching our calls to the appropriate class based on
# the name of the method.
sub _find_procedure {
- my $self = shift;
+ my $self = shift;
- my $method = shift;
- $self->{_bz_method_name} = $method;
+ my $method = shift;
+ $self->{_bz_method_name} = $method;
- # This tricks SUPER::_find_procedure into finding the right class.
- $method =~ /^(\S+)\.(\S+)$/;
- $self->path_info($1);
- unshift(@_, $2);
+ # This tricks SUPER::_find_procedure into finding the right class.
+ $method =~ /^(\S+)\.(\S+)$/;
+ $self->path_info($1);
+ unshift(@_, $2);
- return $self->SUPER::_find_procedure(@_);
+ return $self->SUPER::_find_procedure(@_);
}
# This is a hacky way to do something right before methods are called.
# This is the last thing that JSON::RPC::Server::_handle calls right before
# the method is actually called.
sub _argument_type_check {
- my $self = shift;
- my $params = $self->SUPER::_argument_type_check(@_);
-
- # 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.
- $self->_bz_method_name =~ /^(\S+)\.(\S+)$/;
- my ($class, $method) = ($1, $2);
- my $pkg = $self->{dispatch_path}->{$class};
- 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});
- }
- }
-
- # Update the params to allow for several convenience key/values
- # use for authentication
- fix_credentials($params);
-
- Bugzilla->input_params($params);
-
- if ($self->request->method eq 'POST') {
- # CSRF is possible via XMLHttpRequest when the Content-Type header
- # is not application/json (for example: text/plain or
- # application/x-www-form-urlencoded).
- # application/json is the single official MIME type, per RFC 4627.
- my $content_type = $self->cgi->content_type;
- # The charset can be appended to the content type, so we use a regexp.
- if ($content_type !~ m{^application/json(-rpc)?(;.*)?$}i) {
- ThrowUserError('json_rpc_illegal_content_type',
- { content_type => $content_type });
- }
- }
- else {
- # When being called using GET, we don't allow calling
- # methods that can change data. This protects us against cross-site
- # request forgeries.
- if (!grep($_ eq $method, $pkg->READ_ONLY)) {
- ThrowUserError('json_rpc_post_only',
- { method => $self->_bz_method_name });
- }
- }
-
- # Only allowed methods to be used from our whitelist
- if (none { $_ eq $method} $pkg->PUBLIC_METHODS) {
- ThrowCodeError('unknown_method', { method => $self->_bz_method_name });
- }
-
- # This is the best time to do login checks.
- $self->handle_login();
-
- # 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];
- }
-
- return $params;
+ my $self = shift;
+ my $params = $self->SUPER::_argument_type_check(@_);
+
+ # 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.
+ $self->_bz_method_name =~ /^(\S+)\.(\S+)$/;
+ my ($class, $method) = ($1, $2);
+ my $pkg = $self->{dispatch_path}->{$class};
+ 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});
+ }
+ }
+
+ # Update the params to allow for several convenience key/values
+ # use for authentication
+ fix_credentials($params);
+
+ Bugzilla->input_params($params);
+
+ if ($self->request->method eq 'POST') {
+
+ # CSRF is possible via XMLHttpRequest when the Content-Type header
+ # is not application/json (for example: text/plain or
+ # application/x-www-form-urlencoded).
+ # application/json is the single official MIME type, per RFC 4627.
+ my $content_type = $self->cgi->content_type;
+
+ # The charset can be appended to the content type, so we use a regexp.
+ if ($content_type !~ m{^application/json(-rpc)?(;.*)?$}i) {
+ ThrowUserError('json_rpc_illegal_content_type',
+ {content_type => $content_type});
+ }
+ }
+ else {
+ # When being called using GET, we don't allow calling
+ # methods that can change data. This protects us against cross-site
+ # request forgeries.
+ if (!grep($_ eq $method, $pkg->READ_ONLY)) {
+ ThrowUserError('json_rpc_post_only', {method => $self->_bz_method_name});
+ }
+ }
+
+ # Only allowed methods to be used from our whitelist
+ if (none { $_ eq $method } $pkg->PUBLIC_METHODS) {
+ ThrowCodeError('unknown_method', {method => $self->_bz_method_name});
+ }
+
+ # This is the best time to do login checks.
+ $self->handle_login();
+
+ # 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];
+ }
+
+ return $params;
}
##########################
@@ -434,22 +442,24 @@ sub _argument_type_check {
# _bz_method_name is stored by _find_procedure for later use.
sub _bz_method_name {
- return $_[0]->{_bz_method_name};
+ return $_[0]->{_bz_method_name};
}
sub _bz_callback {
- my ($self, $value) = @_;
- if (defined $value) {
- $value = trim($value);
- # We don't use \w because we don't want to allow Unicode here.
- if ($value !~ /^[A-Za-z0-9_\.\[\]]+$/) {
- ThrowUserError('json_rpc_invalid_callback', { callback => $value });
- }
- $self->{_bz_callback} = $value;
- # JSONP needs to be parsed by a JS parser, not by a JSON parser.
- $self->content_type('text/javascript');
+ my ($self, $value) = @_;
+ if (defined $value) {
+ $value = trim($value);
+
+ # We don't use \w because we don't want to allow Unicode here.
+ if ($value !~ /^[A-Za-z0-9_\.\[\]]+$/) {
+ ThrowUserError('json_rpc_invalid_callback', {callback => $value});
}
- return $self->{_bz_callback};
+ $self->{_bz_callback} = $value;
+
+ # JSONP needs to be parsed by a JS parser, not by a JSON parser.
+ $self->content_type('text/javascript');
+ }
+ return $self->{_bz_callback};
}
1;
diff --git a/Bugzilla/WebService/Server/REST.pm b/Bugzilla/WebService/Server/REST.pm
index 8450a7a28..8108e1d4f 100644
--- a/Bugzilla/WebService/Server/REST.pm
+++ b/Bugzilla/WebService/Server/REST.pm
@@ -40,134 +40,134 @@ 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} = 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);
}
#######################################
@@ -175,36 +175,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 +218,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 +297,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 +344,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;
diff --git a/Bugzilla/WebService/Server/REST/Resources/Bug.pm b/Bugzilla/WebService/Server/REST/Resources/Bug.pm
index 3fa8b65cf..5cc25f432 100644
--- a/Bugzilla/WebService/Server/REST/Resources/Bug.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/Bug.pm
@@ -15,150 +15,150 @@ use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Bug;
BEGIN {
- *Bugzilla::WebService::Bug::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::Bug::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/bug$}, {
- GET => {
- method => 'search',
- },
- POST => {
- method => 'create',
- status_code => STATUS_CREATED
- }
- },
- qr{^/bug/$}, {
- GET => {
- method => 'get'
- }
- },
- qr{^/bug/([^/]+)$}, {
- GET => {
- method => 'get',
- params => sub {
- return { ids => [ $_[0] ] };
- }
- },
- PUT => {
- method => 'update',
- params => sub {
- return { ids => [ $_[0] ] };
- }
- }
- },
- qr{^/bug/([^/]+)/comment$}, {
- GET => {
- method => 'comments',
- params => sub {
- return { ids => [ $_[0] ] };
- }
- },
- POST => {
- method => 'add_comment',
- params => sub {
- return { id => $_[0] };
- },
- success_code => STATUS_CREATED
- }
- },
- qr{^/bug/comment/([^/]+)$}, {
- GET => {
- method => 'comments',
- params => sub {
- return { comment_ids => [ $_[0] ] };
- }
- }
- },
- qr{^/bug/comment/tags/([^/]+)$}, {
- GET => {
- method => 'search_comment_tags',
- params => sub {
- return { query => $_[0] };
- },
- },
- },
- qr{^/bug/comment/([^/]+)/tags$}, {
- PUT => {
- method => 'update_comment_tags',
- params => sub {
- return { comment_id => $_[0] };
- },
- },
- },
- qr{^/bug/([^/]+)/history$}, {
- GET => {
- method => 'history',
- params => sub {
- return { ids => [ $_[0] ] };
- },
- }
- },
- qr{^/bug/([^/]+)/attachment$}, {
- GET => {
- method => 'attachments',
- params => sub {
- return { ids => [ $_[0] ] };
- }
- },
- POST => {
- method => 'add_attachment',
- params => sub {
- return { ids => [ $_[0] ] };
- },
- success_code => STATUS_CREATED
- }
- },
- qr{^/bug/attachment/([^/]+)$}, {
- GET => {
- method => 'attachments',
- params => sub {
- return { attachment_ids => [ $_[0] ] };
- }
- },
- PUT => {
- method => 'update_attachment',
- params => sub {
- return { ids => [ $_[0] ] };
- }
- }
+ my $rest_resources = [
+ qr{^/bug$},
+ {
+ GET => {method => 'search',},
+ POST => {method => 'create', status_code => STATUS_CREATED}
+ },
+ qr{^/bug/$},
+ {GET => {method => 'get'}},
+ qr{^/bug/([^/]+)$},
+ {
+ GET => {
+ method => 'get',
+ params => sub {
+ return {ids => [$_[0]]};
+ }
+ },
+ PUT => {
+ method => 'update',
+ params => sub {
+ return {ids => [$_[0]]};
+ }
+ }
+ },
+ qr{^/bug/([^/]+)/comment$},
+ {
+ GET => {
+ method => 'comments',
+ params => sub {
+ return {ids => [$_[0]]};
+ }
+ },
+ POST => {
+ method => 'add_comment',
+ params => sub {
+ return {id => $_[0]};
},
- qr{^/field/bug$}, {
- GET => {
- method => 'fields',
- }
+ success_code => STATUS_CREATED
+ }
+ },
+ qr{^/bug/comment/([^/]+)$},
+ {
+ GET => {
+ method => 'comments',
+ params => sub {
+ return {comment_ids => [$_[0]]};
+ }
+ }
+ },
+ qr{^/bug/comment/tags/([^/]+)$},
+ {
+ GET => {
+ method => 'search_comment_tags',
+ params => sub {
+ return {query => $_[0]};
},
- qr{^/field/bug/([^/]+)$}, {
- GET => {
- method => 'fields',
- params => sub {
- my $value = $_[0];
- my $param = 'names';
- $param = 'ids' if $value =~ /^\d+$/;
- return { $param => [ $_[0] ] };
- }
- }
+ },
+ },
+ qr{^/bug/comment/([^/]+)/tags$},
+ {
+ PUT => {
+ method => 'update_comment_tags',
+ params => sub {
+ return {comment_id => $_[0]};
},
- qr{^/field/bug/([^/]+)/values$}, {
- GET => {
- method => 'legal_values',
- params => sub {
- return { field => $_[0] };
- }
- }
+ },
+ },
+ qr{^/bug/([^/]+)/history$},
+ {
+ GET => {
+ method => 'history',
+ params => sub {
+ return {ids => [$_[0]]};
},
- qr{^/field/bug/([^/]+)/([^/]+)/values$}, {
- GET => {
- method => 'legal_values',
- params => sub {
- return { field => $_[0],
- product_id => $_[1] };
- }
- }
+ }
+ },
+ qr{^/bug/([^/]+)/attachment$},
+ {
+ GET => {
+ method => 'attachments',
+ params => sub {
+ return {ids => [$_[0]]};
+ }
+ },
+ POST => {
+ method => 'add_attachment',
+ params => sub {
+ return {ids => [$_[0]]};
},
- ];
- return $rest_resources;
+ success_code => STATUS_CREATED
+ }
+ },
+ qr{^/bug/attachment/([^/]+)$},
+ {
+ GET => {
+ method => 'attachments',
+ params => sub {
+ return {attachment_ids => [$_[0]]};
+ }
+ },
+ PUT => {
+ method => 'update_attachment',
+ params => sub {
+ return {ids => [$_[0]]};
+ }
+ }
+ },
+ qr{^/field/bug$},
+ {GET => {method => 'fields',}},
+ qr{^/field/bug/([^/]+)$},
+ {
+ GET => {
+ method => 'fields',
+ params => sub {
+ my $value = $_[0];
+ my $param = 'names';
+ $param = 'ids' if $value =~ /^\d+$/;
+ return {$param => [$_[0]]};
+ }
+ }
+ },
+ qr{^/field/bug/([^/]+)/values$},
+ {
+ GET => {
+ method => 'legal_values',
+ params => sub {
+ return {field => $_[0]};
+ }
+ }
+ },
+ qr{^/field/bug/([^/]+)/([^/]+)/values$},
+ {
+ GET => {
+ method => 'legal_values',
+ params => sub {
+ return {field => $_[0], product_id => $_[1]};
+ }
+ }
+ },
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm b/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm
index 8502d6b3b..806c3f9c7 100644
--- a/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/BugUserLastVisit.pm
@@ -12,27 +12,28 @@ use strict;
use warnings;
BEGIN {
- *Bugzilla::WebService::BugUserLastVisit::rest_resources = \&_rest_resources;
+ *Bugzilla::WebService::BugUserLastVisit::rest_resources = \&_rest_resources;
}
sub _rest_resources {
- return [
- # bug-id
- qr{^/bug_user_last_visit/(\d+)$}, {
- GET => {
- method => 'get',
- params => sub {
- return { ids => [$_[0]] };
- },
- },
- POST => {
- method => 'update',
- params => sub {
- return { ids => [$_[0]] };
- },
- },
+ return [
+ # bug-id
+ qr{^/bug_user_last_visit/(\d+)$},
+ {
+ GET => {
+ method => 'get',
+ params => sub {
+ return {ids => [$_[0]]};
},
- ];
+ },
+ POST => {
+ method => 'update',
+ params => sub {
+ return {ids => [$_[0]]};
+ },
+ },
+ },
+ ];
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/Bugzilla.pm b/Bugzilla/WebService/Server/REST/Resources/Bugzilla.pm
index a8f3f9330..072cfe2f6 100644
--- a/Bugzilla/WebService/Server/REST/Resources/Bugzilla.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/Bugzilla.pm
@@ -15,43 +15,19 @@ use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Bugzilla;
BEGIN {
- *Bugzilla::WebService::Bugzilla::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::Bugzilla::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/version$}, {
- GET => {
- method => 'version'
- }
- },
- qr{^/extensions$}, {
- GET => {
- method => 'extensions'
- }
- },
- qr{^/timezone$}, {
- GET => {
- method => 'timezone'
- }
- },
- qr{^/time$}, {
- GET => {
- method => 'time'
- }
- },
- qr{^/last_audit_time$}, {
- GET => {
- method => 'last_audit_time'
- }
- },
- qr{^/parameters$}, {
- GET => {
- method => 'parameters'
- }
- }
- ];
- return $rest_resources;
+ my $rest_resources = [
+ qr{^/version$}, {GET => {method => 'version'}},
+ qr{^/extensions$}, {GET => {method => 'extensions'}},
+ qr{^/timezone$}, {GET => {method => 'timezone'}},
+ qr{^/time$}, {GET => {method => 'time'}},
+ qr{^/last_audit_time$}, {GET => {method => 'last_audit_time'}},
+ qr{^/parameters$}, {GET => {method => 'parameters'}}
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/Classification.pm b/Bugzilla/WebService/Server/REST/Resources/Classification.pm
index 3f8d32a03..ed65aea5c 100644
--- a/Bugzilla/WebService/Server/REST/Resources/Classification.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/Classification.pm
@@ -15,22 +15,23 @@ use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Classification;
BEGIN {
- *Bugzilla::WebService::Classification::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::Classification::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/classification/([^/]+)$}, {
- GET => {
- method => 'get',
- params => sub {
- my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
- return { $param => [ $_[0] ] };
- }
- }
+ my $rest_resources = [
+ qr{^/classification/([^/]+)$},
+ {
+ GET => {
+ method => 'get',
+ params => sub {
+ my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
+ return {$param => [$_[0]]};
}
- ];
- return $rest_resources;
+ }
+ }
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/Component.pm b/Bugzilla/WebService/Server/REST/Resources/Component.pm
index 198c09332..8870a0f04 100644
--- a/Bugzilla/WebService/Server/REST/Resources/Component.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/Component.pm
@@ -17,19 +17,15 @@ use Bugzilla::WebService::Component;
use Bugzilla::Error;
BEGIN {
- *Bugzilla::WebService::Component::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::Component::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/component$}, {
- POST => {
- method => 'create',
- success_code => STATUS_CREATED
- }
- },
- ];
- return $rest_resources;
+ my $rest_resources = [
+ qr{^/component$},
+ {POST => {method => 'create', success_code => STATUS_CREATED}},
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/FlagType.pm b/Bugzilla/WebService/Server/REST/Resources/FlagType.pm
index 21dad0f73..438c8fb30 100644
--- a/Bugzilla/WebService/Server/REST/Resources/FlagType.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/FlagType.pm
@@ -17,43 +17,40 @@ use Bugzilla::WebService::FlagType;
use Bugzilla::Error;
BEGIN {
- *Bugzilla::WebService::FlagType::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::FlagType::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/flag_type$}, {
- POST => {
- method => 'create',
- success_code => STATUS_CREATED
- }
- },
- qr{^/flag_type/([^/]+)/([^/]+)$}, {
- GET => {
- method => 'get',
- params => sub {
- return { product => $_[0],
- component => $_[1] };
- }
- }
- },
- qr{^/flag_type/([^/]+)$}, {
- GET => {
- method => 'get',
- params => sub {
- return { product => $_[0] };
- }
- },
- PUT => {
- method => 'update',
- params => sub {
- my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
- return { $param => [ $_[0] ] };
- }
- }
- },
- ];
- return $rest_resources;
+ my $rest_resources = [
+ qr{^/flag_type$},
+ {POST => {method => 'create', success_code => STATUS_CREATED}},
+ qr{^/flag_type/([^/]+)/([^/]+)$},
+ {
+ GET => {
+ method => 'get',
+ params => sub {
+ return {product => $_[0], component => $_[1]};
+ }
+ }
+ },
+ qr{^/flag_type/([^/]+)$},
+ {
+ GET => {
+ method => 'get',
+ params => sub {
+ return {product => $_[0]};
+ }
+ },
+ PUT => {
+ method => 'update',
+ params => sub {
+ my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
+ return {$param => [$_[0]]};
+ }
+ }
+ },
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/Group.pm b/Bugzilla/WebService/Server/REST/Resources/Group.pm
index b052e384b..7f607b7d1 100644
--- a/Bugzilla/WebService/Server/REST/Resources/Group.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/Group.pm
@@ -15,31 +15,28 @@ use Bugzilla::WebService::Constants;
use Bugzilla::WebService::Group;
BEGIN {
- *Bugzilla::WebService::Group::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::Group::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/group$}, {
- GET => {
- method => 'get'
- },
- POST => {
- method => 'create',
- success_code => STATUS_CREATED
- }
- },
- qr{^/group/([^/]+)$}, {
- PUT => {
- method => 'update',
- params => sub {
- my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
- return { $param => [ $_[0] ] };
- }
- }
+ my $rest_resources = [
+ qr{^/group$},
+ {
+ GET => {method => 'get'},
+ POST => {method => 'create', success_code => STATUS_CREATED}
+ },
+ qr{^/group/([^/]+)$},
+ {
+ PUT => {
+ method => 'update',
+ params => sub {
+ my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
+ return {$param => [$_[0]]};
}
- ];
- return $rest_resources;
+ }
+ }
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/Product.pm b/Bugzilla/WebService/Server/REST/Resources/Product.pm
index 607b94b53..eabe19681 100644
--- a/Bugzilla/WebService/Server/REST/Resources/Product.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/Product.pm
@@ -17,53 +17,41 @@ use Bugzilla::WebService::Product;
use Bugzilla::Error;
BEGIN {
- *Bugzilla::WebService::Product::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::Product::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/product_accessible$}, {
- GET => {
- method => 'get_accessible_products'
- }
- },
- qr{^/product_enterable$}, {
- GET => {
- method => 'get_enterable_products'
- }
- },
- qr{^/product_selectable$}, {
- GET => {
- method => 'get_selectable_products'
- }
- },
- qr{^/product$}, {
- GET => {
- method => 'get'
- },
- POST => {
- method => 'create',
- success_code => STATUS_CREATED
- }
- },
- qr{^/product/([^/]+)$}, {
- GET => {
- method => 'get',
- params => sub {
- my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
- return { $param => [ $_[0] ] };
- }
- },
- PUT => {
- method => 'update',
- params => sub {
- my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
- return { $param => [ $_[0] ] };
- }
- }
- },
- ];
- return $rest_resources;
+ my $rest_resources = [
+ qr{^/product_accessible$},
+ {GET => {method => 'get_accessible_products'}},
+ qr{^/product_enterable$},
+ {GET => {method => 'get_enterable_products'}},
+ qr{^/product_selectable$},
+ {GET => {method => 'get_selectable_products'}},
+ qr{^/product$},
+ {
+ GET => {method => 'get'},
+ POST => {method => 'create', success_code => STATUS_CREATED}
+ },
+ qr{^/product/([^/]+)$},
+ {
+ GET => {
+ method => 'get',
+ params => sub {
+ my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
+ return {$param => [$_[0]]};
+ }
+ },
+ PUT => {
+ method => 'update',
+ params => sub {
+ my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
+ return {$param => [$_[0]]};
+ }
+ }
+ },
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/REST/Resources/User.pm b/Bugzilla/WebService/Server/REST/Resources/User.pm
index a83109e73..4555b4dbc 100644
--- a/Bugzilla/WebService/Server/REST/Resources/User.pm
+++ b/Bugzilla/WebService/Server/REST/Resources/User.pm
@@ -15,53 +15,41 @@ use Bugzilla::WebService::Constants;
use Bugzilla::WebService::User;
BEGIN {
- *Bugzilla::WebService::User::rest_resources = \&_rest_resources;
-};
+ *Bugzilla::WebService::User::rest_resources = \&_rest_resources;
+}
sub _rest_resources {
- my $rest_resources = [
- qr{^/login$}, {
- GET => {
- method => 'login'
- }
- },
- qr{^/logout$}, {
- GET => {
- method => 'logout'
- }
- },
- qr{^/valid_login$}, {
- GET => {
- method => 'valid_login'
- }
- },
- qr{^/user$}, {
- GET => {
- method => 'get'
- },
- POST => {
- method => 'create',
- success_code => STATUS_CREATED
- }
- },
- qr{^/user/([^/]+)$}, {
- GET => {
- method => 'get',
- params => sub {
- my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
- return { $param => [ $_[0] ] };
- }
- },
- PUT => {
- method => 'update',
- params => sub {
- my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
- return { $param => [ $_[0] ] };
- }
- }
+ my $rest_resources = [
+ qr{^/login$},
+ {GET => {method => 'login'}},
+ qr{^/logout$},
+ {GET => {method => 'logout'}},
+ qr{^/valid_login$},
+ {GET => {method => 'valid_login'}},
+ qr{^/user$},
+ {
+ GET => {method => 'get'},
+ POST => {method => 'create', success_code => STATUS_CREATED}
+ },
+ qr{^/user/([^/]+)$},
+ {
+ GET => {
+ method => 'get',
+ params => sub {
+ my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
+ return {$param => [$_[0]]};
+ }
+ },
+ PUT => {
+ method => 'update',
+ params => sub {
+ my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
+ return {$param => [$_[0]]};
}
- ];
- return $rest_resources;
+ }
+ }
+ ];
+ return $rest_resources;
}
1;
diff --git a/Bugzilla/WebService/Server/XMLRPC.pm b/Bugzilla/WebService/Server/XMLRPC.pm
index 8deb253ad..b0eae8e19 100644
--- a/Bugzilla/WebService/Server/XMLRPC.pm
+++ b/Bugzilla/WebService/Server/XMLRPC.pm
@@ -14,9 +14,10 @@ use warnings;
use XMLRPC::Transport::HTTP;
use Bugzilla::WebService::Server;
if ($ENV{MOD_PERL}) {
- our @ISA = qw(XMLRPC::Transport::HTTP::Apache Bugzilla::WebService::Server);
-} else {
- our @ISA = qw(XMLRPC::Transport::HTTP::CGI Bugzilla::WebService::Server);
+ our @ISA = qw(XMLRPC::Transport::HTTP::Apache Bugzilla::WebService::Server);
+}
+else {
+ our @ISA = qw(XMLRPC::Transport::HTTP::CGI Bugzilla::WebService::Server);
}
use Bugzilla::WebService::Constants;
@@ -26,97 +27,99 @@ use Bugzilla::Util;
use List::MoreUtils qw(none);
BEGIN {
- # Allow WebService methods to call XMLRPC::Lite's type method directly
- *Bugzilla::WebService::type = sub {
- my ($self, $type, $value) = @_;
- if ($type eq 'dateTime') {
- # This is the XML-RPC implementation, see the README in Bugzilla/WebService/.
- # Our "base" implementation is in Bugzilla::WebService::Server.
- $value = Bugzilla::WebService::Server->datetime_format_outbound($value);
- $value =~ s/-//g;
- }
- elsif ($type eq 'email') {
- $type = 'string';
- if (Bugzilla->params->{'webservice_email_filter'}) {
- $value = email_filter($value);
- }
- }
- return XMLRPC::Data->type($type)->value($value);
- };
-
- # Add support for ETags into XMLRPC WebServices
- *Bugzilla::WebService::bz_etag = sub {
- return Bugzilla::WebService::Server->bz_etag($_[1]);
- };
+ # Allow WebService methods to call XMLRPC::Lite's type method directly
+ *Bugzilla::WebService::type = sub {
+ my ($self, $type, $value) = @_;
+ if ($type eq 'dateTime') {
+
+ # This is the XML-RPC implementation, see the README in Bugzilla/WebService/.
+ # Our "base" implementation is in Bugzilla::WebService::Server.
+ $value = Bugzilla::WebService::Server->datetime_format_outbound($value);
+ $value =~ s/-//g;
+ }
+ elsif ($type eq 'email') {
+ $type = 'string';
+ if (Bugzilla->params->{'webservice_email_filter'}) {
+ $value = email_filter($value);
+ }
+ }
+ return XMLRPC::Data->type($type)->value($value);
+ };
+
+ # Add support for ETags into XMLRPC WebServices
+ *Bugzilla::WebService::bz_etag = sub {
+ return Bugzilla::WebService::Server->bz_etag($_[1]);
+ };
}
sub initialize {
- my $self = shift;
- my %retval = $self->SUPER::initialize(@_);
- $retval{'serializer'} = Bugzilla::XMLRPC::Serializer->new;
- $retval{'deserializer'} = Bugzilla::XMLRPC::Deserializer->new;
- $retval{'dispatch_with'} = WS_DISPATCH;
- return %retval;
+ my $self = shift;
+ my %retval = $self->SUPER::initialize(@_);
+ $retval{'serializer'} = Bugzilla::XMLRPC::Serializer->new;
+ $retval{'deserializer'} = Bugzilla::XMLRPC::Deserializer->new;
+ $retval{'dispatch_with'} = WS_DISPATCH;
+ return %retval;
}
sub make_response {
- my $self = shift;
- my $cgi = Bugzilla->cgi;
-
- # Fix various problems with IIS.
- if ($ENV{'SERVER_SOFTWARE'} =~ /IIS/) {
- $ENV{CONTENT_LENGTH} = 0;
- binmode(STDOUT, ':bytes');
- }
-
- $self->SUPER::make_response(@_);
-
- # XMLRPC::Transport::HTTP::CGI doesn't know about Bugzilla carrying around
- # its cookies in Bugzilla::CGI, so we need to copy them over.
- foreach my $cookie (@{$cgi->{'Bugzilla_cookie_list'}}) {
- $self->response->headers->push_header('Set-Cookie', $cookie);
- }
-
- # Copy across security related headers from Bugzilla::CGI
- foreach my $header (split(/[\r\n]+/, $cgi->header)) {
- my ($name, $value) = $header =~ /^([^:]+): (.*)/;
- if (!$self->response->headers->header($name)) {
- $self->response->headers->header($name => $value);
- }
- }
-
- # ETag support
- my $etag = $self->bz_etag;
- if (!$etag) {
- my $data = $self->response->as_string;
- $etag = $self->bz_etag($data);
- }
-
- if ($etag && $cgi->check_etag($etag)) {
- $self->response->headers->push_header('ETag', $etag);
- $self->response->headers->push_header('status', '304 Not Modified');
- }
- elsif ($etag) {
- $self->response->headers->push_header('ETag', $etag);
+ my $self = shift;
+ my $cgi = Bugzilla->cgi;
+
+ # Fix various problems with IIS.
+ if ($ENV{'SERVER_SOFTWARE'} =~ /IIS/) {
+ $ENV{CONTENT_LENGTH} = 0;
+ binmode(STDOUT, ':bytes');
+ }
+
+ $self->SUPER::make_response(@_);
+
+ # XMLRPC::Transport::HTTP::CGI doesn't know about Bugzilla carrying around
+ # its cookies in Bugzilla::CGI, so we need to copy them over.
+ foreach my $cookie (@{$cgi->{'Bugzilla_cookie_list'}}) {
+ $self->response->headers->push_header('Set-Cookie', $cookie);
+ }
+
+ # Copy across security related headers from Bugzilla::CGI
+ foreach my $header (split(/[\r\n]+/, $cgi->header)) {
+ my ($name, $value) = $header =~ /^([^:]+): (.*)/;
+ if (!$self->response->headers->header($name)) {
+ $self->response->headers->header($name => $value);
}
+ }
+
+ # ETag support
+ my $etag = $self->bz_etag;
+ if (!$etag) {
+ my $data = $self->response->as_string;
+ $etag = $self->bz_etag($data);
+ }
+
+ if ($etag && $cgi->check_etag($etag)) {
+ $self->response->headers->push_header('ETag', $etag);
+ $self->response->headers->push_header('status', '304 Not Modified');
+ }
+ elsif ($etag) {
+ $self->response->headers->push_header('ETag', $etag);
+ }
}
sub handle_login {
- my ($self, $classes, $action, $uri, $method) = @_;
- my $class = $classes->{$uri};
- my $full_method = $uri . "." . $method;
- # Only allowed methods to be used from the module's whitelist
- my $file = $class;
- $file =~ s{::}{/}g;
- $file .= ".pm";
- require $file;
- if (none { $_ eq $method } $class->PUBLIC_METHODS) {
- ThrowCodeError('unknown_method', { method => $full_method });
- }
-
- $ENV{CONTENT_LENGTH} = 0 if $ENV{'SERVER_SOFTWARE'} =~ /IIS/;
- $self->SUPER::handle_login($class, $method, $full_method);
- return;
+ my ($self, $classes, $action, $uri, $method) = @_;
+ my $class = $classes->{$uri};
+ my $full_method = $uri . "." . $method;
+
+ # Only allowed methods to be used from the module's whitelist
+ my $file = $class;
+ $file =~ s{::}{/}g;
+ $file .= ".pm";
+ require $file;
+ if (none { $_ eq $method } $class->PUBLIC_METHODS) {
+ ThrowCodeError('unknown_method', {method => $full_method});
+ }
+
+ $ENV{CONTENT_LENGTH} = 0 if $ENV{'SERVER_SOFTWARE'} =~ /IIS/;
+ $self->SUPER::handle_login($class, $method, $full_method);
+ return;
}
1;
@@ -140,100 +143,111 @@ use Bugzilla::WebService::Util qw(fix_credentials);
use Scalar::Util qw(tainted);
sub new {
- my $self = shift->SUPER::new(@_);
- # Initialise XML::Parser to not expand references to entities, to prevent DoS
- require XML::Parser;
- my $parser = XML::Parser->new( NoExpand => 1, Handlers => { Default => sub {} } );
- $self->{_parser}->parser($parser, $parser);
- return $self;
+ my $self = shift->SUPER::new(@_);
+
+ # Initialise XML::Parser to not expand references to entities, to prevent DoS
+ require XML::Parser;
+ my $parser = XML::Parser->new(
+ NoExpand => 1,
+ Handlers => {
+ Default => sub { }
+ }
+ );
+ $self->{_parser}->parser($parser, $parser);
+ return $self;
}
sub deserialize {
- my $self = shift;
-
- # Only allow certain content types to protect against CSRF attacks
- my $content_type = lc($ENV{'CONTENT_TYPE'});
- # Remove charset, etc, if provided
- $content_type =~ s/^([^;]+);.*/$1/;
- if (!grep($_ eq $content_type, XMLRPC_CONTENT_TYPE_WHITELIST)) {
- ThrowUserError('xmlrpc_illegal_content_type',
- { content_type => $ENV{'CONTENT_TYPE'} });
- }
+ my $self = shift;
- my ($xml) = @_;
- my $som = $self->SUPER::deserialize(@_);
- if (tainted($xml)) {
- $som->{_bz_do_taint} = 1;
- }
- bless $som, 'Bugzilla::XMLRPC::SOM';
- my $params = $som->paramsin;
- # This allows positional parameters for Testopia.
- $params = {} if ref $params ne 'HASH';
+ # Only allow certain content types to protect against CSRF attacks
+ my $content_type = lc($ENV{'CONTENT_TYPE'});
+
+ # Remove charset, etc, if provided
+ $content_type =~ s/^([^;]+);.*/$1/;
+ if (!grep($_ eq $content_type, XMLRPC_CONTENT_TYPE_WHITELIST)) {
+ ThrowUserError('xmlrpc_illegal_content_type',
+ {content_type => $ENV{'CONTENT_TYPE'}});
+ }
- # Update the params to allow for several convenience key/values
- # use for authentication
- fix_credentials($params);
+ my ($xml) = @_;
+ my $som = $self->SUPER::deserialize(@_);
+ if (tainted($xml)) {
+ $som->{_bz_do_taint} = 1;
+ }
+ bless $som, 'Bugzilla::XMLRPC::SOM';
+ my $params = $som->paramsin;
- Bugzilla->input_params($params);
+ # This allows positional parameters for Testopia.
+ $params = {} if ref $params ne 'HASH';
- return $som;
+ # Update the params to allow for several convenience key/values
+ # use for authentication
+ fix_credentials($params);
+
+ Bugzilla->input_params($params);
+
+ return $som;
}
# Some method arguments need to be converted in some way, when they are input.
sub decode_value {
- my $self = shift;
- my ($type) = @{ $_[0] };
- my $value = $self->SUPER::decode_value(@_);
-
- # We only validate/convert certain types here.
- return $value if $type !~ /^(?:int|i4|boolean|double|dateTime\.iso8601)$/;
-
- # Though the XML-RPC standard doesn't allow an empty <int>,
- # <double>,or <dateTime.iso8601>, we do, and we just say
- # "that's undef".
- if (grep($type eq $_, qw(int double dateTime))) {
- return undef if $value eq '';
- }
-
- my $validator = $self->_validation_subs->{$type};
- if (!$validator->($value)) {
- ThrowUserError('xmlrpc_invalid_value',
- { type => $type, value => $value });
- }
-
- # We convert dateTimes to a DB-friendly date format.
- if ($type eq 'dateTime.iso8601') {
- if ($value !~ /T.*[\-+Z]/i) {
- # The caller did not specify a timezone, so we assume UTC.
- # pass 'Z' specifier to datetime_from to force it
- $value = $value . 'Z';
- }
- $value = Bugzilla::WebService::Server::XMLRPC->datetime_format_inbound($value);
+ my $self = shift;
+ my ($type) = @{$_[0]};
+ my $value = $self->SUPER::decode_value(@_);
+
+ # We only validate/convert certain types here.
+ return $value if $type !~ /^(?:int|i4|boolean|double|dateTime\.iso8601)$/;
+
+ # Though the XML-RPC standard doesn't allow an empty <int>,
+ # <double>,or <dateTime.iso8601>, we do, and we just say
+ # "that's undef".
+ if (grep($type eq $_, qw(int double dateTime))) {
+ return undef if $value eq '';
+ }
+
+ my $validator = $self->_validation_subs->{$type};
+ if (!$validator->($value)) {
+ ThrowUserError('xmlrpc_invalid_value', {type => $type, value => $value});
+ }
+
+ # We convert dateTimes to a DB-friendly date format.
+ if ($type eq 'dateTime.iso8601') {
+ if ($value !~ /T.*[\-+Z]/i) {
+
+ # The caller did not specify a timezone, so we assume UTC.
+ # pass 'Z' specifier to datetime_from to force it
+ $value = $value . 'Z';
}
+ $value = Bugzilla::WebService::Server::XMLRPC->datetime_format_inbound($value);
+ }
- return $value;
+ return $value;
}
sub _validation_subs {
- my $self = shift;
- return $self->{_validation_subs} if $self->{_validation_subs};
- # The only place that XMLRPC::Lite stores any sort of validation
- # regex is in XMLRPC::Serializer. We want to re-use those regexes here.
- my $lookup = Bugzilla::XMLRPC::Serializer->new->typelookup;
-
- # $lookup is a hash whose values are arrayrefs, and whose keys are the
- # names of types. The second item of each arrayref is a subroutine
- # that will do our validation for us.
- my %validators = map { $_ => $lookup->{$_}->[1] } (keys %$lookup);
- # Add a boolean validator
- $validators{'boolean'} = sub {$_[0] =~ /^[01]$/};
- # Some types have multiple names, or have a different name in
- # XMLRPC::Serializer than their standard XML-RPC name.
- $validators{'dateTime.iso8601'} = $validators{'dateTime'};
- $validators{'i4'} = $validators{'int'};
-
- $self->{_validation_subs} = \%validators;
- return \%validators;
+ my $self = shift;
+ return $self->{_validation_subs} if $self->{_validation_subs};
+
+ # The only place that XMLRPC::Lite stores any sort of validation
+ # regex is in XMLRPC::Serializer. We want to re-use those regexes here.
+ my $lookup = Bugzilla::XMLRPC::Serializer->new->typelookup;
+
+ # $lookup is a hash whose values are arrayrefs, and whose keys are the
+ # names of types. The second item of each arrayref is a subroutine
+ # that will do our validation for us.
+ my %validators = map { $_ => $lookup->{$_}->[1] } (keys %$lookup);
+
+ # Add a boolean validator
+ $validators{'boolean'} = sub { $_[0] =~ /^[01]$/ };
+
+ # Some types have multiple names, or have a different name in
+ # XMLRPC::Serializer than their standard XML-RPC name.
+ $validators{'dateTime.iso8601'} = $validators{'dateTime'};
+ $validators{'i4'} = $validators{'int'};
+
+ $self->{_validation_subs} = \%validators;
+ return \%validators;
}
1;
@@ -249,16 +263,16 @@ our @ISA = qw(XMLRPC::SOM);
use Bugzilla::WebService::Util qw(taint_data);
sub paramsin {
- my $self = shift;
- if (!$self->{bz_params_in}) {
- my @params = $self->SUPER::paramsin(@_);
- if ($self->{_bz_do_taint}) {
- taint_data(@params);
- }
- $self->{bz_params_in} = \@params;
+ my $self = shift;
+ if (!$self->{bz_params_in}) {
+ my @params = $self->SUPER::paramsin(@_);
+ if ($self->{_bz_do_taint}) {
+ taint_data(@params);
}
- my $params = $self->{bz_params_in};
- return wantarray ? @$params : $params->[0];
+ $self->{bz_params_in} = \@params;
+ }
+ my $params = $self->{bz_params_in};
+ return wantarray ? @$params : $params->[0];
}
1;
@@ -272,43 +286,46 @@ use strict;
use warnings;
use Scalar::Util qw(blessed reftype);
+
# We can't use "use parent" because XMLRPC::Serializer doesn't return
# a true value.
use XMLRPC::Lite;
our @ISA = qw(XMLRPC::Serializer);
sub new {
- my $class = shift;
- my $self = $class->SUPER::new(@_);
- # This fixes UTF-8.
- $self->{'_typelookup'}->{'base64'} =
- [10, sub { !utf8::is_utf8($_[0]) && $_[0] =~ /[^\x09\x0a\x0d\x20-\x7f]/},
- 'as_base64'];
- # This makes arrays work right even though we're a subclass.
- # (See http://rt.cpan.org//Ticket/Display.html?id=34514)
- $self->{'_encodingStyle'} = '';
- return $self;
+ my $class = shift;
+ my $self = $class->SUPER::new(@_);
+
+ # This fixes UTF-8.
+ $self->{'_typelookup'}->{'base64'} = [
+ 10, sub { !utf8::is_utf8($_[0]) && $_[0] =~ /[^\x09\x0a\x0d\x20-\x7f]/ },
+ 'as_base64'
+ ];
+
+ # This makes arrays work right even though we're a subclass.
+ # (See http://rt.cpan.org//Ticket/Display.html?id=34514)
+ $self->{'_encodingStyle'} = '';
+ return $self;
}
# Here the XMLRPC::Serializer is extended to use the XMLRPC nil extension.
sub encode_object {
- my $self = shift;
- my @encoded = $self->SUPER::encode_object(@_);
+ my $self = shift;
+ my @encoded = $self->SUPER::encode_object(@_);
- return $encoded[0]->[0] eq 'nil'
- ? ['value', {}, [@encoded]]
- : @encoded;
+ return $encoded[0]->[0] eq 'nil' ? ['value', {}, [@encoded]] : @encoded;
}
# Removes undefined values so they do not produce invalid XMLRPC.
sub envelope {
- my $self = shift;
- my ($type, $method, $data) = @_;
- # If the type isn't a successful response we don't want to change the values.
- if ($type eq 'response') {
- _strip_undefs($data);
- }
- return $self->SUPER::envelope($type, $method, $data);
+ my $self = shift;
+ my ($type, $method, $data) = @_;
+
+ # If the type isn't a successful response we don't want to change the values.
+ if ($type eq 'response') {
+ _strip_undefs($data);
+ }
+ return $self->SUPER::envelope($type, $method, $data);
}
# In an XMLRPC response we have to handle hashes of arrays, hashes, scalars,
@@ -316,58 +333,58 @@ sub envelope {
# The whole XMLRPC::Data object must be removed if its value key is undefined
# so it cannot be recursed like the other hash type objects.
sub _strip_undefs {
- my ($initial) = @_;
- my $type = reftype($initial) or return;
-
- if ($type eq "HASH") {
- while (my ($key, $value) = each(%$initial)) {
- if ( !defined $value
- || (blessed $value && $value->isa('XMLRPC::Data') && !defined $value->value) )
- {
- # If the value is undefined remove it from the hash.
- delete $initial->{$key};
- }
- else {
- _strip_undefs($value);
- }
- }
+ my ($initial) = @_;
+ my $type = reftype($initial) or return;
+
+ if ($type eq "HASH") {
+ while (my ($key, $value) = each(%$initial)) {
+ if (!defined $value
+ || (blessed $value && $value->isa('XMLRPC::Data') && !defined $value->value))
+ {
+ # If the value is undefined remove it from the hash.
+ delete $initial->{$key};
+ }
+ else {
+ _strip_undefs($value);
+ }
}
- elsif ($type eq "ARRAY") {
- for (my $count = 0; $count < scalar @{$initial}; $count++) {
- my $value = $initial->[$count];
- if ( !defined $value
- || (blessed $value && $value->isa('XMLRPC::Data') && !defined $value->value) )
- {
- # If the value is undefined remove it from the array.
- splice(@$initial, $count, 1);
- $count--;
- }
- else {
- _strip_undefs($value);
- }
- }
+ }
+ elsif ($type eq "ARRAY") {
+ for (my $count = 0; $count < scalar @{$initial}; $count++) {
+ my $value = $initial->[$count];
+ if (!defined $value
+ || (blessed $value && $value->isa('XMLRPC::Data') && !defined $value->value))
+ {
+ # If the value is undefined remove it from the array.
+ splice(@$initial, $count, 1);
+ $count--;
+ }
+ else {
+ _strip_undefs($value);
+ }
}
+ }
}
sub BEGIN {
- no strict 'refs';
- for my $type (qw(double i4 int dateTime)) {
- my $method = 'as_' . $type;
- *$method = sub {
- my ($self, $value) = @_;
- if (!defined($value)) {
- return as_nil();
- }
- else {
- my $super_method = "SUPER::$method";
- return $self->$super_method($value);
- }
- }
- }
+ no strict 'refs';
+ for my $type (qw(double i4 int dateTime)) {
+ my $method = 'as_' . $type;
+ *$method = sub {
+ my ($self, $value) = @_;
+ if (!defined($value)) {
+ return as_nil();
+ }
+ else {
+ my $super_method = "SUPER::$method";
+ return $self->$super_method($value);
+ }
+ }
+ }
}
sub as_nil {
- return ['nil', {}];
+ return ['nil', {}];
}
1;