package SOAP::Transport::HTTP;
use strict;
use vars qw($VERSION);
$VERSION = $SOAP::Lite::VERSION;
use SOAP::Lite;
use SOAP::Packager;
package SOAP::Transport::HTTP::Client;
use vars qw(@ISA $COMPRESS $USERAGENT_CLASS);
$USERAGENT_CLASS = 'LWP::UserAgent';
@ISA = qw(SOAP::Client);
$COMPRESS = 'deflate';
my(%redirect, %mpost, %nocompress);
my $_patched = 0;
sub patch {
return if $_patched;
BEGIN { local ($^W) = 0; }
{
local $^W = 0;
sub LWP::UserAgent::redirect_ok; *LWP::UserAgent::redirect_ok = sub {1}
}
{
package LWP::Protocol;
local $^W = 0;
my $collect = \&collect; *collect = sub {
if (defined $_[2]->header('Connection')
&& $_[2]->header('Connection') eq 'Keep-Alive') {
my $data = $_[3]->();
my $next =
SOAP::Utils::bytelength($$data) == $_[2]->header('Content-Length')
? sub { my $str = ''; \$str; }
: $_[3];
my $done = 0;
$_[3] = sub {
$done++ ? &$next : $data
};
}
goto &$collect;
};
}
$_patched++;
};
sub DESTROY { SOAP::Trace::objects('()') }
sub http_request {
my $self = shift;
if (@_) { $self->{'_http_request'} = shift; return $self }
return $self->{'_http_request'};
}
sub http_response {
my $self = shift;
if (@_) { $self->{'_http_response'} = shift; return $self }
return $self->{'_http_response'};
}
sub new {
my $class = shift;
return $class if ref $class;
push @ISA,$USERAGENT_CLASS;
eval("require $USERAGENT_CLASS")
or die "Could not load UserAgent class $USERAGENT_CLASS: $@";
require HTTP::Request;
require HTTP::Headers;
patch() if $SOAP::Constants::PATCH_HTTP_KEEPALIVE;
my(@params, @methods);
while (@_) {
$class->can($_[0])
? push(@methods, shift() => shift)
: push(@params, shift)
}
my $self = $class->SUPER::new(@params);
die "SOAP::Transport::HTTP::Client must inherit from LWP::UserAgent, or one of its subclasses"
if !$self->isa("LWP::UserAgent");
$self->agent(join '/', 'SOAP::Lite', 'Perl', SOAP::Transport::HTTP->VERSION);
$self->options({});
$self->http_request(HTTP::Request->new);
$self->http_request->headers(HTTP::Headers->new);
$self->http_request->header(Accept => ['text/xml', 'multipart/*', 'application/soap']);
while (@methods) {
my($method, $params) = splice(@methods,0,2);
$self->$method(ref $params eq 'ARRAY' ? @$params : $params)
}
SOAP::Trace::objects('()');
return $self;
}
sub send_receive {
my ($self, %parameters) = @_;
my ($context, $envelope, $endpoint, $action, $encoding, $parts) =
@parameters{qw(context envelope endpoint action encoding parts)};
$endpoint ||= $self->endpoint;
my $method = 'POST';
$COMPRESS = 'gzip';
$self->options->{is_compress}
||= exists $self->options->{compress_threshold}
&& eval { require Compress::Zlib };
$self->http_request->method($method);
$self->http_request->url($endpoint);
no strict 'refs';
if ($parts) {
my $packager = $context->packager;
$envelope = $packager->package($envelope,$context);
for my $hname (keys %{$packager->headers_http}) {
$self->http_request->headers->header(
$hname => $packager->headers_http->{$hname}
);
}
}
COMPRESS: {
my $compressed = !exists $nocompress{$endpoint}
&& $self->options->{is_compress}
&& ($self->options->{compress_threshold} || 0) < length $envelope;
$envelope = Compress::Zlib::memGzip($envelope) if $compressed;
my $original_encoding = $self->http_request->content_encoding;
while (1) {
$endpoint = $redirect{$endpoint} if exists $redirect{$endpoint};
$method = 'M-POST' if exists $mpost{$endpoint};
my $bytelength = SOAP::Utils::bytelength($envelope);
$envelope = pack('C0A*', $envelope)
if !$SOAP::Constants::DO_NOT_USE_LWP_LENGTH_HACK
&& length($envelope) != $bytelength;
$self->http_request->content($envelope);
$self->http_request->protocol('HTTP/1.1');
$self->http_request
->proxy_authorization_basic($ENV{'HTTP_proxy_user'},
$ENV{'HTTP_proxy_pass'})
if ($ENV{'HTTP_proxy_user'} && $ENV{'HTTP_proxy_pass'});
if ($method eq 'M-POST') {
my $prefix = sprintf '%04d', int(rand(1000));
$self->http_request->header(
Man => qq!"$SOAP::Constants::NS_ENV"; ns=$prefix!);
$self->http_request->header("$prefix-SOAPAction" => $action)
if defined $action;
}
else {
$self->http_request->header(SOAPAction => $action)
if defined $action;
}
$self->http_request->header('Accept-Encoding' =>
[$SOAP::Transport::HTTP::Client::COMPRESS])
if $self->options->{is_compress};
$self->http_request->content_encoding($SOAP::Transport::HTTP::Client::COMPRESS)
if $compressed;
if(!$self->http_request->content_type) {
$self->http_request->content_type(join '; ',
$SOAP::Constants::DEFAULT_HTTP_CONTENT_TYPE,
!$SOAP::Constants::DO_NOT_USE_CHARSET && $encoding
? 'charset=' . lc($encoding)
: ()
);
}
elsif (!$SOAP::Constants::DO_NOT_USE_CHARSET && $encoding ) {
my $tmpType = $self->http_request->headers->header('Content-type');
my $addition = '; charset=' . lc($encoding);
$self->http_request->content_type($tmpType.$addition) if ($tmpType !~ /$addition/);
}
$self->http_request->content_length($bytelength);
SOAP::Trace::transport($self->http_request);
SOAP::Trace::debug($self->http_request->as_string);
$self->SUPER::env_proxy if $ENV{'HTTP_proxy'};
$self->http_response($self->SUPER::request($self->http_request));
SOAP::Trace::transport($self->http_response);
SOAP::Trace::debug($self->http_response->as_string);
if (($self->http_response->code == 510
|| $self->http_response->code == 501)
&& $method ne 'M-POST') {
$mpost{$endpoint} = 1;
}
elsif ($self->http_response->code == 415 && $compressed) {
$nocompress{$endpoint} = 1;
$envelope = Compress::Zlib::memGunzip($envelope);
$self->http_request
->headers->remove_header('Content-Encoding');
redo COMPRESS; }
else {
last;
}
}
}
$redirect{$endpoint} = $self->http_response->request->url
if $self->http_response->previous
&& $self->http_response->previous->is_redirect;
$self->code($self->http_response->code);
$self->message($self->http_response->message);
$self->is_success($self->http_response->is_success);
$self->status($self->http_response->status_line);
$self->{'_cookie_jar'}->extract_cookies($self->http_response) if
$self->{'_cookie_jar'};
my $content =
($self->http_response->content_encoding || '')
=~ /\b$SOAP::Transport::HTTP::Client::COMPRESS\b/o &&
$self->options->{is_compress}
? Compress::Zlib::memGunzip($self->http_response->content)
: ($self->http_response->content_encoding || '') =~ /\S/
? die "Can't understand returned Content-Encoding (@{[$self->http_response->content_encoding]})\n"
: $self->http_response->content;
return $self->http_response->content_type =~ m!^multipart/!i
? join("\n", $self->http_response->headers_as_string, $content)
: $content;
}
package SOAP::Transport::HTTP::Server;
use vars qw(@ISA $COMPRESS);
@ISA = qw(SOAP::Server);
use URI;
$COMPRESS = 'deflate';
sub DESTROY { SOAP::Trace::objects('()') }
sub new {
require LWP::UserAgent;
my $self = shift;
return $self if ref $self;
my $class = $self;
$self = $class->SUPER::new(@_);
$self->{'_on_action'} = sub {
(my $action = shift || '') =~ s/^(\"?)(.*)\1$/$2/;
die "SOAPAction shall match 'uri#method' if present (got '$action', expected '@{[join('#', @_)]}'\n"
if $action && $action ne join('#', @_)
&& $action ne join('/', @_)
&& (substr($_[0], -1, 1) ne '/'
|| $action ne join('', @_));
};
SOAP::Trace::objects('()');
return $self;
}
sub BEGIN {
no strict 'refs';
for my $method (qw(request response)) {
my $field = '_' . $method;
*$method = sub {
my $self = shift->new;
@_
? ($self->{$field} = shift, return $self)
: return $self->{$field};
};
}
}
sub handle {
my $self = shift->new;
if ($self->request->method eq 'POST') {
$self->action($self->request->header('SOAPAction') || undef);
}
elsif ($self->request->method eq 'M-POST') {
return $self->response(HTTP::Response->new(510, "Expected Mandatory header with $SOAP::Constants::NS_ENV as unique URI"))
if $self->request->header('Man') !~ /^"$SOAP::Constants::NS_ENV";\s*ns\s*=\s*(\d+)/;
$self->action($self->request->header("$1-SOAPAction") || undef);
}
else {
return $self->response(HTTP::Response->new(405)) }
my $compressed = ($self->request->content_encoding || '') =~ /\b$COMPRESS\b/;
$self->options->{is_compress} ||= $compressed && eval { require Compress::Zlib };
return $self->response(HTTP::Response->new(415)) if $compressed && !$self->options->{is_compress} ||
!$compressed && ($self->request->content_encoding || '') =~ /\S/;
my $content_type = $self->request->content_type || '';
return $self->make_fault($SOAP::Constants::FAULT_CLIENT,
"Content-Type must be 'text/xml,' 'multipart/*,' or 'application/dime' instead of '$content_type'")
if $content_type
&& $content_type ne 'text/xml'
&& $content_type ne 'application/dime'
&& $content_type !~ m!^multipart/!;
if (defined($self->request->header("Expect")) &&
($self->request->header("Expect") eq "100-Continue")) {
}
my $content = $compressed
? Compress::Zlib::uncompress($self->request->content)
: $self->request->content;
my $response = $self->SUPER::handle(
$self->request->content_type =~ m!^multipart/!
? join("\n", $self->request->headers_as_string, $content)
: $content
)
or return;
$self->make_response($SOAP::Constants::HTTP_ON_SUCCESS_CODE, $response);
}
sub make_fault {
my $self = shift;
$self->make_response($SOAP::Constants::HTTP_ON_FAULT_CODE => $self->SUPER::make_fault(@_));
return;
}
sub make_response {
my ($self, $code, $response) = @_;
my $encoding = $1
if $response =~ /^<\?xml(?: version="1.0"| encoding="([^\"]+)")+\?>/;
$response =~ s!(\?>)!$1<?xml-stylesheet type="text/css"?>!
if $self->request->content_type eq 'multipart/form-data';
$self->options->{is_compress} ||=
exists $self->options->{compress_threshold}
&& eval { require Compress::Zlib };
my $compressed = $self->options->{is_compress}
&& grep(/\b($COMPRESS|\*)\b/, $self->request->header('Accept-Encoding'))
&& ($self->options->{compress_threshold} || 0) < SOAP::Utils::bytelength $response;
$response = Compress::Zlib::compress($response) if $compressed;
my ($is_multipart) = ($response =~ /content-type:.* boundary="([^\"]*)"/im);
$self->response(HTTP::Response->new(
$code => undef,
HTTP::Headers->new(
'SOAPServer' => $self->product_tokens,
$compressed ? ('Content-Encoding' => $COMPRESS) : (),
'Content-Type' => join('; ', 'text/xml',
!$SOAP::Constants::DO_NOT_USE_CHARSET &&
$encoding ? 'charset=' . lc($encoding) : ()),
'Content-Length' => SOAP::Utils::bytelength $response
),
($] > 5.007)
? do { require Encode; Encode::encode($encoding, $response) }
: $response,
));
$self->response->headers->header('Content-Type' => 'Multipart/Related; type="text/xml"; start="<main_envelope>"; boundary="'.$is_multipart.'"') if $is_multipart;
}
sub product_tokens { join '/', 'SOAP::Lite', 'Perl', $SOAP::Transport::HTTP::VERSION; }
package SOAP::Transport::HTTP::CGI;
use vars qw(@ISA);
@ISA = qw(SOAP::Transport::HTTP::Server);
sub DESTROY { SOAP::Trace::objects('()') }
sub new {
my $self = shift;
return $self if ref $self;
my $class = ref($self) || $self;
$self = $class->SUPER::new(@_);
SOAP::Trace::objects('()');
return $self;
}
sub make_response {
my $self = shift;
$self->SUPER::make_response(@_);
}
sub handle {
my $self = shift->new;
my $length = $ENV{'CONTENT_LENGTH'} || 0;
if (!$length) {
$self->response(HTTP::Response->new(411)) }
elsif (defined $SOAP::Constants::MAX_CONTENT_SIZE && $length > $SOAP::Constants::MAX_CONTENT_SIZE) {
$self->response(HTTP::Response->new(413)) }
else {
if (exists $ENV{EXPECT} && $ENV{EXPECT} =~ /\b100-Continue\b/i) {
print "HTTP/1.1 100 Continue\r\n\r\n";
}
my $content = q{};
my $buffer;
binmode(STDIN);
while (read(STDIN,$buffer,$length)) {
$content .= $buffer;
}
$self->request(HTTP::Request->new(
$ENV{'REQUEST_METHOD'} || '' => $ENV{'SCRIPT_NAME'},
HTTP::Headers->new(
map {
(/^HTTP_(.+)/i
? ($1=~m/SOAPACTION/)
?('SOAPAction')
:($1)
: $_
) => $ENV{$_}
} keys %ENV),
$content,
));
$self->SUPER::handle;
}
my $status = defined($ENV{'SERVER_SOFTWARE'}) && $ENV{'SERVER_SOFTWARE'}=~/IIS/
? $ENV{SERVER_PROTOCOL} || 'HTTP/1.0'
: 'Status:';
my $code = $self->response->code;
binmode(STDOUT);
print STDOUT "$status $code ", HTTP::Status::status_message($code)
, "\015\012", $self->response->headers_as_string("\015\012")
, "\015\012", $self->response->content;
}
package SOAP::Transport::HTTP::Daemon;
use Carp ();
use vars qw($AUTOLOAD @ISA);
@ISA = qw(SOAP::Transport::HTTP::Server);
sub DESTROY { SOAP::Trace::objects('()') }
sub new {
my $self = shift;
return $self if (ref $self);
my $class = $self;
my(@params, @methods);
while (@_) {
$class->can($_[0])
? push(@methods, shift() => shift)
: push(@params, shift)
}
$self = $class->SUPER::new;
$self->SSL(1) if !$self->SSL && grep /^SSL_/, @params;
my $http_daemon = $self->http_daemon_class;
eval "require $http_daemon"
or Carp::croak $@
unless UNIVERSAL::can($http_daemon => 'new');
$self->{_daemon} = $http_daemon->new(@params)
or Carp::croak "Can't create daemon: $!";
$self->myuri(URI->new($self->url)->canonical->as_string);
while (@methods) {
my($method, $params) = splice(@methods,0,2);
$self->$method(ref $params eq 'ARRAY'
? @$params
: $params
);
}
SOAP::Trace::objects('()');
return $self;
}
sub SSL {
my $self = shift->new;
if (@_) {
$self->{_SSL} = shift;
return $self;
}
return $self->{_SSL};
}
sub http_daemon_class { shift->SSL ? 'HTTP::Daemon::SSL' : 'HTTP::Daemon' }
sub AUTOLOAD {
my $method = substr($AUTOLOAD, rindex($AUTOLOAD, '::') + 2);
return if $method eq 'DESTROY';
no strict 'refs';
*$AUTOLOAD = sub { shift->{_daemon}->$method(@_) };
goto &$AUTOLOAD;
}
sub handle {
my $self = shift->new;
while (my $c = $self->accept) {
while (my $r = $c->get_request) {
$self->request($r);
$self->SUPER::handle;
$c->send_response($self->response)
}
UNIVERSAL::can($c, 'shutdown')
? $c->shutdown(2)
: $c->close();
$c->close;
}
}
package SOAP::Transport::HTTP::Apache;
use vars qw(@ISA);
@ISA = qw(SOAP::Transport::HTTP::Server);
sub DESTROY { SOAP::Trace::objects('()') }
sub new {
my $self = shift;
unless (ref $self) {
my $class = ref($self) || $self;
$self = $class->SUPER::new(@_);
SOAP::Trace::objects('()');
}
if(defined $ENV{MOD_PERL_API_VERSION} && $ENV{MOD_PERL_API_VERSION} >= 2) {
require Apache2::RequestRec;
require Apache2::RequestIO;
require Apache2::Const;
require Apache2::RequestUtil;
require APR::Table;
Apache2::Const->import(-compile => 'OK');
$self->{'MOD_PERL_VERSION'} = 2;
$self->{OK} = &Apache2::Const::OK;
}
else { die "Could not find or load mod_perl"
unless (eval "require mod_perl");
die "Could not detect your version of mod_perl"
if (!defined($mod_perl::VERSION));
if ($mod_perl::VERSION < 1.99) {
require Apache;
require Apache::Constants;
Apache::Constants->import('OK');
$self->{'MOD_PERL_VERSION'} = 1;
$self->{OK} = &Apache::Constants::OK;
}
else {
require Apache::RequestRec;
require Apache::RequestIO;
require Apache::Const;
Apache::Const->import(-compile => 'OK');
$self->{'MOD_PERL_VERSION'} = 1.99;
$self->{OK} = &Apache::OK;
}
}
return $self;
}
sub handler {
my $self = shift->new;
my $r = shift;
if (!$r) {
if ( $self->{'MOD_PERL_VERSION'} < 2 ) {
$r = Apache->request();
}
else {
$r = Apache2::RequestUtil->request();
}
}
my $cont_len;
if ( $self->{'MOD_PERL_VERSION'} < 2 ) {
$cont_len = $r->header_in ('Content-length');
}
else {
$cont_len = $r->headers_in->get('Content-length');
}
my $content = "";
if ($cont_len > 0) {
my $buf;
$content .= $buf while ($r->read($buf,$cont_len) > 0);
}
else {
return Apache2::Const::HTTP_BAD_REQUEST()
if ($self->{'MOD_PERL_VERSION'} >= 2);
return Apache::Constants::BAD_REQUEST();
}
$self->request(HTTP::Request->new(
$r->method() => $r->uri,
HTTP::Headers->new($r->headers_in),
$content
));
$self->SUPER::handle;
$r->status($self->response->code);
if ( $self->{'MOD_PERL_VERSION'} > 1 ) {
$self->response->headers->scan(sub { $r->headers_out->set(@_) });
$r->content_type(join '; ', $self->response->content_type);
}
else {
$self->response->headers->scan(sub { $r->header_out(@_) });
$r->send_http_header(join '; ', $self->response->content_type);
}
$r->print($self->response->content);
return $self->{OK};
}
sub configure {
my $self = shift->new;
my $config = shift->dir_config;
for (%$config) {
$config->{$_} =~ /=>/
? $self->$_({split /\s*(?:=>|,)\s*/, $config->{$_}})
: ref $self->$_()
? () : $self->$_(split /\s+|\s*,\s*/, $config->{$_})
if $self->can($_);
}
return $self;
}
{
sub handle;
*handle = \&handler
}
package SOAP::Transport::HTTP::FCGI;
use vars qw(@ISA);
@ISA = qw(SOAP::Transport::HTTP::CGI);
sub DESTROY { SOAP::Trace::objects('()') }
sub new {
require FCGI;
Exporter::require_version('FCGI' => 0.47);
my $class = shift;
return $class if ref $class;
my $self = $class->SUPER::new(@_);
$self->{_fcgirq} = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR);
SOAP::Trace::objects('()');
return $self;
}
sub handle {
my $self = shift->new;
my ($r1, $r2);
my $fcgirq = $self->{_fcgirq};
while (($r1 = $fcgirq->Accept()) >= 0) {
$r2 = $self->SUPER::handle;
}
return undef;
}
1;