FusionInventory-Agent

 view release on metacpan or  search on metacpan

lib/FusionInventory/Agent/HTTP/Server.pm  view on Meta::CPAN

package FusionInventory::Agent::HTTP::Server;

use strict;
use warnings;

use UNIVERSAL::require;
use English qw(-no_match_vars);
use File::Basename;
use HTTP::Daemon;
use IO::Handle;
use Net::IP;
use Text::Template;
use File::Glob;
use URI;
use Socket qw(IN6ADDR_ANY inet_ntop);

use FusionInventory::Agent::Version;
use FusionInventory::Agent::Logger;
use FusionInventory::Agent::Tools;
use FusionInventory::Agent::Tools::Network;

# Limit maximum requests number handled in a keep-alive connection
use constant MaxKeepAlive => 8;

my $log_prefix = "[http server] ";

sub new {
    my ($class, %params) = @_;

    my $self = {
        logger    => $params{logger} ||
                     FusionInventory::Agent::Logger->new(),
        agent     => $params{agent},
        htmldir   => $params{htmldir},
        ip        => $params{ip},
        port      => $params{port} || 62354,
        listeners => {},
    };
    bless $self, $class;

    $self->setTrustedAddresses(%params);

    # Load any Server sub-module as plugin
    my @plugins = ();
    my ($sub_modules_path) = $INC{module2file(__PACKAGE__)} =~ /(.*)\.pm/;
    foreach my $file (File::Glob::bsd_glob("$sub_modules_path/*.pm")) {
        if ($OSNAME eq 'MSWin32') {
            $file =~ s{\\}{/}g;
            $sub_modules_path =~ s{\\}{/}g;
        }

        my ($name) = $file =~ m{$sub_modules_path/(\S+)\.pm$};
        next unless $name;

        # Don't load Plugin base class
        next if $name eq "Plugin";

        $self->{logger}->debug($log_prefix . "Trying to load $name Server plugin");

        my $module = __PACKAGE__ . "::" . $name;
        $module->require();
        if ($EVAL_ERROR) {
            $self->{logger}->debug($log_prefix . "Failed to load $name Server plugin: $EVAL_ERROR");
            next;
        }

        my $plugin = $module->new(server => $self)
            or next;

        $plugin->init();
        if ($plugin->disabled()) {
            $self->{logger}->debug($log_prefix . "HTTPD $name Server plugin loaded but disabled");
        } else {
            $self->{logger}->info($log_prefix . "HTTPD $name Server plugin loaded");
        }

        push @plugins, $plugin;
    }

    # Sort and store loaded plugins
    @plugins = sort { $b->priority() <=> $a->priority() } @plugins
        if @plugins > 1;
    $self->{_plugins} = \@plugins;

    return $self;
}

sub setTrustedAddresses {
    my ($self, %params) = @_;

    # compute addresses allowed for push requests
    foreach my $target ($self->{agent}->getTargets()) {
        next unless $target->isType('server');
        my $url  = $target->getUrl();
        my $host = URI->new($url)->host();
        my @addresses = compile($host, $self->{logger});
        $self->{trust}->{$url} = \@addresses;
        $self->{logger}->debug("Trusted target ip: ".join(", ",map { $_->print() } @addresses));
    }
    if ($params{trust}) {
        foreach my $string (@{$params{trust}}) {
            my @addresses = compile($string, $self->{logger})
                or next;
            $self->{trust}->{$string} = \@addresses;
            $self->{logger}->debug("Trusted client ip: ".join(", ",map { $_->print() } @addresses));
        }
    }
}

