1package Gantry::Engine::MP13;
2require Exporter;
3
4use strict;
5use Carp qw( croak );
6
7use Apache::Constants qw( DECLINED OK REDIRECT HTTP_MOVED_PERMANENTLY
8                          AUTH_REQUIRED SERVER_ERROR FORBIDDEN BAD_REQUEST );
9use Apache::Request;
10use File::Basename;
11use Gantry::Conf;
12use Gantry::Utils::DBConnHelper::MP13;
13use CGI::Simple::Util ();
14
15use vars qw( @ISA @EXPORT );
16
17############################################################
18# Variables                                                #
19############################################################
20@ISA        = qw( Exporter );
21@EXPORT     = qw(
22    apache_param_hash
23    apache_uf_param_hash
24    apache_request
25    base_server
26    cast_custom_error
27    consume_post_body
28    declined_response
29    dispatch_location
30    engine
31    engine_init
32    err_header_out
33    fish_config
34    fish_location
35    fish_method
36    fish_path_info
37    fish_uri
38    fish_user
39    get_arg_hash
40    get_auth_dbh
41    get_cached_config
42    get_config
43    get_dbh
44    get_post_body
45    header_in
46    header_out
47    hostname
48    is_connection_secure
49    is_status_declined
50    log_error
51    port
52    print_output
53    redirect_response
54    remote_ip
55    send_http_header
56    send_error_output
57    server_root
58    set_cached_config
59    set_content_type
60    set_no_cache
61    set_req_params
62    status_const
63    success_code
64    file_upload
65    url_encode
66    url_decode
67);
68
69############################################################
70# Functions                                                #
71############################################################
72
73#-------------------------------------------------
74# $self->file_upload( param_name )
75#-------------------------------------------------
76sub file_upload {
77    my( $self, $param ) = @_;
78
79    die "param required" if ! $param;
80
81    my $apr = $self->ap_req;
82    my $status = $apr->parse;
83
84    if ( $status ) { die "upload error: $status" };
85
86    my $upload = $apr->upload( $param );
87
88    my $filename = $upload->filename;
89    $filename =~ s/\\/\//g;
90
91    my( $name, $path, $suffix ) = fileparse(
92		$filename,
93		qr/\.(tar\.gz$|[^.]*)/
94	);
95
96    return( {
97        unique_key => time . rand( 6 ),
98        name       => $name,
99        suffix     => $suffix,
100        fullname   => ( $name . $suffix ),
101        size       => ( $upload->size || 0 ),
102        mime       => $upload->type,
103        filehandle => $upload->fh,
104    } );
105
106}
107
108#-------------------------------------------------
109# $self->log_error( error )
110#-------------------------------------------------
111sub log_error {
112    my( $self, $msg ) = @_;
113
114    $self->r->log_error( $msg );
115
116}
117
118#-------------------------------------------------
119# $self->cast_custom_error( error )
120#-------------------------------------------------
121sub cast_custom_error {
122    my( $self, $error_page, $die_msg ) = @_;
123
124    $error_page ||= '';
125    $die_msg    ||= '';
126
127    my $status = ( $self->status() ? $self->status()
128        : $self->status_const( 'BAD_REQUEST' ) );
129
130    $self->r->log_error( $die_msg ) if defined $die_msg;
131
132    $self->r->custom_response( $status, $error_page );
133
134    return( $status );
135}
136
137#-------------------------------------------------
138# $self->apache_param_hash( $req )
139#-------------------------------------------------
140sub apache_param_hash {
141    my( $self, $req ) = @_;
142
143    # If the application has specified that they want the unfiltered params
144    # by default, then make it happen.
145    if ($self->fish_config( 'unfiltered_params' ) && $self->fish_config( 'unfiltered_params' ) =~ /(1|on)/i) {
146        return $self->apache_uf_param_hash( $req );
147    }
148
149    my $hash = {};
150    my @param_names = $req->param;
151
152    foreach my $p ( @param_names ) {
153        my @values = $req->param( $p );
154
155        # Replace angle brackets and quotes with named-entity equivalents.
156        $_ =~ s/</&lt;/g foreach @values;
157        $_ =~ s/>/&gt;/g foreach @values;
158        $_ =~ s/"/&#34;/g foreach @values;
159        $_ =~ s/'/&#39;/g foreach @values;
160
161        # Trim leading / trailing whitespace.
162        $_ =~ s/^\s+//o foreach @values;
163        $_ =~ s/\s+$//o foreach @values;
164
165        $hash->{$p} = ( scalar @values == 1 ) ? shift @values : [ @values ];
166    }
167
168    return( $hash );
169
170} # end: apache_param_hash
171
172#-------------------------------------------------
173# $self->apache_uf_param_hash( $req )
174#-------------------------------------------------
175sub apache_uf_param_hash {
176    my( $self, $req ) = @_;
177
178    my $hash = {};
179    my @param_names = $req->param;
180
181    foreach my $p ( @param_names ) {
182        my @values = $req->param( $p );
183
184        $hash->{$p} = ( scalar @values == 1 ) ? shift @values : [ @values ];
185    }
186
187    return( $hash );
188
189} # end: apache_uf_param_hash
190
191#-------------------------------------------------
192# $self->apache_request( )
193#-------------------------------------------------
194sub apache_request {
195    my ( $self, $r ) = @_;
196
197    unless ( $self->{__AP_REQ__} ) {
198        $self->{__AP_REQ__} = Apache::Request->new(
199            $self->r,
200            POST_MAX => ( $self->fish_config( 'post_max' ) || '20000000' )
201        );
202    }
203
204    return $self->{__AP_REQ__};
205} # end: apache_request
206
207#-------------------------------------------------
208# $self->base_server( $r )
209#-------------------------------------------------
210sub base_server {
211    my( $self, $r ) = ( shift, shift );
212
213    return(
214        $r ? $r->server->server_hostname
215        : $self->r->server->server_hostname );
216
217} # end base_server
218
219#-------------------------------------------------
220# $self->hostname( $r )
221#-------------------------------------------------
222sub hostname {
223    my( $self, $r ) = ( shift, shift );
224
225    return(
226        $r ? $r->hostname
227        : $self->r->hostname );
228
229} # end hostname
230
231#-------------------------------------------------
232# $self->consume_post_body( $r )
233#-------------------------------------------------
234sub consume_post_body {
235    my $self = shift;
236    my $r    = shift;
237
238    my $content_length = $r->headers_in->{'Content-length'};
239
240    return unless $content_length;
241
242     $content_length    = 1e6 if $content_length > 1e6; # limit to ~ 1Meg
243
244    my ( $content, $buffer );
245
246    while ( $r->read( $buffer, $content_length ) ) {
247        $content .= $buffer;
248    }
249
250    $self->{__POST_BODY__} = $content;
251}
252
253#-------------------------------------------------
254# $self->declined_response( )
255#-------------------------------------------------
256sub declined_response {
257    my $self     = shift;
258
259    return $self->status_const( 'DECLINED' );
260} # END declined_response
261
262#-------------------------------------------------
263# $self->dispatch_location( )
264#-------------------------------------------------
265sub dispatch_location {
266    my $self     = shift;
267
268    return $self->uri, $self->location;
269} # END dispatch_location
270
271#-------------------------------------------------
272# $self->engine
273#-------------------------------------------------
274sub engine {
275    return __PACKAGE__;
276
277} # end engine
278
279#-------------------------------------------------
280# $self->engine_init( )
281#-------------------------------------------------
282sub engine_init {
283    my $self = shift;
284    my $r    = shift;
285
286    $self->r( $r );
287} # END engine_init
288
289#-------------------------------------------------
290# $self->err_header_out( $key, $value )
291#-------------------------------------------------
292sub err_header_out {
293    my( $self, $k, $v ) = @_;
294
295    $self->r->err_headers_out->add( $k => $v );
296
297} # end err_header_out
298
299#-------------------------------------------------
300# $self->fish_config( $param )
301#-------------------------------------------------
302sub fish_config {
303    my ( $self, $param ) = @_;
304
305    # see if there is Gantry::Conf data
306    my $conf     = $self->get_config();
307
308    return $$conf{$param} if ( defined $conf and defined $$conf{$param} );
309
310    # otherwise, use dir_config for traditional approach
311    return $self->r()->dir_config( $param );
312
313} # END fish_config
314
315#-------------------------------------------------
316# $self->fish_location( )
317#-------------------------------------------------
318sub fish_location {
319    my $self = shift;
320
321    return $self->r()->location;
322} # END fish_location
323
324#-------------------------------------------------
325# $self->fish_method( )
326#-------------------------------------------------
327sub fish_method {
328    my $self = shift;
329
330    return $self->r()->method;
331} # END fish_method
332
333#-------------------------------------------------
334# $self->fish_path_info( )
335#-------------------------------------------------
336sub fish_path_info {
337    my $self = shift;
338
339    return $self->r()->path_info;
340} # END fish_path_info
341
342#-------------------------------------------------
343# $self->fish_uri( )
344#-------------------------------------------------
345sub fish_uri {
346    my $self = shift;
347
348    return $self->r()->uri;
349} # END fish_uri
350
351#-------------------------------------------------
352# $self->fish_user( )
353#-------------------------------------------------
354sub fish_user {
355    my $self = shift;
356
357    return $self->user() || $self->r()->user;
358} # END fish_user
359
360#-------------------------------------------------
361# $self->get_arg_hash
362#-------------------------------------------------
363sub get_arg_hash {
364    my( $self, $r ) = @_;
365
366    my %args;
367    if ( $r ) {
368        %args = $r->args;
369    }
370    else {
371        %args = $self->r->args;
372    }
373
374    return wantarray ? %args : \%args;
375
376} # end get_arg_hash
377
378#-------------------------------------------------
379# $self->get_auth_dbh( )
380#-------------------------------------------------
381sub get_auth_dbh {
382    return Gantry::Utils::DBConnHelper::MP13->get_auth_dbh;
383}
384
385#-------------------------------------------------
386# $self->get_config( )
387#-------------------------------------------------
388sub get_config {
389    my ( $self ) = @_;
390
391    # see if there Gantry::Conf data
392    my $instance  = $self->r()->dir_config( 'GantryConfInstance' );
393
394    return unless defined $instance;
395
396    my $file      = $self->r()->dir_config( 'GantryConfFile'     );
397
398    my $conf;
399    my $cached    = 0;
400    my $location  = '';
401
402    eval {
403        $location = $self->location;
404    };
405
406    $conf = $self->get_cached_config( $instance, $location );
407
408    if ( defined $conf ) {
409        return $conf;
410    }
411
412    my $gantry_cache     = 0;
413    my $gantry_cache_key = '';
414    my $gantry_cache_hit = 0;
415    eval {
416        ++$gantry_cache if $self->cache_inited();
417    };
418
419    # are we using gantry cache ?
420    if ( $gantry_cache ) {
421
422        $self->cache_namespace('gantry');
423
424        # blow the gantry conf cache when server starts
425        if ( $self->engine_cycle() == 1 ) {
426
427            eval {
428                foreach my $key ( @{ $self->cache_keys() } ) {
429                    my @a = split( ':', $key );
430                    if ( $a[0] eq 'gantryconf' ) {
431                        $self->cache_del( $key );
432                    }
433                }
434            };
435        }
436
437        # build cache key
438        $gantry_cache_key = join( ':',
439            "gantryconf",
440            ( $self->namespace() || '' ),
441            $instance,
442            $location
443        );
444
445        $conf = $self->cache_get( $gantry_cache_key );
446
447        ++$gantry_cache_hit if defined $conf;
448    }
449
450    $conf ||= Gantry::Conf->retrieve(
451        {
452            instance    => $instance,
453            config_file => $file,
454            location    => $location
455        }
456    );
457
458    if ( defined $conf ) {
459        $self->set_cached_config( $instance, $location, $conf );
460
461        if ( $gantry_cache && ! $gantry_cache_hit ) {
462            $self->cache_set( $gantry_cache_key, $conf );
463        }
464    }
465
466    return $conf;
467
468} # END get_config
469
470#-------------------------------------------------
471# $self->get_cached_config( $instance, $location )
472#-------------------------------------------------
473sub get_cached_config {
474    my $self     = shift;
475    my $instance = shift;
476    my $location = shift;
477
478    return $self->r()->pnotes( "conf_${instance}_${location}" );
479}
480
481#-------------------------------------------------
482# $self->set_cached_config( $instance, $location, $conf )
483#-------------------------------------------------
484sub set_cached_config {
485    my $self     = shift;
486    my $instance = shift;
487    my $location = shift;
488    my $conf     = shift;
489
490    $self->r()->pnotes( "conf_${instance}_${location}", $conf );
491}
492
493#-------------------------------------------------
494# $self->get_dbh( )
495#-------------------------------------------------
496sub get_dbh {
497    return Gantry::Utils::DBConnHelper::MP13->get_dbh;
498}
499
500#-------------------------------------------------
501# $self->get_post_body( )
502#-------------------------------------------------
503sub get_post_body {
504    my $self = shift;
505
506    return $self->{__POST_BODY__};
507}
508
509#-------------------------------------------------
510# $self->header_in( $key )
511#-------------------------------------------------
512sub header_in {
513    my( $self, $key ) = @_;
514
515    return $self->r->header_in($key);
516
517} # end header_in
518
519#-------------------------------------------------
520# $self->header_out( $header_key, $header_value )
521#-------------------------------------------------
522sub header_out {
523    my( $self, $k, $v ) = @_;
524
525    $self->r->header_out( $k => $v );
526
527} # end header_out
528
529#-------------------------------------------------
530# $self->is_connection_secure()
531#-------------------------------------------------
532sub is_connection_secure {
533    my $self = shift;
534
535    return $self->r->subprocess_env('HTTPS') ? 1 : 0;
536} # END is_connection_secure
537
538#-------------------------------------------------
539# $self->is_status_declined( $status )
540#-------------------------------------------------
541sub is_status_declined {
542    my $self = shift;
543
544    my $status = $self->status || '';
545
546    return 1 if ( $status eq $self->status_const( 'DECLINED' ) );
547} # END is_status_declined
548
549#-------------------------------------------------
550# $self->port( $r ) - NOT TIED TO API YET
551#-------------------------------------------------
552sub port {
553    my( $self, $r ) = ( shift, shift );
554
555    return( '' );
556
557} # end port
558
559#-------------------------------------------------
560# $self->print_output( )
561#-------------------------------------------------
562sub print_output {
563    my $self     = shift;
564    my $response = shift;
565
566    $self->r()->print( $response );
567} # END print_output
568
569#-------------------------------------------------
570# $self->redirect_response( )
571#-------------------------------------------------
572sub redirect_response {
573    my $self     = shift;
574
575    return $self->status_const( 'REDIRECT' );
576} # END redirect_response
577
578#-------------------------------------------------
579# $self->remote_ip( $r )
580#-------------------------------------------------
581sub remote_ip {
582    my( $self, $r ) = @_;
583
584    return(
585        $r ? $r->connection->remote_ip
586        : $self->r->connection->remote_ip );
587
588} # end remote_ip
589
590#-------------------------------------------------
591# $self->send_error_output( $@ )
592#-------------------------------------------------
593sub send_error_output {
594    my $self     = shift;
595
596    $self->do_error( $@ );
597    return( $self->custom_error( $@ ) );
598
599} # END send_error_output
600
601#-------------------------------------------------
602# $self->send_http_header( )
603#-------------------------------------------------
604sub send_http_header {
605    my( $self ) = @_;
606
607    $self->r->send_http_header;
608
609} # end send_http_header
610
611#-------------------------------------------------
612# $self->server_root( $r ) - NOT TIED TO API YET
613#-------------------------------------------------
614sub server_root {
615    my( $self, $r ) = ( shift, shift );
616
617    return('');
618
619} # end server_root
620
621#-------------------------------------------------
622# $self->set_content_type( )
623#-------------------------------------------------
624sub set_content_type {
625    my $self = shift;
626
627    $self->r()->content_type( $self->content_type );
628} # END set_content_type
629
630#-------------------------------------------------
631# $self->set_no_cache( )
632#-------------------------------------------------
633sub set_no_cache {
634    my $self = shift;
635
636    $self->r()->no_cache( 1 ) if ( $self->no_cache );
637} # END set_no_cache
638
639#-------------------------------------------------
640# $self->set_req_params( )
641#-------------------------------------------------
642sub set_req_params {
643    my $self = shift;
644
645    $self->ap_req( $self->apache_request( $self->r ) );
646    $self->params( $self->apache_param_hash( $self->ap_req ) );
647    $self->uf_params( $self->apache_uf_param_hash( $self->ap_req ) );
648
649} # END set_req_params
650
651#-------------------------------------------------
652# $self->status_const( 'OK | DECLINED | REDIRECT' )
653#-------------------------------------------------
654sub status_const {
655    my( $self, $status ) = @_;
656
657    # Upper case our status
658    $status = uc $status;
659
660    return BAD_REQUEST      if $status eq 'BAD_REQUEST';
661    return DECLINED         if $status eq 'DECLINED';
662    return OK               if $status eq 'OK';
663    return REDIRECT         if $status eq 'REDIRECT';
664    return HTTP_MOVED_PERMANENTLY
665                            if $status eq 'MOVED_PERMANENTLY';
666    return FORBIDDEN        if $status eq 'FORBIDDEN';
667    return AUTH_REQUIRED    if $status eq 'AUTH_REQUIRED';
668    return AUTH_REQUIRED    if $status eq 'HTTP_UNAUTHORIZED';
669    return SERVER_ERROR     if $status eq 'SERVER_ERROR';
670
671    die( "Undefined constant $status" );
672
673} # end status_const
674
675#-------------------------------------------------
676# $self->success_code( )
677#-------------------------------------------------
678sub success_code {
679    my $self = shift;
680
681    return $self->status_const( 'OK' );
682} # END success_code
683
684#-------------------------------------------------
685# $self->url_encode( )
686#-------------------------------------------------
687sub url_encode {
688    my $self = shift;
689    my $value = shift;
690
691    return CGI::Simple::Util::escape( $value );
692} # END url_encode
693
694#-------------------------------------------------
695# $self->url_decode( )
696#-------------------------------------------------
697sub url_decode {
698    my $self = shift;
699    my $value = shift;
700
701    return CGI::Simple::Util::unescape( $value );
702} # END url_decode
703
704# EOF
7051;
706
707__END__
708
709=head1 NAME
710
711Gantry::Engine::MP13 - mod_perl 1.0 plugin ( or mixin )
712
713=head1 SYNOPSIS
714
715  use Gantry::Engine::MP13;
716
717
718=head1 DESCRIPTION
719
720This module is the binding between the Gantry framework and the mod_perl API.
721This particluar module contains the mod_perl 1.0 specific bindings.
722
723See mod_perl documentation for a more detailed description for some of these
724bindings.
725
726=head1 METHODS
727
728=over 4
729
730=item $self->apache_param_hash
731
732Return a hash reference to the apache request body parameters.
733
734=item $self->apache_uf_param_hash
735
736Return a hash reference to the apache request body parameters unfiltered.
737
738=item $self->apache_request
739
740Apache::Request is a subclass of the Apache class, which adds methods
741for parsing GET requests and POST requests where Content-type is one of
742application/x-www-form-urlencoded or multipart/form-data. See the
743libapreq(3) manpage for more details.
744
745=item $self->base_server
746
747Returns the physical server this connection came in
748on (main server or vhost):
749
750=item $self->hostname
751
752Returns the virtual server name
753
754=item $self->cast_custom_error
755
756Called by the handler in Gantry.pm when things go wrong.  It receives
757html output and a death message.  It logs the death message and sets
758the html output via the custom_response routine of the request object.
759Returns FORBIDDEN status code.
760
761=item $self->consume_post_body
762
763This must be used by a plugin at the pre_init phase.  It takes all of the
764data from the body of the HTTP POST request, storing it for retrieval
765via C<get_post_body>.  You cannot mix this with regular form handling.
766
767=item $self->declined_response
768
769Returns the proper numeric status code for DECLINED.
770
771=item dispatch_location
772
773The uri tail specific to this request.  Returns:
774
775    $self->uri, $self->location
776
777Note that this is a two element list.
778
779=item $self->engine
780
781Returns the name of the engine, i.e. Gantry::Engine::MP13
782
783=item $self->engine_init
784
785Receives the request object and stores it in the site object.  Returns
786nothing useful.
787
788=item $self->err_header_out
789
790The $r->err_headers_out method will return a %hash of server response
791headers. This can be used to initialize a perl hash, or one could use
792the $r->err_header_out() method (described below) to retrieve or set a
793specific header value directly
794
795See mod_perl docs.
796
797=item fish_config
798
799Pass this method the name of a conf parameter you need.  Returns the
800value for the parameter.
801
802=item fish_location
803
804Returns the location for the current request.
805
806=item fish_method
807
808Returns the HTTP method of the current request.
809
810=item fish_path_info
811
812Returns the path info for the current request.
813
814=item fish_uri
815
816Returns the uri for the current request.
817
818=item fish_user
819
820Returns the currently logged-in user.
821
822=item $self->get_arg_hash
823
824    returns a hash of url arguments.
825
826    /some/where?arg1=don&arg2=johnson
827
828=item $self->get_auth_dbh
829
830Same as get_dbh, but for the authentication database.
831
832=item $self->get_cached_config
833
834Users should call get_config instead.
835
836Pulls the config object out of the pnotes so Gantry::Conf doesn't have
837to regenerate it repeatedly.  (See set_cached_config.)
838
839=item get_config
840
841If you are using Gantry::Conf, this will return the config hash reference
842for the current location.
843
844=item get_cached_conf/set_cached_conf
845
846These cache the Gantry::Conf config hash in pnotes.  Override them if
847you want more persistent caching.  These are instance methods.  get
848receives the invoking object, the name of the GantryConfInstance,
849and the current location (for ease of use, its also in the invocant).
850set receives those plus the conf hash it should cache.
851
852=item $self->get_dbh
853
854Returns the current regular database connection if one is available or
855undef otherwise.
856
857=item $self->get_post_body
858
859If C<consume_post_body> was used by a plugin during the pre_init phase,
860this method returns the consumed body of the HTTP POST request.
861
862=item $self->header_in
863
864The $r->headers_in method will return a %hash of client request headers.
865This can be used to initialize a perl hash, or one could use the
866$r->header_in() method (described below) to retrieve a specific header
867value directly.
868
869See mod_perl docs.
870
871=item $self->header_out( $r, $header_key, $header_value )
872
873Change the value of a response header, or create a new one.
874
875=item $self->is_connection_secure()
876
877Return whether the current request is being served by an SSL-enabled host.
878
879=item $self->is_status_declined
880
881Returns a true value if the status is currently DECLINED or false otherwise.
882
883=item $self->log_error( message )
884
885Writes message to the apache web server log
886
887=item $self->port
888
889Returns port number in which the request came in on.
890
891=item $self->print_output( $response_page )
892
893This method sends the contents of $response page back to apache.  It
894uses the print method on the request object.
895
896=item $self->redirect_response
897
898Returns the proper numeric status code for REDIRECT.
899
900=item $self->remote_ip
901
902Returns the IP address for the remote user
903
904=item $self->send_error_output
905
906Returns the content of custom_error.  It gives $@ to the custom_error method.
907
908=item $self->send_http_header( $r )
909
910Send the response line and all headers to the client. Takes an optional
911parameter indicating the content-type of the response, i.e. 'text/html'.
912
913This method will create headers from the $r->content_xxx() and $r->no_cache()
914attributes (described below) and then append the headers defined by
915$r->header_out (or $r->err_header_out if status indicates an error).
916
917See mod_perl 1.0 docs.
918
919=item $self->server_root
920
921Returns the value set by the top-level ServerRoot directive
922
923=item set_cached_config
924
925For internal use.  Used to place a config hash into pnotes for reuse
926during the current page request.
927
928=item $self->set_content_type()
929
930Sets the content type stored in the site object's content_type attribute
931on the apache request object.
932
933=item $self->set_no_cache
934
935Sets the no_cache flag in the apache request object with the value
936for no_cache in the site object.
937
938=item set_req_params
939
940Sets up the apreq object and the form parameters from it.
941
942=item $self->status_const( 'OK | DECLINED | REDIRECT' )
943
944Get or set the reply status for the client request. The Apache::Constants
945module provide mnemonic names for the status codes.
946
947=item $self->success_code
948
949Returns the proper numeric status code for OK.
950
951=item url_encode
952
953  url_encode($value)
954
955Accepts a value and returns it url encoded.
956
957=item url_decode
958
959  url_decode($value)
960
961Accepts a value and returns it url decoded.
962
963=item $self->file_upload
964
965Uploads a file from the client's disk.
966
967Parameter: The name of the file input element on the html form.
968
969Returns: A hash with these keys:
970
971=over 4
972
973=item unique_key
974
975a unique identifier for this upload
976
977=item name
978
979the base name of the file
980
981=item suffix
982
983the extension (file type) of the file
984
985=item fullname
986
987name.suffix
988
989=item size
990
991bytes in file
992
993=item mime
994
995mime type of file
996
997=item filehandle
998
999a handle you can read the file from
1000
1001=back
1002
1003=back
1004
1005=head1 SEE ALSO
1006
1007mod_perl(3), Gantry(3)
1008
1009=head1 LIMITATIONS
1010
1011
1012=head1 AUTHOR
1013
1014Tim Keefer <tkeefer@gmail.com>
1015
1016=head1 COPYRIGHT and LICENSE
1017
1018Copyright (c) 2005-6, Tim Keefer.
1019
1020This library is free software; you can redistribute it and/or modify
1021it under the same terms as Perl itself, either Perl version 5.8.6 or,
1022at your option, any later version of Perl 5 you may have available.
1023
1024=cut
1025