use
Encode 2.21
'decode_utf8'
,
'encode'
,
'decode'
;
our
$CHUNKSIZE
= 64 * 1024;
has
env
=> (
is
=>
'rw'
,
writer
=>
'_set_env'
,
weak_ref
=>1);
my
$WARN_ABOUT_ENV
= 0;
around
env
=>
sub
{
my
(
$orig
,
$self
,
@args
) =
@_
;
if
(
@args
) {
warn
"env as a writer is deprecated, you probably need to upgrade Catalyst::Engine::PSGI"
unless
$WARN_ABOUT_ENV
++;
return
$self
->_set_env(
@args
);
}
return
$self
->
$orig
;
};
sub
prepare_connection {
my
(
$self
,
$ctx
) =
@_
;
$ctx
->request->prepare_connection;
}
sub
finalize_body {
my
(
$self
,
$c
) =
@_
;
my
$res
=
$c
->response;
return
if
$res
->_has_write_fh;
my
$body
=
$res
->body;
if
(
$res
->_has_response_cb) {
my
@headers
;
$res
->headers->scan(
sub
{
push
@headers
,
@_
});
if
(
defined
$body
) {
if
(blessed(
$body
)) {
if
(
$body
->can(
'getline'
)) {
}
elsif
(
$body
->can(
'read'
)) {
my
$got
;
do
{
$got
=
read
$body
,
my
(
$buffer
),
$CHUNKSIZE
;
$got
= 0
unless
$self
->
write
(
$c
,
$buffer
);
}
while
$got
> 0;
close
$body
;
return
;
}
else
{
$body
= [
"$body"
];
}
}
elsif
(
ref
$body
) {
if
( (
ref
(
$body
) eq
'GLOB'
) or (
ref
(
$body
) eq
'ARRAY'
)) {
}
else
{
$c
->
log
->error(
"${\ref($body)} is not a valid value for Response->body"
);
return
;
}
}
else
{
$body
= [
$body
];
}
}
else
{
$body
= [];
}
$res
->_response_cb->([
$res
->status, \
@headers
,
$body
]);
$res
->_clear_response_cb;
}
else
{
if
(
my
$body
=
$res
->body) {
if
( blessed(
$body
) &&
$body
->can(
'read'
) or
ref
(
$body
) eq
'GLOB'
) {
my
$got
;
do
{
$got
=
read
$body
,
my
(
$buffer
),
$CHUNKSIZE
;
$got
= 0
unless
$self
->
write
(
$c
,
$buffer
);
}
while
$got
> 0;
close
$body
;
}
else
{
$self
->unencoded_write(
$c
,
$body
);
}
}
$res
->_writer->
close
;
$res
->_clear_writer;
}
return
;
}
sub
finalize_cookies {
my
(
$self
,
$c
) =
@_
;
my
@cookies
;
my
$response
=
$c
->response;
foreach
my
$name
(
keys
%{
$response
->cookies }) {
my
$val
=
$response
->cookies->{
$name
};
my
$cookie
= (
blessed(
$val
)
?
$val
: CGI::Simple::Cookie->new(
-name
=>
$name
,
-value
=>
$val
->{value},
-expires
=>
$val
->{expires},
-domain
=>
$val
->{domain},
-path
=>
$val
->{path},
-secure
=>
$val
->{secure} || 0,
-httponly
=>
$val
->{httponly} || 0,
-samesite
=>
$val
->{samesite},
)
);
if
(!
defined
$cookie
) {
$c
->
log
->
warn
(
"undef passed in '$name' cookie value - not setting cookie"
)
if
$c
->debug;
next
;
}
push
@cookies
,
$cookie
->as_string;
}
for
my
$cookie
(
@cookies
) {
$response
->headers->push_header(
'Set-Cookie'
=>
$cookie
);
}
}
sub
_dump_error_page_element {
my
(
$self
,
$i
,
$element
) =
@_
;
my
(
$name
,
$val
) = @{
$element
};
local
$val
->{
'__MOP__'
} =
"Stringified: "
.
$val
->{
'__MOP__'
}
if
ref
$val
eq
'HASH'
&&
exists
$val
->{
'__MOP__'
};
my
$text
= encode_entities(
dump
(
$val
));
sprintf
<<"EOF", $name, $text;
<h2><a href="#" onclick="toggleDump('dump_$i'); return false">%s</a></h2>
<div id="dump_$i">
<pre wrap="">%s</pre>
</div>
EOF
}
sub
finalize_error {
my
(
$self
,
$c
) =
@_
;
$c
->res->content_type(
'text/html; charset=utf-8'
);
my
$name
=
ref
(
$c
)->config->{name} ||
join
(
' '
,
split
(
'::'
,
ref
$c
));
if
(
$c
->can(
'encoding'
)) {
$c
->{encoding} =
''
;
}
my
(
$title
,
$error
,
$infos
);
if
(
$c
->debug ) {
$error
=
join
''
,
map
{
'<p><code class="error">'
. encode_entities(
$_
)
.
'</code></p>'
} @{
$c
->error };
$error
||=
'No output'
;
$error
=
qq{<pre wrap="">$error</pre>}
;
$title
=
$name
=
"$name on Catalyst $Catalyst::VERSION"
;
$name
=
"<h1>$name</h1>"
;
$c
->res->_clear_context;
$c
->req->_clear_body;
my
@infos
;
my
$i
= 0;
for
my
$dump
(
$c
->dump_these ) {
push
@infos
,
$self
->_dump_error_page_element(
$i
,
$dump
);
$i
++;
}
$infos
=
join
"\n"
,
@infos
;
}
else
{
$title
=
$name
;
$error
=
''
;
$infos
= <<
""
;
<pre>
(en) Please come back later
(fr) SVP veuillez revenir plus tard
(de) Bitte versuchen sie es spaeter nocheinmal
(at) Konnten
's bitt'
schoen spaeter nochmal reinschauen
(
no
) Vennligst prov igjen senere
(dk) Venligst prov igen senere
(pl) Prosze sprobowac pozniej
(pt) Por favor volte mais tarde
(ru) Попробуйте еще раз позже
(ua) Спробуйте ще раз пізніше
(it) Per favore riprova più tardi
(cs) Vraťte se prosím později
</pre>
$name
=
''
;
}
$c
->res->body( <<
""
);
<!DOCTYPE html PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN"
<head>
<meta http-equiv=
"Content-Language"
content=
"en"
/>
<meta http-equiv=
"Content-Type"
content=
"text/html; charset=utf-8"
/>
<title>
$title
</title>
<script type=
"text/javascript"
>
<!--
function toggleDump (dumpElement) {
var e = document.getElementById( dumpElement );
if
(e.style.display ==
"none"
) {
e.style.display =
""
;
}
else
{
e.style.display =
"none"
;
}
}
-->
</script>
<style type=
"text/css"
>
body {
font-family:
"Bitstream Vera Sans"
,
"Trebuchet MS"
, Verdana,
Tahoma, Arial, helvetica, sans-serif;
color:
background-color:
margin: 0px;
padding: 0px;
}
:
link
, :
link
:hover, :visited, :visited:hover {
color:
}
div.box {
position: relative;
background-color:
border: 1px solid
padding: 4px;
margin: 10px;
}
div.error {
background-color:
border: 1px solid
padding: 8px;
margin: 4px;
margin-bottom: 10px;
}
div.infos {
background-color:
border: 1px solid
padding: 8px;
margin: 4px;
margin-bottom: 10px;
}
div.name {
background-color:
border: 1px solid
padding: 8px;
margin: 4px;
}
code.error {
display: block;
margin: 1em 0;
overflow: auto;
}
div.name h1, div.error p {
margin: 0;
}
h2 {
margin-top: 0;
margin-bottom: 10px;
font-size: medium;
font-weight: bold;
text-decoration: underline;
}
h1 {
font-size: medium;
font-weight: normal;
}
/* Browser specific (not valid) styles to make preformatted text wrap */
pre {
white-space: pre-wrap; /* css-3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
</style>
</head>
<body>
<div class=
"box"
>
<div class=
"error"
>
$error
</div>
<div class=
"infos"
>
$infos
</div>
<div class=
"name"
>
$name
</div>
</div>
</body>
</html>
$c
->res->{body} .= (
' '
x 512 );
$c
->res->{body} = Encode::encode(
"UTF-8"
,
$c
->res->{body});
$c
->res->status(500);
}
sub
finalize_headers {
my
(
$self
,
$ctx
) =
@_
;
$ctx
->finalize_headers
unless
$ctx
->response->finalized_headers;
return
;
}
sub
finalize_uploads {
my
(
$self
,
$c
) =
@_
;
my
$request
=
$c
->request;
foreach
my
$key
(
keys
%{
$request
->uploads }) {
my
$upload
=
$request
->uploads->{
$key
};
unlink
grep
{ -e
$_
}
map
{
$_
->tempname }
(
ref
$upload
eq
'ARRAY'
? @{
$upload
} : (
$upload
));
}
}
sub
prepare_body {
my
(
$self
,
$c
) =
@_
;
$c
->request->prepare_body;
}
sub
prepare_body_chunk {
my
(
$self
,
$c
,
$chunk
) =
@_
;
$c
->request->prepare_body_chunk(
$chunk
);
}
sub
prepare_body_parameters {
my
(
$self
,
$c
) =
@_
;
$c
->request->prepare_body_parameters;
}
sub
prepare_parameters {
my
(
$self
,
$c
) =
@_
;
$c
->request->_clear_parameters;
return
$c
->request->parameters;
}
sub
prepare_path {
my
(
$self
,
$ctx
) =
@_
;
my
$env
=
$ctx
->request->env;
my
$scheme
=
$ctx
->request->secure ?
'https'
:
'http'
;
my
$host
=
$env
->{HTTP_HOST} ||
$env
->{SERVER_NAME};
my
$port
=
$env
->{SERVER_PORT} || 80;
my
$base_path
=
$env
->{SCRIPT_NAME} ||
"/"
;
my
$path
;
if
(!
$ctx
->config->{use_request_uri_for_path}) {
my
$path_info
=
$env
->{PATH_INFO};
if
(
exists
$env
->{REDIRECT_URL} ) {
$base_path
=
$env
->{REDIRECT_URL};
$base_path
=~ s/\Q
$path_info
\E$//;
}
$path
=
$base_path
.
$path_info
;
$path
=~ s{^/+}{};
$path
=~ s/([^
$URI::uric
])/
$URI::Escape::escapes
{$1}/go;
$path
=~ s/\?/%3F/g;
}
else
{
my
$req_uri
=
$env
->{REQUEST_URI};
$req_uri
=~ s/\?.*$//;
$path
=
$req_uri
;
$path
=~ s{^/+}{};
}
my
$uri_class
=
"URI::$scheme"
;
$host
=~ s/:(?:80|443)$//;
if
(
$port
!~ /^(?:80|443)$/ &&
$host
!~ /:/) {
$host
.=
":$port"
;
}
my
$query
=
$env
->{QUERY_STRING} ?
'?'
.
$env
->{QUERY_STRING} :
''
;
my
$uri
=
$scheme
.
'://'
.
$host
.
'/'
.
$path
.
$query
;
$ctx
->request->uri( (
bless
\
$uri
,
$uri_class
)->canonical );
$base_path
.=
'/'
unless
$base_path
=~ m{/$};
my
$base_uri
=
$scheme
.
'://'
.
$host
.
$base_path
;
$ctx
->request->base(
bless
\
$base_uri
,
$uri_class
);
return
;
}
sub
prepare_query_parameters {
my
(
$self
,
$c
) =
@_
;
my
$env
=
$c
->request->env;
my
$do_not_decode_query
=
$c
->config->{do_not_decode_query};
my
$old_encoding
;
if
(
my
$new
=
$c
->config->{default_query_encoding}) {
$old_encoding
=
$c
->encoding;
$c
->encoding(
$new
);
}
my
$check
=
$c
->config->{do_not_check_query_encoding} ?
undef
:
$c
->_encode_check;
my
$decoder
=
sub
{
my
$str
=
shift
;
return
$str
if
$do_not_decode_query
;
return
$c
->_handle_param_unicode_decoding(
$str
,
$check
);
};
my
$query_string
=
exists
$env
->{QUERY_STRING}
?
$env
->{QUERY_STRING}
:
''
;
$query_string
=~ s/\A[&;]+//;
my
@unsplit_pairs
=
split
/[&;]+/,
$query_string
;
my
$p
= Hash::MultiValue->new();
my
$is_first_pair
= 1;
for
my
$pair
(
@unsplit_pairs
) {
my
(
$name
,
$value
)
=
map
{
defined
$_
?
$decoder
->(
$self
->unescape_uri(
$_
)) :
$_
}
(
split
/=/,
$pair
, 2 )[0,1];
if
(
$is_first_pair
) {
$c
->request->query_keywords(
$name
)
unless
defined
$value
;
$is_first_pair
= 0;
}
$p
->add(
$name
=>
$value
);
}
$c
->encoding(
$old_encoding
)
if
$old_encoding
;
$c
->request->query_parameters(
$c
->request->_use_hash_multivalue ?
$p
:
$p
->mixed );
}
sub
prepare_read {
my
(
$self
,
$c
) =
@_
;
$c
->request->_read_length;
}
sub
prepare_request {
my
(
$self
,
$ctx
,
%args
) =
@_
;
$ctx
->
log
->psgienv(
$args
{env})
if
$ctx
->
log
->can(
'psgienv'
);
$ctx
->request->_set_env(
$args
{env});
$self
->_set_env(
$args
{env});
$ctx
->response->_set_response_cb(
$args
{response_cb});
}
sub
prepare_uploads {
my
(
$self
,
$c
) =
@_
;
my
$request
=
$c
->request;
return
unless
$request
->_body;
my
$enc
=
$c
->encoding;
my
$uploads
=
$request
->_body->upload;
my
$parameters
=
$request
->parameters;
foreach
my
$name
(
keys
%$uploads
) {
my
$files
=
$uploads
->{
$name
};
$name
=
$c
->_handle_unicode_decoding(
$name
)
if
$enc
;
my
@uploads
;
for
my
$upload
(
ref
$files
eq
'ARRAY'
?
@$files
: (
$files
)) {
my
$headers
= HTTP::Headers->new( %{
$upload
->{headers} } );
my
$filename
=
$upload
->{filename};
$filename
=
$c
->_handle_unicode_decoding(
$filename
)
if
$enc
;
my
$u
= Catalyst::Request::Upload->new
(
size
=>
$upload
->{size},
type
=>
scalar
$headers
->content_type,
charset
=>
scalar
$headers
->content_type_charset,
headers
=>
$headers
,
tempname
=>
$upload
->{tempname},
filename
=>
$filename
,
);
push
@uploads
,
$u
;
}
$request
->uploads->{
$name
} =
@uploads
> 1 ? \
@uploads
:
$uploads
[0];
my
@filenames
=
map
{
$_
->{filename} }
@uploads
;
if
(
exists
$parameters
->{
$name
}) {
if
(
ref
$parameters
->{
$name
} eq
'ARRAY'
) {
push
@{
$parameters
->{
$name
} },
@filenames
;
}
else
{
$parameters
->{
$name
} = [
$parameters
->{
$name
},
@filenames
];
}
}
else
{
$parameters
->{
$name
} =
@filenames
> 1 ? \
@filenames
:
$filenames
[0];
}
}
}
sub
write
{
my
(
$self
,
$c
,
$buffer
) =
@_
;
$c
->response->
write
(
$buffer
);
}
sub
unencoded_write {
my
(
$self
,
$c
,
$buffer
) =
@_
;
$c
->response->unencoded_write(
$buffer
);
}
sub
read
{
my
(
$self
,
$c
,
$maxlength
) =
@_
;
$c
->request->
read
(
$maxlength
);
}
sub
read_chunk {
my
(
$self
,
$ctx
) = (
shift
,
shift
);
return
$ctx
->request->read_chunk(
@_
);
}
sub
run {
my
(
$self
,
$app
,
$psgi
,
@args
) =
@_
;
my
$server
=
pop
@args
if
(
scalar
@args
&& blessed
$args
[-1]);
my
$options
=
pop
@args
if
(
scalar
@args
&&
ref
(
$args
[-1]) eq
'HASH'
);
if
(
scalar
@args
&& !
ref
(
$args
[0])) {
if
(
my
$listen
=
shift
@args
) {
$options
->{
listen
} ||= [
$listen
];
}
}
if
(!
$server
) {
$server
= Catalyst::EngineLoader->new(
application_name
=>
ref
(
$self
))->auto(
%$options
);
$app
->
log
->
warn
(
"Not supplied a Plack engine, falling back to engine auto-loader (are your scripts ancient?)"
)
}
$app
->run_options(
$options
);
$server
->run(
$psgi
,
$options
);
}
sub
build_psgi_app {
my
(
$self
,
$app
,
@args
) =
@_
;
return
sub
{
my
(
$env
) =
@_
;
return
sub
{
my
(
$respond
) =
@_
;
confess(
"Did not get a response callback for writer, cannot continue"
)
unless
$respond
;
$app
->handle_request(
env
=>
$env
,
response_cb
=>
$respond
);
};
};
}
sub
unescape_uri {
my
(
$self
,
$str
) =
@_
;
$str
=~ s/(?:%([0-9A-Fa-f]{2})|\+)/
defined
$1 ?
chr
(
hex
($1)) :
' '
/eg;
return
$str
;
}
__PACKAGE__->meta->make_immutable;
1;