# Copyright 1999-2014. Parallels IP Holdings GmbH. All Rights Reserved.
package ContentDumper;

use strict;

use Dumper;
use Parser;
use Logging;
use Mailman;
use XmlNode;
use HelpFuncs;
use Db::Connection;
use Storage::Storage;
use Storage::ContentNameCreator;
use ContentTransport::Rsync;
use ContentTransport::Archive;

use vars qw|@ISA|;

sub new {
  my $self = {};
  bless( $self, shift );
  $self->_init(@_);
  return $self;
}

sub _init {
  my ( $self, $storagepolicy ) = @_;
  $self->{storage} = $storagepolicy;
  $self->{namecreator} = Storage::ContentNameCreator->new();
  
  $self->{content_transport_type} = 'archive';
  $self->{archive_transport} = ContentTransport::Archive->new( $_[0], $self->{storage} );
  # always use archive content transport for some cids (sql_dump, error_docs)
  $self->{content_transport} = $self->{archive_transport};
}

sub setContentTransport{
  my $self = shift;
  my $contentTransportType = shift;
  unless ( $self->{content_transport_type} eq $contentTransportType) {
    $self->{content_transport_type} = $contentTransportType;
    if ( $contentTransportType eq 'archive' ){
      $self->{content_transport} = ContentTransport::Archive->new( $_[0], $self->{storage} );
    }
    if ( $contentTransportType eq 'rsync' ){
      $self->{content_transport} = ContentTransport::Rsync->new( $_[0], $self->{storage} );
    }
  }
}

sub getContentTransportDescription {
  my $self = shift;
  return $self->{content_transport}->getContentTransportDescription();
}

sub getPhostingContent {
  my ($self, $domainName, $account, $metadataHash) = @_;

  my ( @wwwExclude, @childrens );

  my $domainWwwRoot = $metadataHash->{'www_root'};
  my $domainCgiRoot = $metadataHash->{'cgi_root'};
  my $domainCgiBinMode = $metadataHash->{'cgi_bin_mode'};

  if ( -d $domainCgiRoot ){
    my $proposedId = $self->{namecreator}->getPhostingDstFile('cgi', $domainName );
    my %options;
    $options{'checkEmptyDir'} = 1;
    $options{'directory'} = $domainCgiRoot;
    my $cgiNode = $self->{content_transport}->addContent('cgi', $proposedId, %options);
    push @childrens, $cgiNode if ( ref($cgiNode) =~ /XmlNode/ );
  }
  my $relativePath = HelpFuncs::getRelativePath($domainCgiRoot,$domainWwwRoot);
  if( $relativePath ) {
    @wwwExclude = ( $relativePath );
  }
  else {
    @wwwExclude = ();
  }

  my $ptrSubdomainsDirs = $self->getSubdomainsDirs($domainName, $account);
  #my $ptrFtpUsersDirs = $self->getFtpUsersDirs($account, $ptrSubdomainsDirs);
  push @wwwExclude, @{$ptrSubdomainsDirs};
 
  if ( -d $domainWwwRoot ){
    my $proposedId = $self->{namecreator}->getPhostingDstFile('docroot', $domainName);
    my %options;
    $options{'checkEmptyDir'} = 1;
    $options{'directory'} = $domainWwwRoot;
    $options{'exclude'} = \@wwwExclude;
    $options{'include_hidden_files'} = 1;
    my $docrootNode = $self->{content_transport}->addContent('docroot', $proposedId, %options);
    if ( ref($docrootNode)=~/XmlNode/ ) {
      addGroupMap($docrootNode, $account, 'psaserv');
      addGroupMap($docrootNode, 'nobody', 'psaserv');
      push @childrens, $docrootNode;
    }
  }

  my $errdocsNode = $self->getErrDocsContent( $domainName, $account, $metadataHash);
  push @childrens, $errdocsNode if ( ref($errdocsNode)=~/XmlNode/ );

  if ( @childrens ) {
    my $contentNode = XmlNode->new('content');
    foreach my $node ( @childrens ){
      $contentNode->addChild($node);
    }
    return $contentNode;
  }
  return;
}

