Bivio::Agent::HTTP::Request
# Copyright (c) 1999-2017 bivio Software, Inc. All rights reserved. # $Id$ package Bivio::Agent::HTTP::Request; use strict; use Bivio::Base 'Agent.Request'; use Bivio::IO::Trace; #TODO: Should be Socket (); Fix unqualified symbols use Socket; # C<Bivio::Agent::HTTP::Request> is a Bivio Request wrapper for an # Apache::Request. It gathers request information from the URI and posted # parameters. # # A note about URI vs URL. Basically, we use URI everywhere. [RJN: I don't # understand the distinction, but there is a distinction and RFC2616 uses # URI for the most part, so we do, too.] # needed for is_https_port() our($_TRACE); my($_C) = b_use('AgentHTTP.Cookie'); my($_D) = b_use('Bivio.Die'); my($_DC) = b_use('Bivio.DieCode'); my($_DT) = b_use('Type.DateTime'); my($_F) = b_use('AgentHTTP.Form'); my($_FCT) = b_use('FacadeComponent.Task'); my($_FM) = b_use('Biz.FormModel'); my($_H) = b_use('Bivio.HTML'); my($_R) = b_use('AgentHTTP.Reply'); my($_T) = b_use('Agent.Task'); my($_TI) = b_use('Agent.TaskId'); my($_READ_SIZE) = 4096; sub client_redirect { my($self) = shift; if (my $named = $self->internal_client_redirect(@_)) { $self->SUPER::server_redirect($named); # DOES NOT RETURN } $_D->throw_quietly($_DC->CLIENT_REDIRECT_TASK); # DOES NOT RETURN } sub get_content { # (self) : string_ref # (self, IO::File) : IO::File # Returns the content associated with request. my($self, $fh) = @_; return $self->get_if_exists_else_put(content => sub { my($r) = $self->get('r'); my($res) = ''; my($expect) = $r->header_in('content-length'); _trace('Content-Length=', $expect) if $_TRACE; return \$res unless $expect && $expect > 0; my($read) = 0; while ($read < $expect) { my($buf); my($to_read) = $expect - $read; $r->read($buf, $to_read < $_READ_SIZE ? $to_read : $_READ_SIZE); $self->throw_die(CLIENT_ERROR => 'timeout occurred while reading request content' ) unless defined($buf); unless (length($buf)) { $self->warn('read returned zero length buffer, exitting loop'); last; } $read += length($buf); if ($fh) { $self->throw_die(IO_ERROR => { message => 'write failed to file', entity => "$!", }) unless $fh->print($buf); } else { $res .= $buf; } } $self->throw_die(CLIENT_ERROR => 'client interrupt or timeout while reading form-data', ) if $r->connection->aborted; return $fh if $fh; $self->throw_die(CLIENT_ERROR => "Content-Length ($expect) >= actual length: " . length($res) ) unless $expect == length($res); _trace('length', length($res)) if $_TRACE; return \$res; }); } sub get_form { # (self) : hash_ref # Returns form associated the request or C<undef> if no form. # I<form_model> must be set. my($self) = @_; return $self->get_if_exists_else_put( form => sub {$_F->parse($self)}, ); } sub internal_client_redirect { # NOTE: Use cient_redirect unless you know what you are doing my($self, $named) = shift->internal_client_redirect_args(@_); unless (defined($named->{uri})) { # use previous query if not specified, maintains state across pages $self->internal_copy_implicit($named); return $named unless $_FCT->has_uri($named->{task_id}, $self); _trace( 'current: ', $self->get('task_id'), ', new: ', $named->{task_id} ) if $_TRACE && !$named->{realm}; #TODO: Probably needs to be elsewhere foreach my $k (keys(%$named)) { delete($named->{$k}) unless grep($k eq $_, @{$self->FORMAT_URI_PARAMETERS}); } $named->{uri} = $self->format_uri($named); } $self->internal_call_handlers(handle_client_redirect => [$named, $self]); $self->get('reply')->client_redirect($self, $named); return; } sub new { # (proto, Apache.Request) : HTTP.Request # Creates a Request from an apache request. The target and path are # separated. my($proto, $r) = @_; # Set remote IP address if passed through by mod_proxy (RH6.2 and RH7.2) my($client_addr) = $proto->client_addr($r, 1); # Sets Bivio::Agent::Request->get_current, so do the minimal thing my($self) = $proto->internal_new({ reply => $_R->new($r), r => $r, client_addr => $client_addr, is_secure => $ENV{HTTPS} || _is_https_port($proto, $r) ? 1 : 0, }); Bivio::Type::UserAgent->from_header($r->header_in('user-agent') || '') ->put_on_request($self, 1); # Cookie parsed first, so log code works properly. # We must put the cookie now, because it may be used below. # auth_user (may) is set by cookie. $self->put_durable(cookie => $_C->new($self, $r)); $self->put(referer => my $referer = $r->header_in('Referer')); $self->internal_initialize_with_uri( scalar($r->uri), scalar($r->args), ); $self->delete_from_query($_FM->FORM_CONTEXT_QUERY_KEY) unless $referer; return $self; } sub put_client_redirect_state { my($self) = shift; if (my $named = $self->internal_client_redirect(@_)) { b_die($named, ': invalid redirect state'); } return; } sub reset_reply { my($self) = @_; $self->put(reply => $_R->new($self->get('r'))); return; } sub client_addr { my(undef, $r, $update_with_proxy) = @_; if ($r->can('useragent_ip')) { return $r->useragent_ip(); } # backwards compatible interface for apache <= 2.2 if (($r->header_in('x-forwarded-for') || $r->header_in('via') || '') =~ /((?:\d+\.){3}\d+)/) { unless ($update_with_proxy) { return $1; } $r->connection->remote_ip($1); } return $r->connection->remote_ip(); } sub _is_https_port { my($proto, $r) = @_; return $r->connection->local_addr->port % 2; } 1;