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

190 lines
6 KiB
Perl
Raw Normal View History

2026-05-24 01:03:05 +00:00
#!/usr/bin/perl
#
# DW::Controller::API::Media
#
# API controls for the media system.
#
# Authors:
# Mark Smith <mark@dreamwidth.org>
#
# Copyright (c) 2013-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::API::Media;
use strict;
use warnings;
use DW::Routing;
use DW::Request;
use DW::Controller;
use DW::Controller::API;
use DW::Media;
use LJ::JSON;
DW::Routing->register_api_endpoints(
[ '/file/edit', \&file_edit_handler, 1 ],
[ '/file/new', \&file_new_handler, 1 ],
);
#{
# files:
# [
# {
# url: "http://url.to/file/or/page",
# thumbnail_url: "http://url.to/thumnail.jpg",
# name: "thumb2.jpg",
# type: "image/jpeg",
# size: 46353,
#OPT delete_url: "http://url.to/delete/file/",
#OPT delete_type: "DELETE"
# }
# ]
#}
# Allows uploading a file. Allocates and returns a unique media ID for the upload.
sub file_new_handler {
# we want to handle the not logged in case ourselves
my ( $ok, $rv ) = controller( anonymous => 1 );
return $rv unless $ok;
my $r = $rv->{r};
$r->did_post
or return api_error( $r->HTTP_METHOD_NOT_ALLOWED, 'Needs a POST request' );
LJ::isu( $rv->{u} )
or return api_error( $r->HTTP_UNAUTHORIZED, 'Not logged in' );
return api_error( $r->HTTP_UNAUTHORIZED, 'Invalid account type' )
if $rv->{u}->is_identity;
return api_error( $r->HTTP_BAD_REQUEST, 'Quota exceeded' )
unless DW::Media->can_upload_media( $rv->{u} );
my $uploads = $r->uploads;
return api_error( $r->HTTP_BAD_REQUEST, 'No uploads found' )
unless ref $uploads eq 'ARRAY' && scalar @$uploads;
foreach my $upload (@$uploads) {
my ( $type, $ext ) = DW::Media->get_upload_type( $upload->{'content-type'} );
next unless defined $type && $type == DW::Media::TYPE_PHOTO;
# Try to upload this item since we know it's a photo.
my $obj = DW::Media->upload_media(
user => $rv->{u},
data => $upload->{body},
security => $rv->{u}->newpost_minsecurity,
);
return api_error( $r->SERVER_ERROR, 'Failed to upload media' )
unless $obj;
# For now, we only support a single upload per call, so finish now.
return api_ok(
{
id => $obj->displayid,
url => $obj->url,
thumbnail_url => $obj->url( extra => '100x100/' ),
name => "image",
type => $obj->mimetype,
size => $obj->size,
}
);
}
return api_error( $r->HTTP_BAD_REQUEST, 'No uploads found' );
}
# Allows editing the metadata and security on a media object. The input to this
# function is a dict, keys are the ids to modify, and the value is another dict
# that contains what to modify. Example:
#
# {
# 1234: {
# security => "public", # public, private, access, usemask
# allowmask => 3553, # only valid in usemask security
# title => "some title", # else, the name of the property
# otherprop => 5,
# ...
# }
# 5653: ...
# }
#
sub file_edit_handler {
# we want to handle the not logged in case ourselves
my ( $ok, $rv ) = controller( anonymous => 1 );
return $rv unless $ok;
my $r = $rv->{r};
$r->did_post
or return api_error( $r->HTTP_METHOD_NOT_ALLOWED, 'Needs a POST request' );
LJ::isu( $rv->{u} )
or return api_error( $r->HTTP_UNAUTHORIZED, 'Not logged in' );
my $args = $r->json
or return api_error( $r->HTTP_BAD_REQUEST, 'Invalid/no JSON input' );
# First pass to check arguments.
my %media;
foreach my $id ( keys %$args ) {
# sometimes JS sends us the string 'null' so let's make sure $id is OK
return api_error( $r->HTTP_BAD_REQUEST, 'Media ID not provided' )
unless defined $id && $id ne 'null';
# use eval to catch croaks
$media{$id} = eval { DW::Media->new( user => $rv->{u}, mediaid => int( $id / 256 ) ) };
return api_error( $r->NOT_FOUND, 'Media ID not found or invalid' )
unless $media{$id};
return api_error( $r->HTTP_BAD_REQUEST, 'Security invalid' )
if $args->{$id}->{security}
&& $args->{$id}->{security} !~ /^(?:public|private|usemask)$/;
if ( exists $args->{$id}->{allowmask} ) {
return api_error( $r->HTTP_BAD_REQUEST, 'Allowmask invalid with chosen security' )
unless $args->{$id}->{security} eq 'usemask';
return api_error( $r->HTTP_BAD_REQUEST, 'Allowmask must be numeric' )
unless $args->{$id}->{allowmask} =~ /^\d+$/;
}
# Check to be sure this is valid. Security and Allowmask are separate
# from the rest, which are properties.
foreach my $key ( keys %{ $args->{$id} } ) {
next if $key eq 'security' || $key eq 'allowmask';
my $pobj = LJ::get_prop( media => $key );
return api_error( $r->HTTP_BAD_REQUEST, 'Invalid property' )
unless ref $pobj eq 'HASH' && $pobj->{id};
}
}
# We did that in two phases so we could verify that all of the objects
# were loadable, to try to make it an atomic process.
foreach my $id ( keys %$args ) {
my ( $security, $allowmask ) =
( delete $args->{$id}->{security}, int( delete $args->{$id}->{allowmask} // 0 ) );
if ( defined $security ) {
$media{$id}->set_security(
security => $security,
allowmask => $allowmask,
);
}
# At this point, we must have deleted all non-property items.
foreach my $prop ( keys %{ $args->{$id} } ) {
$media{$id}->prop( $prop => $args->{$id}->{$prop} );
}
}
return api_ok($args);
}
1;