%# vim:ft=mason %# $Id: _sendfile,v 1.13 2006/05/30 10:05:15 altblue Exp $ <%doc> SOURCE (one of...): $handle: file handle $path: full path (e.g.: /full/path/to/filename.txt) $data: file content SEND ATTRIBUTES: $type: content type (e.g.: application/octet-stream) $inline: send the content inline or as an attachment ("save as" etc) ? $name: desired filename (useful only if $inline = 0) $last_modified: in seconds since epoch $cache: seconds to cache the answer % ############################################################################ <%args> $path => undef $handle => undef $data => undef $inline => 1 $type => '' $name => '' $last_modified => time() $cache => 0 % ############################################################################ <%once> use File::MMagic (); use Apache::Util (); % ############################################################################ <%flags> inherit => undef % ############################################################################ <%init> # used for notifying ugly %filter blocks about us :) $r->pnotes( no_content_filter => 1 ); # path -> handle if ($path) { return $m->comp('/_not_found') unless -f $path; $handle = FileHandle->new($path); if ( !$handle ) { $r->log->error( '[' . $r->hostname . "] Cannot _sendfile '$path': " . $! ); return $m->comp( '/_not_found', 403 ); } # no name? be nice if ( !$name ) { ( $name = $path ) =~ s{^.+/}{}; } } # bail out if ( !$handle && !defined $data ) { $r->log->error( '[' . $r->hostname . '] Cannot _sendfile: WHAT to send?!' ); return $m->comp( '/_not_found', 500 ); } # override file type when appropriate if ($name) { my %map = ( css => 'text/css; charset=UTF-8', js => 'text/javascript; charset=UTF-8', txt => 'text/plain; charset=UTF-8', xml => 'text/xml; charset=UTF-8', ); foreach my $ext ( keys %map ) { next if $name !~ /\.$ext$/xms; $type = $map{$ext}; last; } } # get more info... my $content_length = 0; my $mm = File::MMagic->new; if ( defined $data ) { $type ||= eval { $mm->checktype_contents($data) }; $content_length = bytes::length $data; } else { ( $content_length, $last_modified ) = ( $handle->stat )[ 7, 9 ]; $type ||= eval { $mm->checktype_filehandle($handle) }; $handle->seek( 0, 0 ); } $type ||= 'application/octet-stream'; # prepare HTTP answer $m->auto_send_headers(0); $m->clear_buffer; $r->set_content_length($content_length); $r->header_out( 'Cache-Control', 'max-age=' . $cache ); $r->header_out( 'Expires', Apache::Util::ht_time( time + $cache ) ); $r->set_last_modified($last_modified); if ($inline) { $r->content_type($type); } else { $r->content_type("$type; name=$name"); $r->header_out( 'Content-Disposition' => 'attachment; filename=' . $name ); $r->header_out( 'Content-Transfer-Encoding' => 'binary' ); } # TODO: should make this work at some point #if((my $rc = $r->meets_conditions) != 200) { # return $rc; #} # send answer $r->send_http_header; if (defined $data) { $m->autoflush(1); $m->print($data); $m->autoflush(0); } else { $r->send_fd($handle); $handle->close; } $m->abort; return 1;