sub _handle {
    my ($self, $client, $request, $clientIp, $maxKeepAlive) = @_;

    my $logger = $self->{logger};

    if (!$request) {
        $client->close();
        return;
    }

    my $path = $request->uri()->path();
    my $method = $request->method();
    $logger->debug($log_prefix . "$method request $path from client $clientIp");

    my $keepalive = $request->header('connection') =~ /keep-alive/i;
    my $status = 400;
    my $error_400 = $log_prefix . "invalid request type: $method";

    SWITCH: {
        # root request
        if ($path eq '/') {
            last SWITCH if $method ne 'GET';
            $status = $self->_handle_root($client, $request, $clientIp);
            last SWITCH;
        }

        # deploy request
        if ($path =~ m{^/deploy/getFile/./../([\w\d/-]+)$}) {
            last SWITCH if $method ne 'GET';
            $status = $self->_handle_deploy($client, $request, $clientIp, $1);
            last SWITCH;
        }

        # plugins request
        foreach my $plugin (@{$self->{_plugins}}) {
            next if $plugin->disabled();
            if ($plugin->urlMatch($path)) {
                undef $error_400;
                last SWITCH unless $plugin->supported_method($method);
                $status = $plugin->handle($client, $request, $clientIp);
                last SWITCH if $status;
            }
        }

        # now request
        if ($path =~ m{^/now(?:/(\S*))?$}) {
            last SWITCH if $method ne 'GET';
            $status = $self->_handle_now($client, $request, $clientIp, $1);
            last SWITCH;
        }

        # status request
        if ($path eq '/status') {
            last SWITCH if $method ne 'GET';
            $status = $self->_handle_status($client, $request, $clientIp);
            last SWITCH;
        }

        # static content request
        if ($path =~ m{^/(logo.png|site.css|favicon.ico)$}) {
            my $file = $1;
            last SWITCH if $method ne 'GET';
            $client->send_file_response("$self->{htmldir}/$file");
            $status = 200;
            last SWITCH;
        }

        $error_400 = $log_prefix . "unknown path: $path";
    }

    if ($status == 400) {
        $logger->error($error_400) if $error_400;
        $client->send_error(400)
    }

    $logger->debug($log_prefix . "response status $status") unless $status == 1;

    if ($status == 200 && $keepalive && --$maxKeepAlive) {
        # Looking for another request
        $request = $client->get_request();
        $self->_handle($client, $request, $clientIp, $maxKeepAlive) if $request;
    }

    $client->close();
}

sub _handle_plugins {
    my ($self, $client, $request, $clientIp, $plugins, $maxKeepAlive) = @_;

    my $logger = $self->{logger};

    if (!$request) {
        $client->close();
        return;
    }

    my $path = $request->uri()->path();
    my $method = $request->method();
    my $keepalive = $request->header('connection') =~ /keep-alive/i;
    $logger->debug($log_prefix . "$method request $path from client $clientIp via plugin");
    my $status = 400;
    my $match  = 0;

    foreach my $plugin (@{$plugins}) {
        next if $plugin->disabled();
        if ($plugin->urlMatch($path)) {
            $match = 1;
            last unless ($plugin->supported_method($method));
            $status = $plugin->handle($client, $request, $clientIp);
            last if $status;
        }
    }

    if ($status == 400) {
        $logger->error($log_prefix . "unknown path: $path") unless $match;
        $client->send_error(400);
        $status = 400;
    }

    # Don't log status if we forked
    $logger->debug($log_prefix . "response status $status") unless $status == 1;

    if ($status == 200 && $keepalive && --$maxKeepAlive) {
        # Looking for another request
        $request = $client->get_request();
        $self->_handle_plugins($client, $request, $clientIp, $plugins, $maxKeepAlive) if $request;
    }

    $client->close();
}

sub _handle_root {
    my ($self, $client, $request, $clientIp) = @_;

    my $logger = $self->{logger};

    my $template = Text::Template->new(
        TYPE => 'FILE', SOURCE => "$self->{htmldir}/index.tpl"
    );
    if (!$template) {
        $logger->error(
            $log_prefix . "Template access failed: $Text::Template::ERROR"
        );

        my $response = HTTP::Response->new(
            500,
            'KO',
            HTTP::Headers->new('Content-Type' => 'text/html'),
            "No template"
        );

        $client->send_response($response);
        return 500;
    }

    my @server_targets =
        map { { name => $_->getUrl(), date => $_->getFormatedNextRunDate() } }
        grep { $_->isType('server') }
        $self->{agent}->getTargets();

    my @local_targets =
        map { { name => $_->getPath(), date => $_->getFormatedNextRunDate() } }
        grep { $_->isType('local') }
        $self->{agent}->getTargets();

    my @httpd_plugins = map { @{$_->{plugins}} } values(%{$self->{listeners}});
    push @httpd_plugins, @{$self->{_plugins}};
    my @listening_plugins =
        map { { port => $_->config('port') || $self->{port}, name => $_->name() } }
            grep { ! $_->disabled() }
                @httpd_plugins;

    my $hash = {
        version        => $FusionInventory::Agent::Version::VERSION,
        trust          => $self->_isTrusted($clientIp),
        status         => $self->{agent}->getStatus(),
        httpd_plugins  => \@listening_plugins,
        server_targets => \@server_targets,
        local_targets  => \@local_targets
    };

    my $response = HTTP::Response->new(
        200,



( run in 0.712 second using v1.01-cache-2.11-cpan-39bf76dae61 )