# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.

package Bugzilla::WebService::Server;

use 5.10.1;
use strict;
use warnings;

use Bugzilla::Logging;
use Bugzilla::Error;
use Bugzilla::Util qw(datetime_from);

use Digest::MD5 qw(md5_base64);
use Scalar::Util qw(blessed);
use Storable qw(freeze);
use Module::Runtime qw(require_module);
use Try::Tiny;

sub handle_login {
  my ($self, $class, $method, $full_method) = @_;

  # Throw error if the supplied class does not exist or the method is private
  ThrowCodeError('unknown_method', {method => $full_method})
    if (!$class or $method =~ /^_/);

  # We never want to create a new session unless the user is calling the
  # login method.  Setting dont_persist_session makes
  # Bugzilla::Auth::_handle_login_result() skip calling persist_login().
  if ($full_method ne 'User.login') {
    Bugzilla->request_cache->{dont_persist_session} = 1;
  }

  try {
    require_module($class);
  }
  catch {
    ThrowCodeError('unknown_method', {method => $full_method});
    FATAL($_);
  };
  return
    if ($class->login_exempt($method)
    and !defined Bugzilla->input_params->{Bugzilla_login});
  Bugzilla->login();

  Bugzilla::Hook::process('webservice_before_call',
    {'method' => $method, full_method => $full_method});
}

sub datetime_format_inbound {
  my ($self, $time) = @_;

  my $converted = datetime_from($time, Bugzilla->local_timezone);
  if (!defined $converted) {
    ThrowUserError('illegal_date', {date => $time});
  }
  $time = $converted->ymd() . ' ' . $converted->hms();
  return $time;
}

sub datetime_format_outbound {
  my ($self, $date) = @_;

  return undef if (!defined $date or $date eq '');

  my $time = $date;
  if (blessed($date)) {

    # We expect this to mean we were sent a datetime object
    $time->set_time_zone('UTC');
  }
  else {
    # We always send our time in UTC, for consistency.
    # passed in value is likely a string, create a datetime object
    $time = datetime_from($date, 'UTC');
  }
  return $time->iso8601();
}

# ETag support
sub bz_etag {
  my ($self, $data) = @_;
  my $cache = Bugzilla->request_cache;

  if (Bugzilla->request_cache->{bz_etag_disable}) {
    return undef;
  }
  elsif (defined $data) {

    # Serialize the data if passed a reference
    local $Storable::canonical = 1;
    $data = freeze($data) if ref $data;

    # Wide characters cause md5_base64() to die.
    utf8::encode($data) if utf8::is_utf8($data);

    # Append content_type to the end of the data
    # string as we want the etag to be unique to
    # the content_type. We do not need this for
    # XML-RPC as text/xml is always returned.
    if (blessed($self) && $self->can('content_type')) {
      $data .= $self->content_type if $self->content_type;
    }

    $cache->{'bz_etag'} = md5_base64($data);
  }
  return $cache->{'bz_etag'};
}

1;

=head1 NAME

Bugzilla::WebService::Server - Base server class for the WebService API

=head1 DESCRIPTION

Bugzilla::WebService::Server is the base class for the individual WebService API
servers such as XML-RPC, JSON-RPC, and REST. You never actually create a
Bugzilla::WebService::Server directly, you only make subclasses of it.

=head1 FUNCTIONS

=over

=item C<bz_etag>

This function is used to store an ETag value that will be used when returning
the data by the different API server modules such as XML-RPC, or REST. The individual
webservice methods can also set the value earlier in the process if needed such as
before a unique update token is added. If a value is not set earlier, an etag will
automatically be created using the returned data except in some cases when an error
has occurred.

=back

=head1 SEE ALSO

L<Bugzilla::WebService::Server::XMLRPC|XMLRPC>, L<Bugzilla::WebService::Server::JSONRPC|JSONRPC>,
and L<Bugzilla::WebService::Server::REST|REST>.

=head1 B<Methods in need of POD>

=over

=item handle_login

=item datetime_format_outbound

=item datetime_format_inbound

=back
