mourningdove/cgi-bin/DW/Controller/Media.pm

371 lines
12 KiB
Perl
Raw Normal View History

2026-05-24 01:03:05 +00:00
#!/usr/bin/perl
#
# DW::Controller::Media
#
# Displays media for a user.
#
# Authors:
# Mark Smith <mark@dreamwidth.org>
#
# Copyright (c) 2010-2018 by Dreamwidth Studios, LLC.
#
# This program is free software; you may redistribute it and/or modify it under
# the same terms as Perl itself. For a copy of the license, please reference
# 'perldoc perlartistic' or 'perldoc perlgpl'.
#
package DW::Controller::Media;
use strict;
use v5.10;
use Log::Log4perl;
my $log = Log::Log4perl->get_logger(__PACKAGE__);
use DW::BlobStore;
use DW::Routing;
use DW::Request;
use DW::Controller;
use DW::External::Site;
use POSIX;
my %VALID_SIZES =
( map { $_ => $_ } ( 100, 320, 200, 640, 480, 1024, 768, 1280, 800, 600, 720, 1600, 1200 ) );
DW::Routing->register_regex(
qr!^/file/(\d+)$!, \&media_handler,
user => 1,
formats => 1,
);
DW::Routing->register_regex(
qr!^/file/(\d+x\d+|full)(/\w:[\d\w]+)*/(\d+)$!,
\&media_handler,
user => 1,
formats => 1,
);
DW::Routing->register_string( '/file/list', \&media_manage_handler, app => 1 );
DW::Routing->register_string( '/file/edit', \&media_bulkedit_handler, app => 1 );
DW::Routing->register_string( '/file/new', \&media_new_handler, app => 1 );
DW::Routing->register_string( '/file', \&media_index_handler, app => 1 );
sub media_manage_handler {
my ( $ok, $rv ) = controller();
return $rv unless $ok;
my ( $remote, $r ) = ( $rv->{remote}, $rv->{r} );
return error_ml(
'error.openid',
{
sitename => $LJ::SITENAMESHORT,
aopts => '/create'
}
) if $remote->is_identity;
my $u = $remote;
my $adminmode = $u && $u->has_priv( 'canview', 'images' );
my $user = LJ::canonical_username( $r->get_args->{user} || $r->post_args->{user} );
if ( $adminmode && $user ) {
$u = LJ::load_user($user);
return error_ml('error.username_notfound') unless $u;
return error_ml('error.purged.text') if $u->is_expunged;
$user = undef if $remote->equals($u);
}
else {
$user = undef;
}
# load all of a user's media. this is inefficient and won't be like this forever,
# but it's simple for now...
my @media = DW::Media->get_active_for_user( $u, width => 200, height => 200 );
$rv->{media} = \@media;
$rv->{make_embed_url} = \&make_embed_url;
$rv->{page} = $r->get_args->{page} || '1';
$rv->{view_type} = $r->get_args->{view} || '';
$rv->{maxpage} = POSIX::ceil( scalar @media / 20 );
$rv->{valid_sizes} = [%VALID_SIZES];
$rv->{convert_time} = \&LJ::mysql_time;
$rv->{adminmode} = $adminmode ? 1 : 0;
$rv->{user} = $user;
my $media_usage = DW::Media->get_usage_for_user($u);
my $media_quota = DW::Media->get_quota_for_user($u);
$rv->{usage} = sprintf( "%0.3f MB", $media_usage / 1024 / 1024 );
$rv->{quota} = sprintf( "%0.1f MB", $media_quota / 1024 / 1024 );
$rv->{percentage} = sprintf( "%0.1f%%", $media_usage / $media_quota * 100 );
return DW::Template->render_template( 'media/index.tt', $rv );
}
sub media_bulkedit_handler {
my ( $ok, $rv ) = controller();
return $rv unless $ok;
return error_ml(
'error.openid',
{
sitename => $LJ::SITENAMESHORT,
aopts => '/create'
}
) if $rv->{remote}->is_identity;
my @security = (
{ value => "public", text => LJ::Lang::ml('label.security.public2') },
{ value => "usemask", text => LJ::Lang::ml('label.security.accesslist') },
{ value => "private", text => LJ::Lang::ml('label.security.private2') },
);
$rv->{security} = \@security;
my $r = DW::Request->get;
if ( $r->did_post ) {
my $post_args = $r->post_args;
return error_ml('error.invalidauth')
unless LJ::check_form_auth( $post_args->{lj_form_auth} );
if ( $post_args->{"action:edit"} ) {
my %post = %{ $post_args->as_hashref || {} };
# transform our HTML field names to property names
# and group by what media object they belong to
# we don't care if the id or prop might not exist
# right now, later steps will verify them
my %props;
while ( my ( $key, $val ) = each %post ) {
next if $key eq "delete";
next unless $key =~ m/^(\w+)-(\d+)/;
my $mediaid = $2 >> 8;
if ( exists $props{$mediaid} ) {
$props{$mediaid}{$1} = $val;
}
else {
$props{$mediaid} = { $1 => $val };
}
}
# go through and try to fetch a media object from
# each id, then try to set it's properties
for my $media_key ( keys %props ) {
my $media = DW::Media->new( user => $rv->{u}, mediaid => $media_key );
next unless $media;
while ( my ( $key, $val ) = each %{ $props{$media_key} } ) {
if ( $key eq 'security' ) {
my $amask = $val eq "usemask" ? 1 : 0;
$media->set_security( security => $val, allowmask => $amask );
}
else {
$media->prop( $key, $val );
}
}
}
}
elsif ( $post_args->{"action:delete"} ) {
# FIXME: update with more efficient mass loader
my @to_delete = $post_args->get_all("delete");
foreach my $id (@to_delete) {
# FIXME: error messages
my $mediaid = $id >> 8;
my $media = DW::Media->new( user => $rv->{u}, mediaid => $mediaid );
next unless $media;
$media->delete;
}
}
}
my @media = DW::Media->get_active_for_user( $rv->{remote}, width => 200, height => 200 );
my $media_usage = DW::Media->get_usage_for_user( $rv->{u} );
my $media_quota = DW::Media->get_quota_for_user( $rv->{u} );
$rv->{usage} = sprintf( "%0.3f MB", $media_usage / 1024 / 1024 );
$rv->{quota} = sprintf( "%0.1f MB", $media_quota / 1024 / 1024 );
$rv->{percentage} = sprintf( "%0.1f%%", $media_usage / $media_quota * 100 );
$rv->{ehtml} = \&LJ::ehtml;
$rv->{media} = \@media;
$rv->{page} = $r->get_args->{page} || '1';
$rv->{maxpage} = POSIX::ceil( scalar @media / 20 );
return DW::Template->render_template( 'media/edit.tt', $rv );
}
sub media_handler {
my ($opts) = @_;
my ( $ok, $rv ) = controller( anonymous => 1 );
return $rv unless $ok;
my $r = $rv->{r};
# Outputs an error message
my $error_out = sub {
my ( $code, $message ) = @_;
$r->status($code);
return $r->NOT_FOUND if $code == 404;
# don't cache transient error responses
$r->header_out( "Cache-Control" => "no-cache" );
$r->print($message);
return $r->OK;
};
# Old format or new format detection
my ( $size, $extra, $id ) = @{ $opts->subpatterns };
my ( $width, $height );
if ( $size =~ /^(\d+)x(\d+)$/ ) {
( $width, $height ) = ( $1, $2 );
}
elsif ( $size eq 'full' ) {
# Do nothing, leave width/height undef
}
elsif ( $size =~ /^\d+$/ ) {
# Should be old style format, so let's assume
( $id, $size, $extra ) = ( $size + 0, undef, undef );
}
else {
return $error_out->( 404, 'Not found' );
}
# Ensure if a width or height are given, BOTH are given
return $error_out->( 404, 'Not found' )
if defined $width xor defined $height;
# Constrain widths and heights to certain valid sets
if ( defined $width ) {
return $error_out->( 404, 'Not found' )
unless exists $VALID_SIZES{$width}
&& exists $VALID_SIZES{$height};
}
# Finalize id and extension checking
my $ext = $opts->{format};
return $error_out->( 404, 'Not found' )
unless $id && $ext;
my $anum = $id % 256;
$id = ( $id - $anum ) / 256;
# Load the account or error
return $error_out->( 404, 'Need account name as user parameter' )
unless $opts->username;
my $u = LJ::load_user_or_identity( $opts->username )
or return $error_out->( 404, 'Invalid account' );
# try to get the media object
my $obj = DW::Media->new(
user => $u,
mediaid => $id,
width => $width,
height => $height
) or return $error_out->( 404, 'Not found' );
return $error_out->( 404, 'Not found' )
unless $obj->is_active && $obj->anum == $anum && $obj->ext eq $ext;
# access control
my $remote = $rv->{remote};
my $viewall = $r->get_args->{viewall} ? 1 : 0; # did they request it
$viewall &&= defined $remote; # are they logged in
$viewall &&= $remote->has_priv( 'canview', '*' ); # can they do it
LJ::statushistory_add( $u->userid, $remote->userid, "viewall", $obj->url )
if $viewall;
return $error_out->( 403, 'Not authorized' )
unless $viewall || $obj->visible_to($remote);
# remote access, including crossposts
my $refer_ok = sub {
return 1 if LJ::check_referer();
my @xpost = map { $_->{domain} } DW::External::Site->get_xpost_sites;
my ($ref_dom) = $r->header_in("Referer") =~ m!^https?://([^/]+)!;
foreach my $domain (@xpost) {
return 1 if $ref_dom eq $domain; # top level domain
return 1 if $ref_dom =~ m!\.\Q$domain\E$!; # subdomain
}
return 0;
};
return $error_out->( 403, 'Not authorized' )
unless $refer_ok->(); # limit offsite loading
# load the data for this object
my $dataref = DW::BlobStore->retrieve( media => $obj->mogkey );
return $error_out->( 500, 'Unexpected internal error locating file' )
unless defined $dataref && ref $dataref eq 'SCALAR';
# now we're done!
$r->set_last_modified( $obj->{logtime} ) if $obj->{logtime};
$r->content_type( $obj->mimetype );
$r->print($$dataref);
return $r->OK;
}
sub media_new_handler {
my ( $ok, $rv ) = controller();
return $rv unless $ok;
return error_ml(
'error.openid',
{
sitename => $LJ::SITENAMESHORT,
aopts => '/create'
}
) if $rv->{remote}->is_identity;
$rv->{security} = [
{ value => "public", text => LJ::Lang::ml('label.security.public2') },
{ value => "usemask", text => LJ::Lang::ml('label.security.accesslist') },
{ value => "private", text => LJ::Lang::ml('label.security.private2') },
];
$rv->{default_security} = $rv->{remote}->newpost_minsecurity;
$rv->{default_security} = 'usemask' if $rv->{default_security} eq 'friends';
return DW::Template->render_template( 'media/new.tt', $rv );
}
sub media_index_handler {
my ( $ok, $rv ) = controller();
return $rv unless $ok;
my $media_usage = DW::Media->get_usage_for_user( $rv->{u} );
my $media_quota = DW::Media->get_quota_for_user( $rv->{u} );
$rv->{usage} = sprintf( "%0.3f MB", $media_usage / 1024 / 1024 );
$rv->{quota} = sprintf( "%0.1f MB", $media_quota / 1024 / 1024 );
$rv->{percentage} = sprintf( "%0.1f%%", $media_usage / $media_quota * 100 );
return DW::Template->render_template( 'media/home.tt', $rv );
}
# a helper function to build the embed code, being sure to
# clean user-entered fields before outputing them.
sub make_embed_url {
my ( $obj, %opts ) = @_;
my $url = $obj->full_url;
my $alt = $obj->prop('alttext') || '';
my $title = $obj->prop('title') || '';
my $embed;
if ( defined $opts{type} && $opts{type} eq 'thumbnail' ) {
my $thumb_url = $obj->url();
$embed =
"<a href='$url'><img src='$thumb_url' alt='"
. LJ::ehtml($alt)
. "' title='"
. LJ::ehtml($title)
. "'/></a>";
}
else {
$embed =
"<img src='$url' alt='" . LJ::ehtml($alt) . "' title='" . LJ::ehtml($title) . "' />";
}
return $embed;
}
1;