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