my %errdocsFilenames = (
  '400' => 'bad_request.html',
  '401' => 'unauthorized.html',
  '403' => 'forbidden.html',
  '404' => 'not_found.html',
  '405' => 'method_not_allowed.html',
  '406' => 'not_acceptable.html',
  '407' => 'proxy_authentication_required.html',
  '412' => 'precondition_failed.html',
  '414' => 'request-uri_too_long.html',
  '415' => 'unsupported_media_type.html',
  '500' => 'internal_server_error.html',
  '501' => 'not_implemented.html',
  '502' => 'bad_gateway.html',
  '503' => 'maintenance.html',
);
sub getErrDocsContent {
  my ($self, $domainName, $account, $metadataHash) = @_;
  my $errdocPaths = Dumper::getErrdocPaths();
  my $domainWwwRoot = $metadataHash->{'www_root'};
  unless ( -d $domainWwwRoot ) {
    return;
  }
  
  my $proposedId = $self->{namecreator}->getPhostingDstFile('error_docs', $domainName);
  my $temp_dir = $self->{storage}->{'output_dir'}."/$proposedId"."_pmmtmp";
  HelpFuncs::make_path($temp_dir, { owner => $account,
                                    group => $account,
                                    mode  => 0755 } );
  my @includes;
  while ( ( my $errcode, my $path ) = each( %{$errdocPaths} ) ) {
    next unless exists $errdocsFilenames{$errcode};
    substr($path,0,1) = '' if substr($path,0,1) eq '/';
    my $errdoc_srcpath = "$domainWwwRoot/$path";
    my $errdoc_dstfile = $errdocsFilenames{$errcode};
    my $errdoc_dstpath = "$temp_dir/$errdoc_dstfile";
    next unless (-e $errdoc_srcpath);
    if( system("ln -s $errdoc_srcpath $errdoc_dstpath") != 0 ) {
      warn "Unable to create symbolic link: $!";
    }
    if ( -f $errdoc_dstpath) {
      push @includes, $errdoc_dstfile;
    }
  }
  my $errdocsNode;
  if ( @includes ) {
    my %options;
    $options{'directory'} = $temp_dir;
    $options{'include'} = [@includes];
    $options{'follow_symlinks'} = 1;
    $errdocsNode = $self->{archive_transport}->addContent('error_docs', $proposedId, %options);
    if ( ref($errdocsNode)=~/XmlNode/ ) {
      addGroupMap($errdocsNode, $account, 'psaserv');
    }
  }
  HelpFuncs::deleteFolder($temp_dir);
  return $errdocsNode;
}

sub getAnonFtpContent {
  my ($self, $domainName, $account) = @_;
  my $home = Dumper::getHomeDir($account);

  my (%options, @childrens);
  $options{'checkEmptyDir'} = 1;
  $options{'include_hidden_files'} = 1;

  my $publicFtp = "$home/public_ftp";
  unless(-d $publicFtp){
    return;
  }

  my $proposedId = $self->{namecreator}->getPhostingDstFile('ftp_incoming', $domainName);
  $options{'directory'} = "$publicFtp/incoming";
  my $incomingNode = $self->{content_transport}->addContent('ftp_incoming', $proposedId, %options);
  push @childrens, $incomingNode if ( ref($incomingNode) =~ /XmlNode/ );

  $proposedId = $self->{namecreator}->getPhostingDstFile('ftp_pub', $domainName);
  $options{'directory'} = $publicFtp;
  my @exclude = ('incoming');
  $options{'exclude'} = \@exclude;
  my $pubNode = $self->{content_transport}->addContent('ftp_pub', $proposedId, %options);
  push @childrens, $pubNode if ( ref($pubNode) =~ /XmlNode/ );

  if ( @childrens ) {
    my $contentNode = XmlNode->new('content');
    foreach my $node (@childrens){
      $contentNode->addChild($node);
    }
    return $contentNode;
  }
  return;
}

