Bivio::Biz::Action::RealmFile
# Copyright (c) 2005-2010 bivio Software, Inc. All Rights Reserved.
# $Id$
package Bivio::Biz::Action::RealmFile;
use strict;
use Bivio::Base 'Biz.Action';
use Fcntl ();
my($_FP) = b_use('Type.FilePath');
my($_DATA_READ) = ${b_use('Auth.PermissionSet')->from_array(['DATA_READ'])};
my($_RF) = b_use('Model.RealmFile');
my($_DC) = b_use('Bivio.DieCode');
my($_AC) = b_use('Ext.ApacheConstants');
my($_R) = b_use('Auth.Realm');
sub access_controlled_execute {
my($proto, $req) = @_;
my($rf) = $proto->access_controlled_load(
$req->get('auth_id'),
$_RF->parse_path($req->get('path_info')),
$req,
);
return $proto->set_output_for_get($rf)
if $rf;
if (($rf = $req->ureq('Model.RealmFile')) && $req->get('path_info')) {
$req->put(path_info => $rf->get('path'))
if $rf->get('is_folder');
}
return 0;
}
sub access_controlled_load {
my($proto, $realm_id, $path, $req, $die_code) = @_;
my($rf) = Bivio::Biz::Model->new($req, 'RealmFile');
foreach my $is_public (1, 0) {
last if $rf->unauth_load({
path => $is_public ? $_FP->to_public($path) : $path,
realm_id => $realm_id,
is_public => $path =~ $_FP->VERSION_REGEX ? 0 : $is_public,
});
}
my($e) = 'MODEL_NOT_FOUND';
if ($rf->is_loaded) {
my($aipo) = $proto->access_is_public_only($req, $rf);
if ($rf->get('is_public') || !$aipo) {
return $rf
unless $rf->get('is_folder');
if ($req->unsafe_get_nested(qw(task want_folder_fall_thru))) {
return undef
unless $aipo;
# allow viewing public folders
if ($rf->get('is_public')) {
$req->put(path_info => $rf->get('path'));
return undef;
}
$e = 'FORBIDDEN';
}
}
else {
$e = 'FORBIDDEN';
}
}
$rf->throw_die($e => {
entity => $path,
realm_id => $realm_id,
}) unless $die_code;
$$die_code = $_DC->from_name($e);
return undef;
}
sub access_is_public_only {
my($proto, $req, $realm_file) = @_;
return (
$realm_file ? $_R->new($realm_file->get('realm_id'), $req)
: $req->get('auth_realm')
)->does_user_have_permissions($_DATA_READ, $req) ? 0 : 1;
}
sub execute_private {
# allow any file, not just public files (unauthenticated)
my($proto, $req) = @_;
return $proto->unauth_execute($req, undef, $req->get('auth_id'));
}
sub execute_public {
my($proto, $req) = @_;
$req->put(path_info => $_FP->to_public($req->get('path_info')));
return $proto->unauth_execute($req, 1, $req->get('auth_id'));
}
sub execute_put {
my($proto, $req) = @_;
$req->assert_http_method('put');
my($rf) = Bivio::Biz::Model->new($req, 'RealmFile');
$rf->create_or_update_with_content(
{path => $rf->parse_path($req->get('path_info'))},
$req->get_content,
);
return;
}
sub execute_show_original {
my($proto, $req) = @_;
my($res) = shift->access_controlled_execute(@_);
$req->get('reply')->set_output_type('text/plain')
if $res;
return $res;
}
sub set_output_for_get {
my(undef, $realm_file) = @_;
return
unless $realm_file;
my($reply) = $realm_file->req->get('reply');
my($range) = $realm_file->req->unsafe_get('r');
$range &&= $range->header_in('Range');
# Firefox and Chrome ask for bytes=<start>-; Safari requests specific ranges
my($start, $end) = ($range || '') =~ /^\s*bytes\s*=\s*(\d+)\s*-(?:\s*(\d+)\s*)?$/is;
if (defined($range)) {
if (!defined($start) || defined($end) && $start > $end) {
b_warn('request contains invalid Range: ', $range);
$reply->set_output(\(''));
$reply->set_http_status($_AC->BAD_REQUEST);
return 1;
}
if (!defined($end)) {
$end = $realm_file->get_content_length - 1;
}
}
$reply->set_output_type($realm_file->get_content_type);
$reply->set_cache_private
unless $realm_file->get('is_public');
if (! $range) {
$reply->set_output($realm_file->get_handle);
return 1;
}
my($to_read) = $end - $start + 1;
my($buf) = '';
my($h) = $realm_file->get_handle;
$h->seek($start, Fcntl::SEEK_SET) || die;
while ($to_read > 0) {
my($r) = $h->read($buf, $to_read, length($buf));
if (!defined($r)) {
$realm_file->die("failed to read: error=$!");
}
if ($r == 0) {
# No more data so file shrunk (highly unlikely, but need to handle)
$end -= $to_read;
last;
}
$to_read -= $r;
}
$reply->set_output(\$buf)
->set_header(
'Content-Range',
sprintf('bytes %d-%d/%d', $start, $end, $realm_file->get_content_length)
)->set_http_status($_AC->HTTP_PARTIAL_CONTENT);
return 1;
}
sub unauth_execute {
my($proto, $req, $is_public, $realm_id, $path_info) = @_;
my($f) = Bivio::Biz::Model->new($req, 'RealmFile');
return $proto->set_output_for_get(
$f->unauth_load_or_die({
realm_id => $realm_id,
is_folder => 0,
path => $f->parse_path($path_info || $req->get('path_info')),
defined($is_public) ? (is_public => $is_public) : (),
})
);
}
1;