forked from github/Quit.mwForum
216 lines
6.8 KiB
Perl
Executable File
216 lines
6.8 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
#------------------------------------------------------------------------------
|
|
# mwForum - Web-based discussion forum
|
|
# Copyright (c) 1999-2015 Markus Wichitill
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#------------------------------------------------------------------------------
|
|
|
|
use strict;
|
|
use warnings;
|
|
no warnings qw(uninitialized redefine);
|
|
|
|
# Imports
|
|
use TyfMain;
|
|
|
|
#------------------------------------------------------------------------------
|
|
|
|
# Init
|
|
my ( $m, $cfg, $lng, $user, $userId ) = TyfMain->new( $_[0] );
|
|
|
|
# Get CGI parameters
|
|
my $postId = $m->paramInt('pid');
|
|
my $url = $m->paramStr('url');
|
|
|
|
# Check request source authentication
|
|
$m->checkSourceAuth() or $m->error('errSrcAuth');
|
|
|
|
# Get post
|
|
my $post = $m->fetchHash( "
|
|
SELECT * FROM posts WHERE id = ?", $postId );
|
|
$post or $m->error('errPstNotFnd');
|
|
my $postIdMod = $postId % 100;
|
|
my $boardId = $post->{boardId};
|
|
my $topicId = $post->{topicId};
|
|
|
|
# Get board
|
|
my $board = $m->fetchHash( "
|
|
SELECT * FROM boards WHERE id = ?", $boardId );
|
|
$board or $m->error('errBrdNotFnd');
|
|
|
|
# Get topic
|
|
my $topic = $m->fetchHash( "
|
|
SELECT * FROM topics WHERE id = ?", $topicId );
|
|
$topic or $m->error('errTpcNotFnd');
|
|
|
|
# Check if user is admin or moderator
|
|
$user->{admin} || $m->boardAdmin( $userId, $boardId )
|
|
or $m->error('errNoAccess');
|
|
|
|
# Check if attachments are enabled
|
|
$cfg->{attachments} && $board->{attach}
|
|
or $m->error("Attachments are disabled.");
|
|
$cfg->{attachImg} && $cfg->{attachImgThb}
|
|
or $m->error("Thumbnails are disabled.");
|
|
|
|
# Fetch image with HTTP::Tiny or LWP
|
|
my $maxLen = $cfg->{maxAttXferLen} || $cfg->{maxAttachLen};
|
|
my $file = "$cfg->{attachFsPath}/transfer-" . int( rand(99999) ) . ".tmp";
|
|
my $fileName = "";
|
|
if ( eval { require HTTP::Tiny } ) {
|
|
|
|
# Use HTTP::Tiny
|
|
my $ua = HTTP::Tiny->new(
|
|
agent => "tyForum/$TyfMain::VERSION; $cfg->{baseUrl}",
|
|
timeout => 5,
|
|
max_redirect => 2,
|
|
max_size => $maxLen,
|
|
default_headers =>
|
|
{ 'accept-language' => "en", 'accept-encoding' => "identity" }
|
|
);
|
|
|
|
# Check file size with HEAD request
|
|
my $rsp = $ua->request( 'HEAD', $url );
|
|
my $length = $rsp->{headers}{'content-length'};
|
|
$length <= $maxLen
|
|
or
|
|
error( $m, "Maximum file size exceeded. ($length > $maxLen)", $file );
|
|
$rsp->{status} == 200
|
|
or error( $m, "File size check failed. ($rsp->{reason})", $file );
|
|
|
|
# Transfer file from remote host to temp file
|
|
$rsp = undef;
|
|
eval { $rsp = $ua->mirror( $url, $file ); }
|
|
or error( $m, "Maximum file size exceeded.", $file );
|
|
$length = -s $file;
|
|
$length <= $maxLen
|
|
or
|
|
error( $m, "Maximum file size exceeded. ($length > $maxLen)", $file );
|
|
$length && $rsp->{status} == 200
|
|
or error( $m, "Transfer failed. ($rsp->{status})", $file );
|
|
$m->setMode( $file, 'file' );
|
|
($fileName) = $url =~ m!([\w.-]+\.(?:jpg|png|gif))!i;
|
|
}
|
|
elsif ( eval { require LWP::UserAgent } ) {
|
|
|
|
# Use LWP (better but bloaty)
|
|
my $ua = LWP::UserAgent->new(
|
|
agent => "tyForum/$TyfMain::VERSION; $cfg->{baseUrl}",
|
|
timeout => 5,
|
|
parse_head => 0,
|
|
max_redirect => 2,
|
|
max_size => $maxLen,
|
|
default_headers => HTTP::Headers->new(
|
|
'Accept-Language' => "en",
|
|
'Accept-Encoding' => "identity"
|
|
),
|
|
ssl_opts => { verify_hostname => 0 }
|
|
);
|
|
|
|
# Check file size with HEAD request
|
|
my $rsp = $ua->head( $url, ':content_file' => $file );
|
|
$rsp->content_length() <= $maxLen
|
|
or error(
|
|
$m,
|
|
"Maximum file size exceeded. ("
|
|
. $rsp->content_length()
|
|
. " > $maxLen)",
|
|
$file
|
|
);
|
|
$rsp->code() == 200
|
|
or error( $m, "File size check failed. (" . $rsp->status_line() . ")",
|
|
$file );
|
|
|
|
# Transfer file from remote host to temp file
|
|
$rsp = $ua->get( $url, ':content_file' => $file );
|
|
!$rsp->header('Client-Aborted')
|
|
or error(
|
|
$m,
|
|
"Maximum file size exceeded. ("
|
|
. $rsp->content_length()
|
|
. " > $maxLen)",
|
|
$file
|
|
);
|
|
my $length = -s $file;
|
|
$length <= $maxLen
|
|
or
|
|
error( $m, "Maximum file size exceeded. ($length > $maxLen)", $file );
|
|
$length && $rsp->code() == 200
|
|
or
|
|
error( $m, "Transfer failed. (" . $rsp->status_line() . ")", $file );
|
|
$m->setMode( $file, 'file' );
|
|
$fileName = $rsp->filename();
|
|
}
|
|
else {
|
|
$m->error("Neither HTTP::Tiny nor LWP modules are available.");
|
|
}
|
|
|
|
# Remove problematic stuff from filename and make sure it doesn't clash
|
|
$fileName =~ /\.(?:jpg|png|gif)\z/i
|
|
or error( $m, "No valid filename found.", $file );
|
|
$fileName =~ s![^A-Za-z_0-9.-]+!!g;
|
|
my ( $name, $ext ) = $fileName =~ /(.+?)(\.[^.]+)?\z/;
|
|
$name = substr( $name, 0, $cfg->{attachNameLen} || 40 );
|
|
$ext = substr( $ext, 0, $cfg->{attachNameLen} || 40 );
|
|
my $like = $m->{pgsql} ? 'ILIKE' : 'LIKE';
|
|
my $num = "";
|
|
|
|
for my $i ( 0 .. 100 ) {
|
|
$num = $i ? "-$i" : "";
|
|
my $nameExists = $m->fetchArray( "
|
|
SELECT 1 FROM attachments WHERE postId = ? AND LOWER(fileName) $like LOWER(?)",
|
|
$postId, "$name$num%" );
|
|
last if !$nameExists;
|
|
$i < 100 or error( $m, "Too many filename collisions.", $file );
|
|
}
|
|
$fileName = "$name$num$ext";
|
|
|
|
# Create directories and move temp file
|
|
my $path = "$cfg->{attachFsPath}/$postIdMod";
|
|
$m->createDirectories( $path, $postId );
|
|
rename $file, "$path/$postId/$fileName";
|
|
|
|
# Add attachments table entry
|
|
$m->dbDo( "
|
|
INSERT INTO attachments (postId, webImage, fileName) VALUES (?, 2, ?)",
|
|
$postId, $fileName );
|
|
my $attachId = $m->dbInsertId("attachments");
|
|
|
|
# Resize image
|
|
$fileName = $m->resizeAttachment($attachId) || $fileName
|
|
if $cfg->{attachImgRsz};
|
|
|
|
# Replace tag
|
|
my $body = \$post->{body};
|
|
my $urlRxEsc = quotemeta( $m->escHtml($url) );
|
|
$$body
|
|
=~ s!<img class="emi" src="$urlRxEsc" alt=""/?>![img thb]${fileName}[/img]!;
|
|
$m->dbDo( "
|
|
UPDATE posts SET body = ? WHERE id = ?", $$body, $postId );
|
|
|
|
# Log action and finish
|
|
$m->logAction( 1, 'attach', 'transfer', $userId, $boardId, $topicId, $postId,
|
|
$attachId );
|
|
$m->redirect( 'topic_show', pid => $postId );
|
|
|
|
#------------------------------------------------------------------------------
|
|
# Delete temp file(s) before erroring
|
|
|
|
sub error {
|
|
my $m = shift();
|
|
my $msg = shift();
|
|
my $file = shift();
|
|
|
|
unlink $file;
|
|
unlink "$file.err";
|
|
$m->error($msg);
|
|
}
|