sub getSubdomainContent {
  my ($self, $subdomainName, $domainName, $account, $metadataHash) = @_;

  my $subdomainWwwRoot = $metadataHash->{'www_root'};
  my $subdomainCgiRoot = $metadataHash->{'cgi_root'};
  my $subdomainCgiBinMode = $metadataHash->{'cgi_bin_mode'};

  my ( @wwwExclude, @childrens);

  if( ( $subdomainCgiBinMode eq 'www-root') &&  ( -d $subdomainCgiRoot) ){
    my $proposedId = $self->{namecreator}->getSubdomainDstFile('cgi', $domainName, $subdomainName );
    my %options;
    $options{'checkEmptyDir'} = 1;
    $options{'directory'} = $subdomainCgiRoot;
    my $cgiNode = $self->{content_transport}->addContent('cgi', $proposedId, %options);;
    push @childrens, $cgiNode if ( ref($cgiNode) =~ /XmlNode/ );
    @wwwExclude = ( HelpFuncs::getRelativePath($subdomainCgiRoot,$subdomainWwwRoot) );
  }
  else {
    @wwwExclude = ();
  }

  if ( -d $subdomainWwwRoot ) {
    my $proposedId = $self->{namecreator}->getSubdomainDstFile('docroot', $domainName, $subdomainName);
    my %options;
    $options{'checkEmptyDir'} = 1;
    $options{'directory'} = $subdomainWwwRoot;
    $options{'exclude'} = \@wwwExclude;
    $options{'include_hidden_files'} = 1;
    my $docrootNode = $self->{content_transport}->addContent('docroot', $proposedId, %options);
    if ( ref($docrootNode) =~ /XmlNode/ ) {
      addGroupMap($docrootNode, $account, 'psaserv');
      push @childrens, $docrootNode;
    }
  }

  if ( @childrens ) {
    my $contentNode = XmlNode->new('content');
    foreach my $node ( @childrens ){
      $contentNode->addChild($node);
    }
    return $contentNode;
  }
  return;
}

sub getSubdomainAsDomainContent {
  my ($self, $subdomainName, $domainName, $resultName, $account, $metadataHash) = @_;

  my $domainWwwRoot = $metadataHash->{'www_root'};
  my $domainCgiRoot = $metadataHash->{'cgi_root'};
  my $domainCgiBinMode = $metadataHash->{'cgi_bin_mode'};

  my ( @wwwExclude, @childrens);

  if( ( $domainCgiBinMode eq 'www-root') &&  ( -d $domainCgiRoot) ){
    my $proposedId = $self->{namecreator}->getPhostingDstFile('cgi', $resultName );
    my %options;
    $options{'checkEmptyDir'} = 1;
    $options{'directory'} = $domainCgiRoot;
    my $cgiNode = $self->{content_transport}->addContent('cgi', $proposedId, %options);
    push @childrens, $cgiNode if (ref($cgiNode) =~ /XmlNode/);
    @wwwExclude = ( HelpFuncs::getRelativePath($domainCgiRoot,$domainWwwRoot) );
  }
  else {
    @wwwExclude = ();
  }

  if ( -d $domainWwwRoot ) {
    my $proposedId = $self->{namecreator}->getPhostingDstFile('docroot', $resultName);
    my %options;
    $options{'checkEmptyDir'} = 1;
    $options{'directory'} = $domainWwwRoot;
    $options{'exclude'} = \@wwwExclude;
    $options{'include_hidden_files'} = 1;
    my $docrootNode = $self->{content_transport}->addContent('docroot', $proposedId, %options);
    if ( ref($docrootNode) =~ /XmlNode/ ) {
      addGroupMap($docrootNode, $account, 'psaserv');
      push @childrens, $docrootNode;
    }
  }

  if ( @childrens ) {
    my $contentNode = XmlNode->new('content');
    foreach my $node ( @childrens ){
      $contentNode->addChild($node);
    }
    return $contentNode;
  }
  return;
}

