#!/usr/local/cpanel/3rdparty/bin/perl
BEGIN { # Suppress load of all of these at earliest point.
$INC{'HTTP/Tiny.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Try/Tiny.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Time/Local.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Fcntl/Constants.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Fcntl.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/FileUtils/Open.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Parser/Vars.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Encoder/Tiny/Rare.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Encoder/Tiny.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Regex.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Carp.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/ExceptionMessage.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Locale/Utils/Fallback.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/ExceptionMessage/Raw.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/LoadModule/Utils.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/ScalarUtil.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Exception/CORE.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/TimeHiRes.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/SafeFileLock.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/LoadModule.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/FHUtils/Tiny.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Hash.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/SafeFile.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Linux/Constants.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Validate/FilesystemNodeName.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Debug.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Notify.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Server/Utils.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Logger.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Sys/GetOS.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Sys/OS.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Struct/Common/Time.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Struct/timespec.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/NanoStat.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/NanoUtime.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/HiRes.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Env.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Autodie.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/FileUtils/Touch.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Config/TouchFileBase.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Update/IsCron.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/SafeDir/MK.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/FHUtils/Autoflush.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Update/Logger.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/FileUtils/TouchFile.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/LoadFile/ReadFast.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/LoadFile.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Usage.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Unix/PID/Tiny.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Encoder/ASCII.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/UTF8/Strict.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/JSON.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/JSON/FailOK.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/ConfigFiles.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Destruct.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Finally.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/FindBin.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/SafeRun/Simple.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Readlink.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/FileUtils/Write.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/FileUtils/Write/JSON/Lazy.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/CPAN/I18N/LangTags.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/CPAN/I18N/LangTags/Detect.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/CPAN/Locale/Maketext.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Locale/Utils/Normalize.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/CPAN/Locales/Legacy.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/CPAN/Locales/DB/LocaleDisplayPattern/Tiny.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/CPAN/Locales/DB/CharacterOrientation/Tiny.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/CPAN/Locales/Compile.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/CPAN/Locales.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Encoder/Punycode.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/CPAN/Locale/Maketext/Utils.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Locale/Utils/Paths.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Locale/Utils.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/DB/Utils.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/AdminBin/Serializer.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/AdminBin/Serializer/FailOK.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Config/Constants.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Hash/Stringify.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Umask.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Config/LoadConfig.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Config/LoadWwwAcctConf.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Conf.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Config/LoadCpUserFile.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Config/HasCpUserFile.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/NSCD/Constants.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Socket/UNIX/Micro.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/NSCD/Check.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/PwCache/Helpers.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/PwCache/Cache.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/PwCache/Find.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/PwCache/Build.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/PwCache.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Locale/Utils/User.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Cookies.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/SafeDir/Read.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Locale/Utils/Charmap.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/StringFunc/Case.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Locale/Utils/Legacy.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Config/LoadCpUserFile/CurrentUser.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/YAML/Syck.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/ArrayFunc/Uniq.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/PwUtils.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/AccessIds/Normalize.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/AccessIds/Utils.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/AccessIds/ReducedPrivileges.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/DataStore.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/StringFunc/Trim.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Locale/Utils/3rdparty.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/JS/Variations.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Locale/Utils/Display.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Locale/Utils/Api1.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/StatCache.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/CachedCommand/Utils.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/CachedCommand/Valid.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/CachedCommand/Save.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Context.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/LocaleString.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Errno.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Config/Constants/Perl.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/ChildErrorStringifier.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/FHUtils/OS.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/FHUtils/Blocking.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/IO/Flush.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/ReadMultipleFH.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/ForkAsync.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/SafeRun/Object.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/SafeRun/Env.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/CachedCommand.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Time/TZ.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Locale/Utils/DateTime.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/DateUtils.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Validate/Time.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Time/ISO.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Config/LoadUserDomains/Count.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Server/Type.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Config/LoadUserDomains.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Config/CpUser.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Config/FlushConfig.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/LinkedNode/Worker/Storage.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/SafeFile/Replace.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Config/CpUserGuard.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Locale/Utils/User/Modify.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Locale.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Sys/Uname.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Sys/Hostname/Fallback.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Sys/Hostname.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Hostname.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Config/CpConfGuard/CORE.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Config/CpConfGuard.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Config/LoadCpConf.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Maxmem.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/OSSys/Bits.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Pack.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Syscall.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Sys/Rlimit.pm'} = '/usr/local/cpanel/scripts/upcp.static';
$INC{'Cpanel/Rlimit.pm'} = '/usr/local/cpanel/scripts/upcp.static';
}
{ # --- BEGIN HTTP::Tiny
# vim: ts=4 sts=4 sw=4 et:
package HTTP::Tiny;
use strict;
use warnings;
# ABSTRACT: A small, simple, correct HTTP/1.1 client
our $VERSION = '0.076';
sub _croak { require Carp; Carp::croak(@_) }
#pod =method new
#pod
#pod $http = HTTP::Tiny->new( %attributes );
#pod
#pod This constructor returns a new HTTP::Tiny object. Valid attributes include:
#pod
#pod =for :list
#pod * C<agent> — A user-agent string (defaults to 'HTTP-Tiny/$VERSION'). If
#pod C<agent> — ends in a space character, the default user-agent string is
#pod appended.
#pod * C<cookie_jar> — An instance of L<HTTP::CookieJar> — or equivalent class
#pod that supports the C<add> and C<cookie_header> methods
#pod * C<default_headers> — A hashref of default headers to apply to requests
#pod * C<local_address> — The local IP address to bind to
#pod * C<keep_alive> — Whether to reuse the last connection (if for the same
#pod scheme, host and port) (defaults to 1)
#pod * C<max_redirect> — Maximum number of redirects allowed (defaults to 5)
#pod * C<max_size> — Maximum response size in bytes (only when not using a data
#pod callback). If defined, responses larger than this will return an
#pod exception.
#pod * C<http_proxy> — URL of a proxy server to use for HTTP connections
#pod (default is C<$ENV{http_proxy}> — if set)
#pod * C<https_proxy> — URL of a proxy server to use for HTTPS connections
#pod (default is C<$ENV{https_proxy}> — if set)
#pod * C<proxy> — URL of a generic proxy server for both HTTP and HTTPS
#pod connections (default is C<$ENV{all_proxy}> — if set)
#pod * C<no_proxy> — List of domain suffixes that should not be proxied. Must
#pod be a comma-separated string or an array reference. (default is
#pod C<$ENV{no_proxy}> —)
#pod * C<timeout> — Request timeout in seconds (default is 60) If a socket open,
#pod read or write takes longer than the timeout, an exception is thrown.
#pod * C<verify_SSL> — A boolean that indicates whether to validate the SSL
#pod certificate of an C<https> — connection (default is false)
#pod * C<SSL_options> — A hashref of C<SSL_*> — options to pass through to
#pod L<IO::Socket::SSL>
#pod
#pod Passing an explicit C<undef> for C<proxy>, C<http_proxy> or C<https_proxy> will
#pod prevent getting the corresponding proxies from the environment.
#pod
#pod Exceptions from C<max_size>, C<timeout> or other errors will result in a
#pod pseudo-HTTP status code of 599 and a reason of "Internal Exception". The
#pod content field in the response will contain the text of the exception.
#pod
#pod The C<keep_alive> parameter enables a persistent connection, but only to a
#pod single destination scheme, host and port. Also, if any connection-relevant
#pod attributes are modified, or if the process ID or thread ID change, the
#pod persistent connection will be dropped. If you want persistent connections
#pod across multiple destinations, use multiple HTTP::Tiny objects.
#pod
#pod See L</SSL SUPPORT> for more on the C<verify_SSL> and C<SSL_options> attributes.
#pod
#pod =cut
my @attributes;
BEGIN {
@attributes = qw(
cookie_jar default_headers http_proxy https_proxy keep_alive
local_address max_redirect max_size proxy no_proxy
SSL_options verify_SSL
);
my %persist_ok = map {; $_ => 1 } qw(
cookie_jar default_headers max_redirect max_size
);
no strict 'refs';
no warnings 'uninitialized';
for my $accessor ( @attributes ) {
*{$accessor} = sub {
@_ > 1
? do {
delete $_[0]->{handle} if !$persist_ok{$accessor} && $_[1] ne $_[0]->{$accessor};
$_[0]->{$accessor} = $_[1]
}
: $_[0]->{$accessor};
};
}
}
sub agent {
my($self, $agent) = @_;
if( @_ > 1 ){
$self->{agent} =
(defined $agent && $agent =~ / $/) ? $agent . $self->_agent : $agent;
}
return $self->{agent};
}
sub timeout {
my ($self, $timeout) = @_;
if ( @_ > 1 ) {
$self->{timeout} = $timeout;
if ($self->{handle}) {
$self->{handle}->timeout($timeout);
}
}
return $self->{timeout};
}
sub new {
my($class, %args) = @_;
my $self = {
max_redirect => 5,
timeout => defined $args{timeout} ? $args{timeout} : 60,
keep_alive => 1,
verify_SSL => $args{verify_SSL} || $args{verify_ssl} || 0, # no verification by default
no_proxy => $ENV{no_proxy},
};
bless $self, $class;
$class->_validate_cookie_jar( $args{cookie_jar} ) if $args{cookie_jar};
for my $key ( @attributes ) {
$self->{$key} = $args{$key} if exists $args{$key}
}
$self->agent( exists $args{agent} ? $args{agent} : $class->_agent );
$self->_set_proxies;
return $self;
}
sub _set_proxies {
my ($self) = @_;
# get proxies from %ENV only if not provided; explicit undef will disable
# getting proxies from the environment
# generic proxy
if (! exists $self->{proxy} ) {
$self->{proxy} = $ENV{all_proxy} || $ENV{ALL_PROXY};
}
if ( defined $self->{proxy} ) {
$self->_split_proxy( 'generic proxy' => $self->{proxy} ); # validate
}
else {
delete $self->{proxy};
}
# http proxy
if (! exists $self->{http_proxy} ) {
# under CGI, bypass HTTP_PROXY as request sets it from Proxy header
local $ENV{HTTP_PROXY} if $ENV{REQUEST_METHOD};
$self->{http_proxy} = $ENV{http_proxy} || $ENV{HTTP_PROXY} || $self->{proxy};
}
if ( defined $self->{http_proxy} ) {
$self->_split_proxy( http_proxy => $self->{http_proxy} ); # validate
$self->{_has_proxy}{http} = 1;
}
else {
delete $self->{http_proxy};
}
# https proxy
if (! exists $self->{https_proxy} ) {
$self->{https_proxy} = $ENV{https_proxy} || $ENV{HTTPS_PROXY} || $self->{proxy};
}
if ( $self->{https_proxy} ) {
$self->_split_proxy( https_proxy => $self->{https_proxy} ); # validate
$self->{_has_proxy}{https} = 1;
}
else {
delete $self->{https_proxy};
}
# Split no_proxy to array reference if not provided as such
unless ( ref $self->{no_proxy} eq 'ARRAY' ) {
$self->{no_proxy} =
(defined $self->{no_proxy}) ? [ split /\s*,\s*/, $self->{no_proxy} ] : [];
}
return;
}
#pod =method get|head|put|post|delete
#pod
#pod $response = $http->get($url);
#pod $response = $http->get($url, \%options);
#pod $response = $http->head($url);
#pod
#pod These methods are shorthand for calling C<request()> for the given method. The
#pod URL must have unsafe characters escaped and international domain names encoded.
#pod See C<request()> for valid options and a description of the response.
#pod
#pod The C<success> field of the response will be true if the status code is 2XX.
#pod
#pod =cut
for my $sub_name ( qw/get head put post delete/ ) {
my $req_method = uc $sub_name;
no strict 'refs';
eval <<"HERE"; ## no critic
sub $sub_name {
my (\$self, \$url, \$args) = \@_;
\@_ == 2 || (\@_ == 3 && ref \$args eq 'HASH')
or _croak(q/Usage: \$http->$sub_name(URL, [HASHREF])/ . "\n");
return \$self->request('$req_method', \$url, \$args || {});
}
HERE
}
#pod =method post_form
#pod
#pod $response = $http->post_form($url, $form_data);
#pod $response = $http->post_form($url, $form_data, \%options);
#pod
#pod This method executes a C<POST> request and sends the key/value pairs from a
#pod form data hash or array reference to the given URL with a C<content-type> of
#pod C<application/x-www-form-urlencoded>. If data is provided as an array
#pod reference, the order is preserved; if provided as a hash reference, the terms
#pod are sorted on key and value for consistency. See documentation for the
#pod C<www_form_urlencode> method for details on the encoding.
#pod
#pod The URL must have unsafe characters escaped and international domain names
#pod encoded. See C<request()> for valid options and a description of the response.
#pod Any C<content-type> header or content in the options hashref will be ignored.
#pod
#pod The C<success> field of the response will be true if the status code is 2XX.
#pod
#pod =cut
sub post_form {
my ($self, $url, $data, $args) = @_;
(@_ == 3 || @_ == 4 && ref $args eq 'HASH')
or _croak(q/Usage: $http->post_form(URL, DATAREF, [HASHREF])/ . "\n");
my $headers = {};
while ( my ($key, $value) = each %{$args->{headers} || {}} ) {
$headers->{lc $key} = $value;
}
delete $args->{headers};
return $self->request('POST', $url, {
%$args,
content => $self->www_form_urlencode($data),
headers => {
%$headers,
'content-type' => 'application/x-www-form-urlencoded'
},
}
);
}
#pod =method mirror
#pod
#pod $response = $http->mirror($url, $file, \%options)
#pod if ( $response->{success} ) {
#pod print "$file is up to date\n";
#pod }
#pod
#pod Executes a C<GET> request for the URL and saves the response body to the file
#pod name provided. The URL must have unsafe characters escaped and international
#pod domain names encoded. If the file already exists, the request will include an
#pod C<If-Modified-Since> header with the modification timestamp of the file. You
#pod may specify a different C<If-Modified-Since> header yourself in the C<<
#pod $options->{headers} >> hash.
#pod
#pod The C<success> field of the response will be true if the status code is 2XX
#pod or if the status code is 304 (unmodified).
#pod
#pod If the file was modified and the server response includes a properly
#pod formatted C<Last-Modified> header, the file modification time will
#pod be updated accordingly.
#pod
#pod =cut
sub mirror {
my ($self, $url, $file, $args) = @_;
@_ == 3 || (@_ == 4 && ref $args eq 'HASH')
or _croak(q/Usage: $http->mirror(URL, FILE, [HASHREF])/ . "\n");
if ( exists $args->{headers} ) {
my $headers = {};
while ( my ($key, $value) = each %{$args->{headers} || {}} ) {
$headers->{lc $key} = $value;
}
$args->{headers} = $headers;
}
if ( -e $file and my $mtime = (stat($file))[9] ) {
$args->{headers}{'if-modified-since'} ||= $self->_http_date($mtime);
}
my $tempfile = $file . int(rand(2**31));
require Fcntl;
sysopen my $fh, $tempfile, Fcntl::O_CREAT()|Fcntl::O_EXCL()|Fcntl::O_WRONLY()
or _croak(qq/Error: Could not create temporary file $tempfile for downloading: $!\n/);
binmode $fh;
$args->{data_callback} = sub { print {$fh} $_[0] };
my $response = $self->request('GET', $url, $args);
close $fh
or _croak(qq/Error: Caught error closing temporary file $tempfile: $!\n/);
if ( $response->{success} ) {
rename $tempfile, $file
or _croak(qq/Error replacing $file with $tempfile: $!\n/);
my $lm = $response->{headers}{'last-modified'};
if ( $lm and my $mtime = $self->_parse_http_date($lm) ) {
utime $mtime, $mtime, $file;
}
}
$response->{success} ||= $response->{status} eq '304';
unlink $tempfile;
return $response;
}
#pod =method request
#pod
#pod $response = $http->request($method, $url);
#pod $response = $http->request($method, $url, \%options);
#pod
#pod Executes an HTTP request of the given method type ('GET', 'HEAD', 'POST',
#pod 'PUT', etc.) on the given URL. The URL must have unsafe characters escaped and
#pod international domain names encoded.
#pod
#pod B<NOTE>: Method names are B<case-sensitive> per the HTTP/1.1 specification.
#pod Don't use C<get> when you really want C<GET>. See L<LIMITATIONS> for
#pod how this applies to redirection.
#pod
#pod If the URL includes a "user:password" stanza, they will be used for Basic-style
#pod authorization headers. (Authorization headers will not be included in a
#pod redirected request.) For example:
#pod
#pod $http->request('GET', 'http://Aladdin:open sesame@example.com/');
#pod
#pod If the "user:password" stanza contains reserved characters, they must
#pod be percent-escaped:
#pod
#pod $http->request('GET', 'http://john%40example.com:password@example.com/');
#pod
#pod A hashref of options may be appended to modify the request.
#pod
#pod Valid options are:
#pod
#pod =for :list
#pod * C<headers> —
#pod A hashref containing headers to include with the request. If the value for
#pod a header is an array reference, the header will be output multiple times with
#pod each value in the array. These headers over-write any default headers.
#pod * C<content> —
#pod A scalar to include as the body of the request OR a code reference
#pod that will be called iteratively to produce the body of the request
#pod * C<trailer_callback> —
#pod A code reference that will be called if it exists to provide a hashref
#pod of trailing headers (only used with chunked transfer-encoding)
#pod * C<data_callback> —
#pod A code reference that will be called for each chunks of the response
#pod body received.
#pod * C<peer> —
#pod Override host resolution and force all connections to go only to a
#pod specific peer address, regardless of the URL of the request. This will
#pod include any redirections! This options should be used with extreme
#pod caution (e.g. debugging or very special circumstances). It can be given as
#pod either a scalar or a code reference that will receive the hostname and
#pod whose response will be taken as the address.
#pod
#pod The C<Host> header is generated from the URL in accordance with RFC 2616. It
#pod is a fatal error to specify C<Host> in the C<headers> option. Other headers
#pod may be ignored or overwritten if necessary for transport compliance.
#pod
#pod If the C<content> option is a code reference, it will be called iteratively
#pod to provide the content body of the request. It should return the empty
#pod string or undef when the iterator is exhausted.
#pod
#pod If the C<content> option is the empty string, no C<content-type> or
#pod C<content-length> headers will be generated.
#pod
#pod If the C<data_callback> option is provided, it will be called iteratively until
#pod the entire response body is received. The first argument will be a string
#pod containing a chunk of the response body, the second argument will be the
#pod in-progress response hash reference, as described below. (This allows
#pod customizing the action of the callback based on the C<status> or C<headers>
#pod received prior to the content body.)
#pod
#pod The C<request> method returns a hashref containing the response. The hashref
#pod will have the following keys:
#pod
#pod =for :list
#pod * C<success> —
#pod Boolean indicating whether the operation returned a 2XX status code
#pod * C<url> —
#pod URL that provided the response. This is the URL of the request unless
#pod there were redirections, in which case it is the last URL queried
#pod in a redirection chain
#pod * C<status> —
#pod The HTTP status code of the response
#pod * C<reason> —
#pod The response phrase returned by the server
#pod * C<content> —
#pod The body of the response. If the response does not have any content
#pod or if a data callback is provided to consume the response body,
#pod this will be the empty string
#pod * C<headers> —
#pod A hashref of header fields. All header field names will be normalized
#pod to be lower case. If a header is repeated, the value will be an arrayref;
#pod it will otherwise be a scalar string containing the value
#pod * C<protocol> -
#pod If this field exists, it is the protocol of the response
#pod such as HTTP/1.0 or HTTP/1.1
#pod * C<redirects>
#pod If this field exists, it is an arrayref of response hash references from
#pod redirects in the same order that redirections occurred. If it does
#pod not exist, then no redirections occurred.
#pod
#pod On an exception during the execution of the request, the C<status> field will
#pod contain 599, and the C<content> field will contain the text of the exception.
#pod
#pod =cut
my %idempotent = map { $_ => 1 } qw/GET HEAD PUT DELETE OPTIONS TRACE/;
sub request {
my ($self, $method, $url, $args) = @_;
@_ == 3 || (@_ == 4 && ref $args eq 'HASH')
or _croak(q/Usage: $http->request(METHOD, URL, [HASHREF])/ . "\n");
$args ||= {}; # we keep some state in this during _request
# RFC 2616 Section 8.1.4 mandates a single retry on broken socket
my $response;
for ( 0 .. 1 ) {
$response = eval { $self->_request($method, $url, $args) };
last unless $@ && $idempotent{$method}
&& $@ =~ m{^(?:Socket closed|Unexpected end)};
}
if (my $e = $@) {
# maybe we got a response hash thrown from somewhere deep
if ( ref $e eq 'HASH' && exists $e->{status} ) {
$e->{redirects} = delete $args->{_redirects} if @{ $args->{_redirects} || []};
return $e;
}
# otherwise, stringify it
$e = "$e";
$response = {
url => $url,
success => q{},
status => 599,
reason => 'Internal Exception',
content => $e,
headers => {
'content-type' => 'text/plain',
'content-length' => length $e,
},
( @{$args->{_redirects} || []} ? (redirects => delete $args->{_redirects}) : () ),
};
}
return $response;
}
#pod =method www_form_urlencode
#pod
#pod $params = $http->www_form_urlencode( $data );
#pod $response = $http->get("http://example.com/query?$params");
#pod
#pod This method converts the key/value pairs from a data hash or array reference
#pod into a C<x-www-form-urlencoded> string. The keys and values from the data
#pod reference will be UTF-8 encoded and escaped per RFC 3986. If a value is an
#pod array reference, the key will be repeated with each of the values of the array
#pod reference. If data is provided as a hash reference, the key/value pairs in the
#pod resulting string will be sorted by key and value for consistent ordering.
#pod
#pod =cut
sub www_form_urlencode {
my ($self, $data) = @_;
(@_ == 2 && ref $data)
or _croak(q/Usage: $http->www_form_urlencode(DATAREF)/ . "\n");
(ref $data eq 'HASH' || ref $data eq 'ARRAY')
or _croak("form data must be a hash or array reference\n");
my @params = ref $data eq 'HASH' ? %$data : @$data;
@params % 2 == 0
or _croak("form data reference must have an even number of terms\n");
my @terms;
while( @params ) {
my ($key, $value) = splice(@params, 0, 2);
if ( ref $value eq 'ARRAY' ) {
unshift @params, map { $key => $_ } @$value;
}
else {
push @terms, join("=", map { $self->_uri_escape($_) } $key, $value);
}
}
return join("&", (ref $data eq 'ARRAY') ? (@terms) : (sort @terms) );
}
#pod =method can_ssl
#pod
#pod $ok = HTTP::Tiny->can_ssl;
#pod ($ok, $why) = HTTP::Tiny->can_ssl;
#pod ($ok, $why) = $http->can_ssl;
#pod
#pod Indicates if SSL support is available. When called as a class object, it
#pod checks for the correct version of L<Net::SSLeay> and L<IO::Socket::SSL>.
#pod When called as an object methods, if C<SSL_verify> is true or if C<SSL_verify_mode>
#pod is set in C<SSL_options>, it checks that a CA file is available.
#pod
#pod In scalar context, returns a boolean indicating if SSL is available.
#pod In list context, returns the boolean and a (possibly multi-line) string of
#pod errors indicating why SSL isn't available.
#pod
#pod =cut
sub can_ssl {
my ($self) = @_;
my($ok, $reason) = (1, '');
# Need IO::Socket::SSL 1.42 for SSL_create_ctx_callback
local @INC = @INC;
pop @INC if $INC[-1] eq '.';
unless (eval {require IO::Socket::SSL; IO::Socket::SSL->VERSION(1.42)}) {
$ok = 0;
$reason .= qq/IO::Socket::SSL 1.42 must be installed for https support\n/;
}
# Need Net::SSLeay 1.49 for MODE_AUTO_RETRY
unless (eval {require Net::SSLeay; Net::SSLeay->VERSION(1.49)}) {
$ok = 0;
$reason .= qq/Net::SSLeay 1.49 must be installed for https support\n/;
}
# If an object, check that SSL config lets us get a CA if necessary
if ( ref($self) && ( $self->{verify_SSL} || $self->{SSL_options}{SSL_verify_mode} ) ) {
my $handle = HTTP::Tiny::Handle->new(
SSL_options => $self->{SSL_options},
verify_SSL => $self->{verify_SSL},
);
unless ( eval { $handle->_find_CA_file; 1 } ) {
$ok = 0;
$reason .= "$@";
}
}
wantarray ? ($ok, $reason) : $ok;
}
#pod =method connected
#pod
#pod $host = $http->connected;
#pod ($host, $port) = $http->connected;
#pod
#pod Indicates if a connection to a peer is being kept alive, per the C<keep_alive>
#pod option.
#pod
#pod In scalar context, returns the peer host and port, joined with a colon, or
#pod C<undef> (if no peer is connected).
#pod In list context, returns the peer host and port or an empty list (if no peer
#pod is connected).
#pod
#pod B<Note>: This method cannot reliably be used to discover whether the remote
#pod host has closed its end of the socket.
#pod
#pod =cut
sub connected {
my ($self) = @_;
# If a socket exists...
if ($self->{handle} && $self->{handle}{fh}) {
my $socket = $self->{handle}{fh};
# ...and is connected, return the peer host and port.
if ($socket->connected) {
return wantarray
? ($socket->peerhost, $socket->peerport)
: join(':', $socket->peerhost, $socket->peerport);
}
}
return;
}
#--------------------------------------------------------------------------#
# private methods
#--------------------------------------------------------------------------#
my %DefaultPort = (
http => 80,
https => 443,
);
sub _agent {
my $class = ref($_[0]) || $_[0];
(my $default_agent = $class) =~ s{::}{-}g;
return $default_agent . "/" . $class->VERSION;
}
sub _request {
my ($self, $method, $url, $args) = @_;
my ($scheme, $host, $port, $path_query, $auth) = $self->_split_url($url);
my $request = {
method => $method,
scheme => $scheme,
host => $host,
port => $port,
host_port => ($port == $DefaultPort{$scheme} ? $host : "$host:$port"),
uri => $path_query,
headers => {},
};
my $peer = $args->{peer} || $host;
# Allow 'peer' to be a coderef.
if ('CODE' eq ref $peer) {
$peer = $peer->($host);
}
# We remove the cached handle so it is not reused in the case of redirect.
# If all is well, it will be recached at the end of _request. We only
# reuse for the same scheme, host and port
my $handle = delete $self->{handle};
if ( $handle ) {
unless ( $handle->can_reuse( $scheme, $host, $port, $peer ) ) {
$handle->close;
undef $handle;
}
}
$handle ||= $self->_open_handle( $request, $scheme, $host, $port, $peer );
$self->_prepare_headers_and_cb($request, $args, $url, $auth);
$handle->write_request($request);
my $response;
do { $response = $handle->read_response_header }
until (substr($response->{status},0,1) ne '1');
$self->_update_cookie_jar( $url, $response ) if $self->{cookie_jar};
my @redir_args = $self->_maybe_redirect($request, $response, $args);
my $known_message_length;
if ($method eq 'HEAD' || $response->{status} =~ /^[23]04/) {
# response has no message body
$known_message_length = 1;
}
else {
# Ignore any data callbacks during redirection.
my $cb_args = @redir_args ? +{} : $args;
my $data_cb = $self->_prepare_data_cb($response, $cb_args);
$known_message_length = $handle->read_body($data_cb, $response);
}
if ( $self->{keep_alive}
&& $known_message_length
&& $response->{protocol} eq 'HTTP/1.1'
&& ($response->{headers}{connection} || '') ne 'close'
) {
$self->{handle} = $handle;
}
else {
$handle->close;
}
$response->{success} = substr( $response->{status}, 0, 1 ) eq '2';
$response->{url} = $url;
# Push the current response onto the stack of redirects if redirecting.
if (@redir_args) {
push @{$args->{_redirects}}, $response;
return $self->_request(@redir_args, $args);
}
# Copy the stack of redirects into the response before returning.
$response->{redirects} = delete $args->{_redirects}
if @{$args->{_redirects}};
return $response;
}
sub _open_handle {
my ($self, $request, $scheme, $host, $port, $peer) = @_;
my $handle = HTTP::Tiny::Handle->new(
timeout => $self->{timeout},
SSL_options => $self->{SSL_options},
verify_SSL => $self->{verify_SSL},
local_address => $self->{local_address},
keep_alive => $self->{keep_alive}
);
if ($self->{_has_proxy}{$scheme} && ! grep { $host =~ /\Q$_\E$/ } @{$self->{no_proxy}}) {
return $self->_proxy_connect( $request, $handle );
}
else {
return $handle->connect($scheme, $host, $port, $peer);
}
}
sub _proxy_connect {
my ($self, $request, $handle) = @_;
my @proxy_vars;
if ( $request->{scheme} eq 'https' ) {
_croak(qq{No https_proxy defined}) unless $self->{https_proxy};
@proxy_vars = $self->_split_proxy( https_proxy => $self->{https_proxy} );
if ( $proxy_vars[0] eq 'https' ) {
_croak(qq{Can't proxy https over https: $request->{uri} via $self->{https_proxy}});
}
}
else {
_croak(qq{No http_proxy defined}) unless $self->{http_proxy};
@proxy_vars = $self->_split_proxy( http_proxy => $self->{http_proxy} );
}
my ($p_scheme, $p_host, $p_port, $p_auth) = @proxy_vars;
if ( length $p_auth && ! defined $request->{headers}{'proxy-authorization'} ) {
$self->_add_basic_auth_header( $request, 'proxy-authorization' => $p_auth );
}
$handle->connect($p_scheme, $p_host, $p_port, $p_host);
if ($request->{scheme} eq 'https') {
$self->_create_proxy_tunnel( $request, $handle );
}
else {
# non-tunneled proxy requires absolute URI
$request->{uri} = "$request->{scheme}://$request->{host_port}$request->{uri}";
}
return $handle;
}
sub _split_proxy {
my ($self, $type, $proxy) = @_;
my ($scheme, $host, $port, $path_query, $auth) = eval { $self->_split_url($proxy) };
unless(
defined($scheme) && length($scheme) && length($host) && length($port)
&& $path_query eq '/'
) {
_croak(qq{$type URL must be in format http[s]://[auth@]<host>:<port>/\n});
}
return ($scheme, $host, $port, $auth);
}
sub _create_proxy_tunnel {
my ($self, $request, $handle) = @_;
$handle->_assert_ssl;
my $agent = exists($request->{headers}{'user-agent'})
? $request->{headers}{'user-agent'} : $self->{agent};
my $connect_request = {
method => 'CONNECT',
uri => "$request->{host}:$request->{port}",
headers => {
host => "$request->{host}:$request->{port}",
'user-agent' => $agent,
}
};
if ( $request->{headers}{'proxy-authorization'} ) {
$connect_request->{headers}{'proxy-authorization'} =
delete $request->{headers}{'proxy-authorization'};
}
$handle->write_request($connect_request);
my $response;
do { $response = $handle->read_response_header }
until (substr($response->{status},0,1) ne '1');
# if CONNECT failed, throw the response so it will be
# returned from the original request() method;
unless (substr($response->{status},0,1) eq '2') {
die $response;
}
# tunnel established, so start SSL handshake
$handle->start_ssl( $request->{host} );
return;
}
sub _prepare_headers_and_cb {
my ($self, $request, $args, $url, $auth) = @_;
for ($self->{default_headers}, $args->{headers}) {
next unless defined;
while (my ($k, $v) = each %$_) {
$request->{headers}{lc $k} = $v;
$request->{header_case}{lc $k} = $k;
}
}
if (exists $request->{headers}{'host'}) {
die(qq/The 'Host' header must not be provided as header option\n/);
}
$request->{headers}{'host'} = $request->{host_port};
$request->{headers}{'user-agent'} ||= $self->{agent};
$request->{headers}{'connection'} = "close"
unless $self->{keep_alive};
if ( defined $args->{content} ) {
if (ref $args->{content} eq 'CODE') {
$request->{headers}{'content-type'} ||= "application/octet-stream";
$request->{headers}{'transfer-encoding'} = 'chunked'
unless $request->{headers}{'content-length'}
|| $request->{headers}{'transfer-encoding'};
$request->{cb} = $args->{content};
}
elsif ( length $args->{content} ) {
my $content = $args->{content};
if ( $] ge '5.008' ) {
utf8::downgrade($content, 1)
or die(qq/Wide character in request message body\n/);
}
$request->{headers}{'content-type'} ||= "application/octet-stream";
$request->{headers}{'content-length'} = length $content
unless $request->{headers}{'content-length'}
|| $request->{headers}{'transfer-encoding'};
$request->{cb} = sub { substr $content, 0, length $content, '' };
}
$request->{trailer_cb} = $args->{trailer_callback}
if ref $args->{trailer_callback} eq 'CODE';
}
### If we have a cookie jar, then maybe add relevant cookies
if ( $self->{cookie_jar} ) {
my $cookies = $self->cookie_jar->cookie_header( $url );
$request->{headers}{cookie} = $cookies if length $cookies;
}
# if we have Basic auth parameters, add them
if ( length $auth && ! defined $request->{headers}{authorization} ) {
$self->_add_basic_auth_header( $request, 'authorization' => $auth );
}
return;
}
sub _add_basic_auth_header {
my ($self, $request, $header, $auth) = @_;
require MIME::Base64;
$request->{headers}{$header} =
"Basic " . MIME::Base64::encode_base64($auth, "");
return;
}
sub _prepare_data_cb {
my ($self, $response, $args) = @_;
my $data_cb = $args->{data_callback};
$response->{content} = '';
if (!$data_cb || $response->{status} !~ /^2/) {
if (defined $self->{max_size}) {
$data_cb = sub {
$_[1]->{content} .= $_[0];
die(qq/Size of response body exceeds the maximum allowed of $self->{max_size}\n/)
if length $_[1]->{content} > $self->{max_size};
};
}
else {
$data_cb = sub { $_[1]->{content} .= $_[0] };
}
}
return $data_cb;
}
sub _update_cookie_jar {
my ($self, $url, $response) = @_;
my $cookies = $response->{headers}->{'set-cookie'};
return unless defined $cookies;
my @cookies = ref $cookies ? @$cookies : $cookies;
$self->cookie_jar->add( $url, $_ ) for @cookies;
return;
}
sub _validate_cookie_jar {
my ($class, $jar) = @_;
# duck typing
for my $method ( qw/add cookie_header/ ) {
_croak(qq/Cookie jar must provide the '$method' method\n/)
unless ref($jar) && ref($jar)->can($method);
}
return;
}
sub _maybe_redirect {
my ($self, $request, $response, $args) = @_;
my $headers = $response->{headers};
my ($status, $method) = ($response->{status}, $request->{method});
$args->{_redirects} ||= [];
if (($status eq '303' or ($status =~ /^30[1278]/ && $method =~ /^GET|HEAD$/))
and $headers->{location}
and @{$args->{_redirects}} < $self->{max_redirect}
) {
my $location = ($headers->{location} =~ /^\//)
? "$request->{scheme}://$request->{host_port}$headers->{location}"
: $headers->{location} ;
return (($status eq '303' ? 'GET' : $method), $location);
}
return;
}
sub _split_url {
my $url = pop;
# URI regex adapted from the URI module
my ($scheme, $host, $path_query) = $url =~ m<\A([^:/?#]+)://([^/?#]*)([^#]*)>
or die(qq/Cannot parse URL: '$url'\n/);
$scheme = lc $scheme;
$path_query = "/$path_query" unless $path_query =~ m<\A/>;
my $auth = '';
if ( (my $i = index $host, '@') != -1 ) {
# user:pass@host
$auth = substr $host, 0, $i, ''; # take up to the @ for auth
substr $host, 0, 1, ''; # knock the @ off the host
# userinfo might be percent escaped, so recover real auth info
$auth =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
}
my $port = $host =~ s/:(\d*)\z// && length $1 ? $1
: $scheme eq 'http' ? 80
: $scheme eq 'https' ? 443
: undef;
return ($scheme, (length $host ? lc $host : "localhost") , $port, $path_query, $auth);
}
# Date conversions adapted from HTTP::Date
my $DoW = "Sun|Mon|Tue|Wed|Thu|Fri|Sat";
my $MoY = "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec";
sub _http_date {
my ($sec, $min, $hour, $mday, $mon, $year, $wday) = gmtime($_[1]);
return sprintf("%s, %02d %s %04d %02d:%02d:%02d GMT",
substr($DoW,$wday*4,3),
$mday, substr($MoY,$mon*4,3), $year+1900,
$hour, $min, $sec
);
}
sub _parse_http_date {
my ($self, $str) = @_;
require Time::Local;
my @tl_parts;
if ($str =~ /^[SMTWF][a-z]+, +(\d{1,2}) ($MoY) +(\d\d\d\d) +(\d\d):(\d\d):(\d\d) +GMT$/) {
@tl_parts = ($6, $5, $4, $1, (index($MoY,$2)/4), $3);
}
elsif ($str =~ /^[SMTWF][a-z]+, +(\d\d)-($MoY)-(\d{2,4}) +(\d\d):(\d\d):(\d\d) +GMT$/ ) {
@tl_parts = ($6, $5, $4, $1, (index($MoY,$2)/4), $3);
}
elsif ($str =~ /^[SMTWF][a-z]+ +($MoY) +(\d{1,2}) +(\d\d):(\d\d):(\d\d) +(?:[^0-9]+ +)?(\d\d\d\d)$/ ) {
@tl_parts = ($5, $4, $3, $2, (index($MoY,$1)/4), $6);
}
return eval {
my $t = @tl_parts ? Time::Local::timegm(@tl_parts) : -1;
$t < 0 ? undef : $t;
};
}
# URI escaping adapted from URI::Escape
# c.f. http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
# perl 5.6 ready UTF-8 encoding adapted from JSON::PP
my %escapes = map { chr($_) => sprintf("%%%02X", $_) } 0..255;
$escapes{' '}="+";
my $unsafe_char = qr/[^A-Za-z0-9\-\._~]/;
sub _uri_escape {
my ($self, $str) = @_;
if ( $] ge '5.008' ) {
utf8::encode($str);
}
else {
$str = pack("U*", unpack("C*", $str)) # UTF-8 encode a byte string
if ( length $str == do { use bytes; length $str } );
$str = pack("C*", unpack("C*", $str)); # clear UTF-8 flag
}
$str =~ s/($unsafe_char)/$escapes{$1}/g;
return $str;
}
package
HTTP::Tiny::Handle; # hide from PAUSE/indexers
use strict;
use warnings;
use Errno qw[EINTR EPIPE];
use IO::Socket qw[SOCK_STREAM];
use Socket qw[SOL_SOCKET SO_KEEPALIVE];
# PERL_HTTP_TINY_IPV4_ONLY is a private environment variable to force old
# behavior if someone is unable to boostrap CPAN from a new perl install; it is
# not intended for general, per-client use and may be removed in the future
my $SOCKET_CLASS =
$ENV{PERL_HTTP_TINY_IPV4_ONLY} ? 'IO::Socket::INET' :
eval { require IO::Socket::IP; IO::Socket::IP->VERSION(0.25) } ? 'IO::Socket::IP' :
'IO::Socket::INET';
sub BUFSIZE () { 32768 } ## no critic
my $Printable = sub {
local $_ = shift;
s/\r/\\r/g;
s/\n/\\n/g;
s/\t/\\t/g;
s/([^\x20-\x7E])/sprintf('\\x%.2X', ord($1))/ge;
$_;
};
my $Token = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]/;
my $Field_Content = qr/[[:print:]]+ (?: [\x20\x09]+ [[:print:]]+ )*/x;
sub new {
my ($class, %args) = @_;
return bless {
rbuf => '',
timeout => 60,
max_line_size => 16384,
max_header_lines => 64,
verify_SSL => 0,
SSL_options => {},
%args
}, $class;
}
sub timeout {
my ($self, $timeout) = @_;
if ( @_ > 1 ) {
$self->{timeout} = $timeout;
if ( $self->{fh} && $self->{fh}->can('timeout') ) {
$self->{fh}->timeout($timeout);
}
}
return $self->{timeout};
}
sub connect {
@_ == 5 || die(q/Usage: $handle->connect(scheme, host, port, peer)/ . "\n");
my ($self, $scheme, $host, $port, $peer) = @_;
if ( $scheme eq 'https' ) {
$self->_assert_ssl;
}
elsif ( $scheme ne 'http' ) {
die(qq/Unsupported URL scheme '$scheme'\n/);
}
$self->{fh} = $SOCKET_CLASS->new(
PeerHost => $peer,
PeerPort => $port,
$self->{local_address} ?
( LocalAddr => $self->{local_address} ) : (),
Proto => 'tcp',
Type => SOCK_STREAM,
Timeout => $self->{timeout},
) or die(qq/Could not connect to '$host:$port': $@\n/);
binmode($self->{fh})
or die(qq/Could not binmode() socket: '$!'\n/);
if ( $self->{keep_alive} ) {
unless ( defined( $self->{fh}->setsockopt( SOL_SOCKET, SO_KEEPALIVE, 1 ) ) ) {
CORE::close($self->{fh});
die(qq/Could not set SO_KEEPALIVE on socket: '$!'\n/);
}
}
$self->start_ssl($host) if $scheme eq 'https';
$self->{scheme} = $scheme;
$self->{host} = $host;
$self->{peer} = $peer;
$self->{port} = $port;
$self->{pid} = $$;
$self->{tid} = _get_tid();
return $self;
}
sub start_ssl {
my ($self, $host) = @_;
# As this might be used via CONNECT after an SSL session
# to a proxy, we shut down any existing SSL before attempting
# the handshake
if ( ref($self->{fh}) eq 'IO::Socket::SSL' ) {
unless ( $self->{fh}->stop_SSL ) {
my $ssl_err = IO::Socket::SSL->errstr;
die(qq/Error halting prior SSL connection: $ssl_err/);
}
}
my $ssl_args = $self->_ssl_args($host);
IO::Socket::SSL->start_SSL(
$self->{fh},
%$ssl_args,
SSL_create_ctx_callback => sub {
my $ctx = shift;
Net::SSLeay::CTX_set_mode($ctx, Net::SSLeay::MODE_AUTO_RETRY());
},
);
unless ( ref($self->{fh}) eq 'IO::Socket::SSL' ) {
my $ssl_err = IO::Socket::SSL->errstr;
die(qq/SSL connection failed for $host: $ssl_err\n/);
}
}
sub close {
@_ == 1 || die(q/Usage: $handle->close()/ . "\n");
my ($self) = @_;
CORE::close($self->{fh})
or die(qq/Could not close socket: '$!'\n/);
}
sub write {
@_ == 2 || die(q/Usage: $handle->write(buf)/ . "\n");
my ($self, $buf) = @_;
if ( $] ge '5.008' ) {
utf8::downgrade($buf, 1)
or die(qq/Wide character in write()\n/);
}
my $len = length $buf;
my $off = 0;
local $SIG{PIPE} = 'IGNORE';
while () {
$self->can_write
or die(qq/Timed out while waiting for socket to become ready for writing\n/);
my $r = syswrite($self->{fh}, $buf, $len, $off);
if (defined $r) {
$len -= $r;
$off += $r;
last unless $len > 0;
}
elsif ($! == EPIPE) {
die(qq/Socket closed by remote server: $!\n/);
}
elsif ($! != EINTR) {
if ($self->{fh}->can('errstr')){
my $err = $self->{fh}->errstr();
die (qq/Could not write to SSL socket: '$err'\n /);
}
else {
die(qq/Could not write to socket: '$!'\n/);
}
}
}
return $off;
}
sub read {
@_ == 2 || @_ == 3 || die(q/Usage: $handle->read(len [, allow_partial])/ . "\n");
my ($self, $len, $allow_partial) = @_;
my $buf = '';
my $got = length $self->{rbuf};
if ($got) {
my $take = ($got < $len) ? $got : $len;
$buf = substr($self->{rbuf}, 0, $take, '');
$len -= $take;
}
while ($len > 0) {
$self->can_read
or die(q/Timed out while waiting for socket to become ready for reading/ . "\n");
my $r = sysread($self->{fh}, $buf, $len, length $buf);
if (defined $r) {
last unless $r;
$len -= $r;
}
elsif ($! != EINTR) {
if ($self->{fh}->can('errstr')){
my $err = $self->{fh}->errstr();
die (qq/Could not read from SSL socket: '$err'\n /);
}
else {
die(qq/Could not read from socket: '$!'\n/);
}
}
}
if ($len && !$allow_partial) {
die(qq/Unexpected end of stream\n/);
}
return $buf;
}
sub readline {
@_ == 1 || die(q/Usage: $handle->readline()/ . "\n");
my ($self) = @_;
while () {
if ($self->{rbuf} =~ s/\A ([^\x0D\x0A]* \x0D?\x0A)//x) {
return $1;
}
if (length $self->{rbuf} >= $self->{max_line_size}) {
die(qq/Line size exceeds the maximum allowed size of $self->{max_line_size}\n/);
}
$self->can_read
or die(qq/Timed out while waiting for socket to become ready for reading\n/);
my $r = sysread($self->{fh}, $self->{rbuf}, BUFSIZE, length $self->{rbuf});
if (defined $r) {
last unless $r;
}
elsif ($! != EINTR) {
if ($self->{fh}->can('errstr')){
my $err = $self->{fh}->errstr();
die (qq/Could not read from SSL socket: '$err'\n /);
}
else {
die(qq/Could not read from socket: '$!'\n/);
}
}
}
die(qq/Unexpected end of stream while looking for line\n/);
}
sub read_header_lines {
@_ == 1 || @_ == 2 || die(q/Usage: $handle->read_header_lines([headers])/ . "\n");
my ($self, $headers) = @_;
$headers ||= {};
my $lines = 0;
my $val;
while () {
my $line = $self->readline;
if (++$lines >= $self->{max_header_lines}) {
die(qq/Header lines exceeds maximum number allowed of $self->{max_header_lines}\n/);
}
elsif ($line =~ /\A ([^\x00-\x1F\x7F:]+) : [\x09\x20]* ([^\x0D\x0A]*)/x) {
my ($field_name) = lc $1;
if (exists $headers->{$field_name}) {
for ($headers->{$field_name}) {
$_ = [$_] unless ref $_ eq "ARRAY";
push @$_, $2;
$val = \$_->[-1];
}
}
else {
$val = \($headers->{$field_name} = $2);
}
}
elsif ($line =~ /\A [\x09\x20]+ ([^\x0D\x0A]*)/x) {
$val
or die(qq/Unexpected header continuation line\n/);
next unless length $1;
$$val .= ' ' if length $$val;
$$val .= $1;
}
elsif ($line =~ /\A \x0D?\x0A \z/x) {
last;
}
else {
die(q/Malformed header line: / . $Printable->($line) . "\n");
}
}
return $headers;
}
sub write_request {
@_ == 2 || die(q/Usage: $handle->write_request(request)/ . "\n");
my($self, $request) = @_;
$self->write_request_header(@{$request}{qw/method uri headers header_case/});
$self->write_body($request) if $request->{cb};
return;
}
# Standard request header names/case from HTTP/1.1 RFCs
my @rfc_request_headers = qw(
Accept Accept-Charset Accept-Encoding Accept-Language Authorization
Cache-Control Connection Content-Length Expect From Host
If-Match If-Modified-Since If-None-Match If-Range If-Unmodified-Since
Max-Forwards Pragma Proxy-Authorization Range Referer TE Trailer
Transfer-Encoding Upgrade User-Agent Via
);
my @other_request_headers = qw(
Content-Encoding Content-MD5 Content-Type Cookie DNT Date Origin
X-XSS-Protection
);
my %HeaderCase = map { lc($_) => $_ } @rfc_request_headers, @other_request_headers;
# to avoid multiple small writes and hence nagle, you can pass the method line or anything else to
# combine writes.
sub write_header_lines {
(@_ >= 2 && @_ <= 4 && ref $_[1] eq 'HASH') || die(q/Usage: $handle->write_header_lines(headers, [header_case, prefix])/ . "\n");
my($self, $headers, $header_case, $prefix_data) = @_;
$header_case ||= {};
my $buf = (defined $prefix_data ? $prefix_data : '');
# Per RFC, control fields should be listed first
my %seen;
for my $k ( qw/host cache-control expect max-forwards pragma range te/ ) {
next unless exists $headers->{$k};
$seen{$k}++;
my $field_name = $HeaderCase{$k};
my $v = $headers->{$k};
for (ref $v eq 'ARRAY' ? @$v : $v) {
$_ = '' unless defined $_;
$buf .= "$field_name: $_\x0D\x0A";
}
}
# Other headers sent in arbitrary order
while (my ($k, $v) = each %$headers) {
my $field_name = lc $k;
next if $seen{$field_name};
if (exists $HeaderCase{$field_name}) {
$field_name = $HeaderCase{$field_name};
}
else {
if (exists $header_case->{$field_name}) {
$field_name = $header_case->{$field_name};
}
else {
$field_name =~ s/\b(\w)/\u$1/g;
}
$field_name =~ /\A $Token+ \z/xo
or die(q/Invalid HTTP header field name: / . $Printable->($field_name) . "\n");
$HeaderCase{lc $field_name} = $field_name;
}
for (ref $v eq 'ARRAY' ? @$v : $v) {
# unwrap a field value if pre-wrapped by user
s/\x0D?\x0A\s+/ /g;
die(qq/Invalid HTTP header field value ($field_name): / . $Printable->($_). "\n")
unless $_ eq '' || /\A $Field_Content \z/xo;
$_ = '' unless defined $_;
$buf .= "$field_name: $_\x0D\x0A";
}
}
$buf .= "\x0D\x0A";
return $self->write($buf);
}
# return value indicates whether message length was defined; this is generally
# true unless there was no content-length header and we just read until EOF.
# Other message length errors are thrown as exceptions
sub read_body {
@_ == 3 || die(q/Usage: $handle->read_body(callback, response)/ . "\n");
my ($self, $cb, $response) = @_;
my $te = $response->{headers}{'transfer-encoding'} || '';
my $chunked = grep { /chunked/i } ( ref $te eq 'ARRAY' ? @$te : $te ) ;
return $chunked
? $self->read_chunked_body($cb, $response)
: $self->read_content_body($cb, $response);
}
sub write_body {
@_ == 2 || die(q/Usage: $handle->write_body(request)/ . "\n");
my ($self, $request) = @_;
if ($request->{headers}{'content-length'}) {
return $self->write_content_body($request);
}
else {
return $self->write_chunked_body($request);
}
}
sub read_content_body {
@_ == 3 || @_ == 4 || die(q/Usage: $handle->read_content_body(callback, response, [read_length])/ . "\n");
my ($self, $cb, $response, $content_length) = @_;
$content_length ||= $response->{headers}{'content-length'};
if ( defined $content_length ) {
my $len = $content_length;
while ($len > 0) {
my $read = ($len > BUFSIZE) ? BUFSIZE : $len;
$cb->($self->read($read, 0), $response);
$len -= $read;
}
return length($self->{rbuf}) == 0;
}
my $chunk;
$cb->($chunk, $response) while length( $chunk = $self->read(BUFSIZE, 1) );
return;
}
sub write_content_body {
@_ == 2 || die(q/Usage: $handle->write_content_body(request)/ . "\n");
my ($self, $request) = @_;
my ($len, $content_length) = (0, $request->{headers}{'content-length'});
while () {
my $data = $request->{cb}->();
defined $data && length $data
or last;
if ( $] ge '5.008' ) {
utf8::downgrade($data, 1)
or die(qq/Wide character in write_content()\n/);
}
$len += $self->write($data);
}
$len == $content_length
or die(qq/Content-Length mismatch (got: $len expected: $content_length)\n/);
return $len;
}
sub read_chunked_body {
@_ == 3 || die(q/Usage: $handle->read_chunked_body(callback, $response)/ . "\n");
my ($self, $cb, $response) = @_;
while () {
my $head = $self->readline;
$head =~ /\A ([A-Fa-f0-9]+)/x
or die(q/Malformed chunk head: / . $Printable->($head) . "\n");
my $len = hex($1)
or last;
$self->read_content_body($cb, $response, $len);
$self->read(2) eq "\x0D\x0A"
or die(qq/Malformed chunk: missing CRLF after chunk data\n/);
}
$self->read_header_lines($response->{headers});
return 1;
}
sub write_chunked_body {
@_ == 2 || die(q/Usage: $handle->write_chunked_body(request)/ . "\n");
my ($self, $request) = @_;
my $len = 0;
while () {
my $data = $request->{cb}->();
defined $data && length $data
or last;
if ( $] ge '5.008' ) {
utf8::downgrade($data, 1)
or die(qq/Wide character in write_chunked_body()\n/);
}
$len += length $data;
my $chunk = sprintf '%X', length $data;
$chunk .= "\x0D\x0A";
$chunk .= $data;
$chunk .= "\x0D\x0A";
$self->write($chunk);
}
$self->write("0\x0D\x0A");
if ( ref $request->{trailer_cb} eq 'CODE' ) {
$self->write_header_lines($request->{trailer_cb}->())
}
else {
$self->write("\x0D\x0A");
}
return $len;
}
sub read_response_header {
@_ == 1 || die(q/Usage: $handle->read_response_header()/ . "\n");
my ($self) = @_;
my $line = $self->readline;
$line =~ /\A (HTTP\/(0*\d+\.0*\d+)) [\x09\x20]+ ([0-9]{3}) [\x09\x20]+ ([^\x0D\x0A]*) \x0D?\x0A/x
or die(q/Malformed Status-Line: / . $Printable->($line). "\n");
my ($protocol, $version, $status, $reason) = ($1, $2, $3, $4);
die (qq/Unsupported HTTP protocol: $protocol\n/)
unless $version =~ /0*1\.0*[01]/;
return {
status => $status,
reason => $reason,
headers => $self->read_header_lines,
protocol => $protocol,
};
}
sub write_request_header {
@_ == 5 || die(q/Usage: $handle->write_request_header(method, request_uri, headers, header_case)/ . "\n");
my ($self, $method, $request_uri, $headers, $header_case) = @_;
return $self->write_header_lines($headers, $header_case, "$method $request_uri HTTP/1.1\x0D\x0A");
}
sub _do_timeout {
my ($self, $type, $timeout) = @_;
$timeout = $self->{timeout}
unless defined $timeout && $timeout >= 0;
my $fd = fileno $self->{fh};
defined $fd && $fd >= 0
or die(qq/select(2): 'Bad file descriptor'\n/);
my $initial = time;
my $pending = $timeout;
my $nfound;
vec(my $fdset = '', $fd, 1) = 1;
while () {
$nfound = ($type eq 'read')
? select($fdset, undef, undef, $pending)
: select(undef, $fdset, undef, $pending) ;
if ($nfound == -1) {
$! == EINTR
or die(qq/select(2): '$!'\n/);
redo if !$timeout || ($pending = $timeout - (time - $initial)) > 0;
$nfound = 0;
}
last;
}
$! = 0;
return $nfound;
}
sub can_read {
@_ == 1 || @_ == 2 || die(q/Usage: $handle->can_read([timeout])/ . "\n");
my $self = shift;
if ( ref($self->{fh}) eq 'IO::Socket::SSL' ) {
return 1 if $self->{fh}->pending;
}
return $self->_do_timeout('read', @_)
}
sub can_write {
@_ == 1 || @_ == 2 || die(q/Usage: $handle->can_write([timeout])/ . "\n");
my $self = shift;
return $self->_do_timeout('write', @_)
}
sub _assert_ssl {
my($ok, $reason) = HTTP::Tiny->can_ssl();
die $reason unless $ok;
}
sub can_reuse {
my ($self,$scheme,$host,$port,$peer) = @_;
return 0 if
$self->{pid} != $$
|| $self->{tid} != _get_tid()
|| length($self->{rbuf})
|| $scheme ne $self->{scheme}
|| $host ne $self->{host}
|| $port ne $self->{port}
|| $peer ne $self->{peer}
|| eval { $self->can_read(0) }
|| $@ ;
return 1;
}
# Try to find a CA bundle to validate the SSL cert,
# prefer Mozilla::CA or fallback to a system file
sub _find_CA_file {
my $self = shift();
my $ca_file =
defined( $self->{SSL_options}->{SSL_ca_file} )
? $self->{SSL_options}->{SSL_ca_file}
: $ENV{SSL_CERT_FILE};
if ( defined $ca_file ) {
unless ( -r $ca_file ) {
die qq/SSL_ca_file '$ca_file' not found or not readable\n/;
}
return $ca_file;
}
local @INC = @INC;
pop @INC if $INC[-1] eq '.';
return Mozilla::CA::SSL_ca_file()
if eval { require Mozilla::CA; 1 };
# cert list copied from golang src/crypto/x509/root_unix.go
foreach my $ca_bundle (
"/etc/ssl/certs/ca-certificates.crt", # Debian/Ubuntu/Gentoo etc.
"/etc/pki/tls/certs/ca-bundle.crt", # Fedora/RHEL
"/etc/ssl/ca-bundle.pem", # OpenSUSE
"/etc/openssl/certs/ca-certificates.crt", # NetBSD
"/etc/ssl/cert.pem", # OpenBSD
"/usr/local/share/certs/ca-root-nss.crt", # FreeBSD/DragonFly
"/etc/pki/tls/cacert.pem", # OpenELEC
"/etc/certs/ca-certificates.crt", # Solaris 11.2+
) {
return $ca_bundle if -e $ca_bundle;
}
die qq/Couldn't find a CA bundle with which to verify the SSL certificate.\n/
. qq/Try installing Mozilla::CA from CPAN\n/;
}
# for thread safety, we need to know thread id if threads are loaded
sub _get_tid {
no warnings 'reserved'; # for 'threads'
return threads->can("tid") ? threads->tid : 0;
}
sub _ssl_args {
my ($self, $host) = @_;
my %ssl_args;
# This test reimplements IO::Socket::SSL::can_client_sni(), which wasn't
# added until IO::Socket::SSL 1.84
if ( Net::SSLeay::OPENSSL_VERSION_NUMBER() >= 0x01000000 ) {
$ssl_args{SSL_hostname} = $host, # Sane SNI support
}
if ($self->{verify_SSL}) {
$ssl_args{SSL_verifycn_scheme} = 'http'; # enable CN validation
$ssl_args{SSL_verifycn_name} = $host; # set validation hostname
$ssl_args{SSL_verify_mode} = 0x01; # enable cert validation
$ssl_args{SSL_ca_file} = $self->_find_CA_file;
}
else {
$ssl_args{SSL_verifycn_scheme} = 'none'; # disable CN validation
$ssl_args{SSL_verify_mode} = 0x00; # disable cert validation
}
# user options override settings from verify_SSL
for my $k ( keys %{$self->{SSL_options}} ) {
$ssl_args{$k} = $self->{SSL_options}{$k} if $k =~ m/^SSL_/;
}
return \%ssl_args;
}
1;
} # --- END HTTP::Tiny
{ # --- BEGIN Try::Tiny
package Try::Tiny; # git description: v0.29-2-g3b23a06
use 5.006;
# ABSTRACT: Minimal try/catch with proper preservation of $@
our $VERSION = '0.30';
use strict;
use warnings;
BEGIN {
use Exporter 5.57 'import';
our @EXPORT = our @EXPORT_OK = qw(try catch finally);
if ($INC{'Sub/Util.pm'} && defined &Sub::Util::set_subname ) {
*_subname = \&Sub::Util::set_subname;
*_HAS_SUBNAME = sub {1};
}
elsif( $INC{'Sub/Name.pm'} && eval { Sub::Name->VERSION(0.08) } ){
*_subname = \&Sub::Name::subname;
*_HAS_SUBNAME = sub {1};
}
else {
*_HAS_SUBNAME = sub {0};
}
}
my %_finally_guards;
# Need to prototype as @ not $$ because of the way Perl evaluates the prototype.
# Keeping it at $$ means you only ever get 1 sub because we need to eval in a list
# context & not a scalar one
sub try (&;@) {
my ( $try, @code_refs ) = @_;
# we need to save this here, the eval block will be in scalar context due
# to $failed
my $wantarray = wantarray;
# work around perl bug by explicitly initializing these, due to the likelyhood
# this will be used in global destruction (perl rt#119311)
my ( $catch, @finally ) = ();
# find labeled blocks in the argument list.
# catch and finally tag the blocks by blessing a scalar reference to them.
foreach my $code_ref (@code_refs) {
if ( ref($code_ref) eq 'Try::Tiny::Catch' ) {
_croak('A try() may not be followed by multiple catch() blocks')
if $catch;
$catch = ${$code_ref};
} elsif ( ref($code_ref) eq 'Try::Tiny::Finally' ) {
push @finally, ${$code_ref};
} else {
_croak(
'try() encountered an unexpected argument ('
. ( defined $code_ref ? $code_ref : 'undef' )
. ') - perhaps a missing semi-colon before or'
);
}
}
# FIXME consider using local $SIG{__DIE__} to accumulate all errors. It's
# not perfect, but we could provide a list of additional errors for
# $catch->();
# name the blocks if we have Sub::Name installed
_subname(caller().'::try {...} ' => $try)
if _HAS_SUBNAME;
# set up scope guards to invoke the finally blocks at the end.
# this should really be a function scope lexical variable instead of
# file scope + local but that causes issues with perls < 5.20 due to
# perl rt#119311
local $_finally_guards{guards} = [
map { Try::Tiny::ScopeGuard->_new($_) }
@finally
];
# save the value of $@ so we can set $@ back to it in the beginning of the eval
# and restore $@ after the eval finishes
my $prev_error = $@;
my ( @ret, $error );
# failed will be true if the eval dies, because 1 will not be returned
# from the eval body
my $failed = not eval {
$@ = $prev_error;
# evaluate the try block in the correct context
if ( $wantarray ) {
@ret = $try->();
} elsif ( defined $wantarray ) {
$ret[0] = $try->();
} else {
$try->();
};
return 1; # properly set $failed to false
};
# preserve the current error and reset the original value of $@
$error = $@;
$@ = $prev_error;
# at this point $failed contains a true value if the eval died, even if some
# destructor overwrote $@ as the eval was unwinding.
if ( $failed ) {
# pass $error to the finally blocks
push @$_, $error for @{$_finally_guards{guards}};
# if we got an error, invoke the catch block.
if ( $catch ) {
# This works like given($error), but is backwards compatible and
# sets $_ in the dynamic scope for the body of C<$catch>
for ($error) {
return $catch->($error);
}
# in case when() was used without an explicit return, the C<for>
# loop will be aborted and there's no useful return value
}
return;
} else {
# no failure, $@ is back to what it was, everything is fine
return $wantarray ? @ret : $ret[0];
}
}
sub catch (&;@) {
my ( $block, @rest ) = @_;
_croak('Useless bare catch()') unless wantarray;
_subname(caller().'::catch {...} ' => $block)
if _HAS_SUBNAME;
return (
bless(\$block, 'Try::Tiny::Catch'),
@rest,
);
}
sub finally (&;@) {
my ( $block, @rest ) = @_;
_croak('Useless bare finally()') unless wantarray;
_subname(caller().'::finally {...} ' => $block)
if _HAS_SUBNAME;
return (
bless(\$block, 'Try::Tiny::Finally'),
@rest,
);
}
sub _croak {
my $err;
if (!$INC{'Carp.pm'}) {
local $@;
eval { require Carp; };
$err = $@;
}
die @_ if $err;
{
$Carp::Internal{+__PACKAGE__}++;
}
return Carp::croak(@_);
}
{
package # hide from PAUSE
Try::Tiny::ScopeGuard;
use constant UNSTABLE_DOLLARAT => ("$]" < '5.013002') ? 1 : 0;
sub _new {
shift;
bless [ @_ ];
}
sub DESTROY {
my ($code, @args) = @{ $_[0] };
local $@ if UNSTABLE_DOLLARAT;
eval {
$code->(@args);
1;
} or do {
warn
"Execution of finally() block $code resulted in an exception, which "
. '*CAN NOT BE PROPAGATED* due to fundamental limitations of Perl. '
. 'Your program will continue as if this event never took place. '
. "Original exception text follows:\n\n"
. (defined $@ ? $@ : '$@ left undefined...')
. "\n"
;
}
}
}
1;
} # --- END Try::Tiny
{ # --- BEGIN Cpanel/Time/Local.pm
package Cpanel::Time::Local;
use strict;
our $server_offset_string;
our ( $timecacheref, $localtimecacheref ) = ( [ -1, '', -1 ], [ -1, '', -1 ] );
my $server_offset;
my $localtime_link_or_mtime;
our $ETC_LOCALTIME = q{/etc/localtime};
sub _clear_caches {
undef $_
for (
$server_offset,
$server_offset_string,
$timecacheref,
$localtimecacheref,
$localtime_link_or_mtime,
);
return;
}
sub localtime2timestamp {
my ( $time, $delimiter ) = @_;
$delimiter ||= ' ';
$time ||= time();
return $localtimecacheref->[2] if $localtimecacheref->[0] == $time && $localtimecacheref->[1] eq $delimiter;
my $tz_offset = get_server_offset_as_offset_string($time);
my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime $time;
@{$localtimecacheref}[ 0, 1 ] = ( $time, $delimiter );
return ( $localtimecacheref->[2] = sprintf( '%04d-%02d-%02d' . $delimiter . '%02d:%02d:%02d %s', $year + 1900, $mon + 1, $mday, $hour, $min, $sec, $tz_offset ) );
}
sub get_server_offset_as_offset_string {
my ($time_supplied) = @_;
if ( !$time_supplied ) {
my $link_or_mtime;
if ( -l $ETC_LOCALTIME ) {
$link_or_mtime = readlink($ETC_LOCALTIME);
}
else {
$link_or_mtime = ( stat($ETC_LOCALTIME) )[9];
}
if ( defined $link_or_mtime ) {
$localtime_link_or_mtime ||= $link_or_mtime;
if ( $localtime_link_or_mtime ne $link_or_mtime ) {
_clear_caches();
$localtime_link_or_mtime = $link_or_mtime;
}
}
}
if ( $time_supplied || !defined $server_offset_string ) {
UNTIL_SAME_SECOND: {
my $starttime = time();
my $time = $time_supplied || $starttime;
my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday ) = localtime $time;
my ( $gmmin, $gmhour, $gmyear, $gmyday ) = ( gmtime($time) )[ 1, 2, 5, 7 ];
redo UNTIL_SAME_SECOND if time != $starttime;
my $yday_offset;
if ( $year == $gmyear ) {
$yday_offset = ( $yday <=> $gmyday );
}
elsif ( $year < $gmyear ) {
$yday_offset = -1;
}
elsif ( $year > $gmyear ) {
$yday_offset = 1;
}
my $gmoffset = ( $hour * 60 + $min ) - ( $gmhour * 60 + $gmmin ) + 1440 * $yday_offset;
my $offset_string = sprintf( '%+03d%02d', int( $gmoffset / 60 ), $gmoffset % 60 );
if ($time_supplied) {
return $offset_string;
}
else {
$server_offset_string = $offset_string;
}
}
}
return $server_offset_string;
}
sub get_server_offset_in_seconds {
if ( !defined $server_offset ) {
if ( get_server_offset_as_offset_string() =~ m/([-+]?[0-9]{2})([0-9]{2})/ ) {
my ( $hours, $minutes ) = ( $1, $2 );
my $seconds = ( ( abs($hours) * 60 * 60 ) + ( $minutes * 60 ) );
$server_offset = $hours < 0 ? "-$seconds" : $seconds;
}
else {
$server_offset = 0;
}
}
return $server_offset;
}
1;
} # --- END Cpanel/Time/Local.pm
{ # --- BEGIN Cpanel/Fcntl/Constants.pm
package Cpanel::Fcntl::Constants;
use strict;
use warnings;
BEGIN {
our $O_RDONLY = 0;
our $O_WRONLY = 1;
our $O_RDWR = 2;
our $O_ACCMODE = 3;
our $F_GETFD = 1;
our $F_SETFD = 2;
our $F_GETFL = 3;
our $F_SETFL = 4;
our $SEEK_SET = 0;
our $SEEK_CUR = 1;
our $SEEK_END = 2;
our $S_IWOTH = 2;
our $S_ISUID = 2048;
our $S_ISGID = 1024;
our $O_CREAT = 64;
our $O_EXCL = 128;
our $O_TRUNC = 512;
our $O_APPEND = 1024;
our $O_NONBLOCK = 2048;
our $O_DIRECTORY = 65536;
our $O_NOFOLLOW = 131072;
our $O_CLOEXEC = 524288;
our $S_IFREG = 32768;
our $S_IFDIR = 16384;
our $S_IFCHR = 8192;
our $S_IFBLK = 24576;
our $S_IFIFO = 4096;
our $S_IFLNK = 40960;
our $S_IFSOCK = 49152;
our $S_IFMT = 61440;
our $LOCK_SH = 1;
our $LOCK_EX = 2;
our $LOCK_NB = 4;
our $LOCK_UN = 8;
our $FD_CLOEXEC = 1;
}
1;
} # --- END Cpanel/Fcntl/Constants.pm
{ # --- BEGIN Cpanel/Fcntl.pm
package Cpanel::Fcntl;
use strict;
use warnings;
# use Cpanel::Fcntl::Constants ();
my %CONSTANTS;
my %CACHE;
sub or_flags {
my (@flags) = @_;
my $flag_cache_key = join( '|', @flags );
return $CACHE{$flag_cache_key} if defined $CACHE{$flag_cache_key};
my $numeric = 0;
foreach my $o_const (@flags) {
$numeric |= (
$CONSTANTS{$o_const} ||= do {
my $glob = $Cpanel::Fcntl::Constants::{$o_const};
my $number_r = $glob && *{$glob}{'SCALAR'};
die "Missing \$Cpanel::Fcntl::Constants::$o_const! (does it need to be added?)" if !$number_r;
$$number_r;
}
);
}
return ( $CACHE{$flag_cache_key} = $numeric );
}
1;
} # --- END Cpanel/Fcntl.pm
{ # --- BEGIN Cpanel/FileUtils/Open.pm
package Cpanel::FileUtils::Open;
use strict;
# use Cpanel::Fcntl ();
sub sysopen_with_real_perms { ##no critic qw(RequireArgUnpacking)
my ( $file, $mode, $custom_perms ) = ( @_[ 1 .. 3 ] );
if ( $mode && substr( $mode, 0, 1 ) eq 'O' ) {
$mode = Cpanel::Fcntl::or_flags( split m<\|>, $mode );
}
my ( $sysopen_perms, $original_umask );
if ( defined $custom_perms ) {
$custom_perms &= 0777;
$original_umask = umask( $custom_perms ^ 07777 );
$sysopen_perms = $custom_perms;
}
else {
$sysopen_perms = 0666;
}
my $ret = sysopen( $_[0], $file, $mode, $sysopen_perms );
if ( defined $custom_perms ) {
() = umask($original_umask);
}
return $ret;
}
1;
} # --- END Cpanel/FileUtils/Open.pm
{ # --- BEGIN Cpanel/Parser/Vars.pm
package Cpanel::Parser::Vars;
use strict;
our $current_tag = '';
our $can_leave_cpanelaction = 1;
our $buffer = '';
our $loaded_api = 0;
our $trial_mode = 0;
our $sent_headers = 0;
our $live_socket_file;
our $incpanelaction = 0;
our $altmode = 0;
our $jsonmode = 0;
our $javascript = 0;
our $title = 0;
our $input = 0;
our $style = 0;
our $embtag = 0;
our $textarea = 0;
our $file = '[stdin]';
our $firstfile = '[stdin]';
our $trap_defaultfh = undef; # Known to be boolean.
our %BACKCOMPAT;
our $cptag;
our $sent_content_type;
1;
} # --- END Cpanel/Parser/Vars.pm
{ # --- BEGIN Cpanel/Encoder/Tiny/Rare.pm
package Cpanel::Encoder::Tiny::Rare;
use strict;
use warnings;
sub angle_bracket_decode {
my ($string) = @_;
$string =~ s{ < }{<}xmsg;
$string =~ s{ > }{>}xmsg;
return $string;
}
sub decode_utf8_html_entities {
my $str = shift;
$str =~ s/&\#(\d{4})\;/chr($1);/eg;
return $str;
}
my %uri_encoding_cache = (
'"' => '%22',
q{'} => '%27',
'(' => '%28',
')' => '%29',
q{ } => '%20',
"\t" => '%09',
);
sub css_encode_str {
my $str = shift;
$str =~ s{([\(\)\s"'])}{
$uri_encoding_cache{$1}
|| require Cpanel::Encoder::URI && Cpanel::Encoder::URI::uri_encode_str($1)
}ge;
return $str;
}
1;
} # --- END Cpanel/Encoder/Tiny/Rare.pm
{ # --- BEGIN Cpanel/Encoder/Tiny.pm
package Cpanel::Encoder::Tiny;
use strict;
my %XML_ENCODE_MAP = ( '&' => '&', '<' => '<', '>' => '>', '"' => '"', "'" => ''' );
my %HTML_ENCODE_MAP = ( '&' => '&', '<' => '<', '>' => '>', '"' => '"', "'" => ''' );
my %HTML_DECODE_MAP = ( 'amp' => '&', 'lt' => '<', 'gt' => '>', 'quot' => '"', 'apos' => q{'}, '#39' => q{'} );
my $decode_regex = do { my $tmp = join( '|', keys %HTML_DECODE_MAP ); "&($tmp);"; };
sub angle_bracket_encode {
my ($string) = @_;
$string =~ s{<}{<}xmsg;
$string =~ s{>}{>}xmsg;
return $string;
}
sub safe_xml_encode_str {
my $data = join( '', @_ );
return $data if $data !~ tr/&<>"'//;
$data =~ s/([&<>"'])/$XML_ENCODE_MAP{$1}/sg;
return $data;
}
sub safe_html_encode_str {
return $_[0] if !defined $_[0] || ( !defined $_[1] && $_[0] !~ tr/&<>"'// );
my $data = defined $_[1] ? join( '', @_ ) : $_[0];
return $data if $data !~ tr/&<>"'//;
$data =~ s/([&<>"'])/$HTML_ENCODE_MAP{$1}/sg;
return $data;
}
sub safe_html_decode_str {
return undef if !defined $_[0];
my $data = join( '', @_ );
$data =~ s/$decode_regex/$HTML_DECODE_MAP{$1}/g;
return $data;
}
sub css_encode_str {
require Cpanel::Encoder::Tiny::Rare;
*css_encode_str = *Cpanel::Encoder::Tiny::Rare::css_encode_str;
goto \&Cpanel::Encoder::Tiny::Rare::css_encode_str;
}
1;
} # --- END Cpanel/Encoder/Tiny.pm
{ # --- BEGIN Cpanel/Regex.pm
package Cpanel::Regex;
use strict;
our $VERSION = '0.2.5';
my $dblquotedstr = q{"([^\\\\"]*(?:\\\\.[^\\\\"]*)*)"};
my $sglquotedstr = $dblquotedstr;
$sglquotedstr =~ tr{"}{'};
my $zero_through_255 = '(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?|0)';
our %regex = (
'emailaddr' => '[a-zA-Z0-9!#\$\-=?^_{}~]+(?:\.[a-zA-Z0-9!#\$\-=?^_{}~]+)*(?:\+[a-zA-Z0-9 \.=\-\_]+)*\@[\da-zA-Z](?:[-\da-zA-Z]*[\da-zA-Z])?(?:\.[\da-zA-Z](?:[-\da-zA-Z]*[\da-zA-Z])?)*',
'oneplusdot' => '\.+',
'oneplusspacetab' => '[\s\t]+',
'multipledot' => '\.{2,}',
'commercialat' => '\@',
'plussign' => '\+',
'singledot' => '\.',
'newline' => '\n',
'doubledot' => '\.\.',
'lineofdigits' => '^\d+$',
'lineofnonprintingchars' => '^[\s\t]*$',
'getemailtransport' => '^from\s+.*\s+by\s+\S+\s+with\s+(\S+)',
'getreceivedfrom' => '^from\s+(.*)\s+by\s+',
'emailheaderterminator' => '^[\r\n]*$',
'forwardslash' => '\/',
'backslash' => chr(92) x 4,
'singlequote' => q('),
'doublequote' => '"',
'allspacetabchars' => '[\s\t]*',
'beginswithspaceortabs' => '^[\s\t]',
doublequotedstring => $dblquotedstr,
singlequotedstring => $sglquotedstr,
DUNS => '[0-9]{2}(?:-[0-9]{3}-[0-9]{4}|[0-9]{7})',
YYYY_MM_DD => '[0-9]{4}-(?:1[012]|0[1-9])-(?:3[01]|[12][0-9]|0[1-9])',
ipv4 => "(?:$zero_through_255\.){3}$zero_through_255",
);
1;
} # --- END Cpanel/Regex.pm
{ # --- BEGIN Cpanel/Carp.pm
package Cpanel::Carp;
use strict;
# use Cpanel::Parser::Vars ();
our ( $SHOW_TRACE, $OUTPUT_FORMAT, $VERBOSE ) = ( 1, 'text', 0 );
my $__CALLBACK_AFTER_DIE_SPEW; # Set when we need to run a code ref after spewing on die
my $error_count = 0;
sub import { return enable(); }
sub enable {
my (
$callback_before_warn_or_die_spew, # Runs before the spew on warn or die, currently used in cpanel to ensure we emit headers before body in the event of a warn or die spew
$callback_before_die_spew, # Runs before the spew on die, not currently used
$callback_after_die_spew, # Runs after the spew on die, currently used in whostmgr to ensure we emit the javascript footer when we die to avoid the UI breaking
) = @_;
$SIG{'__WARN__'} = sub { ## no critic qw(Variables::RequireLocalizedPunctuationVars)
my @caller = caller(1);
return if defined $caller[3] && index( $caller[3], 'eval' ) > -1; # Case 35335: Quiet spurious warn errors from evals
++$error_count;
my $time = time();
my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime($time);
my ( $gmmin, $gmhour, $gmday ) = ( gmtime($time) )[ 1, 2, 3 ];
my $gmoffset = ( $hour * 60 + $min ) - ( $gmhour * 60 + $gmmin ) + 1440 * ( $mday <=> $gmday );
my $tz = sprintf( '%+03d%02d', int( $gmoffset / 60 ), $gmoffset % 60 );
my $error_timestamp = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $year + 1900, $mon + 1, $mday, $hour, $min, $sec, $tz );
my $longmess;
my $ignorable;
if ( UNIVERSAL::isa( $_[0], 'Cpanel::Exception' ) ) {
$longmess = Cpanel::Carp::safe_longmess( $_[0]->to_locale_string() );
}
elsif ( ref $_[0] eq 'Template::Exception' ) {
$longmess = Cpanel::Carp::safe_longmess( "Template::Exception:\n\t[TYPE]=[" . $_[0]->[0] . "]\n\t[INFO]=[" . $_[0]->[1] . "]\n\t[TEXT]=[" . ( ref $_[0]->[2] eq 'SCALAR' ? ${ $_[0]->[2] } : $_[0]->[2] ) . "]\n" );
}
else {
$longmess = Cpanel::Carp::safe_longmess(@_);
$ignorable = 1 if index( $_[0], 'Use of uninitialized value' ) == 0;
}
my $error_container_text = 'A warning occurred while processing this directive.';
my $current_file = $Cpanel::Parser::Vars::file || 'unknown';
print STDERR "[$error_timestamp] warn [Internal Warning while parsing $current_file $$] $longmess\n\n";
return if ( $OUTPUT_FORMAT eq 'suppress' || $OUTPUT_FORMAT eq 'supress' || $ENV{'CPANEL_PHPENGINE'} );
return if $ignorable && !$VERBOSE;
_run_callback_without_die_handler($callback_before_warn_or_die_spew) if $callback_before_warn_or_die_spew;
if ( $OUTPUT_FORMAT eq 'html' ) {
if ($SHOW_TRACE) {
_print_without_die_handler( _generate_html_error_message( 'warn', $error_container_text, $longmess ) );
}
else {
_print_without_die_handler(qq{<span class="error" style="cursor:hand;cursor:pointer;">[$error_container_text]</span>});
}
}
elsif ( $OUTPUT_FORMAT eq 'xml' ) {
_print_without_die_handler("<error>$error_container_text</error>");
}
else {
_print_without_die_handler("[$error_container_text]\n");
}
};
$SIG{'__DIE__'} = sub { ## no critic qw(Variables::RequireLocalizedPunctuationVars)
return if $^S;
die $_[0] unless defined $^S;
delete $SIG{'__DIE__'};
_run_callback_without_die_handler($callback_before_warn_or_die_spew) if $callback_before_warn_or_die_spew;
_run_callback_without_die_handler($callback_before_die_spew) if $callback_before_die_spew;
$__CALLBACK_AFTER_DIE_SPEW = $callback_after_die_spew;
goto \&spew_on_die;
};
return 1;
}
sub spew_on_die { ## no critic qw(Subroutines::RequireArgUnpacking)
my ($err) = @_;
++$error_count;
my $time = time();
my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime($time);
my ( $gmmin, $gmhour, $gmday ) = ( gmtime($time) )[ 1, 2, 3 ];
my $gmoffset = ( $hour * 60 + $min ) - ( $gmhour * 60 + $gmmin ) + 1440 * ( $mday <=> $gmday );
my $tz = sprintf( '%+03d%02d', int( $gmoffset / 60 ), $gmoffset % 60 );
my $error_timestamp = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $year + 1900, $mon + 1, $mday, $hour, $min, $sec, $tz );
my $error_text;
if ( UNIVERSAL::isa( $err, 'Cpanel::Exception' ) ) {
$error_text = Cpanel::Carp::safe_longmess( $err->to_locale_string() );
}
elsif ( UNIVERSAL::isa( $err, 'Template::Exception' ) ) {
$error_text = Cpanel::Carp::safe_longmess( "Template::Exception:\n\t[TYPE]=[" . $err->type() . "]\n\t[INFO]=[" . $err->info() . "]\n\t[TEXT]=[" . $err->text() . "]\n" );
}
else {
$error_text = Cpanel::Carp::safe_longmess(@_);
}
my $current_file = $Cpanel::Parser::Vars::file || 'unknown';
print STDERR "[$error_timestamp] die [Internal Death while parsing $current_file $$] $error_text\n\n";
return if ( $OUTPUT_FORMAT eq 'suppress' || $OUTPUT_FORMAT eq 'supress' || $ENV{'CPANEL_PHPENGINE'} );
my $error_container_text = 'A fatal error or timeout occurred while processing this directive.';
if ( $OUTPUT_FORMAT eq 'html' ) {
if ($SHOW_TRACE) {
_print_without_die_handler( _generate_html_error_message( 'error', $error_container_text, $error_text ) );
}
else {
_print_without_die_handler(qq{<span class="error" style="cursor:hand;cursor:pointer;">[$error_container_text]</span>});
}
}
elsif ( $OUTPUT_FORMAT eq 'xml' ) {
_print_without_die_handler("<error>[$error_container_text]</error>");
}
else {
_print_without_die_handler("[$error_container_text]\n");
}
_run_callback_without_die_handler($__CALLBACK_AFTER_DIE_SPEW) if $__CALLBACK_AFTER_DIE_SPEW;
return;
}
my @SAFE_LONGMESS_KEY_REGEXP_ITEMS = (
'(?<![a-zA-Z0-9_])pw(?![a-zA-Z0-9_])',
qw(
hash
pass
auth
root
key
fullbackup
),
);
my @SAFE_LONGMESS_FUNCTION_REGEXP_ITEMS = (
@SAFE_LONGMESS_KEY_REGEXP_ITEMS,
'__ANON__',
);
sub _print_without_die_handler {
my ($text) = @_;
local $SIG{'__WARN__'} = sub { };
local $SIG{'__DIE__'} = 'DEFAULT';
return print $text;
}
sub _run_callback_without_die_handler {
my ($callback) = @_;
local $SIG{'__WARN__'} = sub { };
local $SIG{'__DIE__'} = 'DEFAULT';
return $callback->();
}
sub _generate_html_error_message {
my ( $type, $error_container_message, $error_message ) = @_;
require Cpanel::Encoder::Tiny;
my $safe_error_message = Cpanel::Encoder::Tiny::safe_html_encode_str($error_message);
return qq[
<style type="text/css">.cpanel_internal_message_container {display: inline-block; margin: 10px; width: auto;} .cpanel_internal_message { border: 1px solid #fff; outline-style: solid; outline-width: 1px; outline-color: #aaa; padding: 5px; } .cpanel_internal_error_warn { background-color: #FFF6CF; } .cpanel_internal_error_error { background-color: #F8E7E6; }</style>
<div id="cpanel_notice_item_$error_count" class="cjt-pagenotice-container cjt-notice-container cpanel_internal_message_container internal-error-container">
<div class="yui-module cjt-notice cjt-pagenotice cjt-notice-$type">
<div class="cpanel_internal_message cpanel_internal_error_$type bd">
<div class="cjt-notice-content" style="width: 420px;">
<span>
$error_container_message
<a
class="error"
style="cursor:hand;cursor:pointer;"
onClick="document.getElementById('cpanel_internal_error_$error_count').style.display='';this.style.display='none'; return false;">
[show]
</a>
<a
class="error"
style="cursor:hand;cursor:pointer;"
onClick="document.getElementById('cpanel_notice_item_$error_count').style.display='none'; return false;">
[close]
</a>
</span>
<div id="cpanel_internal_error_$error_count" style="display:none;">
<textarea class="cpanel_internal_error_$type" style="font-weight:900; height:200px; width:410px; color: black;">$safe_error_message</textarea>
</div>
</div>
</div>
</div>
</div>
];
}
sub safe_longmess {
require Carp;
$Carp::Internal{'Cpanel::Carp'} = 1;
return sanitize_longmess( scalar Carp::longmess(@_) );
}
my ( $key_regexp, $key_regexp_double, $function_regexp );
sub sanitize_longmess {
_build_regexes() if !$key_regexp;
return join(
"\n",
map {
( tr{'"}{} && ( m{$key_regexp}o || m{$key_regexp_double}o || ( ( $_ =~ m{^[ \t]*([^\(]+)\(} )[0] || '' ) =~ m{$function_regexp}o ) ) # matches a line that needs to be sanitized
&& _sanitize_line($_); # sanitize
$_
} split( m{\n}, $_[0] )
) . "\n";
}
sub _sanitize_line { # Operates directly on $_[0] for speed
if ( !$INC{'Cpanel/Regex.pm'} ) { # PPI NO PARSE - inc check
local $@;
eval {
local $SIG{__DIE__};
local $SIG{__WARN__};
require Cpanel::Regex; # PPI NO PARSE - inc check
};
}
$_[0] =~ s/$Cpanel::Regex::regex{'singlequotedstring'}/__CPANEL_HIDDEN__/go if index( $_[0], q{'} ) != -1;
$_[0] =~ s/$Cpanel::Regex::regex{'doublequotedstring'}/__CPANEL_HIDDEN__/go if index( $_[0], q{"} ) != -1;
return 1;
}
sub _build_regexes {
my $key_regexp_items = join '|', @SAFE_LONGMESS_KEY_REGEXP_ITEMS;
$key_regexp = qr<
'
.*?
(?:
$key_regexp_items
)
.*?
'
\s*
,
>x;
$key_regexp_double = $key_regexp;
$key_regexp_double =~ tr{'}{"}; # "' fix for poor editors
my $function_regexp_items = join '|', @SAFE_LONGMESS_FUNCTION_REGEXP_ITEMS;
$function_regexp = qr<
::
.*?
(?:
$function_regexp_items
)
.*?
$
>x;
return 1;
}
1;
} # --- END Cpanel/Carp.pm
{ # --- BEGIN Cpanel/ExceptionMessage.pm
package Cpanel::ExceptionMessage;
use strict;
# use Cpanel::Exception ();
*load_perl_module = \&Cpanel::Exception::load_perl_module;
1;
} # --- END Cpanel/ExceptionMessage.pm
{ # --- BEGIN Cpanel/Locale/Utils/Fallback.pm
package Cpanel::Locale::Utils::Fallback;
use strict;
use warnings;
sub interpolate_variables {
my ( $str, @maketext_opts ) = @_;
my $c = 1;
my %h = map { $c++, $_ } @maketext_opts;
$str =~ s{(\[(?:[^_]+,)?_([0-9])+\])}{$h{$2}}g;
return $str;
}
1;
} # --- END Cpanel/Locale/Utils/Fallback.pm
{ # --- BEGIN Cpanel/ExceptionMessage/Raw.pm
package Cpanel::ExceptionMessage::Raw;
use strict;
use warnings;
# use Cpanel::ExceptionMessage();
our @ISA;
BEGIN { push @ISA, qw(Cpanel::ExceptionMessage); }
# use Cpanel::Locale::Utils::Fallback ();
sub new {
my ( $class, $str ) = @_;
my $str_copy = $str;
return bless( \$str_copy, $class );
}
sub to_string {
my ($self) = @_;
return $$self;
}
sub get_language_tag {
return 'en';
}
BEGIN {
*Cpanel::ExceptionMessage::Raw::convert_localized_to_raw = *Cpanel::Locale::Utils::Fallback::interpolate_variables;
*Cpanel::ExceptionMessage::Raw::to_locale_string = *Cpanel::ExceptionMessage::Raw::to_string;
*Cpanel::ExceptionMessage::Raw::to_en_string = *Cpanel::ExceptionMessage::Raw::to_string;
}
1;
} # --- END Cpanel/ExceptionMessage/Raw.pm
{ # --- BEGIN Cpanel/LoadModule/Utils.pm
package Cpanel::LoadModule::Utils;
use strict;
use warnings;
sub module_is_loaded {
my $p = module_path( $_[0] );
return 0 unless defined $p;
return defined $INC{$p} ? 1 : 0;
}
sub module_path {
my ($module_name) = @_;
if ( defined $module_name && length($module_name) ) {
substr( $module_name, index( $module_name, '::' ), 2, '/' ) while index( $module_name, '::' ) > -1;
$module_name .= '.pm' unless substr( $module_name, -3 ) eq '.pm';
}
return $module_name;
}
sub is_valid_module_name {
return $_[0] =~ m/\A[A-Za-z_]\w*(?:(?:'|::)\w+)*\z/ ? 1 : 0;
}
1;
} # --- END Cpanel/LoadModule/Utils.pm
{ # --- BEGIN Cpanel/ScalarUtil.pm
package Cpanel::ScalarUtil;
use strict;
use warnings;
sub blessed {
return ref( $_[0] ) && UNIVERSAL::isa( $_[0], 'UNIVERSAL' ) || undef;
}
1;
} # --- END Cpanel/ScalarUtil.pm
{ # --- BEGIN Cpanel/Exception/CORE.pm
package Cpanel::Exception;
use strict;
BEGIN {
$INC{'Cpanel/Exception.pm'} = '__BYPASSED__';
}
our $_SUPPRESS_STACK_TRACES = 0;
our $_EXCEPTION_MODULE_PREFIX = 'Cpanel::Exception';
our $IN_EXCEPTION_CREATION = 0;
our $_suppressed_msg = '__STACK_TRACE_SUPPRESSED__YOU_SHOULD_NEVER_SEE_THIS_MESSAGE__';
my $PACKAGE = 'Cpanel::Exception';
my $locale;
my @ID_CHARS = qw( a b c d e f g h j k m n p q r s t u v w x y z 2 3 4 5 6 7 8 9 );
my $ID_LENGTH = 6;
# use Cpanel::ExceptionMessage::Raw ();
# use Cpanel::LoadModule::Utils ();
use constant _TRUE => 1;
use overload (
'""' => \&__spew,
bool => \&_TRUE,
fallback => 1,
);
BEGIN {
die "Cannot compile Cpanel::Exception::CORE" if $INC{'B/C.pm'};
}
sub _init { return 1 } # legacy
sub create {
my ( $exception_type, @args ) = @_;
_init();
if ($IN_EXCEPTION_CREATION) {
_load_cpanel_carp();
die 'Cpanel::Carp'->can('safe_longmess')->("Attempted to create a “$exception_type” exception with arguments “@args” while creating exception “$IN_EXCEPTION_CREATION->[0]” with arguments “@{$IN_EXCEPTION_CREATION->[1]}”.");
}
local $IN_EXCEPTION_CREATION = [ $exception_type, \@args ];
if ( $exception_type !~ m/\A[A-Za-z0-9_]+(?:\:\:[A-Za-z0-9_]+)*\z/ ) {
die "Invalid exception type: $exception_type";
}
my $perl_class;
if ( $exception_type eq __PACKAGE__ ) {
$perl_class = $exception_type;
}
else {
$perl_class = "${_EXCEPTION_MODULE_PREFIX}::$exception_type";
}
_load_perl_module($perl_class) unless $perl_class->can('new');
if ( $args[0] && ref $args[0] eq 'ARRAY' && scalar @{ $args[0] } > 1 ) {
$args[0] = { @{ $args[0] } };
}
return $perl_class->new(@args);
}
sub create_raw {
my ( $class, $msg, @extra_args ) = @_;
_init();
my $msg_obj = 'Cpanel::ExceptionMessage::Raw'->new($msg);
if ( $class =~ m<\A(?:\Q${_EXCEPTION_MODULE_PREFIX}::\E)?Collection\z> ) {
die "Use create('Collection', ..) to create a Cpanel::Exception::Collection object.";
}
return create( $class, $msg_obj, @extra_args );
}
sub _load_perl_module {
my ($module) = @_;
local ( $!, $@ );
if ( !defined $module ) {
die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("load_perl_module requires a module name.") );
}
return 1 if Cpanel::LoadModule::Utils::module_is_loaded($module);
my $module_name = $module;
$module_name =~ s{\.pm$}{};
if ( !Cpanel::LoadModule::Utils::is_valid_module_name($module_name) ) {
die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("load_perl_module requires a valid module name: '$module_name'.") );
}
{
eval qq{use $module (); 1 }
or die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("load_perl_module cannot load '$module_name': $@") )
}
return 1;
}
sub new {
my ( $class, @args ) = @_;
@args = grep { defined } @args;
my $self = {};
bless $self, $class;
if ( ref $args[-1] eq 'HASH' ) {
$self->{'_metadata'} = pop @args;
}
if ( defined $self->{'_metadata'}->{'longmess'} ) {
$self->{'_longmess'} = &{ $self->{'_metadata'}->{'longmess'} }($self)
if $self->{'_metadata'}->{'longmess'};
}
elsif ($_SUPPRESS_STACK_TRACES) {
$self->{'_longmess'} = $_suppressed_msg;
}
else {
if ( !$INC{'Carp.pm'} ) { _load_carp(); }
$self->{'_longmess'} = scalar do {
local $Carp::CarpInternal{'Cpanel::Exception'} = 1;
local $Carp::CarpInternal{$class} = 1;
'Carp'->can('longmess')->();
};
}
_init();
$self->{'_auxiliaries'} = [];
if ( UNIVERSAL::isa( $args[0], 'Cpanel::ExceptionMessage' ) ) {
$self->{'_message'} = shift @args;
}
else {
my @mt_args;
if ( @args && !ref $args[0] ) {
@mt_args = ( shift @args );
if ( ref $args[0] eq 'ARRAY' ) {
push @mt_args, @{ $args[0] };
}
}
else {
my $phrase = $self->_default_phrase( $args[0] );
if ($phrase) {
if ( ref $phrase ) {
@mt_args = $phrase->to_list();
}
else {
$self->{'_message'} = Cpanel::ExceptionMessage::Raw->new($phrase);
return $self;
}
}
}
if ( my @extras = grep { !ref } @args ) {
die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("Extra scalar(s) passed to $PACKAGE! (@extras)") );
}
if ( !length $mt_args[0] ) {
die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("No args passed to $PACKAGE constructor!") );
}
$self->{'_mt_args'} = \@mt_args;
}
return $self;
}
sub get_string {
my ( $exc, $no_id_yn ) = @_;
return get_string_no_id($exc) if $no_id_yn;
return _get_string( $exc, 'to_string' );
}
sub get_string_no_id {
my ($exc) = @_;
return _get_string( $exc, 'to_string_no_id' );
}
sub _get_string {
my ( $exc, $cp_exc_stringifier_name ) = @_;
return $exc if !ref $exc;
{
local $@;
my $ret = eval { $exc->$cp_exc_stringifier_name() };
return $ret if defined $ret && !$@ && !ref $ret;
}
if ( ref $exc eq 'HASH' && $exc->{'message'} ) {
return $exc->{'message'};
}
if ( $INC{'Cpanel/YAML.pm'} ) {
local $@;
my $ret = eval { 'Cpanel::YAML'->can('Dump')->($exc); };
return $ret if defined $ret && !$@;
}
if ( $INC{'Cpanel/JSON.pm'} ) {
local $@;
my $ret = eval { 'Cpanel::JSON'->can('Dump')->($exc); };
return $ret if defined $ret && !$@;
}
return $exc;
}
sub _create_id {
srand();
return join(
q<>,
map { $ID_CHARS[ int rand( 0 + @ID_CHARS ) ]; } ( 1 .. $ID_LENGTH ),
);
}
sub get_stack_trace_suppressor {
return Cpanel::Exception::_StackTraceSuppression->new();
}
sub set_id {
my ( $self, $new_id ) = @_;
$self->{'_id'} = $new_id;
return $self;
}
sub id {
my ($self) = @_;
return $self->{'_id'} ||= _create_id();
}
sub set {
my ( $self, $key ) = @_;
$self->{'_metadata'}{$key} = $_[2];
return $self;
}
sub get {
my ( $self, $key ) = @_;
my $v = $self->{'_metadata'}{$key};
if ( my $reftype = ref $v ) {
local $@;
if ( $reftype eq 'HASH' ) {
$v = { %{$v} }; # shallow copy
}
elsif ( $reftype eq 'ARRAY' ) {
$v = [ @{$v} ]; # shallow copy
}
elsif ( $reftype eq 'SCALAR' ) {
$v = \${$v}; # shallow copy
}
else {
local ( $@, $! );
require Cpanel::ScalarUtil;
if ( $reftype ne 'GLOB' && !Cpanel::ScalarUtil::blessed($v) ) {
warn if !eval {
_load_perl_module('Clone') if !$INC{'Clone.pm'};
$v = 'Clone'->can('clone')->($v);
};
}
}
}
return $v;
}
my $loaded_LocaleString;
sub _require_LocaleString {
return $loaded_LocaleString ||= do {
local $@;
eval 'require Cpanel::LocaleString; 1;' or die $@; ## no critic qw(BuiltinFunctions::ProhibitStringyEval) - # PPI NO PARSE - load on demand
1;
};
}
my $loaded_ExceptionMessage_Locale;
sub _require_ExceptionMessage_Locale {
return $loaded_ExceptionMessage_Locale ||= do {
local $@;
eval 'require Cpanel::ExceptionMessage::Locale; 1;' or die $@; ## no critic qw(BuiltinFunctions::ProhibitStringyEval) - # PPI NO PARSE - load on demand
1;
};
}
sub _default_phrase {
_require_LocaleString();
return 'Cpanel::LocaleString'->new( 'An unknown error in the “[_1]” package has occurred.', scalar ref $_[0] ); # PPI NO PARSE - loaded above
}
sub longmess {
my ($self) = @_;
return '' if $self->{'_longmess'} eq $_suppressed_msg;
_load_cpanel_carp() if !$INC{'Cpanel/Carp.pm'};
return Cpanel::Carp::sanitize_longmess( $self->{'_longmess'} );
}
sub to_string {
my ($self) = @_;
return _apply_id_prefix( $self->id(), $self->to_string_no_id() );
}
sub to_string_no_id {
my ($self) = @_;
my $string = $self->to_locale_string_no_id();
if ( $self->_message()->get_language_tag() ne 'en' ) {
my $en_string = $self->to_en_string_no_id();
$string .= "\n$en_string" if ( $en_string ne $string );
}
return $string;
}
sub _apply_id_prefix {
my ( $id, $msg ) = @_;
return sprintf "(XID %s) %s", $id, $msg;
}
sub to_en_string {
my ($self) = @_;
return _apply_id_prefix( $self->id(), $self->to_en_string_no_id() );
}
sub to_en_string_no_id {
my ($self) = @_;
return $self->_message()->to_en_string() . $self->_stringify_auxiliaries('to_en_string');
}
sub to_locale_string {
my ($self) = @_;
return _apply_id_prefix( $self->id(), $self->to_locale_string_no_id() );
}
sub to_locale_string_no_id {
my ($self) = @_;
return $self->_message()->to_locale_string() . $self->_stringify_auxiliaries('to_locale_string');
}
sub add_auxiliary_exception {
my ( $self, $aux ) = @_;
return push @{ $self->{'_auxiliaries'} }, $aux;
}
sub get_auxiliary_exceptions {
my ($self) = @_;
die 'List context only!' if !wantarray; #Can’t use Cpanel::Context
return @{ $self->{'_auxiliaries'} };
}
sub __spew {
my ($self) = @_;
return $self->_spew();
}
sub _spew {
my ($self) = @_;
return ref($self) . '/' . join "\n", $self->to_string() || '<no message>', $self->longmess() || ();
}
sub _stringify_auxiliaries {
my ( $self, $method ) = @_;
my @lines;
if ( @{ $self->{'_auxiliaries'} } ) {
local $@;
_require_LocaleString();
my $intro = 'Cpanel::LocaleString'->new( 'The following additional [numerate,_1,error,errors] occurred:', 0 + @{ $self->{'_auxiliaries'} } ); # PPI NO PARSE - required above
if ( $method eq 'to_locale_string' ) {
push @lines, _locale()->makevar( $intro->to_list() );
}
elsif ( $method eq 'to_en_string' ) {
push @lines, _locale()->makethis_base( $intro->to_list() );
}
else {
die "Invalid method: $method";
}
push @lines, map { UNIVERSAL::isa( $_, __PACKAGE__ ) ? $_->$method() : $_ } @{ $self->{'_auxiliaries'} };
}
return join q<>, map { "\n$_" } @lines;
}
*TO_JSON = \&to_string;
sub _locale {
return $locale ||= do {
local $@;
eval 'require Cpanel::Locale; 1;' or die $@;
'Cpanel::Locale'->get_handle(); # hide from perlcc
};
}
sub _reset_locale {
return undef $locale;
}
sub _load_carp {
if ( !$INC{'Carp.pm'} ) {
local $@;
eval 'require Carp; 1;' or die $@; ## no critic qw(BuiltinFunctions::ProhibitStringyEval) -- hide from perlcc
}
return;
}
sub _load_cpanel_carp {
if ( !$INC{'Cpanel/Carp.pm'} ) {
local $@;
eval 'require Cpanel::Carp; 1;' or die $@; ## no critic qw(BuiltinFunctions::ProhibitStringyEval) -- hide from perlcc
}
return;
}
sub _message {
my ($self) = @_;
return $self->{'_message'} if $self->{'_message'};
local $!;
if ($Cpanel::Exception::LOCALIZE_STRINGS) { # the default
_require_ExceptionMessage_Locale();
return ( $self->{'_message'} ||= 'Cpanel::ExceptionMessage::Locale'->new( @{ $self->{'_mt_args'} } ) ); # PPI NO PARSE - required above
}
return ( $self->{'_message'} ||= Cpanel::ExceptionMessage::Raw->new( Cpanel::ExceptionMessage::Raw::convert_localized_to_raw( @{ $self->{'_mt_args'} } ) ) );
}
package Cpanel::Exception::_StackTraceSuppression;
sub new {
my ($class) = @_;
$Cpanel::Exception::_SUPPRESS_STACK_TRACES++;
return bless [], $class;
}
sub DESTROY {
$Cpanel::Exception::_SUPPRESS_STACK_TRACES--;
return;
}
1;
} # --- END Cpanel/Exception/CORE.pm
{ # --- BEGIN Cpanel/TimeHiRes.pm
package Cpanel::TimeHiRes;
use strict;
use warnings;
use constant {
_gettimeofday => 96,
_clock_gettime => 228,
_CLOCK_REALTIME => 0,
_EINTR => 4,
_PACK_TEMPLATE => 'L!L!',
};
sub clock_gettime {
my $timeval = pack( _PACK_TEMPLATE, () );
_get_time_from_syscall(
_clock_gettime,
_CLOCK_REALTIME,
$timeval,
);
return unpack( _PACK_TEMPLATE, $timeval );
}
sub time {
my ( $secs, $nsecs ) = clock_gettime();
return $secs + ( $nsecs / 1_000_000_000 );
}
sub sleep {
my ($secs) = @_;
local $!;
my $retval = select( undef, undef, undef, $secs );
if ( $retval == -1 && $! != _EINTR ) {
require Cpanel::Exception;
die 'Cpanel::Exception'->can('create')->( 'SystemCall', 'The system failed to suspend command execution for [quant,_1,second,seconds] because of an error: [_2]', [ $secs, $! ] );
}
return $secs;
}
sub gettimeofday {
my $timeval = pack( _PACK_TEMPLATE, () );
_get_time_from_syscall(
_gettimeofday,
$timeval,
undef,
);
return unpack( _PACK_TEMPLATE, $timeval );
}
sub _get_time_from_syscall { ##no critic qw(RequireArgUnpacking)
my $syscall_num = shift;
local $!;
my $retval = syscall( $syscall_num, @_ );
if ( $retval == -1 ) {
require Cpanel::Exception;
die 'Cpanel::Exception'->can('create')->( 'SystemCall', 'The system failed to retrieve the time because of an error: [_1]', [$!] );
}
return;
}
1;
} # --- END Cpanel/TimeHiRes.pm
{ # --- BEGIN Cpanel/SafeFileLock.pm
package Cpanel::SafeFileLock;
use strict;
use warnings;
use constant {
_ENOENT => 2,
_EDQUOT => 122,
DEBUG => 0,
MAX_LOCKFILE_SIZE => 8192,
};
sub new {
my ( $class, $path_to_lockfile, $fh, $path_to_file_being_locked ) = @_;
if ( scalar @_ != 4 ) {
die 'Usage: Cpanel::SafeFileLock->new($path_to_lockfile, $fh, $path_to_file_being_locked)';
}
if ($fh) {
write_lock_contents( $fh, $path_to_lockfile ) or return;
}
my $self = bless [
$path_to_lockfile,
$fh,
$path_to_file_being_locked,
], $class;
push @$self, @{ $self->stat_ar() }[ 1, 9 ];
return $self;
}
sub new_before_lock {
my ( $class, $path_to_lockfile, $path_to_file_being_locked ) = @_;
if ( scalar @_ != 3 ) {
die 'Usage: Cpanel::SafeFileLock->new_before_lock($path_to_lockfile, $path_to_file_being_locked)';
}
return bless [
$path_to_lockfile,
undef,
$path_to_file_being_locked,
], $class;
}
sub set_filehandle_and_unlinker_after_lock {
$_[0][1] = $_[1];
push @{ $_[0] }, @{ $_[0]->stat_ar() }[ 1, 9 ];
$_[0][5] = $_[2];
return $_[0];
}
sub get_path {
return $_[0]->[0];
}
sub get_path_to_file_being_locked {
return $_[0]->[2] // die "get_path_to_file_being_locked requires the object to be instantiated with the path_to_file_being_locked";
}
sub set_filehandle {
$_[0][1] = $_[1];
return $_[0];
}
sub get_filehandle {
return $_[0]->[1];
}
sub get_inode {
return $_[0]->[3];
}
sub get_mtime {
return $_[0]->[4];
}
sub get_path_fh_inode_mtime {
return @{ $_[0] }[ 0, 1, 3, 4 ];
}
sub stat_ar {
return [ stat( ( $_[0]->[1] && fileno( $_[0]->[1] ) ) ? $_[0]->[1] : $_[0]->[0] ) ];
}
sub lstat_ar {
return [ $_[0]->[1] && fileno( $_[0]->[1] ) ? stat( $_[0]->[1] ) : lstat( $_[0]->[0] ) ];
}
sub close {
return close $_[0]->[1] if ref $_[0]->[1];
$_[0]->[5] = undef;
return;
}
sub write_lock_contents { ## no critic qw(Subroutines::RequireArgUnpacking) -- only unpack on the failure case
local $!;
if (DEBUG) {
require Cpanel::Carp;
return 1 if syswrite( $_[0], "$$\n$0\n" . Cpanel::Carp::safe_longmess() . "\n" );
}
return 1 if syswrite( $_[0], "$$\n$0\n" );
my ( $fh, $path_to_lockfile ) = @_;
my $write_error = $!;
CORE::close($fh);
unlink $path_to_lockfile;
require Cpanel::Exception;
die Cpanel::Exception::create( 'IO::FileWriteError', [ 'path' => $path_to_lockfile, 'error' => $write_error ] );
}
sub fetch_lock_contents_if_exists {
my ($lockfile) = @_;
die 'Need lock file!' if !$lockfile;
open my $lockfile_fh, '<:stdio', $lockfile or do {
return if $! == _ENOENT();
die "open($lockfile): $!";
};
my $buffer;
my $read_result = read( $lockfile_fh, $buffer, MAX_LOCKFILE_SIZE );
if ( !defined $read_result ) {
die "read($lockfile): $!";
}
my ( $pid_line, $lock_name, $lock_obj ) = split( /\n/, $buffer, 3 );
chomp($lock_name) if length $lock_name;
my ($lock_pid) = $pid_line && ( $pid_line =~ m/(\d+)/ );
return ( $lock_pid, $lock_name || 'unknown', $lock_obj || 'unknown', $lockfile_fh );
}
1;
} # --- END Cpanel/SafeFileLock.pm
{ # --- BEGIN Cpanel/LoadModule.pm
package Cpanel::LoadModule;
use strict;
# use Cpanel::Exception ();
# use Cpanel::LoadModule::Utils ();
my $logger;
my $has_perl_dir = 0;
sub _logger_warn {
my ( $msg, $fail_ok ) = @_;
return if $fail_ok && $ENV{'CPANEL_BASE_INSTALL'} && index( $^X, '/usr/local/cpanel' ) == -1;
if ( $INC{'Cpanel/Logger.pm'} ) {
$logger ||= 'Cpanel::Logger'->new();
$logger->warn($msg);
}
return warn $msg;
}
sub _reset_has_perl_dir {
$has_perl_dir = 0;
return;
}
sub load_perl_module { ## no critic qw(Subroutines::RequireArgUnpacking)
if ( -1 != index( $_[0], q<'> ) ) {
die Cpanel::Exception::create_raw( 'InvalidParameter', "Module names with single-quotes are prohibited. ($_[0])" );
}
return $_[0] if Cpanel::LoadModule::Utils::module_is_loaded( $_[0] );
my ( $mod, @LIST ) = @_;
local ( $!, $@ );
if ( !is_valid_module_name($mod) ) {
die Cpanel::Exception::create( 'InvalidParameter', '“[_1]” is not a valid name for a Perl module.', [$mod] );
}
my $args_str;
if (@LIST) {
$args_str = join ',', map {
die "Only scalar arguments allowed in LIST! (@LIST)" if ref;
_single_quote($_);
} @LIST;
}
else {
$args_str = q<>;
}
eval "use $mod ($args_str);"; ## no critic qw(BuiltinFunctions::ProhibitStringyEval)
if ($@) {
die Cpanel::Exception::create( 'ModuleLoadError', [ module => $mod, error => $@ ] );
}
return $mod;
}
*module_is_loaded = *Cpanel::LoadModule::Utils::module_is_loaded;
*is_valid_module_name = *Cpanel::LoadModule::Utils::is_valid_module_name;
sub loadmodule {
return 1 if cpanel_namespace_module_is_loaded( $_[0] );
return _modloader( $_[0] );
}
sub lazy_load_module {
my $mod = shift;
my $mod_path = $mod;
$mod_path =~ s{::}{/}g;
if ( exists $INC{ $mod_path . '.pm' } ) {
return;
}
if ( !is_valid_module_name($mod) ) {
_logger_warn("Cpanel::LoadModule: Invalid module name ($mod)");
return;
}
eval "use $mod ();";
if ($@) {
delete $INC{ $mod_path . '.pm' };
_logger_warn( "Cpanel::LoadModule:: Failed to load module $mod - $@", 1 );
return;
}
return 1;
}
sub cpanel_namespace_module_is_loaded {
my ($modpart) = @_;
$modpart =~ s{::}{/}g;
return exists $INC{"Cpanel/$modpart.pm"} ? 1 : 0;
}
sub _modloader {
my $module = shift;
if ( !$module ) {
_logger_warn("Empty module name passed to modloader");
return;
}
if ( !is_valid_module_name($module) ) {
_logger_warn("Invalid module name ($module) passed to modloader");
return;
}
eval qq[ use Cpanel::${module}; Cpanel::${module}::${module}_init() if "Cpanel::${module}"->can("${module}_init"); ]; # PPI USE OK - This looks like usage of the Cpanel module and it's not.
if ($@) {
_logger_warn("Error loading module $module - $@");
return;
}
return 1;
}
sub _single_quote {
local ($_) = $_[0];
s/([\\'])/\\$1/g;
return qq('$_');
}
1;
} # --- END Cpanel/LoadModule.pm
{ # --- BEGIN Cpanel/FHUtils/Tiny.pm
package Cpanel::FHUtils::Tiny;
use strict;
use warnings;
sub is_a {
return !ref $_[0] ? 0 : ( ref $_[0] eq 'IO::Handle' || ref $_[0] eq 'GLOB' || UNIVERSAL::isa( $_[0], 'GLOB' ) ) ? 1 : 0;
}
sub are_same {
my ( $fh1, $fh2 ) = @_;
return 1 if $fh1 eq $fh2;
if ( fileno($fh1) && ( fileno($fh1) != -1 ) && fileno($fh2) && ( fileno($fh2) != -1 ) ) {
return 1 if fileno($fh1) == fileno($fh2);
}
return 0;
}
sub to_bitmask {
my @fhs = @_;
my $mask = q<>;
for my $fh (@fhs) {
vec( $mask, ref($fh) ? fileno($fh) : $fh, 1 ) = 1;
}
return $mask;
}
1;
} # --- END Cpanel/FHUtils/Tiny.pm
{ # --- BEGIN Cpanel/Hash.pm
package Cpanel::Hash;
use strict;
*get_fastest_hash = \&fnv1a_32;
use constant FNV1_32A_INIT => 0x811c9dc5;
use constant FNV_32_PRIME => 0x01000193;
use constant FNV_32_MOD => 2**32; # AKA 0x100000000 but that it non-portable;
sub fnv1a_32 {
my $fnv32 = FNV1_32A_INIT();
( $fnv32 = ( ( $fnv32 ^ $_ ) * FNV_32_PRIME() ) % FNV_32_MOD ) for unpack( 'C*', $_[0] );
return $fnv32;
}
1;
} # --- END Cpanel/Hash.pm
{ # --- BEGIN Cpanel/SafeFile.pm
package Cpanel::SafeFile;
use strict;
use warnings;
# use Cpanel::TimeHiRes ();
# use Cpanel::Fcntl::Constants ();
# use Cpanel::SafeFileLock ();
# use Cpanel::LoadModule ();
# use Cpanel::FHUtils::Tiny ();
use constant {
_EWOULDBLOCK => 11,
_EACCES => 13,
_EDQUOT => 122,
_ENOENT => 2,
_EINTR => 4,
_EEXIST => 17,
_ENOSPC => 28,
_EPERM => 1,
MAX_LOCK_CREATE_ATTEMPTS => 90,
NO_PERM_TO_WRITE_TO_DOTLOCK_DIR => -1,
INOTIFY_FILE_DISAPPEARED => 2,
CREATE_FCNTL_VALUE => ( $Cpanel::Fcntl::Constants::O_WRONLY | $Cpanel::Fcntl::Constants::O_EXCL | $Cpanel::Fcntl::Constants::O_CREAT | $Cpanel::Fcntl::Constants::O_NONBLOCK ),
UNLOCK_FCNTL_VALUE => $Cpanel::Fcntl::Constants::LOCK_UN,
LOCK_FILE_PERMS => 0644,
DEFAULT_LOCK_WAIT_TIME => 196,
MAX_LOCK_WAIT_TIME => 400,
MAX_LOCK_FILE_LENGTH => 225,
};
$Cpanel::SafeFile::VERSION = '5.0';
my $OVERWRITE_FCNTL_VALUE;
my $verbose = 0; # initialized in safelock
our $LOCK_WAIT_TIME; #allow lock wait time to be overwritten
my $OPEN_LOCKS = 0;
our $TIME_BETWEEN_DOTLOCK_CHECKS = 0.3;
our $TIME_BETWEEN_FLOCK_CHECKS = 0.05;
our $MAX_FLOCK_WAIT = 60; # allowed to be overwritten in tests
our $_SKIP_DOTLOCK_WHEN_NO_PERMS = 0;
our $_SKIP_WARN_ON_OPEN_FAIL = 0;
my $DOUBLE_LOCK_DETECTED = 4096;
sub safeopen { #fh, open()-style mode, path
my ( $mode, $file ) = _get_open_args( @_[ 1 .. $#_ ] );
my $open_method_coderef = sub {
my $ret = open( $_[0], $_[1], $_[2] ) || do {
_log_warn("open($_[1], $_[2]): $!");
return undef;
};
return $ret;
};
return _safe_open( $_[0], $mode, $file, $open_method_coderef, 'safeopen' );
}
sub safesysopen_no_warn_on_fail {
local $_SKIP_WARN_ON_OPEN_FAIL = 1;
return safesysopen(@_);
}
sub safesysopen_skip_dotlock_if_not_root {
local $_SKIP_DOTLOCK_WHEN_NO_PERMS = $> == 0 ? 0 : 1;
return safesysopen(@_);
}
sub safeopen_skip_dotlock_if_not_root {
local $_SKIP_DOTLOCK_WHEN_NO_PERMS = $> == 0 ? 0 : 1;
return safeopen(@_);
}
sub safelock_skip_dotlock_if_not_root {
local $_SKIP_DOTLOCK_WHEN_NO_PERMS = $> == 0 ? 0 : 1;
return safelock(@_);
}
sub safereopen { ##no critic qw(RequireArgUnpacking)
my $fh = shift;
if ( !$fh ) {
require Cpanel::Carp;
die Cpanel::Carp::safe_longmess("Undefined filehandle not allowed!");
}
elsif ( !fileno $fh ) {
require Cpanel::Carp;
die Cpanel::Carp::safe_longmess("Closed filehandle ($fh) not allowed!");
}
my ( $mode, $file ) = _get_open_args(@_);
my $open_method_coderef = sub {
return open( $_[0], $_[1], $_[2] ) || do {
_log_warn("open($_[1], $_[2]): $!");
return undef;
};
};
return _safe_re_open( $fh, $mode, $file, $open_method_coderef, 'safereopen' );
}
sub safesysopen { ##no critic qw(RequireArgUnpacking)
my ( $file, $open_mode, $custom_perms ) = ( @_[ 1 .. 3 ] );
my ( $sysopen_perms, $original_umask );
$open_mode = _sanitize_open_mode($open_mode);
my $open_method_coderef = sub {
return sysopen( $_[0], $_[2], $_[1], $sysopen_perms ) || do {
_log_warn("open($_[2], $_[1], $sysopen_perms): $!") unless $_SKIP_WARN_ON_OPEN_FAIL;
return undef;
};
};
if ( defined $custom_perms ) {
$custom_perms &= 0777;
$original_umask = umask( $custom_perms ^ 07777 );
$sysopen_perms = $custom_perms;
}
else {
$sysopen_perms = 0666;
}
my $lock_ref;
local $@;
my $ok = eval {
$lock_ref = _safe_open( $_[0], $open_mode, $file, $open_method_coderef, 'safesysopen' );
1;
};
if ( defined $custom_perms ) {
umask($original_umask);
}
die if !$ok;
return $lock_ref;
}
sub safeclose {
my ( $fh, $lockref, $do_something_before_releasing_lock ) = @_;
if ( $do_something_before_releasing_lock && ref $do_something_before_releasing_lock eq 'CODE' ) {
$do_something_before_releasing_lock->();
}
my $success = 1;
if ( $fh && defined fileno $fh ) {
flock( $fh, UNLOCK_FCNTL_VALUE ) or _log_warn( "flock(LOCK_UN) on “" . $lockref->get_path() . "” failed with error: $!" ); # LOCK_UN
$success = close $fh;
}
my $safe_unlock = safeunlock($lockref);
$OPEN_LOCKS-- if ( $safe_unlock && $success );
return ( $safe_unlock && $success );
}
sub safelock {
my ($file) = @_;
my $lock_obj = _safelock($file);
return if !ref $lock_obj;
return $lock_obj;
}
sub _safelock {
my ($file) = @_;
if ( !$file || $file =~ tr/\0// ) {
_log_warn('safelock: Invalid arguments');
return;
}
$verbose ||= ( _verbose_flag_file_exists() ? 1 : -1 );
my $lockfile = _calculate_lockfile($file);
my $safefile_lock = Cpanel::SafeFileLock->new_before_lock( $lockfile, $file );
my ( $lock_status, $lock_fh, $attempts, $last_err );
{
local $@;
while ( ++$attempts < MAX_LOCK_CREATE_ATTEMPTS ) {
( $lock_status, $lock_fh ) = _lock_wait( $file, $safefile_lock, $lockfile );
last if $lock_status;
$last_err = $!;
if ( $lock_fh && $lock_fh == $DOUBLE_LOCK_DETECTED ) {
return 0;
}
}
}
if ( $lock_fh == 1 ) {
return 1;
}
elsif ( $lock_status && $lock_fh ) {
return $safefile_lock;
}
_log_warn( 'safelock: waited for lock (' . $lockfile . ') ' . $attempts . ' times' );
require Cpanel::Exception;
die Cpanel::Exception::create( 'IO::FileCreateError', [ 'path' => $lockfile, 'error' => $last_err ] );
}
sub _write_temp_lock_file {
my ($lockfile) = @_;
my $temp_file = sprintf(
'%s-%x-%x-%x',
$lockfile,
substr( rand, 2 ),
scalar( reverse time ),
scalar( reverse $$ ),
);
my ( $ok, $fh_or_err ) = _create_lockfile($temp_file);
if ( !$ok ) {
if ( $fh_or_err == _EPERM() || $fh_or_err == _EACCES() ) {
local $!;
my $lock_dir = _getdir($lockfile);
if ( !-w $lock_dir ) {
if ($_SKIP_DOTLOCK_WHEN_NO_PERMS) { # A hack to allow /etc/valiases to still be flock()ed until we can refactor
return ( NO_PERM_TO_WRITE_TO_DOTLOCK_DIR, $fh_or_err );
}
else {
_log_warn("safelock: Failed to create a lockfile '$temp_file' in the directory '$lock_dir' that isn't writable: $fh_or_err");
}
}
}
return ( 0, $fh_or_err );
}
Cpanel::SafeFileLock::write_lock_contents( $fh_or_err, $temp_file );
return ( $temp_file, $fh_or_err );
}
sub _try_to_install_lockfile {
my ( $temp_file, $lockfile ) = @_;
link( $temp_file => $lockfile ) or do {
return 0 if $! == _EEXIST;
Cpanel::LoadModule::load_perl_module('Cpanel::Exception');
die Cpanel::Exception::create( 'IO::LinkError', [ oldpath => $temp_file, newpath => $lockfile, error => $! ] );
};
return 1;
}
sub safeunlock {
my $lockref = shift;
if ( !$lockref ) {
_log_warn('safeunlock: Invalid arguments');
return;
}
elsif ( !ref $lockref ) {
return 1 if $lockref eq '1'; # No lock file created so just succeed
$lockref = Cpanel::SafeFileLock->new( $lockref, undef, undef );
if ( !$lockref ) {
_log_warn("safeunlock: failed to generate a Cpanel::SafeFileLock object from a path");
return;
}
}
my ( $lock_path, $fh, $lock_inode, $lock_mtime ) = $lockref->get_path_fh_inode_mtime();
my ( $filesys_lock_ino, $filesys_lock_mtime ) = ( lstat $lock_path )[ 1, 9 ];
if ( $fh && !defined fileno($fh) ) {
return 1;
}
elsif ( !$filesys_lock_mtime ) {
_log_warn( 'Lock on ' . $lockref->get_path_to_file_being_locked() . ' lost!' );
$lockref->close();
return; # return false on false
}
elsif ( $lock_inode && ( $lock_inode == $filesys_lock_ino ) && $lock_path && ( $lock_mtime == $filesys_lock_mtime ) ) {
unlink $lock_path or do {
_log_warn("Could not unlink lock file “$lock_path” as ($>/$)): $!\n");
$lockref->close();
return; # return false on false
};
return $lockref->close();
}
$lockref->close();
my ( $lock_pid, $lock_name, $lock_obj ) = Cpanel::SafeFileLock::fetch_lock_contents_if_exists($lock_path);
if ($lock_pid) {
$lock_inode ||= 0;
$lock_mtime ||= 0;
_log_warn("[$$] Attempt to unlock file that was locked by another process [LOCK_PATH]=[$lock_path] [LOCK_PID]=[$lock_pid] [LOCK_PROCESS]=[$lock_name] [LOCK_INODE]=[$filesys_lock_ino] [LOCK_MTIME]=[$filesys_lock_mtime] -- [NON_LOCK_PID]=[$$] [NON_LOCK_PROCESS]=[$0] [NON_LOCK_INODE]=[$lock_inode] [NON_LOCK_MTIME]=[$lock_mtime]");
}
return;
}
sub _safe_open {
my ( undef, $open_mode, $file, $open_method_coderef, $open_method ) = @_;
if ( !defined $open_mode || !$open_method_coderef || !$file || $file =~ tr/\0// ) {
_log_warn('_safe_open: Invalid arguments');
return;
}
elsif ( defined $_[0] ) {
my $fh_type = ref $_[0];
if ( !Cpanel::FHUtils::Tiny::is_a( $_[0] ) ) {
_log_warn("Invalid file handle type '$fh_type' provided for $open_method of '$file'");
return;
}
}
if ( my $lockref = _safelock($file) ) {
if ( $open_method_coderef->( $_[0], $open_mode, $file ) ) {
if ( my $err = _do_flock_or_return_exception( $_[0], $open_mode, $file ) ) {
safeunlock($lockref);
local $@ = $err;
die;
}
$OPEN_LOCKS++;
return $lockref;
}
else {
local $!;
safeunlock($lockref);
return;
}
}
else {
_log_warn("safeopen: could not acquire a lock for '$file': $!");
return;
}
}
my $_lock_ex_nb;
my $_lock_sh_nb;
sub _do_flock_or_return_exception {
my ( $fh, $open_mode, $path ) = @_;
my $flock_start_time;
my $lock_op =
_is_write_open_mode($open_mode)
? ( $_lock_ex_nb //= $Cpanel::Fcntl::Constants::LOCK_EX | $Cpanel::Fcntl::Constants::LOCK_NB )
: ( $_lock_sh_nb //= $Cpanel::Fcntl::Constants::LOCK_SH | $Cpanel::Fcntl::Constants::LOCK_NB );
local $!;
my $flock_err;
my $flock_max_wait_time_is_whole_number = int($MAX_FLOCK_WAIT) == $MAX_FLOCK_WAIT;
while ( !flock $fh, $lock_op ) {
$flock_err = $!;
if ( $flock_err == _EINTR || $flock_err == _EWOULDBLOCK ) {
if ( !$flock_start_time ) {
$flock_start_time = $flock_max_wait_time_is_whole_number ? time() : Cpanel::TimeHiRes::time();
next;
}
if ( ( ( $flock_max_wait_time_is_whole_number ? time() : Cpanel::TimeHiRes::time() ) - $flock_start_time ) > $MAX_FLOCK_WAIT ) {
Cpanel::LoadModule::load_perl_module('Cpanel::Exception');
return _timeout_exception( $path, $MAX_FLOCK_WAIT );
}
else {
Cpanel::TimeHiRes::sleep($TIME_BETWEEN_FLOCK_CHECKS);
}
next;
}
Cpanel::LoadModule::load_perl_module('Cpanel::Exception');
return Cpanel::Exception::create( 'IO::FlockError', [ path => $path, error => $flock_err, operation => $lock_op ] );
}
return undef;
}
sub _safe_re_open {
my ( $fh, $open_mode, $file, $open_method_coderef, $open_method ) = @_;
if ( !defined $open_mode || !$open_method_coderef || !$file || $file =~ tr/\0// ) {
_log_warn('_safe_re_open: Invalid arguments');
return;
}
else {
my $fh_type = ref $fh;
if ( !Cpanel::FHUtils::Tiny::is_a($fh) ) {
_log_warn("Invalid file handle type '$fh_type' provided for $open_method of '$file'");
return;
}
}
close $fh;
if ( $open_method_coderef->( $fh, $open_mode, $file ) ) {
if ( my $err = _do_flock_or_return_exception( $fh, $open_mode, $file ) ) {
die $err;
}
return $fh;
}
return;
}
sub _log_warn {
Cpanel::LoadModule::load_perl_module('Cpanel::Debug');
goto &Cpanel::Debug::log_warn;
}
sub _get_open_args {
my ( $mode, $file ) = @_;
if ( !$file ) {
( $mode, $file ) = $mode =~ m/^([<>+|]+|)(.*)/;
if ( $file && !$mode ) {
$mode = '<';
}
elsif ( !$file ) {
return;
}
}
$mode =
$mode eq '<' ? '<'
: $mode eq '>' ? '>'
: $mode eq '>>' ? '>>'
: $mode eq '+<' ? '+<'
: $mode eq '+>' ? '+>'
: $mode eq '+>>' ? '+>>'
: return;
return ( $mode, $file );
}
sub _sanitize_open_mode {
my ($mode) = @_;
return if $mode =~ m/[^0-9]/;
my $safe_mode = ( $mode & $Cpanel::Fcntl::Constants::O_RDONLY );
$safe_mode |= ( $mode & $Cpanel::Fcntl::Constants::O_WRONLY );
$safe_mode |= ( $mode & $Cpanel::Fcntl::Constants::O_RDWR );
$safe_mode |= ( $mode & $Cpanel::Fcntl::Constants::O_CREAT );
$safe_mode |= ( $mode & $Cpanel::Fcntl::Constants::O_EXCL );
$safe_mode |= ( $mode & $Cpanel::Fcntl::Constants::O_APPEND );
$safe_mode |= ( $mode & $Cpanel::Fcntl::Constants::O_TRUNC );
$safe_mode |= ( $mode & $Cpanel::Fcntl::Constants::O_NONBLOCK );
return $safe_mode;
}
sub _calculate_lockfile { ## no critic qw(Subroutines::RequireArgUnpacking)
my $lockfile = $_[0] =~ tr{<>}{} ? ( ( $_[0] =~ /^[><]*(.*)/ )[0] . '.lock' ) : $_[0] . '.lock';
return $lockfile if ( length $lockfile <= MAX_LOCK_FILE_LENGTH );
require File::Basename;
my $lock_basename = File::Basename::basename($lockfile);
return $lockfile if ( length $lock_basename <= MAX_LOCK_FILE_LENGTH );
require Cpanel::Hash;
my $hashed_lock_basename = Cpanel::Hash::get_fastest_hash($lock_basename) . ".lock";
if ( $lockfile eq $lock_basename ) {
return $hashed_lock_basename;
}
else {
return File::Basename::dirname($lockfile) . '/' . $hashed_lock_basename;
}
}
sub is_locked {
my ($file) = @_;
my $lockfile = _calculate_lockfile($file);
my ( $lock_pid, $lock_name, $lock_obj ) = Cpanel::SafeFileLock::fetch_lock_contents_if_exists($lockfile);
if ( _is_valid_pid($lock_pid) && _pid_is_alive($lock_pid) ) {
return 1;
}
return 0;
}
sub _timeout_exception {
my ( $path, $waited ) = @_;
Cpanel::LoadModule::load_perl_module('Cpanel::Exception');
return Cpanel::Exception::create( 'Timeout', 'The system failed to lock the file “[_1]” after [quant,_2,second,seconds].', [ $path, $waited ] );
}
sub _die_if_file_is_flocked_cuz_already_waited_a_while {
my ( $file, $waited ) = @_;
if ( _open_to_write( my $fh, $file ) ) {
$_lock_ex_nb //= $Cpanel::Fcntl::Constants::LOCK_EX | $Cpanel::Fcntl::Constants::LOCK_NB;
if ( flock( $fh, $_lock_ex_nb ) == 1 ) {
flock $fh, UNLOCK_FCNTL_VALUE or die "Failed to unlock “$file” after having just locked it: $!";
}
else {
Cpanel::LoadModule::load_perl_module('Cpanel::Exception');
if ( $! == _EWOULDBLOCK ) {
die _timeout_exception( $file, $waited );
}
else {
die Cpanel::Exception::create( 'IO::FlockError', [ path => $file, error => $!, operation => $_lock_ex_nb ] );
}
}
}
return;
}
sub _lock_wait { ## no critic qw(Subroutines::ProhibitExcessComplexity)
my ( $file, $safefile_lock, $lockfile ) = @_;
my ( $temp_file, $fh ) = _write_temp_lock_file( $lockfile, $file );
if ( $temp_file eq NO_PERM_TO_WRITE_TO_DOTLOCK_DIR ) {
return ( 1, 1 );
}
if ( !$temp_file ) {
return ( 0, $fh );
}
$safefile_lock->set_filehandle_and_unlinker_after_lock( $fh, Cpanel::SafeFile::_temp->new($temp_file) );
return ( 1, $fh ) if _try_to_install_lockfile( $temp_file, $lockfile );
local $0 = ( $verbose == 1 ) ? "$0 - waiting for lock on $file" : "$0 - waiting for lock";
Cpanel::LoadModule::load_perl_module('Cpanel::SafeFile::LockInfoCache');
Cpanel::LoadModule::load_perl_module('Cpanel::SafeFile::LockWatcher');
my $watcher = Cpanel::SafeFile::LockWatcher->new($lockfile);
my $waittime = _calculate_waittime_for_file($file);
my ( $inotify_obj, $inotify_mask, $inotify_file_disappeared );
my $start_time = time;
my $waited = 0;
my $lockfile_cache = Cpanel::SafeFile::LockInfoCache->new($lockfile);
my ( $inotify_inode, $inotify_mtime );
LOCK_WAIT:
while (1) {
$waited = ( time() - $start_time );
if ( $waited > $waittime ) {
_die_if_file_is_flocked_cuz_already_waited_a_while( $file, $waited );
if ( defined $watcher->{'inode'} ) {
Cpanel::LoadModule::load_perl_module('Cpanel::Debug');
Cpanel::Debug::log_warn( sprintf "Replacing stale lock file: $lockfile. The kernel’s lock is gone, last modified %s seconds ago (mtime=$watcher->{'mtime'}), and waited over $waittime seconds.", time - $watcher->{'mtime'} );
}
return ( 1, $fh ) if _overwrite_lockfile_if_inode_mtime_matches( $temp_file, $lockfile, $watcher->{'inode'}, $watcher->{'mtime'} );
die _timeout_exception( $file, $waittime );
}
if ( $watcher->{'inode'} ) {
my $lock_get = $lockfile_cache->get( @{$watcher}{ 'inode', 'mtime' } );
if ( !$lock_get ) {
my $size_before_reload = $watcher->{'size'};
$watcher->reload_from_disk();
if ( $size_before_reload == 0 && $watcher->{'size'} == 0 ) {
_log_warn("[$$] UID $> clobbering empty lock file “$lockfile” (UID $watcher->{'uid'}) written by “unknown” at $watcher->{'mtime'}");
return ( 1, $fh ) if _overwrite_lockfile_if_inode_mtime_matches( $temp_file, $lockfile, $watcher->{'inode'}, $watcher->{'mtime'} );
}
next LOCK_WAIT;
}
my ( $lock_pid, $lock_name, $lock_obj ) = @$lock_get;
if ( $lock_pid == $$ ) {
$watcher->reload_from_disk();
_log_warn("[$$] Double locking detected by self [LOCK_PATH]=[$lockfile] [LOCK_PID]=[$lock_pid] [LOCK_OBJ]=[$lock_obj] [LOCK_PROCESS]=[$lock_name] [ACTUAL_INODE]=[$watcher->{'inode'}] [ACTUAL_MTIME]=[$watcher->{'mtime'}]");
return ( 0, $DOUBLE_LOCK_DETECTED );
}
elsif ( !_pid_is_alive($lock_pid) ) {
my $time = time();
if ( _overwrite_lockfile_if_inode_mtime_matches( $temp_file, $lockfile, $watcher->{'inode'}, $watcher->{'mtime'} ) ) {
_log_warn("[$$] TIME $time UID $> clobbered stale lock file “$lockfile” (NAME “$lock_name”, UID $watcher->{'uid'}) written by PID $lock_pid at $watcher->{'mtime'}");
return ( 1, $fh );
}
$watcher->reload_from_disk();
next LOCK_WAIT;
}
else {
Cpanel::LoadModule::load_perl_module('Cpanel::Debug');
Cpanel::Debug::log_info("[$$] Waiting for lock on $file held by $lock_name with pid $lock_pid") if $verbose == 1;
}
}
return ( 1, $fh ) if _try_to_install_lockfile( $temp_file, $lockfile );
$watcher->reload_from_disk();
if ( !$inotify_obj || !$inotify_inode || !$watcher->{'inode'} || $inotify_inode != $watcher->{'inode'} || $inotify_mtime != $watcher->{'mtime'} ) {
INOTIFY: {
( $inotify_obj, $inotify_mask, $inotify_file_disappeared ) = _generate_inotify_for_lock_file($lockfile);
$watcher->reload_from_disk();
if ( $inotify_file_disappeared || !$watcher->{'inode'} ) {
undef $inotify_obj;
next LOCK_WAIT;
}
redo INOTIFY if $watcher->{'changed'};
( $inotify_inode, $inotify_mtime ) = @{$watcher}{ 'inode', 'mtime' };
}
}
my $selected = _select( my $m = $inotify_mask, undef, undef, $TIME_BETWEEN_DOTLOCK_CHECKS );
if ( $selected == -1 ) {
die "select() error: $!" if $! != _EINTR();
}
elsif ($selected) {
return ( 1, $fh ) if _try_to_install_lockfile( $temp_file, $lockfile );
$watcher->reload_from_disk();
() = $inotify_obj->poll();
}
}
return;
}
sub _select {
return select( $_[0], $_[1], $_[2], $_[3] );
}
sub _generate_inotify_for_lock_file {
my ($file) = @_;
Cpanel::LoadModule::load_perl_module('Cpanel::Inotify');
my $inotify_obj;
my $rin = '';
local $@;
eval {
$inotify_obj = Cpanel::Inotify->new( flags => ['NONBLOCK'] );
$inotify_obj->add( $file, flags => [ 'ATTRIB', 'DELETE_SELF' ] );
vec( $rin, $inotify_obj->fileno(), 1 ) = 1;
};
if ($@) {
my $err = $@;
if ( eval { $err->isa('Cpanel::Exception::SystemCall') } ) {
my $err = $err->get('error');
if ( $err == _ENOENT ) {
return ( undef, undef, INOTIFY_FILE_DISAPPEARED );
}
elsif ( $err != _EACCES ) { # Don’t warn if EACCES
local $@ = $err;
warn;
}
}
else {
local $@ = $err;
warn;
}
return;
}
return ( $inotify_obj, $rin, 0 );
}
sub _pid_is_alive {
my ($pid) = @_;
local $!;
if ( kill( 0, $pid ) ) {
return 1;
}
elsif ( $! == _EPERM ) {
return !!( stat "/proc/$pid" )[0];
}
return 0;
}
sub _calculate_waittime_for_file {
my ($file) = @_;
return $LOCK_WAIT_TIME if $LOCK_WAIT_TIME;
my $waittime = DEFAULT_LOCK_WAIT_TIME;
if ( -e $file ) {
$waittime = int( ( stat _ )[7] / 10000 );
$waittime = $waittime > MAX_LOCK_WAIT_TIME ? MAX_LOCK_WAIT_TIME : $waittime < DEFAULT_LOCK_WAIT_TIME ? DEFAULT_LOCK_WAIT_TIME : $waittime;
}
return $waittime;
}
sub _is_valid_pid {
my $pid = shift;
return 0 unless defined $pid;
return $pid =~ tr{0-9}{}c ? 0 : 1;
}
sub _getdir {
my @path = split( /\/+/, $_[0] );
return join( '/', (@path)[ 0 .. ( $#path - 1 ) ] ) || '.';
}
sub _create_lockfile {
my $lock_fh;
return sysopen( $lock_fh, $_[0], CREATE_FCNTL_VALUE, LOCK_FILE_PERMS ) ? ( 1, $lock_fh ) : ( 0, $! );
}
sub _open_to_write {
my $path = $_[1];
$OVERWRITE_FCNTL_VALUE ||= ( $Cpanel::Fcntl::Constants::O_WRONLY | $Cpanel::Fcntl::Constants::O_NONBLOCK | $Cpanel::Fcntl::Constants::O_APPEND | $Cpanel::Fcntl::Constants::O_NOFOLLOW );
return sysopen( $_[0], $path, $OVERWRITE_FCNTL_VALUE, LOCK_FILE_PERMS );
}
sub _overwrite_lockfile_if_inode_mtime_matches {
my ( $temp_file, $lockfile, $lockfile_inode, $lockfile_mtime ) = @_;
my ( $inode, $mtime ) = ( stat $lockfile )[ 1, 9 ];
if ( !$inode ) {
die "stat($lockfile): $!" if $! != _ENOENT();
}
if ( !$inode || ( $inode == $lockfile_inode && $mtime == $lockfile_mtime ) ) {
rename( $temp_file, $lockfile ) or do {
Cpanel::LoadModule::load_perl_module('Cpanel::Exception');
die Cpanel::Exception::create( 'IO::RenameError', [ oldpath => $temp_file, newpath => $lockfile, error => $! ] );
};
return 1;
}
return 0;
}
sub _is_write_open_mode {
my ($mode) = @_;
if ( $mode =~ tr{0-9}{}c ) {
if ( $mode && ( -1 != index( $mode, '>' ) || -1 != index( $mode, '+' ) ) ) {
return 1;
}
}
else {
if ( $mode && ( ( $mode & $Cpanel::Fcntl::Constants::O_WRONLY ) || ( $mode & $Cpanel::Fcntl::Constants::O_RDWR ) ) ) {
return 1;
}
}
return 0;
}
sub _verbose_flag_file_exists {
return -e '/var/cpanel/safefile_verbose';
}
package Cpanel::SafeFile::_temp;
use constant _ENOENT => 2;
sub new { return bless [ $_[1], $_SKIP_DOTLOCK_WHEN_NO_PERMS, $$ ], $_[0]; }
sub DESTROY {
local $!;
unlink $_[0]->[0] or do {
if ( !$_[0]->[1] && $! != _ENOENT && $_[0]->[2] == $$ ) {
warn "unlink($_[0]->[0]): $!";
}
};
return;
}
1;
} # --- END Cpanel/SafeFile.pm
{ # --- BEGIN Cpanel/Linux/Constants.pm
package Cpanel::Linux::Constants;
use strict;
use warnings;
use constant {
NAME_MAX => 255,
PATH_MAX => 4096,
};
1;
} # --- END Cpanel/Linux/Constants.pm
{ # --- BEGIN Cpanel/Validate/FilesystemNodeName.pm
package Cpanel::Validate::FilesystemNodeName;
use strict;
use Try::Tiny;
# use Cpanel::Exception ();
# use Cpanel::Linux::Constants ();
sub is_valid {
my ($node) = @_;
my $err;
try {
validate_or_die($node);
}
catch {
$err = $_;
};
return $err ? 0 : 1;
}
sub validate_or_die {
my ($name) = @_;
if ( !length $name ) {
die Cpanel::Exception::create('Empty');
}
elsif ( $name eq '.' || $name eq '..' ) {
die Cpanel::Exception::create( 'Reserved', [ value => $name ] );
}
elsif ( length $name > Cpanel::Linux::Constants::NAME_MAX() ) {
die Cpanel::Exception::create( 'TooManyBytes', [ value => $name, maxlength => Cpanel::Linux::Constants::NAME_MAX() ] );
}
elsif ( index( $name, '/' ) != -1 ) {
die Cpanel::Exception::create( 'InvalidCharacters', [ value => $name, invalid_characters => ['/'] ] );
}
elsif ( index( $name, "\0" ) != -1 ) {
die Cpanel::Exception::create( 'InvalidCharacters', 'This value may not contain a [asis,NUL] byte.', [ value => $name, invalid_characters => ["\0"] ] );
}
return 1;
}
1;
} # --- END Cpanel/Validate/FilesystemNodeName.pm
{ # --- BEGIN Cpanel/Debug.pm
package Cpanel::Debug;
use strict;
use warnings;
our $HOOKS_DEBUG_FILE = '/var/cpanel/debughooks';
our $level = ( exists $ENV{'CPANEL_DEBUG_LEVEL'} && $ENV{'CPANEL_DEBUG_LEVEL'} ? int $ENV{'CPANEL_DEBUG_LEVEL'} : 0 );
my $debug_hooks_value;
my $logger;
sub logger {
$logger = shift if (@_); # Set method for $logger if something is passed in.
return $logger ||= do {
local ( $@, $! );
require Cpanel::Logger;
Cpanel::Logger->new();
};
}
sub log_error {
local $!; #prevent logger from overwriting $!
return logger()->error( $_[0] );
}
sub log_warn {
local $!; #prevent logger from overwriting $!
return logger()->warn( $_[0] );
}
sub log_invalid {
local $!; #prevent logger from overwriting $!
return logger()->invalid( $_[0] );
}
sub log_deprecated {
local $!; #prevent logger from overwriting $!
return logger()->deprecated( $_[0] );
}
sub log_panic {
local $!; #prevent logger from overwriting $!
return logger()->panic( $_[0] );
}
sub log_die {
local $!; #prevent logger from overwriting $!
return logger()->die( $_[0] );
}
sub log_info {
local $!; #prevent logger from overwriting $!
return logger()->info( $_[0] );
}
sub log_debug {
local $!; #prevent logger from overwriting $!
return logger()->debug( $_[0] );
}
sub debug_hooks_value {
return $debug_hooks_value if defined $debug_hooks_value;
return ( $debug_hooks_value = ( stat($HOOKS_DEBUG_FILE) )[7] || 0 );
}
1;
} # --- END Cpanel/Debug.pm
{ # --- BEGIN Cpanel/Notify.pm
package Cpanel::Notify;
use strict;
use warnings;
# use Cpanel::Fcntl ();
# use Cpanel::SafeFile ();
# use Cpanel::LoadModule ();
# use Cpanel::Validate::FilesystemNodeName ();
# use Cpanel::Exception ();
# use Cpanel::Debug ();
our $VERSION = '1.8';
my $DEFAULT_CONTENT_TYPE = 'text/plain; charset=utf-8';
our $NOTIFY_INTERVAL_STORAGE_DIR = '/var/cpanel/notifications';
sub notification_class {
my (%args) = @_;
if ( !defined $args{'interval'} ) {
$args{'interval'} = 1;
}
if ( !defined $args{'status'} ) {
$args{'status'} = 'No status set';
}
foreach my $param (qw(application status class constructor_args)) {
die Cpanel::Exception::create( 'MissingParameter', [ 'name' => $param ] ) if !defined $args{$param};
}
my $constructor_args = { @{ $args{'constructor_args'} } };
if ( $constructor_args->{'skip_send'} ) {
my $class = "Cpanel::iContact::Class::$args{'class'}";
Cpanel::LoadModule::load_perl_module($class);
return $class->new(%$constructor_args);
}
return _notification_backend(
$args{'application'},
$args{'status'},
$args{'interval'},
sub {
my $class = "Cpanel::iContact::Class::$args{'class'}";
Cpanel::LoadModule::load_perl_module($class);
return $class->new(%$constructor_args);
},
);
}
sub notification {
my %AGS = @_;
my $app = $AGS{'app'} || $AGS{'application'} || 'Notice';
return _notification_backend(
$app,
$AGS{'status'},
$AGS{'interval'} || 0,
sub {
my $module = "Cpanel::iContact";
Cpanel::LoadModule::load_perl_module($module);
my $from = $AGS{'from'};
my $to = $AGS{'to'};
my $msgheader = $AGS{'msgheader'} || $AGS{'subject'};
my $message = $AGS{'message'};
my $plaintext_message = $AGS{'plaintext_message'};
my $priority = $AGS{'priority'} || 3;
my $attach_files = $AGS{'attach_files'} || [];
my $content_type = $AGS{'content-type'} || $DEFAULT_CONTENT_TYPE;
"$module"->can('icontact')->(
'attach_files' => $attach_files,
'application' => $app,
'level' => $priority,
'from' => $from,
'to' => $to,
'subject' => $msgheader,
'message' => $message,
'plaintext_message' => $plaintext_message,
'content-type' => $content_type,
);
}
);
}
sub _notification_backend {
my ( $app, $status, $interval, $todo_cr ) = @_;
my $is_ready = _checkstatusinterval(
'app' => $app,
'status' => $status,
'interval' => $interval,
);
if ($is_ready) {
return $todo_cr->();
}
elsif ( $Cpanel::Debug::level > 3 ) {
Cpanel::Debug::log_warn("not sending notify app=[$app] status=[$status] interval=[$interval]");
}
return $is_ready ? 1 : 0;
}
sub notify_blocked {
my %AGS = @_;
my $app = $AGS{'app'};
my $status = $AGS{'status'};
my $interval = $AGS{'interval'};
return 0 if $interval <= 1; # Special Case (ignore interval check);
$app =~ s{/}{_}g; # Its possible to have slashes in the app name
$status =~ s{:}{_}g; # Its possible to have colons in the status
my $db_file = "$NOTIFY_INTERVAL_STORAGE_DIR/$app";
return 0 if !-e $db_file;
my %notifications;
my $notify_db_fh;
if (
my $nlock = Cpanel::SafeFile::safesysopen(
$notify_db_fh, $db_file, Cpanel::Fcntl::or_flags('O_RDONLY'),
0600
)
) {
local $/;
%notifications = map { ( split( /:/, $_, 2 ) )[ 0, 1 ] } split( m{\n}, readline($notify_db_fh) );
Cpanel::SafeFile::safeclose( $notify_db_fh, $nlock );
}
else {
Cpanel::Debug::log_warn("Could not open $db_file: $!");
return;
}
if ( $notifications{$status} && ( ( $notifications{$status} + $interval ) > time() ) ) {
return 1;
}
return 0;
}
{
no warnings 'once';
*update_notification_time_if_interval_reached = \&_checkstatusinterval;
}
sub _checkstatusinterval {
my %AGS = @_;
my $app = $AGS{'app'};
my $status = $AGS{'status'};
my $interval = $AGS{'interval'};
return 1 if $interval <= 1; # Special Case (ignore interval check);
$app =~ s{/}{_}g; # Its possible to have slashes in the app name
$status =~ s{:}{_}g; # Its possible to have colons in the status
Cpanel::Validate::FilesystemNodeName::validate_or_die($app);
my $notify = 0;
if ( !-e $NOTIFY_INTERVAL_STORAGE_DIR ) {
Cpanel::LoadModule::load_perl_module('Cpanel::SafeDir::MK');
Cpanel::SafeDir::MK::safemkdir( $NOTIFY_INTERVAL_STORAGE_DIR, '0700' );
if ( !-d $NOTIFY_INTERVAL_STORAGE_DIR ) {
Cpanel::Debug::log_warn("Failed to setup notifications directory: $NOTIFY_INTERVAL_STORAGE_DIR: $!");
return;
}
}
my %notifications;
my $notify_db_fh;
my $db_file = "$NOTIFY_INTERVAL_STORAGE_DIR/$app";
if ( my $nlock = Cpanel::SafeFile::safesysopen( $notify_db_fh, $db_file, Cpanel::Fcntl::or_flags(qw( O_RDWR O_CREAT )), 0600 ) ) {
local $/;
%notifications = map { ( split( /:/, $_, 2 ) )[ 0, 1 ] } split( m{\n}, readline($notify_db_fh) );
if ( !exists $notifications{$status} || ( int( $notifications{$status} ) + int($interval) ) < time() ) {
$notifications{$status} = time;
$notify = 1;
}
seek( $notify_db_fh, 0, 0 );
print {$notify_db_fh} join( "\n", map { $_ . ':' . $notifications{$_} } sort keys %notifications );
truncate( $notify_db_fh, tell($notify_db_fh) );
Cpanel::SafeFile::safeclose( $notify_db_fh, $nlock );
}
else {
Cpanel::Debug::log_warn("Could not open $db_file: $!");
return;
}
return $notify;
}
1;
} # --- END Cpanel/Notify.pm
{ # --- BEGIN Cpanel/Server/Utils.pm
package Cpanel::Server::Utils;
use strict;
sub is_subprocess_of_cpsrvd {
return 0 if $INC{'cpanel/cpsrvd.pm'}; # If we ARE cpsrvd we do not want this behavior
return $ENV{'CPANEL'} ? 1 : 0;
}
1;
} # --- END Cpanel/Server/Utils.pm
{ # --- BEGIN Cpanel/Logger.pm
package Cpanel::Logger;
use strict;
# use Cpanel::Time::Local ();
my $is_sandbox;
my $is_smoker;
our $VERSION = 1.3;
our $ENABLE_BACKTRACE = 1;
our $DISABLE_OUPUT; # used by cpanminus
our $ALWAYS_OUTPUT_TO_STDERR;
our $STD_LOG_FILE = '/usr/local/cpanel/logs/error_log';
our $PANIC_LOG_FILE = '/usr/local/cpanel/logs/panic_log';
my ( $cached_progname, $cached_prog_pid, %singleton_stash );
sub new {
my ( $class, $hr_args ) = @_;
if ( $hr_args->{'open_now'} && $hr_args->{'use_no_files'} ) {
die "“open_now” and “use_no_files” mutually exclude!";
}
my $args_sig = 'no_args';
if ( $hr_args && ref($hr_args) eq 'HASH' ) {
$args_sig = join( ',', map { $_ . '=>' . $hr_args->{$_} } sort keys %{$hr_args} ); # Storable::freeze($hr_args);
}
my $no_load_from_cache = $hr_args->{'no_load_from_cache'} ? 1 : 0;
if ( exists $singleton_stash{$class}{$args_sig} and !$no_load_from_cache ) {
$singleton_stash{$class}{$args_sig}->{'cloned'}++;
}
else {
$singleton_stash{$class}{$args_sig} = bless( {}, $class );
if ( $hr_args && ref($hr_args) eq 'HASH' ) {
foreach my $k ( keys %$hr_args ) {
$singleton_stash{$class}{$args_sig}->{$k} = $hr_args->{$k};
}
}
}
my $self = $singleton_stash{$class}{$args_sig};
if ( !$self->{'cloned'} ) {
if ( $self->{'open_now'} && !$self->{'use_no_files'} ) {
$self->_open_logfile();
}
}
return $self;
}
sub __Logger_pushback {
if ( @_ && index( ref( $_[0] ), __PACKAGE__ ) == 0 ) {
return @_;
}
return ( __PACKAGE__->new(), @_ );
}
sub invalid {
my ( $self, @list ) = __Logger_pushback(@_);
my %log = (
'message' => $list[0],
'level' => 'invalid',
'output' => 0,
'service' => $self->find_progname(),
'backtrace' => $ENABLE_BACKTRACE,
'die' => 0,
);
if ( is_sandbox() ) {
if ( !-e '/var/cpanel/DEBUG' ) {
$self->notify( 'invalid', \%log );
}
$log{'output'} = _stdin_is_tty() ? 2 : 1;
}
return $self->logger( \%log );
} # end of invalid
sub is_sandbox {
return 0 if $INC{'B/C.pm'}; # avoid cache during compile
return $is_sandbox if defined $is_sandbox;
return ( $is_sandbox = -e '/var/cpanel/dev_sandbox' ? 1 : 0 );
}
sub is_smoker {
return 0 if $INC{'B/C.pm'}; # avoid cache during compile
return $is_smoker if defined $is_smoker;
return ( $is_smoker = -e '/var/cpanel/smoker' ? 1 : 0 );
}
sub deprecated { ## no critic qw(Subroutines::RequireArgUnpacking)
my ( $self, @list ) = __Logger_pushback(@_);
my %log = (
'message' => $list[0],
'level' => 'deprecated',
'output' => 0,
'service' => $self->find_progname(),
'backtrace' => $ENABLE_BACKTRACE,
'die' => 0,
);
unless ( is_sandbox() ) {
$self->logger( \%log );
return;
}
$self->notify( 'deprecated', \%log );
$log{'output'} = _stdin_is_tty() ? 2 : 1;
$log{'die'} = 1;
return $self->logger( \%log );
}
sub debug {
my ( $self, $message, $conf_hr ) = @_; # not appropriate for debug() : __Logger_pushback(@_);
$self = $self->new() if !ref $self;
$conf_hr ||= {
'force' => 0,
'backtrace' => 0,
'output' => 1, # Logger's debug level should output to STDOUT
};
return unless $conf_hr->{'force'} || ( defined $Cpanel::Debug::level && $Cpanel::Debug::level ); ## PPI NO PARSE - avoid recursive use statements
if ( !defined $message ) {
my @caller = caller();
$message = "debug() at $caller[1] line $caller[2].";
}
my %log = (
'message' => $message,
'level' => 'debug',
'output' => $conf_hr->{'output'},
'backtrace' => $conf_hr->{'backtrace'},
);
if ( ref $log{'message'} ) {
my $outmsg = $log{'message'};
eval 'local $SIG{__DIE__}; local $SIG{__WARN__}; require Cpanel::YAML::Syck; $outmsg = YAML::Syck::Dump($outmsg);';
my @caller = caller();
$log{'message'} = "$log{'message'} at $caller[1] line $caller[2]:\n" . $outmsg;
}
elsif ( $log{'message'} =~ m/\A\d+(?:\.\d+)?\z/ ) {
$log{'message'} = "debug() number $log{'message'}";
}
$self->logger( \%log );
return \%log;
}
sub info {
my ( $self, @list ) = __Logger_pushback(@_);
return $self->logger(
{
'message' => $list[0],
'level' => 'info',
'output' => $self->{'open_now'} ? 0 : 1, # FB#59177: info level should output to STDOUT
'backtrace' => 0
}
);
} # end of info
sub warn {
my ( $self, @list ) = __Logger_pushback(@_);
return $self->logger(
{
'message' => $list[0],
'level' => 'warn',
'output' => _stdin_is_tty() ? 2 : 0,
'backtrace' => $ENABLE_BACKTRACE
}
);
} # end of warn
sub error {
my ( $self, @list ) = __Logger_pushback(@_);
return $self->logger(
{
'message' => $list[0],
'level' => 'error',
'output' => -t STDIN ? 2 : 0,
'backtrace' => $ENABLE_BACKTRACE
}
);
} # end of error
sub die {
my ( $self, @list ) = __Logger_pushback(@_);
my %log = (
'message' => $list[0],
'level' => 'die',
'output' => _stdin_is_tty() ? 2 : 0,
'backtrace' => $ENABLE_BACKTRACE
);
return $self->logger( \%log );
} # end of die
sub panic {
my ( $self, @list ) = __Logger_pushback(@_);
my %log = (
'message' => $list[0],
'level' => 'panic',
'output' => 2,
'backtrace' => $ENABLE_BACKTRACE
);
return $self->logger( \%log );
} # end of panic
sub raw {
return $_[0]->logger(
{
'message' => $_[1],
'level' => 'raw',
'output' => 0,
'backtrace' => 0
}
);
}
sub cplog {
my $msg = shift;
my $loglevel = shift;
my $service = shift;
my $nostdout = shift;
if ( !$nostdout ) {
$nostdout = 1;
}
else {
$nostdout = 0;
}
logger( { 'message' => $msg, 'level' => $loglevel, 'service' => $service, 'output' => $nostdout, 'backtrace' => $ENABLE_BACKTRACE } );
} # end of cplog (deprecated)
sub _get_configuration_for_logger {
my ( $self, $cfg_or_msg ) = @_;
my $hr = ref($cfg_or_msg) eq 'HASH' ? $cfg_or_msg : { 'message' => $cfg_or_msg };
$hr->{'message'} ||= 'Something is wrong';
$hr->{'level'} ||= '';
$hr->{'output'} ||= 0;
$hr->{'output'} = 0 if $DISABLE_OUPUT;
if ( !exists $hr->{'backtrace'} ) {
$hr->{'backtrace'} = $ENABLE_BACKTRACE;
}
$hr->{'use_no_files'} ||= 0;
$hr->{'use_fullmsg'} ||= 0;
return $hr;
}
sub _write {
return print { $_[0] } $_[1];
}
sub get_fh {
my ($self) = @_;
return $self->{'log_fh'};
}
sub set_fh {
my ( $self, $fh ) = @_;
$self->{'log_fh'} = $fh;
return 1;
}
sub logger { ## no critic(RequireArgUnpacking)
my ( $self, @list ) = __Logger_pushback(@_);
my $hr = $self->_get_configuration_for_logger( $list[0] );
my ( $msg, $time, $status );
$status = 1;
my ($msg_maybe_bt) = $hr->{'backtrace'} ? $self->backtrace( $hr->{'message'} ) : $hr->{'message'} . "\n";
if ( $hr->{'level'} eq 'raw' ) {
$msg = $hr->{'message'};
}
else {
$time ||= Cpanel::Time::Local::localtime2timestamp();
$hr->{'service'} ||= $self->find_progname(); # only compute the service name if we HAVE to do so as it can be expensive
if ( $self->{'log_pid'} ) {
$msg = "[$time] $hr->{'level'} [$hr->{'service'}] [$$] $msg_maybe_bt";
}
else {
$msg = "[$time] $hr->{'level'} [$hr->{'service'}] $msg_maybe_bt";
}
}
unless ( $hr->{'use_no_files'} ) {
local $self->{'log_fh'} = \*STDERR if $ALWAYS_OUTPUT_TO_STDERR;
$self->_open_logfile() if !$self->{'log_fh'} || ( !eval { fileno( $self->{'log_fh'} ) } && !UNIVERSAL::isa( $self->{'log_fh'}, 'IO::Scalar' ) );
_write( $self->{'log_fh'}, $msg ) or $status = 0;
if ( $hr->{'level'} eq 'panic' || $hr->{'level'} eq 'invalid' || $hr->{'level'} eq 'deprecated' ) {
my $panic_fh;
require Cpanel::FileUtils::Open;
if ( Cpanel::FileUtils::Open::sysopen_with_real_perms( $panic_fh, $PANIC_LOG_FILE, 'O_WRONLY|O_APPEND|O_CREAT', 0600 ) ) {
$time ||= Cpanel::Time::Local::localtime2timestamp();
$hr->{'service'} ||= $self->find_progname(); # only compute the service name if we HAVE to do so as it can be expensive
_write( $panic_fh, "$time $hr->{level} [$hr->{'service'}] $msg_maybe_bt" );
close $panic_fh;
}
}
}
if ( $hr->{'output'} ) {
$hr->{'service'} ||= $self->find_progname(); # only compute the service name if we HAVE to do so as it can be expensive
my $out = "$hr->{level} [$hr->{'service'}] $hr->{'message'}\n";
if ( $self->{'timestamp_prefix'} ) {
$out = "[$time] $out";
}
$out = $msg if $hr->{'use_fullmsg'};
$status &&= $self->_write_message( $hr, $out );
}
if ( ( $hr->{'level'} eq 'die' || $hr->{'level'} eq 'panic' || $hr->{'die'} ) ) {
CORE::die "exit level [$hr->{'level'}] [pid=$$] ($hr->{'message'})\n"; # make sure we die if die is overwritten
}
return $status;
} # end of logger
sub _write_message {
my ( $self, $hr, $out ) = @_;
my $status = 1;
if ( $hr->{'output'} == 3 ) {
_write( \*STDOUT, $out ) or $status = 0;
_write( \*STDERR, $out ) or $status = 0;
}
elsif ( $hr->{'output'} == 1 && ( $self->{'use_stdout'} || _stdout_is_tty() ) ) {
_write( \*STDOUT, $out ) or $status = 0;
}
elsif ( $hr->{'output'} == 2 ) {
_write( \*STDERR, $out ) or $status = 0;
}
return $status;
}
sub find_progname {
if ( $cached_progname && $cached_prog_pid == $$ ) {
return $cached_progname;
}
my $s = $0;
if ( !length $s ) { # Someone _could_ set $0 = '';
my $i = 1; # 0 is always find_progname
while ( my @service = caller( $i++ ) ) {
last if ( $service[3] =~ /::BEGIN$/ );
$s = $service[1] if ( $service[1] ne '' );
}
}
$s =~ s@.+/(.+)$@$1@ if $s =~ tr{/}{};
$s =~ s@\..+$@@ if $s =~ tr{\.}{};
$s =~ s@ .*$@@ if $s =~ tr{ }{};
$cached_progname = $s;
$cached_prog_pid = $$;
return $s;
}
sub backtrace { ## no critic qw(Subroutines::RequireArgUnpacking)
my ( $self, @list ) = __Logger_pushback(@_);
if ( ref $list[0] ) {
return $list[0] if scalar @list == 1;
return (@list);
}
require Cpanel::Carp;
local $_; # Protect surrounding program - just in case...
local $Carp::Internal{ (__PACKAGE__) } = 1;
local $Carp::Internal{'Cpanel::Debug'} = 1;
return Cpanel::Carp::safe_longmess(@list);
}
sub redirect_stderr_to_error_log {
return open( STDERR, '>>', $STD_LOG_FILE );
}
sub notify {
my ( $self, $call, $log_ref ) = @_;
my $time = Cpanel::Time::Local::localtime2timestamp();
my ($bt) = $self->backtrace( $log_ref->{'message'} );
$log_ref->{'service'} //= '';
my $logfile = qq{$time [$log_ref->{'service'}] } . ( $bt // '' );
if ( eval { require Cpanel::LoadModule; Cpanel::LoadModule::load_perl_module('Cpanel::iContact::Class::Logger::Notify'); 1; } ) {
eval {
require Cpanel::Notify;
Cpanel::Notify::notification_class(
'class' => 'Logger::Notify',
'application' => 'Logger::Notify',
'constructor_args' => [
'origin' => $log_ref->{'service'},
'logger_call' => $call,
'attach_files' => [ { name => 'cpanel-logger-log.txt', content => \$logfile } ],
'subject' => $log_ref->{'subject'},
]
);
};
}
elsif ( eval { require Cpanel::LoadModule; Cpanel::LoadModule::load_perl_module('Cpanel::iContact'); 1; } ) {
Cpanel::iContact::icontact(
'application' => $log_ref->{'service'},
'subject' => $log_ref->{'subject'} ? $log_ref->{'subject'} : qq{Cpanel::Logger::$call called in $log_ref->{'service'}},
'message' => $logfile,
);
}
else {
CORE::warn( $log_ref->{'subject'} ? $log_ref->{'subject'} : qq{Cpanel::Logger::$call called in $log_ref->{'service'}} . ": $logfile" );
}
return;
}
*fatal = *die;
*out = *info;
*success = *info;
*throw = *die;
*warning = *warn;
sub _is_subprocess_of_cpsrvd {
require Cpanel::Server::Utils;
goto \&Cpanel::Server::Utils::is_subprocess_of_cpsrvd;
}
sub _open_logfile {
my ($self) = @_;
my $usingstderr = 0;
my $log_fh;
$self->{'alternate_logfile'} ||= $STD_LOG_FILE;
if ( $STD_LOG_FILE eq $self->{'alternate_logfile'} && _is_subprocess_of_cpsrvd() ) {
$log_fh = \*STDERR;
$usingstderr = 1;
}
else {
require Cpanel::FileUtils::Open;
if ( !Cpanel::FileUtils::Open::sysopen_with_real_perms( $log_fh, $self->{'alternate_logfile'}, 'O_WRONLY|O_APPEND|O_CREAT', 0600 ) ) {
( $usingstderr, $log_fh ) = ( 1, \*STDERR );
}
select( ( select($log_fh), $| = 1 )[0] ); ## no critic qw(Variables::RequireLocalizedPunctuationVars InputOutput::ProhibitOneArgSelect) -- Cpanel::FHUtils::Autoflush would be expensive to load every time
}
$self->{'log_fh'} = $log_fh;
$self->{'usingstderr'} = $usingstderr;
return 1;
}
sub _stdin_is_tty {
local $@;
return eval { -t STDIN };
}
sub _stdout_is_tty {
local $@;
return eval { -t STDOUT };
}
sub clear_singleton_stash {
%singleton_stash = ();
return;
}
1;
} # --- END Cpanel/Logger.pm
{ # --- BEGIN Cpanel/Sys/GetOS.pm
package Cpanel::Sys::GetOS;
our $VERSION = '1.2';
use strict;
my ( $cached_os, $cached_os_release_file );
sub getos {
defined $cached_os ? $cached_os : ( getos_and_release_file() )[0];
}
sub getos_and_release_file {
return ( $cached_os, $cached_os_release_file ) if defined $cached_os;
if ( ( my $os_cache_fs_mtime = ( stat('/var/cpanel/GetOS.cache') )[9] ) && open( my $os_fh, '<', '/var/cpanel/GetOS.cache' ) ) {
local $/;
my ( $fs_os_release_file, $fs_os, $fs_version ) = split( /\n/, readline($os_fh) );
my ( $fs_os_release_file_mtime, $fs_os_release_file_ctime ) = ( stat($fs_os_release_file) )[ 9, 10 ];
if ( $fs_version eq $VERSION && $os_cache_fs_mtime > $fs_os_release_file_mtime && $os_cache_fs_mtime > $fs_os_release_file_ctime ) {
return ( $cached_os = $fs_os, $cached_os_release_file = $fs_os_release_file );
}
}
my ( $os_release_file, $os );
foreach my $test_release_file ( 'CentOS-release', 'redhat-release', 'system-release' ) {
if ( -e '/etc/' . $test_release_file ) {
if ( ( ($os) = $test_release_file =~ m/^([^\-_]+)/ )[0] ) {
$os = lc $os; #lc ok here as no danger of utf-8 data
$os_release_file = '/etc/' . $test_release_file;
if ( $os eq 'system' ) {
$os = 'amazon';
}
last;
}
}
}
if ( !$os ) {
require Cpanel::Logger;
Cpanel::Logger::cplog( 'Unsupported operating system', 'die', __PACKAGE__ );
}
if ( $os eq 'redhat' || $os eq 'amazon' ) {
if ( open my $release_fh, '<', $os_release_file ) {
local $/;
if ( readline($release_fh) =~ /(centos|cloudlinux|amazon)/i ) { $os = lc $1; }
close $release_fh;
}
else {
require Cpanel::Logger;
Cpanel::Logger::cplog( 'Cannot open ' . $os_release_file, 'die', __PACKAGE__ );
}
}
if ( $> == 0 && open( my $os_fh, '>', '/var/cpanel/GetOS.cache' ) ) {
print {$os_fh} $os_release_file . "\n" . $os . "\n" . $VERSION;
}
return ( $cached_os = $os, $cached_os_release_file = $os_release_file );
}
1;
} # --- END Cpanel/Sys/GetOS.pm
{ # --- BEGIN Cpanel/Sys/OS.pm
package Cpanel::Sys::OS;
use strict;
# use Cpanel::Sys::GetOS ();
our $VERSION = '1.3';
my $cached_release_version;
my $cached_ises;
{
no warnings 'once';
*getos = \&Cpanel::Sys::GetOS::getos;
}
sub getreleaseversion {
if ( defined $cached_release_version && $cached_release_version ) {
return wantarray ? ( $cached_release_version, $cached_ises ) : $cached_release_version;
}
my ( $os, $releasefile ) = Cpanel::Sys::GetOS::getos_and_release_file();
if ( !$releasefile ) {
require Cpanel::Logger;
Cpanel::Logger::cplog( "Unsupported OS: $os", 'die', __PACKAGE__ );
}
( $cached_release_version, $cached_ises ) = getversionfromfile($releasefile);
return wantarray ? ( $cached_release_version, $cached_ises ) : $cached_release_version;
}
sub getversionfromfile {
my $file = shift;
my $ises = 0;
my $version = '';
if ( !defined($file) ) {
require Cpanel::Logger;
Cpanel::Logger::cplog( "No file argument", 'die', __PACKAGE__ );
}
if ( open my $fh, '<', $file ) {
my $line = readline $fh;
close $fh;
chomp $line;
if ( $line =~ m/(?:Corporate|Advanced\sServer|Enterprise|Amazon)/i ) { $ises = 1; }
elsif ( $line =~ /CloudLinux|CentOS/i ) { $ises = 2; }
if ( $line =~ /(\d+\.\d+)/ ) { $version = $1; }
elsif ( $line =~ /(\d+)/ ) { $version = $1; }
}
else {
require Cpanel::Logger;
Cpanel::Logger::cplog( "Cannot open file $file", 'die', __PACKAGE__ );
}
if ( $version eq '' ) {
require Cpanel::Logger;
Cpanel::Logger::cplog( 'Can not find distro version', 'die', __PACKAGE__ );
}
return wantarray ? ( $version, $ises ) : $version;
}
sub is_booting {
if ( getreleaseversion() < 7 || !_has_systemctl() ) {
chomp( my $runlevel = _run_runlevel() );
return 1 if !$runlevel;
return 1 if $runlevel !~ m/(\b[0-9]$)/;
return 1 if $1 < 3;
}
else {
if ( defined( my $systemd_is_operational = systemd_state_is_operational() ) ) { # Fall through if not defined.
return ( $systemd_is_operational ? 0 : 1 );
}
return 1 if 'active' ne _run_systemctl(qw{ is-active multi-user.target }); # Fall back to original but less accurate test
}
return 0;
}
sub _run_runlevel {
chomp( my $runlevel = qx{/sbin/runlevel} );
return $runlevel;
}
sub _has_systemctl {
return !!-x '/bin/systemctl';
}
sub _run_systemctl {
my (@args) = @_;
my $cmd = join ' ', '/bin/systemctl', @args;
chomp( my $res = qx/$cmd/ );
return $res || 'unknown';
}
sub systemd_state_is_operational {
my $res = _run_systemctl(qw{ show --property=SystemState });
return undef unless length $res; # Allows fall back
return undef if $res eq 'unknown'; # Allows fall back
return 1 if $res eq 'SystemState=running';
return 1 if $res eq 'SystemState=degraded'; # This Is Fine (insert appropriate meme) for our purposes
return 0;
}
1;
} # --- END Cpanel/Sys/OS.pm
{ # --- BEGIN Cpanel/Struct/Common/Time.pm
package Cpanel::Struct::Common::Time;
use strict;
use warnings;
use constant PACK_TEMPLATE => 'L!L!';
my %CLASS_PRECISION;
sub float_to_binary {
return pack(
PACK_TEMPLATE(),
int( $_[1] ),
int( 0.5 + ( $_[0]->_PRECISION() * $_[1] ) - ( $_[0]->_PRECISION() * int( $_[1] ) ) ),
);
}
sub binary_to_float {
return $_[0]->_binary_to_float( PACK_TEMPLATE(), $_[1] )->[0];
}
sub binaries_to_floats_at {
return $_[0]->_binary_to_float(
"\@$_[3] " . ( PACK_TEMPLATE() x $_[2] ),
$_[1],
);
}
my ( $i, $precision, @sec_psec_pairs );
sub _binary_to_float { ## no critic qw(RequireArgUnpacking)
@sec_psec_pairs = unpack( $_[1], $_[2] );
$i = 0;
my @floats;
$precision = $CLASS_PRECISION{ $_[0] } ||= $_[0]->_PRECISION();
while ( $i < @sec_psec_pairs ) {
push @floats, 0 + ( q<> . ( $sec_psec_pairs[$i] + ( $sec_psec_pairs[ $i + 1 ] / $precision ) ) );
$i += 2;
}
return \@floats;
}
1;
} # --- END Cpanel/Struct/Common/Time.pm
{ # --- BEGIN Cpanel/Struct/timespec.pm
package Cpanel::Struct::timespec;
use strict;
use warnings;
# use Cpanel::Struct::Common::Time();
our @ISA;
BEGIN { push @ISA, qw(Cpanel::Struct::Common::Time); }
use constant {
_PRECISION => 1_000_000_000, # nanoseconds
};
1;
} # --- END Cpanel/Struct/timespec.pm
{ # --- BEGIN Cpanel/NanoStat.pm
package Cpanel::NanoStat;
use strict;
use warnings;
# use Cpanel::Struct::timespec ();
use constant {
_NR_stat => 4,
_NR_fstat => 5,
_NR_lstat => 6,
};
use constant _PACK_TEMPLATE => q<
Q # st_dev
Q # st_ino
@24 L # st_mode
@16 Q # st_nlink
@28
L # st_uid
L # st_gid
x![Q]
Q # st_rdev
Q # st_size
Q # st_blksize
Q # st_blocks
>;
my $pre_times_pack_len = length pack _PACK_TEMPLATE();
my $buf = ( "\0" x 144 );
sub stat {
return _syscall( _NR_stat(), $_[0] );
}
sub lstat {
return _syscall( _NR_lstat(), $_[0] );
}
sub fstat {
return _syscall( _NR_fstat(), 0 + ( ref( $_[0] ) ? fileno( $_[0] ) : $_[0] ) );
}
sub _syscall { ## no critic qw(RequireArgUnpacking)
my $arg_dupe = $_[1];
return undef if -1 == syscall( $_[0], $arg_dupe, $buf );
my @vals = unpack _PACK_TEMPLATE(), $buf;
splice(
@vals, 8, 0,
@{ Cpanel::Struct::timespec->binaries_to_floats_at( $buf, 3, $pre_times_pack_len ) },
);
return @vals;
}
1;
} # --- END Cpanel/NanoStat.pm
{ # --- BEGIN Cpanel/NanoUtime.pm
package Cpanel::NanoUtime;
use strict;
use warnings;
# use Cpanel::Struct::timespec ();
use constant {
_NR_utimensat => 280,
_AT_FDCWD => -100,
_AT_SYMLINK_NOFOLLOW => 0x100,
};
sub utime {
return _syscall( 0 + _AT_FDCWD(), $_[2], @_[ 0, 1 ], 0 );
}
sub futime {
return _syscall(
0 + ( ref( $_[2] ) ? fileno( $_[2] ) : $_[2] ),
undef,
@_[ 0, 1 ],
0,
);
}
sub lutime {
return _syscall( 0 + _AT_FDCWD(), $_[2], @_[ 0, 1 ], 0 + _AT_SYMLINK_NOFOLLOW() );
}
my ( $path, $buf ) = @_;
sub _syscall {
if ( defined $_[-3] ) {
if ( defined $_[-2] ) {
$buf = Cpanel::Struct::timespec->float_to_binary( $_[-3] ) . Cpanel::Struct::timespec->float_to_binary( $_[-2] );
}
else {
die "atime is “$_[-3]”, but mtime is undef!";
}
}
elsif ( defined $_[-2] ) {
die "atime is undef, but mtime is “$_[-2]”!";
}
else {
$buf = undef;
}
$path = $_[1];
return undef if -1 == syscall( 0 + _NR_utimensat(), $_[0], $path // undef, $buf // undef, $_[-1] );
return 1;
}
1;
} # --- END Cpanel/NanoUtime.pm
{ # --- BEGIN Cpanel/HiRes.pm
package Cpanel::HiRes;
use strict;
use warnings;
my %_routes = (
'fstat' => [ 'NanoStat', 'fstat', 'stat', 1 ],
'lstat' => [ 'NanoStat', 'lstat', 'lstat', 1 ],
'stat' => [ 'NanoStat', 'stat', 'stat', 1 ],
'time' => [ 'TimeHiRes', 'time', 'time' ],
'utime' => [ 'NanoUtime', 'utime', 'utime' ],
'futime' => [ 'NanoUtime', 'futime', 'utime' ],
'lutime' => [ 'NanoUtime', 'lutime', undef ],
);
my $preloaded;
sub import {
my ( $class, %opts ) = @_;
if ( my $preload = $opts{'preload'} ) {
if ( $preload eq 'xs' ) {
require Time::HiRes;
}
elsif ( $preload eq 'perl' ) {
if ( !$preloaded ) {
require Cpanel::TimeHiRes; # PPI USE OK - preload
require Cpanel::NanoStat; # PPI USE OK - preload
require Cpanel::NanoUtime; # PPI USE OK - preload
}
}
else {
die "Unknown “preload”: “$preload”";
}
$preloaded = $preload;
}
return;
}
our $AUTOLOAD;
sub AUTOLOAD { ## no critic qw(Subroutines::RequireArgUnpacking)
substr( $AUTOLOAD, 0, 1 + rindex( $AUTOLOAD, ':' ) ) = q<>;
if ( !$AUTOLOAD || !$_routes{$AUTOLOAD} ) {
die "Unknown function in Cpanel::HiRes::$_[0]";
}
my $function = $AUTOLOAD;
undef $AUTOLOAD;
my ( $pp_module, $pp_function, $xs_function, $xs_needs_closure ) = @{ $_routes{$function} };
no strict 'refs';
if ( $INC{'Time/HiRes.pm'} && $xs_function ) {
*$function = *{"Time::HiRes::$xs_function"};
return Time::HiRes->can($xs_function)->(@_);
}
else {
_require("Cpanel/${pp_module}.pm") if !$INC{"Cpanel/${pp_module}.pm"};
my $pp_cr = "Cpanel::${pp_module}"->can($pp_function);
if ($xs_function) {
*$function = sub {
if ( $INC{'Time/HiRes.pm'} ) {
*$function = *{"Time::HiRes::$xs_function"};
return Time::HiRes->can($xs_function)->(@_);
}
goto &$pp_cr;
};
}
else {
*$function = $pp_cr;
}
}
goto &$function;
}
sub _require {
local ( $!, $^E, $@ );
require $_[0];
return;
}
1;
} # --- END Cpanel/HiRes.pm
{ # --- BEGIN Cpanel/Env.pm
package Cpanel::Env;
use strict;
use warnings;
our $VERSION = '1.7';
my $SAFE_ENV_VARS;
BEGIN {
$SAFE_ENV_VARS = q<
ALLUSERSPROFILE
APPDATA
CLIENTNAME
COMMONPROGRAMFILES
COMPUTERNAME
COMSPEC
CPANEL_IS_CRON
FORCEDCPUPDATE
CPANEL_BASE_INSTALL
CPBACKUP
DOCUMENT_ROOT
FP_NO_HOST_CHECK
HOMEDRIVE
HOMEPATH
LC_ALL
LOGONSERVER
NEWWHMUPDATE
NUMBER_OF_PROCESSORS
OPENSSL_NO_DEFAULT_ZLIB
OS
PATH
PATHEXT
PROCESSOR_ARCHITECTURE
PROCESSOR_IDENTIFIER
PROCESSOR_LEVEL
PROCESSOR_REVISION
PROGRAMFILES
PROMPT
SERVER_SOFTWARE
SESSIONNAME
SKIP_DEFERRAL_CHECK
SSH_CLIENT
SYSTEMDRIVE
SYSTEMROOT
TEMP
TERM
TMP
UPDATENOW_NO_RETRY
UPDATENOW_PRESERVE_FAILED_FILES
USERDOMAIN
USERNAME
USERPROFILE
WINDIR
>;
$SAFE_ENV_VARS =~ tr<\n >< >s;
$SAFE_ENV_VARS =~ s<\A\s+><>;
}
{
no warnings 'once';
*cleanenv = *clean_env;
}
sub clean_env {
my %OPTS = @_;
my %SAFE_ENV_VARS = map { $_ => undef } split( m{ }, $SAFE_ENV_VARS );
if ( defined $OPTS{'keep'} && ref $OPTS{'keep'} eq 'ARRAY' ) {
@SAFE_ENV_VARS{ @{ $OPTS{'keep'} } } = undef;
}
if ( defined $OPTS{'delete'} && ref $OPTS{'delete'} eq 'ARRAY' ) {
delete @SAFE_ENV_VARS{ @{ $OPTS{'delete'} } };
}
delete @ENV{ grep { !exists $SAFE_ENV_VARS{$_} } keys %ENV };
if ( $OPTS{'http_purge'} ) {
delete @ENV{ 'SERVER_SOFTWARE', 'DOCUMENT_ROOT' };
}
return;
}
sub get_safe_env_vars {
return $SAFE_ENV_VARS;
}
sub get_safe_path {
return '/usr/local/jdk/bin:/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/X11R6/bin:/usr/local/bin:/usr/X11R6/bin:/root/bin:/opt/bin';
}
sub set_safe_path {
return ( $ENV{'PATH'} = get_safe_path() );
}
1;
} # --- END Cpanel/Env.pm
{ # --- BEGIN Cpanel/Autodie.pm
package Cpanel::Autodie;
use strict;
use warnings;
sub _ENOENT { return 2; }
sub _EEXIST { return 17; }
sub _EINTR { return 4; }
sub import {
shift;
_load_function($_) for @_;
return;
}
our $AUTOLOAD;
sub AUTOLOAD {
substr( $AUTOLOAD, 0, 1 + rindex( $AUTOLOAD, ':' ) ) = q<>;
_load_function($AUTOLOAD);
goto &{ Cpanel::Autodie->can($AUTOLOAD) };
}
sub _load_function {
_require("Cpanel/Autodie/CORE/$_[0].pm");
return;
}
sub _require {
local ( $!, $^E, $@ );
require $_[0];
return;
}
1;
} # --- END Cpanel/Autodie.pm
{ # --- BEGIN Cpanel/FileUtils/Touch.pm
package Cpanel::FileUtils::Touch;
use strict;
use warnings;
use Try::Tiny;
use Cpanel::Autodie;
use Cpanel::Fcntl;
sub touch_if_not_exists {
my ($path) = @_;
my $fh;
try {
Cpanel::Autodie::sysopen(
$fh,
$path,
Cpanel::Fcntl::or_flags(qw( O_WRONLY O_CREAT O_EXCL )),
);
}
catch {
undef $fh;
if ( !try { $_->error_name() eq 'EEXIST' } ) {
local $@ = $_;
die;
}
};
return $fh ? 1 : 0;
}
1;
} # --- END Cpanel/FileUtils/Touch.pm
{ # --- BEGIN Cpanel/Config/TouchFileBase.pm
package Cpanel::Config::TouchFileBase;
use strict;
use warnings;
# use Cpanel::Autodie ();
# use Cpanel::Exception ();
sub _TOUCH_FILE { die Cpanel::Exception::create('AbstractClass') }
sub is_on {
my ( $self, @args ) = @_;
my $exists = Cpanel::Autodie::exists( $self->_TOUCH_FILE(@args) );
if ( $exists && !-f _ ) {
die Cpanel::Exception->create( '“[_1]” exists but is not a file!', [ $self->_TOUCH_FILE(@args) ] );
}
return $exists;
}
sub set_on {
my ( $self, @args ) = @_;
my $path = $self->_TOUCH_FILE(@args);
require Cpanel::FileUtils::Touch;
return Cpanel::FileUtils::Touch::touch_if_not_exists($path);
}
sub set_off {
my ( $self, @args ) = @_;
return Cpanel::Autodie::unlink_if_exists( $self->_TOUCH_FILE(@args) );
}
1;
} # --- END Cpanel/Config/TouchFileBase.pm
{ # --- BEGIN Cpanel/Update/IsCron.pm
package Cpanel::Update::IsCron;
use strict;
use warnings;
# use Cpanel::Config::TouchFileBase();
our @ISA;
BEGIN { push @ISA, qw(Cpanel::Config::TouchFileBase); }
our $_PATH = '/var/cpanel/upgrade_is_from_cron';
sub _TOUCH_FILE { return $_PATH }
1;
} # --- END Cpanel/Update/IsCron.pm
{ # --- BEGIN Cpanel/SafeDir/MK.pm
package Cpanel::SafeDir::MK;
use strict;
use warnings;
# use Cpanel::Debug ();
my $DEFAULT_PERMISSIONS = 0755;
sub safemkdir { ## no critic(Subroutines::ProhibitExcessComplexity) -- Refactoring this function is a project, not a bug fix
my ( $dir, $mode, $errors, $created ) = @_;
if ( defined $mode ) {
if ( $mode eq '' ) {
$mode = undef;
}
elsif ( index( $mode, '0' ) == 0 ) {
if ( length $mode < 3 || $mode =~ tr{0-7}{}c || !defined oct $mode ) {
$mode = $DEFAULT_PERMISSIONS;
}
else {
$mode = oct($mode);
}
}
elsif ( $mode =~ tr{0-9}{}c ) {
$mode = $DEFAULT_PERMISSIONS;
}
}
$dir =~ tr{/}{}s;
my $default = '';
if ( index( $dir, '/' ) == 0 ) {
$default = '/';
}
elsif ( $dir eq '.' || $dir eq './' ) {
if ( !-l $dir && defined $mode ) {
return chmod $mode, $dir;
}
return 1;
}
else {
substr( $dir, 0, 2, '' ) if index( $dir, './' ) == 0;
}
if ( _has_dot_dot($dir) ) {
Cpanel::Debug::log_warn("Possible improper directory $dir specified");
my @dir_parts = split m{/}, $dir;
my @good_parts;
my $first;
foreach my $part (@dir_parts) {
next if ( !defined $part || $part eq '' );
next if $part eq '.';
if ( $part eq '..' ) {
if ( !$first || !@good_parts ) {
Cpanel::Debug::log_warn("Will not proceed above first directory part $first");
return 0;
}
if ( $first eq $good_parts[$#good_parts] ) {
undef $first;
}
pop @good_parts;
next;
}
elsif ( $part !~ tr{.}{}c ) {
Cpanel::Debug::log_warn("Total stupidity found in directory $dir");
return 0;
}
push @good_parts, $part;
if ( !$first ) { $first = $part }
}
$dir = $default . join '/', @good_parts;
if ( !$dir ) {
Cpanel::Debug::log_warn("Could not validate given directory");
return;
}
Cpanel::Debug::log_warn("Improper directory updated to $dir");
}
if ( -d $dir ) {
if ( !-l $dir && defined $mode ) {
return chmod $mode, $dir;
}
return 1;
}
elsif ( -e _ ) {
Cpanel::Debug::log_warn("$dir was expected to be a directory!");
require Errno;
$! = Errno::ENOTDIR(); ## no critic qw(Variables::RequireLocalizedPunctuationVars) -- for legacy reasons
return 0;
}
my @dir_parts = split m{/}, $dir;
if ( scalar @dir_parts > 100 ) {
Cpanel::Debug::log_warn("Encountered excessive directory length. This should never happen.");
return 0;
}
my $returnvalue;
foreach my $i ( 0 .. $#dir_parts ) {
my $newdir = join( '/', @dir_parts[ 0 .. $i ] );
next if $newdir eq '';
my $is_dir = -d $newdir;
my $exists = -e _;
if ( !$exists ) {
my $local_mode = defined $mode ? $mode : $DEFAULT_PERMISSIONS;
if ( mkdir( $newdir, $local_mode ) ) {
push @{$created}, $newdir if $created;
$returnvalue++;
}
else {
Cpanel::Debug::log_warn("mkdir $newdir failed: $!");
return;
}
}
elsif ( !$is_dir ) {
Cpanel::Debug::log_warn("Encountered non-directory $newdir in path of $dir: $!");
require Errno;
$! = Errno::ENOTDIR(); ## no critic qw(Variables::RequireLocalizedPunctuationVars) -- for legacy reasons
last;
}
}
return $returnvalue;
}
sub _has_dot_dot { ## no critic qw(RequireArgUnpacking)
return 1 if $_[0] eq '..';
return 1 if -1 != index( $_[0], '/../' );
return 1 if 0 == index( $_[0], '../' );
return 1 if ( length( $_[0] ) - 3 ) == rindex( $_[0], '/..' );
return 0;
}
1;
} # --- END Cpanel/SafeDir/MK.pm
{ # --- BEGIN Cpanel/FHUtils/Autoflush.pm
package Cpanel::FHUtils::Autoflush;
use strict;
use warnings;
sub enable {
select( ( select( $_[0] ), $| = 1 )[0] ); ## no critic qw(InputOutput::ProhibitOneArgSelect Variables::RequireLocalizedPunctuationVars) - aka $socket->autoflush(1) without importing IO::Socket
return;
}
1;
} # --- END Cpanel/FHUtils/Autoflush.pm
{ # --- BEGIN Cpanel/Update/Logger.pm
package Cpanel::Update::Logger;
use strict;
use warnings;
# use Cpanel::SafeDir::MK ();
# use Cpanel::Time::Local ();
# use Cpanel::FHUtils::Autoflush ();
use File::Basename ();
use constant {
DEBUG => 0,
INFO => 25,
WARN => 50,
ERROR => 75,
FATAL => 100,
};
our $VERSION = '1.2';
our $_BACKLOG_TIE_CLASS;
sub new {
my $class = shift;
my $self = shift || {};
ref($self) eq 'HASH' or CORE::die("hashref not passed to new");
bless( $self, $class );
$self->{'stdout'} = 1 if ( !defined $self->{'stdout'} );
$self->{'timestamp'} = 1 if ( !defined $self->{'timestamp'} );
if ( $self->{'to_memory'} ) {
$self->{'backlog'} = [];
tie @{ $self->{'backlog'} }, $_BACKLOG_TIE_CLASS if $_BACKLOG_TIE_CLASS;
}
eval { $self->set_logging_level( $self->{'log_level'} ); 1 }
or CORE::die("An invalid logging level was passed to new: $self->{'log_level'}");
$self->open_log() if $self->{'logfile'};
if ( exists $self->{'pbar'} and defined $self->{'pbar'} ) {
$self->{'pbar'} += 0;
$self->update_pbar( $self->{'pbar'} );
}
return $self;
}
sub open_log {
my $self = shift or CORE::die();
my $log_file = $self->{'logfile'};
my $logfile_dir = File::Basename::dirname($log_file);
my $created_dir = 0;
if ( !-d $logfile_dir ) {
Cpanel::SafeDir::MK::safemkdir( $logfile_dir, '0700', 2 );
$created_dir = 1;
}
my $old_umask = umask(0077); # Case 92381: Logs should not be world-readable
open( my $fh, '>>', $log_file ) or do {
CORE::die("Failed to open '$log_file' for append: $!");
};
umask($old_umask);
Cpanel::FHUtils::Autoflush::enable($fh);
Cpanel::FHUtils::Autoflush::enable( \*STDOUT ) if $self->{'stdout'};
$self->{'fh'} = $fh;
unless ( $self->{brief} ) {
print {$fh} '-' x 100 . "\n";
print {$fh} "=> Log opened from $0 ($$) at " . localtime(time) . "\n";
}
$self->warning("Had to create directory $logfile_dir before opening log") if ($created_dir);
return;
}
sub close_log {
my $self = shift or CORE::die();
return if ( !$self->{'fh'} );
my $fh = $self->{'fh'};
unless ( $self->{brief} ) {
print {$fh} "=> Log closed " . localtime(time) . "\n";
}
warn("Failed to close file handle for $self->{'logfile'}") if ( !close $fh );
delete $self->{'fh'};
return;
}
sub DESTROY {
my $self = shift or CORE::die("DESTROY called without an object");
$self->close_log if ( $self->{'fh'} );
return;
}
sub log {
my $self = shift or CORE::die("log called as a class");
ref $self eq __PACKAGE__ or CORE::die("log called as a class");
my $msg = shift or return;
my $stdout = shift;
$stdout = $self->{'stdout'} if ( !defined $stdout );
my $to_memory = $self->{'to_memory'};
my $fh = $self->{'fh'};
foreach my $line ( split( /[\r\n]+/, $msg ) ) {
if ( $self->{'timestamp'} ) {
substr( $line, 0, 0, '[' . Cpanel::Time::Local::localtime2timestamp() . '] ' );
}
chomp $line;
print STDOUT "$line\n" if $stdout;
print {$fh} "$line\n" if $fh;
push @{ $self->{'backlog'} }, "$line" if ($to_memory);
}
return;
}
sub _die {
my $self = shift or CORE::die();
my $message = shift || '';
$self->log("***** DIE: $message");
return CORE::die( "exit level [die] [pid=$$] ($message) " . join ' ', caller() );
}
sub fatal {
my $self = shift or CORE::die();
return if ( $self->{'log_level_numeric'} > FATAL );
my $message = shift || '';
$self->log("***** FATAL: $message");
$self->set_need_notify();
return;
}
sub error {
my $self = shift or CORE::die();
return if ( $self->{'log_level_numeric'} > ERROR );
my $message = shift || '';
$self->log("E $message");
return;
}
sub warning {
my $self = shift or CORE::die();
return if ( $self->{'log_level_numeric'} > WARN );
my $message = shift || '';
$self->log("W $message");
return;
}
sub panic {
my $self = shift or CORE::die();
return if ( $self->{'log_level_numeric'} > ERROR );
my $message = shift || '';
$self->log("***** PANIC!");
$self->log("E $message");
$self->log("***** PANIC!");
$self->set_need_notify();
return;
}
sub info {
my $self = shift or CORE::die();
return if ( $self->{'log_level_numeric'} > INFO );
my $message = shift || '';
$self->log(" $message");
return;
}
sub debug {
my $self = shift or CORE::die();
return if ( $self->{'log_level_numeric'} > DEBUG );
my $message = shift || '';
$self->log("D $message");
return;
}
sub get_logging_level { return shift->{'log_level'} }
sub set_logging_level {
my $self = shift or CORE::die();
my $log_level = shift;
$log_level = 'info' if ( !defined $log_level );
my $old_log_level = $self->get_logging_level();
if ( $log_level =~ m/^fatal/i ) {
$self->{'log_level'} = 'fatal';
$self->{'log_level_numeric'} = FATAL;
}
elsif ( $log_level =~ m/^error/i ) {
$self->{'log_level'} = 'error';
$self->{'log_level_numeric'} = ERROR;
}
elsif ( $log_level =~ m/^warn/i ) {
$self->{'log_level'} = 'warning';
$self->{'log_level_numeric'} = WARN;
}
elsif ( $log_level =~ m/^info/i ) {
$self->{'log_level'} = 'info';
$self->{'log_level_numeric'} = INFO;
}
elsif ( $log_level =~ m/^debug/i ) {
$self->{'log_level'} = 'debug';
$self->{'log_level_numeric'} = DEBUG;
}
else {
CORE::die("Unknown logging level '$log_level' passed to set_logging_level");
}
return $old_log_level;
}
sub get_pbar { return shift->{'pbar'} }
sub increment_pbar {
my $self = shift or CORE::die();
return if ( !exists $self->{'pbar'} );
my $amount = shift || 1;
my $new_value = $self->{'pbar'} + $amount;
return $self->update_pbar($new_value);
}
sub update_pbar {
my $self = shift or CORE::die();
return if ( !exists $self->{'pbar'} );
my $new_value = shift || 0;
if ( $new_value > 100 ) {
$self->debug("Pbar set to > 100 ($new_value)");
$new_value = 100;
}
return if $new_value == $self->{'pbar'};
$self->{'pbar'} = $new_value;
$self->info( $new_value . '% complete' );
return;
}
sub set_need_notify {
my $self = shift;
ref $self eq __PACKAGE__ or CORE::die("log called as a class");
$self->info("The Administrator will be notified to review this output when this script completes");
return $self->{'need_notify'} = 1;
}
sub get_need_notify {
my $self = shift;
ref $self eq __PACKAGE__ or CORE::die("log called as a class");
return $self->{'need_notify'};
}
sub get_stored_log {
my $self = shift;
ref $self eq __PACKAGE__ or CORE::die("log called as a class");
return if ( !$self->{'to_memory'} );
return $self->{'backlog'};
}
sub get_next_log_message {
my $self = shift;
ref $self eq __PACKAGE__ or CORE::die("log called as a class");
return if ( !$self->{'to_memory'} );
return shift @{ $self->{'backlog'} };
}
sub success { goto \&info; }
sub out { goto \&info; }
sub warn { goto \&warning; }
sub die { goto \&_die; }
1;
} # --- END Cpanel/Update/Logger.pm
{ # --- BEGIN Cpanel/FileUtils/TouchFile.pm
package Cpanel::FileUtils::TouchFile;
use strict;
use warnings;
use constant {
_ENOENT => 2,
};
my $logger;
our $VERSION = '1.3';
sub _log {
my ( $level, $msg ) = @_;
require Cpanel::Logger;
$logger ||= Cpanel::Logger->new();
$logger->$level($msg);
return;
}
my $mtime;
sub touchfile {
my ( $file, $verbose, $fail_ok ) = @_;
if ( !defined $file ) {
_log( 'warn', "touchfile called with undefined file" );
return;
}
my $mtime;
if ( utime undef, undef, $file ) {
return 1;
}
elsif ( $! != _ENOENT() ) {
_log( 'warn', "utime($file) as $>: $!" );
$mtime = -e $file ? ( stat _ )[9] : 0; # for warnings-safe numeric comparison
if ( !$mtime && $! != _ENOENT ) {
_log( 'warn', "Failed to stat($file) as $>: $!" );
return;
}
}
$mtime = ( stat $file )[9] // 0;
if ( open my $fh, '>>', $file ) { # append so we don't wipe out contents
my $mtime_after_open = ( stat $fh )[9] || 0; # for warnings safe numeric comparison
return 1 if $mtime != $mtime_after_open; # in case open does not change it, see comment below
}
else {
_log( 'warn', "Failed to open(>> $file) as $>: $!" ) unless $fail_ok;
}
if ($fail_ok) { return; }
my $at_this_point = ( stat $file )[9] || 0; # for warnings safe numeric comparison
if ( $mtime == $at_this_point ) {
my $new_at_this_point = ( stat $file )[9] || 0; # for warnings safe numeric comparison
if ( $mtime == $new_at_this_point ) {
if ($verbose) {
_log( 'info', 'Trying to do system “touch” command!' );
}
if ( system( 'touch', $file ) != 0 ) {
if ($verbose) {
_log( 'info', 'system method 1 failed.' );
}
}
}
}
if ( !-e $file ) { # obvisouly it didn't touch it if it doesn't exist...
_log( 'warn', "Failed to create $file: $!" );
return;
}
else {
my $after_all_that = ( stat $file )[9] || 0; # for warnings safe numeric comparison
if ( $mtime && $mtime == $after_all_that ) {
_log( 'warn', "mtime of “$file” not changed!" );
return;
}
return 1;
}
}
1;
} # --- END Cpanel/FileUtils/TouchFile.pm
{ # --- BEGIN Cpanel/LoadFile/ReadFast.pm
package Cpanel::LoadFile::ReadFast;
use strict;
use warnings;
use constant READ_CHUNK => 1 << 18; # 262144
use constant _EINTR => 4;
sub read_fast {
$_[1] //= q<>;
return ( @_ > 3 ? sysread( $_[0], $_[1], $_[2], $_[3] ) : sysread( $_[0], $_[1], $_[2] ) ) // do {
goto \&read_fast if $! == _EINTR;
die "Failed to read data: $!";
};
}
my $_ret;
sub read_all_fast {
$_[1] //= q<>;
$_ret = 1;
while ($_ret) {
$_ret = sysread( $_[0], $_[1], READ_CHUNK, length $_[1] ) // do {
redo if $! == _EINTR;
die "Failed to read data: $!";
}
}
return;
}
1;
} # --- END Cpanel/LoadFile/ReadFast.pm
{ # --- BEGIN Cpanel/LoadFile.pm
package Cpanel::LoadFile;
use strict;
use warnings;
# use Cpanel::Exception ();
# use Cpanel::Fcntl::Constants ();
# use Cpanel::LoadFile::ReadFast ();
sub loadfileasarrayref {
my $fileref = _load_file( shift, { 'array_ref' => 1 } );
return ref $fileref eq 'ARRAY' ? $fileref : undef;
}
sub loadbinfile {
my $fileref = _load_file( shift, { 'binmode' => 1 } );
return ref $fileref eq 'SCALAR' ? $$fileref : undef;
}
sub slurpfile {
my $fh = shift;
my $fileref = _load_file(shift);
if ( ref $fileref eq 'SCALAR' ) {
print {$fh} $$fileref;
}
return;
}
sub loadfile {
my $fileref = _load_file(@_);
return ref $fileref eq 'SCALAR' ? $$fileref : undef;
}
sub loadfile_r {
my ( $file, $arg_ref ) = @_;
if ( open my $lf_fh, '<:stdio', $file ) {
if ( $arg_ref->{'binmode'} ) { binmode $lf_fh; }
my $data;
if ( $arg_ref->{'array_ref'} ) {
@{$data} = readline $lf_fh;
close $lf_fh;
return $data;
}
else {
$data = '';
local $@;
eval { Cpanel::LoadFile::ReadFast::read_all_fast( $lf_fh, $data ); };
return $@ ? undef : \$data;
}
}
return;
}
*_load_file = *loadfile_r;
sub _open {
return _open_if_exists( $_[0] ) || die Cpanel::Exception::create( 'IO::FileNotFound', [ path => $_[0], error => _ENOENT() ] );
}
sub _open_if_exists {
local $!;
open my $fh, '<:stdio', $_[0] or do {
if ( $! == _ENOENT() ) {
return undef;
}
die Cpanel::Exception::create( 'IO::FileOpenError', [ path => $_[0], error => $!, mode => '<' ] );
};
return $fh;
}
sub load_if_exists {
my $ref = _load_r( \&_open_if_exists, @_ );
return $ref ? $$ref : undef;
}
sub load_r_if_exists {
return _load_r( \&_open_if_exists, @_ );
}
sub load {
return ${ _load_r( \&_open, @_ ) };
}
sub load_r {
return _load_r( \&_open,, @_ );
}
sub _load_r {
my ( $open_coderef, $path, $offset, $length ) = @_;
my $fh = $open_coderef->($path) or return undef;
local $!;
my $file_size = -f $fh && -s _;
if ($offset) {
sysseek( $fh, $offset, $Cpanel::Fcntl::Constants::SEEK_SET );
if ($!) {
die Cpanel::Exception::create(
'IO::FileSeekError',
[
path => $path,
position => $offset,
whence => $Cpanel::Fcntl::Constants::SEEK_SET,
error => $!,
]
);
}
}
my $data = q<>;
if ( !defined $length ) {
if ($file_size) {
Cpanel::LoadFile::ReadFast::read_fast( $fh, $data, $file_size );
}
else {
Cpanel::LoadFile::ReadFast::read_all_fast( $fh, $data );
}
}
else {
my $togo = $length;
my $bytes_read;
while ( $bytes_read = Cpanel::LoadFile::ReadFast::read_fast( $fh, $data, $togo, length $data ) && length $data < $length ) {
$togo -= $bytes_read;
}
}
if ($!) {
die Cpanel::Exception::create( 'IO::FileReadError', [ path => $path, error => $! ] );
}
close $fh or warn "The system failed to close the file “$path” because of an error: $!";
return \$data;
}
sub _ENOENT { return 2; }
1;
} # --- END Cpanel/LoadFile.pm
{ # --- BEGIN Cpanel/Usage.pm
package Cpanel::Usage;
my $g_prefs; # Ref to hash containing up to three boolean preferences, as follows:
$Cpanel::Usage::VERSION = '1.08';
sub version { # Reports our current revision number.
$Cpanel::Usage::VERSION;
}
sub wrap_options {
my $arg1 = $_[0];
$g_prefs = {};
if ( defined $arg1 && ( ref $arg1 ) =~ /\bHASH\b/ ) { # hash of preferences
$g_prefs = $arg1;
shift;
}
my ( $ar_argv, $cr_usage, $hr_opts ) = @_;
getoptions( usage( $ar_argv, $cr_usage ), $hr_opts );
}
sub usage {
my ( $ar_argv, $cr_usage ) = @_;
foreach my $arg (@$ar_argv) {
if ( $arg =~ /^-+(h|help|usage)$/ ) {
if ( defined($cr_usage) ) {
&$cr_usage();
}
return 1;
}
}
$ar_argv;
}
sub getoptions {
my ( $ar_cmdline, $hr ) = @_;
my $non_opt_arg_seen = "";
return $ar_cmdline if ( ref $ar_cmdline || "" ) !~ /\bARRAY\b/;
if ( !$#$ar_cmdline && $ar_cmdline->[0] eq "1" ) {
return 1;
}
unless ( defined $hr && ( ref $hr ) =~ /\bHASH\b/ ) {
print "Error: opts must be a hash reference\n";
return 2;
}
my $predefined = keys %{$hr};
my @cmdline_out = @$ar_cmdline; # save a copy of the arg array
if ( !$predefined ) {
if ( no_switches($ar_cmdline) ) {
my $i = 0;
foreach my $arg (@$ar_cmdline) {
$hr->{ $i++ } = $arg;
}
return "";
}
}
if ($predefined) {
my $default_value = exists $g_prefs->{'default_value'} ? $g_prefs->{'default_value'} : 0;
foreach my $k ( keys %$hr ) {
if ( ref( $hr->{$k} ) =~ /^HASH/ ) {
foreach my $kk ( keys %{ $hr->{$k} } ) {
${ $hr->{$k}->{$kk} } = $default_value unless ( defined ${ $hr->{$k}->{$kk} } );
}
}
else {
${ $hr->{$k} } = $default_value unless ( defined ${ $hr->{$k} } );
}
}
}
my $seen_dash_dash = 0;
for ( my $i = 0; $i <= $#$ar_cmdline; $i++ ) {
if ( $ar_cmdline->[$i] eq '--' ) {
$seen_dash_dash = 1;
}
elsif ( !$seen_dash_dash && $ar_cmdline->[$i] =~ /^-+(.+)$/ ) {
my $o = $1;
if ( "" ne $non_opt_arg_seen and $g_prefs->{'require_left'} ) {
print qq{Error: Preference require_left was specified, all opt args must therefore appear first on the command line; option "-$o" found after "$non_opt_arg_seen" violates this rule\n};
return 3;
}
my $eq_value = '';
if ( $o =~ /(.+?)=(.+)/ ) {
$o = $1;
$eq_value = $2;
$eq_value =~ s@^\s+@@;
$eq_value =~ s@\s+$@@;
}
if ( $g_prefs->{'strict'} && $predefined && !exists $hr->{$o} ) {
print qq{Error: While "strict" is in effect, we have encountered option --$o on the command line, an option that was not specified in the opts hash.\n};
return 4;
}
if ( # It is a "lone switch", that is, an
$eq_value eq '' && ( $i == $#$ar_cmdline
|| $ar_cmdline->[ $i + 1 ] =~ /^-+.+$/ )
) {
if ( ref( $hr->{$o} ) =~ /^HASH/ ) {
foreach my $kk ( keys %{ $hr->{$o} } ) {
if ($predefined) {
${ $hr->{$o}->{$kk} }++ if ( exists( $hr->{$o} ) );
}
}
}
else {
if ($predefined) {
${ $hr->{$o} }++ if ( exists( $hr->{$o} ) );
}
else {
$hr->{ _multihelp($o) }++;
}
}
}
else { # not a "lone switch"; the next arg might be the value
if ( ref( $hr->{$o} ) =~ /^HASH/ ) {
print "Error: A multi-level option can only be used when implicitly boolean (true), but you have attempted to use a multi-level option with an explicitly specified option argument.\n";
return 5;
}
if ( $eq_value ne '' ) { # Sorry, we already have a value for the switch
if ($predefined) {
${ $hr->{$o} } = $eq_value if ( exists( $hr->{$o} ) );
}
else {
$hr->{$o} = $eq_value;
}
}
else { # We have no value yet for the switch, so use next arg as the value
$cmdline_out[$i] = undef if $g_prefs->{'remove'};
++$i;
if ($predefined) {
${ $hr->{$o} } = $ar_cmdline->[$i]
if ( exists( $hr->{$o} ) );
}
else {
$hr->{$o} = $ar_cmdline->[$i];
}
}
}
$cmdline_out[$i] = undef if $g_prefs->{'remove'};
}
else { # It's a regular (non-hyphen-prefixed) arg, not an option arg
if ( "" eq $non_opt_arg_seen ) {
$non_opt_arg_seen = $ar_cmdline->[$i];
}
}
}
if ( $g_prefs->{'remove'} ) {
@cmdline_out = grep { defined } @cmdline_out;
@{$ar_cmdline} = @cmdline_out;
}
return ""; # aka 0, successful completion
}
sub _multihelp { # For internal use only
my $name = shift;
return $name =~ /^(h|help|usage)$/ ? 'help' : $name;
}
sub no_switches {
my $ar = shift;
return !grep { /^-+.+/ } @{$ar};
}
sub dump_args {
my $hr_opts = shift;
require Data::Dumper;
print Data::Dumper::Dumper($hr_opts);
}
1;
} # --- END Cpanel/Usage.pm
{ # --- BEGIN Cpanel/Unix/PID/Tiny.pm
package Cpanel::Unix::PID::Tiny;
use strict;
$Cpanel::Unix::PID::Tiny::VERSION = 0.9_2;
sub new {
my ( $self, $args_hr ) = @_;
$args_hr->{'minimum_pid'} = 11 if !exists $args_hr->{'minimum_pid'} || $args_hr->{'minimum_pid'} !~ m{\A\d+\z}ms; # this does what one assumes m{^\d+$} would do
if ( defined $args_hr->{'ps_path'} ) {
$args_hr->{'ps_path'} .= '/' if $args_hr->{'ps_path'} !~ m{/$};
if ( !-d $args_hr->{'ps_path'} || !-x "$args_hr->{'ps_path'}ps" ) {
$args_hr->{'ps_path'} = '';
}
}
else {
$args_hr->{'ps_path'} = '';
}
return bless { 'ps_path' => $args_hr->{'ps_path'}, 'minimum_pid' => $args_hr->{'minimum_pid'} }, $self;
}
sub kill {
my ( $self, $pid, $give_kill_a_chance ) = @_;
$give_kill_a_chance = int $give_kill_a_chance if defined $give_kill_a_chance;
$pid = int $pid;
my $min = int $self->{'minimum_pid'};
if ( $pid < $min ) {
warn "kill() called with integer value less than $min";
return;
}
return 1 unless $self->is_pid_running($pid);
my @signals = ( 15, 2, 1, 9 ); # TERM, INT, HUP, KILL
foreach my $signal ( 15, 2, 1, 9 ) { # TERM, INT, HUP, KILL
_kill( $signal, $pid );
if ($give_kill_a_chance) {
my $start_time = time();
while ( time() < $start_time + $give_kill_a_chance ) {
if ( $self->is_pid_running($pid) ) {
select( undef, undef, undef, 0.25 );
}
else {
return 1;
}
}
}
return 1 unless $self->is_pid_running($pid);
}
return;
}
sub is_pid_running {
my ( $self, $check_pid ) = @_;
$check_pid = int $check_pid;
return if !$check_pid || $check_pid < 0;
return 1 if $> == 0 && _kill( 0, $check_pid ); # if we are superuser we can avoid the the system call. For details see `perldoc -f kill`
return 1 if -e "/proc/$$" && -r "/proc/$$" && -r "/proc/$check_pid";
return;
}
sub pid_info_hash {
my ( $self, $pid ) = @_;
$pid = int $pid;
return if !$pid || $pid < 0;
my @outp = $self->_raw_ps( 'u', '-p', $pid );
chomp @outp;
my %info;
@info{ split( /\s+/, $outp[0], 11 ) } = split( /\s+/, $outp[1], 11 );
return wantarray ? %info : \%info;
}
sub _raw_ps {
my ( $self, @ps_args ) = @_;
my $psargs = join( ' ', @ps_args );
my @res = `$self->{'ps_path'}ps $psargs`;
return wantarray ? @res : join '', @res;
}
sub get_pid_from_pidfile {
my ( $self, $pid_file ) = @_;
return 0 if !-e $pid_file or -z _;
open my $pid_fh, '<', $pid_file or return;
my $pid = <$pid_fh>;
close $pid_fh;
return 0 if !$pid;
chomp $pid;
return int( abs($pid) );
}
sub is_pidfile_running {
my ( $self, $pid_file ) = @_;
my $pid = $self->get_pid_from_pidfile($pid_file) || return;
return $pid if $self->is_pid_running($pid);
return;
}
sub pid_file {
my ( $self, $pid_file, $newpid, $retry_conf ) = @_;
$newpid = $$ if !$newpid;
my $rc = $self->pid_file_no_unlink( $pid_file, $newpid, $retry_conf );
if ( $rc && $newpid == $$ ) {
$self->create_end_blocks($pid_file);
}
return 1 if defined $rc && $rc == 1;
return 0 if defined $rc && $rc == 0;
return;
}
sub create_end_blocks {
my ( $self, $pid_file ) = @_; ## no critic qw(Variables::ProhibitUnusedVariables);
if ( $self->{'unlink_end_use_current_pid_only'} ) {
eval 'END { unlink $pid_file if $$ eq ' . $$ . '}'; ## no critic qw(ProhibitStringyEval)
if ( $self->{'carp_unlink_end'} ) {
eval 'END { require Carp;Carp::carp("[info] $$ unlink $pid_file (current pid check)") if $$ eq ' . $$ . '}'; ## no critic qw(ProhibitStringyEval)
}
}
else {
eval 'END { unlink $pid_file if Cpanel::Unix::PID::Tiny->get_pid_from_pidfile($pid_file) eq $$ }'; ## no critic qw(ProhibitStringyEval)
if ( $self->{'carp_unlink_end'} ) {
eval 'END { require Carp;Carp::carp("[info] $$ unlink $pid_file (pid file check)") if Cpanel::Unix::PID::Tiny->get_pid_from_pidfile($pid_file) eq $$ }'; ## no critic qw(ProhibitStringyEval)
}
}
return;
}
*pid_file_no_cleanup = \&pid_file_no_unlink; # more intuitively named alias
sub pid_file_no_unlink {
my ( $self, $pid_file, $newpid, $retry_conf ) = @_;
$newpid = $$ if !$newpid;
if ( ref($retry_conf) eq 'ARRAY' ) {
$retry_conf->[0] = int( abs( $retry_conf->[0] ) );
for my $idx ( 1 .. scalar( @{$retry_conf} ) - 1 ) {
next if ref $retry_conf->[$idx] eq 'CODE';
$retry_conf->[$idx] = int( abs( $retry_conf->[$idx] ) );
}
}
else {
$retry_conf = [ 3, 1, 2 ];
}
my $passes = 0;
require Fcntl;
EXISTS:
$passes++;
if ( -e $pid_file ) {
my $curpid = $self->get_pid_from_pidfile($pid_file);
return 1 if int $curpid == $$ && $newpid == $$; # already setup
return if int $curpid == $$; # can't change it while $$ is alive
return if $self->is_pid_running( int $curpid );
unlink $pid_file; # must be a stale PID file, so try to remove it for sysopen()
}
my $pid_fh = _sysopen($pid_file);
if ( !$pid_fh ) {
return 0 if $passes >= $retry_conf->[0];
if ( ref( $retry_conf->[$passes] ) eq 'CODE' ) {
$retry_conf->[$passes]->( $self, $pid_file, $passes );
}
else {
sleep( $retry_conf->[$passes] ) if $retry_conf->[$passes];
}
goto EXISTS;
}
print {$pid_fh} int( abs($newpid) );
close $pid_fh;
return 1;
}
sub _sysopen {
my ($pid_file) = @_;
sysopen( my $pid_fh, $pid_file, Fcntl::O_WRONLY() | Fcntl::O_EXCL() | Fcntl::O_CREAT() ) || return;
return $pid_fh;
}
sub _kill { ## no critic(RequireArgUnpacking
return CORE::kill(@_); # goto &CORE::kill; is problematic
}
sub get_run_lock {
my ( $pid_file, $min_age_seconds, $max_age_seconds, $cmdline_regex ) = @_;
$pid_file or die("Need a pid file to get a run lock.");
defined $min_age_seconds or $min_age_seconds = 15 * 60;
defined $max_age_seconds or $max_age_seconds = 20 * 60 * 60;
foreach ( 1 .. 2 ) {
my $upid = Cpanel::Unix::PID::Tiny->new();
my $got_pid = $upid->pid_file($pid_file);
return 1 if ($got_pid);
my @pid_stat = stat($pid_file);
next if ( !@pid_stat );
my $pid_age = time() - $pid_stat[9];
return 0 if ( $min_age_seconds && $pid_age < $min_age_seconds );
my $active_pid = $upid->get_pid_from_pidfile($pid_file);
if ( !-e "/proc/$active_pid" ) {
unlink $pid_file;
next;
}
open( my $fh, '<', "/proc/$active_pid/cmdline" ) or next;
my $cmdline = <$fh>;
if ( $max_age_seconds && $pid_age > $max_age_seconds ) {
_kill( 'TERM', $active_pid );
unlink $pid_file;
}
if ( !$cmdline or ( $cmdline_regex && $cmdline !~ $cmdline_regex ) ) {
unlink $pid_file;
}
}
return undef; # I give up!
}
1;
} # --- END Cpanel/Unix/PID/Tiny.pm
{ # --- BEGIN Cpanel/Encoder/ASCII.pm
package Cpanel::Encoder::ASCII;
use strict;
use warnings;
sub to_hex {
my ($readable) = @_;
$readable =~ s<\\><\\\\>g;
$readable =~ s<([\0-\x{1f}\x{7f}-\x{ff}])><sprintf '\x{%02x}', ord $1>eg;
return $readable;
}
1;
} # --- END Cpanel/Encoder/ASCII.pm
{ # --- BEGIN Cpanel/UTF8/Strict.pm
package Cpanel::UTF8::Strict;
use strict;
use warnings;
sub decode {
utf8::decode( $_[0] ) or do {
local ( $@, $! );
require Cpanel::Encoder::ASCII;
die sprintf "Invalid UTF-8 in string: “%s”", Cpanel::Encoder::ASCII::to_hex( $_[0] );
};
return $_[0];
}
1;
} # --- END Cpanel/UTF8/Strict.pm
{ # --- BEGIN Cpanel/JSON.pm
package Cpanel::JSON;
use strict;
# use Cpanel::Fcntl::Constants ();
# use Cpanel::FHUtils::Tiny ();
# use Cpanel::LoadFile::ReadFast ();
use JSON::XS ();
# use Cpanel::UTF8::Strict ();
our $NO_DECODE_UTF8 = 0;
our $DECODE_UTF8 = 1;
our $LOAD_STRICT = 0;
our $LOAD_RELAXED = 1;
our $MAX_LOAD_LENGTH_UNLIMITED = 0;
our $MAX_LOAD_LENGTH = 65535;
our $MAX_PRIV_LOAD_LENGTH = 4194304; # four megs
our $XS_ConvertBlessed_obj;
our $XS_RelaxedConvertBlessed_obj;
our $XS_NoSetUTF8RelaxedConvertBlessed_obj;
our $XS_NoSetUTF8ConvertBlessed_obj;
our $VERSION = '2.5';
my $copied_boolean = 0;
sub DumpFile {
my ( $file, $data ) = @_;
if ( Cpanel::FHUtils::Tiny::is_a($file) ) {
print {$file} Dump($data) || return 0;
}
else {
if ( open( my $fh, '>', $file ) ) {
print {$fh} Dump($data);
close($fh);
}
else {
return 0;
}
}
return 1;
}
sub copy_boolean {
if ( !$copied_boolean ) {
*Types::Serialiser::Boolean:: = *JSON::PP::Boolean::;
$copied_boolean = 1;
}
return;
}
sub _create_new_json_object {
copy_boolean() if !$copied_boolean;
return JSON::XS->new()->shrink(1)->allow_nonref(1)->convert_blessed(1);
}
sub true {
copy_boolean() if !$copied_boolean;
my $x = 1;
return bless \$x, 'Types::Serialiser::Boolean';
}
sub false {
copy_boolean() if !$copied_boolean;
my $x = 0;
return bless \$x, 'Types::Serialiser::Boolean';
}
sub pretty_dump {
return _create_new_json_object()->pretty(1)->encode( $_[0] );
}
my $XS_Canonical_obj;
sub canonical_dump {
return ( $XS_Canonical_obj ||= _create_new_json_object()->canonical(1) )->encode( $_[0] );
}
sub pretty_canonical_dump {
return _create_new_json_object()->canonical(1)->indent->space_before->space_after->encode( $_[0] );
}
sub Dump {
return ( $XS_ConvertBlessed_obj ||= _create_new_json_object() )->encode( $_[0] );
}
sub Load {
local $@;
return eval { ( $XS_ConvertBlessed_obj ||= _create_new_json_object() )->decode( $_[0] ); } // ( $@ && _throw_json_error( $@, $_[1], \$_[0] ) ) || undef;
}
sub LoadRelaxed {
local $@;
return eval { ( $XS_RelaxedConvertBlessed_obj ||= _create_new_json_object()->relaxed(1) )->decode( $_[0] ); } // ( $@ && _throw_json_error( $@, $_[1], \$_[0] ) ) || undef;
}
sub _throw_json_error {
my ( $exception, $path, $dataref ) = @_;
local $@;
require Cpanel::Exception;
die $exception if $@;
die 'Cpanel::Exception'->can('create')->( 'JSONParseError', { 'error' => $exception, 'path' => $path, 'dataref' => $dataref } );
}
sub LoadNoSetUTF8 {
local $@;
return eval { ( $XS_NoSetUTF8ConvertBlessed_obj ||= _create_new_no_set_utf8_json_object() )->decode( $_[0] ); } // ( $@ && _throw_json_error( $@, $_[1], \$_[0] ) ) || undef;
}
sub LoadNoSetUTF8Relaxed {
local $@;
return eval { ( $XS_NoSetUTF8RelaxedConvertBlessed_obj ||= _create_new_no_set_utf8_json_object()->relaxed(1) )->decode( $_[0] ); } // ( $@ && _throw_json_error( $@, $_[1], \$_[0] ) ) || undef;
}
sub _create_new_no_set_utf8_json_object {
my $obj = _create_new_json_object();
if ( $obj->can('no_set_utf8') ) {
$obj->no_set_utf8(1);
}
else {
warn "JSON::XS is missing the no_set_utf8 flag";
}
return $obj;
}
sub SafeLoadFile { # only allow a small bit of data to be loaded
return _LoadFile( $_[0], $MAX_LOAD_LENGTH, $_[2] || $NO_DECODE_UTF8, $_[1], $LOAD_STRICT );
}
sub LoadFile {
return _LoadFile( $_[0], $MAX_LOAD_LENGTH_UNLIMITED, $_[2] || $NO_DECODE_UTF8, $_[1], $LOAD_STRICT );
}
sub LoadFileRelaxed {
return _LoadFile( $_[0], $MAX_LOAD_LENGTH_UNLIMITED, $_[2] || $NO_DECODE_UTF8, $_[1], $LOAD_RELAXED );
}
sub LoadFileNoSetUTF8 {
return _LoadFile( $_[0], $_[1] || $MAX_LOAD_LENGTH_UNLIMITED, $DECODE_UTF8, $_[2], $LOAD_STRICT );
}
sub _LoadFile {
my ( $file, $max, $decode_utf8, $path, $relaxed ) = @_;
my $data;
if ( Cpanel::FHUtils::Tiny::is_a($file) ) {
if ($max) {
my $togo = $max;
$data = '';
my $bytes_read;
while ( $bytes_read = read( $file, $data, $togo, length $data ) && length $data < $max ) {
$togo -= $bytes_read;
}
}
else {
Cpanel::LoadFile::ReadFast::read_all_fast( $file, $data );
}
}
else {
local $!;
open( my $fh, '<:stdio', $file ) or do {
my $err = $!;
require Cpanel::Carp;
die Cpanel::Carp::safe_longmess("Cannot open “$file”: $err");
};
Cpanel::LoadFile::ReadFast::read_all_fast( $fh, $data );
if ( !length $data ) {
require Cpanel::Carp;
die Cpanel::Carp::safe_longmess("“$file” is empty.");
}
close $fh or warn "close($file) failed: $!";
}
if ( $decode_utf8 && $decode_utf8 == $DECODE_UTF8 ) {
Cpanel::UTF8::Strict::decode($data);
return $relaxed ? LoadNoSetUTF8Relaxed( $data, $path || $file ) : LoadNoSetUTF8( $data, $path || $file );
}
return $relaxed ? LoadRelaxed( $data, $path || $file ) : Load( $data, $path || $file );
}
sub SafeDump {
my $raw_json = ( $XS_ConvertBlessed_obj ||= _create_new_json_object() )->encode( $_[0] );
$raw_json =~ s{\/}{\\/}g if $raw_json =~ tr{/}{};
return $raw_json;
}
sub _fh_looks_like_json {
my ($fh) = @_;
my $bytes_read = 0;
my $buffer = q{};
local $!;
while ( $buffer !~ tr{ \t\r\n\f}{}c && !eof $fh ) {
$bytes_read += ( read( $fh, $buffer, 1, length $buffer ) // die "read() failed: $!" );
}
return (
_string_looks_like_json($buffer),
\$buffer,
);
}
sub _string_looks_like_json { ##no critic qw(RequireArgUnpacking)
return $_[0] =~ m/\A\s*[\[\{"0-9]/ ? 1 : 0;
}
sub looks_like_json { ##no critic qw(RequireArgUnpacking)
if ( Cpanel::FHUtils::Tiny::is_a( $_[0] ) ) {
my $fh = $_[0];
my ( $looks_like_json, $fragment_ref ) = _fh_looks_like_json($fh);
my $bytes_read = length $$fragment_ref;
if ($bytes_read) {
seek( $fh, -$bytes_read, $Cpanel::Fcntl::Constants::SEEK_CUR ) or die "seek() failed: $!";
}
return $looks_like_json;
}
return _string_looks_like_json( $_[0] );
}
1;
} # --- END Cpanel/JSON.pm
{ # --- BEGIN Cpanel/JSON/FailOK.pm
package Cpanel::JSON::FailOK;
use strict;
use warnings;
sub LoadJSONModule {
local $@;
my $load_ok = eval {
local $SIG{'__DIE__'}; # Suppress spewage as we may be reading an invalid cache
local $SIG{'__WARN__'}; # and since failure is ok to throw it away
require Cpanel::JSON; # PPI NO PARSE - FailOK
1;
};
if ( !$load_ok && !$ENV{'CPANEL_BASE_INSTALL'} && index( $^X, '/usr/local/cpanel' ) == 0 ) {
warn $@;
}
return $load_ok ? 1 : 0;
}
sub LoadFile {
return undef if !$INC{'Cpanel/JSON.pm'};
return eval {
local $SIG{'__DIE__'}; # Suppress spewage as we may be reading an invalid cache
local $SIG{'__WARN__'}; # and since failure is ok to throw it away
Cpanel::JSON::LoadFile(@_); # PPI NO PARSE - inc check above
};
}
1;
} # --- END Cpanel/JSON/FailOK.pm
{ # --- BEGIN Cpanel/ConfigFiles.pm
package Cpanel::ConfigFiles;
use strict;
our $VERSION = '1.4';
our $cpanel_users = '/var/cpanel/users';
our $cpanel_users_cache = '/var/cpanel/users.cache';
our $backup_config_touchfile = '/var/cpanel/config/backups/metadata_disabled';
our $backup_config_touchfile_dir = '/var/cpanel/config/backups/';
our $backup_config = '/var/cpanel/backups/config';
our $cpanel_config_file = '/var/cpanel/cpanel.config';
our $cpanel_config_cache_file = '/var/cpanel/cpanel.config.cache';
our $cpanel_config_defaults_file = '/usr/local/cpanel/etc/cpanel.config';
our $features_cache_dir = "/var/cpanel/features.cache";
our $BASE_INSTALL_IN_PROGRESS_FILE = '/root/installer.lock';
our $CPSRVD_CHECK_CPLISC_FILE = q{/var/cpanel/cpsrvd_check_license};
our $ROOT_CPANEL_HOMEDIR = '/var/cpanel/userhomes/cpanel';
our $RESELLERS_FILE = '/var/cpanel/resellers';
our $RESELLERS_NAMESERVERS_FILE = '/var/cpanel/resellers-nameservers';
our $ACCOUNTING_LOG_FILE = '/var/cpanel/accounting.log';
our $FEATURES_DIR = '/var/cpanel/features';
our $BANDWIDTH_LIMIT_DIR = '/var/cpanel/bwlimited';
our $CUSTOM_PERL_MODULES_DIR = '/var/cpanel/perl';
our $PACKAGES_DIR; #defined below
our $QUOTA_CONF_FILE = '/etc/quota.conf';
our $DEDICATED_IPS_FILE = '/etc/domainips';
our $DELEGATED_IPS_DIR = '/var/cpanel/dips';
our $MAIN_IPS_DIR = '/var/cpanel/mainips';
our $RESERVED_IPS_FILE = '/etc/reservedips';
our $RESERVED_IP_REASONS_FILE = '/etc/reservedipreasons';
our $IP_ADDRESS_POOL_FILE = '/etc/ipaddrpool';
our $ACL_LISTS_DIR = '/var/cpanel/acllists';
our $OUTGOING_MAIL_SUSPENDED_USERS_FILE = '/etc/outgoing_mail_suspended_users';
our $OUTGOING_MAIL_HOLD_USERS_FILE = '/etc/outgoing_mail_hold_users';
our $TRUEUSEROWNERS_FILE = '/etc/trueuserowners';
our $TRUEUSERDOMAINS_FILE = '/etc/trueuserdomains';
our $USERDOMAINS_FILE = '/etc/userdomains';
our $DBOWNERS_FILE = '/etc/dbowners';
our $DOMAINUSERS_FILE = '/etc/domainusers';
our $LOCALDOMAINS_FILE = '/etc/localdomains';
our $REMOTEDOMAINS_FILE = '/etc/remotedomains';
our $SECONDARYMX_FILE = '/etc/secondarymx';
our $USERBWLIMITS_FILE = '/etc/userbwlimits';
our $MAILIPS_FILE = '/etc/mailips';
our $MAILHELO_FILE = '/etc/mailhelo';
our $NEIGHBOR_NETBLOCKS_FILE = '/etc/neighbor_netblocks';
our $CPANEL_MAIL_NETBLOCKS_FILE = '/etc/cpanel_mail_netblocks';
our $GREYLIST_TRUSTED_NETBLOCKS_FILE = '/etc/greylist_trusted_netblocks';
our $GREYLIST_COMMON_MAIL_PROVIDERS_FILE = '/etc/greylist_common_mail_providers';
our $RECENT_RECIPIENT_MAIL_SERVER_IPS_FILE = '/etc/recent_recipient_mail_server_ips';
our $DEMOUSERS_FILE = '/etc/demousers';
our $APACHE_CONFIG_DIR = '/var/cpanel/conf/apache';
our $APACHE_PRIMARY_VHOSTS_FILE = '/var/cpanel/conf/apache/primary_virtual_hosts.conf';
our $MYSQL_CNF = '/etc/my.cnf';
our $SERVICEAUTH_DIR = '/var/cpanel/serviceauth';
our $DORMANT_SERVICES_DIR = '/var/cpanel/dormant_services';
our $DOMAIN_KEYS_ROOT = '/var/cpanel/domain_keys';
our $USER_NOTIFICATIONS_DIR = '/var/cpanel/user_notifications';
our $DATABASES_INFO_DIR = '/var/cpanel/databases';
our $CPANEL_ROOT = '/usr/local/cpanel';
our $MAILMAN_ROOT = "$CPANEL_ROOT/3rdparty/mailman";
our $FPM_CONFIG_ROOT = "/var/cpanel/php-fpm.d";
our $FPM_ROOT = "/var/cpanel/php-fpm";
our $MAILMAN_LISTS_DIR = "$MAILMAN_ROOT/lists";
our $MAILMAN_USER = 'mailman';
our $FTP_PASSWD_DIR = '/etc/proftpd';
our $FTP_SYMLINKS_DIR = '/etc/pure-ftpd';
our $VALIASES_DIR = '/etc/valiases';
our $VDOMAINALIASES_DIR = '/etc/vdomainaliases';
our $VFILTERS_DIR = '/etc/vfilters';
our $JAILSHELL_PATH = '/usr/local/cpanel/bin/jailshell';
our @COMMONDOMAINS_FILES = qw{/usr/local/cpanel/etc/commondomains /var/cpanel/commondomains};
our @IP_ADDRESS_LIST_FILES = qw{ /etc/ips /etc/ips.dnsmaster /etc/ip.remotedns /etc/ips.remotedns };
our $BANDWIDTH_DIRECTORY = '/var/cpanel/bandwidth';
our $BANDWIDTH_CACHE_DIRECTORY = '/var/cpanel/bandwidth.cache';
our $BANDWIDTH_USAGE_CACHE_DIRECTORY = '/var/cpanel/bwusagecache';
our $TEMPLATE_COMPILE_DIR = '/var/cpanel/template_compiles';
our $DOVECOT_SNI_CONF = '/etc/dovecot/sni.conf';
our $GOOGLE_AUTH_TEMPFILE_PREFIX = '/var/cpanel/backups/google_oauth_tempfile_';
our $APACHE_LOGFILE_CLEANUP_QUEUE = '/var/cpanel/apache_logfile_cleanup.json';
our $SKIP_REPO_SETUP_FLAG = '/var/cpanel/skip-repo-setup';
BEGIN {
$PACKAGES_DIR = '/var/cpanel/packages';
}
1;
} # --- END Cpanel/ConfigFiles.pm
{ # --- BEGIN Cpanel/Destruct.pm
package Cpanel::Destruct;
use strict;
my $in_global_destruction = 0;
my ( $package, $filename, $line, $subroutine ); # preallocate
sub in_dangerous_global_destruction {
if ( !$INC{'Test2/API.pm'} ) {
return 1 if in_global_destruction() && $INC{'Cpanel/BinCheck.pm'};
}
return 0;
}
sub in_global_destruction {
return $in_global_destruction if $in_global_destruction;
if ( defined( ${^GLOBAL_PHASE} ) ) {
if ( ${^GLOBAL_PHASE} eq 'DESTRUCT' ) {
$in_global_destruction = 1;
}
}
else {
local $SIG{'__WARN__'} = \&_detect_global_destruction_pre_514_WARN_handler;
warn;
}
return $in_global_destruction;
}
sub _detect_global_destruction_pre_514_WARN_handler {
if ( length $_[0] > 26 && rindex( $_[0], 'during global destruction.' ) == ( length( $_[0] ) - 26 ) ) {
$in_global_destruction = 1;
}
return;
}
1;
} # --- END Cpanel/Destruct.pm
{ # --- BEGIN Cpanel/Finally.pm
package Cpanel::Finally;
use strict;
sub new {
my ( $class, @todo_crs ) = @_;
return bless { 'pid' => $$, 'todo' => \@todo_crs }, $class;
}
sub add {
my ( $self, @new_crs ) = @_;
push @{ $self->{'todo'} }, @new_crs;
return;
}
sub skip {
my ($self) = @_;
return delete $self->{'todo'};
}
sub DESTROY {
my ($self) = @_;
return if $$ != $self->{'pid'} || !$self->{'todo'};
local $@; #prevent insidious clobber of error messages
while ( @{ $self->{'todo'} } ) {
my $ok = eval {
while ( my $item = shift @{ $self->{'todo'} } ) {
$item->();
}
1;
};
warn $@ if !$ok;
}
return;
}
1;
} # --- END Cpanel/Finally.pm
{ # --- BEGIN Cpanel/FindBin.pm
package Cpanel::FindBin;
use strict;
use warnings;
use constant _ENOENT => 2;
our $VERSION = 1.2;
my %bin_cache;
my @default_path = qw( /usr/bin /usr/local/bin /bin /sbin /usr/sbin /usr/local/sbin );
sub findbin { ## no critic qw(Subroutines::RequireArgUnpacking)
my $binname = shift;
return if !$binname;
my @lookup_path = get_path(@_);
my $nocache = grep( /nocache/, @_ );
if ( !$nocache && exists $bin_cache{$binname} && $bin_cache{$binname} ne '' ) {
return $bin_cache{$binname};
}
foreach my $path (@lookup_path) {
$path .= "/$binname";
if ( -e $path ) {
if ( -x _ ) {
$bin_cache{$binname} = $path unless $nocache;
return $path;
}
else {
warn "“$path” exists but is not executable; ignoring.\n";
}
}
elsif ( $! != _ENOENT() ) {
warn "stat($path): $!\n";
}
}
return;
}
sub get_path {
if ( !$_[0] ) {
return @default_path;
}
elsif ( scalar @_ > 1 ) {
my %opts;
%opts = @_ if ( scalar @_ % 2 == 0 );
if ( exists $opts{'path'} && ref $opts{'path'} eq 'ARRAY' ) {
return @{ $opts{'path'} };
}
else {
return @_;
}
}
elsif ( ref $_[0] eq 'ARRAY' ) {
return @{ $_[0] };
}
return @default_path;
}
1;
} # --- END Cpanel/FindBin.pm
{ # --- BEGIN Cpanel/SafeRun/Simple.pm
package Cpanel::SafeRun::Simple;
use strict;
# use Cpanel::FHUtils::Autoflush ();
# use Cpanel::LoadFile::ReadFast ();
BEGIN {
eval { require Proc::FastSpawn; };
}
my $KEEP_STDERR = 0;
my $MERGE_STDERR = 1;
my $NULL_STDERR = 2;
my $NULL_STDOUT = 3;
sub saferun_r {
return _saferun_r( \@_ );
}
sub _saferun_r { ## no critic qw(Subroutines::ProhibitExcessComplexity)
my ( $cmdline, $error_flag ) = @_;
if ($Cpanel::AccessIds::ReducedPrivileges::PRIVS_REDUCED) { # PPI NO PARSE -- can't be reduced if the module isn't loaded
eval "use Cpanel::Carp;"; ## no critic qw(BuiltinFunctions::ProhibitStringyEval)
die Cpanel::Carp::safe_longmess( __PACKAGE__ . " cannot be used with ReducedPrivileges. Use Cpanel::SafeRun::Object instead" );
}
elsif ( scalar @$cmdline == 1 && $cmdline->[0] =~ tr{><*?[]`$()|;&#$\\\r\n\t }{} ) {
eval "use Cpanel::Carp;"; ## no critic qw(BuiltinFunctions::ProhibitStringyEval)
die Cpanel::Carp::safe_longmess( __PACKAGE__ . " prevents accidental execution of a shell. If you intended to execute a shell use saferun(" . join( ',', '/bin/sh', '-c', @$cmdline ) . ")" );
}
my $output;
if ( index( $cmdline->[0], '/' ) == 0 ) {
my ($check) = !-e $cmdline->[0] && $cmdline->[0] =~ /[\s<>&\|\;]/ ? split( /[\s<>&\|\;]/, $cmdline->[0], 2 ) : $cmdline->[0];
if ( !-x $check ) {
$? = -1;
return \$output;
}
}
$error_flag ||= 0;
local ($/);
my ( $pid, $prog_fh, $did_fastspawn );
if ( $INC{'Proc/FastSpawn.pm'} ) { # may not be available yet due to upcp.static or updatenow.static
my @env = map { exists $ENV{$_} && $_ ne 'IFS' && $_ ne 'CDPATH' && $_ ne 'ENV' && $_ ne 'BASH_ENV' ? ( $_ . '=' . ( $ENV{$_} // '' ) ) : () } keys %ENV;
my ($child_write);
pipe( $prog_fh, $child_write ) or warn "Failed to pipe(): $!";
my $null_fh;
if ( $error_flag == $NULL_STDERR || $error_flag == $NULL_STDOUT ) {
open( $null_fh, '>', '/dev/null' ) or die "Failed open /dev/null: $!";
}
Cpanel::FHUtils::Autoflush::enable($_) for ( $prog_fh, $child_write );
$did_fastspawn = 1;
my $stdout_fileno = fileno($child_write);
my $stderr_fileno = -1;
if ( $error_flag == $MERGE_STDERR ) {
$stderr_fileno = fileno($child_write);
}
elsif ( $error_flag == $NULL_STDERR ) {
$stderr_fileno = fileno($null_fh);
}
elsif ( $error_flag == $NULL_STDOUT ) {
$stdout_fileno = fileno($null_fh);
$stderr_fileno = fileno($child_write);
}
$pid = Proc::FastSpawn::spawn_open3(
-1, # stdin
$stdout_fileno, # stdout
$stderr_fileno, # stderr
$cmdline->[0], # program
$cmdline, # args
\@env, #env
);
}
else {
if ( $pid = open( $prog_fh, '-|' ) ) {
}
elsif ( defined $pid ) {
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
$ENV{'PATH'} = each %{ { ( $ENV{'PATH'} || '' ) => undef } }; # untaint
if ( $error_flag == $MERGE_STDERR ) {
open( STDERR, '>&STDOUT' ) or die "Failed to redirect STDERR to STDOUT: $!";
}
elsif ( $error_flag == $NULL_STDERR ) {
open( STDERR, '>', '/dev/null' ) or die "Failed to open /dev/null: $!";
}
elsif ( $error_flag == $NULL_STDOUT ) {
open( STDERR, '>&STDOUT' ) or die "Failed to redirect STDERR to STDOUT: $!";
open( STDOUT, '>', '/dev/null' ) or die "Failed to redirect STDOUT to /dev/null: $!";
}
exec(@$cmdline) or exit( $! || 127 );
}
else {
die "fork() failed: $!";
}
}
if ( !$prog_fh || !$pid ) {
$? = -1; ## no critic qw(Variables::RequireLocalizedPunctuationVars)
return \$output;
}
Cpanel::LoadFile::ReadFast::read_all_fast( $prog_fh, $output );
close($prog_fh);
waitpid( $pid, 0 ) if $did_fastspawn;
return \$output;
}
sub _call_saferun {
my ( $args, $flag ) = @_;
my $ref = _saferun_r( $args, $flag || 0 );
return $$ref if $ref;
return;
}
sub saferun {
return _call_saferun( \@_, $KEEP_STDERR );
}
sub saferunallerrors {
return _call_saferun( \@_, $MERGE_STDERR );
}
sub saferunnoerror {
return _call_saferun( \@_, $NULL_STDERR );
}
sub saferunonlyerrors {
return _call_saferun( \@_, $NULL_STDOUT );
}
1;
} # --- END Cpanel/SafeRun/Simple.pm
{ # --- BEGIN Cpanel/Readlink.pm
package Cpanel::Readlink;
use strict;
use warnings;
# use Cpanel::Autodie ();
# use Cpanel::Exception ();
our $MAX_SYMLINK_DEPTH = 1024;
sub deep {
my ( $link, $provide_trailing_slash ) = @_;
die Cpanel::Exception::create( 'MissingParameter', 'Provide a link path.' ) if !length $link;
if ( length($link) > 1 && substr( $link, -1, 1 ) eq '/' ) {
$link = substr( $link, 0, length($link) - 1 );
return deep( $link, 1 );
}
if ( !-l $link ) {
return $provide_trailing_slash ? qq{$link/} : $link;
}
my %is_link;
$is_link{$link} = 1;
my $depth = 0;
my $base = _get_base_for($link);
if ( substr( $link, 0, 1 ) ne '/' ) {
$base = cwd() . '/' . $base;
}
while ( ( $is_link{$link} ||= -l $link ) && ++$depth <= $MAX_SYMLINK_DEPTH ) {
$link = Cpanel::Autodie::readlink($link);
if ( substr( $link, 0, 1 ) ne '/' ) {
$link = $base . '/' . $link;
}
$base = _get_base_for($link);
}
return $provide_trailing_slash ? qq{$link/} : $link;
}
sub _get_base_for {
my $basename = shift;
my @path = split( '/', $basename );
pop(@path);
return join( '/', @path );
}
sub _pwd {
require Cpanel::FindBin;
my $bin = Cpanel::FindBin::findbin('pwd');
{
no warnings 'redefine';
*Cpanel::Readlink::_pwd = sub { return $bin; };
}
return $bin;
}
sub cwd {
goto \&Cwd::cwd if $INC{'Cwd.pm'};
require Cpanel::SafeRun::Simple;
my $cwd = Cpanel::SafeRun::Simple::saferun( _pwd() );
chomp $cwd;
return $cwd;
}
1;
} # --- END Cpanel/Readlink.pm
{ # --- BEGIN Cpanel/FileUtils/Write.pm
package Cpanel::FileUtils::Write;
use strict;
use warnings;
# use Cpanel::Fcntl::Constants ();
use Cpanel::Autodie ( 'rename', 'syswrite_sigguard', 'seek', 'print', 'truncate' );
# use Cpanel::Exception ();
# use Cpanel::FileUtils::Open ();
# use Cpanel::Finally ();
# use Cpanel::Debug ();
our $Errno_EEXIST = 17;
our $MAX_TMPFILE_CREATE_ATTEMPTS = 1024;
my $DEFAULT_PERMS = 0600;
my $_WRONLY_CREAT_EXCL;
sub write_fh { ##no critic qw(RequireArgUnpacking)
my $fh = $_[0];
Cpanel::Autodie::seek( $fh, 0, 0 );
Cpanel::Autodie::print( $fh, $_[1] );
Cpanel::Autodie::truncate( $fh, tell($fh) );
return 1;
}
sub write {
return _write_to_tmpfile( @_[ 0 .. 2 ], \&_write_finish );
}
sub overwrite {
return _write_to_tmpfile( @_[ 0 .. 2 ], \&_overwrite_finish );
}
sub overwrite_no_exceptions {
my $fh;
local $@;
eval {
$fh = overwrite(@_);
1;
} or Cpanel::Debug::log_warn("overwrite exception: $@");
return !!$fh;
}
sub _write_to_tmpfile { ##no critic qw(RequireArgUnpacking)
my ( $filename, $perms_or_hr, $finish_cr ) = ( $_[0], $_[2], $_[3] );
if ( !defined $filename ) {
exists $INC{'Carp.pm'} ? Carp::confess("write() called with undefined filename") : die("write() called with undefined filename");
}
if ( ref $filename ) {
die "Use write_fh to write to a file handle. ($filename is a filehandle, right?)";
}
my ( $fh, $tmpfile_is_renamed );
if ( -l $filename ) {
require Cpanel::Readlink;
$filename = Cpanel::Readlink::deep($filename);
}
my ( $callback_cr, $tmp_perms );
if ( 'HASH' eq ref $perms_or_hr ) {
$callback_cr = $perms_or_hr->{'before_installation'};
}
else {
$tmp_perms = $perms_or_hr;
}
$tmp_perms //= $DEFAULT_PERMS;
my ( $tmpfile, $attempts ) = ( '', 0 );
while (1) {
local $!;
my $rand = each %{ { rand(99999999) => undef } }; #untaint
$rand = sprintf( '%x', substr( $rand, 2 ) );
my $last_slash_idx = rindex( $filename, '/' );
$tmpfile = $filename;
substr( $tmpfile, 1 + $last_slash_idx, 0 ) = ".tmp.$rand.";
last if Cpanel::FileUtils::Open::sysopen_with_real_perms(
$fh,
$tmpfile,
( $_WRONLY_CREAT_EXCL ||= ( $Cpanel::Fcntl::Constants::O_CREAT | $Cpanel::Fcntl::Constants::O_EXCL | $Cpanel::Fcntl::Constants::O_WRONLY ) ),
$tmp_perms,
);
if ( $! != $Errno_EEXIST ) {
die Cpanel::Exception::create( 'IO::FileCreateError', [ error => $!, path => $tmpfile, permissions => $tmp_perms ] );
}
++$attempts;
if ( $attempts >= $MAX_TMPFILE_CREATE_ATTEMPTS ) {
die Cpanel::Exception::create_raw( 'IO::FileCreateError', "Too many ($MAX_TMPFILE_CREATE_ATTEMPTS) failed attempts to create a temp file as EUID $> and GID $) based on “$filename”! The last tried file was “$tmpfile”, and the last error was: $!" );
}
}
my $finally = Cpanel::Finally->new(
sub {
if ( !$tmpfile_is_renamed ) {
Cpanel::Autodie::unlink_if_exists($tmpfile);
}
return;
}
);
if ( my $ref = ref $_[1] ) {
if ( $ref eq 'SCALAR' ) {
_write_fh( $fh, ${ $_[1] } );
}
else {
die Cpanel::Exception::create( 'InvalidParameter', 'Invalid content type “[_1]”, expect a scalar.', [$ref] );
}
}
else {
_write_fh( $fh, $_[1] );
}
$callback_cr->($fh) if $callback_cr;
$tmpfile_is_renamed = $finish_cr->( $tmpfile, $filename );
if ( !$tmpfile_is_renamed ) {
Cpanel::Autodie::unlink_if_exists($tmpfile);
}
$finally->skip();
return $fh;
}
*_syswrite = *Cpanel::Autodie::syswrite_sigguard;
our $DEBUG_WRITE;
sub _write_fh {
if ( length $_[1] ) {
my $pos = 0;
do {
local $SIG{'XFSZ'} = 'IGNORE' if $pos;
$pos += _syswrite( $_[0], $_[1], length( $_[1] ), $pos ) || do {
die "Zero bytes written, non-error!";
};
} while $pos < length( $_[1] );
}
return;
}
sub _write_finish {
Cpanel::Autodie::link(@_);
return 0;
}
*_overwrite_finish = *Cpanel::Autodie::rename;
1;
} # --- END Cpanel/FileUtils/Write.pm
{ # --- BEGIN Cpanel/FileUtils/Write/JSON/Lazy.pm
package Cpanel::FileUtils::Write::JSON::Lazy;
use strict;
use warnings;
sub write_file {
my ( $file_or_fh, $data, $perms ) = @_;
if ( exists $INC{'Cpanel/JSON.pm'} && exists $INC{'JSON/XS.pm'} && ( my $Dump = 'Cpanel::JSON'->can('Dump') ) ) { # PPI NO PARSE -- check earlier - must be quoted or it ends up in the stash
require Cpanel::FileUtils::Write if !$INC{'Cpanel/FileUtils/Write.pm'};
require Cpanel::FHUtils::Tiny if !$INC{'Cpanel/FHUtils/Tiny.pm'};
my $func = Cpanel::FHUtils::Tiny::is_a($file_or_fh) ? 'write_fh' : 'overwrite';
if ( $func eq 'write_fh' ) {
if ( !defined $perms ) {
$perms = 0600;
}
chmod( $perms, $file_or_fh ) or die "Failed to set permissions on the file handle passed to Cpanel::FileUtils::Write::JSON::Lazy::write_file because of an error: $!";
}
return Cpanel::FileUtils::Write->can($func)->(
$file_or_fh,
$Dump->($data),
$perms
);
}
return 0;
}
sub write_file_pretty {
my ( $file_or_fh, $data, $perms ) = @_;
if ( exists $INC{'Cpanel/JSON.pm'} && exists $INC{'JSON/XS.pm'} && ( my $Dump = 'Cpanel::JSON'->can('pretty_dump') ) ) { # PPI NO PARSE -- check earlier - must be quoted or it ends up in the stash
require Cpanel::FileUtils::Write if !$INC{'Cpanel/FileUtils/Write.pm'};
require Cpanel::FHUtils::Tiny if !$INC{'Cpanel/FHUtils/Tiny.pm'};
my $func = Cpanel::FHUtils::Tiny::is_a($file_or_fh) ? 'write_fh' : 'overwrite';
if ( $func eq 'write_fh' ) {
if ( !defined $perms ) {
$perms = 0600;
}
chmod( $perms, $file_or_fh ) or die "Failed to set permissions on the file handle passed to Cpanel::FileUtils::Write::JSON::Lazy::write_file because of an error: $!";
}
return Cpanel::FileUtils::Write->can($func)->(
$file_or_fh,
$Dump->($data),
$perms
);
}
return 0;
}
1;
} # --- END Cpanel/FileUtils/Write/JSON/Lazy.pm
{ # --- BEGIN Cpanel/CPAN/I18N/LangTags.pm
package Cpanel::CPAN::I18N::LangTags;
use strict;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw();
our @EXPORT_OK = qw(is_language_tag same_language_tag
extract_language_tags super_languages
similarity_language_tag is_dialect_of
locale2language_tag alternate_language_tags
encode_language_tag panic_languages
implicate_supers
implicate_supers_strictly
);
our %EXPORT_TAGS = ( 'ALL' => \@EXPORT_OK );
our %Panic;
our $VERSION = "0.35";
sub uniq { my %seen; return grep( !( $seen{$_}++ ), @_ ); } # a util function
sub is_language_tag {
my ($tag) = lc( $_[0] );
return 0 if $tag eq "i" or $tag eq "x";
return $tag =~ /^(?: # First subtag
[xi] | [a-z]{2,3}
)
(?: # Subtags thereafter
- # separator
[a-z0-9]{1,8} # subtag
)*
$/xs ? 1 : 0;
}
sub extract_language_tags {
my ($text) = $_[0] =~ m/(.+)/ # to make for an untainted result
? $1
: '';
return grep( !m/^[ixIX]$/s, # 'i' and 'x' aren't good tags
$text =~ m/
\b
(?: # First subtag
[iIxX] | [a-zA-Z]{2,3}
)
(?: # Subtags thereafter
- # separator
[a-zA-Z0-9]{1,8} # subtag
)*
\b
/xsg
);
}
sub same_language_tag {
my $el1 = &encode_language_tag( $_[0] );
return 0 unless defined $el1;
return $el1 eq &encode_language_tag( $_[1] ) ? 1 : 0;
}
sub similarity_language_tag {
my $lang1 = &encode_language_tag( $_[0] );
my $lang2 = &encode_language_tag( $_[1] );
return undef if !defined($lang1) and !defined($lang2);
return 0 if !defined($lang1) or !defined($lang2);
my @l1_subtags = split( '-', $lang1 );
my @l2_subtags = split( '-', $lang2 );
my $similarity = 0;
while ( @l1_subtags and @l2_subtags ) {
if ( shift(@l1_subtags) eq shift(@l2_subtags) ) {
++$similarity;
}
else {
last;
}
}
return $similarity;
}
sub is_dialect_of {
my $lang1 = &encode_language_tag( $_[0] );
my $lang2 = &encode_language_tag( $_[1] );
return undef if !defined($lang1) and !defined($lang2);
return 0 if !defined($lang1) or !defined($lang2);
return 1 if $lang1 eq $lang2;
return 0 if length($lang1) < length($lang2);
$lang1 .= '-';
$lang2 .= '-';
return ( substr( $lang1, 0, length($lang2) ) eq $lang2 ) ? 1 : 0;
}
sub super_languages {
my $lang1 = $_[0];
return () unless defined($lang1) && &is_language_tag($lang1);
$lang1 =~ s/^nb\b/no-bok/i; # yes, backwards
$lang1 =~ s/^nn\b/no-nyn/i; # yes, backwards
$lang1 =~ s/^[ix](-hakka\b)/zh$1/i; # goes the right way
my @l1_subtags = split( '-', $lang1 );
my @supers = ();
foreach my $bit (@l1_subtags) {
push @supers, scalar(@supers) ? ( $supers[-1] . '-' . $bit ) : $bit;
}
pop @supers if @supers;
shift @supers if @supers && $supers[0] =~ m<^[iIxX]$>s;
return reverse @supers;
}
sub locale2language_tag {
my $lang = $_[0] =~ m/(.+)/ # to make for an untainted result
? $1
: '';
return $lang if &is_language_tag($lang); # like "en"
$lang =~ tr<_><->; # "en_US" -> en-US
$lang =~ s<(?:[\.\@][-_a-zA-Z0-9]+)+$><>s; # "en_US.ISO8859-1" -> en-US
return $lang if &is_language_tag($lang);
return;
}
sub encode_language_tag {
my ($tag) = $_[0] || return undef;
return undef unless &is_language_tag($tag);
$tag =~ s/^iw\b/he/i; # Hebrew
$tag =~ s/^in\b/id/i; # Indonesian
$tag =~ s/^cre\b/cr/i; # Cree
$tag =~ s/^jw\b/jv/i; # Javanese
$tag =~ s/^[ix]-lux\b/lb/i; # Luxemburger
$tag =~ s/^[ix]-navajo\b/nv/i; # Navajo
$tag =~ s/^ji\b/yi/i; # Yiddish
$tag =~ s/^[ix]-hakka\b/zh-hakka/i; # Hakka
$tag =~ s/^nb\b/no-bok/i; # BACKWARDS for Bokmal
$tag =~ s/^nn\b/no-nyn/i; # BACKWARDS for Nynorsk
$tag =~ s/^[xiXI]-//s;
return "~" . uc($tag);
}
my %alt = qw( i x x i I X X I );
sub alternate_language_tags {
my $tag = $_[0];
return () unless &is_language_tag($tag);
my @em; # push 'em real goood!
if ( $tag =~ m/^[ix]-hakka\b(.*)/i ) {
push @em, "zh-hakka$1";
}
elsif ( $tag =~ m/^zh-hakka\b(.*)/i ) {
push @em, "x-hakka$1", "i-hakka$1";
}
elsif ( $tag =~ m/^he\b(.*)/i ) {
push @em, "iw$1";
}
elsif ( $tag =~ m/^iw\b(.*)/i ) {
push @em, "he$1";
}
elsif ( $tag =~ m/^in\b(.*)/i ) {
push @em, "id$1";
}
elsif ( $tag =~ m/^id\b(.*)/i ) {
push @em, "in$1";
}
elsif ( $tag =~ m/^[ix]-lux\b(.*)/i ) {
push @em, "lb$1";
}
elsif ( $tag =~ m/^lb\b(.*)/i ) {
push @em, "i-lux$1", "x-lux$1";
}
elsif ( $tag =~ m/^[ix]-navajo\b(.*)/i ) {
push @em, "nv$1";
}
elsif ( $tag =~ m/^nv\b(.*)/i ) {
push @em, "i-navajo$1", "x-navajo$1";
}
elsif ( $tag =~ m/^yi\b(.*)/i ) {
push @em, "ji$1";
}
elsif ( $tag =~ m/^ji\b(.*)/i ) {
push @em, "yi$1";
}
elsif ( $tag =~ m/^nb\b(.*)/i ) {
push @em, "no-bok$1";
}
elsif ( $tag =~ m/^no-bok\b(.*)/i ) {
push @em, "nb$1";
}
elsif ( $tag =~ m/^nn\b(.*)/i ) {
push @em, "no-nyn$1";
}
elsif ( $tag =~ m/^no-nyn\b(.*)/i ) {
push @em, "nn$1";
}
push @em, $alt{$1} . $2 if $tag =~ /^([XIxi])(-.+)/;
return @em;
}
{
my @panic = ( # MUST all be lowercase!
'sv' => [qw(nb no da nn)],
'da' => [qw(nb no sv nn)], # I guess
[qw(no nn nb)], [qw(no nn nb sv da)],
'is' => [qw(da sv no nb nn)],
'fo' => [qw(da is no nb nn sv)], # I guess
'pt' => [qw(es ca it fr)], # Portuguese, Spanish, Catalan, Italian, French
'ca' => [qw(es pt it fr)],
'es' => [qw(ca it fr pt)],
'it' => [qw(es fr ca pt)],
'fr' => [qw(es it ca pt)],
[
qw(
as bn gu kn ks kok ml mni mr ne or pa sa sd te ta ur
)
] => 'hi',
'hi' => [qw(bn pa as or)],
( [qw(ru be uk)] ) x 2, # Russian, Belarusian, Ukranian
'sr' => 'hr', 'hr' => 'sr', # Serb + Croat
'cs' => 'sk', 'sk' => 'cs', # Czech + Slovak
'ms' => 'id', 'id' => 'ms', # Malay + Indonesian
'et' => 'fi', 'fi' => 'et', # Estonian + Finnish
);
my ( $k, $v );
while (@panic) {
( $k, $v ) = splice( @panic, 0, 2 );
foreach my $k ( ref($k) ? @$k : $k ) {
foreach my $v ( ref($v) ? @$v : $v ) {
push @{ $Panic{$k} ||= [] }, $v unless $k eq $v;
}
}
}
}
sub panic_languages {
my ( @out, %seen );
foreach my $t (@_) {
next unless $t;
next if $seen{$t}++; # so we don't return it or hit it again
push @out, @{ $Panic{ lc $t } || next };
}
return grep !$seen{$_}++, @out, 'en';
}
sub implicate_supers {
my @languages = grep is_language_tag($_), @_;
my %seen_encoded;
foreach my $lang (@languages) {
$seen_encoded{ Cpanel::CPAN::I18N::LangTags::encode_language_tag($lang) } = 1;
}
my (@output_languages);
foreach my $lang (@languages) {
push @output_languages, $lang;
foreach my $s ( Cpanel::CPAN::I18N::LangTags::super_languages($lang) ) {
last if $seen_encoded{ Cpanel::CPAN::I18N::LangTags::encode_language_tag($s) };
push @output_languages, $s;
}
}
return uniq(@output_languages);
}
sub implicate_supers_strictly {
my @tags = grep is_language_tag($_), @_;
return uniq( @_, map super_languages($_), @_ );
}
1;
} # --- END Cpanel/CPAN/I18N/LangTags.pm
{ # --- BEGIN Cpanel/CPAN/I18N/LangTags/Detect.pm
package Cpanel::CPAN::I18N::LangTags::Detect;
use strict;
use vars qw( @ISA $VERSION $MATCH_SUPERS $USING_LANGUAGE_TAGS
$USE_LITERALS $MATCH_SUPERS_TIGHTLY);
BEGIN {
unless ( defined &DEBUG ) {
*DEBUG = sub () { 0 }
}
}
$VERSION = "1.04";
@ISA = ();
# use Cpanel::CPAN::I18N::LangTags ();
sub _uniq { my %seen; return grep( !( $seen{$_}++ ), @_ ); }
sub _normalize {
my (@languages) =
map lc($_),
grep $_,
map { ; $_, Cpanel::CPAN::I18N::LangTags::alternate_language_tags($_) } @_;
return _uniq(@languages) if wantarray;
return $languages[0];
}
sub detect () { return __PACKAGE__->ambient_langprefs; }
sub ambient_langprefs { # always returns things untainted
my $base_class = $_[0];
return $base_class->http_accept_langs
if length( $ENV{'REQUEST_METHOD'} || '' ); # I'm a CGI
my @languages;
foreach my $envname (qw( LANGUAGE LC_ALL LC_MESSAGES LANG )) {
next unless $ENV{$envname};
DEBUG and print "Noting \$$envname: $ENV{$envname}\n";
push @languages, map Cpanel::CPAN::I18N::LangTags::locale2language_tag($_),
split m/[,:]/, $ENV{$envname};
last; # first one wins
}
if ( $ENV{'IGNORE_WIN32_LOCALE'} ) {
}
elsif ( &_try_use('Win32::Locale') ) {
push @languages, Win32::Locale::get_language() || ''
if defined &Win32::Locale::get_language;
}
return _normalize @languages;
}
sub http_accept_langs {
no integer;
my $in = ( @_ > 1 ) ? $_[1] : $ENV{'HTTP_ACCEPT_LANGUAGE'};
return () unless defined $in and length $in;
$in =~ s/\([^\)]*\)//g; # nix just about any comment
if ( $in =~ m/^\s*([a-zA-Z][-a-zA-Z]+)\s*$/s ) {
return _normalize $1;
}
elsif ( $in =~ m/^\s*[a-zA-Z][-a-zA-Z]+(?:\s*,\s*[a-zA-Z][-a-zA-Z]+)*\s*$/s ) {
return _normalize( $in =~ m/([a-zA-Z][-a-zA-Z]+)/g );
}
$in =~ s/\s+//g; # Yes, we can just do without the WS!
my @in = $in =~ m/([^,]+)/g;
my %pref;
my $q;
foreach my $tag (@in) {
next unless $tag =~ m/^([a-zA-Z][-a-zA-Z]+)
(?:
;q=
(
\d* # a bit too broad of a RE, but so what.
(?:
\.\d+
)?
)
)?
$
/sx
;
$q = ( defined $2 and length $2 ) ? $2 : 1;
push @{ $pref{$q} }, lc $1;
}
return _normalize(
map @{ $pref{$_} },
sort { $b <=> $a }
keys %pref
);
}
my %tried = ();
sub _try_use { # Basically a wrapper around "require Modulename"
return $tried{ $_[0] } if exists $tried{ $_[0] }; # memoization
my $module = $_[0]; # ASSUME sane module name!
{
no strict 'refs';
return ( $tried{$module} = 1 )
if %{ $module . "::Lexicon" }
or @{ $module . "::ISA" };
}
print " About to use $module ...\n" if DEBUG;
{
local $SIG{'__DIE__'};
eval "require $module"; # used to be "use $module", but no point in that.
}
if ($@) {
print "Error using $module \: $@\n" if DEBUG > 1;
return $tried{$module} = 0;
}
else {
print " OK, $module is used\n" if DEBUG;
return $tried{$module} = 1;
}
}
1;
} # --- END Cpanel/CPAN/I18N/LangTags/Detect.pm
{ # --- BEGIN Cpanel/CPAN/Locale/Maketext.pm
package Cpanel::CPAN::Locale::Maketext;
use strict;
our @ISA;
our $VERSION;
our $MATCH_SUPERS;
our $USING_LANGUAGE_TAGS;
our $USE_LITERALS;
our $MATCH_SUPERS_TIGHTLY;
use constant IS_ASCII => ord('A') == 65;
BEGIN {
unless ( defined &DEBUG ) {
*DEBUG = sub () { 0 }
}
}
$VERSION = '1.13_89';
$VERSION = eval $VERSION;
@ISA = ();
$MATCH_SUPERS = 1;
$MATCH_SUPERS_TIGHTLY = 1;
$USING_LANGUAGE_TAGS = 1;
my $FORCE_REGEX_LAZY = '';
$USE_LITERALS = 1 unless defined $USE_LITERALS;
my %isa_scan = ();
my %isa_ones = ();
sub quant {
my ( $handle, $num, @forms ) = @_;
return $num if @forms == 0; # what should this mean?
return $forms[2] if @forms > 2 and $num == 0; # special zeroth case
return ( $handle->numf($num) . ' ' . $handle->numerate( $num, @forms ) );
}
sub numerate {
my ( $handle, $num, @forms ) = @_;
my $s = ( $num == 1 );
return '' unless @forms;
if ( @forms == 1 ) { # only the headword form specified
return $s ? $forms[0] : ( $forms[0] . 's' ); # very cheap hack.
}
else { # sing and plural were specified
return $s ? $forms[0] : $forms[1];
}
}
sub numf {
my ( $handle, $num ) = @_[ 0, 1 ];
if ( $num < 10_000_000_000 and $num > -10_000_000_000 and $num == int($num) ) {
$num += 0; # Just use normal integer stringification.
}
else {
$num = CORE::sprintf( '%G', $num );
}
while ( $num =~ s/$FORCE_REGEX_LAZY^([-+]?\d+)(\d{3})/$1,$2/os ) { 1 } # right from perlfaq5
$num =~ tr<.,><,.> if ref($handle) and $handle->{'numf_comma'};
return $num;
}
sub sprintf {
no integer;
my ( $handle, $format, @params ) = @_;
return CORE::sprintf( $format, @params );
}
use integer; # vroom vroom... applies to the whole rest of the module
sub language_tag {
my $it = ref( $_[0] ) || $_[0];
return undef unless $it =~ m/$FORCE_REGEX_LAZY([^':]+)(?:::)?$/os;
$it = lc($1);
$it =~ tr<_><->;
return $it;
}
sub encoding {
my $it = $_[0];
return (
( ref($it) && $it->{'encoding'} )
|| 'iso-8859-1' # Latin-1
);
}
sub fallback_languages { return ( 'i-default', 'en', 'en-US' ) }
sub fallback_language_classes { return () }
sub fail_with { # an actual attribute method!
my ( $handle, @params ) = @_;
return unless ref($handle);
$handle->{'fail'} = $params[0] if @params;
return $handle->{'fail'};
}
sub blacklist {
my ( $handle, @methods ) = @_;
unless ( defined $handle->{'blacklist'} ) {
no strict 'refs';
$handle->{'blacklist'} = {
map { $_ => 1 } (
qw/
blacklist
encoding
fail_with
failure_handler_auto
fallback_language_classes
fallback_languages
get_handle
init
language_tag
maketext
new
whitelist
/, grep { substr( $_, 0, 1 ) eq '_' } keys %{ __PACKAGE__ . "::" }
),
};
}
if ( scalar @methods ) {
$handle->{'blacklist'} = { %{ $handle->{'blacklist'} }, map { $_ => 1 } @methods };
}
delete $handle->{'_external_lex_cache'};
return;
}
sub whitelist {
my ( $handle, @methods ) = @_;
if ( scalar @methods ) {
if ( defined $handle->{'whitelist'} ) {
$handle->{'whitelist'} = { %{ $handle->{'whitelist'} }, map { $_ => 1 } @methods };
}
else {
$handle->{'whitelist'} = { map { $_ => 1 } @methods };
}
}
delete $handle->{'_external_lex_cache'};
return;
}
sub failure_handler_auto {
my $handle = shift;
my $phrase = shift;
$handle->{'failure_lex'} ||= {};
my $lex = $handle->{'failure_lex'};
my $value = $lex->{$phrase} ||= ( $phrase !~ tr/[// ? \"$phrase" : $handle->_compile($phrase) );
return ${$value} if ref($value) eq 'SCALAR';
return $value if ref($value) ne 'CODE';
{
local $SIG{'__DIE__'};
eval { $value = &$value( $handle, @_ ) };
}
if ($@) {
my $err = $@;
$err =~ s{\s+at\s+\(eval\s+\d+\)\s+line\s+(\d+)\.?\n?}
{\n in bracket code [compiled line $1],}s;
require Carp;
Carp::croak("Error in maketexting \"$phrase\":\n$err as used");
}
else {
return $value;
}
}
sub new {
my $class = ref( $_[0] ) || $_[0];
my $handle = bless {}, $class;
$handle->blacklist;
$handle->init;
return $handle;
}
sub init { return } # no-op
sub maketext {
unless ( @_ > 1 ) {
require Carp;
Carp::croak('maketext requires at least one parameter');
}
my ( $handle, $phrase ) = splice( @_, 0, 2 );
unless ( defined($handle) && defined($phrase) ) {
require Carp;
Carp::confess('No handle/phrase');
}
my $value;
if ( exists $handle->{'_external_lex_cache'}{$phrase} ) {
DEBUG and warn "* Using external lex cache version of \"$phrase\"\n";
$value = $handle->{'_external_lex_cache'}{$phrase};
}
else {
my $ns = ref($handle) || $handle;
foreach my $h_r ( @{ $isa_scan{$ns} || $handle->_lex_refs } ) {
DEBUG and warn "* Looking up \"$phrase\" in $h_r\n";
if ( defined( $value = $h_r->{$phrase} ) ) { # Minimize looking at $h_r as much as possible as an expensive tied hash to CDB_File
DEBUG and warn " Found \"$phrase\" in $h_r\n";
unless ( ref $value ) {
if ( !length $value ) {
DEBUG and warn " value is undef or ''";
if ( $isa_ones{"$h_r"} ) {
DEBUG and warn " $ns ($h_r) is Onesided and \"$phrase\" entry is undef or ''\n";
$value = $phrase;
}
}
if ( $handle->{'use_external_lex_cache'} ) {
$handle->{'_external_lex_cache'}{$phrase} = $value = ( $value !~ tr/[// ? \"$value" : $handle->_compile($value) );
}
else {
$h_r->{$phrase} = $value = ( $value !~ tr/[// ? \"$value" : $handle->_compile($value) );
}
}
last;
}
elsif ( substr( $phrase, 0, 1 ) ne '_' and ( $handle->{'use_external_lex_cache'} ? ( exists $handle->{'_external_lex_cache'}{'_AUTO'} ? $handle->{'_external_lex_cache'}{'_AUTO'} : $h_r->{'_AUTO'} ) : $h_r->{'_AUTO'} ) ) {
DEBUG and warn " Automaking \"$phrase\" into $h_r\n";
if ( $handle->{'use_external_lex_cache'} ) {
$handle->{'_external_lex_cache'}{$phrase} = $value = ( $phrase !~ tr/[// ? \"$phrase" : $handle->_compile($phrase) );
}
else {
$h_r->{$phrase} = $value = ( $phrase !~ tr/[// ? \"$phrase" : $handle->_compile($phrase) );
}
last;
}
DEBUG > 1 and print " Not found in $h_r, nor automakable\n";
}
if ( !defined($value) ) {
delete $handle->{'_external_lex_cache'}{$phrase};
DEBUG and warn "! Lookup of \"$phrase\" in/under ", ref($handle) || $handle, " fails.\n";
if ( ref($handle) and $handle->{'fail'} ) {
DEBUG and warn "WARNING0: maketext fails looking for <$phrase>\n";
my $fail;
if ( ref( $fail = $handle->{'fail'} ) eq 'CODE' ) { # it's a sub reference
return &{$fail}( $handle, $phrase, @_ );
}
else { # It's a method name
return $handle->$fail( $phrase, @_ );
}
}
else {
require Carp;
Carp::croak("maketext doesn't know how to say:\n$phrase\nas needed");
}
}
}
if ( ref($value) eq 'SCALAR' ) {
return $$value;
}
elsif ( ref($value) ne 'CODE' ) {
return $value;
}
local $@;
{
local $SIG{'__DIE__'};
return eval { &$value( $handle, @_ ) } unless $@;
}
my $err = $@;
$err =~ s{\s+at\s+\(eval\s+\d+\)\s+line\s+(\d+)\.?\n?}
{\n in bracket code [compiled line $1],}s;
require Carp;
Carp::croak("Error in maketexting \"$phrase\":\n$err as used");
}
sub get_handle { # This is a constructor and, yes, it CAN FAIL.
my ( $base_class, @languages ) = @_;
$base_class = ref($base_class) || $base_class;
my $load_alternate_language_tags = 0;
if (@languages) {
DEBUG and warn 'Lgs@', __LINE__, ': ', map( "<$_>", @languages ), "\n";
$load_alternate_language_tags = 1 if $USING_LANGUAGE_TAGS; # An explicit language-list was given!
}
else {
@languages = $base_class->_ambient_langprefs;
}
my %seen;
foreach my $module_name ( map { $base_class . '::' . $_ } @languages ) {
next
if !length $module_name # sanity
|| $seen{$module_name}++ # Already been here, and it was no-go
|| $module_name =~ tr{/-}{}
|| !&_try_use($module_name); # Try to use() it, but can't it.
return ( $module_name->new ); # Make it!
}
if ($load_alternate_language_tags) {
require Cpanel::CPAN::I18N::LangTags;
@languages =
map { ; $_, Cpanel::CPAN::I18N::LangTags::alternate_language_tags($_) }
map Cpanel::CPAN::I18N::LangTags::locale2language_tag($_),
@languages;
DEBUG and warn 'Lgs@', __LINE__, ': ', map( "<$_>", @languages ), "\n";
}
@languages = $base_class->_langtag_munging(@languages);
foreach my $module_name ( map { $base_class . '::' . $_ } @languages ) {
next
if !length $module_name # sanity
|| $seen{$module_name}++ # Already been here, and it was no-go
|| $module_name =~ tr{/-}{}
|| !&_try_use($module_name); # Try to use() it, but can't it.
return ( $module_name->new ); # Make it!
}
return undef; # Fail!
}
sub _langtag_munging {
my ( $base_class, @languages ) = @_;
DEBUG and warn 'Lgs1: ', map( "<$_>", @languages ), "\n";
if ($USING_LANGUAGE_TAGS) {
require Cpanel::CPAN::I18N::LangTags;
DEBUG and warn 'Lgs@', __LINE__, ': ', map( "<$_>", @languages ), "\n";
@languages = $base_class->_add_supers(@languages);
push @languages, Cpanel::CPAN::I18N::LangTags::panic_languages(@languages);
DEBUG and warn "After adding panic languages:\n", ' Lgs@', __LINE__, ': ', map( "<$_>", @languages ), "\n";
push @languages, $base_class->fallback_languages;
DEBUG and warn 'Lgs@', __LINE__, ': ', map( "<$_>", @languages ), "\n";
@languages = # final bit of processing to turn them into classname things
map {
my $it = $_; # copy
$it =~ tr<-A-Z><_a-z>; # lc, and turn - to _
$it =~ tr<_a-z0-9><>cd; # remove all but a-z0-9_
$it;
} @languages;
DEBUG and warn "Nearing end of munging:\n", ' Lgs@', __LINE__, ': ', map( "<$_>", @languages ), "\n";
}
else {
DEBUG and warn "Bypassing language-tags.\n", ' Lgs@', __LINE__, ': ', map( "<$_>", @languages ), "\n";
}
DEBUG and warn "Before adding fallback classes:\n", ' Lgs@', __LINE__, ': ', map( "<$_>", @languages ), "\n";
push @languages, $base_class->fallback_language_classes;
DEBUG and warn "Finally:\n", ' Lgs@', __LINE__, ': ', map( "<$_>", @languages ), "\n";
return @languages;
}
sub _ambient_langprefs {
require Cpanel::CPAN::I18N::LangTags::Detect;
return Cpanel::CPAN::I18N::LangTags::Detect::detect();
}
sub _add_supers {
my ( $base_class, @languages ) = @_;
if ( !$MATCH_SUPERS ) {
DEBUG and warn "Bypassing any super-matching.\n", ' Lgs@', __LINE__, ': ', map( "<$_>", @languages ), "\n";
}
elsif ($MATCH_SUPERS_TIGHTLY) {
require Cpanel::CPAN::I18N::LangTags;
DEBUG and warn "Before adding new supers tightly:\n", ' Lgs@', __LINE__, ': ', map( "<$_>", @languages ), "\n";
@languages = Cpanel::CPAN::I18N::LangTags::implicate_supers(@languages);
DEBUG and warn "After adding new supers tightly:\n", ' Lgs@', __LINE__, ': ', map( "<$_>", @languages ), "\n";
}
else {
require Cpanel::CPAN::I18N::LangTags;
DEBUG and warn "Before adding supers to end:\n", ' Lgs@', __LINE__, ': ', map( "<$_>", @languages ), "\n";
@languages = Cpanel::CPAN::I18N::LangTags::implicate_supers_strictly(@languages);
DEBUG and warn "After adding supers to end:\n", ' Lgs@', __LINE__, ': ', map( "<$_>", @languages ), "\n";
}
return @languages;
}
my %tried = ();
sub _try_use { # Basically a wrapper around "require Modulename"
return $tried{ $_[0] } if exists $tried{ $_[0] }; # memoization
my $module = $_[0]; # ASSUME sane module name!
{
no strict 'refs';
return ( $tried{$module} = 1 )
if ( %{ $module . '::Lexicon' } or @{ $module . '::ISA' } );
}
DEBUG and warn " About to use $module ...\n";
{
local $SIG{'__DIE__'};
eval "require $module"; # used to be "use $module", but no point in that.
}
if ($@) {
DEBUG and warn "Error using $module \: $@\n";
return $tried{$module} = 0;
}
else {
DEBUG and warn " OK, $module is used\n";
return $tried{$module} = 1;
}
}
sub _lex_refs { # report the lexicon references for this handle's class
no strict 'refs';
no warnings 'once';
my $class = ref( $_[0] ) || $_[0];
DEBUG and warn "Lex refs lookup on $class\n";
return $isa_scan{$class} if exists $isa_scan{$class}; # memoization!
my @lex_refs;
my $seen_r = ref( $_[1] ) ? $_[1] : {};
if ( defined( *{ $class . '::Lexicon' }{'HASH'} ) ) {
push @lex_refs, *{ $class . '::Lexicon' }{'HASH'};
$isa_ones{"$lex_refs[-1]"} = defined ${ $class . '::Onesided' } && ${ $class . '::Onesided' } ? 1 : 0;
DEBUG and warn '%' . $class . '::Lexicon contains ', scalar( keys %{ $class . '::Lexicon' } ), " entries\n";
}
foreach my $superclass ( @{ $class . '::ISA' } ) {
DEBUG and warn " Super-class search into $superclass\n";
next if $seen_r->{$superclass}++;
push @lex_refs, @{ &_lex_refs( $superclass, $seen_r ) }; # call myself
}
$isa_scan{$class} = \@lex_refs; # save for next time
return \@lex_refs;
}
sub clear_isa_scan { %isa_scan = (); return; } # end on a note of simplicity!
BEGIN {
}
sub _compile {
return \"$_[1]" if $_[1] !~ tr/[//;
my ( $handle, $call_count, $big_pile, @c, @code ) = ( $_[0], 0, '', '' );
{
my ( $in_group, $m, @params ) = (0); # scratch
my $under_one = $_[1]; # There are taint issues using regex on $_ - perlbug 60378,27344
while (
$under_one =~ # Iterate over chunks.
m/\G(
[^\~\[\]]+ # non-~[] stuff
|
~. # ~[, ~], ~~, ~other
|
\[ # [ presumably opening a group
|
\] # ] presumably closing a group
|
~ # terminal ~ ?
|
$
)/xgs
) {
DEBUG > 2 and warn qq{ "$1"\n};
if ( $1 eq '[' or $1 eq '' ) { # "[" or end
if ($in_group) {
if ( $1 eq '' ) {
$handle->_die_pointing( $under_one, 'Unterminated bracket group' );
}
else {
$handle->_die_pointing( $under_one, 'You can\'t nest bracket groups' );
}
}
else {
if ( $1 eq '' ) {
DEBUG > 2 and warn " [end-string]\n";
}
else {
$in_group = 1;
}
die "How come \@c is empty?? in <$under_one>" unless @c; # sanity
if ( length $c[-1] ) {
$big_pile .= $c[-1];
if (
$USE_LITERALS and (
IS_ASCII
? $c[-1] !~ tr/\x20-\x7E//c
: $c[-1] !~ m/$FORCE_REGEX_LAZY[^ !"\#\$%&'()*+,\-.\/0-9:;<=>?\@A-Z[\\\]^_`a-z{|}~\x07]/os
)
) {
$c[-1] =~ s/'/\\'/g if $c[-1] =~ tr{'}{};
push @code, q{ '} . $c[-1] . "',\n";
$c[-1] = ''; # reuse this slot
}
else {
$c[-1] =~ s/\\\\/\\/g if $c[-1] =~ tr{\\}{};
push @code, ' $c[' . $#c . "],\n";
push @c, ''; # new chunk
}
}
}
}
elsif ( $1 eq ']' ) { # "]"
if ($in_group) {
$in_group = 0;
DEBUG > 2 and warn " --Closing group [$c[-1]]\n";
if ( !length( $c[-1] ) or $c[-1] !~ tr/ \t\r\n\f//c ) {
DEBUG > 2 and warn " -- (Ignoring)\n";
$c[-1] = ''; # reset out chink
next;
}
( $m, @params ) = split( /,/, $c[-1], -1 ); # was /\s*,\s*/
if (IS_ASCII) { # ASCII, etc
foreach ( $m, @params ) { tr/\x7F/,/ }
}
else { # EBCDIC (1047, 0037, POSIX-BC)
foreach ( $m, @params ) { tr/\x07/,/ }
}
if ( $m eq '_1' or $m eq '_2' or $m eq '_3' or $m eq '_*' or ( substr( $m, 0, 1 ) eq '_' && $m =~ m/^_(-?\d+)$/s ) ) {
unshift @params, $m;
$m = '';
}
elsif ( $m eq '*' ) {
$m = 'quant'; # "*" for "times": "4 cars" is 4 times "cars"
}
elsif ( $m eq '#' ) {
$m = 'numf'; # "#" for "number": [#,_1] for "the number _1"
}
if ( $m eq '' ) {
push @code, ' (';
}
elsif (
$m !~ tr{a-zA-Z0-9_}{}c # does not contain non-word characters
&& !$handle->{'blacklist'}{$m}
&& ( !defined $handle->{'whitelist'} || $handle->{'whitelist'}{$m} )
) {
push @code, ' $_[0]->' . $m . '(';
}
else {
$handle->_die_pointing(
$under_one,
"Can't use \"$m\" as a method name in bracket group",
2 + length( $c[-1] )
);
}
pop @c; # we don't need that chunk anymore
++$call_count;
foreach my $p (@params) {
if ( $p eq '_*' ) {
$code[-1] .= ' @_[1 .. $#_], ';
}
elsif ( substr( $p, 0, 1 ) eq '_' && ( $p eq '_1' || $p eq '_2' || $p eq '_3' || $p =~ m/^_-?\d+$/s ) ) {
$code[-1] .= '$_[' . ( 0 + substr( $p, 1 ) ) . '], ';
}
elsif (
$USE_LITERALS and (
IS_ASCII
? $p !~ tr/\x20-\x7E//c
: $p !~ m/$FORCE_REGEX_LAZY[^ !"\#\$%&'()*+,\-.\/0-9:;<=>?\@A-Z[\\\]^_`a-z{|}~\x07]/os
)
) {
$p =~ s/'/\\'/g if $p =~ tr{'}{};
$code[-1] .= q{'} . $p . q{', };
}
else {
push @c, $p;
push @code, ' $c[' . $#c . '], ';
}
}
$code[-1] .= "),\n";
push @c, '';
}
else {
$handle->_die_pointing( $under_one, q{Unbalanced ']'} );
}
}
elsif ( substr( $1, 0, 1 ) ne '~' ) {
if ( $1 =~ tr{\\}{} ) {
my $text = $1;
$text =~ s/\\/\\\\/g;
$c[-1] .= $text;
}
else {
$c[-1] .= $1;
}
}
elsif ( $1 eq '~~' ) { # "~~"
$c[-1] .= '~';
}
elsif ( $1 eq '~[' ) { # "~["
$c[-1] .= '[';
}
elsif ( $1 eq '~]' ) { # "~]"
$c[-1] .= ']';
}
elsif ( $1 eq '~,' ) { # "~,"
if ($in_group) {
if (IS_ASCII) { # ASCII etc
$c[-1] .= "\x7F";
}
else { # EBCDIC (cp 1047, 0037, POSIX-BC)
$c[-1] .= "\x07";
}
}
else {
$c[-1] .= '~,';
}
}
elsif ( $1 eq '~' ) { # possible only at string-end, it seems.
$c[-1] .= '~';
}
else {
my $text = $1;
$text =~ s/\\/\\\\/g if $text =~ tr{\\}{};
$c[-1] .= $text;
}
}
}
if ($call_count) {
undef $big_pile; # Well, nevermind that.
}
else {
return \$big_pile;
}
die q{Last chunk isn't null??} if @c and length $c[-1]; # sanity
DEBUG and warn scalar(@c), " chunks under closure\n";
my $sub;
if ( @code == 0 ) { # not possible?
DEBUG and warn "Empty code\n";
return \'';
}
elsif ( scalar @code > 1 ) { # most cases, presumably!
$sub = "sub { join '', map { defined \$_ ? \$_ : '' } @code }";
}
else {
$sub = "sub { $code[0] }";
}
DEBUG and warn $sub;
my $code;
{
use strict;
$code = eval $sub;
die "$@ while evalling" . $sub if $@; # Should be impossible.
}
return $code;
}
sub _die_pointing {
my $target = shift;
$target = ref($target) || $target; # class name
my $i = index( $_[0], "\n" );
my $pointy;
my $pos = pos( $_[0] ) - ( defined( $_[2] ) ? $_[2] : 0 ) - 1;
if ( $pos < 1 ) {
$pointy = "^=== near there\n";
}
else { # we need to space over
my $first_tab = index( $_[0], "\t" );
if ( $pos > 2 and ( -1 == $first_tab or $first_tab > pos( $_[0] ) ) ) {
$pointy = ( '=' x $pos ) . "^ near there\n";
}
else {
$pointy = substr( $_[0], 0, $pos );
$pointy =~ tr/\t //cd;
$pointy .= "^=== near there\n";
}
}
my $errmsg = "$_[1], in\:\n$_[0]";
if ( $i == -1 ) {
$errmsg .= "\n" . $pointy;
}
elsif ( $i == ( length( $_[0] ) - 1 ) ) {
$errmsg .= $pointy;
}
else {
}
require Carp;
Carp::croak("$errmsg via $target, as used");
}
1;
} # --- END Cpanel/CPAN/Locale/Maketext.pm
{ # --- BEGIN Cpanel/Locale/Utils/Normalize.pm
package Cpanel::Locale::Utils::Normalize;
use strict;
use warnings;
sub normalize_tag {
my ($tag) = @_;
return if !defined $tag;
$tag =~ tr/A-Z/a-z/;
$tag =~ tr{\r\n \t\f}{}d;
if ( $tag =~ tr{a-z0-9}{}c ) {
$tag =~ s{[^a-z0-9]+$}{}; # I18N::LangTags::locale2language_tag() does not allow trailing '_'
$tag =~ tr{a-z0-9}{_}c;
}
if ( length $tag > 8 ) {
while ( $tag =~ s/([^_]{8})([^_])/$1\_$2/ ) { } # I18N::LangTags::locale2language_tag() only allows parts between 1 and 8 character
}
return $tag;
}
1;
} # --- END Cpanel/Locale/Utils/Normalize.pm
{ # --- BEGIN Cpanel/CPAN/Locales/Legacy.pm
package Cpanel::CPAN::Locales::Legacy;
use strict;
sub numf {
my ( $self, $always_return ) = @_;
my $class = ref($self) ? ref($self) : $self;
$always_return ||= 0;
$self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} = '' if !defined $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'};
$self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} = '' if !defined $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'};
if ( !$self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} || !$self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} ) {
if ($always_return) {
if ( $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} || !$self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} ) {
return 2 if $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} eq '.';
return 1;
}
elsif ( !$self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} || $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} ) {
return 2 if $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} eq ',';
return 1;
}
else {
return 1;
}
}
}
if ( $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'decimal'} eq "\#\,\#\#0\.\#\#\#" ) {
if ( $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} eq ',' && $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} eq '.' ) {
return 1;
}
elsif ( $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} eq '.' && $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} eq ',' ) {
return 2;
}
}
elsif ( $always_return && $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} && $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} ) {
return 2 if $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} eq ',';
return 2 if $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} eq '.';
return 1;
}
return [
$self->{'language_data'}{'misc_info'}{'cldr_formats'}{'decimal'},
$self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'},
$self->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'},
];
}
1;
} # --- END Cpanel/CPAN/Locales/Legacy.pm
{ # --- BEGIN Cpanel/CPAN/Locales/DB/LocaleDisplayPattern/Tiny.pm
package Cpanel::CPAN::Locales::DB::LocaleDisplayPattern::Tiny;
use strict;
$Cpanel::CPAN::Locales::DB::LocaleDisplayPattern::Tiny::VERSION = '0.09';
$Cpanel::CPAN::Locales::DB::LocaleDisplayPattern::Tiny::cldr_version = '2.0';
my %locale_display_lookup = (
'ksh' => '{0} en {1}',
'ja' => '{0}({1})',
'zh' => '{0}({1})',
'ko' => '{0}({1})',
);
sub get_locale_display_pattern {
if ( exists $locale_display_lookup{ $_[0] } ) {
return $locale_display_lookup{ $_[0] };
}
else {
require Cpanel::CPAN::Locales;
my ($l) = Cpanel::CPAN::Locales::split_tag( $_[0] );
if ( $l ne $_[0] ) {
return $locale_display_lookup{$l} if exists $locale_display_lookup{$l};
}
return "\{0\}\ \(\{1\}\)";
}
}
1;
} # --- END Cpanel/CPAN/Locales/DB/LocaleDisplayPattern/Tiny.pm
{ # --- BEGIN Cpanel/CPAN/Locales/DB/CharacterOrientation/Tiny.pm
package Cpanel::CPAN::Locales::DB::CharacterOrientation::Tiny;
use strict;
$Cpanel::CPAN::Locales::DB::CharacterOrientation::Tiny::VERSION = '0.09';
$Cpanel::CPAN::Locales::DB::CharacterOrientation::Tiny::cldr_version = '2.0';
my %rtl = (
'ur' => '',
'ku' => '',
'he' => '',
'fa' => '',
'ps' => '',
'ar' => '',
);
sub get_orientation {
if ( exists $rtl{ $_[0] } ) {
return 'right-to-left';
}
else {
require Cpanel::CPAN::Locales;
my ($l) = Cpanel::CPAN::Locales::split_tag( $_[0] );
if ( $l ne $_[0] ) {
return 'right-to-left' if exists $rtl{$l};
}
return 'left-to-right';
}
}
1;
} # --- END Cpanel/CPAN/Locales/DB/CharacterOrientation/Tiny.pm
{ # --- BEGIN Cpanel/CPAN/Locales/Compile.pm
package Cpanel::CPAN::Locales::Compile;
use strict;
use warnings;
sub plural_rule_string_to_code {
my ( $plural_rule_string, $return ) = @_;
if ( !defined $return ) {
$return = 1;
}
my %m;
while ( $plural_rule_string =~ m/mod ([0-9]+)/g ) {
$m{$1} = "( (\$_[0] % $1) + (\$_[0]-int(\$_[0])) )";
}
my $perl_code = "sub { if (";
for my $or ( split /\s+or\s+/i, $plural_rule_string ) {
my $and_exp;
for my $and ( split /\s+and\s+/i, $or ) {
my $copy = $and;
my $n = '$_[0]';
$copy =~ s/ ?n is not / $n \!\= /g;
$copy =~ s/ ?n is / $n \=\= /g;
$copy =~ s/ ?n mod ([0-9]+) is not / $m{$1} \!\= /g;
$copy =~ s/ ?n mod ([0-9]+) is / $m{$1} \=\= /g;
$copy =~ s/ ?n not in ([0-9]+)\s*\.\.\s*([0-9]+) ?/ int\($n\) \!\= $n \|\| $n < $1 \|\| $n \> $2 /g;
$copy =~ s/ ?n mod ([0-9]+) not in ([0-9]+)\s*\.\.\s*([0-9]+) ?/ int\($n\) \!\= $n \|\| $m{$1} < $2 \|\| $m{$1} \> $3 /g;
$copy =~ s/ ?n not within ([0-9]+)\s*\.\.\s*([0-9]+) ?/ \($n < $1 \|\| $n > $2\) /g;
$copy =~ s/ ?n mod ([0-9]+) not within ([0-9]+)\s*\.\.\s*([0-9]+) ?/ \($m{$1} < $2 \|\| $m{$1} > $3\) /g;
$copy =~ s/ ?n in ([0-9]+)\s*\.\.\s*([0-9]+) ?/ int\($n\) \=\= $n \&\& $n \>\= $1 \&\& $n \<\= $2 /g;
$copy =~ s/ ?n mod ([0-9]+) in ([0-9]+)\s*\.\.\s*([0-9]+) ?/ int\($n\) \=\= $n \&\& $m{$1} \>\= $2 \&\& $m{$1} \<\= $3 /g;
$copy =~ s/ ?n within ([0-9]+)\s*\.\.\s*([0-9]+) ?/ $n \>\= $1 \&\& $n \<\= $2 /g;
$copy =~ s/ ?n mod ([0-9]+) within ([0-9]+)\s*\.\.\s*([0-9]+) ?/ $m{$1} \>\= $2 \&\& $m{$1} \<\= $3 /g;
if ( $copy eq $and ) {
require Carp;
Carp::carp("Unknown plural rule syntax");
return;
}
else {
$and_exp .= "($copy) && ";
}
}
$and_exp =~ s/\s+\&\&\s*$//;
if ($and_exp) {
$perl_code .= " ($and_exp) || ";
}
}
$perl_code =~ s/\s+\|\|\s*$//;
$perl_code .= ") { return '$return'; } return;}";
return $perl_code;
}
sub plural_rule_string_to_javascript_code {
my ( $plural_rule_string, $return ) = @_;
my $perl = plural_rule_string_to_code( $plural_rule_string, $return );
$perl =~ s/sub \{ /function (n) \{/;
$perl =~ s/\$_\[0\]/n/g;
$perl =~ s/ \(n \% ([0-9]+)\) \+ \(n-int\(n\)\) /n % $1/g;
$perl =~ s/int\(/parseInt\(/g;
return $perl;
}
1;
} # --- END Cpanel/CPAN/Locales/Compile.pm
{ # --- BEGIN Cpanel/CPAN/Locales.pm
package Cpanel::CPAN::Locales;
use strict;
# use Cpanel::Locale::Utils::Normalize ();
$Cpanel::CPAN::Locales::VERSION = 0.30_1; # change in POD
$Cpanel::CPAN::Locales::cldr_version = '2.0'; # change in POD
my $FORCE_REGEX_LAZY = '';
*normalize_tag = *Cpanel::Locale::Utils::Normalize::normalize_tag;
my %singleton_stash;
sub get_cldr_version {
return $Cpanel::CPAN::Locales::cldr_version;
}
sub new {
my ( $class, $tag ) = @_;
$tag = normalize_tag($tag) || 'en';
if ( !exists $singleton_stash{$tag} ) {
my $locale = {
'locale' => $tag,
};
if ( my $soft = tag_is_soft_locale($tag) ) {
$locale->{'soft_locale_fallback'} = $soft;
$tag = $soft;
}
my $inc_class = ref($class) ? ref($class) : $class;
$inc_class =~ s{\:\:|\'}{/}g; # per Module::Want::get_inc_key()
if ( !exists $INC{"$inc_class/DB/Language/$tag.pm"} ) {
local $SIG{'__DIE__'}; # cpanel specific: ensure a benign eval does not trigger cpsrvd's DIE handler (may be made moot by internal case 50857)
eval "require $class\::DB::Language::$tag" || return; # Module::Want::have_mod("$class\::DB::Language::$tag");
}
my ( $language, $territory ) = split_tag( $locale->{'locale'} );
$locale->{'language'} = $language;
{
BEGIN { $^H = 0; }; # cheap no strict to allow for ref copy
$locale->{'language_data'} = {
'VERSION' => \${"$class\::DB::Language::$tag\::VERSION"},
'cldr_version' => \${"$class\::DB::Language::$tag\::cldr_version"},
'misc_info' => \%{"$class\::DB::Language::$tag\::misc_info"},
};
}
$locale->{'territory'} = $territory;
$locale->{'misc'}{'list_quote_mode'} = 'none';
$singleton_stash{$tag} = bless $locale, $class;
}
return $singleton_stash{$tag};
}
sub _load_territory_data {
my ($self) = @_;
my $tag = $self->{'locale'};
my $class = scalar ref $self;
my $inc_class = $class;
$inc_class =~ s{\:\:|\'}{/}g; # per Module::Want::get_inc_key()
if ( !exists $INC{"$inc_class/DB/Territory/$tag.pm"} ) {
local $SIG{'__DIE__'}; # cpanel specific: ensure a benign eval does not trigger cpsrvd's DIE handler (may be made moot by internal case 50857)
eval "require $class\::DB::Territory::$tag" || return; # Module::Want::have_mod("$class\::DB::Language::$tag");
}
{
BEGIN { $^H = 0; }; # cheap no strict to allow for ref copy
$self->{'territory_data'} = {
'VERSION' => \${"$class\::DB::Territory::$tag\::VERSION"},
'cldr_version' => \${"$class\::DB::Territory::$tag\::cldr_version"},
'code_to_name' => \%{"$class\::DB::Territory::$tag\::code_to_name"},
};
}
return 1;
}
sub _load_language_data_code_to_name {
my ($self) = @_;
my $tag = $self->{'locale'};
my $class = scalar ref $self;
my $inc_class = $class;
$inc_class =~ s{\:\:|\'}{/}g; # per Module::Want::get_inc_key()
if ( !exists $INC{"$inc_class/DB/Language/code_to_name/$tag.pm"} ) {
local $SIG{'__DIE__'}; # cpanel specific: ensure a benign eval does not trigger cpsrvd's DIE handler (may be made moot by internal case 50857)
eval "require $class\::DB::Language::code_to_name::$tag" || return; # Module::Want::have_mod("$class\::DB::Language::$tag");
}
{
BEGIN { $^H = 0; }; # cheap no strict to allow for ref copy
$self->{'language_data'}{'code_to_name'} = \%{"$class\::DB::Language::$tag\::code_to_name"};
}
return 1;
}
sub get_soft_locale_fallback {
return $_[0]->{'soft_locale_fallback'} if $_[0]->{'soft_locale_fallback'};
return;
}
sub get_locale { shift->{'locale'} }
sub get_territory { shift->{'territory'} }
sub get_language { shift->{'language'} }
sub get_native_language_from_code {
my ( $self, $code, $always_return ) = @_;
my $class = ref($self) ? ref($self) : $self;
if ( !exists $self->{'native_data'} ) {
local $SIG{'__DIE__'}; # cpanel specific: ensure a benign eval does not trigger cpsrvd's DIE handler (may be made moot by internal case 50857)
eval "require $class\::DB::Native;" || return; # Module::Want::have_mod("$class\::DB::Native");
{
BEGIN { $^H = 0; }; # cheap no strict to allow for ref copy
$self->{'native_data'} = {
'VERSION' => \${"$class\::DB::Native::VERSION"},
'cldr_version' => \${"$class\::DB::Native::cldr_version"},
'code_to_name' => \%{"$class\::DB::Native::code_to_name"},
};
}
}
$code ||= $self->{'locale'};
$code = normalize_tag($code);
return if !defined $code;
$always_return ||= 1 if $code eq $self->get_locale() && $self->get_soft_locale_fallback(); # force $always_return under soft locale objects
$always_return ||= 0;
if ( exists $self->{'native_data'}{'code_to_name'}{$code} ) {
return $self->{'native_data'}{'code_to_name'}{$code};
}
elsif ($always_return) {
my ( $l, $t ) = split_tag($code);
my $ln = $self->{'native_data'}{'code_to_name'}{$l};
$self->_load_territory_data() if !$self->{'territory_data'};
my $tn = defined $t ? $self->{'territory_data'}{'code_to_name'}{$t} : '';
return $code if !$ln && !$tn;
if ( defined $t ) {
my $tmp = Cpanel::CPAN::Locales->new($l); # if we even get to this point: this is a singleton so it is cheap
if ($tmp) {
if ( $tmp->get_territory_from_code($t) ) {
$tn = $tmp->get_territory_from_code($t);
}
}
}
$ln ||= $l;
$tn ||= $t;
my $string = get_locale_display_pattern_from_code_fast($code) || $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'locale'} || '{0} ({1})';
substr( $string, index( $string, '{0}' ), 3, $ln ) while index( $string, '{0}' ) > -1;
substr( $string, index( $string, '{1}' ), 3, $tn ) while index( $string, '{1}' ) > -1;
return $string;
}
return;
}
sub numf {
require Cpanel::CPAN::Locales::Legacy if !$INC{'Cpanel/CPAN/Locales/Legacy.pm'};
*numf = *Cpanel::CPAN::Locales::Legacy::numf;
goto \&Cpanel::CPAN::Locales::Legacy::numf;
}
my $get_locale_display_pattern_from_code_fast = 0;
sub get_locale_display_pattern_from_code_fast {
if ( !$get_locale_display_pattern_from_code_fast ) {
$get_locale_display_pattern_from_code_fast++;
require Cpanel::CPAN::Locales::DB::LocaleDisplayPattern::Tiny;
}
if ( @_ == 1 && ref( $_[0] ) ) {
return Cpanel::CPAN::Locales::DB::LocaleDisplayPattern::Tiny::get_locale_display_pattern( $_[0]->get_locale() );
}
return Cpanel::CPAN::Locales::DB::LocaleDisplayPattern::Tiny::get_locale_display_pattern( $_[-1] ); # last arg so it works as function or class method or object method
}
sub get_locale_display_pattern_from_code {
my ( $self, $code, $always_return ) = @_;
my $class = ref($self) ? ref($self) : $self;
if ( !exists $self->{'locale_display_pattern_data'} ) {
local $SIG{'__DIE__'}; # cpanel specific: ensure a benign eval does not trigger cpsrvd's DIE handler (may be made moot by internal case 50857)
eval "require $class\::DB::LocaleDisplayPattern;" || return; # Module::Want::have_mod("$class\::DB::LocaleDisplayPattern");
{
BEGIN { $^H = 0; }; # cheap no strict to allow for ref copy
$self->{'locale_display_pattern_data'} = {
'VERSION' => \${"$class\::DB::LocaleDisplayPattern::VERSION"},
'cldr_version' => \${"$class\::DB::LocaleDisplayPattern::cldr_version"},
'code_to_pattern' => \%{"$class\::DB::LocaleDisplayPattern::code_to_pattern"},
};
}
}
$code ||= $self->{'locale'};
$code = normalize_tag($code);
return if !defined $code;
$always_return ||= 1 if $code eq $self->get_locale() && $self->get_soft_locale_fallback(); # force $always_return under soft locale objects
$always_return ||= 0;
if ( exists $self->{'locale_display_pattern_data'}{'code_to_pattern'}{$code} ) {
return $self->{'locale_display_pattern_data'}{'code_to_pattern'}{$code};
}
elsif ($always_return) {
my ( $l, $t ) = split_tag($code);
if ( exists $self->{'locale_display_pattern_data'}{'code_to_pattern'}{$l} ) {
return $self->{'locale_display_pattern_data'}{'code_to_pattern'}{$l};
}
return '{0} ({1})';
}
return;
}
my $get_character_orientation_from_code_fast = 0;
sub get_character_orientation_from_code_fast {
if ( !$get_character_orientation_from_code_fast ) {
$get_character_orientation_from_code_fast++;
require Cpanel::CPAN::Locales::DB::CharacterOrientation::Tiny;
}
if ( @_ == 1 && ref( $_[0] ) ) {
return Cpanel::CPAN::Locales::DB::CharacterOrientation::Tiny::get_orientation( $_[0]->get_locale() );
}
return Cpanel::CPAN::Locales::DB::CharacterOrientation::Tiny::get_orientation( $_[-1] ); # last arg so it works as function or class method or object method
}
sub get_character_orientation_from_code {
my ( $self, $code, $always_return ) = @_;
my $class = ref($self) ? ref($self) : $self;
if ( !exists $self->{'character_orientation_data'} ) {
local $SIG{'__DIE__'}; # cpanel specific: ensure a benign eval does not trigger cpsrvd's DIE handler (may be made moot by internal case 50857)
eval "require $class\::DB::CharacterOrientation;" || return; # Module::Want::have_mod("$class\::DB::CharacterOrientation");
{
BEGIN { $^H = 0; }; # cheap no strict to allow for ref copy
$self->{'character_orientation_data'} = {
'VERSION' => \${"$class\::DB::CharacterOrientation::VERSION"},
'cldr_version' => \${"$class\::DB::CharacterOrientation::cldr_version"},
'code_to_name' => \%{"$class\::DB::CharacterOrientation::code_to_name"},
};
}
}
$code ||= $self->{'locale'};
$code = normalize_tag($code);
return if !defined $code;
$always_return ||= 1 if $code eq $self->get_locale() && $self->get_soft_locale_fallback(); # force $always_return under soft locale objects
$always_return ||= 0;
if ( exists $self->{'character_orientation_data'}{'code_to_name'}{$code} ) {
return $self->{'character_orientation_data'}{'code_to_name'}{$code};
}
elsif ($always_return) {
my ( $l, $t ) = split_tag($code);
if ( exists $self->{'character_orientation_data'}{'code_to_name'}{$l} ) {
return $self->{'character_orientation_data'}{'code_to_name'}{$l};
}
return 'left-to-right';
}
return;
}
sub get_plural_form_categories {
return @{ $_[0]->{'language_data'}{'misc_info'}{'plural_forms'}{'category_list'} };
}
sub supports_special_zeroth {
return 1 if $_[0]->get_plural_form(0) eq 'other';
return;
}
sub plural_category_count {
return scalar( $_[0]->get_plural_form_categories() );
}
sub get_plural_form {
my ( $self, $n, @category_values ) = @_;
my $category;
my $has_extra_for_zero = 0;
my $abs_n = abs($n); # negatives keep same category as positive
if ( !$self->{'language_data'}{'misc_info'}{'plural_forms'}{'category_rules_function'} ) {
$self->{'language_data'}{'misc_info'}{'plural_forms'}{'category_rules_function'} = Cpanel::CPAN::Locales::plural_rule_hashref_to_code( $self->{'language_data'}{'misc_info'}{'plural_forms'} );
if ( !defined $self->{'language_data'}{'misc_info'}{'plural_forms'}{'category_rules_function'} ) {
require Carp;
Carp::carp("Could not determine plural logic.");
}
}
$category = $self->{'language_data'}{'misc_info'}{'plural_forms'}{'category_rules_function'}->($abs_n);
my @categories = $self->get_plural_form_categories();
if ( !@category_values ) {
@category_values = @categories;
}
else {
my $cat_len = @categories;
my $val_len = @category_values;
if ( $val_len == ( $cat_len + 1 ) ) {
$has_extra_for_zero++;
}
elsif ( $cat_len != $val_len && $self->{'verbose'} ) {
require Carp;
Carp::carp("The number of given values ($val_len) does not match the number of categories ($cat_len).");
}
}
if ( !defined $category ) {
my $cat_idx = $has_extra_for_zero && $abs_n != 0 ? -2 : -1;
return wantarray ? ( $category_values[$cat_idx], $has_extra_for_zero && $abs_n == 0 ? 1 : 0 ) : $category_values[$cat_idx];
}
else {
GET_POSITION:
my $cat_pos_in_list;
my $index = -1;
CATEGORY:
for my $cat (@categories) {
$index++;
if ( $cat eq $category ) {
$cat_pos_in_list = $index;
last CATEGORY;
}
}
if ( !defined $cat_pos_in_list && $category ne 'other' ) {
require Carp;
Carp::carp("The category ($category) is not used by this locale.");
$category = 'other';
goto GET_POSITION;
}
elsif ( !defined $cat_pos_in_list ) {
my $cat_idx = $has_extra_for_zero && $abs_n != 0 ? -2 : -1;
return wantarray ? ( $category_values[$cat_idx], $has_extra_for_zero && $abs_n == 0 ? 1 : 0 ) : $category_values[$cat_idx];
}
else {
if ( $has_extra_for_zero && $category eq 'other' ) { # and 'other' is at the end of the list? nah... && $cat_pos_in_list + 1 == $#category_values
my $cat_idx = $has_extra_for_zero && $abs_n == 0 ? -1 : $cat_pos_in_list;
return wantarray ? ( $category_values[$cat_idx], $has_extra_for_zero && $abs_n == 0 ? 1 : 0 ) : $category_values[$cat_idx];
}
else {
return wantarray ? ( $category_values[$cat_pos_in_list], 0 ) : $category_values[$cat_pos_in_list];
}
}
}
}
sub _quote_get_list_items {
my ( $self, $items_ar ) = @_;
my $cnt = 0;
if ( exists $self->{'misc'}{'list_quote_mode'} && $self->{'misc'}{'list_quote_mode'} ne 'none' ) {
if ( $self->{'misc'}{'list_quote_mode'} eq 'all' ) {
@{$items_ar} = ('') if @{$items_ar} == 0;
for my $i ( 0 .. scalar( @{$items_ar} ) - 1 ) {
$items_ar->[$i] = '' if !defined $items_ar->[$i];
$items_ar->[$i] = $self->quote( $items_ar->[$i] );
$cnt++;
}
}
elsif ( $self->{'misc'}{'list_quote_mode'} eq 'some' ) {
@{$items_ar} = ('') if @{$items_ar} == 0;
for my $i ( 0 .. scalar( @{$items_ar} ) - 1 ) {
$items_ar->[$i] = '' if !defined $items_ar->[$i];
if ( $items_ar->[$i] eq '' || $items_ar->[$i] eq ' ' || $items_ar->[$i] eq "\xc2\xa0" ) {
$items_ar->[$i] = $self->quote( $items_ar->[$i] );
$cnt++;
}
}
}
else {
require Carp;
Carp::carp('$self->{misc}{list_quote_mode} is set to an unknown value');
}
}
return $cnt;
}
sub get_list_and {
my $self = shift;
return $self->_get_list_joined(
$self->{'language_data'}{'misc_info'}{'cldr_formats'}{'list'},
@_,
);
}
sub get_list_or {
my $self = shift;
return $self->_get_list_joined(
$self->{'language_data'}{'misc_info'}{'cldr_formats'}{'list_or'},
@_,
);
}
sub _get_list_joined {
my ( $self, $templates_hr, @items ) = @_;
$self->_quote_get_list_items( \@items );
return if !@items;
return $items[0] if @items == 1;
my $ix; # used to cache index results in the following oneliner
if ( @items == 2 ) {
my $two = $templates_hr->{'2'};
substr( $two, $ix, 3, $items[0] ) while ( $ix = index( $two, '{0}' ) ) > -1;
substr( $two, $ix, 3, $items[1] ) while ( $ix = index( $two, '{1}' ) ) > -1;
return $two;
}
else {
for (@items) {
next if !defined $_;
substr( $_, $ix, 3, '__{__0__}__' ) while ( $ix = index( $_, '{0}' ) ) > -1;
substr( $_, $ix, 3, '__{__1__}__' ) while ( $ix = index( $_, '{1}' ) ) > -1;
}
my $aggregate = $templates_hr->{'start'};
substr( $aggregate, $ix, 3, $items[0] ) while ( $ix = index( $aggregate, '{0}' ) ) > -1;
substr( $aggregate, $ix, 3, $items[1] ) while ( $ix = index( $aggregate, '{1}' ) ) > -1;
for my $i ( 2 .. $#items ) {
next if $i == $#items;
my $middle = $templates_hr->{'middle'};
substr( $middle, $ix, 3, $aggregate ) while ( $ix = index( $middle, '{0}' ) ) > -1;
my $item = defined $items[$i] ? $items[$i] : '';
substr( $middle, $ix, 3, $item ) while ( $ix = index( $middle, '{1}' ) ) > -1;
$aggregate = $middle;
}
my $end = $templates_hr->{'end'};
substr( $end, $ix, 3, $aggregate ) while ( $ix = index( $end, '{0}' ) ) > -1;
substr( $end, $ix, 3, $items[-1] ) while ( $ix = index( $end, '{1}' ) ) > -1;
substr( $end, $ix, 11, '{0}' ) while ( $ix = index( $end, '__{__0__}__' ) ) > -1;
substr( $end, $ix, 11, '{1}' ) while ( $ix = index( $end, '__{__1__}__' ) ) > -1;
return $end;
}
}
sub quote {
my ( $self, $value ) = @_;
$value = '' if !defined $value;
return $self->{'language_data'}{'misc_info'}{'delimiters'}{'quotation_start'} . $value . $self->{'language_data'}{'misc_info'}{'delimiters'}{'quotation_end'};
}
sub quote_alt {
my ( $self, $value ) = @_;
$value = '' if !defined $value;
return $self->{'language_data'}{'misc_info'}{'delimiters'}{'alternate_quotation_start'} . $value . $self->{'language_data'}{'misc_info'}{'delimiters'}{'alternate_quotation_end'};
}
sub get_formatted_ellipsis_initial {
my ( $self, $str ) = @_;
my $pattern = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'ellipsis'}{'initial'} || '…{0}';
substr( $pattern, index( $pattern, '{0}' ), 3, $str ) while index( $pattern, '{0}' ) > -1;
return $pattern;
}
sub get_formatted_ellipsis_medial {
my ($self) = @_; # my ($self, $first, $second) = @_;
my $pattern = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'ellipsis'}{'medial'} || '{0}…{1}';
substr( $pattern, index( $pattern, '{0}' ), 3, $_[1] ) while index( $pattern, '{0}' ) > -1;
substr( $pattern, index( $pattern, '{1}' ), 3, $_[2] ) while index( $pattern, '{1}' ) > -1;
return $pattern;
}
sub get_formatted_ellipsis_final {
my ( $self, $str ) = @_;
my $pattern = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'ellipsis'}{'final'} || '{0}…';
substr( $pattern, index( $pattern, '{0}' ), 3, $str ) while index( $pattern, '{0}' ) > -1;
return $pattern;
}
sub get_formatted_decimal {
my ( $self, $n, $max_decimal_places, $_my_pattern ) = @_; # $_my_pattern not documented on purpose, it is only intended for internal use, and may dropepd/changed at any time
return if !defined $n;
my $is_negative = $n < 0 ? 1 : 0;
my $max_len = defined $max_decimal_places ? abs( int($max_decimal_places) ) : 6; # %f default is 6
$max_len = 14 if $max_len > 14;
if ( $n > 10_000_000_000 || $n < -10_000_000_000 ) {
return $n if $n =~ tr/Ee//; # poor man's is exponential check.
if ( $n =~ m/\.([0-9]{$max_len})([0-9])?/ ) {
my $trim = $1; # (defined $2 && $2 > 4) ? $1 + 1 : $1;
if ( defined $2 && $2 > 4 ) {
if ( ( $trim + 1 ) !~ tr/Ee// ) { # poor man's is exponential check.
$trim++;
}
}
$n =~ s/$FORCE_REGEX_LAZY\.[0-9]+/\.$trim/o;
}
}
else {
return $n if length $n < 3 && $n !~ tr{0-9}{}c;
$n = sprintf( '%.' . $max_len . 'f', $n );
return $n if $n =~ tr/Ee//; # poor man's is exponential check.
}
$n =~ s{$FORCE_REGEX_LAZY([^0-9]+[0-9]*?[1-9])0+$}{$1}o;
$n =~ s{$FORCE_REGEX_LAZY[^0-9]+0+$}{}o;
if ( $n =~ tr{.0-9}{}c ) { # Only strip signs if the string has non-numeric and '.' characters such as '+' or '-'
substr( $n, 0, 1, '' ) while substr( $n, 1 ) =~ tr{0-9}{}c;
}
my $cldr_formats = $self->{'language_data'}{'misc_info'}{'cldr_formats'};
my $format = $_my_pattern || $cldr_formats->{'decimal'}; # from http://unicode.org/repos/cldr-tmp/trunk/diff/by_type/number.pattern.html
my ( $zero_positive_pat, $negative_pat, $err ) = split( /$FORCE_REGEX_LAZY(?<!\')\;(?!\')/o, $format ); # semi-colon that is not literal (?<!\')\;(?!\')
if ($err) {
require Carp;
Carp::carp("Format had more than 2 pos/neg sections. Using default pattern.");
$format = '#,##0.###';
}
elsif ( $is_negative && $negative_pat ) {
$format = $negative_pat;
}
elsif ($zero_positive_pat) {
$format = $zero_positive_pat;
}
my $dec_sec_cnt = 0;
if ( index( $format, q{'} ) == -1 ) {
$dec_sec_cnt = $format =~ tr{\.}{};
}
else {
$dec_sec_cnt++ while ( $format =~ m/$FORCE_REGEX_LAZY(?<!\')\.(?!\')/og );
}
if ( $dec_sec_cnt != 1 ) {
require Carp;
Carp::carp("Format should have one decimal section. Using default pattern.");
$format = '#,##0.###';
}
if ( !length $format || $format !~ tr{ \t\r\n\f}{}c ) {
require Carp;
Carp::carp("Format is empty. Using default pattern.");
$format = '#,##0.###';
}
my $result = '';
if ( $format eq '#,##0.###' ) {
$result = $n;
if ( $n =~ tr{0-9}{} > 3 ) {
while ( $result =~ s/$FORCE_REGEX_LAZY^([-+]?\d+)(\d{3})/$1,$2/os ) { 1 } # right from perlfaq5
}
}
else {
my ( $integer, $decimals ) = split( /\./, $n, 2 );
my ( $i_pat, $d_pat ) = split( /$FORCE_REGEX_LAZY(?<!\')\.(?!\')/o, $format, 2 );
my ( $cur_idx, $trailing_non_n, $cur_d, $cur_pat ) = ( 0, '' ); # buffer
my @i_pat = reverse( split( /$FORCE_REGEX_LAZY(?<!\')\,(?!\')/o, $i_pat ) );
my $next_to_last_pattern = @i_pat == 1 ? $i_pat[0] : $i_pat[-2];
substr( $next_to_last_pattern, -1, 1, '#' ) if substr( $next_to_last_pattern, -1 ) eq '0';
while ( $i_pat[0] =~ s/$FORCE_REGEX_LAZY((?:\'.\')+)$//o || $i_pat[0] =~ s/$FORCE_REGEX_LAZY([^0#]+)$//o ) {
$trailing_non_n = "$1$trailing_non_n";
}
while ( CORE::length( $cur_d = CORE::substr( $integer, -1, 1, '' ) ) ) {
if ( $cur_idx == $#i_pat && !CORE::length( $i_pat[$cur_idx] ) ) {
$i_pat[$cur_idx] = $next_to_last_pattern;
}
if ( !CORE::length( $i_pat[$cur_idx] ) ) { # this chunk is spent
if ( defined $i_pat[ $cur_idx + 1 ] ) { # there are more chunks ...
$cur_idx++; # ... next chunk please
}
}
if ( CORE::length( $i_pat[$cur_idx] ) ) {
if ( substr( $i_pat[$cur_idx], -3 ) eq q{','} ) {
$result = CORE::substr( $i_pat[$cur_idx], -3, 3, '' ) . $result;
redo;
}
$cur_pat = CORE::substr( $i_pat[$cur_idx], -1, 1, '' );
if ( $cur_pat ne '0' && $cur_pat ne '#' ) {
$result = "$cur_pat$result";
redo;
}
}
$result = !CORE::length( $i_pat[$cur_idx] ) && @i_pat != 1 ? ",$cur_d$result" : "$cur_d$result";
if ( $cur_idx == $#i_pat - 1 && $i_pat[$#i_pat] eq '#' && !CORE::length( $i_pat[$cur_idx] ) ) {
$cur_idx++;
$i_pat[$cur_idx] = $next_to_last_pattern;
}
}
if ( CORE::length( $i_pat[$cur_idx] ) ) {
$i_pat[$cur_idx] =~ s/$FORCE_REGEX_LAZY(?<!\')\#(?!\')//og; # remove any left over non-literal #
$result = $result . $i_pat[$cur_idx]; # prepend it (e.g. 0 and -)
}
if ( substr( $result, 0, 1 ) eq ',' ) {
substr( $result, 0, 1, '' );
}
$result .= $trailing_non_n;
if ( defined $decimals && CORE::length($decimals) ) {
my @d_pat = ($d_pat); # TODO ? support sepeartor in decimal, !definedvia CLDR, no patterns have that ATM ? split( /(?<!\')\,(?!\')/, $d_pat );
$result .= '.';
$cur_idx = 0;
$trailing_non_n = '';
while ( $d_pat[-1] =~ s/$FORCE_REGEX_LAZY((?:\'.\')+)$//o || $d_pat[-1] =~ s/$FORCE_REGEX_LAZY([^0#]+)$//o ) {
$trailing_non_n = "$1$trailing_non_n";
}
while ( CORE::length( $cur_d = CORE::substr( $decimals, 0, 1, '' ) ) ) {
if ( !CORE::length( $d_pat[$cur_idx] ) ) { # this chunk is spent
if ( !defined $d_pat[ $cur_idx + 1 ] ) { # there are no more chunks
$cur_pat = '#';
}
else { # next chunk please
$result .= ',';
$cur_idx++;
}
}
if ( CORE::length( $d_pat[$cur_idx] ) ) {
if ( index( $d_pat[$cur_idx], q{'.'} ) == 0 ) {
$result .= CORE::substr( $d_pat[$cur_idx], 0, 3, '' );
redo;
}
$cur_pat = CORE::substr( $d_pat[$cur_idx], 0, 1, '' );
if ( $cur_pat ne '0' && $cur_pat ne '#' ) {
$result .= $cur_pat;
redo;
}
}
$result .= $cur_d;
}
if ( substr( $result, -1, ) eq ',' ) {
chop($result);
}
if ( defined $d_pat[$cur_idx] ) {
$d_pat[$cur_idx] =~ s/$FORCE_REGEX_LAZY(?<!\')\#(?!\')//og; # remove any left over non-literal #
$result .= $d_pat[$cur_idx]; # append it (e.g. 0 and -)
}
$result .= $trailing_non_n;
}
}
my $used_place_holder = $cldr_formats->{_decimal_format_decimal} ne '.' && index( $result, '.' ) > -1 && $result =~ s/$FORCE_REGEX_LAZY(?<!\')\.(?!\')/_LOCALES-DECIMAL-PLACEHOLDER_/g;
if ( $cldr_formats->{_decimal_format_group} ne ',' && index( $result, ',' ) > -1 ) {
$result =~ s/$FORCE_REGEX_LAZY(?<!\')\,(?!\')/$cldr_formats->{_decimal_format_group}/og;
}
if ($used_place_holder) {
my $ix;
substr( $result, $ix, 29, $cldr_formats->{_decimal_format_decimal} ) while ( $ix = index( $result, '_LOCALES-DECIMAL-PLACEHOLDER_' ) ) > -1;
}
if ( $is_negative && !$negative_pat ) {
$result = "-$result";
}
return $result;
}
sub get_territory_codes {
$_[0]->_load_territory_data() if !$_[0]->{'territory_data'};
return keys %{ shift->{'territory_data'}{'code_to_name'} };
}
sub get_territory_names {
$_[0]->_load_territory_data() if !$_[0]->{'territory_data'};
return values %{ shift->{'territory_data'}{'code_to_name'} };
}
sub get_territory_lookup {
$_[0]->_load_territory_data() if !$_[0]->{'territory_data'};
return %{ shift->{'territory_data'}{'code_to_name'} };
}
sub get_territory_from_code {
my ( $self, $code, $always_return ) = @_;
$code ||= $self->{'territory'};
$code = normalize_tag($code);
return if !defined $code;
$self->_load_territory_data() if !$self->{'territory_data'};
if ( exists $self->{'territory_data'}{'code_to_name'}{$code} ) {
return $self->{'territory_data'}{'code_to_name'}{$code};
}
elsif ( !defined $self->{'territory'} || $code ne $self->{'territory'} ) {
my ( $l, $t ) = split_tag($code);
if ( $t && exists $self->{'territory_data'}{'code_to_name'}{$t} ) {
return $self->{'territory_data'}{'code_to_name'}{$t};
}
}
return $code if $always_return;
return;
}
sub get_code_from_territory {
my ( $self, $name ) = @_;
return if !$name;
my $key = normalize_for_key_lookup($name);
$self->_load_territory_data() if !$self->{'territory_data'};
if ( !$self->{'territory_data'}{'nam'} ) {
$self->{'territory_data'}{'name_to_code'} = { map { normalize_for_key_lookup( $self->{'territory_data'}{'code_to_name'}->{$_} ) => $_ } keys %{ $self->{'territory_data'}{'code_to_name'} } };
}
if ( exists $self->{'territory_data'}{'name_to_code'}{$key} ) {
return $self->{'territory_data'}{'name_to_code'}{$key};
}
return;
}
{
no warnings 'once';
*code2territory = *get_territory_from_code;
*territory2code = *get_code_from_territory;
}
sub get_language_codes {
$_[0]->_load_language_data_code_to_name() if !$_[0]->{'language_data'}{'code_to_name'};
return keys %{ $_[0]->{'language_data'}{'code_to_name'} };
}
sub get_language_names {
$_[0]->_load_language_data_code_to_name() if !$_[0]->{'language_data'}{'code_to_name'};
return values %{ $_[0]->{'language_data'}{'code_to_name'} };
}
sub get_language_lookup {
$_[0]->_load_language_data_code_to_name() if !$_[0]->{'language_data'}{'code_to_name'};
return %{ $_[0]->{'language_data'}{'code_to_name'} };
}
sub get_language_from_code {
my ( $self, $code, $always_return ) = @_;
$code ||= $self->{'locale'};
$code = normalize_tag($code);
return if !defined $code;
$always_return ||= 1 if $code eq $self->get_locale() && $self->get_soft_locale_fallback(); # force $always_return under soft locale objects
$always_return ||= 0;
$self->_load_language_data_code_to_name() if !$_[0]->{'language_data'}{'code_to_name'};
if ( exists $self->{'language_data'}{'code_to_name'}{$code} ) {
return $self->{'language_data'}{'code_to_name'}{$code};
}
elsif ($always_return) {
$self->_load_territory_data() if !$self->{'territory_data'};
my ( $l, $t ) = split_tag($code);
my $ln = $self->{'language_data'}{'code_to_name'}{$l};
my $tn = defined $t ? $self->{'territory_data'}{'code_to_name'}{$t} : '';
return $code if !$ln && !$tn;
$ln ||= $l;
$tn ||= $t;
my $string = $self->{'language_data'}{'misc_info'}{'cldr_formats'}{'locale'} || '{0} ({1})';
substr( $string, index( $string, '{0}' ), 3, $ln ) while index( $string, '{0}' ) > -1;
substr( $string, index( $string, '{1}' ), 3, $tn ) while index( $string, '{1}' ) > -1;
return $string;
}
return;
}
sub get_code_from_language {
my ( $self, $name ) = @_;
return if !$name;
my $key = normalize_for_key_lookup($name);
$self->_load_language_data_code_to_name() if !$_[0]->{'language_data'}{'code_to_name'};
if ( !$self->{'language_data'}{'name_to_code'} ) {
$self->{'language_data'}{'name_to_code'} = { map { normalize_for_key_lookup( $self->{'language_data'}{'code_to_name'}->{$_} ) => $_ } keys %{ $self->{'language_data'}{'code_to_name'} } };
}
if ( exists $self->{'language_data'}{'name_to_code'}{$key} ) {
return $self->{'language_data'}{'name_to_code'}{$key};
}
return;
}
{
no warnings 'once';
*code2language = *get_language_from_code;
*language2code = *get_code_from_language;
}
sub tag_is_soft_locale {
my ($tag) = @_;
my ( $l, $t ) = split_tag($tag);
return if !defined $l; # invalid tag is not soft
return if !$t; # no territory part means it is not soft
return if tag_is_loadable($tag); # if it can be loaded directly then it is not soft
return if !territory_code_is_known($t); # if the territory part is not known then it is not soft
return if !tag_is_loadable($l); # if the language part is not known then it is not soft
return $l; # it is soft, so return the value suitable for 'soft_locale_fallback'
}
sub tag_is_loadable {
my ( $tag, $as_territory ) = @_; # not documenting internal $as_territory, just use territory_code_is_known() directly
if ( !exists $INC{"Cpanel/CPAN/Locales/DB/Loadable.pm"} ) {
local $SIG{'__DIE__'}; # cpanel specific: ensure a benign eval does not trigger cpsrvd's DIE handler (may be made moot by internal case 50857)
eval "require Cpanel::CPAN::Locales::DB::Loadable" || return; # Module::Want::have_mod("Cpanel::CPAN::Locales::DB::Loadable") || return;
}
if ($as_territory) {
no warnings 'once';
return 1 if exists $Cpanel::CPAN::Locales::DB::Loadable::territory{$tag};
}
else {
return 1 if exists $Cpanel::CPAN::Locales::DB::Loadable::code{$tag};
}
return;
}
sub get_loadable_language_codes {
if ( !exists $INC{"Cpanel/CPAN/Locales/DB/Loadable.pm"} ) {
local $SIG{'__DIE__'}; # cpanel specific: ensure a benign eval does not trigger cpsrvd's DIE handler (may be made moot by internal case 50857)
eval "require Cpanel::CPAN::Locales::DB::Loadable" || return; # Module::Want::have_mod("Cpanel::CPAN::Locales::DB::Loadable") || return;
}
return keys %Cpanel::CPAN::Locales::DB::Loadable::code;
}
sub territory_code_is_known {
return tag_is_loadable( $_[0], 1 );
}
sub split_tag {
return split( /_/, normalize_tag( $_[0] ), 2 ); # we only do language[_territory]
}
sub get_i_tag_for_string {
my $norm = normalize_tag( $_[0] );
if ( substr( $norm, 0, 2 ) eq 'i_' ) {
return $norm;
}
else {
return 'i_' . $norm;
}
}
my %non_locales = (
'und' => 1,
'zxx' => 1,
'mul' => 1,
'mis' => 1,
'art' => 1,
);
sub non_locale_list {
return ( sort keys %non_locales );
}
sub is_non_locale {
my $tag = normalize_tag( $_[0] ) || return;
return 1 if exists $non_locales{$tag};
return;
}
sub typical_en_alias_list {
return ( 'en_us', 'i_default' );
}
sub is_typical_en_alias {
my $tag = normalize_tag( $_[0] ) || return;
return 1 if $tag eq 'en_us' || $tag eq 'i_default';
return;
}
sub normalize_tag_for_datetime_locale {
my ( $pre, $pst ) = split_tag( $_[0] ); # we only do language[_territory]
return if !defined $pre;
if ($pst) {
return $pre . '_' . uc($pst);
}
else {
return $pre;
}
}
sub normalize_tag_for_ietf {
my ( $pre, $pst ) = split_tag( $_[0] ); # we only do language[_territory]
return if !defined $pre;
if ($pst) {
return $pre . '-' . uc($pst);
}
else {
return $pre;
}
}
sub normalize_for_key_lookup {
my $key = $_[0];
return '' if !defined $key;
$key =~ tr/A-Z/a-z/; # lowercase
$key =~ s{\s+}{}g if $key =~ tr{ \t\r\n\f}{};
$key =~ tr{\'\"\-\(\)\[\]\_}{}d;
return $key;
}
sub plural_rule_string_to_javascript_code {
require Cpanel::CPAN::Locales::Compile;
*plural_rule_string_to_javascript_code = \&Cpanel::CPAN::Locales::Compile::plural_rule_string_to_javascript_code;
goto \&Cpanel::CPAN::Locales::Compile::plural_rule_string_to_javascript_code;
}
sub plural_rule_string_to_code {
require Cpanel::CPAN::Locales::Compile;
*plural_rule_string_to_code = \&Cpanel::CPAN::Locales::Compile::plural_rule_string_to_code;
goto \&Cpanel::CPAN::Locales::Compile::plural_rule_string_to_code;
}
sub plural_rule_hashref_to_code {
my ($hr) = @_;
if ( ref( $hr->{'category_rules'} ) ne 'HASH' ) {
$hr->{'category_rules_compiled'} = {
'one' => q{sub { return 'one' if ( ( $n == 1 ) ); return;};},
};
return sub {
my ($n) = @_;
return 'one' if $n == 1;
return;
};
}
else {
for my $cat ( get_cldr_plural_category_list(1) ) {
next if !exists $hr->{'category_rules'}{$cat};
next if exists $hr->{'category_rules_compiled'}{$cat};
$hr->{'category_rules_compiled'}{$cat} = plural_rule_string_to_code( $hr->{'category_rules'}{$cat}, $cat );
}
return sub {
my ($n) = @_;
my $match;
PCAT:
for my $cat ( get_cldr_plural_category_list(1) ) { # use function instead of keys to preserve processing order
next if !exists $hr->{'category_rules_compiled'}{$cat};
if ( ref( $hr->{'category_rules_compiled'}{$cat} ) ne 'CODE' ) {
local $SIG{'__DIE__'}; # cpanel specific: ensure a benign eval does not trigger cpsrvd's DIE handler (may be made moot by internal case 50857)
$hr->{'category_rules_compiled'}{$cat} = eval "$hr->{'category_rules_compiled'}{$cat}"; ## no critic (ProhibitStringyEval) # As of 0.22 this will be skipped for modules included w/ the main dist
}
if ( $hr->{'category_rules_compiled'}{$cat}->($n) ) {
$match = $cat;
last PCAT;
}
}
return $match if $match;
return;
};
}
}
sub get_cldr_plural_category_list {
return qw(zero one two few many other) if $_[0]; # check order
return qw(one two few many other zero); # quant() arg order
}
sub get_fallback_list {
my ( $self, $special_lookup ) = @_;
my ( $super, $ter ) = split_tag( $self->{'locale'} );
return (
$self->{'locale'},
( $super ne $self->{'locale'} && $super ne 'i' ? $super : () ),
( @{ $self->{'language_data'}{'misc_info'}{'fallback'} } ),
(
defined $special_lookup && ref($special_lookup) eq 'CODE'
? ( map { my $n = Cpanel::Locale::Utils::Normalize::normalize_tag($_); $n ? ($n) : () } $special_lookup->( $self->{'locale'} ) )
: ()
),
'en'
);
}
sub get_cldr_number_symbol_decimal {
return $_[0]->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_decimal'} || '.';
}
sub get_cldr_number_symbol_group {
return $_[0]->{'language_data'}{'misc_info'}{'cldr_formats'}{'_decimal_format_group'} || ',';
}
1;
} # --- END Cpanel/CPAN/Locales.pm
{ # --- BEGIN Cpanel/Encoder/Punycode.pm
package Cpanel::Encoder::Punycode;
use strict;
use warnings;
our $VERSION = '1.0';
sub punycode_encode_str {
my ($string) = @_;
return $string if $string !~ tr<\x00-\x7f><>c;
my $at_at = index( $string, '@' );
require Cpanel::UTF8::Strict;
require Net::IDN::Encode;
if ( $at_at > -1 ) {
my $local_part = substr( $string, 0, $at_at );
my $domain = substr( $string, 1 + $at_at );
Cpanel::UTF8::Strict::decode($local_part);
Cpanel::UTF8::Strict::decode($domain);
return Net::IDN::Encode::domain_to_ascii($local_part) . '@' . Net::IDN::Encode::domain_to_ascii($domain);
}
Cpanel::UTF8::Strict::decode($string);
return Net::IDN::Encode::domain_to_ascii($string);
}
sub punycode_decode_str {
my ($string) = @_;
return $string if index( $string, 'xn--' ) == -1;
require Net::IDN::Encode;
my $at_at = index( $string, '@' );
if ( -1 != $at_at ) {
my $local_part = Net::IDN::Encode::domain_to_unicode( substr( $string, 0, $at_at ) );
my $domain = Net::IDN::Encode::domain_to_unicode( substr( $string, 1 + $at_at ) );
utf8::encode($local_part);
utf8::encode($domain);
return $local_part . '@' . $domain;
}
my $str = Net::IDN::Encode::domain_to_unicode($string);
utf8::encode($str);
return $str;
}
1;
} # --- END Cpanel/Encoder/Punycode.pm
{ # --- BEGIN Cpanel/CPAN/Locale/Maketext/Utils.pm
package Cpanel::CPAN::Locale::Maketext::Utils;
$Cpanel::CPAN::Locale::Maketext::Utils::VERSION = 0.33_95;
# use Cpanel::CPAN::Locale::Maketext 1.13_89 (); # our 1.13_89 contains some optimizations and support for external_lex_cache that made its way to CPAN by v1.22
@Cpanel::CPAN::Locale::Maketext::Utils::ISA = qw(Cpanel::CPAN::Locale::Maketext);
use constant LOCALE_FALLBACK_CACHE_DIR => '/usr/local/cpanel/etc/locale/fallback';
my $FORCE_REGEX_LAZY = '';
my %singleton_stash = ();
sub _compile {
my ( $lh, $string ) = @_;
substr( $string, index( $string, '_TILDE_' ), 7, '~~' ) while index( $string, '_TILDE_' ) > -1; # this helps make parsing easier (via code or visually)
my $compiled = $lh->SUPER::_compile($string);
return $compiled if ref($compiled) ne 'CODE';
return sub {
return $compiled->( $_[0], @_[ 1 .. $#_ ] ) if !grep { defined && index( $_, '_' ) > -1 } @_[ 1 .. $#_ ];
my ( $lh, @ref_args ) = @_;
my $built = $compiled->(
$lh,
map {
if ( defined && index( $_, '_' ) > -1 ) {
s/$FORCE_REGEX_LAZY\_(\-?[0-9]+|\*)/-!-$1-!-/og;
}
$_ # Change embedded-arg-looking-string to a
} @ref_args
);
$built =~ s/$FORCE_REGEX_LAZY-!-(\-?[0-9]+|\*)-!-/_$1/og; # Change placeholders back to their original
return $built;
};
}
sub get_handle {
my ( $class, @langtags ) = @_;
my $args_sig = join( ',', @langtags ) || 'no_args';
if ( exists $singleton_stash{$class}{$args_sig} ) {
$singleton_stash{$class}{$args_sig}->{'_singleton_reused'}++;
}
else {
$singleton_stash{$class}{$args_sig} = $class->SUPER::get_handle(@langtags);
}
return $singleton_stash{$class}{$args_sig};
}
sub get_locales_obj {
my ( $lh, $tag ) = @_;
$tag ||= $lh->get_language_tag();
if ( !exists $lh->{'Locales.pm'}{$tag} ) {
require Cpanel::CPAN::Locales;
$lh->{'Locales.pm'}{$tag} =
Cpanel::CPAN::Locales->new($tag)
|| ( $tag ne substr( $tag, 0, 2 ) ? Cpanel::CPAN::Locales->new( substr( $tag, 0, 2 ) ) : '' )
|| (
$lh->{'fallback_locale'}
? ( Cpanel::CPAN::Locales->new( $lh->{'fallback_locale'} )
|| ( $lh->{'fallback_locale'} ne substr( $lh->{'fallback_locale'}, 0, 2 ) ? Cpanel::CPAN::Locales->new( substr( $lh->{'fallback_locale'}, 0, 2 ) ) : '' ) )
: ''
)
|| Cpanel::CPAN::Locales->new('en');
}
return $lh->{'Locales.pm'}{$tag};
}
sub init {
my ($lh) = @_;
$lh->SUPER::init();
$lh->remove_key_from_lexicons('_AUTO');
no strict 'refs';
for my $ns ( $lh->get_base_class(), $lh->get_language_class() ) {
if ( defined ${ $ns . '::Encoding' } ) {
$lh->{'encoding'} = ${ $ns . '::Encoding' } if ${ $ns . '::Encoding' };
}
}
$lh->fail_with(
sub {
my ( $lh, $key, @args ) = @_;
my $lookup;
if ( exists $lh->{'_get_key_from_lookup'} ) {
if ( ref $lh->{'_get_key_from_lookup'} eq 'CODE' ) {
$lookup = $lh->{'_get_key_from_lookup'}->( $lh, $key, @args );
}
}
return $lookup if defined $lookup;
if ( exists $lh->{'_log_phantom_key'} ) {
if ( ref $lh->{'_log_phantom_key'} eq 'CODE' ) {
$lh->{'_log_phantom_key'}->( $lh, $key, @args );
}
}
if ( $lh->{'use_external_lex_cache'} ) {
local $lh->{'_external_lex_cache'}{'_AUTO'} = 1;
if ( index( $key, '_' ) == 0 ) {
return $lh->{'_external_lex_cache'}{$key} = $key;
}
return $lh->maketext( $key, @args );
}
else {
no strict 'refs';
local ${ $lh->get_base_class() . '::Lexicon' }{'_AUTO'} = 1;
if ( index( $key, '_' ) == 0 ) {
return ${ $lh->get_base_class() . '::Lexicon' }{$key} = $key;
}
return $lh->maketext( $key, @args );
}
}
);
}
*makevar = \&Cpanel::CPAN::Locale::Maketext::maketext;
sub makethis {
my ( $lh, $phrase, @phrase_args ) = @_;
$lh->{'cache'}{'makethis'}{$phrase} ||= $lh->_compile($phrase);
my $type = ref( $lh->{'cache'}{'makethis'}{$phrase} );
if ( $type eq 'SCALAR' ) {
return ${ $lh->{'cache'}{'makethis'}{$phrase} };
}
elsif ( $type eq 'CODE' ) {
return $lh->{'cache'}{'makethis'}{$phrase}->( $lh, @phrase_args );
}
else {
return $lh->{'cache'}{'makethis'}{$phrase};
}
}
sub makethis_base {
my ($lh) = @_;
$lh->{'cache'}{'makethis_base'} ||= $lh->get_base_class()->get_handle( $lh->{'fallback_locale'} || 'en' ); # this allows to have a separate cache of compiled phrases (? get_handle() explicit or base_locales() (i.e. en en_us i_default || L::M->fallback_languages) ?)
return $lh->{'cache'}{'makethis_base'}->makethis( @_[ 1 .. $#_ ] );
}
sub make_alias {
my ( $lh, $pkgs, $is_base_class ) = @_;
my $ns = $lh->get_language_class();
return if $ns =~ tr{:0-9A-Za-z_-}{}c;
my $base = $is_base_class ? $ns : $lh->get_base_class();
no strict 'refs';
for my $pkg ( ref $pkgs ? @{$pkgs} : $pkgs ) {
next if $pkg =~ tr{:0-9A-Za-z_-}{}c;
*{ $base . '::' . $pkg . '::Encoding' } = *{ $ns . '::Encoding' };
*{ $base . '::' . $pkg . '::Lexicon' } = *{ $ns . '::Lexicon' };
@{ $base . '::' . $pkg . '::ISA' } = ($ns);
}
}
sub remove_key_from_lexicons {
my ( $lh, $key ) = @_;
my $idx = 0;
for my $lex_hr ( @{ $lh->_lex_refs() } ) {
$lh->{'_removed_from_lexicons'}{$idx}{$key} = delete $lex_hr->{$key} if exists $lex_hr->{$key};
$idx++;
}
}
my %grapheme_lookup = (
'trademark' => "\xE2\x84\xA2", # 'TRADE MARK SIGN' (U+2122)
'registered' => "\xC2\xAE", # 'REGISTERED SIGN' (U+00AE)
'copyright' => "\xC2\xA9", # 'COPYRIGHT SIGN' (U+00A9)
'left_double_quote' => "\xE2\x80\x9C", # 'LEFT DOUBLE QUOTATION MARK' (U+201C)
'right_double_quote' => "\xE2\x80\x9D", # 'RIGHT DOUBLE QUOTATION MARK' (U+201D)
'ellipsis' => "\xE2\x80\xA6", # 'HORIZONTAL ELLIPSIS' (U+2026)
'left_single_quote' => "\xE2\x80\x98", # 'LEFT SINGLE QUOTATION MARK' (U+2018)
'right_single_quote' => "\xE2\x80\x99", # 'RIGHT SINGLE QUOTATION MARK'
'infinity' => "\xE2\x88\x9E", # 'INFINITY' (U+221E)
);
sub get_grapheme_helper_hashref {
return {%grapheme_lookup}; # copy
}
sub get_base_class {
my $ns = $_[0]->get_language_class();
return $ns if $ns eq 'Cpanel::Locale';
return substr( $ns, 0, rindex( $ns, '::' ) );
}
sub append_to_lexicons {
my ( $lh, $appendage ) = @_;
return if ref $appendage ne 'HASH';
no strict 'refs';
for my $lang ( keys %{$appendage} ) {
my $ns = $lh->get_base_class() . ( $lang eq '_' ? '' : "::$lang" ) . '::Lexicon';
%{$ns} = ( %{$ns}, %{ $appendage->{$lang} } );
}
}
sub langtag_is_loadable {
my ( $lh, $wants_tag ) = @_;
$wants_tag = Cpanel::CPAN::Locale::Maketext::language_tag($wants_tag);
my $tag_obj = eval $lh->get_base_class() . q{->get_handle( $wants_tag );};
my $has_tag = $tag_obj->language_tag();
return $wants_tag eq $has_tag ? $tag_obj : 0;
}
sub get_language_tag {
return ( split '::', $_[0]->get_language_class() )[-1];
}
sub print {
local $Carp::CarpLevel = 1;
print $_[0]->maketext( @_[ 1 .. $#_ ] );
}
sub fetch {
local $Carp::CarpLevel = 1;
return $_[0]->maketext( @_[ 1 .. $#_ ] );
}
sub say {
local $Carp::CarpLevel = 1;
my $text = $_[0]->maketext( @_[ 1 .. $#_ ] );
local $/ = !defined $/ || !$/ ? "\n" : $/; # otherwise assume they are not stupid
print $text . $/ if $text;
}
sub get {
local $Carp::CarpLevel = 1;
my $text = $_[0]->maketext( @_[ 1 .. $#_ ] );
local $/ = !defined $/ || !$/ ? "\n" : $/; # otherwise assume they are not stupid
return $text . $/ if $text;
return;
}
sub get_language_tag_name {
my ( $lh, $tag, $in_locale_tongue ) = @_;
$tag ||= $lh->get_language_tag();
my $loc_obj = $lh->get_locales_obj( $in_locale_tongue ? () : ($tag) );
if ( $loc_obj->{'native_data'} && $tag eq $lh->get_language_tag() ) {
return $loc_obj->get_native_language_from_code($tag);
}
return $loc_obj->get_language_from_code($tag);
}
sub get_html_dir_attr {
my ( $lh, $raw_cldr, $is_tag ) = @_;
if ($is_tag) {
$raw_cldr = $lh->get_language_tag_character_orientation($raw_cldr);
}
else {
$raw_cldr ||= $lh->get_language_tag_character_orientation();
}
if ( $raw_cldr eq 'left-to-right' ) {
return 'ltr';
}
elsif ( $raw_cldr eq 'right-to-left' ) {
return 'rtl';
}
return;
}
sub get_locale_display_pattern {
require Cpanel::CPAN::Locales::DB::LocaleDisplayPattern::Tiny;
return Cpanel::CPAN::Locales::DB::LocaleDisplayPattern::Tiny::get_locale_display_pattern( $_[1] || $_[0]->{'fallback_locale'} || $_[0]->get_language_tag() );
}
sub get_language_tag_character_orientation {
require Cpanel::CPAN::Locales::DB::CharacterOrientation::Tiny;
return Cpanel::CPAN::Locales::DB::CharacterOrientation::Tiny::get_orientation( $_[1] || $_[0]->{'fallback_locale'} || $_[0]->get_language_tag() );
}
*lextext = *text;
sub text {
if ( @_ != 2 ) {
require Carp;
Carp::croak('text() requires a singlef parameter');
}
my ( $handle, $phrase ) = splice( @_, 0, 2 );
unless ( defined($handle) && defined($phrase) ) {
require Carp;
Carp::confess('No handle/phrase');
}
if ( !$handle->{'use_external_lex_cache'} ) {
require Carp;
Carp::carp("text() requires you to have 'use_external_lex_cache' enabled.");
return;
}
local $@;
my $value;
foreach my $h_r ( @{ $handle->_lex_refs } ) { # _lex_refs() caches itself
if ( defined( $value = $h_r->{$phrase} ) ) {
if ( ref $value ) {
require Carp;
Carp::carp("Previously compiled phrase ('use_external_lex_cache' enabled after phrase was compiled?)");
}
return $value eq '' ? $phrase : $value;
}
elsif ( index( $phrase, '_' ) != 0 and $h_r->{'_AUTO'} ) {
return $phrase;
}
}
return ( !defined $value || $value eq '' ) ? $phrase : $value;
}
our $_NATIVE_ONLY = 0;
sub lang_names_hashref_native_only {
local $_NATIVE_ONLY = 1;
return lang_names_hashref(@_);
}
sub lang_names_hashref {
my ( $lh, @langcodes ) = @_;
if ( !@langcodes ) { # they havn't specified any langcodes...
require File::Spec; # only needed here, so we don't use() it
my @search;
my $path = $lh->get_base_class();
substr( $path, index( $path, '::' ), 2, '/' ) while index( $path, '::' ) > -1;
if ( ref $lh->{'_lang_pm_search_paths'} eq 'ARRAY' ) {
@search = @{ $lh->{'_lang_pm_search_paths'} };
}
@search = @INC if !@search; # they havn't told us where they are specifically
DIR:
for my $dir (@search) {
my $lookin = File::Spec->catdir( $dir, $path );
next DIR if !-d $lookin;
if ( opendir my $dh, $lookin ) {
PM:
for my $pm ( grep { /^\w+\.pm$/ } grep !/^\.+$/, readdir($dh) ) {
substr( $pm, -3, 3, '' ); # checked above - if substr( $pm, -3 ) eq '.pm';
next PM if !$pm;
next PM if $pm eq 'Utils';
next PM if $pm eq 'Context';
next PM if $pm eq 'Lazy';
push @langcodes, $pm;
}
closedir $dh;
}
}
}
require Cpanel::CPAN::Locales;
$lh->{'Locales.pm'}{'_main_'} ||= $lh->get_locales_obj();
my $langname = {};
my $native = wantarray && $Cpanel::CPAN::Locales::VERSION > 0.06 ? {} : undef;
my $direction = wantarray && $Cpanel::CPAN::Locales::VERSION > 0.09 ? {} : undef;
for my $code ( 'en', @langcodes ) { # en since it is "built in"
if ( defined $native ) {
$native->{$code} = $lh->{'Locales.pm'}{'_main_'}->get_native_language_from_code( $code, 1 );
}
$langname->{$code} = $_NATIVE_ONLY ? $native->{$code} : $lh->{'Locales.pm'}{'_main_'}->get_language_from_code( $code, 1 );
if ( defined $direction ) {
$direction->{$code} = $lh->{'Locales.pm'}{'_main_'}->get_character_orientation_from_code_fast($code);
}
}
return wantarray ? ( $langname, $native, $direction ) : $langname;
}
sub loadable_lang_names_hashref {
my ( $lh, @langcodes ) = @_;
my $langname = $lh->lang_names_hashref(@langcodes);
for my $tag ( keys %{$langname} ) {
delete $langname->{$tag} if !$lh->langtag_is_loadable($tag);
}
return $langname;
}
sub add_lexicon_override_hash {
my ( $lh, $langtag, $name, $hr ) = @_;
if ( @_ == 3 ) {
$hr = $name;
$name = $langtag;
$langtag = $lh->get_language_tag();
}
my $ns = $lh->get_language_tag() eq $langtag ? $lh->get_language_class() : $lh->get_base_class();
no strict 'refs';
if ( my $ref = tied( %{ $ns . '::Lexicon' } ) ) {
return 1 if $lh->{'add_lex_hash_silent_if_already_added'} && exists $ref->{'hashes'} && exists $ref->{'hashes'}{$name};
if ( $ref->can('add_lookup_override_hash') ) {
return $ref->add_lookup_override_hash( $name, $hr );
}
}
my $cur_errno = $!;
if ( eval { require Sub::Todo } ) {
goto &Sub::Todo::todo;
}
else {
$! = $cur_errno;
return;
}
}
sub add_lexicon_fallback_hash {
my ( $lh, $langtag, $name, $hr ) = @_;
if ( @_ == 3 ) {
$hr = $name;
$name = $langtag;
$langtag = $lh->get_language_tag();
}
my $ns = $lh->get_language_tag() eq $langtag ? $lh->get_language_class() : $lh->get_base_class();
no strict 'refs';
if ( my $ref = tied( %{ $ns . '::Lexicon' } ) ) {
return 1 if $lh->{'add_lex_hash_silent_if_already_added'} && exists $ref->{'hashes'} && exists $ref->{'hashes'}{$name};
if ( $ref->can('add_lookup_fallback_hash') ) {
return $ref->add_lookup_fallback_hash( $name, $hr );
}
}
my $cur_errno = $!;
if ( eval { require Sub::Todo } ) {
goto &Sub::Todo::todo;
}
else {
$! = $cur_errno;
return;
}
}
sub del_lexicon_hash {
my ( $lh, $langtag, $name ) = @_;
if ( @_ == 2 ) {
return if $langtag eq '*';
$name = $langtag;
$langtag = '*';
}
return if !$langtag;
my $count = 0;
if ( $langtag eq '*' ) {
no strict 'refs';
for my $ns ( $lh->get_base_class(), $lh->get_language_class() ) {
if ( my $ref = tied( %{ $ns . '::Lexicon' } ) ) {
if ( $ref->can('del_lookup_hash') ) {
$ref->del_lookup_hash($name);
$count++;
}
}
}
return 1 if $count;
my $cur_errno = $!;
if ( eval { require Sub::Todo } ) {
goto &Sub::Todo::todo;
}
else {
$! = $cur_errno;
return;
}
}
else {
my $ns = $lh->get_language_tag() eq $langtag ? $lh->get_language_class() : $lh->get_base_class();
no strict 'refs';
if ( my $ref = tied( %{ $ns . '::Lexicon' } ) ) {
if ( $ref->can('del_lookup_hash') ) {
return $ref->del_lookup_hash($name);
}
}
my $cur_errno = $!;
if ( eval { require Sub::Todo } ) {
goto &Sub::Todo::todo;
}
else {
$! = $cur_errno;
return;
}
}
}
sub get_language_class {
return ref( $_[0] ) || $_[0];
}
sub get_base_class_dir {
my ($lh) = @_;
if ( !exists $lh->{'Cpanel::CPAN::Locale::Maketext::Utils'}{'_base_clase_dir'} ) {
$lh->{'Cpanel::CPAN::Locale::Maketext::Utils'}{'_base_clase_dir'} = undef;
my $inc_key = $lh->get_base_class();
substr( $inc_key, index( $inc_key, '::' ), 2, '/' ) while index( $inc_key, '::' ) > -1;
$inc_key .= '.pm';
if ( exists $INC{$inc_key} ) {
if ( -e $INC{$inc_key} ) {
my $hr = $lh->{'Cpanel::CPAN::Locale::Maketext::Utils'};
$hr->{'_base_clase_dir'} = $INC{$inc_key};
substr( $hr->{'_base_clase_dir'}, -3, 3, '' ) if substr( $hr->{'_base_clase_dir'}, -3 ) eq '.pm';
}
}
}
return $lh->{'Cpanel::CPAN::Locale::Maketext::Utils'}{'_base_clase_dir'};
}
sub list_available_locales {
my ($lh) = @_;
die "List context only!" if !wantarray;
my $main_ns_dir = $lh->get_base_class_dir() || return;
local $!;
opendir my $dh, $main_ns_dir or die "Failed to open: $main_ns_dir: $!";
return map { ( substr( $_, -3 ) eq '.pm' && $_ ne 'Utils.pm' && $_ ne 'Lazy.pm' && $_ ne 'Context.pm' && $_ ne 'Fallback.pm' ) ? substr( $_, 0, -3 ) : () } readdir($dh); #de-taint
}
sub get_asset {
my ( $lh, $code, $tag ) = @_; # No caching since $code can do anything.
my $root = $tag || $lh->get_language_tag;
my $ret;
die "Invalid locale: $root" if index( $root, '/' ) > -1;
$ret = $code->($root);
return $ret if defined $ret;
my $loc; # buffer
my %seen = ( $root => 1 );
my @fallback_locales;
if ( $lh->_has_fallback_list($root) ) {
my $loc_obj = $lh->get_locales_obj($tag);
@fallback_locales = $loc_obj->get_fallback_list( $lh->{'Locales.pm'}{'get_fallback_list_special_lookup_coderef'} );
}
elsif ( $root ne 'en' ) {
my $super = ( split( m{_}, $root ) )[0];
@fallback_locales = (
( $super ne $root && $super ne 'i' ? $super : () ),
'en'
);
}
for $loc (@fallback_locales) {
next if $seen{$loc}; # get_fallback_list can provide back dupes and its expensive to enumerate each one
$ret = $code->($loc);
$seen{$loc}++;
last if defined $ret;
}
return $ret if defined $ret;
return;
}
sub _has_fallback_list {
return $_[0]->{'_has_fallback_list'}{ $_[1] } if defined $_[0]->{'_has_fallback_list'}{ $_[1] };
my $size = -s LOCALE_FALLBACK_CACHE_DIR . '/' . $_[1];
return ( $_[0]->{'_has_fallback_list'}{ $_[1] } = ( !defined $size || $size ) ? 1 : 0 );
}
sub get_asset_file {
my ( $lh, $find, $return ) = @_;
$return = $find if !defined $return;
return $lh->{'cache'}{'get_asset_file'}{$find}{$return} if exists $lh->{'cache'}{'get_asset_file'}{$find}{$return};
$lh->{'cache'}{'get_asset_file'}{$find}{$return} = $lh->get_asset(
sub {
return sprintf( $return, $_[0] ) if -f sprintf( $find, $_[0] );
return;
}
);
return $lh->{'cache'}{'get_asset_file'}{$find}{$return} if defined $lh->{'cache'}{'get_asset_file'}{$find}{$return};
return;
}
sub get_asset_dir {
my ( $lh, $find, $return ) = @_;
$return = $find if !defined $return;
return $lh->{'cache'}{'get_asset_dir'}{$find}{$return} if exists $lh->{'cache'}{'get_asset_dir'}{$find}{$return};
$lh->{'cache'}{'get_asset_dir'}{$find}{$return} = $lh->get_asset(
sub {
return sprintf( $return, $_[0] ) if -d sprintf( $find, $_[0] );
return;
}
);
return $lh->{'cache'}{'get_asset_dir'}{$find}{$return} if defined $lh->{'cache'}{'get_asset_dir'}{$find}{$return};
return;
}
sub delete_cache {
my ( $lh, $which ) = @_;
if ( defined $which ) {
return delete $lh->{'cache'}{$which};
}
else {
return delete $lh->{'cache'};
}
}
sub quant {
my ( $handle, $num, @forms ) = @_;
my $max_decimal_places = 3;
if ( ref($num) eq 'ARRAY' ) {
$max_decimal_places = $num->[1];
$num = $num->[0];
}
$handle->{'Locales.pm'}{'_main_'} ||= $handle->get_locales_obj();
my ( $string, $spec_zero ) = $handle->{'Locales.pm'}{'_main_'}->get_plural_form( $num, @forms );
if ( index( $string, '%s' ) > -1 ) {
return sprintf( $string, $handle->numf( $num, $max_decimal_places ) );
}
elsif ( $num == 0 && $spec_zero ) {
return $string;
}
else {
$handle->numf( $num, $max_decimal_places ) . " $string";
}
}
sub numerate {
my ( $handle, $num, @forms ) = @_;
$handle->{'Locales.pm'}{'_main_'} ||= $handle->get_locales_obj();
return scalar( $handle->{'Locales.pm'}{'_main_'}->get_plural_form( $num, @forms ) );
}
sub numf {
my ( $handle, $num, $max_decimal_places ) = @_;
$handle->{'Locales.pm'}{'_main_'} ||= $handle->get_locales_obj();
return $handle->{'Locales.pm'}{'_main_'}->get_formatted_decimal( $num, $max_decimal_places );
}
sub join {
shift;
return CORE::join( shift, map { ref($_) eq 'ARRAY' ? @{$_} : $_ } @_ );
}
sub list_and {
my $lh = shift;
$lh->{'Locales.pm'}{'_main_'} ||= $lh->get_locales_obj();
return $lh->{'Locales.pm'}{'_main_'}->get_list_and( map { ref($_) eq 'ARRAY' ? @{$_} : $_ } @_ );
}
sub list_or {
my $lh = shift;
$lh->{'Locales.pm'}{'_main_'} ||= $lh->get_locales_obj();
return $lh->{'Locales.pm'}{'_main_'}->get_list_or( map { ref($_) eq 'ARRAY' ? @{$_} : $_ } @_ );
}
sub list_and_quoted {
my ( $lh, @args ) = @_;
$lh->{'Locales.pm'}{'_main_'} ||= $lh->get_locales_obj();
local $lh->{'Locales.pm'}{'_main_'}{'misc'}{'list_quote_mode'} = 'all';
return $lh->list_and(@args);
}
sub list_or_quoted {
my ( $lh, @args ) = @_;
$lh->{'Locales.pm'}{'_main_'} ||= $lh->get_locales_obj();
local $lh->{'Locales.pm'}{'_main_'}{'misc'}{'list_quote_mode'} = 'all';
return $lh->list_or(@args);
}
sub output_asis {
return $_[1];
}
sub asis {
return $_[0]->output( 'asis', $_[1] ); # this allows for embedded methods but still called via [asis,...] instead of [output,asis,...]
}
sub comment {
return '';
}
sub is_future {
my ( $lh, $dt, $future, $past, $current, $current_type ) = @_;
if ( $dt =~ tr{0-9}{}c ) {
$dt = __get_dt_obj_from_arg( $dt, 0 );
$dt = $dt->epoch();
}
if ($current) {
if ( !ref $dt ) {
$dt = __get_dt_obj_from_arg( $dt, 0 );
}
$current_type ||= 'hour';
if ( $current_type eq 'day' ) {
}
elsif ( $current_type eq 'minute' ) {
}
else {
}
}
return ref $dt ? $dt->epoch() : $dt > time() ? $future : $past;
}
sub __get_dt_obj_from_arg {
require DateTime;
return
!defined $_[0] || $_[0] eq '' ? DateTime->now()
: ref $_[0] eq 'HASH' ? DateTime->new( %{ $_[0] } )
: $_[0] =~ m{ \A (\d+ (?: [.] \d+ )? ) (?: [:] (.*) )? \z }xms ? DateTime->from_epoch( 'epoch' => $1, 'time_zone' => ( $2 || 'UTC' ) )
: !ref $_[0] ? DateTime->now( 'time_zone' => ( $_[0] || 'UTC' ) )
: $_[1] ? $_[0]->clone()
: $_[0];
}
sub current_year {
$_[0]->datetime( '', 'YYYY' );
}
sub datetime {
my ( $lh, $dta, $str ) = @_;
my $dt = __get_dt_obj_from_arg( $dta, 1 );
if ( !$INC{'DateTime/Locale.pm'} ) { # __get_dt_obj_from_arg is loading DateTime
eval q{ require DateTime::Locale; 1 } or die "Cannot load DateTime::Locale: $!";
}
$dt->{'locale'} = DateTime::Locale->load( $lh->language_tag() );
my $format = ref $str eq 'CODE' ? $str->($dt) : $str;
if ( defined $format ) {
if ( $dt->{'locale'}->can($format) ) {
$format = $dt->{'locale'}->$format();
}
}
$format = '' if !defined $format;
return $dt->format_cldr( $dt->{'locale'}->format_for($format) || $format || $dt->{'locale'}->date_format_long() );
}
sub output_amp { return $_[0]->output_chr(38) }
sub output_lt { return $_[0]->output_chr(60) } # TODO: ? make the rest of these embeddable like amp() ?
sub output_gt { return $_[0]->output_chr(62) }
sub output_apos { return $_[0]->output_chr(39) }
sub output_quot { return $_[0]->output_chr(34) }
sub output_shy { return $_[0]->output_chr(173) }
use constant output_nbsp => "\xC2\xA0";
my $space;
sub format_bytes {
my ( $lh, $bytes, $max_decimal_place ) = @_;
$bytes ||= 0;
if ( !defined $max_decimal_place ) {
$max_decimal_place = 2;
}
else {
$max_decimal_place = int( abs($max_decimal_place) );
}
my $absnum = abs($bytes);
$space ||= $lh->output_nbsp(); # avoid method call if we already have it
if ( $absnum < 1024 ) {
return ( $lh->{'_format_bytes_cache'}{ $bytes . '_' . $max_decimal_place } ||= $lh->maketext( '[quant,_1,%s byte,%s bytes]', [ $bytes, $max_decimal_place ] ) ); # the space between the '%s' and the 'b' is a non-break space (e.g. option-spacebar, not spacebar)
}
elsif ( $absnum < 1048576 ) {
return $lh->numf( ( $bytes / 1024 ), $max_decimal_place ) . $space . 'KB';
}
elsif ( $absnum < 1073741824 ) {
return $lh->numf( ( $bytes / 1048576 ), $max_decimal_place ) . $space . 'MB';
}
elsif ( $absnum < 1099511627776 ) {
return $lh->numf( ( $bytes / 1073741824 ), $max_decimal_place ) . $space . 'GB';
}
elsif ( $absnum < 1125899906842624 ) {
return $lh->numf( ( $bytes / 1099511627776 ), $max_decimal_place ) . $space . 'TB';
}
elsif ( $absnum < ( 1125899906842624 * 1024 ) ) {
return $lh->numf( ( $bytes / 1125899906842624 ), $max_decimal_place ) . $space . 'PB';
}
elsif ( $absnum < ( 1125899906842624 * 1024 * 1024 ) ) {
return $lh->numf( ( $bytes / ( 1125899906842624 * 1024 ) ), $max_decimal_place ) . $space . 'EB';
}
elsif ( $absnum < ( 1125899906842624 * 1024 * 1024 * 1024 ) ) {
return $lh->numf( ( $bytes / ( 1125899906842624 * 1024 * 1024 ) ), $max_decimal_place ) . $space . 'ZB';
}
else {
return $lh->numf( ( $bytes / ( 1125899906842624 * 1024 * 1024 * 1024 ) ), $max_decimal_place ) . $space . 'YB';
}
}
sub convert {
shift;
require Math::Units;
return Math::Units::convert(@_);
}
sub is_defined {
my ( $lh, $value, $is_defined, $not_defined, $is_defined_but_false ) = @_;
return __proc_string_with_embedded_under_vars($not_defined) if !defined $value;
if ( defined $is_defined_but_false && !$value ) {
return __proc_string_with_embedded_under_vars($is_defined_but_false);
}
else {
return __proc_string_with_embedded_under_vars($is_defined);
}
}
sub boolean {
my ( $lh, $boolean, $true, $false, $null ) = @_;
if ($boolean) {
return __proc_string_with_embedded_under_vars($true);
}
else {
if ( !defined $boolean && defined $null ) {
return __proc_string_with_embedded_under_vars($null);
}
return __proc_string_with_embedded_under_vars($false);
}
}
sub __proc_string_with_embedded_under_vars {
my $str = $_[0];
return $str if index( $str, '_' ) == -1 || $str !~ m/$FORCE_REGEX_LAZY\_(?:\-?[0-9]+)/o;
my @args = __caller_args( $_[1] ); # this way be dragons
$str =~ s/$FORCE_REGEX_LAZY\_(\-?[0-9]+)/$args[$1]/og;
return $str;
}
sub __caller_args {
package DB;
() = caller( $_[0] + 3 );
return @DB::args;
}
sub __proc_emb_meth {
my ( $lh, $str ) = @_;
$str =~ s/$FORCE_REGEX_LAZY(su[bp])\(((?:\\\)|[^\)])+?)\)/my $s=$2;my $m="output_$1";$s=~s{\\\)}{\)}g;$lh->$m($s)/oeg if index( $str, 'su' ) > -1;
$str =~ s/${FORCE_REGEX_LAZY}chr\(((?:\d+|[\S]))\)/$lh->output_chr($1)/oeg if index( $str, 'chr(' ) > -1;
$str =~ s/${FORCE_REGEX_LAZY}numf\((\d+(?:\.\d+)?)\)/$lh->numf($1)/oeg if index( $str, 'numf(' ) > -1;
substr( $str, index( $str, 'amp()' ), 5, $lh->output_amp() ) while index( $str, 'amp()' ) > -1;
return $str;
}
sub output {
my ( $lh, $output_function, $string, @output_function_args ) = @_;
if ( defined $string && $string ne '' && index( $string, '(' ) > -1 ) {
$string = __proc_emb_meth( $lh, $string );
}
if ( $output_function eq 'url' && defined $output_function_args[0] && $output_function_args[0] ne '' && index( $output_function_args[0], '(' ) > -1 ) {
$output_function_args[0] = __proc_emb_meth( $lh, $output_function_args[0] );
}
if ( my $cr = ( $lh->{'_output_function_cache'}{$output_function} ||= $lh->can( 'output_' . $output_function ) ) ) {
return $cr->( $lh, $string, @output_function_args );
}
else {
my $cur_errno = $!;
if ( eval { require Sub::Todo } ) {
$! = Sub::Todo::get_errno_func_not_impl();
}
else {
$! = $cur_errno;
}
return $string;
}
}
sub output_encode_puny {
my ( $self, $s ) = @_;
require Cpanel::Encoder::Punycode;
return Cpanel::Encoder::Punycode::punycode_encode_str($s);
}
sub output_decode_puny {
my ( $self, $s ) = @_;
require Cpanel::Encoder::Punycode;
return Cpanel::Encoder::Punycode::punycode_decode_str($s);
}
my $has_encode; # checking for Encode this way facilitates only checking @INC once for the module on systems that do not have Encode
sub output_chr {
my ( $lh, $chr_num ) = @_;
if ( $chr_num !~ m/$FORCE_REGEX_LAZY\A\d+\z/o ) {
return if length($chr_num) != 1;
return $chr_num if !$lh->context_is_html();
return
$chr_num eq '"' ? '"'
: $chr_num eq '&' ? '&'
: $chr_num eq "'" ? '''
: $chr_num eq '<' ? '<'
: $chr_num eq '>' ? '>'
: $chr_num;
}
return if $chr_num !~ m/$FORCE_REGEX_LAZY\A\d+\z/o;
my $chr = chr($chr_num);
if ( $chr_num > 127 ) {
if ( !defined $has_encode ) {
$has_encode = 0;
eval { require Encode; $has_encode = 1; };
}
if ($has_encode) {
$chr = Encode::encode( $lh->encoding(), $chr );
}
else {
$chr = eval '"\x{' . sprintf( '%04X', $chr_num ) . '}"';
}
}
if ( !$lh->context_is_html() ) {
return $chr;
}
else {
return
$chr_num == 34 || $chr_num == 147 || $chr_num == 148 ? '"'
: $chr_num == 38 ? '&'
: $chr_num == 39 || $chr_num == 145 || $chr_num == 146 ? '''
: $chr_num == 60 ? '<'
: $chr_num == 62 ? '>'
: $chr_num == 173 ? '­'
: $chr;
}
}
sub output_class {
my ( $lh, $string, @classes ) = @_;
$string = __proc_string_with_embedded_under_vars( $string, 1 );
return $string if $lh->context_is_plain();
return $lh->context_is_ansi() ? "\e[1m$string\e[0m" : qq{<span class="@classes">$string</span>};
}
sub output_asis_for_tests {
my ( $lh, $string ) = @_;
$string = __proc_string_with_embedded_under_vars( $string, 1 );
return $string;
}
sub __make_attr_str_from_ar {
my ( $attr_ar, $strip_hr, $addin ) = @_;
if ( ref($attr_ar) eq 'HASH' ) {
$strip_hr = $attr_ar;
$attr_ar = [];
}
my $attr = '';
my $general_hr = ref( $attr_ar->[-1] ) eq 'HASH' ? pop( @{$attr_ar} ) : undef;
my $idx = 0;
my $ar_len = @{$attr_ar};
$idx = 1 if $ar_len % 2; # handle “Odd number of elements” …
my $did_addin;
while ( $idx < $ar_len ) {
if ( exists $strip_hr->{ $attr_ar->[$idx] } ) {
$idx += 2;
next;
}
my $atr = $attr_ar->[$idx];
my $val = $attr_ar->[ ++$idx ];
if ( exists $addin->{$atr} ) {
$val = "$addin->{$atr} $val";
$did_addin->{$atr}++;
}
$attr .= qq{ $atr="$val"};
$idx++;
}
if ($general_hr) {
for my $k ( keys %{$general_hr} ) {
next if exists $strip_hr->{$k};
if ( exists $addin->{$k} ) {
$general_hr->{$k} = "$addin->{$k} $general_hr->{$k}";
$did_addin->{$k}++;
}
$attr .= qq{ $k="$general_hr->{$k}"};
}
}
for my $r ( keys %{$addin} ) {
if ( !exists $did_addin->{$r} ) {
$attr .= qq{ $r="$addin->{$r}"};
}
}
return $attr;
}
sub output_inline {
my ( $lh, $string, @attrs ) = @_;
$string = __proc_string_with_embedded_under_vars( $string, 1 );
return $string if !$lh->context_is_html();
my $attr = __make_attr_str_from_ar( \@attrs );
return qq{<span$attr>$string</span>};
}
*output_attr = \&output_inline;
sub output_block {
my ( $lh, $string, @attrs ) = @_;
$string = __proc_string_with_embedded_under_vars( $string, 1 );
return $string if !$lh->context_is_html();
my $attr = __make_attr_str_from_ar( \@attrs );
return qq{<div$attr>$string</div>};
}
sub output_img {
my ( $lh, $src, $alt, @attrs ) = @_;
if ( !defined $alt || $alt eq '' ) {
$alt = $src;
}
else {
$alt = __proc_string_with_embedded_under_vars( $alt, 1 );
}
return $alt if !$lh->context_is_html();
my $attr = __make_attr_str_from_ar( \@attrs, { 'alt' => 1, 'src' => 1 } );
return qq{<img src="$src" alt="$alt"$attr/>};
}
sub output_abbr {
my ( $lh, $abbr, $full, @attrs ) = @_;
return !$lh->context_is_html()
? "$abbr ($full)"
: qq{<abbr title="$full"} . __make_attr_str_from_ar( \@attrs, { 'title' => 1 } ) . qq{>$abbr</abbr>};
}
sub output_acronym {
my ( $lh, $acronym, $full, @attrs ) = @_;
return !$lh->context_is_html()
? "$acronym ($full)"
: qq{<abbr title="$full"} . __make_attr_str_from_ar( \@attrs, { 'title' => 1 }, { 'class' => 'initialism' } ) . qq{>$acronym</abbr>};
}
sub output_sup {
my ( $lh, $string, @attrs ) = @_;
$string = __proc_string_with_embedded_under_vars( $string, 1 );
return !$lh->context_is_html() ? $string : qq{<sup} . __make_attr_str_from_ar( \@attrs ) . qq{>$string</sup>};
}
sub output_sub {
my ( $lh, $string, @attrs ) = @_;
$string = __proc_string_with_embedded_under_vars( $string, 1 );
return !$lh->context_is_html() ? $string : qq{<sub} . __make_attr_str_from_ar( \@attrs ) . qq{>$string</sub>};
}
sub output_underline {
my ( $lh, $string, @attrs ) = @_;
$string = __proc_string_with_embedded_under_vars( $string, 1 );
return $string if $lh->context_is_plain();
return $lh->context_is_ansi() ? "\e[4m$string\e[0m" : qq{<span style="text-decoration: underline"} . __make_attr_str_from_ar( \@attrs ) . qq{>$string</span>};
}
sub output_strong {
my ( $lh, $string, @attrs ) = @_;
$string = __proc_string_with_embedded_under_vars( $string, 1 );
return $string if $lh->context_is_plain();
return $lh->context_is_ansi() ? "\e[1m$string\e[0m" : '<strong' . __make_attr_str_from_ar( \@attrs ) . ">$string</strong>";
}
sub output_em {
my ( $lh, $string, @attrs ) = @_;
$string = __proc_string_with_embedded_under_vars( $string, 1 );
return $string if $lh->context_is_plain();
return $lh->context_is_ansi() ? "\e[3m$string\e[0m" : '<em' . __make_attr_str_from_ar( \@attrs ) . ">$string</em>";
}
sub output_url {
my ( $lh, $url, @args ) = @_;
$url ||= ''; # carp() ?
my $arb_args_hr = ref $args[-1] eq 'HASH' ? pop(@args) : {};
my ( $url_text, %output_config ) = @args % 2 ? @args : ( undef, @args );
my $return = $url;
if ( !$lh->context_is_html() ) {
if ($url_text) {
return "$url_text ($url)";
}
if ( exists $output_config{'plain'} ) {
$output_config{'plain'} ||= $url;
my $orig = $output_config{'plain'};
$output_config{'plain'} = __proc_string_with_embedded_under_vars( $output_config{'plain'}, 1 );
$return = $orig ne $output_config{'plain'} && $output_config{'plain'} =~ m/\Q$url\E/ ? $output_config{'plain'} : "$output_config{'plain'} $url";
}
}
else {
if ( exists $output_config{'html'} ) {
$output_config{'html'} = __proc_string_with_embedded_under_vars( $output_config{'html'}, 1 );
}
$output_config{'html'} ||= $url_text || $url;
my $attr = __make_attr_str_from_ar(
[ @args, $arb_args_hr ],
{
'html' => 1,
'plain' => 1,
'_type' => 1,
}
);
$return = exists $output_config{'_type'}
&& $output_config{'_type'} eq 'offsite' ? qq{<a$attr target="_blank" class="offsite" href="$url">$output_config{'html'}</a>} : qq{<a$attr href="$url">$output_config{'html'}</a>};
}
return $return;
}
sub set_context_html {
my ($lh) = @_;
my $cur = $lh->get_context();
$lh->set_context('html');
return if !$lh->context_is_html();
return $cur;
}
sub set_context_ansi {
my ($lh) = @_;
my $cur = $lh->get_context();
$lh->set_context('ansi');
return if !$lh->context_is_ansi();
return $cur;
}
sub set_context_plain {
my ($lh) = @_;
my $cur = $lh->get_context();
$lh->set_context('plain');
return if !$lh->context_is_plain();
return $cur;
}
my %contexts = (
'plain' => undef(),
'ansi' => 1,
'html' => 0,
);
sub set_context {
my ( $lh, $context ) = @_;
if ( !$context ) {
$lh->{'-t-STDIN'} = -t *STDIN ? 1 : 0;
}
elsif ( exists $contexts{$context} ) {
$lh->{'-t-STDIN'} = $contexts{$context};
}
else {
require Carp;
local $Carp::CarpLevel = 1;
Carp::carp("Given context '$context' is unknown.");
$lh->{'-t-STDIN'} = $context;
}
}
sub context_is_html {
return $_[0]->get_context() eq 'html';
}
sub context_is_ansi {
return $_[0]->get_context() eq 'ansi';
}
sub context_is_plain {
return $_[0]->get_context() eq 'plain';
}
sub context_is {
return $_[0]->get_context() eq $_[1];
}
sub get_context {
$_[0]->set_context() if !exists $_[0]->{'-t-STDIN'};
return
!defined $_[0]->{'-t-STDIN'} ? 'plain'
: $_[0]->{'-t-STDIN'} ? 'ansi'
: 'html';
}
sub maketext_html_context {
my ( $lh, @mt_args ) = @_;
my $cur = $lh->set_context_html();
my $res = $lh->maketext(@mt_args);
$lh->set_context($cur);
return $res;
}
sub maketext_ansi_context {
my ( $lh, @mt_args ) = @_;
my $cur = $lh->set_context_ansi();
my $res = $lh->maketext(@mt_args);
$lh->set_context($cur);
return $res;
}
sub maketext_plain_context {
my ( $lh, @mt_args ) = @_;
my $cur = $lh->set_context_plain();
my $res = $lh->maketext(@mt_args);
$lh->set_context($cur);
return $res;
}
1;
} # --- END Cpanel/CPAN/Locale/Maketext/Utils.pm
{ # --- BEGIN Cpanel/Locale/Utils/Paths.pm
package Cpanel::Locale::Utils::Paths;
use strict;
use warnings;
use constant {
get_legacy_lang_cache_root => '/var/cpanel/lang.cache',
get_i_locales_config_path => '/var/cpanel/i_locales',
get_custom_whitelist_path => '/var/cpanel/maketext_whitelist'
};
sub get_locale_database_root { return '/var/cpanel/locale' }
sub get_locale_yaml_root { return '/usr/local/cpanel/locale' }
sub get_legacy_lang_root { return '/usr/local/cpanel/lang' }
sub get_locale_yaml_local_root { return '/var/cpanel/locale.local' }
1;
} # --- END Cpanel/Locale/Utils/Paths.pm
{ # --- BEGIN Cpanel/Locale/Utils.pm
package Cpanel::Locale::Utils;
use strict;
use warnings;
BEGIN {
eval { require CDB_File; };
}
# use Cpanel::Locale::Utils::Paths ();
$Cpanel::Locale::Utils::i_am_the_compiler = 0;
my $logger;
sub _logger {
require Cpanel::Logger;
$logger ||= Cpanel::Logger->new();
}
sub get_readonly_tie {
my ( $cdb_file, $cdb_hr ) = @_;
if ( !$cdb_file ) {
_logger()->warn('Undefined CDB file specified for readonly operation');
return;
}
elsif ( !$INC{'CDB_File.pm'} || !exists $CDB_File::{'TIEHASH'} ) {
_logger()->warn("Failed to load CDB_File.pm") if $^X ne '/usr/bin/perl';
return;
}
my $tie_obj = tie %{$cdb_hr}, 'CDB_File', $cdb_file;
if ( !$tie_obj && !-e $cdb_file ) {
_logger()->warn("Missing CDB file $cdb_file specified for readonly operation");
return;
}
eval { exists $cdb_hr->{'__VERSION'} };
if ($@) {
$tie_obj = undef;
untie %$cdb_hr;
}
if ( !$tie_obj ) {
_logger()->warn("CDB_File could not get read-only association to '$cdb_file': $!");
}
return $tie_obj;
}
sub create_cdb {
my ( $cdb_file, $cdb_hr ) = @_;
if ( !$cdb_file ) {
_logger()->warn('Undefined CDB file specified for writable operation');
return;
}
return CDB_File::create( %{$cdb_hr}, $cdb_file, "$cdb_file.$$" );
}
sub get_writable_tie {
Carp::confess("cdb files are not writable");
}
sub init_lexicon {
my ( $langtag, $hr, $version_sr, $encoding_sr ) = @_;
my $cdb_file;
my $db_root = Cpanel::Locale::Utils::Paths::get_locale_database_root();
for my $file ( $Cpanel::CPDATA{'RS'} ? ("themes/$Cpanel::CPDATA{RS}/$langtag.cdb") : (), "$langtag.cdb" ) { # PPI NO PARSE - Only include Cpanel() when some other module uses it
if ( -e "$db_root/$file" ) {
$cdb_file = "$db_root/$file";
last;
}
}
if ( !$cdb_file ) {
if ( -e Cpanel::Locale::Utils::Paths::get_locale_yaml_root() . "/$langtag.yaml" && !$Cpanel::Locale::Utils::i_am_the_compiler ) {
_logger()->info(qq{Locale needs to be compiled by root (/usr/local/cpanel/bin/build_locale_databases --locale=$langtag)});
}
return;
}
my $cdb_tie = get_readonly_tie( $cdb_file, $hr );
if ( exists $hr->{'__VERSION'} && ref $version_sr ) {
${$version_sr} = $hr->{'__VERSION'};
}
if ( ref $encoding_sr ) {
${$encoding_sr} ||= 'utf-8';
}
return $cdb_file;
}
sub init_package {
my ($caller) = caller();
my ($langtag) = reverse( split( /::/, $caller ) );
no strict 'refs';
no warnings 'once';
${ $caller . '::CDB_File_Path' } ||= init_lexicon( "$langtag", \%{ $caller . '::Lexicon' }, \${ $caller . '::VERSION' }, \${ $caller . '::Encoding' }, );
return;
}
1;
} # --- END Cpanel/Locale/Utils.pm
{ # --- BEGIN Cpanel/DB/Utils.pm
package Cpanel::DB::Utils;
use strict;
sub username_to_dbowner {
my ($username) = @_;
$username =~ tr<_.><>d if defined $username;
return $username;
}
1;
} # --- END Cpanel/DB/Utils.pm
{ # --- BEGIN Cpanel/AdminBin/Serializer.pm
package Cpanel::AdminBin::Serializer;
use strict;
use warnings;
# use Cpanel::JSON ();
our $VERSION = '2.4';
our $MAX_LOAD_LENGTH;
our $MAX_PRIV_LOAD_LENGTH;
BEGIN {
*MAX_LOAD_LENGTH = \$Cpanel::JSON::MAX_LOAD_LENGTH;
*MAX_PRIV_LOAD_LENGTH = \$Cpanel::JSON::MAX_PRIV_LOAD_LENGTH;
*DumpFile = *Cpanel::JSON::DumpFile;
}
BEGIN {
*Dump = *Cpanel::JSON::Dump;
*SafeDump = *Cpanel::JSON::SafeDump;
*LoadFile = *Cpanel::JSON::LoadFileNoSetUTF8;
*Load = *Cpanel::JSON::Load;
*looks_like_serialized_data = *Cpanel::JSON::looks_like_json;
}
sub SafeLoadFile {
return Cpanel::JSON::_LoadFile( $_[0], $Cpanel::JSON::MAX_LOAD_LENGTH, $Cpanel::JSON::DECODE_UTF8, $_[1], $Cpanel::JSON::LOAD_STRICT );
}
sub SafeLoad {
utf8::decode( $_[0] );
return Cpanel::JSON::LoadNoSetUTF8(@_);
}
sub clone {
return Cpanel::JSON::LoadNoSetUTF8( Cpanel::JSON::Dump( $_[0] ) );
}
1;
} # --- END Cpanel/AdminBin/Serializer.pm
{ # --- BEGIN Cpanel/AdminBin/Serializer/FailOK.pm
package Cpanel::AdminBin::Serializer::FailOK;
use strict;
use warnings;
sub LoadModule {
local $@;
return 1 if $INC{'Cpanel/AdminBin/Serializer.pm'};
my $load_ok = eval {
local $SIG{'__DIE__'}; # Suppress spewage as we may be reading an invalid cache
local $SIG{'__WARN__'}; # and since failure is ok to throw it away
require Cpanel::AdminBin::Serializer;
1;
};
if ( !$load_ok && !$ENV{'CPANEL_BASE_INSTALL'} && index( $^X, '/usr/local/cpanel' ) == 0 ) {
warn $@;
}
return $load_ok ? 1 : 0;
}
sub LoadFile {
my ( $file_or_fh, $path ) = @_;
return undef if !$INC{'Cpanel/AdminBin/Serializer.pm'};
return eval {
local $SIG{'__DIE__'}; # Suppress spewage as we may be reading an invalid cache
local $SIG{'__WARN__'}; # and since failure is ok to throw it away
Cpanel::AdminBin::Serializer::LoadFile( $file_or_fh, undef, $path );
};
}
1;
} # --- END Cpanel/AdminBin/Serializer/FailOK.pm
{ # --- BEGIN Cpanel/Config/Constants.pm
package Cpanel::Config::Constants;
our $DEFAULT_CPANEL_THEME = 'paper_lantern';
our $DEFAULT_CPANEL_MAILONLY_THEME = 'paper_lantern';
our $DEFAULT_WEBMAIL_THEME = 'paper_lantern';
our $DEFAULT_WEBMAIL_MAILONLY_THEME = 'paper_lantern';
our @DORMANT_SERVICES_LIST = qw(cpanalyticsd cpdavd cphulkd cpsrvd dnsadmin spamd);
1;
} # --- END Cpanel/Config/Constants.pm
{ # --- BEGIN Cpanel/Hash/Stringify.pm
package Cpanel::Hash::Stringify;
use strict;
use warnings;
sub sorted_hashref_string {
my ($hashref) = @_;
return (
( scalar keys %$hashref )
? join(
'_____', map { $_, ( ref $hashref->{$_} eq 'HASH' ? sorted_hashref_string( $hashref->{$_} ) : ref $hashref->{$_} eq 'ARRAY' ? join( '_____', @{ $hashref->{$_} } ) : defined $hashref->{$_} ? $hashref->{$_} : '' ) }
sort keys %$hashref
)
: ''
); #sort is important for order;
}
1;
} # --- END Cpanel/Hash/Stringify.pm
{ # --- BEGIN Cpanel/Umask.pm
package Cpanel::Umask;
use strict;
# use Cpanel::Finally();
our @ISA;
BEGIN { push @ISA, qw(Cpanel::Finally); }
sub new {
my ( $class, $new ) = @_;
my $old = umask();
umask($new);
return $class->SUPER::new(
sub {
my $cur = umask();
if ( $cur != $new ) {
my ( $cur_o, $old_o, $new_o ) = map { '0' . sprintf( '%o', $_ ) } ( $cur, $old, $new );
warn "I want to umask($old_o). I expected the current umask to be $new_o, but it’s actually $cur_o.";
}
umask($old);
}
);
}
1;
} # --- END Cpanel/Umask.pm
{ # --- BEGIN Cpanel/Config/LoadConfig.pm
package Cpanel::Config::LoadConfig;
use strict;
use warnings;
# use Cpanel::Hash::Stringify ();
# use Cpanel::Debug ();
# use Cpanel::FileUtils::Write::JSON::Lazy ();
# use Cpanel::AdminBin::Serializer::FailOK ();
# use Cpanel::LoadFile::ReadFast ();
# use Cpanel::HiRes ();
use constant _ENOENT => 2;
my $logger;
our $PRODUCT_CONF_DIR = '/var/cpanel';
our $_DEBUG_SAFEFILE = 0;
my %COMMON_CACHE_NAMES = (
':__^\s*[#;]____0__' => 'default_colon',
':\s+__^\s*[#;]____0__' => 'default_colon_any_space',
': __^\s*[#;]____0__' => 'default_colon_with_one_space',
'=__^\s*[#;]____0__skip_readable_check_____1' => 'default_skip_readable',
'=__^\s*[#;]____0__' => 'default',
'=__^\s*[#;]__(?^:\s+)__0__' => 'default_with_preproc_newline',
'=__^\s*[#;]____1__' => 'default_allow_undef',
'\s*[:]\s*__^\s*[#;]____0__' => 'default_colon_before_after_space',
'\s*=\s*__^\s*[#;]____1__' => 'default_equal_before_after_space_allow_undef',
'\s*[\=]\s*__^\s*[#]____0__use_reverse_____0' => 'default_equal_before_after_space',
': __^\s*[#;]____0__limit_____10000000000_____use_reverse_____0' => 'default_with_10000000000_limit',
'\s*[:]\s*__^\s*[#;]____0__use_hash_of_arr_refs_____0_____use_reverse_____0' => 'default_use_hash_of_arr_refs',
': __^\s*[#;]____0__limit__________use_reverse_____0' => 'default_colon_single_space_no_limit',
': __^\s*[#;]____1__skip_keys_____nobody_____use_hash_of_arr_refs_____0_____use_reverse_____0' => 'default_colon_skip_nobody_no_limit',
': __^\s*[#;]____1__use_reverse_____1' => 'default_reverse_allow_undef',
'\s+__^\s*[#;]____0__' => 'default_space_seperated_config',
'\s*=\s*__^\s*[#;]__^\s*__0__' => 'default_equal_space_seperated_config', #ea4.conf
);
my $DEFAULT_DELIMITER = '=';
my $DEFAULT_COMMENT_REGEXP = '^\s*[#;]'; #Keep in sync with tr{} below!!
my @BOOLEAN_OPTIONS = qw(
allow_undef_values
use_hash_of_arr_refs
use_reverse
);
my $CACHE_DIR_PERMS = 0700;
sub _process_parse_args {
my (%opts) = @_;
if ( !defined $opts{'delimiter'} ) {
$opts{'delimiter'} = $DEFAULT_DELIMITER;
}
$opts{'regexp_to_preprune'} ||= q{};
$opts{'comment'} ||= $DEFAULT_COMMENT_REGEXP;
$opts{'comment'} = '' if $opts{'comment'} eq '0E0';
$opts{$_} ||= 0 for @BOOLEAN_OPTIONS;
return %opts;
}
{
no warnings 'once';
*get_homedir_and_cache_dir = *_get_homedir_and_cache_dir;
}
sub _get_homedir_and_cache_dir {
my ( $homedir, $cache_dir );
if ( $> == 0 ) {
$cache_dir = "$PRODUCT_CONF_DIR/configs.cache";
}
else {
{
no warnings 'once';
$homedir = $Cpanel::homedir;
}
if ( !$homedir ) {
eval 'local $SIG{__DIE__}; local $SIG{__WARN__}; require Cpanel::PwCache'; ## no critic qw(ProhibitStringyEval) # PPI USE OK - just after
$homedir = Cpanel::PwCache::gethomedir() if $INC{'Cpanel/PwCache.pm'};
return unless $homedir; # undef for homedir and cache_dir avoid issues later when using undef as hash key
}
$homedir = scalar each %{ { $homedir => undef } }; #untaint
$homedir =~ tr{/}{}s;
return ( $homedir, undef ) if $homedir eq '/';
$cache_dir = $homedir . '/.cpanel/caches/config';
}
return ( $homedir, $cache_dir );
}
sub loadConfig { ## no critic qw(Subroutines::ProhibitExcessComplexity Subroutines::ProhibitManyArgs)
my ( $file, $conf_ref, $delimiter, $comment, $regexp_to_preprune, $allow_undef_values, $arg_ref ) = @_;
$conf_ref ||= -1;
my %processed_positional_args = _process_parse_args(
delimiter => $delimiter,
comment => $comment,
regexp_to_preprune => $regexp_to_preprune,
allow_undef_values => $allow_undef_values,
$arg_ref ? %$arg_ref : (),
);
my $empty_is_invalid = ( defined $arg_ref ) ? delete $arg_ref->{'empty_is_invalid'} : undef;
my ( $use_reverse, $use_hash_of_arr_refs );
( $delimiter, $comment, $regexp_to_preprune, $allow_undef_values, $use_reverse, $use_hash_of_arr_refs ) = @processed_positional_args{
qw(
delimiter
comment
regexp_to_preprune
allow_undef_values
use_reverse
use_hash_of_arr_refs
)
};
if ( !$file || $file =~ tr/\0// ) {
_do_logger( 'warn', 'loadConfig requires valid filename' );
if ( $arg_ref->{'keep_locked_open'} ) {
return ( undef, undef, undef, "loadConfig requires valid filename" );
}
return;
}
my $filesys_mtime = ( Cpanel::HiRes::stat($file) )[9] or do {
if ( $arg_ref->{'keep_locked_open'} ) {
return ( undef, undef, undef, "Unable to stat $file: $!" );
}
return;
};
my $load_into_conf_ref = ( !ref $conf_ref && $conf_ref == -1 ) ? 0 : 1;
if ($load_into_conf_ref) {
$conf_ref = _hashify_ref($conf_ref);
}
my ( $homedir, $cache_dir ) = _get_homedir_and_cache_dir();
my $cache_file;
Cpanel::AdminBin::Serializer::FailOK::LoadModule() if !$INC{'Cpanel/AdminBin/Serializer.pm'};
if ( $cache_dir && $INC{'Cpanel/JSON.pm'} && ( !defined $arg_ref || !ref $arg_ref || !exists $arg_ref->{'nocache'} && !$arg_ref->{'keep_locked_open'} ) ) {
$cache_file = get_cache_file(
'file' => $file,
'cache_dir' => $cache_dir,
'delimiter' => $delimiter,
'comment' => $comment,
'regexp_to_preprune' => $regexp_to_preprune,
'allow_undef_values' => $allow_undef_values,
'arg_ref' => $arg_ref,
);
my ( $cache_valid, $ref ) = load_from_cache_if_valid(
'file' => $file,
'cache_file' => $cache_file,
'filesys_mtime' => $filesys_mtime,
'conf_ref' => $conf_ref,
'load_into_conf_ref' => $load_into_conf_ref,
'empty_is_invalid' => $empty_is_invalid,
);
if ($cache_valid) {
return $ref;
}
}
$conf_ref = {} if !$load_into_conf_ref;
my $conf_fh;
my $conflock;
my $locked;
if ( $arg_ref->{'keep_locked_open'} || $arg_ref->{'rw'} ) {
require Cpanel::SafeFile;
$locked = 1;
$conflock = Cpanel::SafeFile::safeopen( $conf_fh, '+<', $file );
}
else {
$conflock = open( $conf_fh, '<', $file );
}
if ( !$conflock ) {
my $open_err = $! || '(unspecified error)';
local $_DEBUG_SAFEFILE = 1;
require Cpanel::Logger;
my $is_root = ( $> == 0 ? 1 : 0 );
if ( !$is_root && !$arg_ref->{'skip_readable_check'} ) {
if ( !-r $file ) {
my $err = $!;
_do_logger( 'warn', "Unable to read $file: $err" );
if ( $arg_ref->{'keep_locked_open'} ) {
return ( undef, undef, undef, "Unable to read $file: $err" );
}
return;
}
}
my $verb = ( $locked ? 'lock/' : q<> ) . 'open';
my $msg = "Unable to $verb $file as UIDs $</$>: $open_err";
Cpanel::Logger::cplog( $msg, 'warn', __PACKAGE__ );
if ( $arg_ref->{'keep_locked_open'} ) {
return ( undef, undef, undef, $msg );
}
return;
}
my ( $parse_ok, $parsed ) = _parse_from_filehandle(
$conf_fh,
comment => $comment,
delimiter => $delimiter,
regexp_to_preprune => $regexp_to_preprune,
allow_undef_values => $allow_undef_values,
use_reverse => $use_reverse,
use_hash_of_arr_refs => $use_hash_of_arr_refs,
$arg_ref ? %$arg_ref : (),
);
if ( $locked && !$arg_ref->{'keep_locked_open'} ) {
require Cpanel::SafeFile;
Cpanel::SafeFile::safeclose( $conf_fh, $conflock );
}
if ( !$parse_ok ) {
require Cpanel::Logger;
Cpanel::Logger::cplog( "Unable to parse $file: $parsed", 'warn', __PACKAGE__ );
if ( $arg_ref->{'keep_locked_open'} ) {
return ( undef, undef, undef, "Unable to parse $file: $parsed" );
}
return;
}
@{$conf_ref}{ keys %$parsed } = values %$parsed;
if ($cache_file) {
write_cache(
'cache_dir' => $cache_dir,
'cache_file' => $cache_file,
'homedir' => $homedir,
'is_root' => ( $> == 0 ? 1 : 0 ),
'data' => $parsed,
);
}
if ( $arg_ref->{'keep_locked_open'} ) {
return $conf_ref, $conf_fh, $conflock, "open success";
}
return $conf_ref;
}
sub load_from_cache_if_valid {
my (%opts) = @_;
my $cache_file = $opts{'cache_file'} or die "need cache_file!";
my $file = $opts{'file'};
my $conf_ref = $opts{'conf_ref'};
my $load_into_conf_ref = $opts{'load_into_conf_ref'};
my $filesys_mtime = $opts{'filesys_mtime'} || ( Cpanel::HiRes::stat($file) )[9];
open( my $cache_fh, '<:stdio', $cache_file ) or do {
my $err = $!;
my $msg = "non-fatal error: open($cache_file): $err";
warn $msg if $! != _ENOENT();
return ( 0, $msg );
};
my ( $cache_filesys_mtime, $now, $cache_conf_ref ) = ( ( Cpanel::HiRes::fstat($cache_fh) )[9], Cpanel::HiRes::time() ); # stat the file after we have it open to avoid a race condition
if ( ( $Cpanel::Debug::level || 0 ) >= 5 ) {
print STDERR __PACKAGE__ . "::loadConfig file:$file, cache_file:$cache_file, cache_filesys_mtime:$cache_filesys_mtime, filesys_mtime:$filesys_mtime, now:$now\n";
}
if ( $filesys_mtime && _greater_with_same_precision( $cache_filesys_mtime, $filesys_mtime ) && _greater_with_same_precision( $now, $cache_filesys_mtime ) ) {
if ( ( $Cpanel::Debug::level || 0 ) >= 5 ) {
print STDERR __PACKAGE__ . "::loadConfig using cache_file:$cache_file\n";
}
Cpanel::AdminBin::Serializer::FailOK::LoadModule() if !$INC{'Cpanel/AdminBin/Serializer.pm'};
if ( $cache_conf_ref = Cpanel::AdminBin::Serializer::FailOK::LoadFile($cache_fh) ) { #zero keys is a valid file still it may just be all comments or empty
close($cache_fh);
if ( $opts{'empty_is_invalid'} && scalar keys %$cache_conf_ref == 0 ) {
return ( 0, 'Cache is empty' );
}
my $ref_to_return;
if ($load_into_conf_ref) {
@{$conf_ref}{ keys %$cache_conf_ref } = values %$cache_conf_ref;
$ref_to_return = $conf_ref;
}
else {
$ref_to_return = $cache_conf_ref;
}
return ( 1, $ref_to_return );
}
elsif ( ( $Cpanel::Debug::level || 0 ) >= 5 ) {
print STDERR __PACKAGE__ . "::loadConfig failed to load cache_file:$cache_file\n";
}
}
else {
if ( ( $Cpanel::Debug::level || 0 ) >= 5 ) {
print STDERR __PACKAGE__ . "::loadConfig NOT using cache_file:$cache_file\n";
}
}
return ( 0, 'Cache not valid' );
}
sub _greater_with_same_precision {
my ( $float1, $float2 ) = @_;
my ( $int1, $int2 ) = ( int($float1), int($float2) );
if ( $float1 == $int1 or $float2 == $int2 ) {
return $int1 > $int2;
}
return $float1 > $float2;
}
sub get_cache_file { ## no critic qw(Subroutines::RequireArgUnpacking) - Args unpacked by _process_parse_args
my %opts = _process_parse_args(@_);
die 'need cache_dir!' if !$opts{'cache_dir'};
my $stringified_args = join(
'__',
@opts{qw(delimiter comment regexp_to_preprune allow_undef_values)}, ( scalar keys %{ $opts{'arg_ref'} } ? Cpanel::Hash::Stringify::sorted_hashref_string( $opts{'arg_ref'} ) : '' )
);
if ( ( $Cpanel::Debug::level || 0 ) >= 5 ) { # PPI NO PARSE - ok missing
print STDERR __PACKAGE__ . "::loadConfig stringified_args[$stringified_args]\n";
}
my $safe_filename = $opts{'file'};
$safe_filename =~ tr{/}{_};
return $opts{'cache_dir'} . '/' . $safe_filename . '___' . ( $COMMON_CACHE_NAMES{$stringified_args} || _get_fastest_hash($stringified_args) );
}
sub _get_fastest_hash {
require Cpanel::Hash;
goto \&Cpanel::Hash::get_fastest_hash;
}
sub write_cache {
my (%opts) = @_;
my $cache_file = $opts{'cache_file'};
my $cache_dir = $opts{'cache_dir'};
my $homedir = $opts{'homedir'};
my $is_root = $opts{'is_root'};
my $parsed = $opts{'data'};
my @dirs = ($cache_dir);
if ( !$is_root ) {
unshift @dirs, "$homedir/.cpanel", "$homedir/.cpanel/caches";
}
foreach my $dir (@dirs) {
$dir = each %{ { ( $dir => undef ) } }; #detaint
chmod( $CACHE_DIR_PERMS, $dir ) or do {
if ( $! == _ENOENT() ) {
require Cpanel::Umask;
my $umask = Cpanel::Umask->new(0);
mkdir( $dir, $CACHE_DIR_PERMS ) or do {
_do_logger( 'warn', "Failed to create dir “$dir”: $!" );
};
}
else {
_do_logger( 'warn', "chmod($dir): $!" );
}
};
}
my $wrote_ok = eval { Cpanel::FileUtils::Write::JSON::Lazy::write_file( $cache_file, $parsed, 0600 ) };
my $error = $@;
$error ||= "Unknown error" if !defined $wrote_ok;
if ($error) {
_do_logger( 'warn', "Could not create cache file “$cache_file”: $error" );
unlink $cache_file; #outdated
}
if ( ( $Cpanel::Debug::level || 0 ) > 4 ) { # PPI NO PARSE - ok missing
print STDERR __PACKAGE__ . "::loadConfig [lazy write cache file] [$cache_file] wrote_ok:[$wrote_ok]\n";
}
return 1;
}
sub _do_logger {
my ( $action, $msg ) = @_;
require Cpanel::Logger;
$logger ||= Cpanel::Logger->new();
return $logger->$action($msg);
}
sub parse_from_filehandle {
my ( $conf_fh, %opts ) = @_;
return _parse_from_filehandle( $conf_fh, _process_parse_args(%opts) );
}
sub _parse_from_filehandle {
my ( $conf_fh, %opts ) = @_;
my ( $comment, $limit, $regexp_to_preprune, $delimiter, $allow_undef_values, $use_hash_of_arr_refs, $skip_keys, $use_reverse ) = @opts{
qw(
comment
limit
regexp_to_preprune
delimiter
allow_undef_values
use_hash_of_arr_refs
skip_keys
use_reverse
)
};
my $conf_ref = {};
my $parser_code;
my ( $k, $v ); ## no critic qw(Variables::ProhibitUnusedVariables)
my $keys = 0;
my $key_value_text = $use_reverse ? '1,0' : '0,1';
my $cfg_txt = '';
Cpanel::LoadFile::ReadFast::read_all_fast( $conf_fh, $cfg_txt );
my $has_cr = index( $cfg_txt, "\r" ) > -1 ? 1 : 0;
_remove_comments_from_text( \$cfg_txt, $comment, \$has_cr ) if $cfg_txt && $comment;
my $split_on = $has_cr ? '\r?\n' : '\n';
if ( !$limit && !$regexp_to_preprune && !$use_hash_of_arr_refs && length $delimiter ) {
if ($allow_undef_values) {
$parser_code = qq<
\$conf_ref = {
map {
(split(m/> . $delimiter . qq</, \$_, 2))[$key_value_text]
} split(/> . $split_on . qq</, \$cfg_txt)
};
>;
}
else {
$parser_code = ' $conf_ref = { map { ' . '($k,$v) = (split(m/' . $delimiter . '/, $_, 2))[' . $key_value_text . ']; ' . 'defined($v) ? ($k,$v) : () ' . '} split(/' . $split_on . '/, $cfg_txt ) }';
}
}
else {
if ( ( $Cpanel::Debug::level || 0 ) > 4 ) { # PPI NO PARSE - ok if not there
$limit ||= 0;
print STDERR __PACKAGE__ . "::parse_from_filehandle [slow LoadConfig parser used] LIMIT:[!$limit] REGEXP_TO_DELETE[!$regexp_to_preprune] USE_HASH_OF_ARR_REFS[$use_hash_of_arr_refs)]\n";
}
$parser_code = 'foreach (split(m/' . $split_on . '/, $cfg_txt)) {' . "\n" #
. q{next if !length;} . "\n" #
. ( $limit ? q{last if $keys++ == } . $limit . ';' : '' ) . "\n" . ( $regexp_to_preprune ? q{ s/} . $regexp_to_preprune . q{//g;} : '' ) . "\n" #
. (
length $delimiter ? #
(
q{( $k, $v ) = (split( /} . $delimiter . q{/, $_, 2 ))[} . $key_value_text . q{];} . "\n" . #
( !$allow_undef_values ? q{ next if !defined($v); } : '' ) . "\n" . #
( $use_hash_of_arr_refs ? q{ push @{ $conf_ref->{$k} }, $v; } : q{ $conf_ref->{$k} = $v; } ) . "\n" #
)
: q{$conf_ref->{$_} = 1; } . "\n"
) . '};';
}
$parser_code .= "; 1";
$parser_code =~ tr{\n}{\r}; ## no critic qw(Cpanel::TransliterationUsage)
eval($parser_code) or do { ## no critic qw(BuiltinFunctions::ProhibitStringyEval)
$parser_code =~ tr{\r}{\n}; ## no critic qw(Cpanel::TransliterationUsage)
_do_logger( 'panic', "Failed to parse :: $parser_code: $@" );
return ( 0, "$@\n$parser_code" );
};
delete $conf_ref->{''} if !defined( $conf_ref->{''} );
if ($skip_keys) {
my $skip_keys_ar;
if ( ref $skip_keys eq 'ARRAY' ) {
$skip_keys_ar = $skip_keys;
}
elsif ( ref $skip_keys eq 'HASH' ) {
$skip_keys_ar = [ keys %$skip_keys ];
}
else {
return ( 0, 'skip_keys must be an ARRAY or HASH reference' );
}
delete @{$conf_ref}{@$skip_keys_ar};
}
return ( 1, $conf_ref );
}
sub _hashify_ref {
my $conf_ref = shift;
if ( !defined($conf_ref) ) {
$conf_ref = {};
return $conf_ref;
}
unless ( ref $conf_ref eq 'HASH' ) {
if ( ref $conf_ref ) {
require Cpanel::Logger;
Cpanel::Logger::cplog( 'hashifying non-HASH reference', 'warn', __PACKAGE__ );
${$conf_ref} = {};
$conf_ref = ${$conf_ref};
}
else {
require Cpanel::Logger;
Cpanel::Logger::cplog( 'defined value encountered where reference expected', 'die', __PACKAGE__ );
}
}
return $conf_ref;
}
sub default_product_dir {
$PRODUCT_CONF_DIR = shift if @_;
return $PRODUCT_CONF_DIR;
}
sub _remove_comments_from_text {
my ( $cfg_txt_sr, $comment, $has_cr_sr ) = @_;
if ($$has_cr_sr) {
$$cfg_txt_sr = join( "\n", grep ( !m/$comment/, split( m{\r?\n}, $$cfg_txt_sr ) ) );
$$has_cr_sr = 0;
}
elsif ( $comment eq $DEFAULT_COMMENT_REGEXP ) {
if ( rindex( $$cfg_txt_sr, '#', 0 ) == 0 && index( $$cfg_txt_sr, "\n" ) > -1 ) {
substr( $$cfg_txt_sr, 0, index( $$cfg_txt_sr, "\n" ) + 1, '' );
}
$$cfg_txt_sr =~ s{$DEFAULT_COMMENT_REGEXP.*}{}omg if $$cfg_txt_sr =~ tr{#;}{};
}
else {
$$cfg_txt_sr =~ s{$comment.*}{}mg;
}
return 1;
}
1;
} # --- END Cpanel/Config/LoadConfig.pm
{ # --- BEGIN Cpanel/Config/LoadWwwAcctConf.pm
package Cpanel::Config::LoadWwwAcctConf;
use strict;
use warnings;
# use Cpanel::Debug ();
# use Cpanel::JSON::FailOK ();
my $SYSTEM_CONF_DIR = '/etc';
my $wwwconf_cache;
my $wwwconf_mtime = 0;
my $has_serializer;
our $wwwacctconf = "$SYSTEM_CONF_DIR/wwwacct.conf";
our $wwwacctconfshadow = "$SYSTEM_CONF_DIR/wwwacct.conf.shadow";
sub import {
my $this = shift;
if ( !exists $INC{'Cpanel/JSON.pm'} ) {
Cpanel::JSON::FailOK::LoadJSONModule();
}
if ( $INC{'Cpanel/JSON.pm'} ) {
$has_serializer = 1;
}
return Exporter::import( $this, @_ );
}
sub loadwwwacctconf { ## no critic qw(Subroutines::ProhibitExcessComplexity)
if ( $INC{'Cpanel/JSON.pm'} ) { $has_serializer = 1; } #something else loaded it
my $filesys_mtime = ( stat($wwwacctconf) )[9];
return if !$filesys_mtime;
if ( $filesys_mtime == $wwwconf_mtime && $wwwconf_cache ) {
return wantarray ? %{$wwwconf_cache} : $wwwconf_cache;
}
my $wwwacctconf_cache = "$wwwacctconf.cache";
my $wwwacctconfshadow_cache = "$wwwacctconfshadow.cache";
my $is_root = $> ? 0 : 1;
if ($has_serializer) {
my $cache_file;
my $cache_filesys_mtime;
my $have_valid_cache = 1;
if ( $is_root && -e $wwwacctconfshadow_cache ) {
$cache_filesys_mtime = ( stat(_) )[9]; #shadow cache's mtime
my $shadow_file_mtime = ( stat $wwwacctconfshadow )[9] || 0;
if ( $shadow_file_mtime < $cache_filesys_mtime ) {
$cache_file = $wwwacctconfshadow_cache;
}
else { #don't use shadow cache if shadow file is newer
$have_valid_cache = undef;
}
}
elsif ( -e $wwwacctconf_cache && !( $is_root && -r $wwwacctconfshadow ) ) {
$cache_filesys_mtime = ( stat $wwwacctconf_cache )[9]; #regular cache's mtime
$cache_file = $wwwacctconf_cache;
}
else {
$have_valid_cache = undef;
}
my $now = time();
if ( $Cpanel::Debug::level >= 5 ) {
print STDERR __PACKAGE__ . "::loadwwwacctconf cache_filesys_mtime = $cache_filesys_mtime , filesys_mtime: $filesys_mtime , now : $now\n";
}
if ( $have_valid_cache && $cache_filesys_mtime > $filesys_mtime && $cache_filesys_mtime < $now ) {
my $wwwconf_ref;
if ( open( my $conf_fh, '<', $cache_file ) ) {
$wwwconf_ref = Cpanel::JSON::FailOK::LoadFile($conf_fh);
close($conf_fh);
}
if ( $wwwconf_ref && ( scalar keys %{$wwwconf_ref} ) > 0 ) {
if ( $Cpanel::Debug::level >= 5 ) { print STDERR __PACKAGE__ . "::loadwwwconf file system cache hit\n"; }
$wwwconf_cache = $wwwconf_ref;
$wwwconf_mtime = $filesys_mtime;
return wantarray ? %{$wwwconf_ref} : $wwwconf_ref;
}
}
}
my @configfiles;
push @configfiles, $wwwacctconf;
if ($is_root) { push @configfiles, $wwwacctconfshadow; } #shadow file must be last as the cache gets written for each file with all the files before it in it
my $can_write_cache;
if ( $is_root && $has_serializer ) {
$can_write_cache = 1;
}
my %CONF = (
'ADDR' => undef,
'CONTACTEMAIL' => undef,
'DEFMOD' => undef,
'ETHDEV' => undef,
'HOST' => undef,
'NS' => undef,
'NS2' => undef,
);
require Cpanel::Config::LoadConfig;
foreach my $configfile (@configfiles) {
Cpanel::Config::LoadConfig::loadConfig( $configfile, \%CONF, '\s+', undef, undef, undef, { 'nocache' => 1 } );
foreach ( keys %CONF ) {
$CONF{$_} =~ s{\s+$}{} if defined $CONF{$_};
}
foreach (qw(HOMEDIR HOMEMATCH)) {
$CONF{$_} =~ s{/+$}{} if defined $CONF{$_}; # Remove trailing slashes
}
if ($can_write_cache) {
my $cache_file = $configfile . '.cache';
require Cpanel::FileUtils::Write::JSON::Lazy;
Cpanel::FileUtils::Write::JSON::Lazy::write_file( $cache_file, \%CONF, ( $configfile eq $wwwacctconfshadow ) ? 0600 : 0644 );
}
}
$wwwconf_mtime = $filesys_mtime;
$wwwconf_cache = \%CONF;
return wantarray ? %CONF : \%CONF;
}
sub reset_mem_cache {
( $wwwconf_mtime, $wwwconf_cache ) = ( 0, undef );
}
sub reset_has_serializer {
$has_serializer = 0;
}
sub default_conf_dir {
$SYSTEM_CONF_DIR = shift if @_;
$wwwacctconf = "$SYSTEM_CONF_DIR/wwwacct.conf";
$wwwacctconfshadow = "$SYSTEM_CONF_DIR/wwwacct.conf.shadow";
return $SYSTEM_CONF_DIR;
}
sub reset_caches {
my @cache_files = map { "$_.cache" } ( $wwwacctconf, $wwwacctconfshadow );
for my $cache_file (@cache_files) {
unlink $cache_file if -e $cache_file;
}
reset_mem_cache();
return;
}
1;
} # --- END Cpanel/Config/LoadWwwAcctConf.pm
{ # --- BEGIN Cpanel/Conf.pm
package Cpanel::Conf;
# use Cpanel::Config::Constants ();
my $cpanel_theme;
my $webmail_theme;
sub new {
my ( $class, %opts ) = @_;
my $self = {};
bless $self, $class;
if ( exists $opts{'wwwacct'} && ref $opts{'wwwacct'} eq 'HASH' ) {
$self->{'wwwacct'} = $opts{'wwwacct'};
}
undef $cpanel_theme;
undef $webmail_theme;
return $self;
}
sub system_config_dir {
my ($self) = @_;
return '/etc';
}
sub product_config_dir {
my ($self) = @_;
return '/var/cpanel';
}
sub product_base_dir {
my ($self) = @_;
return '/usr/local/cpanel';
}
sub whm_base_dir {
my ($self) = @_;
return $self->product_base_dir . '/whostmgr';
}
sub cpanel_theme_dir {
my ($self) = @_;
return $self->product_base_dir . '/base/frontend';
}
sub whm_theme_dir {
my ($self) = @_;
return $self->whm_base_dir . '/docroot/themes';
}
sub whm_theme {
my ($self) = @_;
return 'x';
}
sub account_creation_defaults {
my ($self) = @_;
if ( exists $self->{'wwwacct'} ) {
my %wwwacct = %{ $self->{'wwwacct'} };
return \%wwwacct;
}
require Cpanel::Config::LoadWwwAcctConf;
return Cpanel::Config::LoadWwwAcctConf::loadwwwacctconf();
}
sub cpanel_theme {
my ($self) = @_;
return $cpanel_theme if defined $cpanel_theme;
$cpanel_theme = $Cpanel::Config::Constants::DEFAULT_CPANEL_THEME;
my $defaults = {};
$defaults = $self->account_creation_defaults();
if ( ref $defaults eq 'HASH' && $defaults->{'DEFMOD'} ) {
$cpanel_theme = $defaults->{'DEFMOD'};
}
return $cpanel_theme;
}
sub default_webmail_theme {
my ($self) = @_;
return $webmail_theme if defined $webmail_theme;
$webmail_theme = $Cpanel::Config::Constants::DEFAULT_WEBMAIL_THEME;
my $defaults = {};
$defaults = $self->account_creation_defaults();
if ( ref $defaults eq 'HASH' && $defaults->{'DEFWEBMAILTHEME'} ) {
$webmail_theme = $defaults->{'DEFWEBMAILTHEME'};
}
return $webmail_theme;
}
1;
} # --- END Cpanel/Conf.pm
{ # --- BEGIN Cpanel/Config/LoadCpUserFile.pm
package Cpanel::Config::LoadCpUserFile;
use strict;
use warnings;
use Try::Tiny;
# use Cpanel::DB::Utils ();
# use Cpanel::Exception ();
# use Cpanel::FileUtils::Write::JSON::Lazy ();
# use Cpanel::AdminBin::Serializer::FailOK ();
# use Cpanel::Config::Constants ();
# use Cpanel::ConfigFiles ();
# use Cpanel::LoadFile::ReadFast ();
our $VERSION = '0.81'; # DO NOT CHANGE THIS FROM A DECIMAL
my %cpuser_defaults = (
'BWLIMIT' => 'unlimited',
'DEADDOMAINS' => undef,
'DEMO' => 0,
'DOMAIN' => '',
'DOMAINS' => undef,
'FEATURELIST' => 'default',
'HASCGI' => 0,
'HASDKIM' => 0,
'HASSPF' => 0,
'IP' => '127.0.0.1',
'MAILBOX_FORMAT' => 'maildir', #keep in sync with cpconf
'MAX_EMAILACCT_QUOTA' => 'unlimited',
'MAXADDON' => 0,
'MAXFTP' => 'unlimited',
'MAXLST' => 'unlimited',
'MAXPARK' => 0,
'MAXPOP' => 'unlimited',
'MAXSQL' => 'unlimited',
'MAXSUB' => 'unlimited',
'OWNER' => 'root',
'PLAN' => 'undefined',
'RS' => '',
'STARTDATE' => '0000000000',
);
sub _cpuser_defaults {
return %cpuser_defaults;
}
my %should_never_be_on_disk = map { $_ => undef } qw(
DBOWNER
DOMAIN
DOMAINS
DEADDOMAINS
HOMEDIRLINKS
);
my $logger;
sub load_or_die {
return ( _load( $_[0], undef, if_missing => 'die' ) )[2];
}
sub load_if_exists {
return ( _load( $_[0], undef, if_missing => 'return' ) )[2] // undef;
}
sub load_file {
my ($file) = @_;
return parse_cpuser_file( _open_cpuser_file( '<', $file ) );
}
sub _open_cpuser_file_locked {
my ( $mode, $file ) = @_;
local $!;
my $cpuser_fh;
require Cpanel::SafeFile;
my $lock_obj = Cpanel::SafeFile::safeopen( $cpuser_fh, $mode, $file ) or do {
die Cpanel::Exception::create( 'IO::FileOpenError', [ path => $file, error => $!, mode => $mode ] );
};
return ( $lock_obj, $cpuser_fh );
}
sub _open_cpuser_file {
my ( $mode, $file ) = @_;
local $!;
my $cpuser_fh;
open( $cpuser_fh, $mode, $file ) or do {
die Cpanel::Exception::create( 'IO::FileOpenError', [ path => $file, error => $!, mode => $mode ] );
};
return $cpuser_fh;
}
sub parse_cpuser_file {
my ($cpuser_fh) = @_;
my %cpuser = %cpuser_defaults;
my %DOMAIN_MAP;
my %DEAD_DOMAIN_MAP;
my %HOMEDIRLINKS_MAP;
local ( $!, $_ );
my $buffer = '';
Cpanel::LoadFile::ReadFast::read_all_fast( $cpuser_fh, $buffer );
foreach ( split( m{\n}, $buffer ) ) {
next if index( $_, '#' ) > -1 && m/^\s*#/;
my ( $key, $value ) = split( /\s*=/, $_, 2 );
if ( !defined $value || exists $should_never_be_on_disk{$key} ) {
next;
}
elsif ( $key eq 'DNS' ) {
$cpuser{'DOMAIN'} = lc $value;
}
elsif ( index( $key, 'DNS' ) == 0 && substr( $key, 3, 1 ) =~ tr{0-9}{} ) {
$DOMAIN_MAP{ lc $value } = undef;
}
elsif ( index( $key, 'XDNS' ) == 0 && substr( $key, 4, 1 ) =~ tr{0-9}{} ) {
$DEAD_DOMAIN_MAP{ lc $value } = undef;
}
elsif ( index( $key, 'HOMEDIRPATHS' ) == 0 && $key =~ m{ \A HOMEDIRPATHS \d* \z }xms ) {
$HOMEDIRLINKS_MAP{$value} = undef;
}
else {
$cpuser{$key} = $value;
}
}
delete @DEAD_DOMAIN_MAP{ keys %DOMAIN_MAP };
delete $DOMAIN_MAP{ $cpuser{'DOMAIN'} };
if ($!) {
die Cpanel::Exception::create( 'IO::FileReadError', [ error => $! ] );
}
if ( exists $cpuser{'USER'} ) {
$cpuser{'DBOWNER'} = Cpanel::DB::Utils::username_to_dbowner( $cpuser{'USER'} );
}
if ( !length $cpuser{'RS'} ) {
require Cpanel::Conf;
my $cp_defaults = Cpanel::Conf->new();
$cpuser{'RS'} = $cp_defaults->cpanel_theme;
}
if ( !$cpuser{'LOCALE'} ) {
$cpuser{'LOCALE'} = 'en';
$cpuser{'__LOCALE_MISSING'} = 1;
}
$cpuser{'DOMAINS'} = [ sort keys %DOMAIN_MAP ]; # Sorted here so they can be tested with Test::More::is_deeply
$cpuser{'DEADDOMAINS'} = [ sort keys %DEAD_DOMAIN_MAP ]; # Sorted here so they can be tested with Test::More::is_deeply
$cpuser{'HOMEDIRLINKS'} = [ sort keys %HOMEDIRLINKS_MAP ];
return \%cpuser;
}
sub _logger {
return $logger ||= do {
require Cpanel::Logger;
Cpanel::Logger->new();
};
}
sub load {
my ( $user, $opts ) = @_;
my $cpuser = ( _load( $user, $opts ) )[2];
if ( !ref $cpuser ) {
_logger()->warn( "Failed to load cPanel user file for '" . ( $user || '' ) . "'" ) unless $opts->{'quiet'};
return wantarray ? () : {};
}
return wantarray ? %$cpuser : $cpuser;
}
sub _load_locked {
my ($user) = @_;
my ( $fh, $lock_fh, $cpuser ) = _load( $user, { lock => 1 } );
return unless $fh && $lock_fh && $cpuser;
return {
'file' => $fh,
'lock' => $lock_fh,
'data' => $cpuser,
};
}
sub clear_cache {
my ($user) = @_;
return unlink "$Cpanel::ConfigFiles::cpanel_users.cache/$user";
}
sub _load { ## no critic(Subroutines::ProhibitExcessComplexity) -- Refactoring this function is a project, not a bug fix
my ( $user, $load_opts_ref, %internal_opts ) = @_;
if ( !$user || $user =~ tr</\0><> ) { #no eq '' needed as !$user covers this
_logger()->warn("Invalid username (falsy or forbidden character) given to loadcpuserfile.");
if ( $internal_opts{'if_missing'} ) {
die Cpanel::Exception::create( 'UserNotFound', [ name => '' ] );
}
return;
}
my ( $now, $has_serializer, $user_file, $user_cache_file ) = (
time(), #now
( exists $INC{'Cpanel/JSON.pm'} ? 1 : 0 ), #has_serializer
$load_opts_ref->{'file'} || "$Cpanel::ConfigFiles::cpanel_users/$user", # user_file
"$Cpanel::ConfigFiles::cpanel_users.cache/$user", # user_cache_file
);
my ( $cpuid, $cpgid, $size, $mtime ) = ( stat($user_file) )[ 4, 5, 7, 9 ];
if ( not defined($size) and my $if_missing = $internal_opts{'if_missing'} ) {
if ( $! == _ENOENT() ) {
if ( $if_missing eq 'return' ) {
return;
}
die Cpanel::Exception::create( 'UserNotFound', [ name => $user ] );
}
die Cpanel::Exception->create( 'The system failed to find the file “[_1]” because of an error: [_2]', [ $user_file, $! ] );
}
$mtime ||= 0;
my $lock_fh;
my $cpuser_fh;
if ( $load_opts_ref->{'lock'} ) {
my $mode = $mtime ? '+<' : '+>';
try {
( $lock_fh, $cpuser_fh ) = _open_cpuser_file_locked( $mode, $user_file );
}
catch {
if ( my $if_missing = $internal_opts{'if_missing'} ) {
die $_ if $if_missing ne 'return';
}
else {
_logger()->warn($_);
}
};
return if !$lock_fh;
}
elsif ( !$size ) {
if ( $user eq 'cpanel' ) {
my $result = load_cpanel_user();
return ( $cpuser_fh, $lock_fh, $result );
}
else {
_logger()->warn("User file '$user_file' is empty or non-existent.") unless $load_opts_ref->{'quiet'};
return;
}
}
if ( $Cpanel::Debug::level && $Cpanel::Debug::level > 3 ) { # PPI NO PARSE - This doesn't need to be loaded
_logger()->debug("load cPanel user file [$user]");
}
if ($has_serializer) {
$user_cache_file = each %{ { ( $user_cache_file => undef ) } }; #detaint - case CPANEL-11199
if ( open( my $cache_fh, '<:stdio', $user_cache_file ) ) { #ok if the file is not there
my $cache_mtime = ( stat($cache_fh) )[9]; # Check the mtime after we have opened the file to prevent a race condition
if ( $cache_mtime >= $mtime && $cache_mtime <= $now ) {
my $cpuser_ref = Cpanel::AdminBin::Serializer::FailOK::LoadFile($cache_fh);
if ( $cpuser_ref && ref $cpuser_ref eq 'HASH' ) {
if ( $Cpanel::Debug::level && $Cpanel::Debug::level > 3 ) { # PPI NO PARSE - This doesn't need to be loaded
_logger()->debug("load cache hit user[$user] now[$now] mtime[$mtime] cache_mtime[$cache_mtime]");
}
$cpuser_ref->{'MTIME'} = $mtime;
if ( ( $cpuser_ref->{'__CACHE_DATA_VERSION'} // 0 ) == $VERSION ) {
return ( $cpuser_fh, $lock_fh, $cpuser_ref );
}
else {
unlink $user_cache_file; # force a re-cache of the latest data set
}
}
}
else {
if ( $Cpanel::Debug::level && $Cpanel::Debug::level > 3 ) { # PPI NO PARSE - This doesn't need to be loaded
_logger()->debug("load cache miss user[$user] now[$now] mtime[$mtime] cache_mtime[$cache_mtime]");
}
}
close($cache_fh);
}
else {
if ( $Cpanel::Debug::level && $Cpanel::Debug::level > 3 ) { # PPI NO PARSE - This doesn't need to be loaded
_logger()->debug("load cache miss user[$user] now[$now] mtime[$mtime] cache_mtime[0]");
}
}
}
if ( !$lock_fh ) {
try {
$cpuser_fh = _open_cpuser_file( '<', $user_file );
}
catch {
die $_ if $internal_opts{'if_missing'};
_logger()->warn($_);
};
return if !$cpuser_fh;
}
my $cpuser_hr;
try {
$cpuser_hr = parse_cpuser_file($cpuser_fh);
}
catch {
_logger()->warn("Failed to read “$user_file”: $_");
};
return if !$cpuser_hr;
$cpuser_hr->{'USER'} = $user;
$cpuser_hr->{'DBOWNER'} = Cpanel::DB::Utils::username_to_dbowner($user);
$cpuser_hr->{'__CACHE_DATA_VERSION'} = $VERSION; # set this before the cache is written so that it will be included in the cache
if ( $> == 0 ) {
create_users_cache_dir();
if ( $has_serializer && Cpanel::FileUtils::Write::JSON::Lazy::write_file( $user_cache_file, $cpuser_hr, 0640 ) ) {
chown 0, $cpgid, $user_cache_file if $cpgid; # this is ok if the chown happens after as we fall though to reading the non-cache on a failed open
}
else {
unlink $user_cache_file; #outdated
}
}
$cpuser_hr->{'MTIME'} = ( stat($cpuser_fh) )[9];
if ( $load_opts_ref->{'lock'} ) {
seek( $cpuser_fh, 0, 0 );
}
else {
if ($lock_fh) {
require Cpanel::SafeFile;
Cpanel::SafeFile::safeclose( $cpuser_fh, $lock_fh );
}
$cpuser_fh = $lock_fh = undef;
}
return ( $cpuser_fh, $lock_fh, $cpuser_hr );
}
sub loadcpuserfile {
load( $_[0] );
}
sub load_cpanel_user {
my %cpuser = (
%cpuser_defaults,
'DEADDOMAINS' => [],
'DOMAIN' => 'domain.tld',
'DOMAINS' => [],
'HASCGI' => 1,
'HOMEDIRLINKS' => [],
'LOCALE' => 'en',
'MAXADDON' => 'unlimited',
'MAXPARK' => 'unlimited',
'RS' => $Cpanel::Config::Constants::DEFAULT_CPANEL_THEME,
'USER' => 'cpanel',
);
return wantarray ? %cpuser : \%cpuser;
}
sub create_users_cache_dir {
my $uc = "$Cpanel::ConfigFiles::cpanel_users.cache";
if ( -f $uc || -l $uc ) {
my $bad = "$uc.bad";
unlink $bad if -e $bad;
rename $uc, $bad;
}
if ( !-e $uc ) {
mkdir $uc;
}
return;
}
sub _ENOENT { return 2; }
1;
} # --- END Cpanel/Config/LoadCpUserFile.pm
{ # --- BEGIN Cpanel/Config/HasCpUserFile.pm
package Cpanel::Config::HasCpUserFile;
use strict;
use warnings;
# use Cpanel::ConfigFiles ();
sub has_cpuser_file {
return 0 if !length $_[0] || $_[0] =~ tr{/\0}{};
return -e "$Cpanel::ConfigFiles::cpanel_users/$_[0]" && -s _;
}
sub has_readable_cpuser_file {
my ($user) = @_;
return unless defined $user and $user ne '' and $user !~ tr/\/\0//;
return -e "$Cpanel::ConfigFiles::cpanel_users/$user" && -s _ && -r _;
}
1;
} # --- END Cpanel/Config/HasCpUserFile.pm
{ # --- BEGIN Cpanel/NSCD/Constants.pm
package Cpanel::NSCD::Constants;
use strict;
our $NSCD_CONFIG_FILE = '/etc/nscd.conf';
our $NSCD_SOCKET = '/var/run/nscd/socket';
1;
} # --- END Cpanel/NSCD/Constants.pm
{ # --- BEGIN Cpanel/Socket/UNIX/Micro.pm
package Cpanel::Socket::UNIX::Micro;
use strict;
my $MAX_PATH_LENGTH = 107;
my $LITTLE_ENDIAN_TEMPLATE = 'vZ' . ( 1 + $MAX_PATH_LENGTH ); # x86_64 is always little endian
my $AF_UNIX = 1;
my $SOCK_STREAM = 1;
sub connect {
socket( $_[0], $AF_UNIX, $SOCK_STREAM, 0 ) or warn "socket(AF_UNIX, SOCK_STREAM): $!";
return connect( $_[0], micro_sockaddr_un( $_[1] ) );
}
sub micro_sockaddr_un {
if ( length( $_[0] ) > $MAX_PATH_LENGTH ) {
my $excess = length( $_[0] ) - $MAX_PATH_LENGTH;
die "“$_[0]” is $excess character(s) too long to be a path to a local socket ($MAX_PATH_LENGTH bytes maximum)!";
}
return pack( 'va*', $AF_UNIX, $_[0] ) if 0 == rindex( $_[0], "\0", 0 );
return pack(
$LITTLE_ENDIAN_TEMPLATE, # x86_64 is always little endian
$AF_UNIX,
$_[0],
);
}
sub unpack_sockaddr_un {
return substr( $_[0], 2 ) if 2 == rindex( $_[0], "\0", 2 );
return ( unpack $LITTLE_ENDIAN_TEMPLATE, $_[0] )[1];
}
1;
} # --- END Cpanel/Socket/UNIX/Micro.pm
{ # --- BEGIN Cpanel/NSCD/Check.pm
package Cpanel::NSCD::Check;
use strict;
# use Cpanel::NSCD::Constants ();
# use Cpanel::Socket::UNIX::Micro ();
our $CACHE_TTL = 600;
my $last_check_time = 0;
my $nscd_is_running_cache;
sub nscd_is_running {
my $now = time();
if ( $last_check_time && $last_check_time + $CACHE_TTL > $now ) {
return $nscd_is_running_cache;
}
$last_check_time = $now;
my $socket;
if ( Cpanel::Socket::UNIX::Micro::connect( $socket, $Cpanel::NSCD::Constants::NSCD_SOCKET ) ) {
return ( $nscd_is_running_cache = 1 );
}
return ( $nscd_is_running_cache = 0 );
}
1;
} # --- END Cpanel/NSCD/Check.pm
{ # --- BEGIN Cpanel/PwCache/Helpers.pm
package Cpanel::PwCache::Helpers;
use strict;
my $skip_uid_cache = 0;
sub no_uid_cache { $skip_uid_cache = 1; return }
sub uid_cache { $skip_uid_cache = 0; return }
sub skip_uid_cache {
return $skip_uid_cache;
}
sub init {
my ( $totie, $skip_uid_cache_value ) = @_;
tiedto($totie);
$skip_uid_cache = $skip_uid_cache_value;
return;
}
{ # debugging helpers
sub confess { require Carp; return Carp::confess(@_) }
sub cluck { require Carp; return Carp::cluck(@_) }
}
{ # tie logic and cache
my $pwcacheistied = 0;
my $pwcachetie;
sub istied { return $pwcacheistied }
sub deinit { $pwcacheistied = 0; return; }
sub tiedto {
my $v = shift;
if ( !defined $v ) { # get
return $pwcacheistied ? $pwcachetie : undef;
}
else { # set
$pwcacheistied = 1;
$pwcachetie = $v;
}
return;
}
}
{
my $SYSTEM_CONF_DIR = '/etc';
my $PRODUCT_CONF_DIR = '/var/cpanel';
*default_conf_dir = sub () { return $SYSTEM_CONF_DIR };
*default_product_dir = sub () { return $PRODUCT_CONF_DIR; };
}
1;
} # --- END Cpanel/PwCache/Helpers.pm
{ # --- BEGIN Cpanel/PwCache/Cache.pm
package Cpanel::PwCache::Cache;
use strict;
use warnings;
my %_cache;
my %_homedir_cache;
use constant get_cache => \%_cache;
use constant get_homedir_cache => \%_homedir_cache;
our $pwcache_inited = 0;
my $PWCACHE_IS_SAFE = 1;
sub clear { # clear all
%_cache = ();
%_homedir_cache = ();
$pwcache_inited = 0;
return;
}
sub remove_key {
my ($pwkey) = @_;
return delete $_cache{$pwkey};
}
sub replace {
my $h = shift;
%_cache = %$h if ref $h eq 'HASH';
return;
}
sub is_safe {
$PWCACHE_IS_SAFE = $_[0] if defined $_[0];
return $PWCACHE_IS_SAFE;
}
sub pwmksafecache {
return if $PWCACHE_IS_SAFE;
$_cache{$_}{'contents'}->[1] = 'x' for keys %_cache;
$PWCACHE_IS_SAFE = 1;
return;
}
1;
} # --- END Cpanel/PwCache/Cache.pm
{ # --- BEGIN Cpanel/PwCache/Find.pm
package Cpanel::PwCache::Find;
use strict;
# use Cpanel::LoadFile::ReadFast ();
our $PW_CHUNK_SIZE = 1 << 17;
sub field_with_value_in_pw_file {
my ( $passwd_fh, $field, $value ) = @_;
return if ( $value =~ tr{\x{00}-\x{1f}\x{7f}:}{} );
my $needle = $field == 0 ? "\n${value}:" : ":${value}";
my $haystack;
my $match_pos = 0;
my $line_start;
my $line_end;
my $not_eof;
my $data = "\n";
while ( ( $not_eof = Cpanel::LoadFile::ReadFast::read_fast( $passwd_fh, $data, $PW_CHUNK_SIZE, length $data ) ) || length($data) > 1 ) {
$haystack = $not_eof ? substr( $data, 0, rindex( $data, "\n" ), '' ) : $data;
while ( -1 < ( $match_pos = index( $haystack, $needle, $match_pos ) ) ) {
$line_start = ( !$field ? $match_pos : rindex( $haystack, "\n", $match_pos ) ) + 1;
if (
!$field || (
$field == ( substr( $haystack, $line_start, $match_pos - $line_start + 1 ) =~ tr{:}{} )
&& ( length($haystack) == $match_pos + length($needle) || substr( $haystack, $match_pos + length($needle), 1 ) =~ tr{:\n}{} )
)
) {
$line_end = index( $haystack, "\n", $match_pos + length($needle) );
my $line = substr( $haystack, $line_start, ( $line_end > -1 ? $line_end : length($haystack) ) - $line_start );
return split( ':', $line );
}
$match_pos += length($needle);
}
last unless $not_eof;
}
return;
}
1;
} # --- END Cpanel/PwCache/Find.pm
{ # --- BEGIN Cpanel/PwCache/Build.pm
package Cpanel::PwCache::Build;
use strict;
use warnings;
# use Cpanel::Debug ();
# use Cpanel::JSON::FailOK ();
# use Cpanel::FileUtils::Write::JSON::Lazy ();
# use Cpanel::PwCache::Helpers ();
# use Cpanel::PwCache::Cache ();
# use Cpanel::LoadFile::ReadFast ();
my ( $MIN_FIELDS_FOR_VALID_ENTRY, $pwcache_has_uid_cache ) = ( 0, 6 );
sub pwmksafecache {
return if Cpanel::PwCache::Cache::is_safe();
my $pwcache_ref = Cpanel::PwCache::Cache::get_cache();
$pwcache_ref->{$_}{'contents'}->[1] = 'x' for keys %{$pwcache_ref};
Cpanel::PwCache::Cache::is_safe(1);
return;
}
sub pwclearcache { # also known as clear_this_process_cache
$pwcache_has_uid_cache = undef;
Cpanel::PwCache::Cache::clear();
return;
}
sub init_pwcache {
Cpanel::PwCache::Cache::is_safe(0);
return _build_pwcache();
}
sub init_passwdless_pwcache {
return _build_pwcache( 'nopasswd' => 1 );
}
sub fetch_pwcache {
init_passwdless_pwcache() unless pwcache_is_initted();
my $pwcache_ref = Cpanel::PwCache::Cache::get_cache();
if ( scalar keys %$pwcache_ref < 3 ) {
die "The password cache unexpectedly had less than 3 entries";
}
return [ map { $pwcache_ref->{$_}->{'contents'} } grep { substr( $_, 0, 1 ) eq '0' } keys %{$pwcache_ref} ];
}
sub _write_json_cache {
my ($cache_file) = @_;
if ( !Cpanel::PwCache::Helpers::istied() && exists $INC{'Cpanel/JSON.pm'} ) {
my $pwcache_ref = Cpanel::PwCache::Cache::get_cache();
if ( !ref $pwcache_ref || scalar keys %$pwcache_ref < 3 ) {
die "The system failed build the password cache";
}
Cpanel::FileUtils::Write::JSON::Lazy::write_file( $cache_file, $pwcache_ref, 0600 );
}
return;
}
sub _write_tied_cache {
my ( $crypted_passwd_ref, $passwdmtime, $hpasswdmtime ) = @_;
my $SYSTEM_CONF_DIR = Cpanel::PwCache::Helpers::default_conf_dir();
local $!;
if ( open( my $pwcache_passwd_fh, '<:stdio', "$SYSTEM_CONF_DIR/passwd" ) ) {
local $/;
my $pwcache_ref = Cpanel::PwCache::Cache::get_cache();
my $data = '';
Cpanel::LoadFile::ReadFast::read_all_fast( $pwcache_passwd_fh, $data );
die "The file “$SYSTEM_CONF_DIR/passwd” was unexpectedly empty" if !length $data;
my @fields;
my $skip_uid_cache = Cpanel::PwCache::Helpers::skip_uid_cache();
foreach my $line ( split( /\n/, $data ) ) {
next unless length $line;
@fields = split( /:/, $line );
next if scalar @fields < $MIN_FIELDS_FOR_VALID_ENTRY || $fields[0] =~ tr/[A-Z][a-z][0-9]._-//c;
$pwcache_ref->{ '0:' . $fields[0] } = {
'cachetime' => $passwdmtime,
'hcachetime' => $hpasswdmtime,
'contents' => [ $fields[0], $crypted_passwd_ref->{ $fields[0] } || $fields[1], $fields[2], $fields[3], '', '', $fields[4], $fields[5], $fields[6], -1, -1, $passwdmtime, $hpasswdmtime ]
};
next if $skip_uid_cache || !defined $fields[2] || exists $pwcache_ref->{ '2:' . $fields[2] };
$pwcache_ref->{ '2:' . $fields[2] } = $pwcache_ref->{ '0:' . $fields[0] };
}
close($pwcache_passwd_fh);
}
else {
die "The system failed to read $SYSTEM_CONF_DIR/passwd because of an error: $!";
}
return;
}
sub _cache_ref_is_valid {
my ( $cache_ref, $passwdmtime, $hpasswdmtime ) = @_;
my @keys = qw/0:root 0:cpanel 0:bin/;
return
$cache_ref
&& ( scalar keys %{$cache_ref} ) > 2
&& scalar @keys == grep { #
$cache_ref->{$_}->{'hcachetime'}
&& $cache_ref->{$_}->{'hcachetime'} == $hpasswdmtime
&& $cache_ref->{$_}->{'cachetime'}
&& $cache_ref->{$_}->{'cachetime'} == $passwdmtime
} @keys;
}
sub _build_pwcache {
my %OPTS = @_;
if ( $INC{'B/C.pm'} ) {
Cpanel::PwCache::Helpers::confess("Cpanel::PwCache::Build::_build_pwcache cannot be run under B::C (see case 162857)");
}
my $SYSTEM_CONF_DIR = Cpanel::PwCache::Helpers::default_conf_dir();
my ( $cache_file, $passwdmtime, $cache_file_mtime, $crypted_passwd_ref, $crypted_passwd_file, $hpasswdmtime ) = ( "$SYSTEM_CONF_DIR/passwd.cache", ( stat("$SYSTEM_CONF_DIR/passwd") )[9] );
if ( $OPTS{'nopasswd'} ) {
$hpasswdmtime = ( stat("$SYSTEM_CONF_DIR/shadow") )[9];
$cache_file = "$SYSTEM_CONF_DIR/passwd" . ( Cpanel::PwCache::Helpers::skip_uid_cache() ? '.nouids' : '' ) . '.cache';
}
elsif ( -r "$SYSTEM_CONF_DIR/shadow" ) {
Cpanel::PwCache::Cache::is_safe(0);
$hpasswdmtime = ( stat(_) )[9];
$crypted_passwd_file = "$SYSTEM_CONF_DIR/shadow";
$cache_file = "$SYSTEM_CONF_DIR/shadow" . ( Cpanel::PwCache::Helpers::skip_uid_cache() ? '.nouids' : '' ) . '.cache';
}
else {
$hpasswdmtime = 0;
}
if ( !Cpanel::PwCache::Helpers::istied() && exists $INC{'Cpanel/JSON.pm'} ) {
if ( open( my $cache_fh, '<:stdio', $cache_file ) ) {
my $cache_file_mtime = ( stat($cache_fh) )[9] || 0;
if ( $cache_file_mtime > $hpasswdmtime && $cache_file_mtime > $passwdmtime ) {
my $cache_ref = Cpanel::JSON::FailOK::LoadFile($cache_fh);
Cpanel::Debug::log_debug("[read pwcache from $cache_file]") if ( $Cpanel::Debug::level > 3 );
if ( _cache_ref_is_valid( $cache_ref, $passwdmtime, $hpasswdmtime ) ) {
Cpanel::Debug::log_debug("[validated pwcache from $cache_file]") if ( $Cpanel::Debug::level > 3 );
my $memory_pwcache_ref = Cpanel::PwCache::Cache::get_cache();
@{$cache_ref}{ keys %$memory_pwcache_ref } = values %$memory_pwcache_ref;
Cpanel::PwCache::Cache::replace($cache_ref);
$Cpanel::PwCache::Cache::pwcache_inited = ( $OPTS{'nopasswd'} ? 1 : 2 );
return;
}
}
}
}
if ($crypted_passwd_file) { $crypted_passwd_ref = _load_pws($crypted_passwd_file); }
$Cpanel::PwCache::Cache::pwcache_inited = ( $OPTS{'nopasswd'} ? 1 : 2 );
$pwcache_has_uid_cache = ( Cpanel::PwCache::Helpers::skip_uid_cache() ? 0 : 1 );
_write_tied_cache( $crypted_passwd_ref, $passwdmtime, $hpasswdmtime );
_write_json_cache($cache_file) if $> == 0;
return 1;
}
sub pwcache_is_initted {
return ( $Cpanel::PwCache::Cache::pwcache_inited ? $Cpanel::PwCache::Cache::pwcache_inited : 0 );
}
sub _load_pws {
my $lookup_file = shift;
if ( $INC{'B/C.pm'} ) {
Cpanel::PwCache::Helpers::confess("Cpanel::PwCache::Build::_load_pws cannot be run under B::C (see case 162857)");
}
my %PW;
if ( open my $lookup_fh, '<:stdio', $lookup_file ) {
my $data = '';
Cpanel::LoadFile::ReadFast::read_all_fast( $lookup_fh, $data );
die "The file “$lookup_file” was unexpectedly empty" if !length $data;
%PW = map { ( split(/:/) )[ 0, 1 ] } split( /\n/, $data );
if ( index( $data, '#' ) > -1 ) {
delete @PW{ '', grep { index( $_, '#' ) == 0 } keys %PW };
}
else {
delete $PW{''};
}
close $lookup_fh;
}
return \%PW;
}
1;
} # --- END Cpanel/PwCache/Build.pm
{ # --- BEGIN Cpanel/PwCache.pm
package Cpanel::PwCache;
use strict;
# use Cpanel::Debug ();
# use Cpanel::NSCD::Check ();
# use Cpanel::PwCache::Helpers ();
# use Cpanel::PwCache::Cache ();
# use Cpanel::PwCache::Find ();
use constant DUMMY_PW_RETURNS => ( -1, -1, 0, 0 );
use constant DEBUG => 0; # Must set $ENV{'CPANEL_DEBUG_LEVEL'} = 5 as well
our $VERSION = '4.2';
my %FIXED_KEYS = (
'0:root' => 1,
'0:nobody' => 1,
'0:cpanel' => 1,
'0:cpanellogin' => 1,
'0:mail' => 1,
'2:0' => 1,
'2:99' => 1
);
our $_WANT_ENCRYPTED_PASSWORD;
sub getpwnam_noshadow {
$_WANT_ENCRYPTED_PASSWORD = 0;
goto &_getpwnam;
}
sub getpwuid_noshadow {
$_WANT_ENCRYPTED_PASSWORD = 0;
goto &_getpwuid;
}
sub getpwnam {
$_WANT_ENCRYPTED_PASSWORD = !!wantarray;
goto &_getpwnam;
}
sub getpwuid {
$_WANT_ENCRYPTED_PASSWORD = !!wantarray;
goto &_getpwuid;
}
sub gethomedir {
my $uid_or_name = $_[0] // $>;
my $hd = Cpanel::PwCache::Cache::get_homedir_cache();
unless ( exists $hd->{$uid_or_name} ) {
$_WANT_ENCRYPTED_PASSWORD = 0;
if ( $uid_or_name !~ tr{0-9}{}c ) {
$hd->{$uid_or_name} = ( _getpwuid($uid_or_name) )[7];
}
else {
$hd->{$uid_or_name} = ( _getpwnam($uid_or_name) )[7];
}
}
return $hd->{$uid_or_name};
}
sub getusername {
my $uid = defined $_[0] ? $_[0] : $>;
$_WANT_ENCRYPTED_PASSWORD = 0;
return scalar _getpwuid($uid);
}
sub init_passwdless_pwcache {
require Cpanel::PwCache::Build;
*init_passwdless_pwcache = \&Cpanel::PwCache::Build::init_passwdless_pwcache;
goto &Cpanel::PwCache::Build::init_passwdless_pwcache;
}
sub _getpwuid { ## no critic qw(Subroutines::RequireArgUnpacking)
return unless ( length( $_[0] ) && $_[0] !~ tr/0-9//c );
my $pwcache_ref = Cpanel::PwCache::Cache::get_cache();
if ( !exists $pwcache_ref->{"2:$_[0]"} && $> != 0 && !Cpanel::PwCache::Helpers::istied() && Cpanel::NSCD::Check::nscd_is_running() ) {
return CORE::getpwuid( $_[0] ) if !wantarray;
my @ret = CORE::getpwuid( $_[0] );
return @ret ? ( @ret, DUMMY_PW_RETURNS() ) : ();
}
if ( my $pwref = _pwfunc( $_[0], 2 ) ) {
return wantarray ? @$pwref : $pwref->[0];
}
return; #important not to return 0
}
sub _getpwnam { ## no critic qw(Subroutines::RequireArgUnpacking)
return unless ( length( $_[0] ) && $_[0] !~ tr{\x{00}-\x{20}\x{7f}:/#}{} );
my $pwcache_ref = Cpanel::PwCache::Cache::get_cache();
if ( !exists $pwcache_ref->{"0:$_[0]"} && $> != 0 && !Cpanel::PwCache::Helpers::istied() && Cpanel::NSCD::Check::nscd_is_running() ) {
return CORE::getpwnam( $_[0] ) if !wantarray;
my @ret = CORE::getpwnam( $_[0] );
return @ret ? ( @ret, DUMMY_PW_RETURNS() ) : ();
}
if ( my $pwref = _pwfunc( $_[0], 0 ) ) {
return wantarray ? @$pwref : $pwref->[2];
}
return; #important not to return 0
}
sub _pwfunc { ## no critic qw(Subroutines::RequireArgUnpacking)
my ( $value, $field, $pwkey ) = ( $_[0], ( $_[1] || 0 ), $_[1] . ':' . ( $_[0] || 0 ) );
if ( Cpanel::PwCache::Helpers::istied() ) {
Cpanel::Debug::log_debug("cache tie (tied) value[$value] field[$field]") if (DEBUG);
my $pwcachetie = Cpanel::PwCache::Helpers::tiedto();
if ( ref $pwcachetie eq 'HASH' ) {
my $cache = $pwcachetie->{$pwkey};
if ( ref $cache eq 'HASH' ) {
return $pwcachetie->{$pwkey}->{'contents'};
}
}
return undef;
}
my $SYSTEM_CONF_DIR = Cpanel::PwCache::Helpers::default_conf_dir();
my $lookup_encrypted_pass = 0;
if ($_WANT_ENCRYPTED_PASSWORD) {
$lookup_encrypted_pass = $> == 0 ? 1 : 0;
}
my ( $passwdmtime, $hpasswdmtime );
my $pwcache_ref = Cpanel::PwCache::Cache::get_cache();
if ( my $cache_entry = $pwcache_ref->{$pwkey} ) {
Cpanel::Debug::log_debug("exists in cache value[$value] field[$field]") if (DEBUG);
if (
( exists( $cache_entry->{'contents'} ) && $cache_entry->{'contents'}->[1] ne 'x' ) # Has shadow entry
|| !$lookup_encrypted_pass # Or we do not need it
) { # If we are root and missing the password field we could fail authentication
if ( $FIXED_KEYS{$pwkey} ) { # We assume root, nobody, and cpanellogin will never change during execution
Cpanel::Debug::log_debug("cache (never change) hit value[$value] field[$field]") if (DEBUG);
return $cache_entry->{'contents'};
}
$passwdmtime = ( stat("$SYSTEM_CONF_DIR/passwd") )[9];
$hpasswdmtime = $lookup_encrypted_pass ? ( stat("$SYSTEM_CONF_DIR/shadow") )[9] : 0;
if ( ( $lookup_encrypted_pass && $hpasswdmtime && $hpasswdmtime != $cache_entry->{'hcachetime'} )
|| ( $passwdmtime && $passwdmtime != $cache_entry->{'cachetime'} ) ) { #timewarp safe
DEBUG && Cpanel::Debug::log_debug( "cache miss value[$value] field[$field] pwkey[$pwkey] " . qq{hpasswdmtime: $hpasswdmtime != $cache_entry->{hcachetime} } . qq{passwdmtime: $passwdmtime != $cache_entry->{cachetime} } );
if ( defined $cache_entry && defined $cache_entry->{'contents'} ) {
Cpanel::PwCache::Cache::clear(); #If the passwd file mtime changes everything is invalid
}
}
else {
Cpanel::Debug::log_debug("cache hit value[$value] field[$field]") if (DEBUG);
return $cache_entry->{'contents'};
}
}
elsif (DEBUG) {
Cpanel::Debug::log_debug( "cache miss pwkey[$pwkey] value[$value] field[$field] passwdmtime[$passwdmtime] pwcacheistied[" . Cpanel::PwCache::Helpers::istied() . "] hpasswdmtime[$hpasswdmtime]" );
}
}
elsif (DEBUG) {
Cpanel::Debug::log_debug( "cache miss (no entry) pwkey[$pwkey] value[$value] field[$field] pwcacheistied[" . Cpanel::PwCache::Helpers::istied() . "]" );
}
my $pwdata = _getpwdata( $value, $field, $passwdmtime, $hpasswdmtime, $lookup_encrypted_pass );
_cache_pwdata( $pwdata, $pwcache_ref ) if $pwdata && @$pwdata;
return $pwdata;
}
sub _getpwdata {
my ( $value, $field, $passwdmtime, $shadowmtime, $lookup_encrypted_pass ) = @_;
return if ( !defined $value || !defined $field || $value =~ tr/\0// );
if ($lookup_encrypted_pass) {
return [ _readshadow( $value, $field, $passwdmtime, $shadowmtime ) ];
}
return [ _readpasswd( $value, $field, $passwdmtime, $shadowmtime ) ];
}
sub _readshadow { ## no critic qw(Subroutines::RequireArgUnpacking)
my $SYSTEM_CONF_DIR = Cpanel::PwCache::Helpers::default_conf_dir();
my ( $value, $field, $passwdmtime, $shadowmtime ) = ( $_[0], ( $_[1] || 0 ), ( $_[2] || ( stat("$SYSTEM_CONF_DIR/passwd") )[9] ), ( $_[3] || ( stat("$SYSTEM_CONF_DIR/shadow") )[9] ) );
my @PW = _readpasswd( $value, $field, $passwdmtime, $shadowmtime );
return if !@PW;
$value = $PW[0];
if ( open my $shadow_fh, '<', "$SYSTEM_CONF_DIR/shadow" ) {
if ( my @SH = Cpanel::PwCache::Find::field_with_value_in_pw_file( $shadow_fh, 0, $value ) ) {
( $PW[1], $PW[9], $PW[10], $PW[11], $PW[12] ) = (
$SH[1], #encrypted pass
$SH[5], #expire time
$SH[2], #change time
$passwdmtime,
$shadowmtime
);
close $shadow_fh;
Cpanel::PwCache::Cache::is_safe(0);
return @PW;
}
}
else {
Cpanel::PwCache::Helpers::cluck("Unable to open $SYSTEM_CONF_DIR/shadow: $!");
}
Cpanel::PwCache::Helpers::cluck("Entry for $value missing in $SYSTEM_CONF_DIR/shadow");
return @PW;
}
sub _readpasswd { ## no critic qw(Subroutines::RequireArgUnpacking)
my $SYSTEM_CONF_DIR = Cpanel::PwCache::Helpers::default_conf_dir();
my ( $value, $field, $passwdmtime, $shadowmtime, $block ) = ( $_[0], ( $_[1] || 0 ), ( $_[2] || ( stat("$SYSTEM_CONF_DIR/passwd") )[9] ), $_[3] );
if ( $INC{'B/C.pm'} ) {
die("Cpanel::PwCache::_readpasswd cannot be run under B::C (see case 162857)");
}
if ( open( my $passwd_fh, '<', "$SYSTEM_CONF_DIR/passwd" ) ) {
if ( my @PW = Cpanel::PwCache::Find::field_with_value_in_pw_file( $passwd_fh, $field, $value ) ) {
return ( $PW[0], $PW[1], $PW[2], $PW[3], '', '', $PW[4], $PW[5], $PW[6], -1, -1, $passwdmtime, ( $shadowmtime || $passwdmtime ) );
}
close($passwd_fh);
}
else {
Cpanel::PwCache::Helpers::cluck("open($SYSTEM_CONF_DIR/passwd): $!");
}
return;
}
sub _cache_pwdata {
my ( $pwdata, $pwcache_ref ) = @_;
$pwcache_ref ||= Cpanel::PwCache::Cache::get_cache();
if ( $pwdata->[2] != 0 || $pwdata->[0] eq 'root' ) { # special case for multiple uid 0 users
@{ $pwcache_ref->{ '2' . ':' . $pwdata->[2] } }{ 'cachetime', 'hcachetime', 'contents' } = ( $pwdata->[11], $pwdata->[12], $pwdata );
}
@{ $pwcache_ref->{ '0' . ':' . $pwdata->[0] } }{ 'cachetime', 'hcachetime', 'contents' } = ( $pwdata->[11], $pwdata->[12], $pwdata );
return 1;
}
1;
} # --- END Cpanel/PwCache.pm
{ # --- BEGIN Cpanel/Locale/Utils/User.pm
package Cpanel::Locale::Utils::User;
use strict;
# use Cpanel::Config::LoadCpUserFile ();
# use Cpanel::Config::HasCpUserFile ();
# use Cpanel::PwCache ();
# use Cpanel::LoadModule ();
our $DATASTORE_MODULE = 'Cpanel::DataStore';
our $LOCALE_LEGACY_MODULE = 'Cpanel::Locale::Utils::Legacy';
my $inited_cpdata_user;
my $userlocale = {};
my $logger;
sub _logger {
require Cpanel::Logger;
return ( $logger ||= Cpanel::Logger->new() );
}
sub init_cpdata_keys {
my $user = shift || $Cpanel::user || $ENV{'REMOTE_USER'} || ( $> == 0 ? 'root' : ( Cpanel::PwCache::getpwuid($>) )[0] );
return if ( defined $inited_cpdata_user && $inited_cpdata_user eq $user );
if ( !$Cpanel::CPDATA{'LOCALE'} && $user ne 'root' ) {
require Cpanel::Server::Utils;
if ( Cpanel::Server::Utils::is_subprocess_of_cpsrvd() && ( $> && $user ne 'cpanel' && $user ne 'cpanellogin' && !-e "/var/cpanel/users/$user" ) ) {
_logger()->panic("get_handle() called before initcp()");
}
if ( $> == 0 || ( $> && $> == Cpanel::PwCache::getpwnam($user) ) ) {
$Cpanel::CPDATA{'LOCALE'} = get_user_locale($user);
}
}
return ( $inited_cpdata_user = $user );
}
sub clear_user_cache {
my ($user) = @_;
return delete $userlocale->{$user};
}
sub get_user_locale {
my $user = ( shift || $Cpanel::user || $ENV{'REMOTE_USER'} || ( $> == 0 ? 'root' : ( Cpanel::PwCache::getpwuid($>) )[0] ) );
my $cpuser_ref = shift; # not required, just faster if it is passed
if ( !$user ) {
require Cpanel::Locale;
return Cpanel::Locale::get_server_locale() || 'en';
}
return $userlocale->{$user} if exists $userlocale->{$user} && !shift;
if ( $Cpanel::user && $user eq $Cpanel::user && $Cpanel::CPDATA{'LOCALE'} ) {
return ( $userlocale->{$user} = $Cpanel::CPDATA{'LOCALE'} );
}
my $locale;
if ( $user eq 'root' ) {
my $root_conf_yaml = ( Cpanel::PwCache::getpwnam('root') )[7] . '/.cpanel_config';
if ( -e $root_conf_yaml ) {
Cpanel::LoadModule::load_perl_module($DATASTORE_MODULE);
my $hr = $DATASTORE_MODULE->can('fetch_ref')->($root_conf_yaml);
$locale = $hr->{'locale'};
}
}
elsif ( $user eq 'cpanel' ) {
require Cpanel::Locale;
$locale = Cpanel::Locale::get_locale_for_user_cpanel();
}
else {
if ( $cpuser_ref || ( Cpanel::Config::HasCpUserFile::has_readable_cpuser_file($user) && ( $cpuser_ref = Cpanel::Config::LoadCpUserFile::loadcpuserfile($user) ) ) ) {
if ( defined $cpuser_ref->{'LOCALE'} ) {
$locale = $cpuser_ref->{'LOCALE'};
}
elsif ( defined $cpuser_ref->{'LANG'} ) {
Cpanel::LoadModule::load_perl_module($LOCALE_LEGACY_MODULE);
$locale = $LOCALE_LEGACY_MODULE->can('map_any_old_style_to_new_style')->( $cpuser_ref->{'LANG'} );
}
}
}
if ( !$locale ) {
require Cpanel::Locale;
return $userlocale->{$user} = Cpanel::Locale::get_server_locale() || 'en';
}
$userlocale->{$user} = $locale;
return $userlocale->{$user};
}
1;
} # --- END Cpanel/Locale/Utils/User.pm
{ # --- BEGIN Cpanel/Cookies.pm
package Cpanel::Cookies;
$Cpanel::Cookies::VERSION = '0.1';
sub get_cookie_hashref_from_string {
return {} if !defined $_[0];
return {
map {
map {
s/%([a-fA-F0-9][a-fA-F0-9])/pack('C', hex($1))/eg if -1 != index( $_, '%' );
$_;
} split m<=>, $_, 2
} split( /; /, $_[0] )
};
}
my $http_cookie_cached;
sub get_cookie_hashref {
if ( !defined $http_cookie_cached ) {
$http_cookie_cached = get_cookie_hashref_from_string( $ENV{'HTTP_COOKIE'} );
}
return $http_cookie_cached;
}
sub get_cookie_hashref_recache {
$http_cookie_cached = get_cookie_hashref_from_string( $ENV{'HTTP_COOKIE'} );
return $http_cookie_cached;
}
1;
} # --- END Cpanel/Cookies.pm
{ # --- BEGIN Cpanel/SafeDir/Read.pm
package Cpanel::SafeDir::Read;
use strict;
use warnings;
sub read_dir {
my ( $dir, $coderef ) = @_;
my @contents;
if ( opendir my $dir_dh, $dir ) {
@contents = grep { $_ ne '.' && $_ ne '..' } readdir($dir_dh);
if ($coderef) {
@contents = grep { $coderef->($_) } @contents;
}
closedir $dir_dh;
return wantarray ? @contents : \@contents;
}
return;
}
1;
} # --- END Cpanel/SafeDir/Read.pm
{ # --- BEGIN Cpanel/Locale/Utils/Charmap.pm
package Cpanel::Locale::Utils::Charmap;
use strict;
use warnings;
my %CHARSET_ALIASES = ( # unpreferred preferred
"ASCII" => "US-ASCII",
"BIG5-ETEN" => "BIG5",
"CP1251" => "WINDOWS-1251",
"CP1252" => "WINDOWS-1252",
"CP936" => "GBK",
"CP949" => "KS_C_5601-1987",
"EUC-CN" => "GB2312",
"KS_C_5601" => "KS_C_5601-1987",
"SHIFTJIS" => "SHIFT_JIS",
"SHIFTJISX0213" => "SHIFT_JISX0213",
"UNICODE-1-1-UTF-7" => "UTF-7", # RFC 1642 (obs.)
"UTF8" => "UTF-8",
"UTF-8-STRICT" => "UTF-8", # Perl internal use
"HZ" => "HZ-GB-2312", # RFC 1842
"GSM0338" => "GSM03.38",
);
my @all_charmaps;
my @non_alias_charmaps;
my @filesystem_charmaps;
sub get_charmap_list {
my ( $root_says_to_make_symlinks, $no_aliases ) = @_;
if ($no_aliases) {
return @non_alias_charmaps if @non_alias_charmaps;
}
else {
return @all_charmaps if @all_charmaps;
}
if ( !@filesystem_charmaps ) {
@filesystem_charmaps = qw(utf-8 us-ascii);
my $charmapsdir = -e '/usr/local/share/i18n/charmaps' ? '/usr/local/share/i18n/charmaps' : '/usr/share/i18n/charmaps';
for my $key ( keys %CHARSET_ALIASES ) {
if ( $root_says_to_make_symlinks && $> == 0 ) {
lstat("$charmapsdir/$key.gz"); # The stat preceding -l _ wasn't an lstat at ...
if ( -e _ ) {
lstat("$charmapsdir/$CHARSET_ALIASES{$key}.gz"); # The stat preceding -l _ wasn't an lstat at ...
if ( !-e _ && !-l _ ) {
symlink( "$charmapsdir/$key.gz", "$charmapsdir/$CHARSET_ALIASES{$key}.gz" );
}
}
elsif ( !-l _ && -e "$charmapsdir/$CHARSET_ALIASES{$key}.gz" ) {
symlink( "$charmapsdir/$CHARSET_ALIASES{$key}.gz", "$charmapsdir/$key.gz" );
}
}
}
if ( opendir my $charmaps_dh, $charmapsdir ) {
@filesystem_charmaps = map { m{\A([^.].*)\.gz\z} ? $1 : () } readdir $charmaps_dh;
closedir $charmaps_dh;
}
}
my %charmaps;
my %excluded_charmaps = $no_aliases
? ( map { tr{A-Z}{a-z}; $_ => 1 } keys %CHARSET_ALIASES ) ## no critic qw(ProhibitMutatingListFunctions)
: ();
for my $cm ( @filesystem_charmaps, ( $no_aliases ? ( values %CHARSET_ALIASES ) : %CHARSET_ALIASES ) ) {
$cm =~ tr{A-Z}{a-z};
my $copy = $cm;
my $stripped = ( $copy =~ tr{_.-}{}d ); #prefer "utf-8" over "utf8"
if ( !exists( $excluded_charmaps{$cm} ) && ( !exists( $charmaps{$copy} ) || $stripped ) ) {
$charmaps{$copy} = $cm;
}
}
if ($no_aliases) {
return @non_alias_charmaps = values %charmaps;
}
else {
return @all_charmaps = values %charmaps;
}
}
1;
} # --- END Cpanel/Locale/Utils/Charmap.pm
{ # --- BEGIN Cpanel/StringFunc/Case.pm
package Cpanel::StringFunc::Case;
use strict;
use warnings;
our $VERSION = '1.2';
sub ToUpper {
return unless defined $_[0];
( local $_ = $_[0] ) =~ tr/a-z/A-Z/; # avoid altering $_[0] by making a copy
return $_;
}
sub ToLower {
return unless defined $_[0];
( local $_ = $_[0] ) =~ tr/A-Z/a-z/; # avoid altering $_[0] by making a copy
return $_;
}
1;
} # --- END Cpanel/StringFunc/Case.pm
{ # --- BEGIN Cpanel/Locale/Utils/Legacy.pm
package Cpanel::Locale::Utils::Legacy;
use strict;
use warnings;
# use Cpanel::Locale::Utils::Normalize ();
# use Cpanel::Locale::Utils::Paths ();
my %oldname_to_locale;
my $loc;
sub _load_oldnames {
%oldname_to_locale = (
'turkish' => 'tr',
'traditional-chinese' => 'zh',
'thai' => 'th',
'swedish' => 'sv',
'spanish-utf8' => 'es',
'spanish' => 'es',
'slovenian' => 'sl',
'simplified-chinese' => 'zh_cn',
'russian' => 'ru',
'romanian' => 'ro',
'portuguese-utf8' => 'pt',
'portuguese' => 'pt',
'polish' => 'pl',
'norwegian' => 'no',
'korean' => 'ko',
'japanese-shift_jis' => 'ja', # see Cpanel::Locale::Utils::MkDB::compile_single_legacy_from_legacy_system()
'japanese-euc-jp' => 'ja', # see Cpanel::Locale::Utils::MkDB::compile_single_legacy_from_legacy_system()
'japanese' => 'ja', # see Cpanel::Locale::Utils::MkDB::compile_single_legacy_from_legacy_system()
'spanish_latinamerica' => 'es_419',
'iberian_spanish' => 'es_es',
'italian' => 'it',
'indonesian' => 'id',
'hungarian' => 'hu',
'german-utf8' => 'de',
'german' => 'de',
'french-utf8' => 'fr',
'french' => 'fr',
'finnish' => 'fi',
'english-utf8' => 'en',
'english' => 'en',
'dutch-utf8' => 'nl',
'dutch' => 'nl',
'chinese' => 'zh',
'bulgarian' => 'bg',
'brazilian-portuguese-utf8' => 'pt_br',
'brazilian-portuguese' => 'pt_br',
'arabic' => 'ar',
);
{
no warnings 'redefine';
*_load_oldnames = sub { };
}
return;
}
sub get_legacy_to_locale_map {
_load_oldnames();
return \%oldname_to_locale;
}
sub get_legacy_list_from_locale {
my ($locale) = @_;
return if !$locale;
$locale = 'en' if $locale eq 'en_us' || $locale eq 'i_default';
_load_oldnames();
return grep { $oldname_to_locale{$_} eq $locale ? 1 : 0 } keys %oldname_to_locale;
}
sub get_best_guess_of_legacy_from_locale {
my ( $locale, $always_return_useable ) = @_;
return if !$locale && !$always_return_useable;
$locale = 'en' if $locale eq 'en_us' || $locale eq 'i_default';
_load_oldnames();
my @legacy_locale_matches = grep { $oldname_to_locale{$_} eq $locale ? 1 : 0 } keys %oldname_to_locale;
return $legacy_locale_matches[0] if @legacy_locale_matches;
return 'english' if $always_return_useable;
return;
}
sub get_legacy_name_list {
_load_oldnames();
return sort { $a =~ m/\.local$/ ? $a cmp $b : $b cmp $a } keys %oldname_to_locale;
}
sub get_existing_filesys_legacy_name_list {
require Cpanel::SafeDir::Read;
my %args = @_;
my @extras;
if ( exists $args{'also_look_in'} && ref $args{'also_look_in'} eq 'ARRAY' ) {
for my $path ( @{ $args{'also_look_in'} } ) {
my $copy = $path;
$copy =~ s/\/lang$//;
next if !-d "$copy/lang";
push @extras, Cpanel::SafeDir::Read::read_dir("$copy/lang");
}
}
my @local_less_names;
my %has_local;
my @names;
my $legacy_dir = Cpanel::Locale::Utils::Paths::get_legacy_lang_root();
for my $name ( grep { $_ !~ m/^\./ } ( $args{'no_root'} ? () : Cpanel::SafeDir::Read::read_dir($legacy_dir) ), @extras ) {
my $copy = $name;
if ( $copy =~ s/\.local$// ) {
$has_local{$copy}++;
}
else {
push @local_less_names, $copy;
}
}
for my $name_localless ( sort { $b cmp $a } @local_less_names ) {
push @names, exists $has_local{$name_localless} ? ( "$name_localless.local", $name_localless ) : $name_localless;
}
return @names;
}
sub get_legacy_root_in_locale_database_root {
return Cpanel::Locale::Utils::Paths::get_locale_database_root() . '/legacy';
}
sub get_legacy_file_cache_path {
my ($legacy_file) = @_;
$legacy_file .= 'cache';
my $legacy_dir = Cpanel::Locale::Utils::Paths::get_legacy_lang_root();
$legacy_file =~ s{$legacy_dir}{/var/cpanel/lang.cache};
return $legacy_file;
}
sub map_any_old_style_to_new_style {
return wantarray
? map { get_new_langtag_of_old_style_langname($_) || $_ } @_
: get_new_langtag_of_old_style_langname( $_[0] ) || $_[0];
}
my %charset_lookup;
sub _determine_via_disassemble {
my ( $lcl, $oldlang ) = @_;
my ( $language, $territory, $encoding, $probable_ext );
my @parts = split( /[^A-Za-z0-9]+/, $oldlang ); # We can't use Cpanel::CPAN::Locales::normalize_tag since it breaks things into 8 character chunks
return if @parts == 1; # we've already tried just $parts[0] if the split is only 1 item
return if @parts > 4; # if there are more than 4 parts then there is unresolveable data
if ( !ref($lcl) ) {
$lcl = Cpanel::CPAN::Locales->new($lcl) or return;
}
for my $part (@parts) {
my $found_part = 0;
if ( $lcl->get_code_from_language($part) || $lcl->get_language_from_code($part) ) {
if ($language) {
if ( !$lcl->get_territory_from_code($part) ) {
return;
}
}
else {
$found_part++;
$language = $lcl->get_language_from_code($part) ? $part : $lcl->get_code_from_language($part);
}
}
if ( !$found_part && ( $lcl->get_code_from_territory($part) || $lcl->get_territory_from_code($part) ) ) {
if ($territory) {
return;
}
else {
$found_part++;
$territory = $lcl->get_territory_from_code($part) ? $part : $lcl->get_code_from_territory($part);
}
}
if ( !$found_part ) {
if ( $part eq $parts[$#parts] ) { # && length($part) < $max_len_for_ext
$probable_ext = $part;
}
else {
if ( !%charset_lookup ) {
require Cpanel::Locale::Utils::Charmap;
@charset_lookup{ map { Cpanel::Locale::Utils::Normalize::normalize_tag($_) } Cpanel::Locale::Utils::Charmap::get_charmap_list() } = ();
}
if ( $charset_lookup{$part} ) {
$found_part++;
$encoding = $part;
}
else {
return;
}
}
}
}
if ($encoding) {
}
if ($probable_ext) {
}
if ($language) {
if ($territory) {
return "$language\_$territory";
}
else {
return $language;
}
}
return;
}
sub real_get_new_langtag_of_old_style_langname {
my ($oldlang) = @_;
$oldlang = Cpanel::StringFunc::Case::ToLower($oldlang) || ""; # case 34321 item #3
$oldlang =~ s/\.legacy_duplicate\..+$//; # This '.legacy_duplicate. naming hack' is for copying legacy file into a name that maps back to it's new target locale
if ( !defined $oldlang || $oldlang eq '' || $oldlang =~ m/^\s+$/ ) {
return; # return a value ?, what is safe ...
}
elsif ( Cpanel::Locale::Utils::Normalize::normalize_tag($oldlang) eq 'default' ) {
return; # return 'en' ? could be an incorrect assumption ...
}
elsif ( exists $oldname_to_locale{$oldlang} ) {
return $oldname_to_locale{$oldlang};
}
{
local $@;
$loc ||= Cpanel::CPAN::Locales->new('en') or die $@;
}
my $return;
if ( $loc->get_language_from_code($oldlang) ) {
$return = Cpanel::Locale::Utils::Normalize::normalize_tag($oldlang); # case 34321 item #4
}
else {
my $locale = $loc->get_code_from_language($oldlang);
if ($locale) {
$return = $locale; # case 34321 item #2
}
else {
$return = _determine_via_disassemble( $loc, $oldlang );
if ( !$return ) {
local $SIG{'__DIE__'}; # may be made moot by case 50857
for my $nen ( grep { $_ ne 'en' } sort( $loc->get_language_codes() ) ) {
my $loca = Cpanel::CPAN::Locales->new($nen) or next; # singleton
my $locale = $loca->get_code_from_language($oldlang);
if ($locale) {
$return = $locale; # case 34321 item #2
last;
}
else {
$return = _determine_via_disassemble( $loca, $oldlang );
last if $return;
}
}
}
}
}
if ( !$return ) {
$return = Cpanel::CPAN::Locales::get_i_tag_for_string($oldlang);
}
return $return;
}
sub get_new_langtag_of_old_style_langname {
_load_oldnames();
require Cpanel::StringFunc::Case;
require Cpanel::CPAN::Locales;
$loc = Cpanel::CPAN::Locales->new('en');
{
no warnings 'redefine';
*get_new_langtag_of_old_style_langname = \&real_get_new_langtag_of_old_style_langname;
}
goto &real_get_new_langtag_of_old_style_langname;
}
my $legacy_lookup;
sub phrase_is_legacy_key {
my ($key) = @_;
if ( !$legacy_lookup ) {
require 'Cpanel/Locale/Utils/MkDB.pm'; ## no critic qw(Bareword) - hide from perlpkg
$legacy_lookup = {
%{ Cpanel::Locale::Utils::MkDB::get_hash_of_legacy_file( Cpanel::Locale::Utils::Paths::get_legacy_lang_root() . '/english-utf8' ) || {} },
%{ Cpanel::Locale::Utils::MkDB::get_hash_of_legacy_file('/usr/local/cpanel/base/frontend/paper_lantern/lang/english-utf8') || {} },
};
}
return exists $legacy_lookup->{$key} ? 1 : 0;
}
sub fetch_legacy_lookup {
return $legacy_lookup if $legacy_lookup;
phrase_is_legacy_key(''); # ensure $legacy_lookup is loaded
return $legacy_lookup;
}
sub get_legacy_key_english_value {
my ($key) = @_;
if ( phrase_is_legacy_key($key) ) { # inits $legacy_lookup cache
return $legacy_lookup->{$key};
}
return;
}
1;
} # --- END Cpanel/Locale/Utils/Legacy.pm
{ # --- BEGIN Cpanel/Config/LoadCpUserFile/CurrentUser.pm
package Cpanel::Config::LoadCpUserFile::CurrentUser;
use strict;
use warnings;
# use Cpanel::Config::LoadCpUserFile ();
my $_cpuser_ref_singleton;
my $_cpuser_user;
sub load {
my ($user) = @_;
if ( $_cpuser_user && $_cpuser_user eq $user ) {
return $_cpuser_ref_singleton;
}
$_cpuser_user = $user;
return ( $_cpuser_ref_singleton = Cpanel::Config::LoadCpUserFile::load($user) );
}
1;
} # --- END Cpanel/Config/LoadCpUserFile/CurrentUser.pm
{ # --- BEGIN Cpanel/YAML/Syck.pm
package Cpanel::YAML::Syck;
use YAML::Syck ();
sub _init {
$YAML::Syck::LoadBlessed = 0;
{
no warnings 'redefine';
*Cpanel::YAML::Syck::_init = sub { };
}
return;
}
_init();
1;
} # --- END Cpanel/YAML/Syck.pm
{ # --- BEGIN Cpanel/ArrayFunc/Uniq.pm
package Cpanel::ArrayFunc::Uniq;
use strict;
use warnings;
sub uniq (@) { ## no critic qw(Subroutines::ProhibitSubroutinePrototypes)
if ( $INC{'List/Util.pm'} ) {
*uniq = *List::Util::uniq;
return List::Util::uniq(@_);
}
my %seen;
return grep { !$seen{$_}++ } @_;
}
1;
} # --- END Cpanel/ArrayFunc/Uniq.pm
{ # --- BEGIN Cpanel/PwUtils.pm
package Cpanel::PwUtils;
use strict;
use warnings;
# use Cpanel::Exception ();
# use Cpanel::PwCache ();
sub normalize_to_uid {
my ($user) = @_;
if ( !length $user ) {
die Cpanel::Exception::create( 'MissingParameter', 'Supply a username or a user ID.' );
}
return $user if $user !~ tr{0-9}{}c; # Only has numbers so its a uid
my $uid = Cpanel::PwCache::getpwnam_noshadow($user);
if ( !defined $uid ) {
die Cpanel::Exception::create( 'UserNotFound', [ name => $user ] );
}
return $uid;
}
1;
} # --- END Cpanel/PwUtils.pm
{ # --- BEGIN Cpanel/AccessIds/Normalize.pm
package Cpanel::AccessIds::Normalize;
use strict;
use warnings;
# use Cpanel::ArrayFunc::Uniq ();
# use Cpanel::PwCache ();
# use Cpanel::PwUtils ();
# use Cpanel::Exception ();
sub normalize_user_and_groups {
my ( $user, @groups ) = @_;
if ( ( scalar @groups == 1 && !defined $groups[0] ) || ( scalar @groups > 1 && scalar( grep { !defined } @groups ) ) ) {
require Cpanel::Carp; # no load module for memory
die Cpanel::Carp::safe_longmess("Undefined group passed to normalize_user_and_groups");
}
my $uid;
if ( defined $user && $user !~ tr{0-9}{}c ) {
if ( scalar @groups == 1 && $groups[0] !~ tr{0-9}{}c ) { # we already have a gid
return ( $user, $groups[0] );
}
$uid = $user;
if ( scalar @groups == 1 && $groups[0] !~ tr{0-9}{}c ) { # we already have a gid
return ( $uid, $groups[0] );
}
}
elsif ( !scalar @groups ) {
( $uid, @groups ) = ( Cpanel::PwCache::getpwnam_noshadow($user) )[ 2, 3 ];
if ( !defined $uid ) {
die Cpanel::Exception::create( 'UserNotFound', [ name => $user ] );
}
return ( $uid, @groups );
}
else {
$uid = Cpanel::PwUtils::normalize_to_uid($user);
}
my @gids =
@groups
? ( map { !tr{0-9}{}c ? $_ : scalar( ( getgrnam $_ )[2] ) } @groups )
: ( ( Cpanel::PwCache::getpwuid_noshadow($uid) )[3] );
if ( scalar @gids > 2 ) {
return ( $uid, Cpanel::ArrayFunc::Uniq::uniq(@gids) );
}
elsif ( scalar @gids == 2 && $gids[0] eq $gids[1] ) {
return ( $uid, $gids[0] );
}
return ( $uid, @gids );
}
sub normalize_code_user_groups {
my @args = @_;
my $code_index;
for my $i ( 0 .. $#args ) {
if ( ref $args[$i] eq 'CODE' ) {
$code_index = $i;
last;
}
}
die "No coderef found!" if !defined $code_index;
my $code = splice( @args, $code_index, 1 );
return ( $code, normalize_user_and_groups( grep { defined } @args ) );
}
1;
} # --- END Cpanel/AccessIds/Normalize.pm
{ # --- BEGIN Cpanel/AccessIds/Utils.pm
package Cpanel::AccessIds::Utils;
use strict;
use warnings;
# use Cpanel::ArrayFunc::Uniq ();
# use Cpanel::Debug ();
sub normalize_user_and_groups {
require Cpanel::AccessIds::Normalize;
goto \&Cpanel::AccessIds::Normalize::normalize_user_and_groups;
}
sub normalize_code_user_groups {
require Cpanel::AccessIds::Normalize;
goto \&Cpanel::AccessIds::Normalize::normalize_code_user_groups;
}
sub set_egid {
my @gids = @_;
if ( !@gids ) {
Cpanel::Debug::log_die("No arguments passed to set_egid()!");
}
if ( scalar @gids > 1 ) {
@gids = Cpanel::ArrayFunc::Uniq::uniq(@gids);
}
_check_positive_int($_) for @gids;
my $new_egid = join( q{ }, $gids[0], @gids );
return _set_var( \$), 'EGID', $new_egid );
}
sub set_rgid {
my ( $gid, @extra_gids ) = @_;
if (@extra_gids) {
Cpanel::Debug::log_die("RGID can only be set to a single value! (Do you want set_egid()?)");
}
_check_positive_int($gid);
return _set_var( \$(, 'RGID', $gid );
}
sub set_euid {
my ($uid) = @_;
_check_positive_int($uid);
return _set_var( \$>, 'EUID', $uid );
}
sub set_ruid {
my ($uid) = @_;
_check_positive_int($uid);
return _set_var( \$<, 'RUID', $uid );
}
sub _check_positive_int {
if ( !length $_[0] || $_[0] =~ tr{0-9}{}c ) {
Cpanel::Debug::log_die("“$_[0] is not a positive integer!");
}
return 1;
}
sub _set_var {
my ( $var_r, $name, $desired_value ) = @_;
my $old_value = $$var_r;
$$var_r = $desired_value;
return $desired_value eq $$var_r ? 1 : validate_var_set(
$name, # The name of the value like 'RUID'
$desired_value, # The value we wanted it to be set to
$$var_r, # Deferenced variable being set, ex $<
$old_value # The value before we set it.
);
}
sub validate_var_set {
my ( $name, $desired_value, $new_value, $old_value ) = @_;
my $error;
if ( $new_value =~ tr/ // ) {
my ( $desired_first, @desired_parts ) = split( ' ', $desired_value );
my ( $new_first, @new_parts ) = split( ' ', $new_value );
if ( $new_first != $desired_first ) {
$error = 1;
}
elsif ( @desired_parts && @new_parts ) {
if ( scalar @desired_parts == 1 && scalar @new_parts == 1 ) {
if ( $new_parts[0] != $desired_parts[0] ) {
$error = 1;
}
}
else {
@desired_parts = sort { $a <=> $b } Cpanel::ArrayFunc::Uniq::uniq(@desired_parts);
@new_parts = sort { $a <=> $b } Cpanel::ArrayFunc::Uniq::uniq(@new_parts);
for my $i ( 0 .. $#desired_parts ) {
if ( $new_parts[$i] != $desired_parts[$i] ) {
$error = 1;
last;
}
}
}
}
}
else {
if ( $new_value != $desired_value ) {
$error = 1;
}
}
return 1 if !$error;
if ( defined $old_value ) {
Cpanel::Debug::log_die("Failed to change $name from “$old_value” to “$desired_value”: $!");
}
Cpanel::Debug::log_die("Failed to change $name to “$desired_value”: $!");
return 0; #not reached
}
1;
} # --- END Cpanel/AccessIds/Utils.pm
{ # --- BEGIN Cpanel/AccessIds/ReducedPrivileges.pm
package Cpanel::AccessIds::ReducedPrivileges;
use strict;
use warnings;
# use Cpanel::Debug ();
# use Cpanel::AccessIds::Utils ();
# use Cpanel::AccessIds::Normalize ();
our $PRIVS_REDUCED = 0;
sub new { ## no critic qw(Subroutines::RequireArgUnpacking)
my $class = shift;
if ( $class ne __PACKAGE__ ) {
Cpanel::Debug::log_die("Attempting to drop privileges as '$class'.");
}
my ( $uid, @gids ) = Cpanel::AccessIds::Normalize::normalize_user_and_groups(@_);
_allowed_to_reduce_privileges();
_prevent_dropping_to_root( $uid, @gids );
my $self = {
'uid' => $>,
'gid' => $),
'new_uid' => $uid,
'new_gid' => join( q< >, @gids ),
};
_reduce_privileges( $uid, @gids );
$PRIVS_REDUCED = 1;
return bless $self;
}
sub DESTROY {
my ($self) = @_;
_allowed_to_restore_privileges( $self->{'new_uid'}, $self->{'new_gid'} );
return _restore_privileges( $self->{'uid'}, $self->{'gid'} );
}
sub call_as_user { ## no critic qw(Subroutines::RequireArgUnpacking)
my ( $code, $uid, $gid, @supplemental_gids ) = Cpanel::AccessIds::Normalize::normalize_code_user_groups(@_);
_prevent_dropping_to_root( $uid, $gid );
if ( !$code ) {
Cpanel::Debug::log_die("No code reference supplied.");
}
_allowed_to_reduce_privileges();
my ( $saved_uid, $saved_gid ) = ( $>, $) );
_reduce_privileges( $uid, $gid, @supplemental_gids );
local $PRIVS_REDUCED = 1;
my ( $scalar, @list );
if (wantarray) { #list context
@list = eval { $code->(); };
}
elsif ( defined wantarray ) { #scalar context
$scalar = eval { $code->(); };
}
else { #void context
eval { $code->(); };
}
my $ex = $@;
_restore_privileges( $saved_uid, $saved_gid );
die $ex if $ex;
return wantarray ? @list : $scalar;
}
sub _allowed_to_reduce_privileges {
if ( $< != 0 ) {
Cpanel::Debug::log_die("Attempting to drop privileges as a normal user with RUID $<");
}
if ( $> != 0 ) {
Cpanel::Debug::log_die("Attempting to drop privileges as a normal user with EUID $>");
}
return 1;
}
sub _reduce_privileges {
my ( $uid, $gid, @supplemental_gids ) = @_;
Cpanel::AccessIds::Utils::set_egid( $gid, @supplemental_gids );
Cpanel::AccessIds::Utils::set_euid($uid);
return 1;
}
sub _prevent_dropping_to_root {
if ( grep { !$_ } @_ ) {
Cpanel::Debug::log_die("Attempting to drop privileges to root.");
}
return 1;
}
sub _allowed_to_restore_privileges {
my ( $uid, $gid ) = @_;
if ( $< != 0 ) {
Cpanel::Debug::log_die("Attempting to restore privileges as a normal user with RUID $<");
}
if ( $> != $uid ) {
Cpanel::Debug::log_warn("EUID ($>) does not match expected reduced user ($uid)");
}
my ( $first_egid, $first_given_gid ) = ( $), $gid );
$_ = ( split m{ } )[0] for ( $first_egid, $first_given_gid );
if ( int $first_egid != int $first_given_gid ) {
Cpanel::Debug::log_warn("EGID ($)) does not match expected reduced user ($gid)");
}
}
sub _restore_privileges {
my ( $saved_uid, $saved_gid ) = @_;
Cpanel::AccessIds::Utils::set_euid($saved_uid);
Cpanel::AccessIds::Utils::set_egid( split m{ }, $saved_gid );
$PRIVS_REDUCED = 0;
return 1;
}
1;
} # --- END Cpanel/AccessIds/ReducedPrivileges.pm
{ # --- BEGIN Cpanel/DataStore.pm
package Cpanel::DataStore;
use strict;
use warnings;
# use Cpanel::Debug ();
sub store_ref {
my ( $file, $outof_ref, $perm ) = @_;
require Cpanel::YAML::Syck;
$YAML::Syck::ImplicitTyping = 0;
local $YAML::Syck::SingleQuote;
local $YAML::Syck::SortKeys;
$YAML::Syck::SingleQuote = 1;
$YAML::Syck::SortKeys = 1;
if ( ref($file) ) {
my $yaml_string = YAML::Syck::Dump($outof_ref);
print( {$file} _format($yaml_string) ) || return;
return $file;
}
if ( ref($perm) eq 'ARRAY' && !-l $file && !-e $file ) {
require Cpanel::FileUtils::TouchFile; # or use() ?
my $touch_chmod = sub {
if ( !Cpanel::FileUtils::TouchFile::touchfile($file) ) {
Cpanel::Debug::log_info("Could not touch \xE2\x80\x9C$file\xE2\x80\x9D: $!");
return;
}
if ( $perm->[0] ) {
if ( !chmod( oct( $perm->[0] ), $file ) ) {
Cpanel::Debug::log_info("Could not chmod \xE2\x80\x9C$file\xE2\x80\x9D to \xE2\x80\x9C$perm->[0]\xE2\x80\x9D: $!");
return;
}
}
return 1;
};
if ( $> == 0 && $perm->[1] && $perm->[1] ne 'root' ) {
require Cpanel::AccessIds::ReducedPrivileges; # or use() ?
Cpanel::AccessIds::ReducedPrivileges::call_as_user( $perm->[1], $touch_chmod ) || return;
}
else {
$touch_chmod->() || return;
}
}
if ( open my $yaml_out, '>', $file ) {
my $yaml_string = YAML::Syck::Dump($outof_ref);
print {$yaml_out} _format($yaml_string);
close $yaml_out;
return 1;
}
else {
Cpanel::Debug::log_warn("Could not open file '$file' for writing: $!");
return;
}
}
sub fetch_ref {
my ( $file, $is_array ) = @_;
my $fetch_ref = load_ref($file);
my $data_type = ref $fetch_ref;
my $data = $data_type ? $fetch_ref : undef;
$data_type ||= 'UNDEF';
if ( $is_array && $data_type ne 'ARRAY' ) {
return [];
}
elsif ( !$is_array && $data_type ne 'HASH' ) {
return {};
}
return $data;
}
sub load_ref {
my ( $file, $into_ref ) = @_;
return if ( !-e $file || -z _ );
require Cpanel::YAML::Syck;
$YAML::Syck::ImplicitTyping = 0;
my $struct;
if ( ref($file) ) {
local $!;
$struct = eval {
local $/;
local $SIG{__WARN__};
local $SIG{__DIE__};
( YAML::Syck::Load(<$file>) )[0];
};
Cpanel::Debug::log_warn("Error loading YAML data: $!") if ( !$struct );
}
elsif ( open my $yaml_in, '<', $file ) {
local $!;
$struct = eval {
local $/;
local $SIG{__WARN__};
local $SIG{__DIE__};
( YAML::Syck::Load(<$yaml_in>) )[0];
};
Cpanel::Debug::log_warn("Error loading YAML data: $!") if ( !$struct );
close $yaml_in;
}
else {
my $err = $!;
Cpanel::Debug::log_warn("Could not open file '$file' for reading: $err");
return;
}
if ( !$struct ) {
Cpanel::Debug::log_warn("Failed to load YAML data from file $file");
return;
}
if ( defined $into_ref ) {
my $type = ref $into_ref;
my $yaml_type = ref $struct;
if ( $yaml_type ne $type ) {
Cpanel::Debug::log_warn("Invalid data type from file $file! YAML type $yaml_type does not match expected type $type. Data ignored!");
return; # if we want an empty ref on failure use fetch_ref()
}
if ( $yaml_type eq 'HASH' ) {
%{$into_ref} = %{$struct};
}
elsif ( $yaml_type eq 'ARRAY' ) {
@{$into_ref} = @{$struct};
}
else {
Cpanel::Debug::log_warn("YAML in '$file' is not a hash or array reference");
return; # if we want an empty ref on failure use fetch_ref()
}
return $into_ref;
}
return $struct;
}
sub edit_datastore {
my ( $file, $editor_cr, $is_array ) = @_;
if ( ref $editor_cr ne 'CODE' ) {
Cpanel::Debug::log_warn('second arg needs to be a coderef');
return;
}
my $ref = $is_array ? [] : {};
if ( !-e $file ) {
Cpanel::Debug::log_info("Data store file $file does not exist. Attempting to create empty datastore.");
store_ref( $file, $ref );
}
if ( load_ref( $file, $ref ) ) {
if ( $editor_cr->($ref) ) {
if ( !store_ref( $file, $ref ) ) {
Cpanel::Debug::log_warn("Modifications to file $file could not be saved");
return;
}
}
}
else {
Cpanel::Debug::log_warn("Could not load datastore $file");
return;
}
return 1;
}
sub _format {
my ($s) = @_;
$s =~ s/[ \t]+$//mg;
return __grapheme_to_character($s);
}
sub __grapheme_to_character {
my ($yaml_string) = @_;
$yaml_string = quotemeta($yaml_string);
$yaml_string =~ s/\\{2}x/\\x/g;
$yaml_string = eval qq{"$yaml_string"};
return $yaml_string;
}
1;
} # --- END Cpanel/DataStore.pm
{ # --- BEGIN Cpanel/StringFunc/Trim.pm
package Cpanel::StringFunc::Trim;
use strict;
use warnings;
$Cpanel::StringFunc::Trim::VERSION = '1.02';
my %ws_chars = ( "\r" => undef, "\n" => undef, " " => undef, "\t" => undef, "\f" => undef );
sub trim {
my ( $str, $totrim ) = @_;
$str = rtrim( ltrim( $str, $totrim ), $totrim );
return $str;
}
sub ltrim {
my ( $str, $totrim ) = @_;
$str =~ s/^$totrim*//;
return $str;
}
sub rtrim {
my ( $str, $totrim ) = @_;
$str =~ s/$totrim*$//;
return $str;
}
sub endtrim {
my ( $str, $totrim ) = @_;
if ( substr( $str, ( length($totrim) * -1 ), length($totrim) ) eq $totrim ) {
return substr( $str, 0, ( length($str) - length($totrim) ) );
}
return $str;
}
sub begintrim {
my ( $str, $totrim ) = @_;
if (
defined $str && defined $totrim # .
&& substr( $str, 0, length($totrim) ) eq $totrim
) {
return substr( $str, length($totrim) );
}
return $str;
}
sub ws_trim {
my ($this) = @_;
return unless defined $this;
my $fix = ref $this eq 'SCALAR' ? $this : \$this;
return unless defined $$fix;
if ( $$fix =~ tr{\r\n \t\f}{} ) {
${$fix} =~ s/^\s+// if exists $ws_chars{ substr( $$fix, 0, 1 ) };
${$fix} =~ s/\s+$// if exists $ws_chars{ substr( $$fix, -1, 1 ) };
}
return ${$fix};
}
sub ws_trim_array {
my $ar = ref $_[0] eq 'ARRAY' ? $_[0] : [@_]; # [@_] :: copy @_ w/ out unpack first: !! not \@_ in this case !!
foreach my $idx ( 0 .. scalar( @{$ar} ) - 1 ) {
$ar->[$idx] = ws_trim( $ar->[$idx] );
}
return wantarray ? @{$ar} : $ar;
}
sub ws_trim_hash_values {
my $hr = ref $_[0] eq 'HASH' ? $_[0] : {@_}; # {@_} :: copy @_ w/ out unpack first:
foreach my $key ( keys %{$hr} ) {
$hr->{$key} = ws_trim( $hr->{$key} );
}
return wantarray ? %{$hr} : $hr;
}
1;
} # --- END Cpanel/StringFunc/Trim.pm
{ # --- BEGIN Cpanel/Locale/Utils/3rdparty.pm
package Cpanel::Locale::Utils::3rdparty;
%Cpanel::Locale::Utils::3rdparty::cpanel_provided = (
'ar' => 1,
'cs' => 1,
'da' => 1,
'de' => 1,
'el' => 1,
'en' => 1,
'es' => 1,
'es_419' => 1,
'es_es' => 1,
'fi' => 1,
'fil' => 1,
'fr' => 1,
'he' => 1,
'hu' => 1,
'i_cpanel_snowmen' => 1,
'id' => 1,
'it' => 1,
'ja' => 1,
'ko' => 1,
'ms' => 1,
'nb' => 1,
'nl' => 1,
'pl' => 1,
'pt' => 1,
'pt_br' => 1,
'ro' => 1,
'ru' => 1,
'sv' => 1,
'th' => 1,
'tr' => 1,
'uk' => 1,
'vi' => 1,
'zh' => 1,
'zh_tw' => 1,
);
my %locale_to_3rdparty;
sub _load_3rdparty {
return if (%locale_to_3rdparty);
%locale_to_3rdparty = (
'ar' => {
'analog' => 'us',
'awstats' => 'ar',
'webalizer' => 'english'
},
'bg' => {
'analog' => 'bg',
'awstats' => 'bg',
'webalizer' => 'english'
},
'bn' => {
'analog' => 'us',
'awstats' => 'en',
'webalizer' => 'english'
},
'de' => {
'analog' => 'de',
'awstats' => 'de',
'webalizer' => 'german'
},
'en' => {
'analog' => 'us',
'awstats' => 'en',
'webalizer' => 'english'
},
'es' => {
'analog' => 'es',
'awstats' => 'es',
'webalizer' => 'spanish'
},
'fi' => {
'analog' => 'fi',
'awstats' => 'fi',
'webalizer' => 'finnish'
},
'fr' => {
'analog' => 'fr',
'awstats' => 'fr',
'webalizer' => 'french'
},
'hi' => {
'analog' => 'us',
'awstats' => 'en',
'webalizer' => 'english'
},
'hu' => {
'analog' => 'hu',
'awstats' => 'hu',
'webalizer' => 'hungarian'
},
'id' => {
'analog' => 'us',
'awstats' => 'id',
'webalizer' => 'indonesian'
},
'it' => {
'analog' => 'it',
'awstats' => 'it',
'webalizer' => 'italian'
},
'ja' => {
'analog' => 'jpu', # appears to be the UTF-8 one
'awstats' => 'jp',
'webalizer' => 'japanese'
},
'ko' => {
'analog' => 'us',
'awstats' => 'ko',
'webalizer' => 'korean'
},
'nl' => {
'analog' => 'nl',
'awstats' => 'nl',
'webalizer' => 'dutch'
},
'no' => {
'analog' => 'no',
'awstats' => 'en',
'webalizer' => 'norwegian'
},
'pl' => {
'analog' => 'pl',
'awstats' => 'pl',
'webalizer' => 'polish'
},
'pt' => {
'analog' => 'pt',
'awstats' => 'pt',
'webalizer' => 'portuguese'
},
'pt_br' => {
'analog' => 'pt',
'awstats' => 'pt',
'webalizer' => 'portuguese_brazil'
},
'ro' => {
'analog' => 'ro',
'awstats' => 'ro',
'webalizer' => 'romanian'
},
'ru' => {
'analog' => 'ru',
'awstats' => 'ru',
'webalizer' => 'russian'
},
'sl' => {
'analog' => 'us',
'awstats' => 'en',
'webalizer' => 'slovene'
},
'sv' => {
'analog' => 'us',
'awstats' => 'en',
'webalizer' => 'swedish'
},
'th' => {
'analog' => 'us',
'awstats' => 'th',
'webalizer' => 'english'
},
'tr' => {
'analog' => 'tr',
'awstats' => 'tr',
'webalizer' => 'turkish'
},
'zh' => {
'analog' => 'cn', # the cn.lng does not say what it is so this is an assumption based on other pervasive bad practices
'awstats' => 'cn',
'webalizer' => 'chinese'
},
'zh_cn' => {
'analog' => 'cn', # the cn.lng does not say what it is so this is an assumption based on other pervasive bad practices
'awstats' => 'cn',
'webalizer' => 'simplified_chinese'
},
);
}
sub get_known_3rdparty_lang {
my ( $locale, $_3rdparty ) = @_;
_load_3rdparty();
my $locale_tag = ref $locale ? $locale->get_language_tag() : $locale;
$locale_tag = 'en' if $locale_tag eq 'en_us' || $locale_tag eq 'i_default';
return if !exists $locale_to_3rdparty{$locale_tag};
return if !exists $locale_to_3rdparty{$locale_tag}{$_3rdparty};
return $locale_to_3rdparty{$locale_tag}{$_3rdparty};
}
my %locale_lookup_cache;
sub get_3rdparty_lang {
my ( $locale, $_3rdparty ) = @_;
my $known = get_known_3rdparty_lang( $locale, $_3rdparty );
return $known if $known;
return if !ref($locale) && $locale =~ m/(?:\.\.|\/)/;
return if $_3rdparty =~ m/(?:\.\.|\/)/;
my $locale_tag = ref $locale ? $locale->get_language_tag() : $locale;
$locale_tag = 'en' if $locale_tag eq 'en_us' || $locale_tag eq 'i_default';
if ( exists $locale_lookup_cache{$_3rdparty} ) {
return $locale_lookup_cache{$_3rdparty}{$locale_tag} if exists $locale_lookup_cache{$_3rdparty}{$locale_tag};
return;
}
require Cpanel::DataStore;
my $hr = Cpanel::DataStore::fetch_ref("/var/cpanel/locale/3rdparty/apps/$_3rdparty.yaml");
my %seen;
%{ $locale_lookup_cache{$_3rdparty} } = map { ++$seen{ $hr->{$_} } == 1 ? ( $hr->{$_} => $_ ) : () } keys %{$hr};
return $locale_lookup_cache{$_3rdparty}{$locale_tag} if exists $locale_lookup_cache{$_3rdparty}{$locale_tag};
return;
}
my @list;
sub get_3rdparty_list {
return @list if @list;
@list = qw(analog awstats webalizer);
if ( -d "/var/cpanel/locale/3rdparty/apps" ) {
require Cpanel::SafeDir::Read;
push @list, sort map { my $f = $_; $f =~ s/\.yaml$// ? ($f) : () } Cpanel::SafeDir::Read::read_dir("/var/cpanel/locale/3rdparty/apps");
}
return @list;
}
my %opt_cache;
sub get_app_options {
my ($_3rdparty) = @_;
return if $_3rdparty =~ m/(?:\.\.|\/)/;
return $opt_cache{$_3rdparty} if exists $opt_cache{$_3rdparty};
if ( $_3rdparty eq 'analog' || $_3rdparty eq 'awstats' || $_3rdparty eq 'webalizer' ) {
_load_3rdparty();
my %seen;
$opt_cache{$_3rdparty} = [ sort map { ++$seen{ $locale_to_3rdparty{$_}{$_3rdparty} } == 1 ? ( $locale_to_3rdparty{$_}{$_3rdparty} ) : () } keys %locale_to_3rdparty ];
}
else {
require Cpanel::DataStore;
my $hr = Cpanel::DataStore::fetch_ref("/var/cpanel/locale/3rdparty/apps/$_3rdparty.yaml");
$opt_cache{$_3rdparty} = [ sort keys %{$hr} ];
}
return $opt_cache{$_3rdparty};
}
sub get_app_setting {
my ( $locale, $_3rdparty ) = @_;
return if !ref($locale) && $locale =~ m/(?:\.\.|\/)/;
return if $_3rdparty =~ m/(?:\.\.|\/)/;
require Cpanel::LoadFile;
require Cpanel::StringFunc::Trim;
my $locale_tag = ref $locale ? $locale->get_language_tag() : $locale;
$locale_tag = 'en' if $locale_tag eq 'en_us' || $locale_tag eq 'i_default';
my $setting = Cpanel::StringFunc::Trim::ws_trim( Cpanel::LoadFile::loadfile("/var/cpanel/locale/3rdparty/conf/$locale_tag/$_3rdparty") );
if ( $_3rdparty eq 'analog' && $setting eq 'en' ) {
$setting = 'us';
}
return $setting;
}
sub set_app_setting {
my ( $locale, $_3rdparty, $setting ) = @_;
return if !ref($locale) && $locale =~ m/(?:\.\.|\/)/;
return if $_3rdparty =~ m/(?:\.\.|\/)/;
require Cpanel::SafeDir::MK;
require Cpanel::FileUtils::Write;
my $locale_tag = ref $locale ? $locale->get_language_tag() : $locale;
$locale_tag = 'en' if $locale_tag eq 'en_us' || $locale_tag eq 'i_default';
Cpanel::SafeDir::MK::safemkdir("/var/cpanel/locale/3rdparty/conf/$locale_tag/");
Cpanel::FileUtils::Write::overwrite_no_exceptions( "/var/cpanel/locale/3rdparty/conf/$locale_tag/$_3rdparty", $setting, 0644 );
return;
}
1;
} # --- END Cpanel/Locale/Utils/3rdparty.pm
{ # --- BEGIN Cpanel/JS/Variations.pm
package Cpanel::JS::Variations;
use strict;
sub lex_filename_for {
my ( $filename, $locale ) = @_;
return if !$filename || !$locale;
return get_base_file( $filename, "-${locale}.js" );
}
sub get_base_file {
my ( $filename, $replace_extension ) = @_;
return if !$filename;
$replace_extension //= '.js';
$filename =~ s{/js2-min/}{/js2/};
$filename =~ s{(?:[\.\-]min|_optimized)?\.js$}{$replace_extension};
return $filename;
}
1;
} # --- END Cpanel/JS/Variations.pm
{ # --- BEGIN Cpanel/Locale/Utils/Display.pm
package Cpanel::Locale::Utils::Display;
# use Cpanel::Locale::Utils::Paths ();
sub get_locale_list {
my ($lh) = @_;
return @{ $lh->{'_cached_get_locale_list'} ||= [ sort ( 'en', $lh->list_available_locales() ) ] };
}
sub get_non_existent_locale_list {
my ( $lh, $loc_obj ) = @_;
$loc_obj ||= $lh->get_locales_obj('en');
my %have;
@have{ get_locale_list($lh), 'en_us', 'i_default', 'und', 'zxx', 'mul', 'mis', 'art' } = ();
return sort grep { !exists $have{$_} } $loc_obj->get_language_codes();
}
sub get_locale_menu_hashref {
my ( $lh, $omit_current_locale, $native_only, $skip_locales ) = @_;
$skip_locales ||= {};
my %langs;
my %dir;
my @langs = get_locale_list($lh);
my @wanted_langs = grep { !$skip_locales->{$_} } @langs;
if ( !@wanted_langs ) {
return ( {}, \@langs, {} );
}
my $func = $native_only ? 'lang_names_hashref_native_only' : 'lang_names_hashref';
my ( $localized_name_for_tag, $native_name_for_tag, $direction_map ) = $lh->$func(@wanted_langs);
my $current_tag = $lh->get_language_tag();
$current_tag = 'en' if $current_tag eq 'en_us' || $current_tag eq 'i_default';
my $i_locales_path = Cpanel::Locale::Utils::Paths::get_i_locales_config_path();
if ($omit_current_locale) {
delete $localized_name_for_tag->{$current_tag};
delete $native_name_for_tag->{$current_tag};
@langs = grep { $_ ne $current_tag } @langs;
}
foreach my $tag ( keys %{$localized_name_for_tag} ) {
if ( index( $tag, 'i_' ) == 0 ) {
require Cpanel::DataStore;
my $i_conf = Cpanel::DataStore::fetch_ref("$i_locales_path/$tag.yaml");
$langs{$tag} = exists $i_conf->{'display_name'} && defined $i_conf->{'display_name'} && $i_conf->{'display_name'} ne '' ? "$i_conf->{'display_name'} - $tag" : $tag; # slightly different format than real tags to visually indicate specialness
$native_name_for_tag->{$tag} = $langs{$tag};
if ( exists $i_conf->{'character_orientation'} ) {
$dir{$tag} = $lh->get_html_dir_attr( $i_conf->{'character_orientation'} );
}
elsif ( exists $i_conf->{'fallback_locale'} && exists $direction_map->{ $i_conf->{'fallback_locale'} } ) {
$dir{$tag} = $direction_map->{ $i_conf->{'fallback_locale'} };
}
next;
}
if ( exists $direction_map->{$tag} ) {
$dir{$tag} = $lh->get_html_dir_attr( $direction_map->{$tag} );
}
next if $native_only;
if ( $native_name_for_tag->{$tag} eq $localized_name_for_tag->{$tag} ) {
if ( $tag eq $current_tag ) {
$langs{$tag} = $native_name_for_tag->{$tag};
}
else {
$langs{$tag} = "$localized_name_for_tag->{$tag} ($tag)";
}
}
else {
$langs{$tag} = "$localized_name_for_tag->{$tag} ($native_name_for_tag->{$tag})";
}
}
if ($native_only) {
return wantarray ? ( $native_name_for_tag, \@langs, \%dir ) : $native_name_for_tag;
}
return wantarray ? ( \%langs, \@langs, \%dir ) : \%langs;
}
sub get_non_existent_locale_menu_hashref {
my $lh = shift;
$lh->{'Locales.pm'}{'_main_'} ||= $lh->get_locales_obj();
my %langs;
my %dir;
my @langs = get_non_existent_locale_list( $lh, $lh->{'Locales.pm'}{'_main_'} );
my $wantarray = wantarray() ? 1 : 0;
for my $code (@langs) {
if ($wantarray) {
if ( my $orient = $lh->{'Locales.pm'}{'_main_'}->get_character_orientation_from_code_fast($code) ) {
$dir{$code} = $lh->get_html_dir_attr($orient);
}
}
my $current = $lh->{'Locales.pm'}{'_main_'}->get_language_from_code( $code, 1 );
my $native = $lh->{'Locales.pm'}{'_main_'}->get_native_language_from_code( $code, 1 );
$langs{$code} = $current eq $native ? "$current ($code)" : "$current ($native)";
}
return wantarray ? ( \%langs, \@langs, \%dir ) : \%langs;
}
sub in_translation_vetting_mode {
return -e '/var/cpanel/translation_vetting_mode' ? 1 : 0;
}
1;
} # --- END Cpanel/Locale/Utils/Display.pm
{ # --- BEGIN Cpanel/Locale/Utils/Api1.pm
package Cpanel::Locale::Utils::Api1;
use strict;
use warnings;
# use Cpanel::Locale ();
my $_lh;
sub _api1_maketext { ## no critic qw(Subroutines::RequireArgUnpacking) ## no extract maketext
$_lh ||= Cpanel::Locale->get_handle();
$_[0] =~ s{\\'}{'}g;
my $localized_str = $_lh->makevar(@_);
if ($Cpanel::Parser::Vars::embtag) { # PPI NO PARSE -- module will already be there is we care about it
require Cpanel::Encoder::Tiny;
$localized_str = Cpanel::Encoder::Tiny::safe_html_encode_str($localized_str);
}
elsif ($Cpanel::Parser::Vars::javascript) { # PPI NO PARSE -- module will already be there is we care about it
$localized_str =~ s/"/\\"/g;
$localized_str =~ s/'/\\'/g;
}
return {
status => 1,
statusmsg => $localized_str,
};
}
1;
} # --- END Cpanel/Locale/Utils/Api1.pm
{ # --- BEGIN Cpanel/StatCache.pm
package Cpanel::StatCache;
use strict;
use warnings;
our $VERSION = 0.4;
my %STATCACHE;
our $USE_LSTAT = 0;
sub StatCache_init { }
sub cachedmtime {
return (
exists $STATCACHE{ $_[0] } ? $STATCACHE{ $_[0] }->[0]
: (
$STATCACHE{ $_[0] } = (
$USE_LSTAT && -l $_[0] ? [ ( lstat(_) )[ 9, 7, 10 ] ]
: -e $_[0] ? [ ( stat(_) )[ 9, 7, 10 ] ]
: [ 0, 0, 0 ]
)
)->[0]
);
}
sub cachedmtime_size {
return (
exists $STATCACHE{ $_[0] } ? @{ $STATCACHE{ $_[0] } }[ 0, 1 ]
: @{
(
$STATCACHE{ $_[0] } = (
$USE_LSTAT && -l $_[0] ? [ ( lstat(_) )[ 9, 7, 10 ] ]
: -e $_[0] ? [ ( stat(_) )[ 9, 7, 10 ] ]
: [ 0, 0, 0 ]
)
)
}[ 0, 1 ]
);
}
sub cachedmtime_ctime {
return (
exists $STATCACHE{ $_[0] } ? @{ $STATCACHE{ $_[0] } }[ 0, 2 ]
: @{
(
$STATCACHE{ $_[0] } = (
$USE_LSTAT && -l $_[0] ? [ ( lstat(_) )[ 9, 7, 10 ] ]
: -e $_[0] ? [ ( stat(_) )[ 9, 7, 10 ] ]
: [ 0, 0, 0 ]
)
)
}[ 0, 2 ]
);
}
sub clearcache {
%STATCACHE = ();
return 1;
}
1;
} # --- END Cpanel/StatCache.pm
{ # --- BEGIN Cpanel/CachedCommand/Utils.pm
package Cpanel::CachedCommand::Utils;
my ( $cached_datastore_myuid, $cached_datastore_dir );
sub destroy {
my %OPTS = @_;
my $cache_file = _get_datastore_filename( $OPTS{'name'}, ( $OPTS{'args'} ? @{ $OPTS{'args'} } : () ) );
if ( -e $cache_file ) {
return unlink $cache_file;
}
else {
return 1;
}
return;
}
*get_datastore_filename = *_get_datastore_filename;
sub _get_datastore_filename {
my ( $bin, @args ) = @_;
my $file = join( '_', $bin, @args );
$file =~ tr{/}{_};
$file = each %{ { $file => undef } }; #detaint
my $datastore_dir = _get_datastore_dir();
$datastore_dir = each %{ { $datastore_dir => undef } }; #detaint
return $datastore_dir . '/' . $file;
}
sub _get_datastore_dir {
my $myuid = $>;
if ( defined $cached_datastore_dir && length $cached_datastore_dir > 1 && $myuid == $cached_datastore_myuid ) {
return $cached_datastore_dir;
}
if ( $myuid != 0 && defined $Cpanel::homedir && $Cpanel::homedir ) { #issafe
$cached_datastore_dir = each %{ { $Cpanel::homedir => undef } }; #detaint
}
else {
require Cpanel::PwCache;
my $homedir = Cpanel::PwCache::gethomedir();
$cached_datastore_dir = each %{ { $homedir => undef } }; #detaint
}
if ( !-e $cached_datastore_dir . '/.cpanel/datastore' && $cached_datastore_dir ne '/' ) { # nobody's homedir is /
if ( !-e $cached_datastore_dir . '/.cpanel' ) {
mkdir $cached_datastore_dir . '/.cpanel', 0700 or warn "Failed to mkdir($cached_datastore_dir/.cpanel): $!";
}
mkdir $cached_datastore_dir . '/.cpanel/datastore', 0700 or warn "Failed to mkdir($cached_datastore_dir/.cpanel/datastore): $!";
}
$cached_datastore_myuid = $myuid;
$cached_datastore_dir .= '/.cpanel/datastore';
return $cached_datastore_dir;
}
sub invalidate_cache {
my $ds_file = get_datastore_filename(@_);
unlink $ds_file;
return $ds_file;
}
sub clearcache {
$cached_datastore_dir = undef;
$cached_datastore_myuid = undef;
return;
}
1;
} # --- END Cpanel/CachedCommand/Utils.pm
{ # --- BEGIN Cpanel/CachedCommand/Valid.pm
package Cpanel::CachedCommand::Valid;
use strict;
use warnings;
# use Cpanel::StatCache ();
# use Cpanel::Debug ();
sub is_cache_valid { ## no critic qw(Subroutines::ProhibitExcessComplexity) -- needs to be refactored
my %OPTS = @_;
my ( $datastore_file, $datastore_file_mtime, $datastore_file_size, $binary, $ttl, $mtime, $min_expire_time, $now ) = ( ( $OPTS{'datastore_file'} || '' ), ( $OPTS{'datastore_file_mtime'} || 0 ), ( $OPTS{'datastore_file_size'} || 0 ), ( $OPTS{'binary'} || '' ), ( $OPTS{'ttl'} || 0 ), ( $OPTS{'mtime'} || 0 ), ( $OPTS{'min_expire_time'} || 0 ), ( $OPTS{'now'} || 0 ) );
if ( !$datastore_file_mtime && !-e $datastore_file ) {
print STDERR "is_cache_valid: rejecting $datastore_file because it does not exist.\n" if $Cpanel::Debug::level;
return 0;
}
if ( !$datastore_file_size || !$datastore_file_mtime ) {
( $datastore_file_size, $datastore_file_mtime ) = ( stat(_) )[ 7, 9 ];
}
if ( $datastore_file_mtime <= 0 ) {
print STDERR "is_cache_valid: rejecting $datastore_file as mtime is zero.\n" if $Cpanel::Debug::level;
return 0;
}
if ($binary) {
if ( substr( $binary, 0, 1 ) ne '/' ) {
require Cpanel::FindBin;
$binary = Cpanel::FindBin::findbin( $binary, split( /:/, $ENV{'PATH'} ) );
}
my ( $binary_mtime, $binary_ctime ) = Cpanel::StatCache::cachedmtime_ctime($binary);
if ( ( $binary_mtime && $binary_mtime > $datastore_file_mtime ) || ( $binary_ctime && $binary_ctime > $datastore_file_mtime ) ) {
if ($Cpanel::Debug::level) {
print STDERR "is_cache_valid: rejecting $datastore_file as binary ($binary) ctime or mtime is newer.\n";
print STDERR "is_cache_valid: datastore_file:$datastore_file mtime[$datastore_file_mtime]\n";
print STDERR "is_cache_valid: binary_file:$binary mtime[$binary_mtime] ctime[$binary_ctime]\n";
}
return 0;
}
}
$now ||= time();
if ( $datastore_file_mtime > $now ) {
print STDERR "is_cache_valid: rejecting $datastore_file as it is from the future (time warp safety).\n" if $Cpanel::Debug::level;
return 0;
}
elsif ( $min_expire_time && $datastore_file_mtime > ( $now - $min_expire_time ) ) {
print STDERR "is_cache_valid: accept $datastore_file (mtime=$datastore_file_mtime) as min_expire_time ($now - $min_expire_time) is older.\n" if $Cpanel::Debug::level;
return 1;
}
elsif ( $mtime > $datastore_file_mtime ) {
print STDERR "is_cache_valid: rejecting $datastore_file because mtime ($mtime) is newer then datastore mtime ($datastore_file_mtime).\n" if $Cpanel::Debug::level;
return 0;
}
elsif ( $ttl && ( $datastore_file_mtime + $ttl ) < $now ) {
print STDERR "is_cache_valid: rejecting $datastore_file as it has reached its time to live.\n" if $Cpanel::Debug::level;
return 0;
}
print STDERR "is_cache_valid: accepting $datastore_file as it passes all tests.\n" if $Cpanel::Debug::level;
return 1;
}
1;
} # --- END Cpanel/CachedCommand/Valid.pm
{ # --- BEGIN Cpanel/CachedCommand/Save.pm
package Cpanel::CachedCommand::Save;
use strict;
use warnings;
# use Cpanel::CachedCommand::Utils ();
# use Cpanel::FileUtils::Write ();
# use Cpanel::Debug ();
# use Cpanel::Exception ();
use Try::Tiny;
sub _savefile {
my ( $filename, $content ) = @_;
return if !defined $content; #should be able to store 0
$filename =~ tr{/}{}s; # collapse //s to /
my @path = split( /\//, $filename );
my $file = pop(@path);
my $dir = join( '/', @path );
my $dir_uid = ( stat($dir) )[4];
if ( !defined $dir_uid ) {
Cpanel::Debug::log_warn("Unable to write datastore file: $filename: target directory: $dir does not exist.");
return;
}
elsif ( $dir_uid != $> ) {
Cpanel::Debug::log_warn("Unable to write datastore file: $filename: target directory: $dir does not match uid $>");
return;
}
local $!;
my $ret;
try {
$ret = Cpanel::FileUtils::Write::overwrite( $filename, ( ref $content ? $$content : $content ), 0600 );
}
catch {
my $err = $_;
Cpanel::Debug::log_warn( Cpanel::Exception::get_string($err) );
};
return $ret;
}
sub store {
my %OPTS = @_;
_savefile( Cpanel::CachedCommand::Utils::_get_datastore_filename( $OPTS{'name'} ), $OPTS{'data'} );
}
1;
} # --- END Cpanel/CachedCommand/Save.pm
{ # --- BEGIN Cpanel/Context.pm
package Cpanel::Context;
use strict;
use warnings;
# use Cpanel::Exception ();
sub must_be_list {
return 1 if ( caller(1) )[5]; # 5 = wantarray
my $msg = ( caller(1) )[3]; # 3 = subroutine
$msg .= $_[0] if defined $_[0];
return _die_context( 'list', $msg );
}
sub must_not_be_scalar {
my ($message) = @_;
my $wa = ( caller(1) )[5]; # 5 = wantarray
if ( !$wa && defined $wa ) {
_die_context( 'list or void', $message );
}
return 1;
}
sub must_not_be_void {
return if defined( ( caller 1 )[5] );
return _die_context('scalar or list');
}
sub _die_context {
my ( $context, $message ) = @_;
local $Carp::CarpInternal{__PACKAGE__} if $INC{'Carp.pm'};
my $to_throw = length $message ? "Must be $context context ($message)!" : "Must be $context context!";
die Cpanel::Exception::create_raw( 'ContextError', $to_throw );
}
1;
} # --- END Cpanel/Context.pm
{ # --- BEGIN Cpanel/LocaleString.pm
package Cpanel::LocaleString;
use strict;
use warnings;
sub DESTROY { }
sub new {
if ( !length $_[1] ) {
die 'Must include at least a string!';
}
return bless \@_, shift;
}
sub set_json_to_freeze {
no warnings 'redefine';
*TO_JSON = \&_to_list_ref;
return ( __PACKAGE__ . '::_JSON_MODE' )->new();
}
sub thaw {
if ( ref( $_[1] ) ne 'ARRAY' ) {
die "Call thaw() on an ARRAY reference, not “$_[1]”!";
}
return $_[0]->new( @{ $_[1] }[ 1 .. $#{ $_[1] } ] );
}
sub is_frozen {
{
last if ref( $_[1] ) ne 'ARRAY';
last if !$_[1][0]->isa( $_[0] );
last if @{ $_[1] } < 2;
return 1;
}
return 0;
}
sub to_string {
return _locale()->makevar( @{ $_[0] } );
}
sub to_en_string {
return _locale()->makethis_base( @{ $_[0] } );
}
sub clone_with_args {
return ( ref $_[0] )->new(
$_[0][0], #the phrase, currently stored in the object
@_[ 1 .. $#_ ], #the new args, supplied by the caller
);
}
sub to_list {
if ( !wantarray ) {
require Cpanel::Context;
Cpanel::Context::must_be_list();
}
return @{ $_[0] };
}
*TO_JSON = \&to_string;
my $_locale;
sub _locale {
return $_locale if $_locale;
local $@;
eval 'require Cpanel::Locale;' or do { ## no critic qw(BuiltinFunctions::ProhibitStringyEval)
warn "Failed to load Cpanel::Locale; falling back to substitute. Error was: $@";
};
eval { $_locale = Cpanel::Locale->get_handle() };
return $_locale || bless {}, 'Cpanel::LocaleString::_Cpanel_Locale_unavailable';
}
sub _put_back {
no warnings 'redefine';
*TO_JSON = \&to_string;
return;
}
sub _to_list_ref {
return [ ref( $_[0] ), @{ $_[0] } ];
}
package Cpanel::LocaleString::_JSON_MODE;
sub new {
require Cpanel::Finally; # PPI USE OK - loaded only when needed
return $_[0]->SUPER::new( \&Cpanel::LocaleString::_put_back );
}
package Cpanel::LocaleString::_Cpanel_Locale_unavailable;
BEGIN {
*Cpanel::LocaleString::_Cpanel_Locale_unavailable::makethis_base = *Cpanel::LocaleString::_Cpanel_Locale_unavailable::makevar;
}
sub makevar {
my ( $self, $str, @maketext_opts ) = @_;
local ( $@, $! );
require Cpanel::Locale::Utils::Fallback;
return Cpanel::Locale::Utils::Fallback::interpolate_variables( $str, @maketext_opts );
}
1;
} # --- END Cpanel/LocaleString.pm
{ # --- BEGIN Cpanel/Errno.pm
package Cpanel::Errno;
use strict;
my %_err_name_cache;
sub get_name_for_errno_number {
my ($number) = @_;
if ( !$INC{'Errno.pm'} ) {
local ( $@, $! );
require Errno;
}
die 'need number!' if !length $number;
if ( !%_err_name_cache ) {
my $s = scalar keys %Errno::; # init iterator
foreach my $k ( sort keys %Errno:: ) {
if ( Errno->EXISTS($k) ) {
my $v = 'Errno'->can($k)->();
$_err_name_cache{$v} = $k;
}
}
}
return $_err_name_cache{$number};
}
1;
} # --- END Cpanel/Errno.pm
{ # --- BEGIN Cpanel/Config/Constants/Perl.pm
package Cpanel::Config::Constants::Perl;
use strict;
our $ABRT = 6;
our $ALRM = 14;
our $BUS = 7;
our $CHLD = 17;
our $CLD = 17;
our $CONT = 18;
our $FPE = 8;
our $HUP = 1;
our $ILL = 4;
our $INT = 2;
our $IO = 29;
our $IOT = 6;
our $KILL = 9;
our $NUM32 = 32;
our $NUM33 = 33;
our $NUM35 = 35;
our $NUM36 = 36;
our $NUM37 = 37;
our $NUM38 = 38;
our $NUM39 = 39;
our $NUM40 = 40;
our $NUM41 = 41;
our $NUM42 = 42;
our $NUM43 = 43;
our $NUM44 = 44;
our $NUM45 = 45;
our $NUM46 = 46;
our $NUM47 = 47;
our $NUM48 = 48;
our $NUM49 = 49;
our $NUM50 = 50;
our $NUM51 = 51;
our $NUM52 = 52;
our $NUM53 = 53;
our $NUM54 = 54;
our $NUM55 = 55;
our $NUM56 = 56;
our $NUM57 = 57;
our $NUM58 = 58;
our $NUM59 = 59;
our $NUM60 = 60;
our $NUM61 = 61;
our $NUM62 = 62;
our $NUM63 = 63;
our $PIPE = 13;
our $POLL = 29;
our $PROF = 27;
our $PWR = 30;
our $QUIT = 3;
our $RTMAX = 64;
our $RTMIN = 34;
our $SEGV = 11;
our $STKFLT = 16;
our $STOP = 19;
our $SYS = 31;
our $TERM = 15;
our $TRAP = 5;
our $TSTP = 20;
our $TTIN = 21;
our $TTOU = 22;
our $UNUSED = 31;
our $URG = 23;
our $USR1 = 10;
our $USR2 = 12;
our $VTALRM = 26;
our $WINCH = 28;
our $XCPU = 24;
our $XFSZ = 25;
our $ZERO = 0;
our %SIGNAL_NAME = qw(
0 ZERO
1 HUP
10 USR1
11 SEGV
12 USR2
13 PIPE
14 ALRM
15 TERM
16 STKFLT
17 CHLD
18 CONT
19 STOP
2 INT
20 TSTP
21 TTIN
22 TTOU
23 URG
24 XCPU
25 XFSZ
26 VTALRM
27 PROF
28 WINCH
29 IO
3 QUIT
30 PWR
31 SYS
32 NUM32
33 NUM33
34 RTMIN
35 NUM35
36 NUM36
37 NUM37
38 NUM38
39 NUM39
4 ILL
40 NUM40
41 NUM41
42 NUM42
43 NUM43
44 NUM44
45 NUM45
46 NUM46
47 NUM47
48 NUM48
49 NUM49
5 TRAP
50 NUM50
51 NUM51
52 NUM52
53 NUM53
54 NUM54
55 NUM55
56 NUM56
57 NUM57
58 NUM58
59 NUM59
6 ABRT
60 NUM60
61 NUM61
62 NUM62
63 NUM63
64 RTMAX
7 BUS
8 FPE
9 KILL
);
1;
} # --- END Cpanel/Config/Constants/Perl.pm
{ # --- BEGIN Cpanel/ChildErrorStringifier.pm
package Cpanel::ChildErrorStringifier;
use strict;
# use Cpanel::LocaleString ();
# use Cpanel::Exception ();
sub new {
my ( $class, $CHILD_ERROR, $PROGRAM_NAME ) = @_;
return bless { _CHILD_ERROR => $CHILD_ERROR, _PROGRAM_NAME => $PROGRAM_NAME }, $class;
}
sub CHILD_ERROR {
my ($self) = @_;
return $self->{'_CHILD_ERROR'};
}
sub error_code {
my ($self) = @_;
return undef if !$self->CHILD_ERROR();
return $self->CHILD_ERROR() >> 8;
}
sub error_name {
my ($self) = @_;
my $error_number = $self->error_code();
return '' if ( !defined $error_number ); # Can't index a hash with undef
require Cpanel::Errno;
return Cpanel::Errno::get_name_for_errno_number($error_number) || q<>;
}
sub dumped_core {
my ($self) = @_;
return $self->CHILD_ERROR() && ( $self->CHILD_ERROR() & 128 ) ? 1 : 0;
}
sub signal_code {
my ($self) = @_;
return if !$self->CHILD_ERROR();
return $self->CHILD_ERROR() & 127;
}
sub signal_name {
my ($self) = @_;
require Cpanel::Config::Constants::Perl;
return $Cpanel::Config::Constants::Perl::SIGNAL_NAME{ $self->signal_code() };
}
sub exec_failed {
return $_[0]->{'_exec_failed'} ? 1 : 0;
}
sub program {
my ($self) = @_;
return $self->{'_PROGRAM_NAME'} || undef;
}
sub set_program {
my ( $self, $program ) = @_;
return ( $self->{'_PROGRAM_NAME'} = $program );
}
sub autopsy {
my ($self) = @_;
return undef if !$self->CHILD_ERROR();
my @localized_strings = (
$self->error_code() ? $self->_ERROR_PHRASE() : $self->_SIGNAL_PHRASE(),
$self->_core_dump_for_phrase_if_needed(),
$self->_additional_phrases_for_autopsy(),
);
return join ' ', map { $_->to_string() } @localized_strings;
}
sub terse_autopsy {
my ($self) = @_;
my $str;
if ( $self->signal_code() ) {
$str .= 'SIG' . $self->signal_name() . " (#" . $self->signal_code() . ")";
}
elsif ( my $code = $self->error_code() ) {
$str .= "exit $code";
}
else {
$str = 'OK';
}
if ( $self->dumped_core() ) {
$str .= ' (+core)';
}
return $str;
}
sub die_if_error {
my ($self) = @_;
if ( $self->signal_code() ) {
die Cpanel::Exception::create(
'ProcessFailed::Signal',
[
process_name => $self->program(),
signal_code => $self->signal_code(),
$self->_extra_error_args_for_die_if_error(),
],
);
}
if ( $self->error_code() ) {
die Cpanel::Exception::create(
'ProcessFailed::Error',
[
process_name => $self->program(),
error_code => $self->error_code(),
$self->_extra_error_args_for_die_if_error(),
],
);
}
return $self;
}
sub _extra_error_args_for_die_if_error { }
sub _additional_phrases_for_autopsy { }
sub _core_dump_for_phrase_if_needed {
my ($self) = @_;
if ( $self->dumped_core() ) {
return Cpanel::LocaleString->new('The process dumped a core file.');
}
return;
}
sub _ERROR_PHRASE {
my ($self) = @_;
if ( $self->program() ) {
return Cpanel::LocaleString->new( 'The subprocess “[_1]” reported error number [numf,_2] when it ended.', $self->program(), $self->error_code() );
}
return Cpanel::LocaleString->new( 'The subprocess reported error number [numf,_1] when it ended.', $self->error_code() );
}
sub _SIGNAL_PHRASE {
my ($self) = @_;
if ( $self->program() ) {
return Cpanel::LocaleString->new( 'The subprocess “[_1]” ended prematurely because it received the “[_2]” ([_3]) signal.', $self->program(), $self->signal_name(), $self->signal_code() );
}
return Cpanel::LocaleString->new( 'The subprocess ended prematurely because it received the “[_1]” ([_2]) signal.', $self->signal_name(), $self->signal_code() );
}
1;
} # --- END Cpanel/ChildErrorStringifier.pm
{ # --- BEGIN Cpanel/FHUtils/OS.pm
package Cpanel::FHUtils::OS;
use strict;
use warnings;
my $fileno;
sub is_os_filehandle {
local $@;
$fileno = eval { fileno $_[0] };
return ( defined $fileno ) && ( $fileno != -1 );
}
1;
} # --- END Cpanel/FHUtils/OS.pm
{ # --- BEGIN Cpanel/FHUtils/Blocking.pm
package Cpanel::FHUtils::Blocking;
use strict;
use warnings;
# use Cpanel::Fcntl::Constants ();
# use Cpanel::Autodie qw(fcntl);
INIT { Cpanel::Autodie->import(qw{fcntl}); }
sub set_non_blocking {
return Cpanel::Autodie::fcntl( $_[0], $Cpanel::Fcntl::Constants::F_SETFL, _get_fl_flags( $_[0] ) | $Cpanel::Fcntl::Constants::O_NONBLOCK ) && 1;
}
sub set_blocking {
return Cpanel::Autodie::fcntl( $_[0], $Cpanel::Fcntl::Constants::F_SETFL, _get_fl_flags( $_[0] ) & ~$Cpanel::Fcntl::Constants::O_NONBLOCK ) && 1;
}
sub is_set_to_block {
return !( _get_fl_flags( $_[0] ) & $Cpanel::Fcntl::Constants::O_NONBLOCK ) ? 1 : 0;
}
sub _get_fl_flags {
return int Cpanel::Autodie::fcntl( $_[0], $Cpanel::Fcntl::Constants::F_GETFL, 0 );
}
1;
} # --- END Cpanel/FHUtils/Blocking.pm
{ # --- BEGIN Cpanel/IO/Flush.pm
package Cpanel::IO::Flush;
use strict;
use warnings;
use constant {
_EAGAIN => 11,
_EINTR => 4,
};
# use Cpanel::Exception ();
use IO::SigGuard ();
sub write_all { ##no critic qw( RequireArgUnpacking )
my ( $fh, $timeout ) = @_; # $_[2] = payload
local ( $!, $^E );
my $offset = 0;
{
my $this_time = IO::SigGuard::syswrite( $fh, $_[2], length( $_[2] ), $offset );
if ($this_time) {
$offset += $this_time;
}
elsif ( $! == _EAGAIN() ) {
_wait_until_ready( $fh, $timeout );
}
else {
die Cpanel::Exception::create( 'IO::WriteError', [ error => $!, length => length( $_[2] ) - $offset ] );
}
redo if $offset < length( $_[2] );
}
return;
}
sub _wait_until_ready {
my ( $fh, $timeout ) = @_;
my $win;
vec( $win, fileno($fh), 1 ) = 1;
my $ready = select( undef, my $wout = $win, undef, $timeout );
if ( $ready == -1 ) {
redo if $! == _EINTR();
die Cpanel::Exception::create( 'IO::SelectError', [ error => $! ] );
}
elsif ( !$ready ) {
die Cpanel::Exception::create_raw( 'Timeout', 'write timeout!' );
}
return;
}
1;
} # --- END Cpanel/IO/Flush.pm
{ # --- BEGIN Cpanel/ReadMultipleFH.pm
package Cpanel::ReadMultipleFH;
use strict;
use warnings;
# use Cpanel::FHUtils::Blocking ();
# use Cpanel::FHUtils::OS ();
# use Cpanel::IO::Flush ();
# use Cpanel::LoadFile::ReadFast ();
my $CHUNK_SIZE = 2 << 16;
my $DEFAULT_TIMEOUT = 600; #10 minutes
my $DEFAULT_READ_TIMEOUT = 0;
sub new { ## no critic qw(Subroutines::ProhibitExcessComplexity)
my ( $class, %opts ) = @_;
my %fh_buffer;
my %output;
my @fhs = @{ $opts{'filehandles'} };
my $read_input = '';
my $read_output = '';
my %fhmap;
my %is_os_filehandle;
for my $fh_buf_ar (@fhs) {
if ( UNIVERSAL::isa( $fh_buf_ar, 'GLOB' ) ) {
$fh_buf_ar = [$fh_buf_ar];
}
elsif ( !UNIVERSAL::isa( $fh_buf_ar, 'ARRAY' ) ) {
die 'items in “filehandles” must be either a filehandle or ARRAY';
}
my $fh = $fh_buf_ar->[0];
Cpanel::FHUtils::Blocking::set_non_blocking($fh);
$fhmap{ fileno($fh) } = $fh;
vec( $read_input, fileno($fh), 1 ) = 1;
if ( defined $fh_buf_ar->[1] && UNIVERSAL::isa( $fh_buf_ar->[1], 'SCALAR' ) ) {
$fh_buffer{$fh} = $fh_buf_ar->[1];
}
else {
my $buf = q{};
$fh_buffer{$fh} = \$buf;
if ( defined $fh_buf_ar->[1] && UNIVERSAL::isa( $fh_buf_ar->[1], 'GLOB' ) ) {
$output{$fh} = $fh_buf_ar->[1];
$is_os_filehandle{$fh} = Cpanel::FHUtils::OS::is_os_filehandle( $fh_buf_ar->[1] );
}
elsif ( defined $fh_buf_ar->[1] ) {
die '2nd value in “filehandles” array member must be undef, SCALAR, or GLOB!';
}
}
}
my $finished;
my $self = {
_fh_buffer => \%fh_buffer,
_finished => 0,
};
bless $self, $class;
my ( $nfound, $select_time_left, $select_timeout );
my $overall_timeout = defined $opts{'timeout'} ? $opts{'timeout'} : $DEFAULT_TIMEOUT;
my $read_timeout = defined $opts{'read_timeout'} ? $opts{'read_timeout'} : $DEFAULT_READ_TIMEOUT;
my $has_overall_timeout = $overall_timeout ? 1 : 0;
my $overall_time_left = $overall_timeout || undef;
READ_LOOP:
while (
!$finished && # has not finished
( !$has_overall_timeout || $overall_time_left > 0 ) # has not reached overall timeout
) {
$select_timeout = _get_shortest_timeout( $overall_time_left, $read_timeout );
( $nfound, $select_time_left ) = select( $read_output = $read_input, undef, undef, $select_timeout );
if ( !$nfound ) {
$self->{'_timed_out'} = ( $select_timeout == $read_timeout ) ? $read_timeout : $overall_timeout;
last;
}
elsif ( $nfound != -1 ) { # case 47309: If we get -1 it probably means we got interrupted by a signal
for my $fileno ( grep { vec( $read_output, $_, 1 ) } keys %fhmap ) {
my $fh = $fhmap{$fileno};
Cpanel::LoadFile::ReadFast::read_fast( $fh, ${ $fh_buffer{$fh} }, $CHUNK_SIZE, length ${ $fh_buffer{$fh} } ) or do {
delete $fhmap{$fileno};
$finished = !( scalar keys %fhmap );
last READ_LOOP if $finished;
vec( $read_input, $fileno, 1 ) = 0;
next;
};
if ( $output{$fh} ) {
my $payload_sr = \substr( ${ $fh_buffer{$fh} }, 0, length ${ $fh_buffer{$fh} }, q<> );
if ( $is_os_filehandle{$fh} ) {
Cpanel::IO::Flush::write_all( $output{$fh}, $read_timeout, $$payload_sr );
}
else {
print { $output{$fh} } $$payload_sr;
}
}
}
}
$overall_time_left -= ( $select_timeout - $select_time_left ) if $has_overall_timeout;
}
delete $fh_buffer{$_} for keys %output;
%fhmap = ();
$self->{'_finished'} = $finished;
if ( !$finished && defined $overall_time_left && $overall_time_left <= 0 ) {
$self->{'_timed_out'} = $overall_timeout;
}
return $self;
}
sub _get_shortest_timeout {
my ( $overall_time_left, $read_timeout ) = @_;
return undef if ( !$overall_time_left && !$read_timeout );
return $read_timeout if !defined $overall_time_left;
return ( !$read_timeout || $overall_time_left <= $read_timeout )
?
$overall_time_left
:
$read_timeout;
}
sub get_buffer {
return $_[0]->{'_fh_buffer'}{ $_[1] };
}
sub did_finish {
return $_[0]->{'_finished'} ? 1 : 0;
}
sub timed_out {
return defined $_[0]->{'_timed_out'} ? $_[0]->{'_timed_out'} : 0;
}
1;
} # --- END Cpanel/ReadMultipleFH.pm
{ # --- BEGIN Cpanel/ForkAsync.pm
package Cpanel::ForkAsync;
use strict;
use warnings;
# use Cpanel::Exception ();
my $DEFAULT_ERROR_CODE = 127; #EKEYEXPIRED
our $quiet = 0;
our $no_warn = 0;
sub do_in_child {
my ( $code, @args ) = @_;
local ( $!, $^E );
my $pid = fork();
die Cpanel::Exception::create( 'IO::ForkError', [ error => $! ] ) if !defined $pid;
if ( !$pid ) {
local $@;
if ( !eval { $code->(@args); 1 } ) {
my $err = $@;
my $io_err = 0 + $!;
_print($err) unless $quiet;
exit( $io_err || $DEFAULT_ERROR_CODE );
}
exit 0;
}
return $pid;
}
sub do_in_child_quiet {
my ( $code, @args ) = @_;
local $quiet = 1;
return do_in_child( $code, @args );
}
sub _print {
my ($msg) = @_;
warn $msg unless $no_warn;
print STDERR $msg;
return;
}
1;
} # --- END Cpanel/ForkAsync.pm
{ # --- BEGIN Cpanel/SafeRun/Object.pm
package Cpanel::SafeRun::Object;
use strict;
use warnings;
# use Cpanel::ChildErrorStringifier();
our @ISA;
BEGIN { push @ISA, qw(Cpanel::ChildErrorStringifier); }
BEGIN {
eval { require Proc::FastSpawn; };
}
use IO::SigGuard ();
# use Cpanel::Env ();
# use Cpanel::Exception ();
# use Cpanel::FHUtils::Autoflush ();
# use Cpanel::FHUtils::OS ();
# use Cpanel::ReadMultipleFH ();
# use Cpanel::LoadModule ();
# use Cpanel::LocaleString ();
use constant _ENOENT => 2;
my $CHUNK_SIZE = 2 << 16;
my $DEFAULT_TIMEOUT = 3600; # 1 hour
my $DEFAULT_READ_TIMEOUT = 0;
our $SAFEKILL_TIMEOUT = 1;
my @_allowed_env_vars_cache;
sub new { ## no critic qw(Subroutines::ProhibitExcessComplexity)
my ( $class, %OPTS ) = @_;
die "No “program”!" if !length $OPTS{'program'};
if ( !defined $OPTS{'timeout'} ) {
$OPTS{'timeout'} = $DEFAULT_TIMEOUT;
}
if ( !defined $OPTS{'read_timeout'} ) {
$OPTS{'read_timeout'} = $DEFAULT_READ_TIMEOUT;
}
if ( $OPTS{'program'} =~ tr{><*?[]`$()|;&#$\\\r\n\t }{} && !-e $OPTS{'program'} ) {
die Cpanel::Exception::create( 'InvalidParameter', 'A value of “[_1]” is invalid for “[_2]” as it does not permit the following characters: “[_3]”', [ $OPTS{'program'}, 'program', '><*?[]`$()|;&#$\\\\\r\\n\\t' ] );
}
my $args_ar = $OPTS{'args'} || [];
die "“args” must be an arrayref" if defined $args_ar && ref $args_ar ne 'ARRAY';
die "Undefined value given as argument! (@$args_ar)" if grep { !defined } @$args_ar;
my $pump_stdin_filehandle_into_child;
my ( %parent_read_fh, %child_write_fh );
my $merge_output_yn = $OPTS{'stdout'} && $OPTS{'stderr'} && ( $OPTS{'stdout'} eq $OPTS{'stderr'} );
local $!;
for my $handle_name (qw(stdout stderr)) {
my $custom_fh = $OPTS{$handle_name} && UNIVERSAL::isa( $OPTS{$handle_name}, 'GLOB' ) && $OPTS{$handle_name};
my $dupe_filehandle_will_work = $custom_fh && !tied(*$custom_fh) && ( fileno($custom_fh) > -1 );
if ( !$custom_fh && $OPTS{$handle_name} ) {
die "“$handle_name” must be a filehandle or undef, not $OPTS{$handle_name}";
}
if ($dupe_filehandle_will_work) {
if ( fileno($custom_fh) < 3 ) {
open my $copy, '>&', $custom_fh or die "dup($handle_name): $!";
$child_write_fh{$handle_name} = $copy;
}
else {
$child_write_fh{$handle_name} = $custom_fh;
}
}
elsif ( $merge_output_yn && $handle_name eq 'stderr' ) {
$parent_read_fh{'stderr'} = $parent_read_fh{'stdout'};
$child_write_fh{'stderr'} = $child_write_fh{'stdout'};
}
else {
pipe $parent_read_fh{$handle_name}, $child_write_fh{$handle_name} or die Cpanel::Exception::create( 'IO::PipeError', [ error => $! ] );
}
}
my ( $child_reads, $parent_writes );
my $close_child_reads = 0;
if ( !defined $OPTS{'stdin'} || !length $OPTS{'stdin'} ) {
open $child_reads, '<', '/dev/null' or die "open(<, /dev/null) failed: $!";
$close_child_reads = 1;
}
elsif ( UNIVERSAL::isa( $OPTS{'stdin'}, 'GLOB' ) ) {
my $fileno = fileno $OPTS{'stdin'};
if ( !defined $fileno || $fileno == -1 ) {
$pump_stdin_filehandle_into_child = 1;
}
else {
$child_reads = $OPTS{'stdin'};
}
}
if ( !$child_reads ) {
$close_child_reads = 1;
pipe( $child_reads, $parent_writes ) or die "pipe() failed: $!";
}
my $self = bless {
_program => $OPTS{'program'},
_args => $OPTS{'args'} || [],
}, $class;
local $SIG{'CHLD'} = 'DEFAULT';
my $exec_failed_message = "exec($OPTS{'program'}) failed:";
my $used_fastspawn = 0;
if (
$INC{'Proc/FastSpawn.pm'} # may not be available yet due to upcp.static or updatenow.static
&& !$OPTS{'before_exec'}
&& !$Cpanel::AccessIds::ReducedPrivileges::PRIVS_REDUCED # PPI NO PARSE - We not ever be set if its not loaded
) {
$used_fastspawn = 1;
my @env;
if ( !$OPTS{'keep_env'} ) {
if ( !@_allowed_env_vars_cache ) {
@_allowed_env_vars_cache = ( split( m{ }, Cpanel::Env::get_safe_env_vars() ) );
}
@env = map { exists $ENV{$_} ? ( $_ . '=' . ( $ENV{$_} // '' ) ) : () } @_allowed_env_vars_cache;
}
my $user = $OPTS{'user'};
my $homedir = $OPTS{'homedir'};
if ( !$user || !$homedir ) {
Cpanel::LoadModule::load_perl_module('Cpanel::PwCache') if !$INC{'Cpanel/PwCache.pm'};
my ( $pw_user, $pw_homedir ) = ( Cpanel::PwCache::getpwuid_noshadow($>) )[ 0, 7 ];
$user ||= $pw_user;
$homedir ||= $pw_homedir;
}
die "Invalid EUID: $>" if !$user || !$homedir;
push @env, "HOME=$homedir", "USER=$user"; # need to always be set since we start clean and don't have before_exec
push @env, "TMP=$homedir/tmp", "TEMP=$homedir/tmp" if !defined $ENV{'TMP'};
$self->{'_child_pid'} = Proc::FastSpawn::spawn_open3(
fileno($child_reads), # stdin
defined $child_write_fh{'stdout'} ? fileno( $child_write_fh{'stdout'} ) : -1, # stdout
defined $child_write_fh{'stderr'} ? fileno( $child_write_fh{'stderr'} ) : -1, # stderr
$OPTS{'program'}, # program
[ $OPTS{'program'}, @$args_ar ], # args
$OPTS{'keep_env'} ? () : \@env # env
);
}
else {
require Cpanel::ForkAsync;
$self->{'_child_pid'} = Cpanel::ForkAsync::do_in_child(
sub {
$SIG{'__DIE__'} = 'DEFAULT'; ## no critic qw(Variables::RequireLocalizedPunctuationVars) -- will never be unset
if ( $parent_read_fh{'stdout'} ) {
close $parent_read_fh{'stdout'} or die "child close parent stdout failed: $!";
}
if ( $parent_read_fh{'stderr'} && !$merge_output_yn ) {
close $parent_read_fh{'stderr'} or die "child close parent stderr failed: $!";
}
if ($parent_writes) {
close $parent_writes or die "close() failed: $!";
}
open( STDIN, '<&=' . fileno $child_reads ) or die "open(STDIN) failed: $!"; ##no critic qw(ProhibitTwoArgOpen)
my $fileno_stdout = fileno \*STDOUT;
if ( $fileno_stdout != fileno( $child_write_fh{'stdout'} ) ) {
if ( $fileno_stdout != 1 ) {
close STDOUT or die "close(STDOUT) failed: $!";
open( STDOUT, '>>&=1' ) or die "open(STDOUT, '>>&=1') failed: $!"; ##no critic qw(ProhibitTwoArgOpen)
}
open( STDOUT, '>>&=' . fileno $child_write_fh{'stdout'} ) or die "open(STDOUT) failed: $!"; ##no critic qw(ProhibitTwoArgOpen)
}
my $fileno_stderr = fileno \*STDERR;
if ( $fileno_stderr != fileno( $child_write_fh{'stderr'} ) ) {
if ( $fileno_stderr != 2 ) {
close STDERR or die "close(STDOUT) failed: $!";
open( STDERR, '>>&=2' ) or die "open(STDERR, '>>&=2') failed: $!"; ##no critic qw(ProhibitTwoArgOpen)
}
open( STDERR, '>>&=' . fileno $child_write_fh{'stderr'} ) or die "open(STDERR) failed: $!"; ##no critic qw(ProhibitTwoArgOpen)
}
if ( !$OPTS{'keep_env'} ) {
Cpanel::Env::clean_env();
}
if ($Cpanel::AccessIds::ReducedPrivileges::PRIVS_REDUCED) { # PPI NO PARSE -- can't be reduced if the module isn't loaded
my $target_euid = "$>";
my $target_egid = ( split( m{ }, "$)" ) )[0];
Cpanel::AccessIds::ReducedPrivileges::_restore_privileges( 0, 0 ); # PPI NO PARSE -- we will never get here if ReducedPrivileges wasn't loaded
Cpanel::LoadModule::load_perl_module('Cpanel::Sys::Setsid::Fast') if !$INC{'Cpanel/Sys/Setsid/Fast.pm'};
Cpanel::Sys::Setsid::Fast::fast_setsid();
Cpanel::LoadModule::load_perl_module('Cpanel::AccessIds::SetUids') if !$INC{'Cpanel/AccessIds/SetUids.pm'};
Cpanel::AccessIds::SetUids::setuids( $target_euid, $target_egid );
}
if ( $OPTS{'before_exec'} ) {
$OPTS{'before_exec'}->();
}
my $user = $OPTS{'user'};
my $homedir = $OPTS{'homedir'};
if ( !$user || !$homedir ) {
Cpanel::LoadModule::load_perl_module('Cpanel::PwCache') if !$INC{'Cpanel/PwCache.pm'};
my ( $pw_user, $pw_homedir ) = ( Cpanel::PwCache::getpwuid_noshadow($>) )[ 0, 7 ];
$user ||= $pw_user;
$homedir ||= $pw_homedir;
}
die "Invalid EUID: $>" if !$user || !$homedir;
$ENV{'HOME'} = $homedir if !defined $ENV{'HOME'}; # always cleared by clean_env, but may be reset in before_exec
$ENV{'USER'} = $user if !defined $ENV{'USER'}; # always cleared by clean_env, but may be reset in before_exec
$ENV{'TMP'} = "$homedir/tmp" if !defined $ENV{'TMP'};
$ENV{'TEMP'} = "$homedir/tmp" if !defined $ENV{'TEMP'};
exec( $OPTS{'program'}, @$args_ar ) or die "$exec_failed_message $!";
}
);
}
if ( $OPTS{'after_fork'} ) {
$OPTS{'after_fork'}->( $self->{'_child_pid'} );
}
if ($close_child_reads) { #only close it if we opened it
close $child_reads or die "close() failed: $!";
}
if ( $parent_read_fh{'stdout'} ) {
close $child_write_fh{'stdout'} or die "close() failed: $!";
}
if ( !$merge_output_yn && $parent_read_fh{'stderr'} ) {
close $child_write_fh{'stderr'} or die "close() failed: $!";
}
if ($parent_writes) {
if ( ref $OPTS{'stdin'} eq 'CODE' ) {
$OPTS{'stdin'}->($parent_writes);
}
else {
local $SIG{'PIPE'} = 'IGNORE';
Cpanel::FHUtils::Autoflush::enable($parent_writes);
if ($pump_stdin_filehandle_into_child) {
my $buffer;
my $is_os_stdin = Cpanel::FHUtils::OS::is_os_filehandle( $OPTS{'stdin'} );
local $!;
if ($is_os_stdin) {
while ( IO::SigGuard::sysread( $OPTS{'stdin'}, $buffer, $CHUNK_SIZE ) ) {
IO::SigGuard::syswrite( $parent_writes, $buffer ) or die $self->_write_error( \$buffer, $! );
}
}
else {
while ( read $OPTS{'stdin'}, $buffer, $CHUNK_SIZE ) {
IO::SigGuard::syswrite( $parent_writes, $buffer ) or die $self->_write_error( \$buffer, $! );
}
}
if ($!) {
die Cpanel::Exception::create( 'IO::ReadError', 'The system failed to read up to [format_bytes,_1] from the filehandle that contains standard input for the process that is running the command “[_2]”. This failure happened because of an error: [_3]', [ $CHUNK_SIZE, "$OPTS{'program'} @$args_ar", "$!" ] );
}
}
else {
my $to_print_r = ( ref $OPTS{'stdin'} eq 'SCALAR' ) ? $OPTS{'stdin'} : \$OPTS{'stdin'};
if ( length $$to_print_r ) {
IO::SigGuard::syswrite( $parent_writes, $$to_print_r ) or die $self->_write_error( $to_print_r, $! );
}
}
}
close $parent_writes or warn "close() failed: $!";
}
my $reader;
my $err_obj;
my @filehandles = map { $parent_read_fh{$_} ? [ $parent_read_fh{$_}, $OPTS{$_} ] : () } qw( stdout stderr );
if (@filehandles) {
local $@;
eval {
$reader = Cpanel::ReadMultipleFH->new(
filehandles => \@filehandles,
timeout => $OPTS{'timeout'},
read_timeout => $OPTS{'read_timeout'},
);
};
$err_obj = $@;
}
if ( $parent_read_fh{'stdout'} ) {
close $parent_read_fh{'stdout'} or warn "parent close(stdout) failed: $!";
}
if ( $parent_read_fh{'stderr'} && !$merge_output_yn ) {
close $parent_read_fh{'stderr'} or warn "parent close(stderr) failed: $!";
}
if ($err_obj) {
$self->{'_CHILD_ERROR'} = $self->_safe_kill_child();
die $err_obj;
}
elsif ($reader) {
if ( !$reader->did_finish() ) {
$self->{'_timed_out_after'} = $reader->timed_out();
$self->{'_CHILD_ERROR'} = $self->_safe_kill_child();
}
$self->{"_stdout"} = $parent_read_fh{stdout} && $reader->get_buffer( $parent_read_fh{stdout} );
$self->{"_stderr"} = $parent_read_fh{stderr} && $reader->get_buffer( $parent_read_fh{stderr} );
}
if ( !defined $self->{'_CHILD_ERROR'} ) {
local $?;
waitpid( $self->{'_child_pid'}, 0 );
$self->{'_CHILD_ERROR'} = $?;
}
if ( $used_fastspawn && $self->{'_CHILD_ERROR'} == 32512 ) {
$self->{'_CHILD_ERROR'} = _ENOENT() << 8;
$self->{'_exec_failed'} = 1;
${ $self->{'_stderr'} } .= "$exec_failed_message $!";
}
elsif ( !$used_fastspawn && $self->{'_stderr'} && $self->{'_CHILD_ERROR'} && ( $self->{'_CHILD_ERROR'} >> 8 ) == 2 && index( ${ $self->{'_stderr'} }, $exec_failed_message ) > -1 ) {
$self->{'_exec_failed'} = 1;
}
return $self;
}
sub new_or_die {
my ( $class, @args ) = @_;
return $class->new(@args)->die_if_error();
}
sub die_if_error {
my ($self) = @_;
if ( $self->timed_out() ) {
die Cpanel::Exception::create(
'ProcessFailed::Timeout',
[
process_name => $self->program(),
( $self->child_pid() ? ( pid => $self->child_pid() ) : () ),
timeout => $self->timed_out(),
$self->_extra_error_args_for_die_if_error(),
],
);
}
return $self->SUPER::die_if_error();
}
sub _extra_error_args_for_die_if_error {
my ($self) = @_;
return (
stdout => $self->{'_stdout'} ? $self->stdout() : '',
stderr => $self->{'_stderr'} ? $self->stderr() : '',
);
}
sub _safe_kill_child {
my ($self) = @_;
Cpanel::LoadModule::load_perl_module('Cpanel::Kill::Single');
return 'Cpanel::Kill::Single'->can('safekill_single_pid')->( $self->{'_child_pid'}, $SAFEKILL_TIMEOUT ); # One second to die
}
sub stdout_r {
if ( !$_[0]->{'_stdout'} ) {
Cpanel::LoadModule::load_perl_module('Cpanel::Carp');
die 'Cpanel::Carp'->can('safe_longmess')->("STDOUT output went to filehandle!");
}
return $_[0]->{'_stdout'};
}
sub _additional_phrases_for_autopsy {
if ( $_[0]->timed_out() ) {
return Cpanel::LocaleString->new( 'The system aborted the subprocess because it reached the timeout of [quant,_1,second,seconds].', $_[0]->timed_out() );
}
return;
}
sub stdout {
return ${ $_[0]->stdout_r() };
}
sub stderr_r {
if ( !$_[0]->{'_stderr'} ) {
Cpanel::LoadModule::load_perl_module('Cpanel::Carp');
die 'Cpanel::Carp'->can('safe_longmess')->("STDERR output went to filehandle!");
}
return $_[0]->{'_stderr'};
}
sub stderr {
return ${ $_[0]->stderr_r() };
}
sub child_pid {
return $_[0]->{'_child_pid'};
}
sub timed_out {
return $_[0]->{'_timed_out_after'};
}
sub program {
return $_[0]->{'_program'};
}
sub _program_with_args_str {
my $args_ar = $_[0]->{'_args'};
return $_[0]->{'_program'} . ( ( $args_ar && ref $args_ar && scalar @$args_ar ) ? " @$args_ar" : '' );
}
sub _ERROR_PHRASE {
my ($self) = @_;
return Cpanel::LocaleString->new( 'The “[_1]” command (process [_2]) reported error number [_3] when it ended.', $self->_program_with_args_str(), $self->{'_child_pid'}, $self->error_code() );
}
sub _SIGNAL_PHRASE {
my ($self) = @_;
return Cpanel::LocaleString->new( 'The “[_1]” command (process [_2]) ended prematurely because it received the “[_3]” ([_4]) signal.', $self->_program_with_args_str(), $self->{'_child_pid'}, $self->signal_name(), $self->signal_code() );
}
sub _write_error {
my ( $self, $buffer_sr, $OS_ERROR ) = @_;
my @cmd = ( $self->{'_program'}, @{ $self->{'_args'} } );
return Cpanel::Exception::create( 'IO::WriteError', 'The system failed to send [format_bytes,_1] to the process that is running the command “[_2]” because of an error: [_3]', [ length($$buffer_sr), "@cmd", $OS_ERROR ], { length => length($$buffer_sr), error => $OS_ERROR } );
}
1;
} # --- END Cpanel/SafeRun/Object.pm
{ # --- BEGIN Cpanel/SafeRun/Env.pm
package Cpanel::SafeRun::Env;
use strict;
# use Cpanel::Env ();
# use Cpanel::Debug ();
our $VERSION = '1.0';
sub saferun_r_cleanenv {
return saferun_cleanenv2( { 'command' => \@_, 'return_ref' => 1, 'cleanenv' => { 'http_purge' => 1 } } );
}
sub saferun_cleanenv2 {
my $args_hr = shift;
return unless ( defined $args_hr->{'command'} && ref $args_hr->{'command'} eq 'ARRAY' );
if ($Cpanel::AccessIds::ReducedPrivileges::PRIVS_REDUCED) { # PPI NO PARSE -- can't be reduced if the module isn't loaded
die __PACKAGE__ . " cannot be used with ReducedPrivileges. Use Cpanel::SafeRun::Object instead";
}
my @command = @{ $args_hr->{'command'} };
my $return_reference = $args_hr->{'return_ref'};
my $error_output = $args_hr->{'errors'};
my %cleanenv_args = defined $args_hr->{'cleanenv'} && ref $args_hr->{'cleanenv'} eq 'HASH' ? %{ $args_hr->{'cleanenv'} } : ();
my $check_cpanel_homedir_user = defined $args_hr->{'check_cpanel_homedir_user'} ? $args_hr->{'check_cpanel_homedir_user'} : 1;
return if ( substr( $command[0], 0, 1 ) eq '/' && !-x $command[0] );
my $output;
if ( !@command ) {
Cpanel::Debug::log_warn('Cannot execute a null program');
return \$output if $return_reference;
return $output;
}
require Cpanel::Env;
local ( $/, *PROG, *RNULL );
no strict 'refs';
open( RNULL, '<', '/dev/null' ); ## no critic(InputOutput::ProhibitBarewordFileHandles InputOutput::RequireCheckedOpen)
my $pid = open( PROG, "-|" ); ## no critic(InputOutput::ProhibitBarewordFileHandles)
if ( $pid > 0 ) {
$output = <PROG>;
}
elsif ( $pid == 0 ) {
open( STDIN, '<&RNULL' );
if ($error_output) {
open STDERR, '>&STDOUT';
}
Cpanel::Env::clean_env(%cleanenv_args);
if ( $check_cpanel_homedir_user && ( !$Cpanel::homedir || !$Cpanel::user ) ) {
( $ENV{'USER'}, $ENV{'HOME'} ) = ( getpwuid($>) )[ 0, 7 ]; #do not use PwCache here
}
exec(@command) or exit(1); # Not reached
}
else {
Cpanel::Debug::log_warn('Could not fork new process');
return \$output if $return_reference;
return $output;
}
close(PROG);
close(RNULL);
waitpid( $pid, 0 );
return \$output if $return_reference;
return $output;
}
1;
} # --- END Cpanel/SafeRun/Env.pm
{ # --- BEGIN Cpanel/CachedCommand.pm
package Cpanel::CachedCommand;
use strict;
use warnings;
# use Cpanel::StatCache ();
# use Cpanel::LoadFile ();
# use Cpanel::CachedCommand::Utils ();
# use Cpanel::CachedCommand::Valid ();
# use Cpanel::Debug ();
our $VERSION = '2.8';
my %MEMORY_CACHE;
sub _is_memory_cache_valid {
my %OPTS = @_;
my $datastore_file = $OPTS{'datastore_file'};
if ( !exists $MEMORY_CACHE{$datastore_file} ) {
print STDERR "_is_memory_cache_valid: rejecting $datastore_file because it does not exist in memory.\n" if $Cpanel::Debug::level;
return 0;
}
my $ttl = $OPTS{'ttl'};
my $mtime = $OPTS{'mtime'};
if ( !$ttl && $mtime && $MEMORY_CACHE{$datastore_file}->{'mtime'} == $mtime ) {
print STDERR "_is_memory_cache_valid: accepting $datastore_file because it passes the mtime test.\n" if $Cpanel::Debug::level;
return 1;
}
else {
my $now = time();
if ( $ttl && $MEMORY_CACHE{$datastore_file}->{'mtime'} > ( $now - $ttl ) ) {
print STDERR "_is_memory_cache_valid: accepting $datastore_file because it passes the ttl test.\n" if $Cpanel::Debug::level;
return 1;
}
}
print STDERR "_is_memory_cache_valid: rejecting $datastore_file because it not pass the ttl or mtime test.\n" if $Cpanel::Debug::level;
delete $MEMORY_CACHE{$datastore_file};
return 0;
}
sub invalidate_cache {
my $ds_file = Cpanel::CachedCommand::Utils::invalidate_cache(@_);
delete $MEMORY_CACHE{$ds_file};
return;
}
sub _cached_cmd {
my %OPTS = @_;
my ( $binary, $ttl, $mtime, $exact, $regexcheck, $args_hr, $min_expire_time, $get_result_cr ) = (
( $OPTS{'binary'} || '' ),
( $OPTS{'ttl'} || 0 ),
( $OPTS{'mtime'} || 0 ),
( $OPTS{'exact'} || 0 ),
( $OPTS{'regexcheck'} || '' ),
( $OPTS{'args_hr'} || {} ),
( $OPTS{'min_expire_time'} || 0 ),
( $OPTS{'get_result_cr'} || \&_default_get_result_cr ),
);
my @AG;
if ( ref $OPTS{'args'} eq 'ARRAY' ) {
@AG = @{ $OPTS{'args'} };
}
if ( substr( $binary, 0, 1 ) eq '/' && !-x $binary ) {
return "$binary is missing or not executable";
}
my @SAFEAG = @AG;
if ( !$exact && scalar @SAFEAG > 4 ) {
splice( @SAFEAG, 4 );
}
my $datastore_file = Cpanel::CachedCommand::Utils::_get_datastore_filename( $binary, @SAFEAG );
if (
_is_memory_cache_valid(
'binary' => $binary,
'datastore_file' => $datastore_file,
'ttl' => $ttl,
'mtime' => $mtime
)
) {
return $MEMORY_CACHE{$datastore_file}->{'contents'};
}
my ( $datastore_file_size, $datastore_file_mtime ) = ( stat($datastore_file) )[ 7, 9 ];
my $data_mtime;
my ( $used_cache, $res );
if (
Cpanel::CachedCommand::Valid::is_cache_valid(
'binary' => $binary,
'datastore_file' => $datastore_file,
'datastore_file_mtime' => $datastore_file_mtime,
'ttl' => $ttl,
'mtime' => $mtime,
'min_expire_time' => $min_expire_time,
)
) {
$res = Cpanel::LoadFile::loadfile_r( $datastore_file, { 'skip_exists_check' => 1 } );
$data_mtime = $datastore_file_mtime;
if ( $res && ( !$regexcheck || $$res =~ m/$regexcheck/ ) ) {
$used_cache = 1;
}
}
if ( !$used_cache ) {
$data_mtime = _time();
$res = $get_result_cr->( { binary => $binary, args => \@AG } );
if ( !$regexcheck || ( defined $res && ( ref $res ? $$res : $res ) =~ m/$regexcheck/ ) ) {
print STDERR "_cached_command: writing datastore file: $datastore_file " . ( $regexcheck ? "regex_check: $regexcheck" : '' ) . "\n" if $Cpanel::Debug::level;
require Cpanel::CachedCommand::Save;
Cpanel::CachedCommand::Save::_savefile( $datastore_file, $res );
}
else {
print STDERR "_cached_command: failed regex check NOT writing datastore file: $datastore_file " . ( $regexcheck ? "regex_check: $regexcheck" : '' ) . "\n" if $Cpanel::Debug::level;
}
}
return _cache_res_if_needed( $res, $ttl, $datastore_file, $data_mtime );
}
sub _cache_res_if_needed {
my ( $res, $ttl, $datastore_file, $data_mtime ) = @_;
if ( ref $res ) {
if ( $ttl && ( !defined $$res || length($$res) < 32768 ) ) { $MEMORY_CACHE{$datastore_file} = { 'mtime' => $data_mtime, 'contents' => $res }; }
return $res;
}
else {
if ( $ttl && ( !defined $res || length($res) < 32768 ) ) { $MEMORY_CACHE{$datastore_file} = { 'mtime' => $data_mtime, 'contents' => \$res }; }
return \$res;
}
}
sub _default_get_result_cr {
my ($opts) = @_;
return _get_cmd_output( 'program' => $opts->{binary}, 'args' => $opts->{args}, 'stderr' => \*STDERR );
}
sub _get_memory_cache {
return \%MEMORY_CACHE;
}
sub _time {
return time();
}
sub _get_cmd_output {
my (@key_val) = @_;
return eval {
require Cpanel::SafeRun::Object;
my $run = Cpanel::SafeRun::Object->new(@key_val);
$run->stdout();
};
}
sub has_cache {
my ( $ttl, $bin, @AG ) = @_;
my @SAFEAG = @AG;
if ( scalar @SAFEAG > 3 ) {
splice( @SAFEAG, 3 );
}
my $datastore_file = Cpanel::CachedCommand::Utils::_get_datastore_filename( $bin, @SAFEAG );
return (
Cpanel::CachedCommand::Valid::is_cache_valid(
'datastore_file' => $datastore_file,
'binary' => $bin,
'ttl' => $ttl
)
) ? 1 : 0;
}
sub cachedcommand {
my ( $binary, @ARGS ) = @_;
my $cache_ref = _cached_cmd(
'binary' => $binary,
'regexcheck' => qr/./, # only cache data that actually exists
'args' => \@ARGS
);
if ( ref $cache_ref eq 'SCALAR' ) { return $$cache_ref; }
return $cache_ref;
}
sub cachedcommand_no_errors {
my (%OPTS) = @_;
return _cached_cmd(
binary => $OPTS{'binary'},
args => $OPTS{'args'},
( defined $OPTS{'mtime'} ? ( mtime => $OPTS{'mtime'} ) : () ),
( defined $OPTS{'ttl'} ? ( ttl => $OPTS{'ttl'} ) : () ),
get_result_cr => sub {
my ($opts) = @_;
return _get_cmd_output( 'program' => $opts->{binary}, 'args' => $opts->{args}, ( $OPTS{ttl} ? ( 'timeout' => $OPTS{ttl}, 'read_timeout' => $OPTS{ttl} ) : () ) );
}
);
}
sub cachedcommand_multifile {
my ( $test_file_ar, $binary, @ARGS ) = @_;
my ( $mtime, $ctime ) = Cpanel::StatCache::cachedmtime_ctime($binary);
if ( $ctime > $mtime ) {
$mtime = $ctime;
}
foreach my $file (@$test_file_ar) {
my @test_times = Cpanel::StatCache::cachedmtime_ctime($file);
foreach my $new_time (@test_times) {
if ( $new_time > $mtime ) {
$mtime = $new_time;
}
}
}
my $cache_ref = _cached_cmd(
'binary' => $binary,
'args' => \@ARGS,
'mtime' => $mtime
);
if ( ref $cache_ref eq 'SCALAR' ) { return $$cache_ref; }
return $cache_ref;
}
sub cachedmcommand {
my ( $ttl, $binary, @ARGS ) = @_;
my $cache_ref = _cached_cmd(
'ttl' => $ttl,
'binary' => $binary,
'args' => \@ARGS
);
if ( ref $cache_ref eq 'SCALAR' ) { return $$cache_ref; }
return $cache_ref;
}
sub cachedmcommand_r_cleanenv {
my ( $ttl, $binary, @ARGS ) = @_;
my $cache_ref = _cached_cmd(
'ttl' => $ttl,
'binary' => $binary,
'args' => \@ARGS,
'get_result_cr' => sub {
my ($opts) = @_;
require Cpanel::SafeRun::Env;
return Cpanel::SafeRun::Env::saferun_r_cleanenv( $opts->{binary}, @{ $opts->{args} } );
},
);
if ( ref $cache_ref ne 'SCALAR' ) { return \$cache_ref; }
return $cache_ref;
}
sub cachedmcommand_cleanenv2 {
my ( $ttl, $args_hr ) = @_;
my @cmd = @{ $args_hr->{'command'} };
my $binary = shift @cmd;
my @ARGS = @cmd;
my $cache_ref = _cached_cmd(
'ttl' => $ttl,
'binary' => $binary,
'args' => \@ARGS,
'get_result_cr' => sub {
require Cpanel::SafeRun::Env;
return Cpanel::SafeRun::Env::saferun_cleanenv2($args_hr);
},
);
return $cache_ref;
}
sub cachedmcommand_r {
my ( $ttl, $binary, @ARGS ) = @_;
my $cache_ref = _cached_cmd(
'ttl' => $ttl,
'binary' => $binary,
'args' => \@ARGS
);
if ( ref $cache_ref ne 'SCALAR' ) { return \$cache_ref; }
return $cache_ref;
}
sub cachedmcommand2 {
my $arg_ref = shift;
my $bin = $arg_ref->{'bin'};
my $ttl = $arg_ref->{'age'};
my $timer = $arg_ref->{'timer'};
my $exact = $arg_ref->{'exact'};
my $regexcheck = $arg_ref->{'regexcheck'};
my @AG = @{ $arg_ref->{'ARGS'} };
my $cache_ref = _cached_cmd(
'binary' => $bin,
'ttl' => $ttl,
'exact' => $exact,
'regexcheck' => $regexcheck,
'args' => \@AG,
'get_result_cr' => sub {
my ($opts) = @_;
return _get_cmd_output( 'program' => $opts->{binary}, 'args' => $opts->{'args'}, 'stderr' => \*STDERR, ( int($timer) > 0 ? ( 'timeout' => $timer, 'read_timeout' => $timer ) : () ) );
},
);
if ( ref $cache_ref eq 'SCALAR' ) { return $$cache_ref; }
return $cache_ref;
}
sub noncachedcommand {
my ( $bin, @AG ) = @_;
if ( substr( $bin, 0, 1 ) eq '/' && !-x $bin ) {
return "$bin is missing or not executable";
}
my $datastore_file = Cpanel::CachedCommand::Utils::_get_datastore_filename( $bin, $AG[0] );
if ( -e $datastore_file ) {
unlink $datastore_file;
}
return _get_cmd_output( 'program' => $bin, 'args' => \@AG );
}
sub retrieve {
my %OPTS = @_;
return Cpanel::LoadFile::loadfile( Cpanel::CachedCommand::Utils::_get_datastore_filename( $OPTS{'name'} ) );
}
sub clear_memory_cache {
%MEMORY_CACHE = ();
}
1;
} # --- END Cpanel/CachedCommand.pm
{ # --- BEGIN Cpanel/Time/TZ.pm
package Cpanel::Time::TZ;
use strict;
use warnings;
our $SYSCONFIG_CLOCK_FILE = '/etc/sysconfig/clock';
our $TIMEDATECTL_BIN = q{/usr/bin/timedatectl};
our $LOCALTIME_FILE = q{/etc/localtime};
# use Cpanel::AdminBin::Serializer (); # PPI NO PARSE - for Cpanel::Config::LoadConfig cache
# use Cpanel::Config::LoadConfig ();
# use Cpanel::CachedCommand ();
sub _clean_zone {
my $zone = shift;
return unless defined $zone && length $zone;
$zone =~ tr{ \t\n\r\f'"}{}d;
return undef if $zone eq 'n/a';
return $zone if length $zone;
return $zone;
}
sub _run_timedatectl {
local $Cpanel::StatCache::USE_LSTAT = 1;
local *STDERR;
open( STDERR, '>', '/dev/null' ) or return;
return Cpanel::CachedCommand::cachedcommand_multifile( [$LOCALTIME_FILE], $TIMEDATECTL_BIN );
}
sub _get_zone_from_sysconfig_clock {
if ( my $sysconfig_clock = Cpanel::Config::LoadConfig::loadConfig( $SYSCONFIG_CLOCK_FILE, undef, q{=} ) ) {
my $zone = _clean_zone( $sysconfig_clock->{'ZONE'} );
return $zone if defined $zone;
}
if ( -x $TIMEDATECTL_BIN ) {
my $out = _run_timedatectl();
if ( defined $out && $out =~ m{^\s*Time\s?zone\s*:\s*([^\(]+)\s*}mi ) {
my $zone = _clean_zone($1);
return $zone if defined $zone;
}
}
if ( my $link = readlink($LOCALTIME_FILE) ) {
return $1 if $link =~ m{/zoneinfo/(\S+)$};
}
return undef;
}
sub calculate_TZ_env {
my $sysconfig_clock_zone = _get_zone_from_sysconfig_clock();
return $sysconfig_clock_zone if $sysconfig_clock_zone;
return undef;
}
1;
} # --- END Cpanel/Time/TZ.pm
{ # --- BEGIN Cpanel/Locale/Utils/DateTime.pm
package Cpanel::Locale::Utils::DateTime;
use strict;
# use Cpanel::LoadModule ();
# use Cpanel::Locale ();
our $ENCODE_MODULE = 'Encode';
our $DATETIME_MODULE = 'DateTime';
our $DATETIME_LOCALE_MODULE = 'DateTime::Locale';
my %known_ids = ();
sub datetime {
my ( $lh, $epoch, $format, $timezone ) = @_;
if ( $epoch && ref $epoch eq 'ARRAY' ) {
$epoch = $epoch->[0];
}
elsif ( !$epoch ) {
$epoch = time;
}
$format ||= 'date_format_long';
my $encoding = $lh->encoding();
if ( _can_use_cpanel_date_format( $encoding, $timezone ) ) {
Cpanel::LoadModule::load_perl_module('Cpanel::Date::Format');
return Cpanel::Date::Format::translate_for_locale( $epoch, $format, $lh->language_tag() );
}
my $locale = _get_best_locale_for_datetime_obj( $lh->language_tag() );
return _get_formatted_datetime( $locale, $encoding, $format, $epoch, $timezone );
}
sub _can_use_cpanel_date_format {
my ( $encoding, $timezone ) = @_;
return ( $encoding eq 'utf-8' ) && ( !$timezone || $timezone eq 'UTC' );
}
sub get_lookup_hash_of_multi_epoch_datetime {
my ( $lh, $epochs_ar, $format, $timezone ) = @_;
$format ||= 'date_format_long';
my %lookups;
my $encoding = $lh->encoding();
my $can_use_cpanel_date_format = _can_use_cpanel_date_format( $encoding, $timezone );
my $locale;
if ($can_use_cpanel_date_format) {
Cpanel::LoadModule::load_perl_module('Cpanel::Date::Format');
$locale = $lh->language_tag();
}
else {
$locale = _get_best_locale_for_datetime_obj( $lh->language_tag() );
}
foreach my $epoch ( @{$epochs_ar} ) {
$lookups{$epoch} ||= do {
if ($can_use_cpanel_date_format) {
Cpanel::Date::Format::translate_for_locale( $epoch, $format, $locale );
}
else {
_get_formatted_datetime( $locale, $encoding, $format, $epoch, $timezone );
}
};
}
return \%lookups;
}
sub _get_formatted_datetime {
my ( $locale, $encoding, $format, $epoch, $timezone ) = @_;
if ( !$timezone ) {
$timezone = 'UTC';
}
elsif ( $timezone !~ m{^[\.0-9A-Za-z\/_\+\-]+$} ) {
die "Invalid timezone “$timezone”";
}
my $datetime_obj = $DATETIME_MODULE->from_epoch( 'epoch' => $epoch, 'locale' => $locale, 'time_zone' => $timezone );
if ( $format && $format !~ m{_format$} && $datetime_obj->{'locale'}->can($format) ) {
return $ENCODE_MODULE->can('encode')->( $encoding, $datetime_obj->format_cldr( $datetime_obj->{'locale'}->$format ) );
}
die 'Invalid datetime format: ' . $format;
}
sub _get_best_locale_for_datetime_obj {
my ($language_tag) = @_;
my ( $fallback, $locale ) = _get_fallback_locale($language_tag);
Cpanel::LoadModule::load_perl_module($ENCODE_MODULE) if !$INC{'Encode.pm'};
Cpanel::LoadModule::load_perl_module($DATETIME_MODULE);
foreach my $try_locale ( $locale, $fallback, 'en_US', 'en' ) {
next if !$try_locale;
return $try_locale if $known_ids{$try_locale} || $Cpanel::Locale::known_locales_character_orientation{$try_locale};
if ( eval { $DATETIME_MODULE->load($try_locale) } ) {
$known_ids{$try_locale} = 1;
return $try_locale;
}
}
die "Could not locale any working DateTime locale";
}
sub _get_fallback_locale {
my ($locale) = @_;
my $fallback;
if ( substr( $locale, 0, 2 ) eq 'i_' ) {
require Cpanel::Locale::Utils::Paths;
my $dir = Cpanel::Locale::Utils::Paths::get_i_locales_config_path();
if ( -e "$dir/$locale.yaml" ) {
require Cpanel::DataStore;
my $hr = Cpanel::DataStore::fetch_ref("$dir/$locale.yaml");
if ( exists $hr->{'fallback_locale'} && $hr->{'fallback_locale'} ) {
$fallback = $hr->{'fallback_locale'};
}
}
}
else {
my ( $pre, $pst ) = split( /[\_\-]/, $locale, 2 );
if ($pst) {
$fallback = $pre;
$locale = $pre . '_' . uc($pst);
}
}
$fallback ||= 'en';
return ( $fallback, $locale );
}
1;
} # --- END Cpanel/Locale/Utils/DateTime.pm
{ # --- BEGIN Cpanel/DateUtils.pm
package Cpanel::DateUtils;
use warnings;
use strict;
use Try::Tiny;
# use Cpanel::LoadModule ();
our $VERSION = '0.0.3';
my %months = do {
my $i = 0;
map { $_ => ++$i } qw/jan feb mar apr may jun jul aug sep oct nov dec/;
};
my @days_in = ( undef, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
sub month_num {
my ($month) = @_;
return unless defined $month;
return $month if $month =~ /^\d+$/;
$month = lc substr( $month, 0, 3 );
return unless exists $months{$month};
return $months{$month};
}
sub month_last_day {
my ( $mon, $yr ) = @_;
if ( 2 == $mon && 0 == ( $yr % 4 ) ) {
if ( !( 0 == ( $yr % 100 ) ) || ( 0 == ( $yr % 400 ) ) ) {
return $days_in[$mon] + 1;
}
}
return ( $days_in[$mon] || die "Invalid month index: $mon" );
}
sub days_til_month_end {
my ($time) = @_;
my ( $month, $year ) = ( localtime($time) )[ 4, 5 ];
if ( ++$month == 12 ) {
$month = 0;
++$year;
}
Cpanel::LoadModule::load_perl_module('Time::Local') if !$INC{'Time/Local.pm'};
my $begin_next_month = Time::Local::timelocal( 0, 0, 0, 1, $month, $year );
return ( $begin_next_month - $time ) / 86400;
}
sub time_til_month_end {
my ($time) = @_;
my ( $month, $year ) = ( localtime($time) )[ 4, 5 ];
if ( ++$month == 12 ) {
$month = 0;
++$year;
}
Cpanel::LoadModule::load_perl_module('Time::Local') if !$INC{'Time/Local.pm'};
my $begin_next_month = Time::Local::timelocal( 0, 0, 0, 1, $month, $year );
return $begin_next_month - $time;
}
sub _now { return time }
sub timestamp_is_in_this_month {
my ($time) = @_;
my ( $month, $year ) = ( localtime $time )[ 4, 5 ];
my ( $thism, $thisy ) = ( localtime _now() )[ 4, 5 ];
return 0 if $month != $thism;
return 0 if $year != $thisy;
return 1;
}
sub get_last_second_of_ymdhm {
my ( $year, $month, $day, $hour, $minute ) = @_;
die 'Need year!' if !$year;
Cpanel::LoadModule::load_perl_module('Cpanel::Time') if !$INC{'Cpanel/Time.pm'};
if ( defined $minute ) {
return Cpanel::Time::timelocal( 59, $minute, $hour, $day, $month, $year );
}
if ( defined $hour ) {
return Cpanel::Time::timelocal( 59, 59, $hour, $day, $month, $year );
}
my $is_last_of_month;
if ($day) {
die 'Need month if day!' if !$month;
return Cpanel::Time::timelocal( 59, 59, 23, $day, $month, $year );
}
else {
$is_last_of_month = 1;
}
if ( defined($month) && $month < 12 ) {
if ($is_last_of_month) {
return Cpanel::Time::timelocal( 0, 0, 0, 1, $month + 1, $year ) - 1;
}
}
return Cpanel::Time::timelocal( 0, 0, 0, 1, 1, $year + 1 ) - 1;
}
my @smhdmy = qw(
second
minute
hour
day
month
year
);
my %unit_index = map { $smhdmy[$_] => $_ } ( 0 .. $#smhdmy );
sub add_local_interval {
my ( $time, $count, $unit, $timezone ) = @_;
local $ENV{'TZ'} = $timezone if length $timezone;
require DateTime;
my $dt = DateTime->from_epoch(
'epoch' => $time,
( length $timezone ? ( time_zone => $timezone ) : () )
);
try {
$dt->add( "${unit}s" => $count );
}
catch {
$dt->set_time_zone('UTC');
$dt->add( "${unit}s" => $count );
$dt->set_time_zone($timezone) if length $timezone;
};
return $dt->epoch;
}
sub local_startof {
my ( $time, $unit, $timezone ) = @_;
local $ENV{'TZ'} = $timezone if length $timezone;
Cpanel::LoadModule::load_perl_module('Time::Local') if !$INC{'Time/Local.pm'};
Cpanel::LoadModule::load_perl_module('Cpanel::Time') if !$INC{'Cpanel/Time.pm'};
return _startof(
$time, $unit,
\&Cpanel::Time::localtime,
\&Cpanel::Time::timelocal,
);
}
sub _startof {
my ( $time, $unit, $splitter_cr, $packer_cr ) = @_;
my @split = ( $splitter_cr->($time) )[ 0 .. 5 ];
my $index = $unit_index{$unit} - 1;
die "Invalid unit: “$unit”" if !length $index || $index < 0;
for my $i ( 0 .. $index ) {
if ( $i == 3 || $i == 4 ) {
$split[$i] = 1;
}
else {
$split[$i] = 0;
}
}
return $packer_cr->(@split);
}
1; # Magic true value required at end of module
} # --- END Cpanel/DateUtils.pm
{ # --- BEGIN Cpanel/Validate/Time.pm
package Cpanel::Validate::Time;
use strict;
use warnings;
# use Cpanel::DateUtils ();
# use Cpanel::Exception ();
my $ISO_REGEXP = q<
([0-9]{4})
-
(0[1-9] | 1[0-2])
-
(0[1-9] | [12][0-9] | 3[01])
T
(?: [01][0-9] | 2[0-3] )
:
[0-5][0-9]
:
[0-5][0-9]
Z
>;
sub iso_or_die {
my $valid = length( $_[0] ) && ( $_[0] =~ m<\A $ISO_REGEXP \z>xo );
if ( $valid && ( $2 == 2 ) && ( $3 > 28 ) ) {
my $last_mday = Cpanel::DateUtils::month_last_day( 2, $1 );
$valid = ( $3 <= $last_mday );
}
if ( !$valid ) {
die Cpanel::Exception::create( 'InvalidParameter', '“[_1]” is not a valid [asis,ISO 8601] timestamp on this system.', [ $_[0] ] );
}
return;
}
sub epoch_or_die {
( length( $_[0] ) && $_[0] !~ tr<0-9><>c && $_[0] <= 67767976233521999 ) or do {
die Cpanel::Exception::create( 'InvalidParameter', '“[_1]” is not a valid [asis,UNIX] epoch timestamp.', [ $_[0] ] );
};
return;
}
1;
} # --- END Cpanel/Validate/Time.pm
{ # --- BEGIN Cpanel/Time/ISO.pm
package Cpanel::Time::ISO;
use strict;
use warnings;
# use Cpanel::Debug ();
# use Cpanel::LoadModule ();
sub unix2iso {
Cpanel::LoadModule::load_perl_module('Cpanel::Time') unless $INC{'Cpanel/Time.pm'};
return sprintf( '%04d-%02d-%02dT%02d:%02d:%02dZ', reverse( ( Cpanel::Time::gmtime( $_[0] || time() ) )[ 0 .. 5 ] ) );
}
sub iso2unix {
my ($iso_time) = @_;
if ( rindex( $iso_time, 'Z' ) != length($iso_time) - 1 ) {
die "Only UTC times, not “$iso_time”!";
}
my @smhdmy = reverse split m<[^0-9.]>, $iso_time;
Cpanel::LoadModule::load_perl_module('Cpanel::Time') unless $INC{'Cpanel/Time.pm'};
return Cpanel::Time::timegm(@smhdmy);
}
sub unix2iso_date {
Cpanel::LoadModule::load_perl_module('Cpanel::Time') unless $INC{'Cpanel/Time.pm'};
Cpanel::Debug::log_deprecated('This function will be removed, please use locale datetime');
return sprintf( '%04d-%02d-%02d', reverse( ( Cpanel::Time::gmtime( $_[0] || time() ) )[ 3 .. 5 ] ) );
}
sub unix2iso_time {
Cpanel::LoadModule::load_perl_module('Cpanel::Time') unless $INC{'Cpanel/Time.pm'};
Cpanel::Debug::log_deprecated('This function will be removed, please use locale datetime');
return sprintf( '%02d:%02d:%02d', reverse( ( Cpanel::Time::gmtime( $_[0] || time() ) )[ 0 .. 2 ] ) );
}
1;
} # --- END Cpanel/Time/ISO.pm
{ # --- BEGIN Cpanel/Config/LoadUserDomains/Count.pm
package Cpanel::Config::LoadUserDomains::Count;
use strict;
use warnings;
# use Cpanel::Autodie qw(exists);
INIT { Cpanel::Autodie->import(qw{exists}); }
# use Cpanel::LoadFile::ReadFast ();
# use Cpanel::ConfigFiles ();
sub counttrueuserdomains {
if ( !Cpanel::Autodie::exists( _trueuserdomains() ) ) {
return 0;
}
return _count_file_lines( _trueuserdomains() );
}
sub countuserdomains {
if ( !Cpanel::Autodie::exists( _userdomains() ) ) {
return 0;
}
return _count_file_lines( _userdomains() ) - 1; # -1 for *: nobody
}
sub _count_file_lines {
my ($file) = @_;
open( my $ud_fh, '<', $file ) or die "open($file): $!";
my $buffer = '';
Cpanel::LoadFile::ReadFast::read_all_fast( $ud_fh, $buffer );
my $num_ud = ( $buffer =~ tr/\n// );
close($ud_fh) or warn "close($file): $!";
$num_ud++ if length($buffer) && substr( $buffer, -1 ) ne "\n";
return $num_ud;
}
sub _userdomains {
return $Cpanel::ConfigFiles::USERDOMAINS_FILE;
}
sub _domainusers {
return $Cpanel::ConfigFiles::DOMAINUSERS_FILE;
}
sub _trueuserdomains {
return $Cpanel::ConfigFiles::TRUEUSERDOMAINS_FILE;
}
1;
} # --- END Cpanel/Config/LoadUserDomains/Count.pm
{ # --- BEGIN Cpanel/Server/Type.pm
package Cpanel::Server::Type;
use strict;
use warnings;
use constant NUMBER_OF_USERS_TO_ASSUME_IF_UNREADABLE => 1;
sub _get_license_file_path { return q{/usr/local/cpanel/cpanel.lisc} }
sub _get_dnsonly_file_path { return q{/var/cpanel/dnsonly} }
use constant _ENOENT => 2;
my @server_config;
our %PRODUCTS;
our $MAXUSERS;
our %FIELDS;
our ( $DNSONLY_MODE, $NODE_MODE );
sub is_dnsonly {
return $DNSONLY_MODE if defined $DNSONLY_MODE;
return 1 if -e _get_dnsonly_file_path();
return 0 if $! == _ENOENT();
my $err = $!;
if ( _read_license() ) {
return $PRODUCTS{'dnsonly'} ? 1 : 0;
}
die sprintf( 'stat(%s): %s', _get_dnsonly_file_path(), "$err" );
}
sub get_producttype {
return $NODE_MODE if defined $NODE_MODE;
return 'DNSONLY' unless _read_license();
return 'STANDARD' if $PRODUCTS{'cpanel'};
foreach my $product (qw/dnsnode mailnode databasenode dnsonly/) {
return uc($product) if $PRODUCTS{$product};
}
return 'DNSONLY';
}
sub get_max_users {
return $MAXUSERS if defined $MAXUSERS;
return NUMBER_OF_USERS_TO_ASSUME_IF_UNREADABLE unless _read_license();
return $MAXUSERS // NUMBER_OF_USERS_TO_ASSUME_IF_UNREADABLE;
}
sub _read_license {
my $LICENSE_FILE = _get_license_file_path();
my @new_stat = stat($LICENSE_FILE) if @server_config;
if ( @server_config && @new_stat && $new_stat[9] == $server_config[9] && $new_stat[7] == $server_config[7] ) {
return 1;
}
open( my $fh, '<', $LICENSE_FILE ) or do {
if ( $! != _ENOENT() ) {
warn "open($LICENSE_FILE): $!";
}
return;
};
_reset_cache();
my $content;
read( $fh, $content, 512 ) // do {
warn "read($LICENSE_FILE): $!";
$content = q<>;
};
return _parse_license_contents_sr( $fh, \$content );
}
sub _parse_license_contents_to_hashref {
my ($content_sr) = @_;
my %vals = map { ( split( m{: }, $_ ) )[ 0, 1 ] } split( m{\n}, $$content_sr );
return \%vals;
}
sub _parse_license_contents_sr {
my ( $fh, $content_sr ) = @_;
my $vals_hr = _parse_license_contents_to_hashref($content_sr);
if ( length $vals_hr->{'products'} ) {
%PRODUCTS = map { ( $_ => 1 ) } split( ",", $vals_hr->{'products'} );
}
else {
return;
}
if ( length $vals_hr->{'maxusers'} ) {
$MAXUSERS = int $vals_hr->{'maxusers'};
}
else {
return;
}
if ( length $vals_hr->{'fields'} ) {
foreach my $field ( split( ",", $vals_hr->{'fields'} ) ) {
my ( $k, $v ) = split( '=', $field, 2 );
$FIELDS{$k} = $v;
}
}
else {
return;
}
@server_config = stat($fh);
return 1;
}
sub _reset_cache {
undef %PRODUCTS;
undef %FIELDS;
undef @server_config;
undef $MAXUSERS;
undef $DNSONLY_MODE;
return;
}
1;
} # --- END Cpanel/Server/Type.pm
{ # --- BEGIN Cpanel/Config/LoadUserDomains.pm
package Cpanel::Config::LoadUserDomains;
use strict;
use warnings;
# use Cpanel::Config::LoadConfig ();
# use Cpanel::Config::LoadUserDomains::Count ();
# use Cpanel::Server::Type ();
sub loaduserdomains {
my ( $conf_ref, $reverse, $usearr ) = @_;
$conf_ref = Cpanel::Config::LoadConfig::loadConfig(
Cpanel::Config::LoadUserDomains::Count::_userdomains(),
$conf_ref,
': ', # We write the file so there is no need to match stray spaces
'0E0', # Avoid looking for comments since there will not be any
0, # reverse
1, # allow_undef_values since there will not be any
{
'use_reverse' => $reverse ? 0 : 1,
'skip_keys' => ['nobody'],
'use_hash_of_arr_refs' => ( $usearr || 0 ),
}
);
if ( !defined($conf_ref) ) {
$conf_ref = {};
}
return wantarray ? %{$conf_ref} : $conf_ref;
}
sub loadtrueuserdomains {
my ( $conf_ref, $reverse, $ignore_limit ) = @_;
$conf_ref = Cpanel::Config::LoadConfig::loadConfig(
( $reverse ? Cpanel::Config::LoadUserDomains::Count::_domainusers() : Cpanel::Config::LoadUserDomains::Count::_trueuserdomains() ),
$conf_ref,
': ', # We write the file so there is no need to match stray spaces
'0E0', # Avoid looking for comments since there will not be any
0, # reverse
1, # allow_undef_values since there will not be any
{ 'limit' => ( $ignore_limit ? 0 : Cpanel::Server::Type::get_max_users() ) }
);
if ( !defined($conf_ref) ) {
$conf_ref = {};
}
return wantarray ? %{$conf_ref} : $conf_ref;
}
*counttrueuserdomains = *counttrueuserdomains = *Cpanel::Config::LoadUserDomains::Count::counttrueuserdomains;
1;
} # --- END Cpanel/Config/LoadUserDomains.pm
{ # --- BEGIN Cpanel/Config/CpUser.pm
package Cpanel::Config::CpUser;
use strict;
# use Cpanel::Debug ();
# use Cpanel::LoadModule ();
# use Cpanel::Config::LoadUserDomains ();
# use Cpanel::Config::LoadCpUserFile ();
# use Cpanel::ConfigFiles ();
# use Cpanel::FileUtils::Write::JSON::Lazy ();
our $cpuser_dir;
*cpuser_dir = \$Cpanel::ConfigFiles::cpanel_users;
our $cpuser_cache_dir = "$cpuser_dir.cache";
our $header = <<END;
END
my %memory_file_list_key = qw(
DOMAINS DNS
DEADDOMAINS XDNS
HOMEDIRLINKS HOMEDIRPATHS
);
sub clean_cpuser_hash {
my ( $cpuser_ref, $user ) = @_;
{
my @missing = grep { !exists $cpuser_ref->{$_} } required_cpuser_keys();
if (@missing) {
$user = q{} if !defined $user;
Cpanel::Debug::log_warn( "The following keys are missing from supplied '$user' cPanel user data: " . join( ', ', @missing ) . ", to prevent data loss, the data was not saved." );
return;
}
}
if ( grep { $_ && index( $_, "\n" ) != -1 } %$cpuser_ref ) {
Cpanel::Debug::log_warn("The cpuser data contains newlines. This is not allowed as it would corrupt the file.");
return;
}
my $domain = $cpuser_ref->{'DOMAIN'};
if ( !$domain ) { # Try to lookup main domain in /etc/trueuserdomains
my $trueuserdomains_ref = Cpanel::Config::LoadUserDomains::loadtrueuserdomains( undef, 1 );
$domain = $trueuserdomains_ref->{$user} || '';
if ( !$domain ) {
Cpanel::Debug::log_info("Unable to determine user ${user}'s main domain");
}
}
my %clean_data = (
%$cpuser_ref,
DNS => $domain,
);
delete @clean_data{
q{},
'DOMAIN',
'DBOWNER',
'__CACHE_DATA_VERSION',
( keys %memory_file_list_key ),
};
if ( defined $clean_data{'DISK_BLOCK_LIMIT'} && $clean_data{'DISK_BLOCK_LIMIT'} eq 'unlimited' ) {
$clean_data{'DISK_BLOCK_LIMIT'} = 0;
}
while ( my ( $memkey, $filekey ) = each %memory_file_list_key ) {
if ( exists $cpuser_ref->{$memkey} && scalar @{ $cpuser_ref->{$memkey} } ) {
my $doms_ar = $cpuser_ref->{$memkey};
my $count = 0;
@clean_data{ ( map { $filekey . ++$count } @$doms_ar ) } = @$doms_ar;
}
}
my $homedirs_key_in_file = $memory_file_list_key{'HOMEDIRLINKS'};
if ( exists $clean_data{ $homedirs_key_in_file . 1 } ) {
$clean_data{$homedirs_key_in_file} = delete $clean_data{ $homedirs_key_in_file . 1 };
}
return wantarray ? %clean_data : \%clean_data;
}
sub get_cpgid {
my ($user) = @_;
my $cpgid = 0;
if ( exists $INC{'Cpanel/PwCache.pm'} || Cpanel::LoadModule::load_perl_module('Cpanel::PwCache') ) {
$cpgid = ( Cpanel::PwCache::getpwnam_noshadow($user) )[3];
}
return $cpgid;
}
sub recache {
my ( $cpuser_ref, $user, $cpgid ) = @_;
my $user_cache_file = $cpuser_cache_dir . '/' . $user;
Cpanel::Config::LoadCpUserFile::create_users_cache_dir();
$cpuser_ref->{'__CACHE_DATA_VERSION'} = $Cpanel::Config::LoadCpUserFile::VERSION; # set this before the cache is written so that it will be included in the cache
if ( Cpanel::FileUtils::Write::JSON::Lazy::write_file( $user_cache_file, $cpuser_ref, 0640 ) ) {
chown 0, $cpgid, $user_cache_file if $cpgid; # this is ok if the chown happens after as we fall though to reading the non-cache on a failed open
}
else {
unlink $user_cache_file; #outdated
}
}
sub required_cpuser_keys {
my @keys = qw( FEATURELIST HASCGI MAXSUB MAXADDON DEMO RS USER MAXFTP MAXLST MAXPARK STARTDATE BWLIMIT IP MAXSQL DOMAIN MAXPOP PLAN OWNER );
return wantarray ? @keys : \@keys;
}
1;
} # --- END Cpanel/Config/CpUser.pm
{ # --- BEGIN Cpanel/Config/FlushConfig.pm
package Cpanel::Config::FlushConfig;
use strict;
use warnings;
# use Cpanel::FileUtils::Write ();
# use Cpanel::Debug ();
# use Cpanel::Exception ();
our $VERSION = '1.4';
my $DEFAULT_DELIMITER = '=';
sub flushConfig {
my ( $filename_or_fh, $conf, $delimiter, $header, $opts ) = @_;
if ( !$filename_or_fh ) {
Cpanel::Debug::log_warn('flushConfig requires valid filename or fh as first argument');
return;
}
elsif ( !$conf || ref $conf ne 'HASH' ) {
Cpanel::Debug::log_warn('flushConfig requires HASH reference as second argument');
return;
}
if ( ref $opts && $opts->{'no_overwrite'} ) {
die Cpanel::Exception::create( 'Unsupported', 'Function ”flushConfig” called with an unsupported option “no_overwrite”.' );
}
my $contents_sr = serialize(
$conf,
do_sort => $opts && $opts->{'sort'},
delimiter => $delimiter,
header => $header,
allow_array_values => $opts && $opts->{'allow_array_values'},
);
my $perms = 0644; # default permissions when unset
if ( defined $opts->{'perms'} ) {
$perms = $opts->{'perms'};
}
elsif ( !ref $filename_or_fh && -e $filename_or_fh ) {
$perms = ( stat(_) )[2] & 0777;
}
if ( ref $filename_or_fh ) {
return Cpanel::FileUtils::Write::write_fh(
$filename_or_fh,
ref $contents_sr eq 'SCALAR' ? $$contents_sr : $contents_sr
);
}
return Cpanel::FileUtils::Write::overwrite_no_exceptions(
$filename_or_fh,
ref $contents_sr eq 'SCALAR' ? $$contents_sr : $contents_sr,
$perms,
);
}
sub serialize {
my ( $conf, %opts ) = @_;
my ( $do_sort, $delimiter, $header, $allow_array_values ) = @opts{qw(do_sort delimiter header allow_array_values)};
$delimiter ||= $DEFAULT_DELIMITER;
if ($allow_array_values) {
my $contents = '';
$contents .= $header . "\n" if $header;
foreach my $key ( $do_sort ? ( sort keys %{$conf} ) : ( keys %{$conf} ) ) {
if ( ref( $conf->{$key} ) eq 'ARRAY' ) {
$contents .= join(
"\n",
map { $key . $delimiter . $_ } ( @{ $conf->{$key} } )
) . "\n";
}
else {
$contents .= $key . $delimiter . ( defined $conf->{$key} ? $conf->{$key} : '' ) . "\n";
}
}
return \$contents;
}
my $contents = ( $header ? ( $header . "\n" ) : '' ) . join(
"\n",
map { $_ . ( defined $conf->{$_} ? ( $delimiter . $conf->{$_} ) : '' ) } ( $do_sort ? ( sort keys %{$conf} ) : ( keys %{$conf} ) )
) . "\n";
return \$contents;
}
1;
} # --- END Cpanel/Config/FlushConfig.pm
{ # --- BEGIN Cpanel/LinkedNode/Worker/Storage.pm
package Cpanel::LinkedNode::Worker::Storage;
use strict;
use warnings;
sub read {
my ( $cpuser_hr, $worker_type ) = @_;
my $str = $cpuser_hr->{ _get_key($worker_type) };
return _parse($str);
}
sub set {
my ( $cpuser_hr, $worker_type, $alias, $token ) = @_;
$cpuser_hr->{ _get_key($worker_type) } = "$alias:$token";
return;
}
sub unset {
my ( $cpuser_hr, $worker_type ) = @_;
return _parse( delete $cpuser_hr->{ _get_key($worker_type) } );
}
sub _get_key {
my ($worker_type) = @_;
substr( $worker_type, 0, 1 ) =~ tr<A-Z><> or do {
die "Worker type names always begin with a capital! (given: “$worker_type”)";
};
return "WORKER_NODE-$worker_type";
}
sub _parse {
my ($str) = @_;
return $str ? [ split m<:>, $str, 2 ] : undef;
}
1;
} # --- END Cpanel/LinkedNode/Worker/Storage.pm
{ # --- BEGIN Cpanel/SafeFile/Replace.pm
package Cpanel::SafeFile::Replace;
use strict;
use warnings;
# use Cpanel::Fcntl::Constants ();
# use Cpanel::FileUtils::Open ();
use constant {
WRONLY_CREAT_EXCL => $Cpanel::Fcntl::Constants::O_WRONLY | $Cpanel::Fcntl::Constants::O_CREAT | $Cpanel::Fcntl::Constants::O_EXCL,
_EEXIST => 17
};
sub safe_replace_content {
my ( $fh, $safelock, @content ) = @_;
return locked_atomic_replace_contents(
$fh,
$safelock,
sub {
local $!;
@content = @{ $content[0] } if scalar @content == 1 && ref $content[0] eq 'ARRAY';
print { $_[0] } @content;
if ($!) {
my $length = 0;
$length += length for @content;
my $err = $!;
require Cpanel::Exception;
die Cpanel::Exception::create( 'IO::WriteError', [ length => $length, error => $err ] );
}
return 1;
}
);
}
my $_lock_ex_nb;
sub locked_atomic_replace_contents {
my ( $fh, $safelock, $coderef ) = @_;
$_lock_ex_nb //= $Cpanel::Fcntl::Constants::LOCK_EX | $Cpanel::Fcntl::Constants::LOCK_NB;
if ( !flock $fh, $_lock_ex_nb ) {
my $err = $!;
require Cpanel::Exception;
die Cpanel::Exception::create_raw( 'IOError', "locked_atomic_replace_contents could not lock the file handle because of an error: $err" );
}
if ( !ref $safelock ) {
local $@;
if ( !eval { $safelock->isa('Cpanel::SafeFileLock') } ) {
die "locked_atomic_replace_contents requires a Cpanel::SafeFileLock object";
}
}
my $locked_path = $safelock->get_path_to_file_being_locked();
die "locked_path must be valid" if !length $locked_path;
my ( $temp_file, $temp_fh, $created_temp_file, $attempts );
my $current_perms = ( stat($fh) )[2] & 07777;
while ( !$created_temp_file && ++$attempts < 100 ) {
$temp_file = sprintf(
'%s-%x-%x-%x',
$locked_path,
substr( rand, 2 ),
scalar( reverse time ),
scalar( reverse $$ ),
);
$created_temp_file = Cpanel::FileUtils::Open::sysopen_with_real_perms( $temp_fh, $temp_file, WRONLY_CREAT_EXCL, $current_perms ) or do {
last if $! != _EEXIST;
};
}
if ( !$created_temp_file ) {
my $lasterr = $!;
die Cpanel::Exception::create( 'TempFileCreateError', [ path => $temp_file, error => $lasterr ] );
}
if ( !flock $temp_fh, $Cpanel::Fcntl::Constants::LOCK_EX ) {
my $err = $!;
require Cpanel::Exception;
die Cpanel::Exception::create( 'IO::FlockError', [ path => $temp_file, error => $err, operation => $Cpanel::Fcntl::Constants::LOCK_EX ] );
}
select( ( select($temp_fh), $| = 1 )[0] ); ##no critic qw(ProhibitOneArgSelect Variables::RequireLocalizedPunctuationVars) #aka $fd->autoflush(1);
if ( $coderef->( $temp_fh, $temp_file, $current_perms ) ) {
rename( $temp_file, $locked_path );
return $temp_fh;
}
local $!;
close $temp_fh;
unlink $temp_file;
die "locked_atomic_replace_contents coderef returns false";
}
1;
} # --- END Cpanel/SafeFile/Replace.pm
{ # --- BEGIN Cpanel/Config/CpUserGuard.pm
package Cpanel::Config::CpUserGuard;
use strict;
use warnings;
# use Cpanel::Destruct ();
# use Cpanel::Config::CpUser ();
# use Cpanel::Config::LoadCpUserFile ();
# use Cpanel::Config::FlushConfig ();
# use Cpanel::Debug ();
sub new {
my ( $class, $user ) = @_;
my ( $data, $file, $lock, $is_locked ) = ( undef, undef, undef, 0 );
my $cpuser = Cpanel::Config::LoadCpUserFile::_load_locked($user);
if ( $cpuser && ref $cpuser eq 'HASH' ) {
$data = $cpuser->{'data'};
$file = $cpuser->{'file'};
$lock = $cpuser->{'lock'};
$is_locked = defined $lock;
}
else {
Cpanel::Debug::log_warn("Failed to load user file for '$user': $!");
return;
}
my $path = "$Cpanel::Config::CpUser::cpuser_dir/$user";
return bless {
user => $user,
data => $data,
path => $path,
_file => $file,
_lock => $lock,
_pid => $$,
is_locked => $is_locked,
};
}
sub set_worker_node {
my ( $self, $worker_type, $hostname, $token ) = @_;
require Cpanel::LinkedNode::Worker::Storage;
Cpanel::LinkedNode::Worker::Storage::set( $self->{'data'}, $worker_type, $hostname, $token );
return $self;
}
sub unset_worker_node {
my ( $self, $worker_type ) = @_;
require Cpanel::LinkedNode::Worker::Storage;
return Cpanel::LinkedNode::Worker::Storage::unset( $self->{'data'}, $worker_type );
}
sub save {
my ($self) = @_;
my $user = $self->{'user'};
my $data = $self->{'data'};
if ( $self->{'_pid'} != $$ ) {
Cpanel::Debug::log_die('Locked in parent, cannot save');
return;
}
if ( ref $data ne 'HASH' ) {
Cpanel::Debug::log_die('hash reference required');
return;
}
my $clean_data = Cpanel::Config::CpUser::clean_cpuser_hash( $self->{'data'}, $user );
if ( !$clean_data ) {
Cpanel::Debug::log_warn("Data for user '$user' was not saved.");
return;
}
if ( !$self->{'_file'} || !$self->{'_lock'} ) {
Cpanel::Debug::log_warn("Unable to save user file for '$user': file not open and locked for writing");
return;
}
require Cpanel::SafeFile::Replace;
require Cpanel::Autodie;
my $newfh = Cpanel::SafeFile::Replace::locked_atomic_replace_contents(
$self->{'_file'}, $self->{'_lock'},
sub {
my ($fh) = @_;
chmod( 0640, $fh ) or do {
warn sprintf( "Failed to set permissions on “%s” to 0%o: %s", $self->{'path'}, 0640, $! );
};
return Cpanel::Autodie::syswrite_sigguard(
$fh,
${
Cpanel::Config::FlushConfig::serialize(
$clean_data,
do_sort => 1,
delimiter => '=',
'header' => $Cpanel::Config::CpUser::header,
)
}
);
}
)
or do {
Cpanel::Debug::log_warn("Failed to save user file for “$user”: $!");
};
$self->{'_file'} = $newfh;
my $cpgid = Cpanel::Config::CpUser::get_cpgid($user);
if ($cpgid) {
chown 0, $cpgid, $self->{'path'} or do {
Cpanel::Debug::log_warn("Failed to chown( 0, $cpgid, $self->{'path'}): $!");
};
}
if ( $INC{'Cpanel/Locale/Utils/User.pm'} ) {
Cpanel::Locale::Utils::User::clear_user_cache($user);
}
Cpanel::Config::CpUser::recache( $data, $user, $cpgid );
require Cpanel::SafeFile;
Cpanel::SafeFile::safeclose( $self->{'_file'}, $self->{'_lock'} ) or do {
Cpanel::Debug::log_warn("Failed to safeclose $self->{'path'}: $!");
};
$self->{'_file'} = $self->{'_lock'} = undef;
$self->{'is_locked'} = 0;
return 1;
}
sub abort {
my ($self) = @_;
my $user = $self->{'user'};
my $data = $self->{'data'};
if ( $self->{'_pid'} != $$ ) {
Cpanel::Debug::log_die('Locked in parent, cannot save');
return;
}
require Cpanel::SafeFile;
Cpanel::SafeFile::safeclose( $self->{'_file'}, $self->{'_lock'} );
$self->{'_file'} = $self->{'_lock'} = undef;
$self->{'is_locked'} = 0;
return 1;
}
sub DESTROY {
my ($self) = @_;
return unless $self->{'is_locked'};
return if Cpanel::Destruct::in_dangerous_global_destruction();
return unless $self->{'_pid'} == $$;
Cpanel::SafeFile::safeclose( $self->{'_file'}, $self->{'_lock'} );
$self->{'is_locked'} = 0;
return;
}
1;
} # --- END Cpanel/Config/CpUserGuard.pm
{ # --- BEGIN Cpanel/Locale/Utils/User/Modify.pm
package Cpanel::Locale::Utils::User::Modify;
use strict;
use warnings;
# use Cpanel::PwCache ();
sub save_user_locale {
my ( $locale, undef, $user ) = @_;
$locale ||= 'en';
$user ||= $Cpanel::user || $ENV{'REMOTE_USER'} || ( $> == 0 ? 'root' : ( Cpanel::PwCache::getpwuid_noshadow($>) )[0] );
if ( $user eq 'root' ) {
require Cpanel::LoadModule;
Cpanel::LoadModule::load_perl_module('Cpanel::DataStore');
my $root_conf_yaml = Cpanel::PwCache::gethomedir('root') . '/.cpanel_config';
my $hr = Cpanel::DataStore::fetch_ref($root_conf_yaml);
return 2 if exists $hr->{'locale'} && $hr->{'locale'} eq $locale;
$hr->{'locale'} = $locale;
return 1 if Cpanel::DataStore::store_ref( $root_conf_yaml, $hr );
return;
}
elsif ( $> == 0 ) {
require Cpanel::Config::CpUserGuard;
my $cpuser_guard = Cpanel::Config::CpUserGuard->new($user) or return;
$cpuser_guard->{'data'}->{'LOCALE'} = $locale;
delete $cpuser_guard->{'data'}->{'LANG'};
delete $cpuser_guard->{'data'}{'__LOCALE_MISSING'};
return $cpuser_guard->save();
}
else {
require Cpanel::LoadModule;
Cpanel::LoadModule::load_perl_module('Cpanel::AdminBin');
return Cpanel::AdminBin::run_adminbin_with_status( 'lang', 'SAVEUSERSETTINGS', $locale, 0, $user )->{'status'};
}
return 1;
}
1;
} # --- END Cpanel/Locale/Utils/User/Modify.pm
{ # --- BEGIN Cpanel/Locale.pm
package Cpanel::Locale;
use strict;
BEGIN {
$ENV{'IGNORE_WIN32_LOCALE'} = 1;
}
# use Cpanel::CPAN::Locale::Maketext::Utils();
our @ISA;
BEGIN { push @ISA, qw(Cpanel::CPAN::Locale::Maketext::Utils); }
# use Cpanel::Locale::Utils (); # Individual Locale modules depend on this being brought in here, if it is removed they will all need updated. Same for cpanel.pl
# use Cpanel::Locale::Utils::Paths ();
# use Cpanel::CPAN::Locale::Maketext ();
# use Cpanel::Exception ();
use constant _ENOENT => 2;
BEGIN {
local $^H = 0; # cheap no warnings without importing it
local $^W = 0;
*Cpanel::CPAN::Locale::Maketext::Utils::remove_key_from_lexicons = sub { }; # PPI NO PARSE - loaded above - disabled
}
our $SERVER_LOCALE_FILE = '/var/cpanel/server_locale';
our $LTR = 1;
our $RTL = 2;
our %known_locales_character_orientation = (
ar => $RTL,
bn => $LTR,
bg => $LTR,
cs => $LTR,
da => $LTR,
de => $LTR,
el => $LTR,
en => $LTR,
en_US => $LTR,
en_GB => $LTR,
es_419 => $LTR,
es => $LTR,
es_es => $LTR,
fi => $LTR,
fil => $LTR,
fr => $LTR,
he => $RTL,
hi => $LTR,
hu => $LTR,
i_cpanel_snowmen => $LTR,
i_cp_qa => $LTR,
id => $LTR,
it => $LTR,
ja => $LTR,
ko => $LTR,
ms => $LTR,
nb => $LTR,
nl => $LTR,
no => $LTR,
pl => $LTR,
pt_br => $LTR,
pt => $LTR,
ro => $LTR,
ru => $LTR,
sl => $LTR,
sv => $LTR,
th => $LTR,
tr => $LTR,
uk => $LTR,
vi => $LTR,
zh => $LTR,
zh_tw => $LTR,
zh_cn => $LTR,
);
my $logger;
sub _logger {
require Cpanel::Logger;
return ( $logger ||= Cpanel::Logger->new() );
}
*get_lookup_hash_of_mutli_epoch_datetime = *get_lookup_hash_of_multi_epoch_datetime;
sub preinit {
if ( exists $INC{'Cpanel.pm'} && !$Cpanel::CPDATA{'LOCALE'} ) {
require Cpanel::Locale::Utils::User if !exists $INC{'Cpanel/Locale/Utils/User.pm'};
Cpanel::Locale::Utils::User::init_cpdata_keys();
}
if ( $ENV{'HTTP_COOKIE'} ) {
require Cpanel::Cookies unless $INC{'Cpanel/Cookies.pm'};
if ( !keys %Cpanel::Cookies ) {
%Cpanel::Cookies = %{ Cpanel::Cookies::get_cookie_hashref() };
}
}
%Cpanel::Grapheme = %{ Cpanel::Locale->get_grapheme_helper_hashref() };
return 1;
}
sub makevar {
return $_[0]->maketext( ref $_[1] ? @{ $_[1] } : @_[ 1 .. $#_ ] ); ## no extract maketext
}
*maketext = *Cpanel::CPAN::Locale::Maketext::maketext; ## no extract maketext
my %singleton_stash = ();
BEGIN {
no warnings; ## no critic(ProhibitNoWarnings)
CHECK {
if ( ( $INC{'O.pm'} || $INC{'Cpanel/BinCheck.pm'} || $INC{'Cpanel/BinCheck/Lite.pm'} ) && %singleton_stash ) {
die("If you use a locale at begin time, you are responsible for deleting it too. Try calling _reset_singleton_stash\n");
}
}
}
sub _reset_singleton_stash {
foreach my $class ( keys %singleton_stash ) {
foreach my $args_sig ( keys %{ $singleton_stash{$class} } ) {
$singleton_stash{$class}{$args_sig}->cpanel_detach_lexicon();
}
}
%singleton_stash = ();
return 1;
}
sub get_handle {
preinit();
no warnings 'redefine';
*get_handle = *_real_get_handle;
goto &_real_get_handle;
}
sub _map_any_old_style_to_new_style {
my (@locales) = @_;
if ( grep { !$known_locales_character_orientation{$_} && index( $_, 'i_' ) != 0 } @locales ) {
require Cpanel::Locale::Utils::Legacy;
goto \&Cpanel::Locale::Utils::Legacy::map_any_old_style_to_new_style;
}
return @locales;
}
our $IN_REAL_GET_HANDLE = 0;
sub _setup_for_real_get_handle { ## no critic qw(RequireArgUnpacking)
if ($IN_REAL_GET_HANDLE) {
_load_carp();
if ( $IN_REAL_GET_HANDLE > 1 ) {
die 'Cpanel::Carp'->can('safe_longmess')->("Attempted to call _setup_for_real_get_handle from _setup_for_real_get_handle");
}
warn 'Cpanel::Carp'->can('safe_longmess')->("Attempted to call _setup_for_real_get_handle from _setup_for_real_get_handle");
if ($Cpanel::Exception::IN_EXCEPTION_CREATION) { # PPI NO PARSE - Only care about this check if the module is loaded
$Cpanel::Exception::LOCALIZE_STRINGS = 0; # PPI NO PARSE - Only care about this check if the module is loaded
}
}
local $IN_REAL_GET_HANDLE = $IN_REAL_GET_HANDLE + 1;
if ( defined $Cpanel::App::appname && defined $ENV{'REMOTE_USER'} ) { # PPI NO PARSE - Only care about this check if the module is loaded
if (
$Cpanel::App::appname eq 'whostmgr' # PPI NO PARSE - Only care about this check if the module is loaded
&& $ENV{'REMOTE_USER'} ne 'root'
) {
require Cpanel::Config::HasCpUserFile;
if ( Cpanel::Config::HasCpUserFile::has_readable_cpuser_file( $ENV{'REMOTE_USER'} ) ) {
require Cpanel::Config::LoadCpUserFile::CurrentUser;
my $cpdata_ref = Cpanel::Config::LoadCpUserFile::CurrentUser::load( $ENV{'REMOTE_USER'} );
if ( scalar keys %{$cpdata_ref} ) {
*Cpanel::CPDATA = $cpdata_ref;
}
}
}
}
my ( $class, @langtags ) = (
$_[0],
(
defined $_[1] ? _map_any_old_style_to_new_style( (@_)[ 1 .. $#_ ] )
: exists $Cpanel::Cookies{'session_locale'} && $Cpanel::Cookies{'session_locale'} ? _map_any_old_style_to_new_style( $Cpanel::Cookies{'session_locale'} )
: ( exists $Cpanel::CPDATA{'LOCALE'} && $Cpanel::CPDATA{'LOCALE'} ) ? ( $Cpanel::CPDATA{'LOCALE'} )
: ( exists $Cpanel::CPDATA{'LANG'} && $Cpanel::CPDATA{'LANG'} ) ? ( _map_any_old_style_to_new_style( $Cpanel::CPDATA{'LANG'} ) )
: ( get_server_locale() )
)
);
if ( !$Cpanel::Locale::CDB_File_Path ) {
$Cpanel::Locale::CDB_File_Path = Cpanel::Locale::Utils::init_lexicon( 'en', \%Cpanel::Locale::Lexicon, \$Cpanel::Locale::VERSION, \$Cpanel::Locale::Encoding );
}
_make_alias_if_needed( @langtags ? @langtags : 'en_us' );
return @langtags;
}
my %_made_aliases;
sub _make_alias_if_needed {
foreach my $tag ( grep { ( $_ eq 'en' || $_ eq 'i_default' || $_ eq 'en_us' ) && !$_made_aliases{$_} } ( 'en', @_ ) ) {
Cpanel::Locale->make_alias( [$tag], 1 );
$_made_aliases{$tag} = 1;
}
return 0;
}
sub _real_get_handle {
my ( $class, @arg_langtags ) = @_;
my @langtags = _setup_for_real_get_handle( $class, @arg_langtags );
my $args_sig = join( ',', @langtags ) || 'no_args';
return (
( defined $singleton_stash{$class}{$args_sig} && ++$singleton_stash{$class}{$args_sig}->{'_singleton_reused'} )
? $singleton_stash{$class}{$args_sig}
: ( $singleton_stash{$class}{$args_sig} = Cpanel::CPAN::Locale::Maketext::get_handle( $class, @langtags ) )
);
}
sub get_non_singleton_handle {
my ( $class, @arg_langtags ) = @_;
my @langtags = _setup_for_real_get_handle( $class, @arg_langtags );
return Cpanel::CPAN::Locale::Maketext::get_handle( $class, @langtags );
}
sub init {
my ($lh) = @_;
$lh->SUPER::init();
$lh->_initialize_unknown_phrase_logging();
$lh->_initialize_bracket_notation_whitelist();
return $lh;
}
sub _initialize_unknown_phrase_logging {
my $lh = shift;
if ( defined $Cpanel::Locale::Context::DEFAULT_OUTPUT_CONTEXT ) { # PPI NO PARSE - Only needed if loaded
my $setter_cr = $lh->can("set_context_${Cpanel::Locale::Context::DEFAULT_OUTPUT_CONTEXT}") or do { # PPI NO PARSE - Only needed if loaded
die "Invalid \$Cpanel::Locale::Context::DEFAULT_OUTPUT_CONTEXT: “$Cpanel::Locale::Context::DEFAULT_OUTPUT_CONTEXT”!"; # PPI NO PARSE - Only needed if loaded
};
$setter_cr->($lh);
}
elsif ( defined $Cpanel::Carp::OUTPUT_FORMAT ) { # issafe
if ( $Cpanel::Carp::OUTPUT_FORMAT eq 'xml' ) { # issafe
$lh->set_context_plain(); # no HTML markup or ANSI escape sequences
}
elsif ( $Cpanel::Carp::OUTPUT_FORMAT eq 'html' ) { # issafe
$lh->set_context_html(); # HTML
}
}
$lh->{'use_external_lex_cache'} = 1;
if ( exists $Cpanel::CPDATA{'LOCALE_LOG_MISSING'} && $Cpanel::CPDATA{'LOCALE_LOG_MISSING'} ) {
$lh->{'_log_phantom_key'} = sub {
my ( $lh, $key ) = @_;
my $chain = '';
my $base_class = $lh->get_base_class();
foreach my $class ( $lh->get_language_class, $base_class ) {
my $lex_path = $lh->get_cdb_file_path( $class eq $base_class ? 1 : 0 );
next if !$lex_path;
$chain .= "\tLOCALE: $class\n\tPATH: $lex_path\n";
last if $class eq 'Cpanel::Locale::en' || $class eq 'Cpanel::Locale::en_us' || $class eq 'Cpanel::Locale::i_default';
}
my $pkg = $lh->get_language_tag();
_logger->info( ( $Cpanel::Parser::Vars::file ? "$Cpanel::Parser::Vars::file ::" : '' ) . qq{ Could not find key via '$pkg' locale:\n\tKEY: '$key'\n$chain} ); # PPI NO PARSE -- module will already be there is we care about it
};
}
return $lh;
}
our @DEFAULT_WHITELIST = qw(quant asis output current_year list_and list_or comment boolean datetime local_datetime format_bytes get_locale_name get_user_locale_name is_defined is_future join list_and_quoted list_or_quoted numerate numf);
sub _initialize_bracket_notation_whitelist {
my $lh = shift;
my @whitelist = @DEFAULT_WHITELIST;
my $custom_whitelist_file = Cpanel::Locale::Utils::Paths::get_custom_whitelist_path();
if ( open( my $fh, '<', $custom_whitelist_file ) ) {
while ( my $ln = readline($fh) ) {
chomp $ln;
push @whitelist, $ln if length($ln);
}
close $fh;
}
$lh->whitelist(@whitelist);
return $lh;
}
sub output_cpanel_error {
my ( $lh, $position ) = @_;
if ( $lh->context_is_ansi() ) {
return "\e[1;31m" if $position eq 'begin';
return "\e[0m" if $position eq 'end';
return '';
}
elsif ( $lh->context_is_html() ) {
return qq{<p style="color:#FF0000">} if $position eq 'begin';
return '</p>' if $position eq 'end';
return '';
}
else {
return ''; # e.g. $lh->context_is_plain()
}
}
sub cpanel_get_3rdparty_lang {
my ( $lh, $_3rdparty ) = @_;
require Cpanel::Locale::Utils::3rdparty;
return Cpanel::Locale::Utils::3rdparty::get_app_setting( $lh, $_3rdparty ) || Cpanel::Locale::Utils::3rdparty::get_3rdparty_lang( $lh, $_3rdparty ) || $lh->get_language_tag() || 'en';
}
sub cpanel_is_valid_locale {
my ( $lh, $locale ) = @_;
my %valid_locales = map { $_ => 1 } ( qw(en en_us i_default), $lh->list_available_locales );
return $valid_locales{$locale} ? 1 : 0;
}
sub cpanel_get_3rdparty_list {
my ($lh) = @_;
require Cpanel::Locale::Utils::3rdparty;
return Cpanel::Locale::Utils::3rdparty::get_3rdparty_list($lh);
}
sub cpanel_get_lex_path {
my ( $lh, $path, $rv ) = @_;
return if !defined $path || $path eq '' || substr( $path, -3 ) ne '.js';
require Cpanel::JS::Variations;
my $query = $path;
$query = Cpanel::JS::Variations::get_base_file( $query, '-%s.js' );
if ( defined $rv && index( $rv, '%s' ) == -1 ) {
substr( $rv, -3, 3, '-%s.js' );
}
my $asset_path = $lh->get_asset_file( $query, $rv );
return $asset_path if $asset_path && substr( $asset_path, -3 ) eq '.js' && index( $asset_path, '-' ) > -1; # Only return a value if there is a localized js file here
return;
}
sub tag_is_default_locale {
my $tag = $_[1] || $_[0]->get_language_tag();
return 1 if $tag eq 'en' || $tag eq 'en_us' || $tag eq 'i_default';
return;
}
sub get_cdb_file_path {
my ( $lh, $core ) = @_;
my $class = $core ? $lh->get_base_class() : $lh->get_language_class();
no strict 'refs';
return
$class eq 'Cpanel::Locale::en'
|| $class eq 'Cpanel::Locale::en_us'
|| $class eq 'Cpanel::Locale::i_default' ? $Cpanel::Locale::CDB_File_Path : ${ $class . '::CDB_File_Path' };
}
sub _slurp_small_file_if_exists_no_exception {
my ($path) = @_;
local ( $!, $^E );
open my $rfh, '<', $path or do {
if ( $! != _ENOENT() ) {
warn "open($path): $!";
}
return undef;
};
read $rfh, my $buf, 8192 or do {
warn "read($path): $!";
};
return $buf;
}
my $_server_locale_file_contents;
sub get_server_locale {
if ( exists $ENV{'CPANEL_SERVER_LOCALE'} ) {
return $ENV{'CPANEL_SERVER_LOCALE'} if $ENV{'CPANEL_SERVER_LOCALE'} !~ tr{A-Za-z0-9_-}{}c;
return undef;
}
if (%main::CPCONF) {
return $main::CPCONF{'server_locale'} if exists $main::CPCONF{'server_locale'};
}
return ( $_server_locale_file_contents //= ( _slurp_small_file_if_exists_no_exception($SERVER_LOCALE_FILE) || '' ) );
}
sub _clear_cache {
$_server_locale_file_contents = undef;
return;
}
sub get_locale_for_user_cpanel {
if (%main::CPCONF) {
return $main::CPCONF{'cpanel_locale'} if exists $main::CPCONF{'cpanel_locale'};
return $main::CPCONF{'server_locale'} if exists $main::CPCONF{'server_locale'};
}
require Cpanel::Config::LoadCpConf;
my $cpconf = Cpanel::Config::LoadCpConf::loadcpconf_not_copy(); # safe since we do not modify cpconf
return $cpconf->{'cpanel_locale'} if $cpconf->{'cpanel_locale'}; # will not be autovivified, 0 and "" are invalid, if the value is invalid they will get 'en'
return $cpconf->{'server_locale'} if $cpconf->{'server_locale'}; # will not be autovivified, 0 and "" are invalid, if the value is invalid they will get 'en'
return;
}
sub cpanel_reinit_lexicon {
my ($lh) = @_;
$lh->cpanel_detach_lexicon();
$lh->cpanel_attach_lexicon();
}
my $detach_locale_lex;
sub cpanel_detach_lexicon {
my ($lh) = @_;
my $locale = $lh->get_language_tag();
no strict 'refs';
undef $Cpanel::Locale::CDB_File_Path;
if ( $locale ne 'en' && $locale ne 'en_us' && $locale ne 'i_default' ) {
$detach_locale_lex = ${ 'Cpanel::Locale::' . $locale . '::CDB_File_Path' };
undef ${ 'Cpanel::Locale::' . $locale . '::CDB_File_Path' };
}
untie( %{ 'Cpanel::Locale::' . $locale . '::Lexicon' } );
untie %Cpanel::Locale::Lexicon;
}
sub cpanel_attach_lexicon {
my ($lh) = @_;
my $locale = $lh->get_language_tag();
$Cpanel::Locale::CDB_File_Path = Cpanel::Locale::Utils::init_lexicon( 'en', \%Cpanel::Locale::Lexicon, \$Cpanel::Locale::VERSION, \$Cpanel::Locale::Encoding );
_make_alias_if_needed($locale);
no strict 'refs';
if ( defined $detach_locale_lex ) {
${ 'Cpanel::Locale::' . $locale . '::CDB_File_Path' } = $detach_locale_lex;
}
else {
${ 'Cpanel::Locale::' . $locale . '::CDB_File_Path' } = $Cpanel::Locale::CDB_File_Path;
}
my $file_path = $lh->get_cdb_file_path();
return if !$file_path;
return Cpanel::Locale::Utils::get_readonly_tie( $lh->get_cdb_file_path(), \%{ 'Cpanel::Locale::' . $locale . '::Lexicon' } );
}
sub is_rtl {
my ($lh) = @_;
return 'right-to-left' eq $lh->get_language_tag_character_orientation() ? 1 : 0;
}
sub get_language_tag_character_orientation {
if ( my $direction = $known_locales_character_orientation{ $_[1] || $_[0]->{'fallback_locale'} || $_[0]->get_language_tag() } ) {
return 'right-to-left' if $direction == $RTL;
return 'left-to-right';
}
$_[0]->SUPER::get_language_tag_character_orientation( @_[ 1 .. $#_ ] );
}
my $menu_ar;
sub get_locale_menu_arrayref {
return $menu_ar if $menu_ar;
require Cpanel::Locale::Utils::Display;
$menu_ar = [ Cpanel::Locale::Utils::Display::get_locale_menu_hashref(@_) ]; # always array context to get all structs, properly uses other args besides object
return $menu_ar;
}
my $non_existent;
sub get_non_existent_locale_menu_arrayref {
return $non_existent if $non_existent;
require Cpanel::Locale::Utils::Display;
$non_existent = [ Cpanel::Locale::Utils::Display::get_non_existent_locale_menu_hashref(@_) ]; # always array context to get all structs, properly uses other args besides object
return $non_existent;
}
sub _api1_maketext {
require Cpanel::Locale::Utils::Api1;
goto \&Cpanel::Locale::Utils::Api1::_api1_maketext; ## no extract maketext
}
our $api1 = {
'maketext' => { ## no extract maketext
'function' => \&_api1_maketext, ## no extract maketext
'internal' => 1,
'legacy_function' => 2,
'modify' => 'inherit',
},
};
sub current_year {
return (localtime)[5] + 1900; # we override datetime() so we can't use the internal current_year()
}
sub local_datetime {
my ( $lh, $epoch, $format ) = @_;
my $timezone = $ENV{'TZ'} // do {
require Cpanel::Time::TZ;
Cpanel::Time::TZ::calculate_TZ_env();
};
return $lh->datetime( $epoch, $format, $timezone );
}
sub datetime {
my ( $lh, $epoch, $format, $timezone ) = @_;
require Cpanel::Locale::Utils::DateTime;
if ( $epoch && $epoch =~ tr<0-9><>c ) {
require Cpanel::Validate::Time;
Cpanel::Validate::Time::iso_or_die($epoch);
require Cpanel::Time::ISO;
$epoch = Cpanel::Time::ISO::iso2unix($epoch);
}
return Cpanel::Locale::Utils::DateTime::datetime( $lh, $epoch, $format, $timezone );
}
sub get_lookup_hash_of_multi_epoch_datetime {
my ( $lh, $epochs_ar, $format, $timezone ) = @_;
require Cpanel::Locale::Utils::DateTime;
return Cpanel::Locale::Utils::DateTime::get_lookup_hash_of_multi_epoch_datetime( $lh, $epochs_ar, $format, $timezone );
}
sub get_locale_name_or_nothing {
my ( $locale, $name, $in_locale_tongue ) = @_;
$name ||= $locale->get_language_tag();
if ( index( $name, 'i_' ) == 0 ) {
require Cpanel::DataStore;
my $i_locales_path = Cpanel::Locale::Utils::Paths::get_i_locales_config_path();
my $i_conf = Cpanel::DataStore::fetch_ref("$i_locales_path/$name.yaml");
return $i_conf->{'display_name'} if $i_conf->{'display_name'};
}
else {
my $real = $locale->get_language_tag_name( $name, $in_locale_tongue );
return $real if $real;
}
return;
}
sub get_locale_name_or_tag {
return $_[0]->get_locale_name_or_nothing( $_[1], $_[2] ) || $_[1] || $_[0]->get_language_tag();
}
*get_locale_name = *get_locale_name_or_tag; # for shorter BN
sub get_user_locale {
return $Cpanel::CPDATA{'LOCALE'} if $Cpanel::CPDATA{'LOCALE'};
require Cpanel::Locale::Utils::User; # probably a no-op but just in case since its loading is conditional
return Cpanel::Locale::Utils::User::get_user_locale();
}
sub get_user_locale_name {
require Cpanel::Locale::Utils::User; # probably a no-op but just in case since its loading is conditional
return $_[0]->get_locale_name_or_tag( Cpanel::Locale::Utils::User::get_user_locale( $_[1] ) );
}
sub set_user_locale {
my ( $locale, $country_code ) = @_;
if ($country_code) {
my $language_name = $locale->lang_names_hashref();
if ( exists $language_name->{$country_code} ) {
require Cpanel::Locale::Utils::Legacy;
require Cpanel::Locale::Utils::User::Modify;
my $language = Cpanel::Locale::Utils::Legacy::get_best_guess_of_legacy_from_locale($country_code);
if ( Cpanel::Locale::Utils::User::Modify::save_user_locale( $country_code, $language, $Cpanel::user ) ) {
return 1;
}
}
}
die Cpanel::Exception::create_raw( "Empty", $locale->maketext("Unable to set locale, please specify a valid country code.") );
}
sub get_locales {
my $locale = shift;
my @listing;
my ( $names, $local_names ) = $locale->lang_names_hashref();
foreach ( keys %{$names} ) {
push @listing, {
locale => $_,
name => $names->{$_},
local_name => $local_names->{$_},
direction => ( !defined $known_locales_character_orientation{$_} || $known_locales_character_orientation{$_} == $LTR ) ? 'ltr' : 'rtl'
};
}
return \@listing;
}
my $api2_lh;
sub api2_get_user_locale {
$api2_lh ||= Cpanel::Locale->get_handle();
return ( { 'locale' => $api2_lh->get_user_locale() } );
}
sub api2_get_user_locale_name {
$api2_lh ||= Cpanel::Locale->get_handle();
return ( { 'name' => $api2_lh->get_user_locale_name() } );
}
sub api2_get_locale_name {
$api2_lh ||= Cpanel::Locale->get_handle();
my $tag = ( scalar @_ > 2 ) ? {@_}->{'locale'} : $_[1];
return ( { 'name' => $api2_lh->get_locale_name_or_tag($tag) } );
}
sub api2_get_encoding {
$api2_lh ||= Cpanel::Locale->get_handle();
return ( { 'encoding' => $api2_lh->encoding() } );
}
sub api2_numf {
my %args = @_;
$api2_lh ||= Cpanel::Locale->get_handle();
return ( { 'numf' => $api2_lh->numf( $args{number}, $args{max_decimal_places} ) } );
}
sub api2_get_html_dir_attr {
$api2_lh ||= Cpanel::Locale->get_handle();
return ( { 'dir' => $api2_lh->get_html_dir_attr() } );
}
my $allow_demo = { allow_demo => 1 };
our %API = (
get_locale_name => $allow_demo,
get_encoding => $allow_demo,
get_html_dir_attr => $allow_demo,
get_user_locale => $allow_demo,
get_user_locale_name => $allow_demo,
numf => $allow_demo,
);
sub api2 {
my ($func) = @_;
return { %{ $API{$func} } } if $API{$func};
return;
}
my $global_lh;
sub lh {
return ( $global_lh ||= Cpanel::Locale->get_handle() );
}
sub import {
my ( $package, @args ) = @_;
my ($namespace) = caller;
if ( @args == 1 && $args[0] eq 'lh' ) {
no strict 'refs'; ## no critic(ProhibitNoStrict)
my $exported_name = "${namespace}::lh";
*$exported_name = \*lh;
}
}
sub _load_carp {
if ( !$INC{'Cpanel/Carp.pm'} ) {
local $@;
eval 'require Cpanel::Carp; 1;' or die $@; # hide from perlcc
}
return;
}
1;
} # --- END Cpanel/Locale.pm
{ # --- BEGIN Cpanel/Sys/Uname.pm
package Cpanel::Sys::Uname;
use strict;
our $SYS_UNAME = 63;
our $UNAME_ELEMENTS = 6;
our $_UTSNAME_LENGTH = 65;
my $UNAME_PACK_TEMPLATE = ( 'c' . $_UTSNAME_LENGTH ) x $UNAME_ELEMENTS;
my $UNAME_UNPACK_TEMPLATE = ( 'Z' . $_UTSNAME_LENGTH ) x $UNAME_ELEMENTS;
my @uname_cache;
sub get_uname_cached {
return ( @uname_cache ? @uname_cache : ( @uname_cache = syscall_uname() ) );
}
sub clearcache {
@uname_cache = ();
return;
}
sub syscall_uname {
my $uname;
if ( syscall( $SYS_UNAME, $uname = pack( $UNAME_PACK_TEMPLATE, () ) ) == 0 ) {
return unpack( $UNAME_UNPACK_TEMPLATE, $uname );
}
else {
die "The uname() system call failed because of an error: $!";
}
return;
}
1;
} # --- END Cpanel/Sys/Uname.pm
{ # --- BEGIN Cpanel/Sys/Hostname/Fallback.pm
package Cpanel::Sys::Hostname::Fallback;
use strict;
use warnings;
use Socket ();
# use Cpanel::Sys::Uname ();
sub get_canonical_hostname {
my @uname = Cpanel::Sys::Uname::get_uname_cached();
my ( $err, @results ) = Socket::getaddrinfo( $uname[1], 0, { flags => Socket::AI_CANONNAME() } );
if ( @results && $results[0]->{'canonname'} ) {
return $results[0]->{'canonname'};
}
return undef;
}
1;
} # --- END Cpanel/Sys/Hostname/Fallback.pm
{ # --- BEGIN Cpanel/Sys/Hostname.pm
package Cpanel::Sys::Hostname;
use strict;
use warnings;
our $VERSION = 2.0;
# use Cpanel::Sys::Uname ();
our $cachedhostname = '';
sub gethostname {
my $nocache = shift || 0;
if ( !$nocache && length $cachedhostname ) { return $cachedhostname }
my $hostname = _gethostname();
if ( length $hostname ) {
$hostname =~ tr{A-Z}{a-z}; # hostnames must be lowercase (see Cpanel::Sys::Hostname::Modify::make_hostname_lowercase_fqdn)
$cachedhostname = $hostname;
}
return $hostname;
}
sub _gethostname {
my $hostname;
my @uname = Cpanel::Sys::Uname::get_uname_cached();
if ( $uname[1] && index( $uname[1], '.' ) > -1 ) {
$hostname = $uname[1];
$hostname =~ tr{A-Z}{a-z}; # hostnames must be lowercase (see Cpanel::Sys::Hostname::Modify::make_hostname_lowercase_fqdn)
return $hostname;
}
eval {
require Cpanel::Sys::Hostname::Fallback;
$hostname = Cpanel::Sys::Hostname::Fallback::get_canonical_hostname();
};
if ($hostname) {
$hostname =~ tr{A-Z}{a-z}; # hostnames must be lowercase (see Cpanel::Sys::Hostname::Modify::make_hostname_lowercase_fqdn)
return $hostname;
}
require Cpanel::LoadFile;
chomp( $hostname = Cpanel::LoadFile::loadfile( '/proc/sys/kernel/hostname', { 'skip_exists_check' => 1 } ) );
if ($hostname) {
$hostname =~ tr{A-Z}{a-z}; # hostnames must be lowercase (see Cpanel::Sys::Hostname::Modify::make_hostname_lowercase_fqdn)
$hostname =~ tr{\r\n}{}d; # chomp is not enough (not sure if this is required, however we cannot test all kernels so its safer to leave it in)
return $hostname;
}
require Cpanel::Debug;
Cpanel::Debug::log_warn('Unable to determine correct hostname');
return;
}
sub shorthostname {
my $hostname = gethostname();
return $hostname if index( $hostname, '.' ) == -1; # Hostname is not a FQDN (this should never happen)
return substr( $hostname, 0, index( $hostname, '.' ) );
}
1;
} # --- END Cpanel/Sys/Hostname.pm
{ # --- BEGIN Cpanel/Hostname.pm
package Cpanel::Hostname;
use strict;
use warnings;
# use Cpanel::Sys::Hostname ();
our $VERSION = 2.0;
{
no warnings 'once';
*gethostname = *Cpanel::Sys::Hostname::gethostname;
*shorthostname = *Cpanel::Sys::Hostname::shorthostname;
}
1;
} # --- END Cpanel/Hostname.pm
{ # --- BEGIN Cpanel/Config/CpConfGuard/CORE.pm
package Cpanel::Config::CpConfGuard::CORE;
use strict;
use warnings;
# use Cpanel::ConfigFiles ();
# use Cpanel::Debug ();
# use Cpanel::FileUtils::Write::JSON::Lazy ();
# use Cpanel::LoadModule ();
# use Cpanel::Config::CpConfGuard ();
our $SENDING_MISSING_FILE_NOTICE = 0;
my $FILESYS_PERMS = 0644;
sub find_missing_keys {
my ($self) = @_;
_verify_called_as_object_method($self);
Cpanel::LoadModule::load_perl_module('Cpanel::Config::CpConfGuard::Default');
my $default = 'Cpanel::Config::CpConfGuard::Default'->new(
current_config => $self->{data},
current_changes => $self->{changes},
);
if ( $self->{'is_missing'} ) {
if ( $self->{'cache'} && ref( $self->{'cache'} ) eq 'HASH' && scalar keys %{ $self->{'cache'} } ) {
$self->{'data'} = {};
%{ $self->{'data'} } = %{ $self->{'cache'} };
my $config = $self->{'data'};
foreach my $key ( $default->get_keys() ) {
next if exists $config->{$key};
$config->{$key} = $default->get_default_for($key);
}
}
else {
$self->{'data'} = $default->get_all_defaults();
}
$self->{'modified'} = 1; # Mark as save needed.
return;
}
my $cache = $self->{'cache'};
undef( $self->{'cache'} ); # we do not need the cache after the first pass
my $config = $self->{'data'};
my $changes = $self->{'changes'}; # used for notifications
$config->{'tweak_unset_vars'} ||= '';
foreach my $key ( $default->get_keys() ) {
next if exists $config->{$key};
$self->{'modified'} = 1; # Mark as save needed.
if ( exists $cache->{$key} ) {
$config->{$key} = $cache->{$key};
$changes->{'from_cache'} ||= [];
push @{ $changes->{'from_cache'} }, $key;
$changes->{'changed_keys'} ||= {};
$changes->{'changed_keys'}{$key} = 'from_cache';
next;
}
my $changes_type = $default->is_dynamic($key) ? 'from_dynamic' : 'from_default';
$changes->{'changed_keys'} ||= {};
$changes->{'changed_keys'}{$key} = $changes_type;
$changes->{$changes_type} ||= [];
push @{ $changes->{$changes_type} }, $key;
$config->{$key} = $default->get_default_for($key);
}
foreach my $key ( @{ $default->dead_variables() } ) {
next unless exists $config->{$key};
$self->{'modified'} = 1; # Mark as save needed.
delete( $config->{$key} );
$changes->{'dead_variable'} ||= [];
push @{ $changes->{'dead_variable'} }, $key;
}
return;
}
sub validate_keys {
my ($self) = @_;
_verify_called_as_object_method($self);
Cpanel::LoadModule::load_perl_module('Cpanel::Config::CpConfGuard::Validate');
my $invalid = 'Cpanel::Config::CpConfGuard::Validate'->can('patch_cfg')->( $self->{'data'} );
if (%$invalid) {
$self->{modified} = 1;
$self->{'changes'}->{'invalid'} = $invalid;
}
return;
}
sub notify_and_save_if_changed {
my ($self) = @_;
_verify_called_as_object_method($self);
return if !$self->{'use_lock'};
return if !$self->{'modified'};
my $config = $self->{'data'};
if ( $ENV{'CPANEL_BASE_INSTALL'} ) {
; # Do nothing for notification.
}
elsif ( $self->{'is_missing'} ) {
$config->{'tweak_unset_vars'} = '';
Cpanel::Debug::log_warn("Missing cpanel.config regenerating …");
$self->notify_missing_file;
}
elsif ( %{ $self->{'changes'} } ) {
my $changes = $self->{'changes'};
my %uniq = map { $_ => 1 } @{ $changes->{'from_default'} || [] }, @{ $changes->{'from_dynamic'} || [] }, split( /\s*,\s*/, $config->{'tweak_unset_vars'} );
$config->{'tweak_unset_vars'} = join ",", sort keys %uniq;
$self->log_missing_values();
}
return $self->save( keep_lock => 1 );
}
sub _server_locale {
my ($self) = @_;
_verify_called_as_object_method($self);
my $locale_name = $self->{'data'}->{'server_locale'} || 'en';
require Cpanel::Locale;
return Cpanel::Locale->_real_get_handle($locale_name);
}
sub _longest {
my @array = @_;
return length( ( sort { length $b <=> length $a } @array )[0] );
}
sub _stringify_undef {
my $value = shift;
return defined $value ? $value : '<undef>';
}
sub log_missing_values {
my ($self) = @_;
require Cpanel::Hostname;
my $changes = $self->{'changes'};
my $locale = $self->_server_locale();
my $hostname = Cpanel::Hostname::gethostname();
my $prev = $locale->set_context_plain();
my $message = '';
$message .= $locale->maketext( 'One or more key settings for “[_1]” were either not found in [asis,cPanel amp() WHM]’s server configuration file ([_2]), or were present but did not pass validation.', $hostname, $self->{'path'} ) . "\n";
if ( $changes->{'from_dynamic'} ) {
$message .= $locale->maketext('The following settings were absent and have been selected based on the current state of your installation.');
$message .= "\n";
my @keys = @{ $changes->{'from_dynamic'} };
my $max_len = _longest(@keys) + 2;
foreach my $key (@keys) {
$message .= sprintf( " %-${max_len}s= %s\n", $key, _stringify_undef( $self->{'data'}->{$key} ) );
}
$message .= "\n";
}
if ( $changes->{'from_cache'} ) {
$message .= $locale->maketext('The following settings were absent, but were restored from your [asis,cpanel.config.cache] file:');
$message .= "\n";
my @keys = @{ $changes->{'from_cache'} };
my $max_len = _longest(@keys) + 2;
foreach my $key (@keys) {
$message .= sprintf( " %-${max_len}s= %s\n", $key, _stringify_undef( $self->{'data'}->{$key} ) );
}
$message .= "\n";
}
if ( $changes->{'from_default'} or $changes->{'invalid'} ) {
$message .= $locale->maketext('The following settings were absent or invalid. Your server has copied the defaults for them from the configuration defaults file ([asis,/usr/local/cpanel/etc/cpanel.config]).');
$message .= "\n";
if ( $changes->{'from_default'} ) {
my @keys = @{ $changes->{'from_default'} };
my $max_len = _longest(@keys) + 2;
foreach my $key (@keys) {
$message .= sprintf( " %-${max_len}s= %s\n", $key, _stringify_undef( $self->{'data'}->{$key} ) );
}
}
if ( $changes->{'invalid'} ) {
my $invalid = $changes->{'invalid'};
my @keys = keys %$invalid;
my $max_len = _longest(@keys) + 2;
foreach my $key (@keys) {
$message .= sprintf( " %-${max_len}s= %s (Previously set to '%s')\n", $key, _stringify_undef( $invalid->{$key}->{'to'} ), _stringify_undef( $invalid->{$key}->{'from'} ) );
}
}
$message .= "\n";
}
if ( $changes->{'dead_variable'} ) {
$message .= $locale->maketext('The following settings are obsolete and have been removed from the server configuration file:');
$message .= "\n";
$message .= ' ' . join( ', ', @{ $changes->{'dead_variable'} } );
$message .= "\n\n";
}
$message .= $locale->maketext( 'Read the [asis,cpanel.config] file [output,url,_1,documentation] for important information about this file.', 'https://go.cpanel.net/cpconfig' );
$message .= "\n\n";
Cpanel::Debug::logger(); # initialize the logger
local $Cpanel::Logger::ENABLE_BACKTRACE = 0;
foreach my $chunk ( split( /\n+/, $message ) ) {
Cpanel::Debug::log_warn($chunk);
}
$locale->set_context($prev);
return;
}
sub notify_missing_file {
my ($self) = @_;
if ($SENDING_MISSING_FILE_NOTICE) {
return; #Already sending notification, don't double up
}
require Cpanel::Hostname;
local $SENDING_MISSING_FILE_NOTICE = 1;
my $locale = $self->_server_locale();
my $prev = $locale->set_context_plain();
my @to_log;
my %critical_values;
my $hostname = Cpanel::Hostname::gethostname();
push @to_log, $locale->maketext('Your server has copied the defaults from your cache and the configuration defaults file ([asis,/usr/local/cpanel/etc/cpanel.config]) to [asis,/var/cpanel/cpanel.config], and it has generated the following critical values:');
Cpanel::LoadModule::load_perl_module('Cpanel::Config::CpConfGuard::Default');
my $critical = Cpanel::Config::CpConfGuard::Default::critical_values();
my $max_len = _longest(@$critical) + 2;
my $critical_value;
foreach my $key ( sort @$critical ) {
$critical_value = _stringify_undef( $self->{'data'}->{$key} );
$critical_values{$key} = $critical_value;
push @to_log, sprintf( " %-${max_len}s= %s\n", $key, $critical_value );
}
push @to_log, $locale->maketext( 'Read the [asis,cpanel.config] file [output,url,_1,documentation] for more information about this file.', 'https://go.cpanel.net/cpconfig' ) . ' ';
Cpanel::Debug::logger(); # initialize the logger
local $Cpanel::Logger::ENABLE_BACKTRACE = 0;
foreach my $chunk (@to_log) {
chomp $chunk;
Cpanel::Debug::log_warn($chunk);
}
_icontact( \%critical_values );
$locale->set_context($prev);
return;
}
sub _icontact {
my $critical_values = shift;
Cpanel::LoadModule::load_perl_module("Cpanel::iContact::Class::Config::CpConfGuard");
Cpanel::LoadModule::load_perl_module('Cpanel::Notify');
'Cpanel::Notify'->can('notification_class')->(
'class' => 'Config::CpConfGuard',
'application' => 'Config::CpConfGuard',
'constructor_args' => [
'origin' => 'cpanel.config',
'critical_values' => $critical_values,
]
);
return;
}
sub save {
my ( $self, %opts ) = @_;
_verify_called_as_object_method($self);
return unless ( $self->{'use_lock'} );
return if ( $] > 5.007 && $] < 5.014 );
return 1 if $Cpanel::Config::CpConfGuard::memory_only;
if ( !$self->{'rw'} ) {
Cpanel::LoadModule::load_perl_module('Cpanel::SafeFile');
$self->{'fh'} = 'Cpanel::SafeFile'->can('safereopen')->( $self->{'fh'}, '+>', $Cpanel::ConfigFiles::cpanel_config_file );
return $self->abort('Cannot reopen file for rw') unless $self->{'fh'};
$self->{'rw'} = 1;
}
return $self->abort('Locked in parent, cannot save') if $self->{'pid'} != $$;
return $self->abort('hash reference required') if ref( $self->{'data'} ) ne 'HASH';
Cpanel::LoadModule::load_perl_module('Cpanel::Config::FlushConfig');
Cpanel::LoadModule::load_perl_module('Cpanel::Config::SaveCpConf');
'Cpanel::Config::FlushConfig'->can('flushConfig')->(
$self->{'fh'},
$self->{'data'},
'=',
'Cpanel::Config::SaveCpConf'->can('header_message')->(),
{
sort => 1,
perms => $FILESYS_PERMS,
},
);
%{$Cpanel::Config::CpConfGuard::MEM_CACHE} = %{ $self->{'data'} };
return 1 if $opts{keep_lock};
$self->release_lock;
return 1;
}
sub _update_cache {
my ($self) = @_;
_verify_called_as_object_method($self);
return 0 if Cpanel::Config::CpConfGuard::_cache_is_valid() && $self->{'cache_is_valid'}; # Don't re-write the file if it looks correct.
$Cpanel::Config::CpConfGuard::MEM_CACHE_CPANEL_CONFIG_MTIME = ( stat($Cpanel::ConfigFiles::cpanel_config_file) )[9] || 0;
return unless $self->{'use_lock'}; # never update the cache when not root
local $@;
my $ok = eval { Cpanel::FileUtils::Write::JSON::Lazy::write_file( $Cpanel::ConfigFiles::cpanel_config_cache_file, $Cpanel::Config::CpConfGuard::MEM_CACHE, $FILESYS_PERMS ) || 0 };
if ( !$ok ) {
if ( !defined $ok ) {
Cpanel::Debug::log_warn("Cannot update cache file: $Cpanel::ConfigFiles::cpanel_config_cache_file $@");
unlink $Cpanel::ConfigFiles::cpanel_config_cache_file;
return -1;
}
return;
}
my $past = ( stat($Cpanel::ConfigFiles::cpanel_config_cache_file) )[9] - 1;
return _adjust_timestamp_for( $Cpanel::ConfigFiles::cpanel_config_file => $past );
}
sub _adjust_timestamp_for {
my ( $f, $time ) = @_;
return unless defined $f && defined $time;
return 1 if utime( $time, $time, $f );
my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime($time);
my $stamp = sprintf( "%04d%02d%02d%02d%02d.%02d", $year + 1900, $mon + 1, $mday, $hour, $min, $sec );
unless ( _touch( $f => $stamp ) ) {
Cpanel::Debug::log_warn("Cannot update mtime on $f: $@");
return;
}
return 1;
}
sub _touch { # mainly created to easily mock that part during the tests
my ( $f, $stamp ) = @_;
return system( 'touch', '-t', $stamp, $f ) == 0 ? 1 : 0;
}
sub _verify_called_as_object_method {
if ( ref( $_[0] ) ne "Cpanel::Config::CpConfGuard" ) {
die '' . ( caller(0) )[3] . " was not called as an object method [" . ref( $_[0] ) . "]\n";
}
return;
}
sub abort {
my ( $self, $msg ) = @_;
_verify_called_as_object_method($self);
if ( $self->{'pid'} != $$ ) {
Cpanel::Debug::log_die('Locked in parent, cannot release lock');
return;
}
$self->release_lock();
Cpanel::Debug::log_die($msg) if $msg;
return 1;
}
sub set {
my ( $self, $k, $v ) = @_;
_verify_called_as_object_method($self);
return unless defined $k;
my $config = $self->{'data'};
$config->{$k} = $v;
if ( $config->{'tweak_unset_vars'} && index( $config->{'tweak_unset_vars'}, $k ) > -1 ) {
my %unset = map { ( $_ => 1 ) } split( /\s*,\s*/, $config->{'tweak_unset_vars'} );
delete( $unset{$k} );
$config->{'tweak_unset_vars'} = join( ',', sort keys %unset );
}
return 1;
}
1;
} # --- END Cpanel/Config/CpConfGuard/CORE.pm
{ # --- BEGIN Cpanel/Config/CpConfGuard.pm
package Cpanel::Config::CpConfGuard;
use strict;
use warnings;
# use Cpanel::JSON::FailOK ();
# use Cpanel::ConfigFiles ();
# use Cpanel::Debug ();
# use Cpanel::Destruct ();
use constant {
_ENOENT => 2,
};
our $IN_LOAD = 0;
our $SENDING_MISSING_FILE_NOTICE = 0;
my $FILESYS_PERMS = 0644;
my $is_daemon;
BEGIN {
$is_daemon = 0; # initialize the value in the begin block
if ( index( $0, 'updatenow' ) > -1
|| index( $0, 'cpsrvd' ) > -1
|| index( $0, 'cpdavd' ) > -1
|| index( $0, 'queueprocd' ) > -1
|| index( $0, 'tailwatchd' ) > -1
|| index( $0, 'cpanellogd' ) > -1
|| ( length $0 > 7 && substr( $0, -7 ) eq '.static' ) ) {
$is_daemon = 1;
}
}
my $module_file;
our ( $MEM_CACHE_CPANEL_CONFIG_MTIME, $MEM_CACHE ) = ( 0, undef );
our $memory_only;
sub _is_daemon { $is_daemon }; # for testing
sub clearcache {
$MEM_CACHE_CPANEL_CONFIG_MTIME = 0;
$MEM_CACHE = undef;
return;
}
sub new {
my ( $class, %opts ) = @_;
Cpanel::JSON::FailOK::LoadJSONModule() if !$is_daemon && !$INC{'Cpanel/JSON.pm'};
my $self = bless {
%opts, # to be improved
'path' => $Cpanel::ConfigFiles::cpanel_config_file,
'pid' => $$,
'modified' => 0,
'changes' => {},
}, $class;
$self->{'use_lock'} //= ( $> == 0 ) ? 1 : 0;
if ($memory_only) {
$self->{'data'} = ref($memory_only) eq 'HASH' ? $memory_only : {};
return $self;
}
( $self->{'cache'}, $self->{'cache_is_valid'} ) = get_cache();
return $self if $self->{'loadcpconf'} && $self->{'cache_is_valid'};
$self->load_cpconf_file();
return $self if $is_daemon || $opts{'no_validate'} || !$self->{'use_lock'};
$self->find_missing_keys();
$self->validate_keys();
$self->notify_and_save_if_changed();
return $self;
}
sub set {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::set;
}
sub config_copy {
my ($self) = @_;
_verify_called_as_object_method($self);
my $config = $self->{'data'} || $self->{'cache'} || {};
return {%$config};
}
sub find_missing_keys {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::find_missing_keys;
}
sub validate_keys {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::validate_keys;
}
sub notify_and_save_if_changed {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::notify_and_save_if_changed;
}
sub log_missing_values {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::log_missing_values;
}
sub notify_missing_file {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::notify_missing_file;
}
sub save {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::save;
}
sub release_lock {
my ($self) = @_;
_verify_called_as_object_method($self);
return unless $self->{'use_lock'} && defined $self->{'pid'} && $self->{'pid'} eq $$ && $self->{'lock'};
require Cpanel::SafeFile;
Cpanel::SafeFile::safeclose( $self->{'fh'}, $self->{'lock'}, sub { return $self->_update_cache() } );
$self->{'fh'} = $self->{'lock'} = undef;
$self->{'is_locked'} = 0;
return;
}
sub abort {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::abort;
}
sub _update_cache {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::_update_cache;
}
sub _server_locale {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::_server_locale;
}
sub get_cache {
my $cpanel_config_mtime = ( stat($Cpanel::ConfigFiles::cpanel_config_file) )[9] || 0;
my $verbose = ( defined $Cpanel::Debug::level ? $Cpanel::Debug::level : 0 ) >= 5;
if ( $MEM_CACHE && ref($MEM_CACHE) eq 'HASH' && $cpanel_config_mtime && $cpanel_config_mtime == $MEM_CACHE_CPANEL_CONFIG_MTIME ) {
Cpanel::Debug::log_info("loadcpconf memory cache hit") if $verbose;
return ( $MEM_CACHE, 1 );
}
clearcache(); # Invalidate the memory cache.
Cpanel::Debug::log_info("loadcpconf memory cache miss") if $verbose;
my $mtime_before_read;
if ( !$INC{'Cpanel/JSON.pm'} ) {
Cpanel::Debug::log_info("Cpanel::JSON not loaded. Skipping cache load.") if $verbose;
return ( undef, 0 );
}
elsif ( -e $Cpanel::ConfigFiles::cpanel_config_cache_file ) { # No need to do -r (costs 5 additional syscalls) since we write this 0644
$mtime_before_read = ( stat _ )[9] || 0;
}
else {
Cpanel::Debug::log_info("The cache file “$Cpanel::ConfigFiles::cpanel_config_cache_file” could not be read. Skipping cache load.") if $verbose;
return ( undef, 0 );
}
my ( $mtime_after_read, $cpconf_ref ) = (0);
my $loop_count = 0;
while ( $mtime_after_read != $mtime_before_read && $loop_count++ < 10 ) {
sleep 1 if ( $mtime_after_read == time ); # If it was just written to, give it a second in case it's being written to.
Cpanel::Debug::log_info( "loadcpconf cache_filesys_mtime = $mtime_before_read , filesys_mtime: $cpanel_config_mtime , memory_mtime: $MEM_CACHE_CPANEL_CONFIG_MTIME , now: " . time ) if $verbose;
$cpconf_ref = Cpanel::JSON::FailOK::LoadFile($Cpanel::ConfigFiles::cpanel_config_cache_file);
$mtime_after_read = ( stat($Cpanel::ConfigFiles::cpanel_config_cache_file) )[9] || 0;
sleep 1 if ( $mtime_after_read != $mtime_before_read );
}
if ( $cpconf_ref && scalar keys %{$cpconf_ref} ) {
if ( _cache_is_valid( $cpanel_config_mtime, $mtime_after_read ) ) {
Cpanel::Debug::log_info("loadcpconf file system cache hit") if $verbose;
( $MEM_CACHE, $MEM_CACHE_CPANEL_CONFIG_MTIME ) = ( $cpconf_ref, $cpanel_config_mtime );
return ( $cpconf_ref, 1 );
}
Cpanel::Debug::log_info("loadcpconf cpanel.config.cache miss.") if $verbose;
return ( $cpconf_ref, 0 );
}
Cpanel::Debug::log_info("loadcpconf cpanel.config.cache miss.") if $verbose;
return ( undef, 0 );
}
sub _cache_is_valid {
my ( $config_mtime, $cache_mtime ) = @_;
$cache_mtime ||= ( stat($Cpanel::ConfigFiles::cpanel_config_cache_file) )[9] || 0;
return 0 unless $cache_mtime;
$config_mtime ||= ( stat($Cpanel::ConfigFiles::cpanel_config_file) )[9] || 0;
return 0 unless $config_mtime;
return ( $config_mtime + 1 == $cache_mtime ) ? 1 : 0;
}
sub load_cpconf_file {
my ($self) = @_;
if ($IN_LOAD) {
require Cpanel::Carp;
die Cpanel::Carp::safe_longmess("Load loop detected");
}
local $IN_LOAD = 1;
_verify_called_as_object_method($self);
my $config = {};
my $config_file = $Cpanel::ConfigFiles::cpanel_config_file;
$self->{'is_missing'} = ( -e $config_file ) ? 0 : 1;
return if ( !$self->{'use_lock'} && $self->{'is_missing'} ); # We can't do anything if the file is missing and we're not root. ABORT!
if ( $self->{'use_lock'} && $self->{'is_missing'} ) {
if ( open( my $touch_fh, '>>', $config_file ) ) {
print {$touch_fh} '';
close $touch_fh;
chown 0, 0, $config_file; # avoid pulling in Cpanel::PwCache for memory reasons
chmod 0644, $config_file;
}
}
$self->{'rw'} = 0;
$self->{'rw'} = 1 if ( $self->{'use_lock'} && !$self->{'cache_is_valid'} );
require Cpanel::Config::LoadConfig;
my ( $ref, $fh, $conflock, $err ) = Cpanel::Config::LoadConfig::loadConfig(
$Cpanel::ConfigFiles::cpanel_config_file,
$config,
(undef) x 4,
{
'keep_locked_open' => !!$self->{'use_lock'},
'nocache' => 1,
'rw' => $self->{'rw'},
'allow_undef_values' => 1,
},
);
if ( !$ref && !$fh && $! != _ENOENT() ) {
$err ||= '(unknown error)';
require Cpanel::Carp;
die Cpanel::Carp::safe_longmess("Can’t read “$Cpanel::ConfigFiles::cpanel_config_file” ($err)");
}
$self->{'fh'} = $fh;
$self->{'lock'} = $conflock;
$self->{'data'} = $config;
if ( $self->{'use_lock'} ) {
Cpanel::Debug::log_warn("Failed to establish lock on $Cpanel::ConfigFiles::cpanel_config_file") unless $self->{'lock'};
Cpanel::Debug::log_warn("Failed to get file handle for $Cpanel::ConfigFiles::cpanel_config_file") unless $self->{'fh'};
}
$self->{'is_locked'} = defined $self->{'lock'} ? 1 : 0; # alias for external usage
if ( !$MEM_CACHE ) {
$MEM_CACHE = {};
%$MEM_CACHE = %$config;
}
return;
}
sub _verify_called_as_object_method {
if ( ref( $_[0] ) ne __PACKAGE__ ) {
die '' . ( caller(0) )[3] . " was not called as an object method [" . ref( $_[0] ) . "]\n";
}
return;
}
sub DESTROY { ## no critic(RequireArgUnpacking)
return 1 if ( $> || $memory_only ); # Special modes we don't or won't write to cpanel.config files.
return 2 if ( !$_[0] || !keys %{ $_[0] } ); # Nothing to cleanup if we're just a blessed empty hash.
return if !$_[0]->{'lock'};
return if Cpanel::Destruct::in_dangerous_global_destruction();
$_[0]->release_lock(); # Close the file so we can update the cache properly.
return;
}
1;
} # --- END Cpanel/Config/CpConfGuard.pm
{ # --- BEGIN Cpanel/Config/LoadCpConf.pm
package Cpanel::Config::LoadCpConf;
use strict;
use warnings;
# use Cpanel::Config::CpConfGuard ();
sub loadcpconf {
my $cpconf = Cpanel::Config::CpConfGuard->new( 'loadcpconf' => 1 )->config_copy;
return wantarray ? %$cpconf : $cpconf;
}
sub loadcpconf_not_copy {
if ( !defined $Cpanel::Config::CpConfGuard::memory_only && $Cpanel::Config::CpConfGuard::MEM_CACHE_CPANEL_CONFIG_MTIME ) {
my ( $cache, $cache_is_valid ) = Cpanel::Config::CpConfGuard::get_cache();
if ($cache_is_valid) {
return wantarray ? %$cache : $cache;
}
}
my $cpconf_obj = Cpanel::Config::CpConfGuard->new( 'loadcpconf' => 1 );
my $cpconf = $cpconf_obj->{'data'} || $cpconf_obj->{'cache'} || {};
return wantarray ? %$cpconf : $cpconf;
}
sub clearcache;
*clearcache = *Cpanel::Config::CpConfGuard::clearcache;
1;
} # --- END Cpanel/Config/LoadCpConf.pm
{ # --- BEGIN Cpanel/Maxmem.pm
package Cpanel::Maxmem;
use strict;
use warnings;
# use Cpanel::Config::LoadUserDomains::Count ();
use constant _INITIAL_DEFAULT => 4096;
sub _count_domains {
return eval { Cpanel::Config::LoadUserDomains::Count::countuserdomains() } // 1;
}
sub minimum {
return _INITIAL_DEFAULT() * ( 1 + int( _count_domains() / 10_000 ) );
}
*default = *minimum;
1;
} # --- END Cpanel/Maxmem.pm
{ # --- BEGIN Cpanel/OSSys/Bits.pm
package Cpanel::OSSys::Bits;
use strict;
use warnings;
our $MAX_32_BIT_SIGNED;
our $MAX_32_BIT_UNSIGNED;
our $MAX_64_BIT_SIGNED;
our $MAX_64_BIT_UNSIGNED;
our $MAX_NATIVE_SIGNED;
our $MAX_NATIVE_UNSIGNED;
sub getbits {
return length( pack( 'l!', 1000 ) ) * 8;
}
BEGIN {
$MAX_32_BIT_UNSIGNED = ( 1 << 32 ) - 1;
$MAX_32_BIT_SIGNED = ( 1 << 31 ) - 1;
$MAX_64_BIT_UNSIGNED = ~0; #true on both 32- and 64-bit systems
$MAX_64_BIT_SIGNED = -1 >> 1; #true on both 32- and 64-bit systems
if ( getbits() == 32 ) {
$MAX_NATIVE_SIGNED = $MAX_32_BIT_SIGNED;
$MAX_NATIVE_UNSIGNED = $MAX_32_BIT_UNSIGNED;
}
else {
$MAX_NATIVE_SIGNED = $MAX_64_BIT_SIGNED;
$MAX_NATIVE_UNSIGNED = $MAX_64_BIT_UNSIGNED;
}
}
1;
} # --- END Cpanel/OSSys/Bits.pm
{ # --- BEGIN Cpanel/Pack.pm
package Cpanel::Pack;
use strict;
sub new {
my ( $class, $template_ar ) = @_;
if ( @$template_ar % 2 ) {
die "Cpanel::Pack::new detected an odd number of elements in hash assignment!";
}
my $self = bless {
'template_str' => '',
'keys' => [],
}, $class;
my $ti = 0;
while ( $ti < $#$template_ar ) {
push @{ $self->{'keys'} }, $template_ar->[$ti];
$self->{'template_str'} .= $template_ar->[ 1 + $ti ];
$ti += 2;
}
return $self;
}
sub unpack_to_hashref { ## no critic (RequireArgUnpacking)
my %result;
@result{ @{ $_[0]->{'keys'} } } = unpack( $_[0]->{'template_str'}, $_[1] );
return \%result;
}
sub pack_from_hashref {
my ( $self, $opts_ref ) = @_;
no warnings 'uninitialized';
return pack( $self->{'template_str'}, @{$opts_ref}{ @{ $self->{'keys'} } } );
}
sub sizeof {
my ($self) = @_;
return ( $self->{'sizeof'} ||= length pack( $self->{'template_str'}, () ) );
}
sub malloc {
my ($self) = @_;
return pack( $self->{'template_str'} );
}
1;
} # --- END Cpanel/Pack.pm
{ # --- BEGIN Cpanel/Syscall.pm
package Cpanel::Syscall;
use strict;
my %NAME_TO_NUMBER = qw(
close 3
fcntl 72
lchown 94
getrlimit 97
getsid 124
gettimeofday 96
sendfile 40
setrlimit 160
splice 275
write 1
setsid 112
getsid 124
inotify_init1 294
inotify_add_watch 254
inotify_rm_watch 255
setresuid 117
setresgid 119
setgroups 116
umount2 166
);
sub name_to_number {
my ($name) = @_;
return $NAME_TO_NUMBER{$name} || _die_unknown_syscall($name);
}
sub _die_unknown_syscall {
my ($name) = @_;
die "Unknown system call: “$name”";
}
sub syscall { ##no critic qw(RequireArgUnpacking)
local $!;
_die_unknown_syscall( $_[0] ) unless defined $_[0] && $NAME_TO_NUMBER{ $_[0] };
my $ret = CORE::syscall( $NAME_TO_NUMBER{ $_[0] }, scalar @_ > 1 ? @_[ 1 .. $#_ ] : () );
if ( ( $ret == -1 ) && $! ) {
if ( $INC{'Cpanel/Exception.pm'} ) {
die Cpanel::Exception::create( 'SystemCall', [ name => $_[0], error => $!, arguments => [ @_[ 1 .. $#_ ] ] ] );
}
else {
die "Failed system call “$_[0]”: $!";
}
}
return $ret;
}
1;
} # --- END Cpanel/Syscall.pm
{ # --- BEGIN Cpanel/Sys/Rlimit.pm
package Cpanel::Sys::Rlimit;
use strict;
use warnings;
# use Cpanel::OSSys::Bits ();
# use Cpanel::Pack ();
# use Cpanel::Syscall ();
my $SYS_getrlimit;
my $SYS_setrlimit;
our $RLIM_INFINITY; # denotes no limit on a resource
our %RLIMITS = (
'CPU' => 0, # CPU time limit in seconds.
'DATA' => 2, # The maximum size of the process's data segment
'CORE' => 4, # Maximum size of a core file
'RSS' => 5, # Specifies the limit (in pages) of the process's resident set
'NPROC' => 6, # The maximum number of processes
'NOFILE' => 7, # The maximum number of file descriptors
'AS' => 9, # The maximum size of the process's virtual memory
'FSIZE' => 1,
'STACK' => 3,
'MEMLOCK' => 8,
'LOCKS' => 10,
'SIGPENDING' => 11,
'MSGQUEUE' => 12,
'NICE' => 13,
'RTPRIO' => 14,
'RTTIME' => 15,
);
BEGIN {
$RLIM_INFINITY = $Cpanel::OSSys::Bits::MAX_NATIVE_UNSIGNED;
}
our $PACK_TEMPLATE = 'L!L!';
our @TEMPLATE = (
rlim_cur => 'L!', # unsigned long
rlim_max => 'L!', # unsigned long
);
sub getrlimit {
my ($rlimit) = @_;
local $!;
die "getrlimit requires an rlimit constant" if !defined $rlimit;
my $buffer = pack( $PACK_TEMPLATE, 0 );
my $rlimit_num = _rlimit_to_num($rlimit);
Cpanel::Syscall::syscall( 'getrlimit', $rlimit_num, $buffer );
my $getrlimit_hr = Cpanel::Pack->new( \@TEMPLATE )->unpack_to_hashref($buffer);
return ( $getrlimit_hr->{'rlim_cur'}, $getrlimit_hr->{'rlim_max'} );
}
sub setrlimit {
my ( $rlimit, $soft, $hard ) = @_;
local $!;
die "setrlimit requires an rlimit constant" if !defined $rlimit;
die "setrlimit requires a soft limit" if !defined $soft;
die "setrlimit requires a hard limit" if !defined $hard;
my $buffer = pack( $PACK_TEMPLATE, $soft, $hard );
my $rlimit_num = _rlimit_to_num($rlimit);
Cpanel::Syscall::syscall( 'setrlimit', $rlimit_num, $buffer );
return 1;
}
sub _rlimit_to_num {
my ($rlimit) = @_;
if ( length($rlimit) && $rlimit !~ tr<0-9><>c ) {
return $rlimit;
}
elsif ( exists $RLIMITS{$rlimit} ) {
return $RLIMITS{$rlimit};
}
die "Unknown RLIMIT: $rlimit";
}
1;
} # --- END Cpanel/Sys/Rlimit.pm
{ # --- BEGIN Cpanel/Rlimit.pm
package Cpanel::Rlimit;
use strict;
# use Cpanel::Config::LoadCpConf ();
# use Cpanel::Maxmem ();
# use Cpanel::Sys::Rlimit ();
sub set_rlimit {
my ( $limit, $limit_names ) = @_;
my ( $default_rlimit, $coredump_are_enabled ) = _get_server_setting_or_default();
$limit ||= $default_rlimit || $Cpanel::Sys::Rlimit::RLIM_INFINITY;
$limit_names ||= [qw/RSS AS/];
my $core_limit = $coredump_are_enabled ? $limit : 0;
if ( $limit > $Cpanel::Sys::Rlimit::RLIM_INFINITY ) {
require Cpanel::Logger;
Cpanel::Logger->new->warn("set_rlimit adjusted the requested limit of “$limit” to infinity because it exceeded the maximum allowed value.");
$limit = $Cpanel::Sys::Rlimit::RLIM_INFINITY;
}
my $error = '';
foreach my $lim (@$limit_names) {
local $@;
eval { Cpanel::Sys::Rlimit::setrlimit( $lim, $limit, $limit ) } or do {
my $limit_human_value = ( $limit == $Cpanel::Sys::Rlimit::RLIM_INFINITY ? 'INFINITY' : $limit );
$error .= "$$: Unable to set RLIMIT_$lim to $limit_human_value: $@\n";
}
}
local $@;
eval { Cpanel::Sys::Rlimit::setrlimit( 'CORE', $core_limit, $core_limit ) }
or $error .= "$$: Unable to set RLIMIT_CORE to $core_limit: $@\n";
if ($error) {
$error =~ s/\n$//;
require Cpanel::Logger;
Cpanel::Logger->new->warn($error);
return 0;
}
return 1;
}
sub set_min_rlimit {
my ($min) = @_;
my $error = '';
foreach my $lim (qw(RSS AS)) {
my ( $current_soft, $current_hard ) = Cpanel::Sys::Rlimit::getrlimit($lim);
if ( $current_soft < $min || $current_hard < $min ) {
local $@;
eval { Cpanel::Sys::Rlimit::setrlimit( $lim, $min, $min ) } or $error .= "$$: Unable to set RLIMIT_$lim to $min: $@\n";
}
}
if ($error) {
$error =~ s/\n$//;
require Cpanel::Logger;
Cpanel::Logger->new->warn($error);
return 0;
}
return 1;
}
sub get_current_rlimits {
return { map { $_ => [ Cpanel::Sys::Rlimit::getrlimit($_) ] } (qw(RSS AS CORE)) };
}
sub restore_rlimits {
my $limit_hr = shift;
my $error = '';
if ( ref $limit_hr eq 'HASH' ) {
foreach my $resource_name ( keys %{$limit_hr} ) {
my $values = $limit_hr->{$resource_name};
if ( ref $values ne 'ARRAY' || scalar @{$values} != 2 ) {
$error .= "Invalid limit arguments, could not restore resource limit for $resource_name.\n";
next;
}
local $@;
eval { Cpanel::Sys::Rlimit::setrlimit( $resource_name, $values->[0], $values->[1] ) }
or $error .= "$$: Unable to set $resource_name to $values->[0] and $values->[1]: $@\n";
}
}
else {
$error .= "Invalid arguments, could not restore resource limits.\n";
}
if ($error) {
$error =~ s/\n$//;
require Cpanel::Logger;
Cpanel::Logger->new->warn($error);
return 0;
}
return 1;
}
sub set_rlimit_to_infinity {
return set_rlimit($Cpanel::Sys::Rlimit::RLIM_INFINITY);
}
sub set_open_files_to_maximum {
my $limit = 1048576;
if ( open( my $fh, '<', '/proc/sys/fs/nr_open' ) ) {
$limit = <$fh>;
chomp($limit);
close($fh);
}
return set_rlimit( $limit, [qw/NOFILE/] );
}
sub _get_server_setting_or_default {
my $cpconf = Cpanel::Config::LoadCpConf::loadcpconf_not_copy();
my $default_maxmem = Cpanel::Maxmem::default();
my $core_dumps_enabled = $cpconf->{'coredump'};
my $configured_maxmem = exists $cpconf->{'maxmem'} ? int( $cpconf->{'maxmem'} || 0 ) : $default_maxmem;
if ( $configured_maxmem && $configured_maxmem < $default_maxmem ) {
return ( _mebibytes_to_bytes($default_maxmem), $core_dumps_enabled );
}
elsif ( $configured_maxmem == 0 ) {
return ( $Cpanel::Sys::Rlimit::RLIM_INFINITY, $core_dumps_enabled );
}
else {
return ( _mebibytes_to_bytes($configured_maxmem), $core_dumps_enabled );
}
}
sub _mebibytes_to_bytes {
my $mebibytes = shift;
return ( $mebibytes * 1024**2 );
}
1;
} # --- END Cpanel/Rlimit.pm
package main;
# cpanel - scripts/upcp Copyright 2020 cPanel, L.L.C.
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
package scripts::upcp;
BEGIN {
unshift @INC, q{/usr/local/cpanel};
# if we are being called with a compile check flag ( perl -c ), skip the begin block
# so we don't actually call upcp.static when just checking syntax and such is OK
return if $^C;
# static never gets --use-checked and should pass all the begin block checks
return if $0 =~ /\.static$/;
# let the '--use-check' instance compiled
if ( grep { $_ eq '--use-check' } @ARGV ) {
no warnings;
# dynamic definition of the INIT block
eval "INIT { exit(0); }";
return;
}
system("$0 --use-check >/dev/null 2>&1");
# compilation is ok with '--use-check', we will continue the non static version
return if $? == 0;
my $static = $0 . ".static";
if ( -f $static ) {
print STDERR "We determined that $0 had compilation issues..\n";
print STDERR "Trying to exec $static " . join( ' ', @ARGV ) . "\n";
exec( $^X, $static, @ARGV );
}
}
use strict;
use Try::Tiny;
# use Cpanel::Sys::OS (); # PPI USE OK -- preload for perlstatic (Cpanel::GenSysInfo)
# use Cpanel::HiRes ( preload => 'perl' );
# use Cpanel::Env ();
# use Cpanel::Update::IsCron ();
# use Cpanel::Update::Logger ();
# use Cpanel::FileUtils::TouchFile ();
# use Cpanel::LoadFile ();
# use Cpanel::LoadModule ();
# use Cpanel::Usage ();
use IO::Handle ();
use POSIX ();
# use Cpanel::Unix::PID::Tiny ();
my $pidfile = '/var/run/upcp.pid';
my $lastlog = '/var/cpanel/updatelogs/last';
my $upcp_disallowed_path = '/root/.upcp_controlc_disallowed';
my $version_upgrade_file = '/usr/local/cpanel/upgrade_in_progress.txt';
our $logger; # Global for logger object.
our $logfile_path;
my $now;
my $forced = 0;
my $fromself = 0;
my $sync_requested = 0;
my $bg = 0;
my $from_version;
my $pbar_starting_point;
exit( upcp() || 0 ) unless caller();
sub usage {
print <<EOS;
Usage: scripts/upcp [--bg] [--cron] [--force] [--help] [--log=[path]] [--sync]
Updates cPanel & WHM.
Options:
--bg Runs upcp in the background. Output is only visible in the log.
--cron Follow WHM's Update Preferences (/etc/cpupdate.conf).
--force Force a reinstall even if the system is up to date.
--help Display this documentation.
--log=[path] Overrides the default log file.
--sync Updates to the version already installed instead of downloading a newer version.
May not be used in conjunction with --force.
EOS
exit 1;
}
sub upcp { ## no critic(Subroutines::ProhibitExcessComplexity) - preserve original code
Cpanel::Usage::wrap_options( \@ARGV, \&usage, {} ); #display usage information on --help
open( STDERR, ">&STDOUT" ) or die $!;
local $| = 1;
umask(0022);
$now = time();
$logfile_path = '/var/cpanel/updatelogs/update.' . $now . '.log';
setupenv();
unset_rlimits();
#############################################################################
# Record the arguments used when started, check for certain flags
my $update_is_available_exit_code = 42;
my @retain_argv = @ARGV;
foreach my $arg (@ARGV) {
if ( $arg =~ m/^--log=(.*)/ ) {
$logfile_path = $1;
}
elsif ( $arg eq '--fromself' ) {
$fromself = 1;
}
elsif ( $arg eq '--force' ) {
$forced = 1;
$ENV{'FORCEDCPUPDATE'} = 1;
}
elsif ( $arg eq '--sync' ) {
$sync_requested = 1;
}
elsif ( $arg eq '--bg' ) {
$bg = 1;
}
}
if ( $sync_requested && $forced ) {
print "FATAL: --force and --sync are mutually exclusive commands.\n";
print " Force is designed to update your installed version, regardless of whether it's already up to date.\n";
print " Sync is designed to update the version already installed, regardless of what is available.\n";
return 1;
}
if ( $> != 0 ) {
die "upcp must be run as root";
}
#############################################################################
# Make sure easyapache isn't already running
my $upid = Cpanel::Unix::PID::Tiny->new();
if ( $upid->is_pidfile_running('/var/run/easyapache.pid') ) {
print "EasyApache is currently running. Please wait for EasyApache to complete before running cPanel Update (upcp).\n";
return 1;
}
#############################################################################
# Make sure we aren't already running && make sure everyone knows we are running
my $curpid = $upid->get_pid_from_pidfile($pidfile) || 0;
if ( $curpid && $curpid != $$ && !$fromself && -e '/var/cpanel/upcpcheck' ) {
my $pidfile_mtime = ( stat($pidfile) )[9];
my $pidfile_age = ( time - $pidfile_mtime );
if ( $pidfile_age > 21600 ) { # Running for > 6 hours
_logger()->warning("previous PID ($curpid) has been running more than 6 hours. Killing processes.");
kill_upcp($curpid); # the pid_file_no_cleanup() will exit if it is still stuck after this
sleep 1; # Give the process group time to die.
}
elsif ( $upid->is_pidfile_running($pidfile) ) {
print "cPanel Update (upcp) is already running. Please wait for the previous upcp (pid $curpid) to complete, then try again. You can use the command 'ps --pid $curpid' to check if the process is running. You may wish to use '--force'\n";
return 1;
}
}
if ( $curpid && $curpid != $$ && !$upid->is_pidfile_running($pidfile) ) {
print "Stale PID file '$pidfile' (pid=$curpid)\n";
}
if ( !$fromself && !$upid->pid_file_no_cleanup($pidfile) ) {
print "process is already running\n";
return 1;
}
# to indicate re-entry into upcp
$pbar_starting_point = $fromself ? 17 : 0;
# record current version
$from_version = fetch_cpanel_version();
#############################################################################
# Set up the upcp log directory and files
setup_updatelogs();
#############################################################################
# Fork a child to the background. The child does all the heavy lifting and
# logs to a file; the parent just watches, reads, and parses the log file,
# displaying what it gets.
#
# Note that the parent reads the log in proper line-oriented -- and buffered!
# -- fashion. An earlier version of this script did raw sysread() calls here,
# and had to deal with all the mess that that entailed. The current approach
# reaps all the benefits of Perl's and Linux's significant file read
# optimizations without needing to re-invent any of them. The parent loop
# below becomes lean, mean, and even elegant.
#
# Note in particular that we do not need to explicitly deal with an
# end-of-file condition (other than avoiding using undefined data). For
# exiting the read loop we merely need to test that the child has expired,
# which in any case is the only situation that can cause an eof condition for
# us on the file the child is writing.
#
# Note, too, that the open() needs to be inside this loop, in case the child
# has not yet created the file.
if ( !$fromself ) {
# we need to be sure that log an pid are the current one when giving back the end
unlink $lastlog if $bg;
if ( my $updatepid = fork() ) {
if ($logger) { # Close if logged about killing stale process.
$logger->{'brief'} = 1; # Don't be chatty about closing
$logger->close_log;
}
if ($bg) {
print "upcp is going into background mode. You can follow “$logfile_path” to watch its progress.\n";
my $progress;
select undef, undef, undef, .10;
while ( !-e $lastlog ) {
print '.';
select undef, undef, undef, .25;
$progress = 1;
}
print "\n" if $progress;
}
else {
monitor_upcp($updatepid);
}
return;
}
}
local $0 = 'cPanel Update (upcp) - Slave';
open( my $RNULL, '<', '/dev/null' ) or die "Cannot open /dev/null: $!";
chdir '/';
_logger(); # Open the log file.
#############################################################################
# Set CPANEL_IS_CRON env var based on detection algorithm
my $cron_reason = set_cron_env();
$logger->info("Detected cron=$ENV{'CPANEL_IS_CRON'} ($cron_reason)");
my $set_cron_method = $ENV{'CPANEL_IS_CRON'} ? 'set_on' : 'set_off';
Cpanel::Update::IsCron->$set_cron_method();
my $openmax = POSIX::sysconf( POSIX::_SC_OPEN_MAX() );
if ( !$openmax ) { $openmax = 64; }
foreach my $i ( 0 .. $openmax ) { POSIX::close($i) unless $i == fileno( $logger->{'fh'} ); }
POSIX::setsid();
open( STDOUT, '>', '/dev/null' ) or warn $!;
open( STDERR, '>', '/dev/null' ) or warn $!;
$logger->update_pbar($pbar_starting_point);
##############################################################################
# Symlink /var/cpanel/updatelogs/last to the current log file
unlink $lastlog;
symlink( $logfile_path, $lastlog ) or $logger->error("Could not symlink $lastlog: $!");
#############################################################################
# now that we have sporked: update our pidfile and ensure it is removed
unlink $pidfile; # so that pid_file() won't see it as running.
if ( !$upid->pid_file($pidfile) ) { # re-verifies (i.e. upcp was not also started after the unlink() and here) and sets up cleanup of $pidfile for sporked proc
$logger->error("Could not update pidfile “$pidfile” with BG process: $!\n");
return 1;
}
# Assuming we didn't get re-executed from a upcp change after updatenow (!$fromself).
# If the file is still there from a failed run, remove it.
unlink($upcp_disallowed_path) if !$fromself && -f $upcp_disallowed_path;
# make sure that the pid file is going to be removed when killed by a signal
$SIG{INT} = $SIG{HUP} = $SIG{TERM} = sub { ## no critic qw(Variables::RequireLocalizedPunctuationVars)
unlink $pidfile;
if ($logger) {
$logger->close_log;
$logger->open_log;
$logger->error("User hit ^C or killed the process ( pid file '$pidfile' removed ).");
$logger->close_log;
}
return;
};
#############################################################################
# Get variables needed for update
my $gotSigALRM = 0;
my $connecttimeout = 30;
my $liveconnect = 0;
my $connectedhost = q{};
my @HOST_IPs = ();
## Case 46528: license checks moved to updatenow and Cpanel::Update::Blocker
$logger->debug("Done getting update config variables..");
$logger->increment_pbar;
#############################################################################
# Run the preupcp hook
if ( -x '/usr/local/cpanel/scripts/preupcp' ) {
$logger->info("Running /usr/local/cpanel/scripts/preupcp");
system '/usr/local/cpanel/scripts/preupcp';
}
if ( -x '/usr/local/cpanel/scripts/hook' ) {
$logger->info("Running Standardized hooks");
system '/usr/local/cpanel/scripts/hook', '--category=System', '--event=upcp', '--stage=pre';
}
$logger->increment_pbar();
#############################################################################
# Check mtime on ourselves before sync
# This is the target for a goto in the case that the remote TIERS file is
# changed sometime during the execution of this upcp run. It prevents the
# need for a new script argument and re-exec.
STARTOVER:
my $mtime = ( stat('/usr/local/cpanel/scripts/upcp') )[9];
$logger->info( "mtime on upcp is $mtime (" . scalar( localtime($mtime) ) . ")" );
# * If no fromself arg is passed, it's either the first run from crontab or called manually.
# * --force is passed to updatenow, has no bearing on upcp itself.
# * Even if upcp is changed 3 times in a row during an update (fastest builds ever?), we
# would never actually update more than once unless the new upcp script changed the logic below
if ( !$fromself ) {
# run updatenow to sync everything
# updatenow expects --upcp to be passed or will error out
my @updatenow_args = ( '/usr/local/cpanel/scripts/updatenow', '--upcp', "--log=$logfile_path" );
# if --forced was received, pass it on to updatenow
if ($forced) { push( @updatenow_args, '--force' ); }
# if --sync was received, pass it on to updatenow. --force makes --sync meaningless.
if ( !$forced && $sync_requested ) { push( @updatenow_args, '--sync' ); }
# This is the point of no return, we are upgrading
# and its no longer abortable.
# set flag to disallow ^C during updatenow
Cpanel::FileUtils::TouchFile::touchfile($upcp_disallowed_path) or $logger->warn("Failed to create: $upcp_disallowed_path: $!");
# call updatenow, if we get a non-zero status, die.
my $exit_code = system(@updatenow_args);
$logger->increment_pbar(15);
if ( $exit_code != 0 ) {
my $signal = $exit_code % 256;
$exit_code = $exit_code >> 8;
analyze_and_report_error(
#success_msg => undef,
error_msg => "Running `@updatenow_args` failed, exited with code $exit_code (signal = $signal)",
type => 'upcp::UpdateNowFailed',
exit_status => $exit_code,
extra => [
'signal' => $signal,
'updatenow_args' => \@updatenow_args,
],
);
# Gathering logs here to catch failures in updatenow
if ( !defer_log_gathering( $$, $logfile_path ) ) {
$logger->info("Couldn't run try-later; maybe atd isn't working?");
}
return ($exit_code);
}
# get the new mtime and compare it, if upcp changed, let's run ourselves again.
# this should be a fairly rare occasion.
my $newmtime = ( stat('/usr/local/cpanel/scripts/upcp') )[9];
if ( $newmtime ne $mtime ) {
#----> Run our new self (and never come back).
$logger->info("New upcp detected, restarting ourself");
$logger->close_log();
exec '/usr/local/cpanel/scripts/upcp', @retain_argv, '--fromself', "--log=$logfile_path";
}
}
#############################################################################
# Run the maintenance script
my $last_logfile_position;
my $save_last_logfile_position = sub {
$last_logfile_position = int( qx{wc -l $logfile_path 2>/dev/null} || 0 );
};
$logger->close_log(); # Allow maintenance to write to the log
$save_last_logfile_position->(); # remember how many lines has the logfile before starting the maintenance script
my $exit_status;
my $version_change_happened = -e $version_upgrade_file;
if ($version_change_happened) {
$exit_status = system( '/usr/local/cpanel/scripts/maintenance', '--pre', '--log=' . $logfile_path, '--pbar-start=20', '--pbar-stop=30' );
}
else {
$exit_status = system( '/usr/local/cpanel/scripts/maintenance', '--log=' . $logfile_path, '--pbar-start=20', '--pbar-stop=95' );
}
$logger->open_log(); # Re-open the log now maintenance is done.
analyze_and_report_error(
success_msg => "Pre Maintenance completed successfully",
error_msg => "Pre Maintenance ended, however it did not exit cleanly ($exit_status). Please check the logs for an indication of what happened",
type => 'upcp::MaintenanceFailed',
exit_status => $exit_status,
logfile => $logfile_path,
last_logfile_position => $last_logfile_position,
);
# Run this here so that we can make sure sysup has run and that atd is installed.
if ( !defer_log_gathering( $$, $logfile_path ) ) {
$logger->info("Couldn't run try-later; maybe atd isn't working?");
}
# Run post-sync cleanup only if updatenow did a sync
# Formerly run after layer2 did a sync.
if ($version_change_happened) {
# post_sync pbar range: 30%-55%
$logger->close_log(); # Yield the log to post_sync_cleanup
$save_last_logfile_position->(); # remember how many lines has the logfile before starting the post_sync_cleanup script
my $post_exit_status = system( '/usr/local/cpanel/scripts/post_sync_cleanup', '--log=' . $logfile_path, '--pbar-start=30', '--pbar-stop=55' );
$logger->open_log; # reopen the log to continue writing messages
analyze_and_report_error(
success_msg => "Post-sync cleanup completed successfully",
error_msg => "Post-sync cleanup has ended, however it did not exit cleanly. Please check the logs for an indication of what happened",
type => 'upcp::PostSyncCleanupFailed',
exit_status => $post_exit_status,
logfile => $logfile_path,
last_logfile_position => $last_logfile_position,
);
unlink $version_upgrade_file;
unlink($upcp_disallowed_path) if -f ($upcp_disallowed_path);
# Maintenance pbar range: 55-95%
$logger->close_log(); # Allow maintenance to write to the log
$save_last_logfile_position->(); # remember how many lines has the logfile before starting the maintenance --post
$exit_status = system( '/usr/local/cpanel/scripts/maintenance', '--post', '--log=' . $logfile_path, '--pbar-start=55', '--pbar-stop=95' );
$logger->open_log(); # Re-open the log now maintenance is done.
analyze_and_report_error(
success_msg => "Post Maintenance completed successfully",
error_msg => "Post Maintenance ended, however it did not exit cleanly ($exit_status). Please check the logs for an indication of what happened",
type => 'upcp::MaintenanceFailed',
exit_status => $exit_status,
logfile => $logfile_path,
last_logfile_position => $last_logfile_position,
);
# Check for new version... used when updating to next LTS version
$logger->info("Polling updatenow to see if a newer version is available for upgrade");
$logger->close_log(); # Yield the log to updatenow
my $update_available = system( '/usr/local/cpanel/scripts/updatenow', "--log=$logfile_path", '--checkremoteversion' );
$logger->open_log; # reopen the log to continue writing messages
if ( !$sync_requested && $update_available && ( $update_available >> 8 ) == $update_is_available_exit_code ) {
$logger->info("\n\n/!\\ - Next LTS version available, restarting upcp and updating system. /!\\\n\n");
$fromself = 0;
goto STARTOVER;
}
}
else {
unlink($upcp_disallowed_path) if -f ($upcp_disallowed_path);
}
#############################################################################
# Run the post upcp hook
$logger->update_pbar(95);
if ( -x '/usr/local/cpanel/scripts/postupcp' ) {
$logger->info("Running /usr/local/cpanel/scripts/postupcp");
system '/usr/local/cpanel/scripts/postupcp';
}
if ( -x '/usr/local/cpanel/scripts/hook' ) {
$logger->info("Running Standardized hooks");
system '/usr/local/cpanel/scripts/hook', '--category=System', '--event=upcp', '--stage=post';
}
close($RNULL);
#############################################################################
# All done.
#############################################################################
$logger->update_pbar(100);
$logger->info( "\n\n\tcPanel update completed\n\n", 1 );
$logger->info("A log of this update is available at $logfile_path\n\n");
# this happens on exit so it shouldn't be necessary
$logger->info("Removing upcp pidfile");
unlink $pidfile if -f $pidfile || $logger->warn("Could not delete pidfile $pidfile : $!");
my $update_blocks_fname = '/var/cpanel/update_blocks.config';
if ( -s $update_blocks_fname ) {
$logger->warning("NOTE: A system upgrade was not possible due to the following blockers:\n");
if ( open( my $blocks_fh, '<', $update_blocks_fname ) ) {
while ( my $line = readline $blocks_fh ) {
my ( $level, $message ) = split /,/, $line, 2;
# Not using the level in the log, cause the logger can emit additional messages
# on some of the levels used (fatal emits an 'email message', etc)
# Remove URL from log output. Make sure message is defined.
if ($message) {
$message =~ s/<a.*?>//ig;
$message =~ s{</a>}{}ig;
}
$logger->warning( uc("[$level]") . " - $message" );
}
}
else {
$logger->warning("Unable to open blocks file! Please review '/var/cpanel/update_blocks.config' manually.");
}
}
else {
$logger->info("\n\nCompleted all updates\n\n");
}
$logger->close_log();
return 0;
}
#############################################################################
######[ Subroutines ]########################################################
#############################################################################
sub analyze_and_report_error {
my %info = @_;
my $type = $info{type} or die;
my $exit_status = $info{exit_status};
if ( $exit_status == 0 ) {
if ( defined $info{success_msg} ) {
$logger->info( $info{success_msg} );
}
return;
}
my $msg = $info{error_msg} or die;
my @extra;
if ( ref $info{extra} ) {
@extra = @{ $info{extra} };
}
my $logfile_content = Cpanel::LoadFile::loadfile_r($logfile_path);
# add events to the end of the error log
if ( try( sub { Cpanel::LoadModule::load_perl_module("Cpanel::Logs::ErrorEvents") } ) ) {
my ($events) = Cpanel::Logs::ErrorEvents::extract_events_from_log( log => $logfile_content, after_line => $info{last_logfile_position} );
if ( $events && ref $events && scalar @$events ) {
my $events_str = join ', ', map { qq["$_"] } @$events;
$events_str = qq[The following events were logged: ${events_str}.];
$msg =~ s{(Please check)}{${events_str} $1} or $msg .= ' ' . $events_str;
}
}
$logger->error( $msg, 1 );
if ( try( sub { Cpanel::LoadModule::load_perl_module("Cpanel::iContact::Class::$type") } ) ) {
require Cpanel::Notify;
Cpanel::Notify::notification_class(
'class' => $type,
'application' => $type,
'constructor_args' => [
'exit_code' => $exit_status,
'events_after_line' => $info{last_logfile_position},
@extra,
'attach_files' => [ { 'name' => 'update_log.txt', 'content' => $logfile_content, 'number_of_preview_lines' => 25 } ]
]
);
}
elsif (
!try(
sub {
Cpanel::LoadModule::load_perl_module("Cpanel::iContact");
Cpanel::iContact::icontact(
'application' => 'upcp',
'subject' => 'cPanel & WHM update failure (upcp)',
'message' => $msg,
);
}
)
) {
$logger->error('Failed to send contact message');
}
return 1;
}
#############################################################################
sub kill_upcp {
my $pid = shift or die;
my $status = shift || 'hanging';
my $msg = shift || "/usr/local/cpanel/scripts/upcp was running as pid '$pid' for longer than 6 hours. cPanel will kill this process and run a new upcp in its place.";
# Attempt to notify admin of the kill.
if ( try( sub { Cpanel::LoadModule::load_perl_module("Cpanel::iContact::Class::upcp::Killed") } ) ) {
require Cpanel::Notify;
Cpanel::Notify::notification_class(
'class' => 'upcp::Killed',
'application' => 'upcp::Killed',
'constructor_args' => [
'upcp_path' => '/usr/local/cpanel/scripts/upcp',
'pid' => $pid,
'status' => $status,
'attach_files' => [ { 'name' => 'update_log.txt', 'content' => Cpanel::LoadFile::loadfile_r($logfile_path), 'number_of_preview_lines' => 25 } ]
]
);
}
else {
try(
sub {
Cpanel::LoadModule::load_perl_module("Cpanel::iContact");
Cpanel::iContact::icontact(
'application' => 'upcp',
'subject' => "cPanel update $status",
'message' => $msg,
);
}
);
}
print "Sending kill signal to process group for $pid\n";
kill -1, $pid; # Kill the process group
for ( 1 .. 60 ) {
print "Waiting for processes to die\n";
waitpid( $pid, POSIX::WNOHANG() );
last if ( !kill( 0, $pid ) );
sleep 1;
}
if ( kill( 0, $pid ) ) {
print "Could not kill upcp nicely. Doing kill -9 $pid\n";
kill 9, $pid;
}
else {
print "Done!\n";
}
return;
}
#############################################################################
sub setupenv {
Cpanel::Env::clean_env();
delete $ENV{'DOCUMENT_ROOT'};
delete $ENV{'SERVER_SOFTWARE'};
if ( $ENV{'WHM50'} ) {
$ENV{'GATEWAY_INTERFACE'} = 'CGI/1.1';
}
( $ENV{'USER'}, $ENV{'HOME'} ) = ( getpwuid($>) )[ 0, 7 ];
$ENV{'PATH'} .= ':/sbin:/usr/sbin:/usr/bin:/bin:/usr/local/bin';
$ENV{'LANG'} = 'C';
$ENV{'LC_ALL'} = 'C';
}
sub unset_rlimits {
# This is required if upcp started running from a pre-1132
eval {
local $SIG{__DIE__};
require Cpanel::Rlimit;
Cpanel::Rlimit::set_rlimit_to_infinity();
};
}
#############################################################################
sub setup_updatelogs {
return if ( -d '/var/cpanel/updatelogs' );
unlink('/var/cpanel/updatelogs');
mkdir( '/var/cpanel/updatelogs', 0700 );
}
sub set_cron_env {
# Do not override the env var if set.
return 'env var CPANEL_IS_CRON was present before this process started.' if ( defined $ENV{'CPANEL_IS_CRON'} );
if ( grep { $_ eq '--cron' } @ARGV ) {
$ENV{'CPANEL_IS_CRON'} = 1;
return 'cron mode set from command line';
}
if ( $ARGV[0] eq 'manual' ) {
$ENV{'CPANEL_IS_CRON'} = 0;
return 'manual flag passed on command line';
}
if ($forced) {
$ENV{'CPANEL_IS_CRON'} = 0;
return '--force passed on command line';
}
if ( -t STDOUT ) {
$ENV{'CPANEL_IS_CRON'} = 0;
return 'Terminal detected';
}
if ( $ENV{'SSH_CLIENT'} ) {
$ENV{'CPANEL_IS_CRON'} = 0;
return 'SSH connection detected';
}
# cron sets TERM=dumb
if ( $ENV{'TERM'} eq 'dumb' ) {
$ENV{'CPANEL_IS_CRON'} = 1;
return 'TERM detected as set to dumb';
}
# Check if parent is whostmgr
if ( readlink( '/proc/' . getppid() . '/exe' ) =~ m/whostmgrd/ ) {
$ENV{'CPANEL_IS_CRON'} = 0;
return 'parent process is whostmgrd';
}
# Default to cron enabled.
$ENV{'CPANEL_IS_CRON'} = 1;
return 'default';
}
#############################################################################
sub fetch_cpanel_version {
my $version;
my $version_file = '/usr/local/cpanel/version';
return if !-f $version_file;
my $fh;
local $/ = undef;
return if !open $fh, '<', $version_file;
$version = <$fh>;
close $fh;
$version =~ s/^\s+|\s+$//gs;
return $version;
}
#############################################################################
sub defer_log_gathering {
my ( $pid, $logfile ) = @_;
return if ( !defined $from_version );
my @action_cmd = (
'/usr/local/cpanel/scripts/gather-update-logs',
# we cannot rely on the timestamp to build the logfile
# as the logfile can be provided as an extra argument
# from a previous call with the --fromself option
'--logfile', $logfile,
'--version-before', $from_version,
);
return if !-x $action_cmd[0];
my @logfile_parts = split /\//, $logfile;
$logfile = pop @logfile_parts;
my @check_cmd = (
'/usr/local/cpanel/scripts/upcp-running',
'--pid', $pid,
'--logfile', $logfile,
'--invert-exit',
'--quiet',
);
return if !-x $check_cmd[0];
my @cmd = (
'/usr/local/cpanel/scripts/try-later',
'--action', join( ' ', @action_cmd ),
'--check', join( ' ', @check_cmd ),
'--delay', 15,
'--max-retries', 24,
'--skip-first',
'--act-finally'
);
return if !-x $cmd[0];
return !system @cmd;
}
#############################################################################
sub monitor_upcp {
my $updatepid = shift or die;
$0 = 'cPanel Update (upcp) - Master';
$SIG{INT} = $SIG{TERM} = sub {
print "User hit ^C\n";
if ( -f $upcp_disallowed_path ) {
print "Not allowing upcp slave to be killed during updatenow, just killing monitoring process.\n";
exit;
}
print "killing upcp\n";
kill_upcp( $updatepid, "aborted", "/usr/local/cpanel/scripts/upcp was aborted by the user hitting Ctrl-C." );
exit;
};
$SIG{HUP} = sub {
print "SIGHUP detected; closing monitoring process.\n";
print "The upcp slave has not been affected\n";
exit;
};
# Wait till the file shows up.
until ( -e $logfile_path ) {
select undef, undef, undef, .25; # sleep just a bit
}
# Wait till we're allowed to open it.
my $fh;
until ( defined $fh && fileno $fh ) {
$fh = IO::Handle->new();
if ( !open $fh, '<', $logfile_path ) {
undef $fh;
select undef, undef, undef, .25; # sleep just a bit
next;
}
}
# Read the file until the pid dies.
my $child_done = 0;
while (1) {
# Read all the available lines.
while (1) {
my $line = <$fh>;
last if ( !defined $line || $line eq '' );
print $line;
}
# Once the child is history, we need to do yet one more final read,
# on the off chance (however remote) that she has written one last
# hurrah after we last checked. Hence the following.
last if $child_done; # from prev. pass
$child_done = 1 if -1 == waitpid( $updatepid, 1 ); # and loop back for one more read
select undef, undef, undef, .25; # Yield idle time to the cpu
}
close $fh if $fh;
exit;
}
sub _logger {
return $logger if $logger;
$logger = Cpanel::Update::Logger->new( { 'logfile' => $logfile_path, 'stdout' => 1, 'log_level' => 'info' } );
# do not set the pbar in the constructor to do not display the 0 % in bg mode
$logger->{pbar} = $pbar_starting_point;
return $logger;
}
Copyright 2K16 - 2K18 Indonesian Hacker Rulez