#
# Copyright (c) 2014--2015 Red Hat Inc.
#
# This software is licensed to you under the GNU General Public License,
# version 3 (GPLv3). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv3
# along with this software; if not, see http://www.gnu.org/licenses/gpl.txt
#
require 'smart_proxy_openscap/openscap_lib'

module Proxy::OpenSCAP
  HTTP_ERRORS = [
    EOFError,
    Errno::ECONNRESET,
    Errno::EINVAL,
    Errno::ECONNREFUSED,
    Net::HTTPBadResponse,
    Net::HTTPHeaderSyntaxError,
    Net::ProtocolError,
    Timeout::Error
  ]

  class Api < ::Sinatra::Base
    include ::Proxy::Log
    helpers ::Proxy::Helpers
    authorize_with_ssl_client
    CLIENT_PATHS = Regexp.compile(%r{^(/arf/\d+|/policies/\d+/content/|/policies/\d+/tailoring/)})

    # authorize via trusted hosts but let client paths in without such authorization
    before do
      pass if request.path_info =~ CLIENT_PATHS
      do_authorize_with_trusted_hosts
    end

    before '/arf/*' do
      begin
        @cn = Proxy::OpenSCAP::common_name request
      rescue Proxy::Error::Unauthorized => e
        log_halt 403, "Client authentication failed: #{e.message}"
      end
      @reported_at = Time.now.to_i
    end

    post "/arf/:policy" do
      policy = params[:policy]

      begin
        post_to_foreman = ForemanArfForwarder.new.post_report(@cn, policy, @reported_at, request.body.string, Proxy::OpenSCAP::Plugin.settings.timeout)
        Proxy::OpenSCAP::StorageFs.new(Proxy::OpenSCAP::Plugin.settings.reportsdir, @cn, post_to_foreman['id'], @reported_at).store_archive(request.body.string)
        post_to_foreman.to_json
      rescue Proxy::OpenSCAP::StoreReportError => e
        Proxy::OpenSCAP::StorageFs.new(Proxy::OpenSCAP::Plugin.settings.failed_dir, @cn, post_to_foreman['id'], @reported_at).store_failed(request.body.string)
        logger.error "Failed to save Report in reports directory (#{Proxy::OpenSCAP::Plugin.settings.reportsdir}). Failed with: #{e.message}.
                      Saving file in #{Proxy::OpenSCAP::Plugin.settings.failed_dir}. Please copy manually to #{Proxy::OpenSCAP::Plugin.settings.reportsdir}"
        { :result => 'Storage failure on proxy, see proxy logs for details' }.to_json
      rescue Nokogiri::XML::SyntaxError => e
        error = "Failed to parse Arf Report, moving to #{Proxy::OpenSCAP::Plugin.settings.corrupted_dir}"
        logger.error error
        Proxy::OpenSCAP::StorageFs.new(Proxy::OpenSCAP::Plugin.settings.corrupted_dir, @cn, policy, @reported_at).store_corrupted(request.body.string)
        { :result => (error << ' on proxy') }.to_json
      rescue *HTTP_ERRORS => e
        ### If the upload to foreman fails then store it in the spooldir
        msg = "Failed to upload to Foreman, saving in spool. Failed with: #{e.message}"
        logger.error msg
        Proxy::OpenSCAP::StorageFs.new(Proxy::OpenSCAP::Plugin.settings.spooldir, @cn, policy, @reported_at).store_spool(request.body.string)
        { :result => msg }.to_json
      rescue Proxy::OpenSCAP::StoreSpoolError => e
        log_halt 500, e.message
      rescue Proxy::OpenSCAP::ReportUploadError, Proxy::OpenSCAP::ReportDecompressError => e
        { :result => e.message }.to_json
      end
    end

    get "/arf/:id/:cname/:date/:digest/xml" do
      content_type 'application/x-bzip2'
      begin
        Proxy::OpenSCAP::StorageFs.new(Proxy::OpenSCAP::Plugin.settings.reportsdir, params[:cname], params[:id], params[:date]).get_arf_xml(params[:digest])
      rescue FileNotFound => e
        log_halt 500, "Could not find requested file, #{e.message}"
      end
    end

    delete "/arf/:id/:cname/:date/:digest" do
      begin
        Proxy::OpenSCAP::StorageFs.new(Proxy::OpenSCAP::Plugin.settings.reportsdir, params[:cname], params[:id], params[:date]).delete_arf_file
      rescue FileNotFound => e
        logger.debug "Could not find requested file, #{e.message} - Assuming deleted"
      end
    end

    get "/arf/:id/:cname/:date/:digest/html" do
      begin
        Proxy::OpenSCAP::OpenscapHtmlGenerator.new(params[:cname], params[:id], params[:date], params[:digest]).get_html
      rescue FileNotFound => e
        log_halt 500, "Could not find requested file, #{e.message}"
      rescue OpenSCAPException => e
        log_halt 500, "Could not generate report in HTML"
      end
    end

    get "/policies/:policy_id/content/:digest" do
      content_type 'application/xml'
      begin
        Proxy::OpenSCAP::FetchScapFile.new(:scap_content)
          .fetch(params[:policy_id], params[:digest], Proxy::OpenSCAP::Plugin.settings.contentdir)
      rescue *HTTP_ERRORS => e
        log_halt e.response.code.to_i, file_not_found_msg
      rescue StandardError => e
        log_halt 500, "Error occurred: #{e.message}"
      end
    end

    get "/policies/:policy_id/tailoring/:digest" do
      content_type 'application/xml'
      begin
        Proxy::OpenSCAP::FetchScapFile.new(:tailoring_file)
          .fetch(params[:policy_id], params[:digest], Proxy::OpenSCAP::Plugin.settings.tailoring_dir)
      rescue *HTTP_ERRORS => e
        log_halt e.response.code.to_i, file_not_found_msg
      rescue StandardError => e
        log_halt 500, "Error occurred: #{e.message}"
      end
    end

    post "/scap_content/policies" do
      begin
        Proxy::OpenSCAP::ProfilesParser.new.profiles('scap_content', request.body.string)
      rescue *HTTP_ERRORS => e
        log_halt 500, e.message
      rescue StandardError => e
        log_halt 500, "Error occurred: #{e.message}"
      end
    end

    post "/tailoring_file/profiles" do
      begin
        Proxy::OpenSCAP::ProfilesParser.new.profiles('tailoring_file', request.body.string)
      rescue *HTTP_ERRORS => e
        log_halt 500, e.message
      rescue StandardError => e
        log_halt 500, "Error occurred: #{e.message}"
      end
    end

    post "/scap_file/validator/:type" do
      validate_scap_file params
    end

    post "/scap_content/validator" do
      logger.warn "DEPRECATION WARNING: '/scap_content/validator' will be removed in the future. Use '/scap_file/validator/scap_content' instead"
      params[:type] = 'scap_content'
      validate_scap_file params
    end

    post "/scap_content/guide/?:policy?" do
      begin
        Proxy::OpenSCAP::PolicyParser.new(params[:policy]).guide(request.body.string)
      rescue *HTTP_ERRORS => e
        log_halt 500, e.message
      rescue StandardError => e
        log_halt 500, "Error occurred: #{e.message}"
      end
    end

    get "/spool_errors" do
      begin
        Proxy::OpenSCAP::StorageFs.new(Proxy::OpenSCAP::Plugin.settings.corrupted_dir, nil, nil, nil).spool_errors.to_json
      rescue StandardError => e
        log_halt 500, "Error occurred: #{e.message}"
      end
    end

    private

    def validate_scap_file(params)
      begin
        Proxy::OpenSCAP::ContentParser.new.validate(params[:type], request.body.string).to_json
      rescue *HTTP_ERRORS => e
        log_halt 500, e.message
      rescue StandardError => e
        log_halt 500, "Error occurred: #{e.message}"
      end
    end

    def file_not_found_msg
      "File not found on Foreman. Wrong policy id?"
    end
  end
end