sub getDatabaseContent {
  my ($self, $dbName, $dbType, $dbUser, $domain, $dbPassword, $dbHost, $ptrDomainBin, $variables, $domainSocket) = @_;

  my %params;

  if ($dbType eq 'Pg') { $dbType = 'postgresql'; }

  $params{'name'} = $dbName;
  $params{'user'} = $dbUser;
  $params{'type'} = $dbType;
  $params{'password'} = $dbPassword;
  $params{'host'} = $dbHost if $dbHost;
  $params{'socket'} = $domainSocket if $domainSocket;
  $params{'preload_dirs'} = $variables if $variables;
  $params{'utf8names'} = 1;

  my %testparams = %params;
  # check db existence, bug 83616
  my $connection = Db::Connection::getConnection(%testparams);
  my $db_exist = $connection->execute_rownum($dbType eq 'postgresql' ? "SELECT 1 FROM pg_database WHERE datname = '$dbName'" : 'SELECT 1');
  $connection->finish();
  $connection->disconnect();

  if ($db_exist) {
    my $proposedId = $self->{namecreator}->getDbDstFile('sqldump', $domain, $dbName);
    my $sqldumpNode = $self->{archive_transport}->addDbContent( $proposedId, %params);
    if ( ref($sqldumpNode) =~ /XmlNode/ ) {
      my $contentNode = XmlNode->new('content');
      $contentNode->addChild( $sqldumpNode );
      return $contentNode;
    }
  }
  return;
}

sub getWebUserContent {
  my ($self, $wuName, $domainName, $account) = @_;

  my $dir;

  my $ptrProftpdRows = Dumper::getProftpdAccountRows($account);
  if ( defined $ptrProftpdRows ) {
    foreach my $ptrRow (@{$ptrProftpdRows}) {
      if ($ptrRow->[0] eq $wuName) {
        $dir = $ptrRow->[5]; 
        last;
      }
    }
    if ( -d $dir) {
      my %options;
      $options{'directory'} = $dir;
      $options{'checkEmptyDir'} = 1;

      my $proposedId = $self->{namecreator}->getWebUserDstFile($domainName, $wuName);
      my $webuserhomeNode = $self->{content_transport}->addContent('webuser_home', $proposedId, %options);
      if ( ref($webuserhomeNode) =~ /XmlNode/ ) {
        my $contentNode = XmlNode->new('content');
        $contentNode->addChild( $webuserhomeNode );
        return $contentNode;
      }
    }
  }
  return;
}

sub _getDefaultMailnameContent {
  my ($self, $mailnameMeta) = @_;

  return unless ( ref( $mailnameMeta ) =~ /HASH/ );

  my $mailname = $mailnameMeta->{'mailname'};
  my $domain = $mailnameMeta->{'domain'};
  my $account = $mailnameMeta->{'account'};
  my $exclude = $mailnameMeta->{'exclude'};

  return if ( !$domain || !$account || !$mailname);

  return unless ( $mailname eq $account );

  my $home = Dumper::getHomeDir($account);

  my $dir = "$home/mail";

  if ( -d $dir) {
    my %options;
    $options{'directory'} = $dir;
    $options{'checkEmptyDir'} = 1;
    $options{'include_hidden_files'} = 1;
    $options{'follow_symlinks'} = 0;
    $options{'exclude'} = $exclude if ( ref($exclude) =~ /ARRAY/ );

    my $proposedId = $self->{namecreator}->getMailnameDstFile($mailname, $domain);
    my $mailboxNode = $self->{content_transport}->addContent('mailbox', $proposedId, %options);
    if ( ref($mailboxNode) =~ /XmlNode/ ) {
      my $contentNode = XmlNode->new('content');
      $contentNode->addChild( $mailboxNode );
      return $contentNode;
    }
    
  }
  return;
}

