1# -*- cperl-indent-level: 4; cperl-continued-brace-offset: -4; cperl-continued-statement-offset: 4 -*- 2 3# Copyright (c) 1998-2005 by Jonathan Swartz. All rights reserved. 4# This program is free software; you can redistribute it and/or modify it 5# under the same terms as Perl itself. 6 7use strict; 8use warnings; 9 10package HTML::Mason::ApacheHandler; 11 12use vars qw($VERSION); 13# do not change the version number 14$VERSION = 1.69; 15 16 17# PerlAddVar was introduced in mod_perl-1.24 18# Support for modperl2 < 1.999022 was removed due to API changes 19BEGIN 20{ 21 if ( $ENV{MOD_PERL} && $ENV{MOD_PERL} =~ /1\.99|2\.0/ ) 22 { 23 require mod_perl2; 24 } 25 elsif ( $ENV{MOD_PERL} ) 26 { 27 require mod_perl; 28 } 29 30 my $mpver = (mod_perl2->VERSION || mod_perl->VERSION || 0); 31 32 # This is the version that introduced PerlAddVar 33 if ($mpver && $mpver < 1.24) 34 { 35 die "mod_perl VERSION >= 1.24 required"; 36 } 37 elsif ($mpver >= 1.99 && $mpver < 1.999022) 38 { 39 die "mod_perl-1.99 is not supported; upgrade to 2.00"; 40 } 41} 42 43#---------------------------------------------------------------------- 44# 45# APACHE-SPECIFIC REQUEST OBJECT 46# 47package HTML::Mason::Request::ApacheHandler; 48 49use HTML::Mason::Request; 50use Class::Container; 51use Params::Validate qw(BOOLEAN); 52Params::Validate::validation_options( on_fail => sub { param_error( join '', @_ ) } ); 53 54use base qw(HTML::Mason::Request); 55 56use HTML::Mason::Exceptions( abbr => [qw(param_error error)] ); 57 58use constant APACHE2 => ($mod_perl2::VERSION || $mod_perl::VERSION || 0) >= 1.999022; 59use constant OK => 0; 60use constant HTTP_OK => 200; 61use constant DECLINED => -1; 62use constant NOT_FOUND => 404; 63use constant REDIRECT => 302; 64 65BEGIN 66{ 67 my $ap_req_class = APACHE2 ? 'Apache2::RequestRec' : 'Apache'; 68 69 __PACKAGE__->valid_params 70 ( ah => { isa => 'HTML::Mason::ApacheHandler', 71 descr => 'An ApacheHandler to handle web requests', 72 public => 0 }, 73 74 apache_req => { isa => $ap_req_class, default => undef, 75 descr => "An Apache request object", 76 public => 0 }, 77 78 cgi_object => { isa => 'CGI', default => undef, 79 descr => "A CGI.pm request object", 80 public => 0 }, 81 82 auto_send_headers => { parse => 'boolean', type => BOOLEAN, default => 1, 83 descr => "Whether HTTP headers should be auto-generated" }, 84 ); 85} 86 87use HTML::Mason::MethodMaker 88 ( read_write => [ map { [ $_ => __PACKAGE__->validation_spec->{$_} ] } 89 qw( ah apache_req auto_send_headers ) ] ); 90 91# A hack for subrequests 92sub _properties { qw(ah apache_req), shift->SUPER::_properties } 93 94sub new 95{ 96 my $class = shift; 97 my $self = $class->SUPER::new(@_); # Magic! 98 99 unless ($self->apache_req or $self->cgi_object) 100 { 101 param_error __PACKAGE__ . "->new: must specify 'apache_req' or 'cgi_object' parameter"; 102 } 103 104 # Record a flag indicating whether the user passed a custom out_method 105 my %params = @_; 106 $self->ah->{has_custom_out_method} = exists $params{out_method}; 107 108 return $self; 109} 110 111sub cgi_object 112{ 113 my ($self) = @_; 114 115 error "Can't call cgi_object() unless 'args_method' is set to CGI.\n" 116 unless $self->ah->args_method eq 'CGI'; 117 118 if (defined($_[1])) { 119 $self->{cgi_object} = $_[1]; 120 } else { 121 # We may not have created a CGI object if, say, request was a 122 # GET with no query string. Create one on the fly if necessary. 123 $self->{cgi_object} ||= CGI->new(''); 124 } 125 126 return $self->{cgi_object}; 127} 128 129# 130# Override this method to return NOT_FOUND when we get a 131# TopLevelNotFound exception. In case of POST we must trick 132# Apache into not reading POST content again. Wish there were 133# a more standardized way to do this... 134# 135sub exec 136{ 137 my $self = shift; 138 my $r = $self->apache_req; 139 my $retval; 140 141 if ( $self->is_subrequest ) 142 { 143 # no need to go through all the rigamorale below for 144 # subrequests, and it may even break things to do so, since 145 # $r's print should only be redefined once. 146 $retval = $self->SUPER::exec(@_); 147 } 148 else 149 { 150 # ack, this has to be done at runtime to account for the fact 151 # that Apache::Filter changes $r's class and implements its 152 # own print() method. 153 my $real_apache_print = $r->can('print'); 154 155 # Remap $r->print to Mason's $m->print while executing 156 # request, but just for this $r, in case user does an internal 157 # redirect or apache subrequest. 158 local $^W = 0; 159 no strict 'refs'; 160 161 my $req_class = ref $r; 162 no warnings 'redefine'; 163 local *{"$req_class\::print"} = sub { 164 my $local_r = shift; 165 return $self->print(@_) if $local_r eq $r; 166 return $local_r->$real_apache_print(@_); 167 }; 168 $retval = $self->SUPER::exec(@_); 169 } 170 171 # On a success code, send headers if they have not been sent and 172 # if we are the top-level request. Since the out_method sends 173 # headers, this will typically only apply after $m->abort. 174 # On an error code, leave it to Apache to send the headers. 175 if ( !$self->is_subrequest 176 and !APACHE2 177 and $self->auto_send_headers 178 and !$r->notes('mason-sent-headers') 179 and ( !$retval or $retval eq HTTP_OK ) ) { 180 181 $r->send_http_header(); 182 } 183 184 # mod_perl 1 treats HTTP_OK and OK the same, but mod_perl-2 does not. 185 return defined $retval && $retval ne HTTP_OK ? $retval : OK; 186} 187 188# 189# Override this method to always die when top level component is not found, 190# so we can return NOT_FOUND. 191# 192sub _handle_error 193{ 194 my ($self, $err) = @_; 195 196 if (isa_mason_exception($err, 'TopLevelNotFound')) { 197 rethrow_exception $err; 198 } else { 199 if ( $self->error_format eq 'html' ) { 200 $self->apache_req->content_type('text/html'); 201 202 unless (APACHE2) { 203 $self->apache_req->send_http_header; 204 } 205 } 206 $self->SUPER::_handle_error($err); 207 } 208} 209 210sub redirect 211{ 212 my ($self, $url, $status) = @_; 213 my $r = $self->apache_req; 214 215 $r->method('GET'); 216 $r->headers_in->unset('Content-length'); 217 $r->err_headers_out->{Location} = $url; 218 $self->clear_and_abort($status || REDIRECT); 219} 220 221#---------------------------------------------------------------------- 222# 223# APACHEHANDLER OBJECT 224# 225package HTML::Mason::ApacheHandler; 226 227use File::Path; 228use File::Spec; 229use HTML::Mason::Exceptions( abbr => [qw(param_error system_error error)] ); 230use HTML::Mason::Interp; 231use HTML::Mason::Tools qw( load_pkg ); 232use HTML::Mason::Utils; 233use Params::Validate qw(:all); 234Params::Validate::validation_options( on_fail => sub { param_error( join '', @_ ) } ); 235 236use constant APACHE2 => ($mod_perl2::VERSION || $mod_perl::VERSION || 0) >= 1.999022; 237use constant OK => 0; 238use constant HTTP_OK => 200; 239use constant DECLINED => -1; 240use constant NOT_FOUND => 404; 241use constant REDIRECT => 302; 242 243BEGIN { 244 if ($ENV{MOD_PERL}) { 245 if (APACHE2) { 246 require Apache2::RequestRec; 247 require Apache2::RequestIO; 248 require Apache2::ServerUtil; 249 require Apache2::RequestUtil; 250 require Apache2::Log; 251 require APR::Table; 252 } else { 253 require Apache; 254 require Apache::Request; 255 require HTML::Mason::Apache::Request; 256 Apache->import(); 257 } 258 } 259} 260 261if ( $ENV{MOD_PERL} && ! APACHE2 ) 262{ 263 # No modern distro/OS packages a mod_perl without all of this 264 # stuff turned on, does it? 265 266 error "mod_perl must be compiled with PERL_METHOD_HANDLERS=1 (or EVERYTHING=1) to use ", __PACKAGE__, "\n" 267 unless Apache::perl_hook('MethodHandlers'); 268 269 error "mod_perl must be compiled with PERL_TABLE_API=1 (or EVERYTHING=1) to use ", __PACKAGE__, "\n" 270 unless Apache::perl_hook('TableApi'); 271} 272 273use base qw(HTML::Mason::Handler); 274 275BEGIN 276{ 277 __PACKAGE__->valid_params 278 ( 279 apache_status_title => 280 { parse => 'string', type => SCALAR, default => 'HTML::Mason status', 281 descr => "The title of the Apache::Status page" }, 282 283 args_method => 284 { parse => 'string', type => SCALAR, 285 default => APACHE2 ? 'CGI' : 'mod_perl', 286 regex => qr/^(?:CGI|mod_perl)$/, 287 descr => "Whether to use CGI.pm or Apache::Request for parsing the incoming HTTP request", 288 }, 289 290 decline_dirs => 291 { parse => 'boolean', type => BOOLEAN, default => 1, 292 descr => "Whether Mason should decline to handle requests for directories" }, 293 294 # the only required param 295 interp => 296 { isa => 'HTML::Mason::Interp', 297 descr => "A Mason interpreter for processing components" }, 298 ); 299 300 __PACKAGE__->contained_objects 301 ( 302 interp => 303 { class => 'HTML::Mason::Interp', 304 descr => 'The interp class coordinates multiple objects to handle request execution' 305 }, 306 ); 307} 308 309use HTML::Mason::MethodMaker 310 ( read_only => [ 'args_method' ], 311 read_write => [ map { [ $_ => __PACKAGE__->validation_spec->{$_} ] } 312 qw( apache_status_title 313 decline_dirs 314 interp ) ] 315 ); 316 317sub _get_apache_server 318{ 319 return APACHE2 ? Apache2::ServerUtil->server() : Apache->server(); 320} 321 322my ($STARTED); 323 324# The "if _get_apache_server" bit is a hack to let this module load 325# when not under mod_perl, which is needed to generate Params.pod 326__PACKAGE__->_startup() if eval { _get_apache_server }; 327sub _startup 328{ 329 my $pack = shift; 330 return if $STARTED++; # Allows a subclass to call this method without running it twice 331 332 if ( my $args_method = $pack->_get_string_param('MasonArgsMethod') ) 333 { 334 if ($args_method eq 'CGI') 335 { 336 eval { require CGI unless defined CGI->VERSION; }; 337 # mod_perl2 does not warn about this, so somebody should 338 if (APACHE2 && CGI->VERSION < 3.08) { 339 die "CGI version 3.08 is required to support mod_perl2 API"; 340 } 341 die $@ if $@; 342 } 343 elsif ( $args_method eq 'mod_perl' && APACHE2 ) 344 { 345 eval "require Apache2::Request" unless defined Apache2::Request->VERSION; 346 } 347 } 348} 349 350# Register with Apache::Status at module startup. Will get replaced 351# with a more informative status once an interpreter has been created. 352my $status_name = 'mason0001'; 353my $apstat_module = APACHE2 ? 'Apache2::Status' : 'Apache::Status'; 354if ( load_pkg($apstat_module) ) 355{ 356 $apstat_module->menu_item 357 ($status_name => __PACKAGE__->allowed_params->{apache_status_title}{default}, 358 sub { ["<b>(no interpreters created in this child yet)</b>"] }); 359} 360 361 362my %AH_BY_CONFIG; 363sub make_ah 364{ 365 my ($package, $r) = @_; 366 367 my $config = $r->dir_config; 368 369 # 370 # If the user has virtual hosts, each with a different document 371 # root, then we will have to be called from the handler method. 372 # This means we have an active request. In order to distinguish 373 # between virtual hosts with identical config directives that have 374 # no comp root defined (meaning they expect to use the default 375 # comp root), we append the document root for the current request 376 # to the key. 377 # 378 my $key = 379 ( join $;, 380 $r->document_root, 381 map { $_, sort $config->get($_) } 382 grep { /^Mason/ } 383 keys %$config 384 ); 385 386 return $AH_BY_CONFIG{$key} if exists $AH_BY_CONFIG{$key}; 387 388 my %p = $package->_get_mason_params($r); 389 390 # can't use hash_list for this one because it's _either_ a string 391 # or a hash_list 392 if (exists $p{comp_root}) { 393 if (@{$p{comp_root}} == 1 && $p{comp_root}->[0] !~ /=>/) { 394 $p{comp_root} = $p{comp_root}[0]; # Convert to a simple string 395 } else { 396 my @roots; 397 foreach my $root (@{$p{comp_root}}) { 398 $root = [ split /\s*=>\s*/, $root, 2 ]; 399 param_error "Configuration parameter MasonCompRoot must be either ". 400 "a single string value or multiple key/value pairs ". 401 "like 'foo => /home/mason/foo'. Invalid parameter:\n$root" 402 unless defined $root->[1]; 403 404 push @roots, $root; 405 } 406 407 $p{comp_root} = \@roots; 408 } 409 } 410 411 my $ah = $package->new(%p, $r); 412 $AH_BY_CONFIG{$key} = $ah if $key; 413 414 return $ah; 415} 416 417# The following routines handle getting information from $r->dir_config 418 419sub calm_form { 420 # Transform from StudlyCaps to name_like_this 421 my ($self, $string) = @_; 422 $string =~ s/^Mason//; 423 $string =~ s/(^|.)([A-Z])/$1 ? "$1\L_$2" : "\L$2"/ge; 424 return $string; 425} 426 427sub studly_form { 428 # Transform from name_like_this to StudlyCaps 429 my ($self, $string) = @_; 430 $string =~ s/(?:^|_)(\w)/\U$1/g; 431 return $string; 432} 433 434sub _get_mason_params 435{ 436 my $self = shift; 437 my $r = shift; 438 439 my $config = $r ? $r->dir_config : _get_apache_server->dir_config; 440 441 # Get all params starting with 'Mason' 442 my %candidates; 443 444 foreach my $studly ( keys %$config ) 445 { 446 (my $calm = $studly) =~ s/^Mason// or next; 447 $calm = $self->calm_form($calm); 448 449 $candidates{$calm} = $config->{$studly}; 450 } 451 452 return unless %candidates; 453 454 # 455 # We will accumulate all the string versions of the keys and 456 # values here for later use. 457 # 458 return ( map { $_ => 459 scalar $self->_get_param( $_, \%candidates, $config, $r ) 460 } 461 keys %candidates ); 462} 463 464sub _get_param { 465 # Gets a single config item from dir_config. 466 467 my ($self, $key, $candidates, $config, $r) = @_; 468 469 $key = $self->calm_form($key); 470 471 my $spec = $self->allowed_params( $candidates || {} )->{$key} 472 or error "Unknown config item '$key'"; 473 474 # Guess the default parse type from the Params::Validate validation spec 475 my $type = ($spec->{parse} or 476 $spec->{type} & ARRAYREF ? 'list' : 477 $spec->{type} & SCALAR ? 'string' : 478 $spec->{type} & CODEREF ? 'code' : 479 undef) 480 or error "Unknown parse type for config item '$key'"; 481 482 my $method = "_get_${type}_param"; 483 return $self->$method('Mason'.$self->studly_form($key), $config, $r); 484} 485 486sub _get_string_param 487{ 488 my $self = shift; 489 return scalar $self->_get_val(@_); 490} 491 492sub _get_boolean_param 493{ 494 my $self = shift; 495 return scalar $self->_get_val(@_); 496} 497 498sub _get_code_param 499{ 500 my $self = shift; 501 my $p = $_[0]; 502 my $val = $self->_get_val(@_); 503 504 return unless $val; 505 506 my $sub_ref = eval $val; 507 508 param_error "Configuration parameter '$p' is not valid perl:\n$@\n" 509 if $@; 510 511 return $sub_ref; 512} 513 514sub _get_list_param 515{ 516 my $self = shift; 517 my @val = $self->_get_val(@_); 518 if (@val == 1 && ! defined $val[0]) 519 { 520 @val = (); 521 } 522 523 return \@val; 524} 525 526sub _get_hash_list_param 527{ 528 my $self = shift; 529 my @val = $self->_get_val(@_); 530 if (@val == 1 && ! defined $val[0]) 531 { 532 return {}; 533 } 534 535 my %hash; 536 foreach my $pair (@val) 537 { 538 my ($key, $val) = split /\s*=>\s*/, $pair, 2; 539 param_error "Configuration parameter $_[0] must be a key/value pair ". 540 qq|like "foo => bar". Invalid parameter:\n$pair| 541 unless defined $key && defined $val; 542 543 $hash{$key} = $val; 544 } 545 546 return \%hash; 547} 548 549sub _get_val 550{ 551 my ($self, $p, $config, $r) = @_; 552 553 my @val; 554 if (wantarray || !$config) 555 { 556 if ($config) 557 { 558 @val = $config->get($p); 559 } 560 else 561 { 562 my $c = $r ? $r : _get_apache_server; 563 @val = $c->dir_config->get($p); 564 } 565 } 566 else 567 { 568 @val = exists $config->{$p} ? $config->{$p} : (); 569 } 570 571 param_error "Only a single value is allowed for configuration parameter '$p'\n" 572 if @val > 1 && ! wantarray; 573 574 return wantarray ? @val : $val[0]; 575} 576 577sub new 578{ 579 my $class = shift; 580 581 # Get $r off end of params if its there 582 my $r; 583 $r = pop() if @_ % 2; 584 my %params = @_; 585 586 my %defaults; 587 $defaults{request_class} = 'HTML::Mason::Request::ApacheHandler' 588 unless exists $params{request}; 589 590 my $allowed_params = $class->allowed_params(%defaults, %params); 591 592 if ( exists $allowed_params->{comp_root} and 593 my $req = $r || (APACHE2 ? undef : Apache->request) ) # DocumentRoot is only available inside requests 594 { 595 $defaults{comp_root} = $req->document_root; 596 } 597 598 if (exists $allowed_params->{data_dir} and not exists $params{data_dir}) 599 { 600 # constructs path to <server root>/mason 601 if (UNIVERSAL::can('Apache2::ServerUtil','server_root')) { 602 $defaults{data_dir} = File::Spec->catdir(Apache2::ServerUtil::server_root(),'mason'); 603 } else { 604 $defaults{data_dir} = Apache->server_root_relative('mason'); 605 } 606 my $def = $defaults{data_dir}; 607 param_error "Default data_dir (MasonDataDir) '$def' must be an absolute path" 608 unless File::Spec->file_name_is_absolute($def); 609 610 my @levels = File::Spec->splitdir($def); 611 param_error "Default data_dir (MasonDataDir) '$def' must be more than two levels deep (or must be set explicitly)" 612 if @levels <= 3; 613 } 614 615 # Set default error_format based on error_mode 616 if (exists($params{error_mode}) and $params{error_mode} eq 'fatal') { 617 $defaults{error_format} = 'line'; 618 } else { 619 $defaults{error_mode} = 'output'; 620 $defaults{error_format} = 'html'; 621 } 622 623 # Push $r onto default allow_globals 624 if (exists $allowed_params->{allow_globals}) { 625 if ( $params{allow_globals} ) { 626 push @{ $params{allow_globals} }, '$r'; 627 } else { 628 $defaults{allow_globals} = ['$r']; 629 } 630 } 631 632 my $self = eval { $class->SUPER::new(%defaults, %params) }; 633 634 # We catch this exception just to provide a better error message 635 if ( $@ && isa_mason_exception( $@, 'Params' ) && $@->message =~ /comp_root/ ) 636 { 637 param_error "No comp_root specified and cannot determine DocumentRoot." . 638 " Please provide comp_root explicitly."; 639 } 640 rethrow_exception $@; 641 642 unless ( $self->interp->resolver->can('apache_request_to_comp_path') ) 643 { 644 error "The resolver class your Interp object uses does not implement " . 645 "the 'apache_request_to_comp_path' method. This means that ApacheHandler " . 646 "cannot resolve requests. Are you using a handler.pl file created ". 647 "before version 1.10? Please see the handler.pl sample " . 648 "that comes with the latest version of Mason."; 649 } 650 651 # If we're running as superuser, change file ownership to http user & group 652 if (!($> || $<) && $self->interp->files_written) 653 { 654 chown $self->get_uid_gid, $self->interp->files_written 655 or system_error( "Can't change ownership of files written by interp object: $!\n" ); 656 } 657 658 $self->_initialize; 659 return $self; 660} 661 662sub get_uid_gid 663{ 664 return (Apache->server->uid, Apache->server->gid) unless APACHE2; 665 666 # Apache2 lacks $s->uid. 667 # Workaround by searching the config tree. 668 require Apache2::Directive; 669 670 my $conftree = Apache2::Directive::conftree(); 671 my $user = $conftree->lookup('User'); 672 my $group = $conftree->lookup('Group'); 673 674 $user =~ s/^["'](.*)["']$/$1/; 675 $group =~ s/^["'](.*)["']$/$1/; 676 677 my $uid = $user ? getpwnam($user) : $>; 678 my $gid = $group ? getgrnam($group) : $); 679 680 return ($uid, $gid); 681} 682 683sub _initialize { 684 my ($self) = @_; 685 686 my $apreq_module = APACHE2 ? 'Apache2::Request' : 'Apache::Request'; 687 if ($self->args_method eq 'mod_perl') { 688 unless (defined $apreq_module->VERSION) { 689 warn "Loading $apreq_module at runtime. You could " . 690 "increase shared memory between Apache processes by ". 691 "preloading it in your httpd.conf or handler.pl file\n"; 692 eval "require $apreq_module"; 693 } 694 } else { 695 unless (defined CGI->VERSION) { 696 warn "Loading CGI at runtime. You could increase shared ". 697 "memory between Apache processes by preloading it in ". 698 "your httpd.conf or handler.pl file\n"; 699 700 require CGI; 701 } 702 } 703 704 # Add an HTML::Mason menu item to the /perl-status page. 705 my $apstat_module = APACHE2 ? 'Apache2::Status' : 'Apache::Status'; 706 if (defined $apstat_module->VERSION) { 707 # A closure, carries a reference to $self 708 my $statsub = sub { 709 my ($r,$q) = @_; # request and CGI objects 710 return [] if !defined($r); 711 712 if ($r->path_info and $r->path_info =~ /expire_code_cache=(.*)/) { 713 $self->interp->delete_from_code_cache($1); 714 } 715 716 return ["<center><h2>" . $self->apache_status_title . "</h2></center>" , 717 $self->status_as_html(apache_req => $r), 718 $self->interp->status_as_html(ah => $self, apache_req => $r)]; 719 }; 720 local $^W = 0; # to avoid subroutine redefined warnings 721 $apstat_module->menu_item($status_name, $self->apache_status_title, $statsub); 722 } 723 724 my $interp = $self->interp; 725 726 # 727 # Allow global $r in components 728 # 729 # This is somewhat redundant with code in new, but seems to be 730 # needed since the user may simply create their own interp. 731 # 732 $interp->compiler->add_allowed_globals('$r') 733 if $interp->compiler->can('add_allowed_globals'); 734} 735 736# Generate HTML that describes ApacheHandler's current status. 737# This is used in things like Apache::Status reports. 738 739sub status_as_html { 740 my ($self, %p) = @_; 741 742 # Should I be scared about this? =) 743 744 my $comp_source = <<'EOF'; 745<h3>ApacheHandler properties:</h3> 746<blockquote> 747 <tt> 748<table width="75%"> 749<%perl> 750foreach my $property (sort keys %$ah) { 751 my $val = $ah->{$property}; 752 my $default = ( defined $val && defined $valid{$property}{default} && $val eq $valid{$property}{default} ) || ( ! defined $val && exists $valid{$property}{default} && ! defined $valid{$property}{default} ); 753 754 my $display = $val; 755 if (ref $val) { 756 $display = '<font color="darkred">'; 757 # only object can ->can, others die 758 my $is_object = eval { $val->can('anything'); 1 }; 759 if ($is_object) { 760 $display .= ref $val . ' object'; 761 } else { 762 if (UNIVERSAL::isa($val, 'ARRAY')) { 763 $display .= 'ARRAY reference - [ '; 764 $display .= join ', ', @$val; 765 $display .= '] '; 766 } elsif (UNIVERSAL::isa($val, 'HASH')) { 767 $display .= 'HASH reference - { '; 768 my @pairs; 769 while (my ($k, $v) = each %$val) { 770 push @pairs, "$k => $v"; 771 } 772 $display .= join ', ', @pairs; 773 $display .= ' }'; 774 } else { 775 $display = ref $val . ' reference'; 776 } 777 } 778 $display .= '</font>'; 779 } 780 781 defined $display && $display =~ s,([\x00-\x1F]),'<font color="purple">control-' . chr( ord('A') + ord($1) - 1 ) . '</font>',eg; # does this work for non-ASCII? 782</%perl> 783 <tr valign="top" cellspacing="10"> 784 <td> 785 <% $property | h %> 786 </td> 787 <td> 788 <% defined $display ? $display : '<i>undef</i>' %> 789 <% $default ? '<font color=green>(default)</font>' : '' %> 790 </td> 791 </tr> 792% } 793</table> 794 </tt> 795</blockquote> 796 797<%args> 798 $ah # The ApacheHandler we'll elucidate 799 %valid # Contains default values for member data 800</%args> 801EOF 802 803 my $interp = $self->interp; 804 my $comp = $interp->make_component(comp_source => $comp_source); 805 my $out; 806 807 $self->interp->make_request 808 ( comp => $comp, 809 args => [ah => $self, valid => $interp->allowed_params], 810 ah => $self, 811 apache_req => $p{apache_req}, 812 out_method => \$out, 813 )->exec; 814 815 return $out; 816} 817 818sub handle_request 819{ 820 my ($self, $r) = @_; 821 822 my $req = $self->prepare_request($r); 823 return $req unless ref($req); 824 825 return $req->exec; 826} 827 828sub prepare_request 829{ 830 my $self = shift; 831 832 my $r = $self->_apache_request_object(@_); 833 834 my $interp = $self->interp; 835 836 my $fs_type = $self->_request_fs_type($r); 837 838 return DECLINED if $fs_type eq 'dir' && $self->decline_dirs; 839 840 # 841 # Compute the component path via the resolver. Return NOT_FOUND on failure. 842 # 843 my $comp_path = $interp->resolver->apache_request_to_comp_path($r, $interp->comp_root_array); 844 unless ($comp_path) { 845 # 846 # Append path_info if filename does not represent an existing file 847 # (mainly for dhandlers). 848 # 849 my $pathname = $r->filename; 850 $pathname .= $r->path_info unless $fs_type eq 'file'; 851 852 warn "[Mason] Cannot resolve file to component: " . 853 "$pathname (is file outside component root?)"; 854 return $self->return_not_found($r); 855 } 856 857 my ($args, undef, $cgi_object) = $self->request_args($r); 858 859 # 860 # Set up interpreter global variables. 861 # 862 $interp->set_global( r => $r ); 863 864 # If someone is using a custom request class that doesn't accept 865 # 'ah' and 'apache_req' that's their problem. 866 # 867 my $m = eval { 868 $interp->make_request( comp => $comp_path, 869 args => [%$args], 870 ah => $self, 871 apache_req => $r, 872 ); 873 }; 874 875 if (my $err = $@) { 876 # We rethrow everything but TopLevelNotFound, Abort, and Decline errors. 877 878 if ( isa_mason_exception($@, 'TopLevelNotFound') ) { 879 $r->log_error("[Mason] File does not exist: ", $r->filename . ($r->path_info || "")); 880 return $self->return_not_found($r); 881 } 882 my $retval = ( isa_mason_exception($err, 'Abort') ? $err->aborted_value : 883 isa_mason_exception($err, 'Decline') ? $err->declined_value : 884 rethrow_exception $err ); 885 $retval = OK if defined $retval && $retval eq HTTP_OK; 886 unless ($retval) { 887 unless (APACHE2) { 888 unless ($r->notes('mason-sent-headers')) { 889 $r->send_http_header(); 890 } 891 } 892 } 893 return $retval; 894 } 895 896 $self->_set_mason_req_out_method($m, $r) unless $self->{has_custom_out_method}; 897 898 $m->cgi_object($cgi_object) if $m->can('cgi_object') && $cgi_object; 899 900 return $m; 901} 902 903my $do_filter = sub { $_[0]->filter_register }; 904my $no_filter = sub { $_[0] }; 905sub _apache_request_object 906{ 907 my $self = shift; 908 909 # We need to be careful to never assign a new apache (subclass) 910 # object to $r or we will leak memory, at least with mp1. 911 my $new_r = APACHE2 ? $_[0] : HTML::Mason::Apache::Request->new( $_[0] ); 912 913 my $r_sub; 914 my $filter = $_[0]->dir_config('Filter'); 915 if ( defined $filter && lc $filter eq 'on' ) 916 { 917 die "To use Apache::Filter with Mason you must have at least version 1.021 of Apache::Filter\n" 918 unless Apache::Filter->VERSION >= 1.021; 919 920 $r_sub = $do_filter; 921 } 922 else 923 { 924 $r_sub = $no_filter; 925 } 926 927 my $apreq_instance = 928 APACHE2 929 ? sub { Apache2::Request->new( $_[0] ) } 930 : sub { $_[0] }; 931 932 return 933 $r_sub->( $self->args_method eq 'mod_perl' ? 934 $apreq_instance->( $new_r ) : 935 $new_r 936 ); 937} 938 939sub _request_fs_type 940{ 941 my ($self, $r) = @_; 942 943 # 944 # If filename is a directory, then either decline or simply reset 945 # the content type, depending on the value of decline_dirs. 946 # 947 # ** We should be able to use $r->finfo here, but finfo is broken 948 # in some versions of mod_perl (e.g. see Shane Adams message on 949 # mod_perl list on 9/10/00) 950 # 951 my $is_dir = -d $r->filename; 952 953 return $is_dir ? 'dir' : -f _ ? 'file' : 'other'; 954} 955 956sub request_args 957{ 958 my ($self, $r) = @_; 959 960 # 961 # Get arguments from Apache::Request or CGI. 962 # 963 my ($args, $cgi_object); 964 if ($self->args_method eq 'mod_perl') { 965 $args = $self->_mod_perl_args($r); 966 } else { 967 $cgi_object = CGI->new; 968 $args = $self->_cgi_args($r, $cgi_object); 969 } 970 971 # we return $r solely for backwards compatibility 972 return ($args, $r, $cgi_object); 973} 974 975# 976# Get $args hashref via CGI package 977# 978sub _cgi_args 979{ 980 my ($self, $r, $q) = @_; 981 982 # For optimization, don't bother creating a CGI object if request 983 # is a GET with no query string 984 return {} if $r->method eq 'GET' && !scalar($r->args); 985 986 return HTML::Mason::Utils::cgi_request_args($q, $r->method); 987} 988 989# 990# Get $args hashref via Apache::Request package. 991# 992sub _mod_perl_args 993{ 994 my ($self, $apr) = @_; 995 996 my %args; 997 foreach my $key ( $apr->param ) { 998 my @values = $apr->param($key); 999 $args{$key} = @values == 1 ? $values[0] : \@values; 1000 } 1001 1002 return \%args; 1003} 1004 1005sub _set_mason_req_out_method 1006{ 1007 my ($self, $m, $r) = @_; 1008 1009 my $final_output_method = ($r->method eq 'HEAD' ? 1010 sub {} : 1011 $r->can('print')); 1012 1013 # Craft the request's out method to handle http headers, content 1014 # length, and HEAD requests. 1015 my $out_method; 1016 if (APACHE2) { 1017 1018 # mod_perl-2 does not need to call $r->send_http_headers 1019 $out_method = sub { 1020 eval { 1021 $r->$final_output_method( grep { defined } @_ ); 1022 $r->rflush; 1023 }; 1024 my $err = $@; 1025 die $err if $err and $err !~ /Software caused connection abort/; 1026 }; 1027 1028 } else { 1029 1030 my $sent_headers = 0; 1031 $out_method = sub { 1032 1033 # Send headers if they have not been sent by us or by user. 1034 # We use instance here because if we store $m we get a 1035 # circular reference and a big memory leak. 1036 if (!$sent_headers and HTML::Mason::Request->instance->auto_send_headers) { 1037 unless ($r->notes('mason-sent-headers')) { 1038 $r->send_http_header(); 1039 } 1040 $sent_headers = 1; 1041 } 1042 1043 # Call $r->print (using the real Apache method, not our 1044 # overridden method). 1045 $r->$final_output_method( grep {defined} @_ ); 1046 $r->rflush; 1047 }; 1048 1049 } 1050 1051 $m->out_method($out_method); 1052} 1053 1054# Utility function to prepare $r before returning NOT_FOUND. 1055sub return_not_found 1056{ 1057 my ($self, $r) = @_; 1058 1059 if ($r->method eq 'POST') { 1060 $r->method('GET'); 1061 $r->headers_in->unset('Content-length'); 1062 } 1063 return NOT_FOUND; 1064} 1065 1066# 1067# PerlHandler HTML::Mason::ApacheHandler 1068# 1069BEGIN 1070{ 1071 # A method handler is prototyped differently in mod_perl 1.x than in 2.x 1072 my $handler_code = sprintf <<'EOF', APACHE2 ? ': method' : '($$)'; 1073sub handler %s 1074{ 1075 my ($package, $r) = @_; 1076 1077 my $ah; 1078 $ah ||= $package->make_ah($r); 1079 1080 return $ah->handle_request($r); 1081} 1082EOF 1083 eval $handler_code; 1084 rethrow_exception $@; 1085} 1086 10871; 1088 1089__END__ 1090 1091=head1 NAME 1092 1093HTML::Mason::ApacheHandler - Mason/mod_perl interface 1094 1095=head1 SYNOPSIS 1096 1097 use HTML::Mason::ApacheHandler; 1098 1099 my $ah = HTML::Mason::ApacheHandler->new (..name/value params..); 1100 ... 1101 sub handler { 1102 my $r = shift; 1103 $ah->handle_request($r); 1104 } 1105 1106=head1 DESCRIPTION 1107 1108The ApacheHandler object links Mason to mod_perl (version 1 or 2), 1109running components in response to HTTP requests. It is controlled 1110primarily through parameters to the new() constructor. 1111 1112=head1 PARAMETERS TO THE new() CONSTRUCTOR 1113 1114=over 1115 1116=item apache_status_title 1117 1118Title that you want this ApacheHandler to appear as under 1119Apache::Status. Default is "HTML::Mason status". This is useful if 1120you create more than one ApacheHandler object and want them all 1121visible via Apache::Status. 1122 1123=item args_method 1124 1125Method to use for unpacking GET and POST arguments. The valid options 1126are 'CGI' and 'mod_perl'; these indicate that a C<CGI.pm> or 1127C<Apache::Request> object (respectively) will be created for the 1128purposes of argument handling. 1129 1130'mod_perl' is the default under mod_perl-1 and requires that you have 1131installed the C<Apache::Request> package. Under mod_perl-2, the default 1132is 'CGI' because C<Apache2::Request> is still in development. 1133 1134If args_method is 'mod_perl', the C<$r> global is upgraded to an 1135Apache::Request object. This object inherits all Apache methods and 1136adds a few of its own, dealing with parameters and file uploads. See 1137C<Apache::Request> for more information. 1138 1139If the args_method is 'CGI', the Mason request object (C<$m>) will have a 1140method called C<cgi_object> available. This method returns the CGI 1141object used for argument processing. 1142 1143While Mason will load C<Apache::Request> or C<CGI> as needed at runtime, it 1144is recommended that you preload the relevant module either in your 1145F<httpd.conf> or F<handler.pl> file, as this will save some memory. 1146 1147=item decline_dirs 1148 1149True or false, default is true. Indicates whether Mason should decline 1150directory requests, leaving Apache to serve up a directory index or a 1151C<FORBIDDEN> error as appropriate. See the L<allowing directory requests|HTML::Mason::Admin/allowing directory requests> section of the administrator's manual 1152for more information about handling directories with Mason. 1153 1154=item interp 1155 1156The interpreter object to associate with this compiler. By default a 1157new object of the specified L<interp_class|HTML::Mason::Params/interp_class> will be created. 1158 1159=item interp_class 1160 1161The class to use when creating a interpreter. Defaults to 1162L<HTML::Mason::Interp|HTML::Mason::Interp>. 1163 1164=back 1165 1166=head1 ACCESSOR METHODS 1167 1168All of the above properties, except interp_class, have standard accessor 1169methods of the same name: no arguments retrieves the value, and one 1170argument sets it, except for args_method, which is not settable. For 1171example: 1172 1173 my $ah = HTML::Mason::ApacheHandler->new; 1174 my $decline_dirs = $ah->decline_dirs; 1175 $ah->decline_dirs(1); 1176 1177=head1 OTHER METHODS 1178 1179The ApacheHandler object has a few other publicly accessible methods 1180that may be of interest to end users. 1181 1182=over 4 1183 1184=item handle_request ($r) 1185 1186This method takes an Apache or Apache::Request object representing a 1187request and translates that request into a form Mason can understand. 1188Its return value is an Apache status code. 1189 1190Passing an Apache::Request object is useful if you want to set 1191Apache::Request parameters, such as POST_MAX or DISABLE_UPLOADS. 1192 1193=item prepare_request ($r) 1194 1195This method takes an Apache object representing a request and returns 1196a new Mason request object or an Apache status code. If it is a 1197request object you can manipulate that object as you like, and then 1198call the request object's C<exec> method to have it generate output. 1199 1200If this method returns an Apache status code, that means that it could 1201not create a Mason request object. 1202 1203This method is useful if you would like to have a chance to decline a 1204request based on properties of the Mason request object or a component 1205object. For example: 1206 1207 my $req = $ah->prepare_request($r); 1208 # $req must be an Apache status code if it's not an object 1209 return $req unless ref($req); 1210 1211 return DECLINED 1212 unless $req->request_comp->source_file =~ /\.html$/; 1213 1214 $req->exec; 1215 1216=item request_args ($r) 1217 1218Given an Apache request object, this method returns a three item list. 1219The first item is a hash reference containing the arguments passed by 1220the client's request. 1221 1222The second is an Apache request object. This is returned for 1223backwards compatibility from when this method was responsible for 1224turning a plain Apache object into an Apache::Request object. 1225 1226The third item may be a CGI.pm object or C<undef>, depending on the 1227value of the L<args_method|HTML::Mason::Params/args_method> parameter. 1228 1229=back 1230 1231=cut 1232