our
$VERSION
=
'0.39'
;
use
Fatal
qw(:void open)
;
my
%blacklist
= (
'MD'
=> 1,
'RU'
=> 1,
'CN'
=> 1,
'BR'
=> 1,
'UY'
=> 1,
'TR'
=> 1,
'MA'
=> 1,
'VE'
=> 1,
'SA'
=> 1,
'CY'
=> 1,
'CO'
=> 1,
'MX'
=> 1,
'IN'
=> 1,
'RS'
=> 1,
'PK'
=> 1,
);
our
$sm
;
our
$smcache
;
sub
new {
my
$class
=
shift
;
my
%args
= (
ref
(
$_
[0]) eq
'HASH'
) ? %{
$_
[0]} :
@_
;
if
(!
defined
(
$class
)) {
$class
= __PACKAGE__;
}
elsif
(Scalar::Util::blessed(
$class
)) {
return
bless
{ %{
$class
},
%args
},
ref
(
$class
);
}
if
(
defined
(
$ENV
{
'HTTP_REFERER'
})) {
Data::Validate::URI->
import
();
unless
(Data::Validate::URI->new()->is_uri(
$ENV
{
'HTTP_REFERER'
})) {
return
0;
}
}
my
$info
=
$args
{info} || CGI::Info->new();
unless
(
$info
->is_search_engine() || !
defined
(
$ENV
{
'REMOTE_ADDR'
})) {
CGI::IDS->
import
();
my
$ids
= CGI::IDS->new();
$ids
->set_scan_keys(
scan_keys
=> 1);
my
$impact
=
$ids
->detect_attacks(
request
=>
$info
->params());
if
(
$impact
> 0) {
die
"IDS impact is $impact"
;
}
Data::Throttler->
import
();
my
$db_file
= File::Spec->catdir(
$info
->tmpdir(),
'throttle'
);
eval
{
my
$throttler
= Data::Throttler->new(
max_items
=> 30,
interval
=> 90,
backend
=>
'YAML'
,
backend_options
=> {
db_file
=>
$db_file
}
);
unless
(
$throttler
->try_push(
key
=>
$ENV
{
'REMOTE_ADDR'
})) {
sleep
(1);
die
"$ENV{REMOTE_ADDR} connexion throttled"
;
}
};
if
($@) {
unlink
(
$db_file
);
}
if
(
my
$lingua
=
$args
{lingua}) {
if
(
$blacklist
{
uc
(
$lingua
->country())}) {
die
"$ENV{REMOTE_ADDR} is from a blacklisted country "
,
$lingua
->country();
}
}
}
my
$config_dir
= _find_config_dir(\
%args
,
$info
);
if
(
$args
{
'logger'
}) {
$args
{
'logger'
}->debug(__PACKAGE__,
': '
, __LINE__,
" path = $config_dir"
);
}
my
$config
;
eval
{
if
(-r File::Spec->catdir(
$config_dir
,
$info
->domain_name())) {
$config
= Config::Auto::parse(
$info
->domain_name(),
path
=>
$config_dir
);
}
elsif
(-r File::Spec->catdir(
$config_dir
,
'default'
)) {
$config
= Config::Auto::parse(
'default'
,
path
=>
$config_dir
);
}
else
{
die
'no suitable config file found'
;
}
};
if
($@ || !
defined
(
$config
)) {
die
"Configuration error: $@: $config_dir/"
,
$info
->domain_name();
}
if
(
defined
(
$args
{
'config'
})) {
$config
= { %{
$config
}, %{
$args
{
'config'
}} };
}
Template::Filters->use_html_entities();
my
$self
= {
cachedir
=>
$args
{cachedir},
_cachedir
=>
$args
{cachedir},
config
=>
$config
,
_config
=>
$config
,
info
=>
$info
,
_info
=>
$info
,
logger
=>
$args
{logger},
_logger
=>
$args
{logger},
%args
,
};
if
(
my
$lingua
=
$args
{
'lingua'
}) {
$self
->{
'lingua'
} =
$lingua
;
$self
->{
'_lingua'
} =
$lingua
;
}
if
(
my
$key
=
$info
->param(
'key'
)) {
$self
->{
'key'
} =
$key
;
$self
->{
'_key'
} =
$key
;
}
if
(
my
$page
=
$info
->param(
'page'
)) {
$self
->{
'page'
} =
$page
;
$self
->{
'_page'
} =
$page
;
}
if
(
my
$twitter
=
$config
->{
'twitter'
}) {
$smcache
||= create_memory_cache(
config
=>
$config
,
logger
=>
$args
{
'logger'
},
namespace
=>
'HTML::SocialMedia'
);
$sm
||= HTML::SocialMedia->new({
twitter
=>
$twitter
,
cache
=>
$smcache
,
lingua
=>
$args
{lingua},
logger
=>
$args
{logger} });
$self
->{
'_social_media'
}->{
'twitter_tweet_button'
} =
$sm
->as_string(
twitter_tweet_button
=> 1);
}
elsif
(!
defined
(
$sm
)) {
$smcache
= create_memory_cache(
config
=>
$config
,
logger
=>
$args
{
'logger'
},
namespace
=>
'HTML::SocialMedia'
);
$sm
= HTML::SocialMedia->new({
cache
=>
$smcache
,
lingua
=>
$args
{lingua},
logger
=>
$args
{logger} });
}
$self
->{
'_social_media'
}->{
'facebook_share_button'
} =
$sm
->as_string(
facebook_share_button
=> 1);
return
bless
$self
,
$class
;
}
sub
_find_config_dir
{
my
(
$args
,
$info
) =
@_
;
if
(
$ENV
{
'CONFIG_DIR'
}) {
return
$ENV
{
'CONFIG_DIR'
};
}
my
$config_dir
= File::Spec->catdir(
$info
->script_dir(),
File::Spec->updir(),
File::Spec->updir(),
'conf'
);
if
(!-d
$config_dir
) {
$config_dir
= File::Spec->catdir(
$info
->script_dir(),
File::Spec->updir(),
'conf'
);
}
if
(!-d
$config_dir
) {
if
(
$ENV
{
'DOCUMENT_ROOT'
}) {
$config_dir
= File::Spec->catdir(
$info
->rootdir(),
File::Spec->updir(),
'lib'
,
'conf'
);
}
else
{
$config_dir
= File::Spec->catdir(
$ENV
{
'HOME'
},
'lib'
,
'conf'
);
}
}
if
(!-d
$config_dir
) {
if
(
$args
->{config_directory}) {
return
$args
->{config_directory};
}
if
(
$args
->{logger}) {
while
(
my
(
$k
,
$v
) =
each
%ENV
) {
$args
->{logger}->debug(
"$k=$v"
);
}
}
}
return
$config_dir
;
}
sub
as_string {
my
(
$self
,
$args
) =
@_
;
unless
(
$args
&&
$args
->{cart}) {
if
(
my
$purchases
=
$self
->{_info}->get_cookie(
cookie_name
=>
'cart'
)) {
my
%cart
=
split
(/:/,
$purchases
);
$args
->{cart} = \
%cart
;
}
}
unless
(
$args
&&
$args
->{itemsincart}) {
if
(
$args
->{cart}) {
my
$itemsincart
;
foreach
my
$key
(
keys
%{
$args
->{cart}}) {
if
(
defined
(
$args
->{cart}{
$key
}) && (
$args
->{cart}{
$key
} ne
''
)) {
$itemsincart
+=
$args
->{cart}{
$key
};
}
else
{
delete
$args
->{cart}{
$key
};
}
}
$args
->{itemsincart} =
$itemsincart
;
}
}
my
$rc
=
$self
->http();
return
$rc
=~ /^Location:\s/ms ?
$rc
:
$rc
.
$self
->html(
$args
);
}
sub
get_template_path
{
my
$self
=
shift
;
my
%args
= (
ref
(
$_
[0]) eq
'HASH'
) ? %{
$_
[0]} :
@_
;
if
(
$self
->{_logger}) {
$self
->{_logger}->trace(
'Entering get_template_path'
);
}
if
(
$self
->{_filename}) {
return
$self
->{_filename};
}
my
$dir
=
$self
->{_config}->{root_dir} ||
$self
->{_info}->root_dir();
if
(
$self
->{_logger}) {
$self
->{_logger}->debug(__PACKAGE__,
': '
, __LINE__,
": root_dir $dir"
);
$self
->{_logger}->debug(Data::Dumper->new([
$self
->{_config}])->Dump());
}
$dir
.=
'/templates'
;
my
$prefix
;
foreach
my
$browser_type
(
$self
->_types()) {
if
(
my
$lingua
=
$self
->{_lingua}) {
$self
->_debug({
message
=>
'Requested language: '
.
$lingua
->requested_language() });
if
(
my
$language
=
$lingua
->language_code_alpha2()) {
if
(
my
$dialect
=
$lingua
->sublanguage_code_alpha2()) {
$prefix
.=
"$dir/$browser_type/$language/$dialect:"
;
$prefix
.=
"$dir/$browser_type/$language/default:"
;
}
$prefix
.=
"$dir/$language/$browser_type:"
if
(-d
"$dir/$language/$browser_type"
);
$prefix
.=
"$dir/$browser_type/$language:"
if
(-d
"$dir/$browser_type/$language"
);
$prefix
.=
"$dir/$browser_type/default:"
if
(-d
"$dir/$browser_type/default"
);
$prefix
.=
"$dir/default/$browser_type/:"
if
(-d
"$dir/default/$browser_type"
);
}
}
$prefix
.=
"$dir/$browser_type:"
if
(-d
"$dir/$browser_type"
);
}
$prefix
.=
"$dir/web:$dir/default/web:$dir/default:$dir"
;
$self
->_debug({
message
=>
"prefix: $prefix"
});
my
$modulepath
=
$args
{
'modulepath'
} ||
ref
(
$self
);
$modulepath
=~ s/::/\//g;
if
(
$prefix
=~ /\.\.\//) {
throw Error::Simple(
"Prefix must not contain ../ ($prefix)"
);
}
(
$prefix
) = (
$prefix
=~ m/^([A-Z0-9_\.\-\/:]+)$/ig);
my
(
$fh
,
$filename
) = File::pfopen::pfopen(
$prefix
,
$modulepath
,
'tmpl:tt:html:htm:txt'
);
if
((!
defined
(
$filename
)) || (!
defined
(
$fh
))) {
throw Error::Simple(
"Can't find suitable $modulepath html or tmpl file in $prefix in $dir or a subdir"
);
}
close
(
$fh
);
$self
->_debug({
message
=>
"using $filename"
});
$self
->{_filename} =
$filename
;
if
(
$self
->{
'log'
}) {
$self
->{
'log'
}->template(
$filename
);
}
return
$filename
;
}
sub
set_cookie
{
my
$self
=
shift
;
my
%params
= (
ref
(
$_
[0]) eq
'HASH'
) ? %{
$_
[0]} :
@_
;
foreach
my
$key
(
keys
(
%params
)) {
$self
->{_cookies}->{
$key
} =
$params
{
$key
};
}
return
$self
;
}
sub
http
{
my
$self
=
shift
;
my
%params
= (
ref
(
$_
[0]) eq
'HASH'
) ? %{
$_
[0]} :
@_
;
if
(
my
$cookies
=
$self
->{_cookies}) {
foreach
my
$cookie
(
keys
(%{
$cookies
})) {
my
$value
=
exists
$cookies
->{
$cookie
} ?
$cookies
->{
$cookie
} :
'0:0'
;
print
"Set-Cookie: $cookie=$value; path=/; HttpOnly\n"
;
}
}
my
$rc
;
if
(
$params
{
'Content-Type'
}) {
$rc
=
$params
{
'Content-Type'
} .
"\n"
;
}
else
{
my
$filename
=
$self
->get_template_path();
if
(
$filename
=~ /\.txt$/) {
$rc
=
"Content-Type: text/plain\n"
;
}
else
{
binmode
(STDOUT,
':utf8'
);
$rc
=
"Content-Type: text/html; charset=UTF-8\n"
;
}
}
return
$rc
.
"X-Frame-Options: SAMEORIGIN\n"
.
"X-Content-Type-Options: nosniff\n"
.
"Referrer-Policy: strict-origin-when-cross-origin\n\n"
;
}
sub
html {
my
$self
=
shift
;
my
%params
= (
ref
(
$_
[0]) eq
'HASH'
) ? %{
$_
[0]} :
@_
;
my
$filename
=
$self
->get_template_path();
my
$rc
;
if
(
$filename
=~ /.+\.t(mpl|t)$/) {
Template->
import
();
my
$info
=
$self
->{_info};
my
$vals
;
if
(
defined
(
$self
->{_config})) {
if
(
$info
->params()) {
$vals
= { %{
$self
->{_config}}, %{
$info
->params()} };
}
else
{
$vals
=
$self
->{_config};
}
if
(
scalar
(
keys
%params
)) {
$vals
= { %{
$vals
},
%params
};
}
}
elsif
(
scalar
(
keys
%params
)) {
$vals
= { %{
$info
->params()},
%params
};
}
else
{
$vals
=
$info
->params();
}
$vals
->{script_name} =
$info
->script_name();
$vals
->{cart} =
$info
->get_cookie(
cookie_name
=>
'cart'
);
$vals
->{lingua} =
$self
->{_lingua};
$vals
->{social_media} =
$self
->{_social_media};
$vals
->{info} =
$info
;
$vals
->{as_string} =
$info
->as_string();
my
$template
= Template->new({
INTERPOLATE
=> 1,
POST_CHOMP
=> 1,
ABSOLUTE
=> 1,
});
$self
->_debug({
message
=> __PACKAGE__ .
': '
. __LINE__ .
': Passing these to the template: '
.
join
(
', '
,
keys
%{
$vals
}) });
if
(!
$template
->process(
$filename
,
$vals
, \
$rc
)) {
if
(
my
$err
=
$template
->error()) {
throw Error::Simple(
$err
);
}
throw Error::Simple(
"Unknown error in template: $filename"
);
}
}
elsif
(
$filename
=~ /\.(html?|txt)$/) {
open
(
my
$fin
,
'<'
,
$filename
) || throw Error::Simple(
"$filename: $!"
);
my
@lines
= <
$fin
>;
close
$fin
;
$rc
=
join
(
''
,
@lines
);
}
else
{
throw Error::Simple(
"Unhandled file type $filename"
);
}
if
((
$filename
!~ /.txt$/) && (
$rc
=~ /\smailto:(.+?)>/) && ($1 !~ /^&/) &&
$self
->{_logger}) {
$self
->{_logger}->
warn
({
message
=>
"Found mailto link $1, you should remove it or use "
. obfuscate($1) .
' instead'
});
}
return
$rc
;
}
sub
_debug
{
my
$self
=
shift
;
if
(
my
$logger
=
$self
->{_logger}) {
my
%params
= (
ref
(
$_
[0]) eq
'HASH'
) ? %{
$_
[0]} :
@_
;
if
(
defined
(
$ENV
{
'REMOTE_ADDR'
})) {
$logger
->debug(
"$ENV{'REMOTE_ADDR'}: $params{'message'}"
);
}
else
{
$logger
->debug(
$params
{
'message'
});
}
}
return
$self
;
}
sub
obfuscate {
return
map
{
'&#'
.
ord
(
$_
) .
';'
}
split
(//,
shift
);
}
sub
_types
{
my
$self
=
shift
;
my
$info
=
$self
->{_info};
my
@rc
;
if
(
$info
->is_search_engine()) {
push
@rc
,
'search'
,
'robot'
;
}
elsif
(
$info
->is_mobile()) {
push
@rc
,
'mobile'
;
}
elsif
(
$info
->is_robot()) {
push
@rc
,
'robot'
,
'search'
;
}
push
@rc
,
'web'
;
return
@rc
;
}
1;