Apache2-API
view release on metacpan or search on metacpan
lib/Apache2/API.pm view on Meta::CPAN
use vars qw( $VERSION $DEBUG @EXPORT $USE_RFC_ERROR );
use version;
use Encode ();
# use Apache2::Const qw( :common :http );
use Apache2::Const -compile => qw( :cmd_how :common :config :conn_keepalive :context :filter_type :http :input_mode :log :methods :mpmq :options :override :platform :remotehost :satisfy :types :proxy );
use APR::Const -compile => qw( :common :error :fopen :filepath :fprot :filetype :finfo :flock :hook :limit :lockmech :poll :read_type :shutdown_how :socket :status :table :uri );
use Apache2::RequestRec ();
use Apache2::RequestIO ();
use Apache2::ServerUtil ();
use Apache2::RequestUtil ();
use Apache2::Response ();
use Apache2::Log ();
use Apache2::API::Request;
use Apache2::API::Response;
use Apache2::API::Status;
use APR::Base64 ();
use APR::Request ();
use APR::UUID ();
use Exporter ();
use JSON ();
use Scalar::Util ();
our @EXPORT = qw( apr1_md5 );
$DEBUG = 0;
$VERSION = 'v0.5.3';
};
use strict;
use warnings;
sub import
{
my( $this, @arguments ) = @_ ;
my $class = CORE::caller();
# my $code = qq{package ${class}; use Apache2::Const -compile => qw( @arguments );};
# print( "Evaluating -> $code\n" );
# eval( $code );
# print( "\$@ -> $@\n" );
# local $Exporter::ExportLevel = 1;
# Apache2::Const->import( '-compile' => @arguments );
# my @argv = grep( !/^\:http/, @arguments );
# Apache2::Const->compile( '-compile' => @argv );
# Apache2::Const->compile( $class => qw( AUTH_REQUIRED ) );
Apache2::Const->compile( $class => @arguments );
Exporter::export_to_level( $this, 1, @EXPORT );
}
sub init
{
my $self = shift( @_ );
my $r;
$r = shift( @_ ) if( @_ % 2 );
# my $r = shift( @_ ) || Apache2::RequestUtil->request;
$self->{request} = undef unless( $self->{request} );
$self->{response} = undef unless( $self->{response} );
$self->{apache_request} = $r unless( $self->{apache_request} );
# 200Kb
$self->{compression_threshold} = 204800 unless( length( $self->{compression_threshold} ) );
$self->{use_rfc_error} = $USE_RFC_ERROR unless( length( $self->{use_rfc_error} ) );
$self->SUPER::init( @_ ) || return( $self->pass_error );
unless( $r = $self->apache_request )
{
$r ||= Apache2::RequestUtil->request;
return( $self->error( "No Apache2::RequestRec object was provided." ) ) if( !$r );
$self->apache_request( $r ) || return( $self->pass_error );
}
my( $req, $resp );
unless( $req = $self->request )
{
$req = Apache2::API::Request->new( $r, debug => $self->debug ) ||
return( $self->pass_error( Apache2::API::Request->error ) );
$self->request( $req );
}
unless( $resp = $self->response )
{
$resp = Apache2::API::Response->new( request => $req, debug => $self->debug ) ||
return( $self->pass_error( Apache2::API::Response->error ) );
$self->response( $resp );
}
return( $self );
}
sub apache_request { return( shift->_set_get_object_without_init( 'apache_request', 'Apache2::RequestRec', @_ ) ); }
sub apr1_md5
{
my( $passwd, $salt ) = @_;
my $ht = Apache2::API::Password->new( $passwd, create => 1, algo => 'md5', ( defined( $salt ) ? ( salt => $salt ) : () ) ) ||
die( Apache2::API::Password->error );
return( $ht->hash );
}
sub bailout
{
my $self = shift( @_ );
my $msg;
if( scalar( @_ ) == 1 && ref( $_[0] ) eq 'HASH' )
{
$msg = shift( @_ );
}
elsif( scalar( @_ ) == 1 && $self->_is_a( $_[0] => 'Module::Generic::Exception' ) )
{
my $ex = shift( @_ );
$msg = {};
if( my $code = $ex->code )
{
$msg->{code} = $code;
}
else
{
$msg->{code} = Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
}
$msg->{message} = $ex->message;
my $lang;
if( $ex->can( 'type' ) && ( my $type = $ex->type ) )
{
$msg->{type} = $type;
}
if( !$msg->{lang} && $ex->can( 'lang' ) && ( $lang = $ex->lang ) )
{
lib/Apache2/API.pm view on Meta::CPAN
}
sub decode_url
{
my $self = shift( @_ );
return( APR::Request::decode( shift( @_ ) ) );
}
sub decode_utf8
{
my $self = shift( @_ );
my $v = shift( @_ );
my $rv = eval
{
## utf8 is more lax than the strict standard of utf-8; see Encode man page
Encode::decode( 'utf8', $v, Encode::FB_CROAK );
};
if( $@ )
{
$self->error( "Error while decoding text: $@" );
return( $v );
}
return( $rv );
}
# https://perl.apache.org/docs/2.0/api/APR/Base64.html#toc_C_encode_
# sub encode_base64 { return( APR::Base64::encode( @_ ) ); }
sub encode_base64
{
my $self = shift( @_ );
my $data = shift( @_ );
return( $self->error( "No valid to base64 encode was provided." ) ) if( !length( $data ) );
# try-catch
local $@;
my $rv = eval
{
return( APR::Base64::encode( $data ) );
};
if( $@ )
{
return( $self->error( "An error occurred while trying to base64 encode data: $@" ) );
}
return( $rv );
}
sub encode_json
{
my $self = shift( @_ );
my $hash = shift( @_ ) || return( $self->error( "No perl hash reference was provided to encode." ) );
return( $self->error( "Hash provided ($hash) is not a hash reference." ) ) if( !$self->_is_hash( $hash ) );
my $json = $self->json->allow_nonref->allow_blessed->convert_blessed->relaxed;
my $data;
# try-catch
local $@;
eval
{
$data = $json->encode( $hash );
};
if( $@ )
{
return( $self->error( "An error occurred while trying to encode perl data: $@\nPerl data are: ", sub{ $self->SUPER::dump( $hash ) } ) );
}
return( $data );
}
sub encode_url
{
my $self = shift( @_ );
return( APR::Request::encode( shift( @_ ) ) );
}
sub encode_utf8
{
my $self = shift( @_ );
my $v = shift( @_ );
my $rv = eval
{
## utf8 is more lax than the strict standard of utf-8; see Encode man page
Encode::encode( 'utf8', $v, Encode::FB_CROAK );
};
if( $@ )
{
$self->error( "Error while encoding text: $@" );
return( $v );
}
return( $rv );
}
# <https://perl.apache.org/docs/2.0/api/APR/UUID.html>
sub generate_uuid
{
my $self = shift( @_ );
# try-catch
local $@;
my $rv = eval
{
return( APR::UUID->new->format );
};
if( $@ )
{
return( $self->error( "An error occurred while trying to generate an uuid using APR::UUID package: $@" ) );
}
return( $rv );
}
# rfc 6750 <https://tools.ietf.org/html/rfc6750>
sub get_auth_bearer
{
my $self = shift( @_ );
my $bearer = $self->request->authorization;
# Found a bearer
if( $bearer )
{
# https://jwt.io/introduction/
# https://tools.ietf.org/html/rfc7519
# if( $bearer =~ /^Bearer[[:blank:]]+([a-zA-Z0-9][a-zA-Z0-9\-\_\~\+\/\=]+(?:\.[a-zA-Z0-9\_][a-zA-Z0-9\-\_\~\+\/\=]+){2,4})$/i )
if( $bearer =~ /^Bearer[[:blank:]]+([a-zA-Z0-9][a-zA-Z0-9\-\_\~\+\/\=]+(?:\.[a-zA-Z0-9\_][a-zA-Z0-9\-\_\~\+\/\=]+)*)$/i )
{
my $token = $1;
return( $token );
}
lib/Apache2/API.pm view on Meta::CPAN
$txt = sprintf( "$txt called from %s in package %s in file %s at line %d\n%s\n", $frame2->subroutine, $frame->package, $frame->filename, $frame->line, $trace->as_string );
return( $r->warn( $txt ) ) if( $r );
return( CORE::warn( $txt ) );
}
sub _try
{
my $self = shift( @_ );
my $pack = shift( @_ ) || return( $self->error( "No Apache package name was provided to call method" ) );
my $meth = shift( @_ ) || return( $self->error( "No method name was provided to try!" ) );
my $r = Apache2::RequestUtil->request;
# $r->log_error( "Apache2::API::_try to call method \"$meth\" in package \"$pack\"." );
# try-catch
local $@;
my $rv = eval
{
return( $self->$pack->$meth ) if( !scalar( @_ ) );
return( $self->$pack->$meth( @_ ) );
};
if( $@ )
{
return( $self->error( "An error occurred while trying to call Apache ", ucfirst( $pack ), " method \"$meth\": $@" ) );
}
return( $rv );
}
# NOTE: sub FREEZE is inherited
sub STORABLE_freeze { CORE::return( CORE::shift->FREEZE( @_ ) ); }
sub STORABLE_thaw { CORE::return( CORE::shift->THAW( @_ ) ); }
# NOTE: sub THAW is inherited
# NOTE: Apache2::API::Password
package Apache2::API::Password;
use parent qw( Module::Generic );
use strict;
use warnings;
use vars qw( $VERSION $APR1_RE $BCRYPT_RE $SHA_RE );
# Compile the regular expression once
our $APR1_RE = qr/\$apr1\$(?<salt>[.\/0-9A-Za-z]{1,8})\$[.\/0-9A-Za-z]{22}/;
our $BCRYPT_RE = qr/\$2[aby]\$(?<bcrypt_cost>\d{2})\$(?<salt>[A-Za-z0-9.\/]{22})[A-Za-z0-9.\/]{31}/;
our $SHA_RE = qr/\$(?<sha_size>[56])\$(?:rounds=(?<rounds>\d+)\$)?(?<salt>[A-Za-z0-9.\/]{1,16})\$[A-Za-z0-9.\/]+/;
our $VERSION = 'v0.1.1';
sub init
{
my $self = shift( @_ );
my $pwd = shift( @_ );
return( $self->error( "No password was provided." ) ) if( !defined( $pwd ) );
$self->{create} = 0 if( !exists( $self->{create} ) );
# md5 | bcrypt | sha256 | sha512
$self->{algo} = 'md5' if( !exists( $self->{algo} ) );
# 04..31
$self->{bcrypt_cost} = 12 if( !exists( $self->{bcrypt_cost} ) );
# undef => default (5000)
$self->{sha_rounds} = undef if( !exists( $self->{sha_rounds} ) );
# By default, like Apache does, we use Apache md5 algorithm
# Other possibilities are bcrypt (Blowfish)
$self->SUPER::init( @_ ) ||
return( $self->pass_error );
if( $self->{create} )
{
my $hash = $self->make( $pwd ) ||
return( $self->pass_error );
$self->hash( $hash );
}
# Existing hash path: validate by known prefixes, also extract salt into ->salt
elsif( $pwd =~ /\A$APR1_RE\z/ ||
$pwd =~ /\A$BCRYPT_RE\z/ ||
$pwd =~ /\A$SHA_RE\z/ )
{
$self->hash( $pwd );
}
else
{
return( $self->error(
"Value provided is not a recognized hash (APR1/bcrypt/SHA-crypt). " .
"If you want to create one from clear text, use the 'create' option."
) );
}
return( $self );
}
sub algo { return( shift->_set_get_enum({
field => 'algo',
allowed => [qw( md5 bcrypt sha256 sha512 )],
}, @_ ) ); }
sub bcrypt_cost { return( shift->_set_get_scalar({
field => 'bcrypt_cost',
check => sub
{
my( $self, $v ) = @_;
return(1) unless( defined( $v ) );
unless( $v =~ /^\d+$/ &&
$v >= 4 &&
$v <= 31 )
{
return( $self->error( "bcrypt_cost must be between 4 and 31" ) );
}
return(1);
},
}, @_ ) ); }
sub create { return( shift->_set_get_boolean( 'create', @_ ) ); }
sub hash { return( shift->_set_get_scalar({
field => 'hash',
callbacks =>
{
set => sub
{
my( $self, $v ) = @_;
if( $v =~ /\A$APR1_RE\z/ )
{
$self->{salt} = $+{salt}
}
elsif( $v =~ /\A$BCRYPT_RE\z/ )
{
( run in 1.547 second using v1.01-cache-2.11-cpan-437f7b0c052 )