sub getMailnameContent {
  my ($self, $mailnameMeta) = @_;

  return unless ( ref( $mailnameMeta) =~ /HASH/ );

  my $mailname = $mailnameMeta->{'mailname'};
  my $domain = $mailnameMeta->{'domain'};
  my $account = $mailnameMeta->{'account'};
  my $exclude = $mailnameMeta->{'exclude'};

  return if ( !$domain || !$account || !$mailname);

  if ( $mailname eq $account ) {
    return _getDefaultMailnameContent( $self, $mailnameMeta );
  }

  my $home = Dumper::getHomeDir($account);

  my $dir = "$home/mail/$domain/$mailname";

  if ( -d $dir) {
    my %options;
    $options{'directory'} = $dir;
    $options{'checkEmptyDir'} = 1;
    $options{'include_hidden_files'} = 1;
    $options{'exclude'} = $exclude if ( ref($exclude) =~ /ARRAY/ );

    my $proposedId = $self->{namecreator}->getMailnameDstFile($mailname, $domain);
    my $mailboxNode = $self->{content_transport}->addContent('mailbox', $proposedId, %options);
    if ( ref($mailboxNode) =~ /XmlNode/ ) {
      my $contentNode = XmlNode->new('content');
      $contentNode->addChild( $mailboxNode );
      return $contentNode;
    }
  }
  return;
}

sub getMaillistContent {
  my ($self, $listname, $domain) = @_;

  return unless ( defined Mailman::version() );

  my $dir = Mailman::getMailListArchiveDir($listname. '_' . $domain);

  if ( -d $dir) {
    my %options;
    $options{'directory'} = $dir;
    $options{'checkEmptyDir'} = 1;

    my $proposedId = $self->{namecreator}->getMaillistDstFile($listname, $domain);
    my $maillistNode = $self->{content_transport}->addContent('domainmaillist', $proposedId, %options);
    if ( ref($maillistNode) =~ /XmlNode/ ) {
      my $contentNode = XmlNode->new('content');
      $contentNode->addChild( $maillistNode );
      return $contentNode;
    }
  }
  return;
}

sub getFtpUsersDirs {
  my ($self, $account, $subdomains) = @_;

  my $home = Dumper::getHomeDir($account);

  my @ftpusersdirs;

  my $ptrProftpdRows = Dumper::getProftpdAccountRows($account);
  if ( defined $ptrProftpdRows ) {
    my ($pattern, $is_subdomain_directory);
    $pattern = qr/\Q$home\E\/public_html\/(.*)/;
    foreach my $ptrRow (@{$ptrProftpdRows}) {
      next unless ($ptrRow->[4] eq $account);
      my $path=$ptrRow->[5];
      if ($path=~/$pattern/) {
        my $dir =$1;
        $is_subdomain_directory=0;
        foreach my $subdompattern (@{$subdomains}) {
          $subdompattern = qr/^\Q$subdompattern\E(?:\/.*)?/; 
          if ($dir=~/$subdompattern/) {
            $is_subdomain_directory=1;
            last;
          }
        }
        next if ($is_subdomain_directory);
        push (@ftpusersdirs, $dir);
      }
    }
  }

  return \@ftpusersdirs;
}

sub getSubdomainsDirs {
  my ($self, $domain, $account) = @_;

  my ($ptrUserdomainsHash, $subdomain, $acc, @subdomains, $dir);

  unless(ref($ptrUserdomainsHash)=~/HASH/){
    my $fileParser = Parser::makeFileParser('/etc/userdomains');
    $ptrUserdomainsHash = $fileParser->{'PARSE'}->('KEYSEPARATOR'=>':');
  }
  if (ref($ptrUserdomainsHash)=~/HASH/) {
    my $pattern = qr/^(.+)\.\Q$domain\E$/;
    while ( ($subdomain,$acc) = each %{$ptrUserdomainsHash} ) {
      next unless ($acc eq $account);
      if ($subdomain=~/$pattern/) {
        $dir = $1;
        push @subdomains,$dir;
      }
    }
  }
  return \@subdomains;
}

sub addGroupMap {
  my ($cidNode, $name_from, $name_to ) = @_;
  if ( ref($cidNode) =~ /XmlNode/ ) {
    my $accoutGroupMapNode = XmlNode->new( 'group-map' );
    $accoutGroupMapNode->setAttribute( 'name-from', $name_from );
    $accoutGroupMapNode->setAttribute( 'name-to', $name_to );
    $cidNode->addChild($accoutGroupMapNode);
  }
  $cidNode;
}


1;
