package Net::FastCGI::Protocol; use strict; use warnings; use Carp qw[croak]; use Net::FastCGI qw[]; use Net::FastCGI::Constant qw[:type :common FCGI_KEEP_CONN]; BEGIN { our $VERSION = '0.14'; our @EXPORT_OK = qw[ build_begin_request build_begin_request_body build_begin_request_record build_end_request build_end_request_body build_end_request_record build_header build_params build_record build_stream build_unknown_type_body build_unknown_type_record check_params parse_begin_request_body parse_end_request_body parse_header parse_params parse_record parse_record_body parse_unknown_type_body get_record_length get_type_name get_role_name get_protocol_status_name is_known_type is_management_type is_discrete_type is_stream_type ]; our %EXPORT_TAGS = ( all => \@EXPORT_OK ); my $use_pp = $ENV{NET_FASTCGI_PP} || $ENV{NET_FASTCGI_PROTOCOL_PP}; if (!$use_pp) { eval { require Net::FastCGI::Protocol::XS; }; $use_pp = !!$@; } if ($use_pp) { require Net::FastCGI::Protocol::PP; Net::FastCGI::Protocol::PP->import(@EXPORT_OK); } else { Net::FastCGI::Protocol::XS->import(@EXPORT_OK); } # shared between XS and PP implementation push @EXPORT_OK, 'dump_record', 'dump_record_body'; require Exporter; *import = \&Exporter::import; } our $DUMP_RECORD_MAX = 78; # undocumented our $DUMP_RECORD_ALIGN = !!0; # undocumented my %ESCAPES = ( "\a" => "\\a", "\b" => "\\b", "\t" => "\\t", "\n" => "\\n", "\f" => "\\f", "\r" => "\\r", ); sub dump_record { goto \&dump_record_body if (@_ == 2 || @_ == 3); # deprecated @_ == 1 || croak(q/Usage: dump_record(octets)/); my $len = &get_record_length; ($len && $len <= length $_[0] && vec($_[0], 0, 8) == FCGI_VERSION_1) || return '{Malformed FCGI_Record}'; return dump_record_body(&parse_record); } sub dump_record_body { @_ == 2 || @_ == 3 || croak(q/Usage: dump_record_body(type, request_id [, content])/); my ($type, $request_id) = @_; my $content_length = defined $_[2] ? length $_[2] : 0; my $max = $DUMP_RECORD_MAX > 0 ? $DUMP_RECORD_MAX : FCGI_MAX_CONTENT_LEN; my $out = ''; if ( $type == FCGI_PARAMS || $type == FCGI_GET_VALUES || $type == FCGI_GET_VALUES_RESULT) { if ($content_length == 0) { $out = q[""]; } elsif (check_params($_[2])) { my ($off, $klen, $vlen) = (0); while ($off < $content_length) { my $pos = $off; for ($klen, $vlen) { $_ = vec($_[2], $off, 8); $_ = vec(substr($_[2], $off, 4), 0, 32) & 0x7FFF_FFFF if $_ > 0x7F; $off += $_ > 0x7F ? 4 : 1; } my $head = substr($_[2], $pos, $off - $pos); $head =~ s/(.)/sprintf('\\%.3o',ord($1))/egs; $out .= $head; my $body = substr($_[2], $off, $klen + $vlen); for ($body) { s/([\\\"])/\\$1/g; s/([\a\b\t\n\f\r])/$ESCAPES{$1}/g; s/([^\x20-\x7E])/sprintf('\\x%.2X',ord($1))/eg; } $out .= $body; $off += $klen + $vlen; last if $off > $max; } substr($out, $max - 5) = ' ... ' if length $out > $max; $out = qq["$out"]; } else { $out = 'Malformed FCGI_NameValuePair(s)'; } } elsif ( $type == FCGI_BEGIN_REQUEST || $type == FCGI_END_REQUEST || $type == FCGI_UNKNOWN_TYPE) { if ($content_length != 8) { my $name = $type == FCGI_BEGIN_REQUEST ? 'FCGI_BeginRequestBody' : $type == FCGI_END_REQUEST ? 'FCGI_EndRequestBody' : 'FCGI_UnknownTypeBody'; $out = sprintf '{Malformed %s (expected 8 octets got %d)}', $name, $content_length; } elsif ($type == FCGI_BEGIN_REQUEST) { my ($role, $flags) = parse_begin_request_body($_[2]); if ($flags != 0) { my @set; if ($flags & FCGI_KEEP_CONN) { $flags &= ~FCGI_KEEP_CONN; push @set, 'FCGI_KEEP_CONN'; } if ($flags) { push @set, sprintf '0x%.2X', $flags; } $flags = join '|', @set; } $out = sprintf '{%s, %s}', get_role_name($role), $flags; } elsif($type == FCGI_END_REQUEST) { my ($astatus, $pstatus) = parse_end_request_body($_[2]); $out = sprintf '{%d, %s}', $astatus, get_protocol_status_name($pstatus); } else { my $unknown_type = parse_unknown_type_body($_[2]); $out = sprintf '{%s}', get_type_name($unknown_type); } } elsif ($content_length) { my $looks_like_binary = do { my $count = () = $_[2] =~ /[\r\n\t\x20-\x7E]/g; ($count / $content_length) < 0.7; }; $out = substr($_[2], 0, $max + 1); for ($out) { if ($looks_like_binary) { s/(.)/sprintf('\\x%.2X',ord($1))/egs; } else { s/([\\\"])/\\$1/g; s/([\a\b\t\n\f\r])/$ESCAPES{$1}/g; s/([^\x20-\x7E])/sprintf('\\x%.2X',ord($1))/eg; } } substr($out, $max - 5) = ' ... ' if length $out > $max; $out = qq["$out"]; } else { $out = q[""]; } my $name = get_type_name($type); my $width = 0; $width = 27 - length $name # length("FCGI_GET_VALUES_RESULT") == 22 if $DUMP_RECORD_ALIGN; # + length(0xFFFF) == 5 return sprintf '{%s, %*d, %s}', $name, $width, $request_id, $out; } 1;