forked from GNUsocial/gnu-social
		
	Merge branch 'nightly' of biodantas/gnu-social into nightly
This commit is contained in:
		@@ -649,7 +649,16 @@ detection.
 | 
			
		||||
* `supported`: an array of mime types you accept to store and distribute,
 | 
			
		||||
    like 'image/gif', 'video/mpeg', 'audio/mpeg', etc. Make sure you
 | 
			
		||||
    setup your server to properly recognize the types you want to
 | 
			
		||||
    support.
 | 
			
		||||
    support. It's important to use the result of calling `image_type_to_extension`
 | 
			
		||||
    for the appropriate image type, in the case of images. This is so all parts of
 | 
			
		||||
    the code see the same extension for each image type (jpg vs jpeg).
 | 
			
		||||
    For example, to enable BMP uploads, add this to the config.php file:
 | 
			
		||||
        $config['attachments']['supported'][image_type_to_mime_type(IMAGETYPE_GIF)]
 | 
			
		||||
        = image_type_to_extension(IMAGETYPE_GIF);
 | 
			
		||||
    See https://www.php.net/manual/en/function.image-type-to-mime-type.php for a
 | 
			
		||||
    list of such constants. If a filetype is not listed there, it's possible to add
 | 
			
		||||
    the mimetype and the extension by hand, but they need to match those returned by
 | 
			
		||||
    the file command.
 | 
			
		||||
 | 
			
		||||
* `uploads`: false to disable uploading files with notices (true by default).
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
# GNU social 1.19.x
 | 
			
		||||
# GNU social 1.20.x
 | 
			
		||||
(c) 2010-2019 Free Software Foundation, Inc
 | 
			
		||||
 | 
			
		||||
This is the README file for GNU social, the free
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										222
									
								
								classes/File.php
									
									
									
									
									
								
							
							
						
						
									
										222
									
								
								classes/File.php
									
									
									
									
									
								
							@@ -1,23 +1,32 @@
 | 
			
		||||
<?php
 | 
			
		||||
/*
 | 
			
		||||
 * StatusNet - the distributed open-source microblogging tool
 | 
			
		||||
 * Copyright (C) 2008, 2009, StatusNet, Inc.
 | 
			
		||||
/**
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * Abstraction for files
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero 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
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU Affero General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
 * along with this program.     If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Files
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 | 
			
		||||
 * @author    Miguel Dantas <biodantas@gmail.com>
 | 
			
		||||
 * @copyright 2008-2009, 2019 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
if (!defined('GNUSOCIAL')) { exit(1); }
 | 
			
		||||
defined('GNUSOCIAL') || die();
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Table Definition for file
 | 
			
		||||
@@ -71,20 +80,20 @@ class File extends Managed_DataObject
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function isProtected($url) {
 | 
			
		||||
    public static function isProtected($url)
 | 
			
		||||
    {
 | 
			
		||||
        $protected_urls_exps = array(
 | 
			
		||||
            'https://www.facebook.com/login.php',
 | 
			
		||||
            common_path('main/login')
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
		$protected_urls_exps = array(
 | 
			
		||||
			'https://www.facebook.com/login.php',
 | 
			
		||||
        	common_path('main/login')
 | 
			
		||||
        	);
 | 
			
		||||
        foreach ($protected_urls_exps as $protected_url_exp) {
 | 
			
		||||
            if (preg_match('!^'.preg_quote($protected_url_exp).'(.*)$!i', $url) === 1) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		foreach ($protected_urls_exps as $protected_url_exp) {
 | 
			
		||||
			if (preg_match('!^'.preg_quote($protected_url_exp).'(.*)$!i', $url) === 1) {
 | 
			
		||||
				return true;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return false;
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -93,6 +102,7 @@ class File extends Managed_DataObject
 | 
			
		||||
     * @param array $redir_data lookup data eg from File_redirection::where()
 | 
			
		||||
     * @param string $given_url
 | 
			
		||||
     * @return File
 | 
			
		||||
     * @throws ServerException
 | 
			
		||||
     */
 | 
			
		||||
    public static function saveNew(array $redir_data, $given_url)
 | 
			
		||||
    {
 | 
			
		||||
@@ -142,16 +152,27 @@ class File extends Managed_DataObject
 | 
			
		||||
 | 
			
		||||
        $file = new File;
 | 
			
		||||
        $file->url = $given_url;
 | 
			
		||||
        if (!empty($redir_data['protected'])) $file->protected = $redir_data['protected'];
 | 
			
		||||
        if (!empty($redir_data['title'])) $file->title = $redir_data['title'];
 | 
			
		||||
        if (!empty($redir_data['type'])) $file->mimetype = $redir_data['type'];
 | 
			
		||||
        if (!empty($redir_data['size'])) $file->size = intval($redir_data['size']);
 | 
			
		||||
        if (isset($redir_data['time']) && $redir_data['time'] > 0) $file->date = intval($redir_data['time']);
 | 
			
		||||
        if (!empty($redir_data['protected'])) {
 | 
			
		||||
            $file->protected = $redir_data['protected'];
 | 
			
		||||
        }
 | 
			
		||||
        if (!empty($redir_data['title'])) {
 | 
			
		||||
            $file->title = $redir_data['title'];
 | 
			
		||||
        }
 | 
			
		||||
        if (!empty($redir_data['type'])) {
 | 
			
		||||
            $file->mimetype = $redir_data['type'];
 | 
			
		||||
        }
 | 
			
		||||
        if (!empty($redir_data['size'])) {
 | 
			
		||||
            $file->size = intval($redir_data['size']);
 | 
			
		||||
        }
 | 
			
		||||
        if (isset($redir_data['time']) && $redir_data['time'] > 0) {
 | 
			
		||||
            $file->date = intval($redir_data['time']);
 | 
			
		||||
        }
 | 
			
		||||
        $file->saveFile();
 | 
			
		||||
        return $file;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function saveFile() {
 | 
			
		||||
    public function saveFile()
 | 
			
		||||
    {
 | 
			
		||||
        $this->urlhash = self::hashurl($this->url);
 | 
			
		||||
 | 
			
		||||
        if (!Event::handle('StartFileSaveNew', array(&$this))) {
 | 
			
		||||
@@ -183,7 +204,8 @@ class File extends Managed_DataObject
 | 
			
		||||
     *
 | 
			
		||||
     * @throws ServerException on failure
 | 
			
		||||
     */
 | 
			
		||||
    public static function processNew($given_url, Notice $notice=null, $followRedirects=true) {
 | 
			
		||||
    public static function processNew($given_url, Notice $notice=null, $followRedirects=true)
 | 
			
		||||
    {
 | 
			
		||||
        if (empty($given_url)) {
 | 
			
		||||
            throw new ServerException('No given URL to process');
 | 
			
		||||
        }
 | 
			
		||||
@@ -212,12 +234,13 @@ class File extends Managed_DataObject
 | 
			
		||||
        return $file;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function respectsQuota(Profile $scoped, $fileSize) {
 | 
			
		||||
    public static function respectsQuota(Profile $scoped, $fileSize)
 | 
			
		||||
    {
 | 
			
		||||
        if ($fileSize > common_config('attachments', 'file_quota')) {
 | 
			
		||||
            // TRANS: Message used to be inserted as %2$s in  the text "No file may
 | 
			
		||||
            // TRANS: be larger than %1$d byte and the file you sent was %2$s.".
 | 
			
		||||
            // TRANS: %1$d is the number of bytes of an uploaded file.
 | 
			
		||||
            $fileSizeText = sprintf(_m('%1$d byte','%1$d bytes',$fileSize),$fileSize);
 | 
			
		||||
            $fileSizeText = sprintf(_m('%1$d byte', '%1$d bytes', $fileSize), $fileSize);
 | 
			
		||||
 | 
			
		||||
            $fileQuota = common_config('attachments', 'file_quota');
 | 
			
		||||
            // TRANS: Message given if an upload is larger than the configured maximum.
 | 
			
		||||
@@ -225,10 +248,16 @@ class File extends Managed_DataObject
 | 
			
		||||
            // TRANS: %2$s is the proper form of "n bytes". This is the only ways to have
 | 
			
		||||
            // TRANS: gettext support multiple plurals in the same message, unfortunately...
 | 
			
		||||
            throw new ClientException(
 | 
			
		||||
                    sprintf(_m('No file may be larger than %1$d byte and the file you sent was %2$s. Try to upload a smaller version.',
 | 
			
		||||
                              'No file may be larger than %1$d bytes and the file you sent was %2$s. Try to upload a smaller version.',
 | 
			
		||||
                              $fileQuota),
 | 
			
		||||
                    $fileQuota, $fileSizeText));
 | 
			
		||||
                sprintf(
 | 
			
		||||
                        _m(
 | 
			
		||||
                        'No file may be larger than %1$d byte and the file you sent was %2$s. Try to upload a smaller version.',
 | 
			
		||||
                        'No file may be larger than %1$d bytes and the file you sent was %2$s. Try to upload a smaller version.',
 | 
			
		||||
                        $fileQuota
 | 
			
		||||
                    ),
 | 
			
		||||
                        $fileQuota,
 | 
			
		||||
                        $fileSizeText
 | 
			
		||||
                    )
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $file = new File;
 | 
			
		||||
@@ -241,10 +270,15 @@ class File extends Managed_DataObject
 | 
			
		||||
            // TRANS: Message given if an upload would exceed user quota.
 | 
			
		||||
            // TRANS: %d (number) is the user quota in bytes and is used for plural.
 | 
			
		||||
            throw new ClientException(
 | 
			
		||||
                    sprintf(_m('A file this large would exceed your user quota of %d byte.',
 | 
			
		||||
                              'A file this large would exceed your user quota of %d bytes.',
 | 
			
		||||
                              common_config('attachments', 'user_quota')),
 | 
			
		||||
                    common_config('attachments', 'user_quota')));
 | 
			
		||||
                sprintf(
 | 
			
		||||
                        _m(
 | 
			
		||||
                        'A file this large would exceed your user quota of %d byte.',
 | 
			
		||||
                        'A file this large would exceed your user quota of %d bytes.',
 | 
			
		||||
                        common_config('attachments', 'user_quota')
 | 
			
		||||
                    ),
 | 
			
		||||
                        common_config('attachments', 'user_quota')
 | 
			
		||||
                    )
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        $query .= ' AND EXTRACT(month FROM file.modified) = EXTRACT(month FROM now()) and EXTRACT(year FROM file.modified) = EXTRACT(year FROM now())';
 | 
			
		||||
        $file->query($query);
 | 
			
		||||
@@ -254,10 +288,15 @@ class File extends Managed_DataObject
 | 
			
		||||
            // TRANS: Message given id an upload would exceed a user's monthly quota.
 | 
			
		||||
            // TRANS: $d (number) is the monthly user quota in bytes and is used for plural.
 | 
			
		||||
            throw new ClientException(
 | 
			
		||||
                    sprintf(_m('A file this large would exceed your monthly quota of %d byte.',
 | 
			
		||||
                              'A file this large would exceed your monthly quota of %d bytes.',
 | 
			
		||||
                              common_config('attachments', 'monthly_quota')),
 | 
			
		||||
                    common_config('attachments', 'monthly_quota')));
 | 
			
		||||
                sprintf(
 | 
			
		||||
                        _m(
 | 
			
		||||
                        'A file this large would exceed your monthly quota of %d byte.',
 | 
			
		||||
                        'A file this large would exceed your monthly quota of %d bytes.',
 | 
			
		||||
                        common_config('attachments', 'monthly_quota')
 | 
			
		||||
                    ),
 | 
			
		||||
                        common_config('attachments', 'monthly_quota')
 | 
			
		||||
                    )
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
@@ -274,7 +313,7 @@ class File extends Managed_DataObject
 | 
			
		||||
 | 
			
		||||
    // where should the file go?
 | 
			
		||||
 | 
			
		||||
    static function filename(Profile $profile, $origname, $mimetype)
 | 
			
		||||
    public static function filename(Profile $profile, $origname, $mimetype)
 | 
			
		||||
    {
 | 
			
		||||
        $ext = self::guessMimeExtension($mimetype, $origname);
 | 
			
		||||
 | 
			
		||||
@@ -298,10 +337,12 @@ class File extends Managed_DataObject
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param $mimetype The mimetype we've discovered for this file.
 | 
			
		||||
     * @param $filename An optional filename which we can use on failure.
 | 
			
		||||
     * @param $mimetype string The mimetype we've discovered for this file.
 | 
			
		||||
     * @param $filename string An optional filename which we can use on failure.
 | 
			
		||||
     * @return mixed|string
 | 
			
		||||
     * @throws ClientException
 | 
			
		||||
     */
 | 
			
		||||
    static function guessMimeExtension($mimetype, $filename=null)
 | 
			
		||||
    public static function guessMimeExtension($mimetype, $filename=null)
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            // first see if we know the extension for our mimetype
 | 
			
		||||
@@ -349,16 +390,17 @@ class File extends Managed_DataObject
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Validation for as-saved base filenames
 | 
			
		||||
     * @param $filename
 | 
			
		||||
     * @return false|int
 | 
			
		||||
     */
 | 
			
		||||
    static function validFilename($filename)
 | 
			
		||||
    public static function validFilename($filename)
 | 
			
		||||
    {
 | 
			
		||||
        return preg_match('/^[A-Za-z0-9._-]+$/', $filename);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static function tryFilename($filename)
 | 
			
		||||
    public static function tryFilename($filename)
 | 
			
		||||
    {
 | 
			
		||||
        if (!self::validFilename($filename))
 | 
			
		||||
        {
 | 
			
		||||
        if (!self::validFilename($filename)) {
 | 
			
		||||
            throw new InvalidFilenameException($filename);
 | 
			
		||||
        }
 | 
			
		||||
        // if successful, return the filename for easy if-statementing
 | 
			
		||||
@@ -366,9 +408,11 @@ class File extends Managed_DataObject
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws ClientException on invalid filename
 | 
			
		||||
     * @param $filename
 | 
			
		||||
     * @return string
 | 
			
		||||
     * @throws InvalidFilenameException
 | 
			
		||||
     */
 | 
			
		||||
    static function path($filename)
 | 
			
		||||
    public static function path($filename)
 | 
			
		||||
    {
 | 
			
		||||
        self::tryFilename($filename);
 | 
			
		||||
 | 
			
		||||
@@ -381,19 +425,18 @@ class File extends Managed_DataObject
 | 
			
		||||
        return $dir . $filename;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static function url($filename)
 | 
			
		||||
    public static function url($filename)
 | 
			
		||||
    {
 | 
			
		||||
        self::tryFilename($filename);
 | 
			
		||||
 | 
			
		||||
        if (common_config('site','private')) {
 | 
			
		||||
 | 
			
		||||
            return common_local_url('getfile',
 | 
			
		||||
                                array('filename' => $filename));
 | 
			
		||||
 | 
			
		||||
        if (common_config('site', 'private')) {
 | 
			
		||||
            return common_local_url(
 | 
			
		||||
                'getfile',
 | 
			
		||||
                array('filename' => $filename)
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (GNUsocial::useHTTPS()) {
 | 
			
		||||
 | 
			
		||||
            $sslserver = common_config('attachments', 'sslserver');
 | 
			
		||||
 | 
			
		||||
            if (empty($sslserver)) {
 | 
			
		||||
@@ -402,7 +445,7 @@ class File extends Managed_DataObject
 | 
			
		||||
                if (is_string(common_config('site', 'sslserver')) &&
 | 
			
		||||
                    mb_strlen(common_config('site', 'sslserver')) > 0) {
 | 
			
		||||
                    $server = common_config('site', 'sslserver');
 | 
			
		||||
                } else if (common_config('site', 'server')) {
 | 
			
		||||
                } elseif (common_config('site', 'server')) {
 | 
			
		||||
                    $server = common_config('site', 'server');
 | 
			
		||||
                }
 | 
			
		||||
                $path = common_config('site', 'path') . '/file/';
 | 
			
		||||
@@ -439,9 +482,10 @@ class File extends Managed_DataObject
 | 
			
		||||
        return $protocol.'://'.$server.$path.$filename;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static $_enclosures = array();
 | 
			
		||||
    public static $_enclosures = array();
 | 
			
		||||
 | 
			
		||||
    function getEnclosure(){
 | 
			
		||||
    public function getEnclosure()
 | 
			
		||||
    {
 | 
			
		||||
        if (isset(self::$_enclosures[$this->getID()])) {
 | 
			
		||||
            return self::$_enclosures[$this->getID()];
 | 
			
		||||
        }
 | 
			
		||||
@@ -515,8 +559,12 @@ class File extends Managed_DataObject
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $image->getFileThumbnail($width, $height, $crop,
 | 
			
		||||
                                        !is_null($upscale) ? $upscale : common_config('thumbnail', 'upscale'));
 | 
			
		||||
        return $image->getFileThumbnail(
 | 
			
		||||
            $width,
 | 
			
		||||
            $height,
 | 
			
		||||
            $crop,
 | 
			
		||||
            !is_null($upscale) ? $upscale : common_config('thumbnail', 'upscale')
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getPath()
 | 
			
		||||
@@ -534,7 +582,9 @@ class File extends Managed_DataObject
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *  @param  mixed   $use_local  true means require local, null means prefer local, false means use whatever is stored
 | 
			
		||||
     * @param mixed $use_local true means require local, null means prefer local, false means use whatever is stored
 | 
			
		||||
     * @return string
 | 
			
		||||
     * @throws FileNotStoredLocallyException
 | 
			
		||||
     */
 | 
			
		||||
    public function getUrl($use_local=null)
 | 
			
		||||
    {
 | 
			
		||||
@@ -554,7 +604,7 @@ class File extends Managed_DataObject
 | 
			
		||||
        return $this->url;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static public function getByUrl($url)
 | 
			
		||||
    public static function getByUrl($url)
 | 
			
		||||
    {
 | 
			
		||||
        $file = new File();
 | 
			
		||||
        $file->urlhash = self::hashurl($url);
 | 
			
		||||
@@ -565,9 +615,11 @@ class File extends Managed_DataObject
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param   string  $hashstr    String of (preferrably lower case) hexadecimal characters, same as result of 'hash_file(...)'
 | 
			
		||||
     * @param string $hashstr String of (preferrably lower case) hexadecimal characters, same as result of 'hash_file(...)'
 | 
			
		||||
     * @return File
 | 
			
		||||
     * @throws NoResultException
 | 
			
		||||
     */
 | 
			
		||||
    static public function getByHash($hashstr)
 | 
			
		||||
    public static function getByHash($hashstr)
 | 
			
		||||
    {
 | 
			
		||||
        $file = new File();
 | 
			
		||||
        $file->filehash = strtolower($hashstr);
 | 
			
		||||
@@ -584,10 +636,13 @@ class File extends Managed_DataObject
 | 
			
		||||
            throw new ServerException('URL already exists in DB');
 | 
			
		||||
        }
 | 
			
		||||
        $sql = 'UPDATE %1$s SET urlhash=%2$s, url=%3$s WHERE urlhash=%4$s;';
 | 
			
		||||
        $result = $this->query(sprintf($sql, $this->tableName(),
 | 
			
		||||
                                             $this->_quote((string)self::hashurl($url)),
 | 
			
		||||
                                             $this->_quote((string)$url),
 | 
			
		||||
                                             $this->_quote((string)$this->urlhash)));
 | 
			
		||||
        $result = $this->query(sprintf(
 | 
			
		||||
            $sql,
 | 
			
		||||
            $this->tableName(),
 | 
			
		||||
            $this->_quote((string)self::hashurl($url)),
 | 
			
		||||
            $this->_quote((string)$url),
 | 
			
		||||
            $this->_quote((string)$this->urlhash)
 | 
			
		||||
        ));
 | 
			
		||||
        if ($result === false) {
 | 
			
		||||
            common_log_db_error($this, 'UPDATE', __FILE__);
 | 
			
		||||
            throw new ServerException("Could not UPDATE {$this->tableName()}.url");
 | 
			
		||||
@@ -604,7 +659,7 @@ class File extends Managed_DataObject
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    function blowCache($last=false)
 | 
			
		||||
    public function blowCache($last=false)
 | 
			
		||||
    {
 | 
			
		||||
        self::blow('file:notice-ids:%s', $this->id);
 | 
			
		||||
        if ($last) {
 | 
			
		||||
@@ -624,7 +679,7 @@ class File extends Managed_DataObject
 | 
			
		||||
     * @return array ids of notices that link to this file
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    function stream($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
 | 
			
		||||
    public function stream($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
 | 
			
		||||
    {
 | 
			
		||||
        // FIXME: Try to get the Profile::current() here in some other way to avoid mixing
 | 
			
		||||
        // the current session user with possibly background/queue processing.
 | 
			
		||||
@@ -632,14 +687,13 @@ class File extends Managed_DataObject
 | 
			
		||||
        return $stream->getNotices($offset, $limit, $since_id, $max_id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function noticeCount()
 | 
			
		||||
    public function noticeCount()
 | 
			
		||||
    {
 | 
			
		||||
        $cacheKey = sprintf('file:notice-count:%d', $this->id);
 | 
			
		||||
        
 | 
			
		||||
        $count = self::cacheGet($cacheKey);
 | 
			
		||||
 | 
			
		||||
        if ($count === false) {
 | 
			
		||||
 | 
			
		||||
            $f2p = new File_to_post();
 | 
			
		||||
 | 
			
		||||
            $f2p->file_id = $this->id;
 | 
			
		||||
@@ -704,7 +758,7 @@ class File extends Managed_DataObject
 | 
			
		||||
        return $this->update($orig);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static public function hashurl($url)
 | 
			
		||||
    public static function hashurl($url)
 | 
			
		||||
    {
 | 
			
		||||
        if (empty($url)) {
 | 
			
		||||
            throw new Exception('No URL provided to hash algorithm.');
 | 
			
		||||
@@ -712,7 +766,7 @@ class File extends Managed_DataObject
 | 
			
		||||
        return hash(self::URLHASH_ALG, $url);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static public function beforeSchemaUpdate()
 | 
			
		||||
    public static function beforeSchemaUpdate()
 | 
			
		||||
    {
 | 
			
		||||
        $table = strtolower(get_called_class());
 | 
			
		||||
        $schema = Schema::get();
 | 
			
		||||
@@ -745,7 +799,7 @@ class File extends Managed_DataObject
 | 
			
		||||
                $dupfile->update($orig);
 | 
			
		||||
                print "\nDeleting duplicate entries of too long URL on $table id: {$file->id} [";
 | 
			
		||||
                // only start deleting with this fetch.
 | 
			
		||||
                while($dupfile->fetch()) {
 | 
			
		||||
                while ($dupfile->fetch()) {
 | 
			
		||||
                    common_log(LOG_INFO, sprintf('Deleting duplicate File entry of %1$d: %2$d (original URL: %3$s collides with these first 191 characters: %4$s', $dupfile->id, $file->id, $origurl, $file->shortenedurl));
 | 
			
		||||
                    print ".";
 | 
			
		||||
                    $dupfile->delete();
 | 
			
		||||
@@ -761,13 +815,13 @@ class File extends Managed_DataObject
 | 
			
		||||
        echo "\n...now running hacky pre-schemaupdate change for $table:";
 | 
			
		||||
        // We have to create a urlhash that is _not_ the primary key,
 | 
			
		||||
        // transfer data and THEN run checkSchema
 | 
			
		||||
        $schemadef['fields']['urlhash'] = array (
 | 
			
		||||
        $schemadef['fields']['urlhash'] = array(
 | 
			
		||||
                                              'type' => 'varchar',
 | 
			
		||||
                                              'length' => 64,
 | 
			
		||||
                                              'not null' => false,  // this is because when adding column, all entries will _be_ NULL!
 | 
			
		||||
                                              'description' => 'sha256 of destination URL (url field)',
 | 
			
		||||
                                            );
 | 
			
		||||
        $schemadef['fields']['url'] = array (
 | 
			
		||||
        $schemadef['fields']['url'] = array(
 | 
			
		||||
                                              'type' => 'text',
 | 
			
		||||
                                              'description' => 'destination URL after following possible redirections',
 | 
			
		||||
                                            );
 | 
			
		||||
@@ -780,11 +834,13 @@ class File extends Managed_DataObject
 | 
			
		||||
        // urlhash is hash('sha256', $url) in the File table
 | 
			
		||||
        echo "Updating urlhash fields in $table table...";
 | 
			
		||||
        // Maybe very MySQL specific :(
 | 
			
		||||
        $tablefix->query(sprintf('UPDATE %1$s SET %2$s=%3$s;',
 | 
			
		||||
                            $schema->quoteIdentifier($table),
 | 
			
		||||
                            'urlhash',
 | 
			
		||||
        $tablefix->query(sprintf(
 | 
			
		||||
            'UPDATE %1$s SET %2$s=%3$s;',
 | 
			
		||||
            $schema->quoteIdentifier($table),
 | 
			
		||||
            'urlhash',
 | 
			
		||||
                            // The line below is "result of sha256 on column `url`"
 | 
			
		||||
                            'SHA2(url, 256)'));
 | 
			
		||||
                            'SHA2(url, 256)'
 | 
			
		||||
        ));
 | 
			
		||||
        echo "DONE.\n";
 | 
			
		||||
        echo "Resuming core schema upgrade...";
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,7 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * StatusNet, the distributed open-source microblogging tool
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * Default settings for core configuration
 | 
			
		||||
 *
 | 
			
		||||
 * PHP version 5
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
@@ -22,9 +19,9 @@
 | 
			
		||||
 * @category  Config
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Evan Prodromou <evan@status.net>
 | 
			
		||||
 * @copyright 2008-9 StatusNet, Inc.
 | 
			
		||||
 * @copyright 2008-2009, 2019 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://www.gnu.org/software/social/
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
$default =
 | 
			
		||||
@@ -253,11 +250,11 @@ $default =
 | 
			
		||||
                                'application/x-go-sgf' => 'sgf',
 | 
			
		||||
                                'application/xml'   => 'xml',
 | 
			
		||||
                                'application/gpx+xml' => 'gpx',
 | 
			
		||||
                                'image/png'         => 'png',
 | 
			
		||||
                                'image/jpeg'        => 'jpg',
 | 
			
		||||
                                'image/gif'         => 'gif',
 | 
			
		||||
                                'image/svg+xml'     => 'svg',
 | 
			
		||||
                                'image/vnd.microsoft.icon'  => 'ico',
 | 
			
		||||
                                image_type_to_mime_type(IMAGETYPE_PNG)  => image_type_to_extension(IMAGETYPE_PNG),
 | 
			
		||||
                                image_type_to_mime_type(IMAGETYPE_JPEG) => image_type_to_extension(IMAGETYPE_JPEG),
 | 
			
		||||
                                image_type_to_mime_type(IMAGETYPE_GIF)  => image_type_to_extension(IMAGETYPE_GIF),
 | 
			
		||||
                                'image/svg+xml'     => 'svg', // No built-in constant
 | 
			
		||||
                                image_type_to_mime_type(IMAGETYPE_ICO)  => image_type_to_extension(IMAGETYPE_ICO),
 | 
			
		||||
                                'audio/ogg'         => 'ogg',
 | 
			
		||||
                                'audio/mpeg'        => 'mpg',
 | 
			
		||||
                                'audio/x-speex'     => 'spx',
 | 
			
		||||
@@ -280,6 +277,7 @@ $default =
 | 
			
		||||
                    'php' => 'phps',    // this turns .php into .phps
 | 
			
		||||
                    'exe' => false,  // this would deny any uploads to keep the "exe" file extension
 | 
			
		||||
                ],
 | 
			
		||||
              'memory_limit' => '1024M' // PHP's memory limit to use temporarily when handling images
 | 
			
		||||
              ),
 | 
			
		||||
        'thumbnail' => [
 | 
			
		||||
              'dir' => null,    // falls back to File::path('thumb') (equivalent to ['attachments']['dir'] .  '/thumb/')
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ if (!defined('GNUSOCIAL')) { exit(1); }
 | 
			
		||||
define('GNUSOCIAL_ENGINE', 'GNU social');
 | 
			
		||||
define('GNUSOCIAL_ENGINE_URL', 'https://www.gnu.org/software/social/');
 | 
			
		||||
 | 
			
		||||
define('GNUSOCIAL_BASE_VERSION', '1.19.4');
 | 
			
		||||
define('GNUSOCIAL_BASE_VERSION', '1.20.0');
 | 
			
		||||
define('GNUSOCIAL_LIFECYCLE', 'rc0'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
 | 
			
		||||
 | 
			
		||||
define('GNUSOCIAL_VERSION', GNUSOCIAL_BASE_VERSION . '-' . GNUSOCIAL_LIFECYCLE);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,9 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * StatusNet, the distributed open-source microblogging tool
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * Abstraction for an image file
 | 
			
		||||
 *
 | 
			
		||||
 * PHP version 5
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
@@ -20,55 +18,41 @@
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Image
 | 
			
		||||
 * @package   StatusNet
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Evan Prodromou <evan@status.net>
 | 
			
		||||
 * @author    Zach Copley <zach@status.net>
 | 
			
		||||
 * @copyright 2008-2009 StatusNet, Inc.
 | 
			
		||||
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 | 
			
		||||
 * @author    Miguel Dantas <biodantasgs@gmail.com>
 | 
			
		||||
 * @copyright 2008, 2019 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://status.net/
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
if (!defined('GNUSOCIAL')) { exit(1); }
 | 
			
		||||
defined('GNUSOCIAL') || die();
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A wrapper on uploaded files
 | 
			
		||||
 * A wrapper on uploaded images
 | 
			
		||||
 *
 | 
			
		||||
 * Makes it slightly easier to accept an image file from upload.
 | 
			
		||||
 *
 | 
			
		||||
 * @category Image
 | 
			
		||||
 * @package  StatusNet
 | 
			
		||||
 * @package  GNUsocial
 | 
			
		||||
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @author   Evan Prodromou <evan@status.net>
 | 
			
		||||
 * @author   Zach Copley <zach@status.net>
 | 
			
		||||
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link     http://status.net/
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
class ImageFile
 | 
			
		||||
class ImageFile extends MediaFile
 | 
			
		||||
{
 | 
			
		||||
    var $id;
 | 
			
		||||
    var $filepath;
 | 
			
		||||
    var $filename;
 | 
			
		||||
    var $type;
 | 
			
		||||
    var $height;
 | 
			
		||||
    var $width;
 | 
			
		||||
    var $rotate=0;  // degrees to rotate for properly oriented image (extrapolated from EXIF etc.)
 | 
			
		||||
    var $animated = null;  // Animated image? (has more than 1 frame). null means untested
 | 
			
		||||
    var $mimetype = null;   // The _ImageFile_ mimetype, _not_ the originating File object
 | 
			
		||||
    public $type;
 | 
			
		||||
    public $height;
 | 
			
		||||
    public $width;
 | 
			
		||||
    public $rotate   = 0;    // degrees to rotate for properly oriented image (extrapolated from EXIF etc.)
 | 
			
		||||
    public $animated = null; // Animated image? (has more than 1 frame). null means untested
 | 
			
		||||
    public $mimetype = null; // The _ImageFile_ mimetype, _not_ the originating File object
 | 
			
		||||
 | 
			
		||||
    protected $fileRecord = null;
 | 
			
		||||
 | 
			
		||||
    function __construct($id, $filepath)
 | 
			
		||||
    public function __construct($id, string $filepath)
 | 
			
		||||
    {
 | 
			
		||||
        $this->id = $id;
 | 
			
		||||
        if (!empty($this->id)) {
 | 
			
		||||
            $this->fileRecord = new File();
 | 
			
		||||
            $this->fileRecord->id = $this->id;
 | 
			
		||||
            if (!$this->fileRecord->find(true)) {
 | 
			
		||||
                // If we have set an ID, we need that ID to exist!
 | 
			
		||||
                throw new NoResultException($this->fileRecord);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // These do not have to be the same as fileRecord->filename for example,
 | 
			
		||||
        // since we may have generated an image source file from something else!
 | 
			
		||||
        $this->filepath = $filepath;
 | 
			
		||||
@@ -76,16 +60,14 @@ class ImageFile
 | 
			
		||||
 | 
			
		||||
        $info = @getimagesize($this->filepath);
 | 
			
		||||
 | 
			
		||||
        if (!(
 | 
			
		||||
            ($info[2] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) ||
 | 
			
		||||
            ($info[2] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) ||
 | 
			
		||||
            $info[2] == IMAGETYPE_BMP ||
 | 
			
		||||
            ($info[2] == IMAGETYPE_WBMP && function_exists('imagecreatefromwbmp')) ||
 | 
			
		||||
            ($info[2] == IMAGETYPE_XBM && function_exists('imagecreatefromxbm')) ||
 | 
			
		||||
            ($info[2] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')))) {
 | 
			
		||||
 | 
			
		||||
        if (!(($info[2] == IMAGETYPE_GIF  && function_exists('imagecreatefromgif'))  ||
 | 
			
		||||
              ($info[2] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) ||
 | 
			
		||||
              ($info[2] == IMAGETYPE_BMP  && function_exists('imagecreatefrombmp')) ||
 | 
			
		||||
              ($info[2] == IMAGETYPE_WBMP && function_exists('imagecreatefromwbmp')) ||
 | 
			
		||||
              ($info[2] == IMAGETYPE_XBM  && function_exists('imagecreatefromxbm'))  ||
 | 
			
		||||
              ($info[2] == IMAGETYPE_PNG  && function_exists('imagecreatefrompng')))) {
 | 
			
		||||
            // TRANS: Exception thrown when trying to upload an unsupported image file format.
 | 
			
		||||
            throw new UnsupportedMediaException(_('Unsupported image format.'), $this->filepath);
 | 
			
		||||
            throw new UnsupportedMediaException(_m('Unsupported image format.'), $this->filepath);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->width    = $info[0];
 | 
			
		||||
@@ -93,11 +75,18 @@ class ImageFile
 | 
			
		||||
        $this->type     = $info[2];
 | 
			
		||||
        $this->mimetype = $info['mime'];
 | 
			
		||||
 | 
			
		||||
        parent::__construct(
 | 
			
		||||
            $filepath,
 | 
			
		||||
            $this->mimetype,
 | 
			
		||||
            null /* filehash, MediaFile will calculate it */,
 | 
			
		||||
            $id
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        if ($this->type === IMAGETYPE_JPEG && function_exists('exif_read_data')) {
 | 
			
		||||
            // Orientation value to rotate thumbnails properly
 | 
			
		||||
            $exif = @exif_read_data($this->filepath);
 | 
			
		||||
            if (is_array($exif) && isset($exif['Orientation'])) {
 | 
			
		||||
                switch ((int)$exif['Orientation']) {
 | 
			
		||||
                switch (intval($exif['Orientation'])) {
 | 
			
		||||
                case 1: // top is top
 | 
			
		||||
                    $this->rotate = 0;
 | 
			
		||||
                    break;
 | 
			
		||||
@@ -126,7 +115,7 @@ class ImageFile
 | 
			
		||||
        $media = common_get_mime_media($file->mimetype);
 | 
			
		||||
        if (Event::handle('CreateFileImageThumbnailSource', array($file, &$imgPath, $media))) {
 | 
			
		||||
            if (empty($file->filename) && !file_exists($imgPath)) {
 | 
			
		||||
                throw new UnsupportedMediaException(_('File without filename could not get a thumbnail source.'));
 | 
			
		||||
                throw new UnsupportedMediaException(_m('File without filename could not get a thumbnail source.'));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // First some mimetype specific exceptions
 | 
			
		||||
@@ -141,7 +130,7 @@ class ImageFile
 | 
			
		||||
                $imgPath = $file->getPath();
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                throw new UnsupportedMediaException(_('Unsupported media format.'), $file->getPath());
 | 
			
		||||
                throw new UnsupportedMediaException(_m('Unsupported media format.'), $file->getPath());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -155,7 +144,8 @@ class ImageFile
 | 
			
		||||
            // Avoid deleting the original
 | 
			
		||||
            try {
 | 
			
		||||
                if (strlen($imgPath) > 0 && $imgPath !== $file->getPath()) {
 | 
			
		||||
                    common_debug(__METHOD__.': Deleting temporary file that was created as image file thumbnail source: '._ve($imgPath));
 | 
			
		||||
                    common_debug(__METHOD__.': Deleting temporary file that was created as image file' .
 | 
			
		||||
                                 'thumbnail source: '._ve($imgPath));
 | 
			
		||||
                    @unlink($imgPath);
 | 
			
		||||
                }
 | 
			
		||||
            } catch (FileNotFoundException $e) {
 | 
			
		||||
@@ -163,7 +153,14 @@ class ImageFile
 | 
			
		||||
                // doesn't exist anyway, so it's safe to delete $imgPath
 | 
			
		||||
                @unlink($imgPath);
 | 
			
		||||
            }
 | 
			
		||||
            common_debug(sprintf('Exception %s caught when creating ImageFile for File id==%s and imgPath==%s: %s', get_class($e), _ve($file->id), _ve($imgPath), _ve($e->getMessage())));
 | 
			
		||||
            common_debug(sprintf(
 | 
			
		||||
                'Exception %s caught when creating ImageFile for File id==%s ' .
 | 
			
		||||
                                 'and imgPath==%s: %s',
 | 
			
		||||
                get_class($e),
 | 
			
		||||
                _ve($file->id),
 | 
			
		||||
                _ve($imgPath),
 | 
			
		||||
                _ve($e->getMessage())
 | 
			
		||||
            ));
 | 
			
		||||
            throw $e;
 | 
			
		||||
        }
 | 
			
		||||
        return $image;
 | 
			
		||||
@@ -178,42 +175,43 @@ class ImageFile
 | 
			
		||||
        return $this->filepath;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static function fromUpload($param='upload')
 | 
			
		||||
    /**
 | 
			
		||||
     * Process a file upload
 | 
			
		||||
     *
 | 
			
		||||
     * Uses MediaFile's `fromUpload` to do the majority of the work and reencodes the image,
 | 
			
		||||
     * to mitigate injection attacks.
 | 
			
		||||
     * @param string $param
 | 
			
		||||
     * @param Profile|null $scoped
 | 
			
		||||
     * @return ImageFile|MediaFile
 | 
			
		||||
     * @throws ClientException
 | 
			
		||||
     * @throws NoResultException
 | 
			
		||||
     * @throws NoUploadedMediaException
 | 
			
		||||
     * @throws ServerException
 | 
			
		||||
     * @throws UnsupportedMediaException
 | 
			
		||||
     * @throws UseFileAsThumbnailException
 | 
			
		||||
     */
 | 
			
		||||
    public static function fromUpload(string $param='upload', Profile $scoped = null)
 | 
			
		||||
    {
 | 
			
		||||
        switch ($_FILES[$param]['error']) {
 | 
			
		||||
         case UPLOAD_ERR_OK: // success, jump out
 | 
			
		||||
            break;
 | 
			
		||||
        return parent::fromUpload($param, $scoped);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
         case UPLOAD_ERR_INI_SIZE:
 | 
			
		||||
         case UPLOAD_ERR_FORM_SIZE:
 | 
			
		||||
            // TRANS: Exception thrown when too large a file is uploaded.
 | 
			
		||||
            // TRANS: %s is the maximum file size, for example "500b", "10kB" or "2MB".
 | 
			
		||||
            throw new Exception(sprintf(_('That file is too big. The maximum file size is %s.'), ImageFile::maxFileSize()));
 | 
			
		||||
 | 
			
		||||
         case UPLOAD_ERR_PARTIAL:
 | 
			
		||||
            @unlink($_FILES[$param]['tmp_name']);
 | 
			
		||||
            // TRANS: Exception thrown when uploading an image and that action could not be completed.
 | 
			
		||||
            throw new Exception(_('Partial upload.'));
 | 
			
		||||
 | 
			
		||||
         case UPLOAD_ERR_NO_FILE:
 | 
			
		||||
            // No file; probably just a non-AJAX submission.
 | 
			
		||||
            throw new ClientException(_('No file uploaded.'));
 | 
			
		||||
 | 
			
		||||
         default:
 | 
			
		||||
            common_log(LOG_ERR, __METHOD__ . ": Unknown upload error " . $_FILES[$param]['error']);
 | 
			
		||||
            // TRANS: Exception thrown when uploading an image fails for an unknown reason.
 | 
			
		||||
            throw new Exception(_('System error uploading file.'));
 | 
			
		||||
    /**
 | 
			
		||||
     * Several obscure file types should be normalized to PNG on resize.
 | 
			
		||||
     *
 | 
			
		||||
     * Keeps only PNG, JPEG and GIF
 | 
			
		||||
     *
 | 
			
		||||
     * @return int
 | 
			
		||||
     */
 | 
			
		||||
    public function preferredType()
 | 
			
		||||
    {
 | 
			
		||||
        // Keep only JPEG and GIF in their orignal format
 | 
			
		||||
        if ($this->type === IMAGETYPE_JPEG || $this->type === IMAGETYPE_GIF) {
 | 
			
		||||
            return $this->type;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $info = @getimagesize($_FILES[$param]['tmp_name']);
 | 
			
		||||
 | 
			
		||||
        if (!$info) {
 | 
			
		||||
            @unlink($_FILES[$param]['tmp_name']);
 | 
			
		||||
            // TRANS: Exception thrown when uploading a file as image that is not an image or is a corrupt file.
 | 
			
		||||
            throw new UnsupportedMediaException(_('Not an image or corrupt file.'), '[deleted]');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new ImageFile(null, $_FILES[$param]['tmp_name']);
 | 
			
		||||
        // We don't want to save some formats as they are rare, inefficient and antiquated
 | 
			
		||||
        // thus we can't guarantee clients will support
 | 
			
		||||
        // So just save it as PNG
 | 
			
		||||
        return IMAGETYPE_PNG;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -224,8 +222,13 @@ class ImageFile
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $outpath
 | 
			
		||||
     * @return ImageFile the image stored at target path
 | 
			
		||||
     * @throws ClientException
 | 
			
		||||
     * @throws NoResultException
 | 
			
		||||
     * @throws ServerException
 | 
			
		||||
     * @throws UnsupportedMediaException
 | 
			
		||||
     * @throws UseFileAsThumbnailException
 | 
			
		||||
     */
 | 
			
		||||
    function copyTo($outpath)
 | 
			
		||||
    public function copyTo($outpath)
 | 
			
		||||
    {
 | 
			
		||||
        return new ImageFile(null, $this->resizeTo($outpath));
 | 
			
		||||
    }
 | 
			
		||||
@@ -234,36 +237,34 @@ class ImageFile
 | 
			
		||||
     * Create and save a thumbnail image.
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $outpath
 | 
			
		||||
     * @param array $box    width, height, boundary box (x,y,w,h) defaults to full image
 | 
			
		||||
     * @param array $box width, height, boundary box (x,y,w,h) defaults to full image
 | 
			
		||||
     * @return string full local filesystem filename
 | 
			
		||||
     * @throws UnsupportedMediaException
 | 
			
		||||
     * @throws UseFileAsThumbnailException
 | 
			
		||||
     */
 | 
			
		||||
    function resizeTo($outpath, array $box=array())
 | 
			
		||||
    public function resizeTo($outpath, array $box=array())
 | 
			
		||||
    {
 | 
			
		||||
        $box['width'] = isset($box['width']) ? intval($box['width']) : $this->width;
 | 
			
		||||
        $box['width']  = isset($box['width'])  ? intval($box['width'])  : $this->width;
 | 
			
		||||
        $box['height'] = isset($box['height']) ? intval($box['height']) : $this->height;
 | 
			
		||||
        $box['x'] = isset($box['x']) ? intval($box['x']) : 0;
 | 
			
		||||
        $box['y'] = isset($box['y']) ? intval($box['y']) : 0;
 | 
			
		||||
        $box['w'] = isset($box['w']) ? intval($box['w']) : $this->width;
 | 
			
		||||
        $box['h'] = isset($box['h']) ? intval($box['h']) : $this->height;
 | 
			
		||||
        $box['x']      = isset($box['x'])      ? intval($box['x'])      : 0;
 | 
			
		||||
        $box['y']      = isset($box['y'])      ? intval($box['y'])      : 0;
 | 
			
		||||
        $box['w']      = isset($box['w'])      ? intval($box['w'])      : $this->width;
 | 
			
		||||
        $box['h']      = isset($box['h'])      ? intval($box['h'])      : $this->height;
 | 
			
		||||
 | 
			
		||||
        if (!file_exists($this->filepath)) {
 | 
			
		||||
            // TRANS: Exception thrown during resize when image has been registered as present, but is no longer there.
 | 
			
		||||
            throw new Exception(_('Lost our file.'));
 | 
			
		||||
            throw new Exception(_m('Lost our file.'));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Don't rotate/crop/scale if it isn't necessary
 | 
			
		||||
        if ($box['width'] === $this->width
 | 
			
		||||
                && $box['height'] === $this->height
 | 
			
		||||
                && $box['x'] === 0
 | 
			
		||||
                && $box['y'] === 0
 | 
			
		||||
                && $box['w'] === $this->width
 | 
			
		||||
                && $box['h'] === $this->height
 | 
			
		||||
                && $this->type == $this->preferredType()) {
 | 
			
		||||
            if ($this->rotate == 0) {
 | 
			
		||||
                // No rotational difference, just copy it as-is
 | 
			
		||||
                @copy($this->filepath, $outpath);
 | 
			
		||||
                return $outpath;
 | 
			
		||||
            } elseif (abs($this->rotate) == 90) {
 | 
			
		||||
        if ($box['width']     === $this->width
 | 
			
		||||
            && $box['height'] === $this->height
 | 
			
		||||
            && $box['x']      === 0
 | 
			
		||||
            && $box['y']      === 0
 | 
			
		||||
            && $box['w']      === $this->width
 | 
			
		||||
            && $box['h']      === $this->height
 | 
			
		||||
            && $this->type    === $this->preferredType()) {
 | 
			
		||||
            if (abs($this->rotate) == 90) {
 | 
			
		||||
                // Box is rotated 90 degrees in either direction,
 | 
			
		||||
                // so we have to redefine x to y and vice versa.
 | 
			
		||||
                $tmp = $box['width'];
 | 
			
		||||
@@ -278,7 +279,6 @@ class ImageFile
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if (Event::handle('StartResizeImageFile', array($this, $outpath, $box))) {
 | 
			
		||||
            $this->resizeToFile($outpath, $box);
 | 
			
		||||
        }
 | 
			
		||||
@@ -294,8 +294,22 @@ class ImageFile
 | 
			
		||||
        return $outpath;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Resizes a file. If $box is omitted, the size is not changed, but this is still useful,
 | 
			
		||||
     * because it will reencode the image in the `self::prefferedType()` format. This only
 | 
			
		||||
     * applies henceforward, not retroactively
 | 
			
		||||
     *
 | 
			
		||||
     * Increases the 'memory_limit' to the one in the 'attachments' section in the config, to
 | 
			
		||||
     * enable the handling of bigger images, which can cause a peak of memory consumption, while
 | 
			
		||||
     * encoding
 | 
			
		||||
     * @param $outpath
 | 
			
		||||
     * @param array $box
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    protected function resizeToFile($outpath, array $box)
 | 
			
		||||
    {
 | 
			
		||||
        $old_limit = ini_set('memory_limit', common_config('attachments', 'memory_limit'));
 | 
			
		||||
        $image_src = null;
 | 
			
		||||
        switch ($this->type) {
 | 
			
		||||
        case IMAGETYPE_GIF:
 | 
			
		||||
            $image_src = imagecreatefromgif($this->filepath);
 | 
			
		||||
@@ -317,7 +331,7 @@ class ImageFile
 | 
			
		||||
            break;
 | 
			
		||||
        default:
 | 
			
		||||
            // TRANS: Exception thrown when trying to resize an unknown file type.
 | 
			
		||||
            throw new Exception(_('Unknown file type'));
 | 
			
		||||
            throw new Exception(_m('Unknown file type'));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($this->rotate != 0) {
 | 
			
		||||
@@ -326,30 +340,45 @@ class ImageFile
 | 
			
		||||
 | 
			
		||||
        $image_dest = imagecreatetruecolor($box['width'], $box['height']);
 | 
			
		||||
 | 
			
		||||
        if ($this->type == IMAGETYPE_GIF || $this->type == IMAGETYPE_PNG || $this->type == IMAGETYPE_BMP) {
 | 
			
		||||
 | 
			
		||||
        if ($this->type == IMAGETYPE_PNG || $this->type == IMAGETYPE_BMP) {
 | 
			
		||||
            $transparent_idx = imagecolortransparent($image_src);
 | 
			
		||||
 | 
			
		||||
            if ($transparent_idx >= 0) {
 | 
			
		||||
 | 
			
		||||
            if ($transparent_idx >= 0 && $transparent_idx < 255) {
 | 
			
		||||
                $transparent_color = imagecolorsforindex($image_src, $transparent_idx);
 | 
			
		||||
                $transparent_idx = imagecolorallocate($image_dest, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
 | 
			
		||||
                $transparent_idx = imagecolorallocate(
 | 
			
		||||
                    $image_dest,
 | 
			
		||||
                    $transparent_color['red'],
 | 
			
		||||
                    $transparent_color['green'],
 | 
			
		||||
                    $transparent_color['blue']
 | 
			
		||||
                );
 | 
			
		||||
                imagefill($image_dest, 0, 0, $transparent_idx);
 | 
			
		||||
                imagecolortransparent($image_dest, $transparent_idx);
 | 
			
		||||
 | 
			
		||||
            } elseif ($this->type == IMAGETYPE_PNG) {
 | 
			
		||||
 | 
			
		||||
                imagealphablending($image_dest, false);
 | 
			
		||||
                $transparent = imagecolorallocatealpha($image_dest, 0, 0, 0, 127);
 | 
			
		||||
                imagefill($image_dest, 0, 0, $transparent);
 | 
			
		||||
                imagesavealpha($image_dest, true);
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        imagecopyresampled($image_dest, $image_src, 0, 0, $box['x'], $box['y'], $box['width'], $box['height'], $box['w'], $box['h']);
 | 
			
		||||
        imagecopyresampled(
 | 
			
		||||
            $image_dest,
 | 
			
		||||
            $image_src,
 | 
			
		||||
            0,
 | 
			
		||||
            0,
 | 
			
		||||
            $box['x'],
 | 
			
		||||
            $box['y'],
 | 
			
		||||
            $box['width'],
 | 
			
		||||
            $box['height'],
 | 
			
		||||
            $box['w'],
 | 
			
		||||
            $box['h']
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        switch ($this->preferredType()) {
 | 
			
		||||
        $type = $this->preferredType();
 | 
			
		||||
        $ext = image_type_to_extension($type, true);
 | 
			
		||||
        $outpath = preg_replace("/\.[^\.]+$/", $ext, $outpath);
 | 
			
		||||
 | 
			
		||||
        switch ($type) {
 | 
			
		||||
         case IMAGETYPE_GIF:
 | 
			
		||||
            imagegif($image_dest, $outpath);
 | 
			
		||||
            break;
 | 
			
		||||
@@ -361,92 +390,32 @@ class ImageFile
 | 
			
		||||
            break;
 | 
			
		||||
         default:
 | 
			
		||||
            // TRANS: Exception thrown when trying resize an unknown file type.
 | 
			
		||||
            throw new Exception(_('Unknown file type'));
 | 
			
		||||
            throw new Exception(_m('Unknown file type'));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        imagedestroy($image_src);
 | 
			
		||||
        imagedestroy($image_dest);
 | 
			
		||||
        ini_set('memory_limit', $old_limit); // Restore the old memory limit
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Several obscure file types should be normalized to PNG on resize.
 | 
			
		||||
     *
 | 
			
		||||
     * @fixme consider flattening anything not GIF or JPEG to PNG
 | 
			
		||||
     * @return int
 | 
			
		||||
     */
 | 
			
		||||
    function preferredType()
 | 
			
		||||
    {
 | 
			
		||||
        if($this->type == IMAGETYPE_BMP) {
 | 
			
		||||
            //we don't want to save BMP... it's an inefficient, rare, antiquated format
 | 
			
		||||
            //save png instead
 | 
			
		||||
            return IMAGETYPE_PNG;
 | 
			
		||||
        } else if($this->type == IMAGETYPE_WBMP) {
 | 
			
		||||
            //we don't want to save WBMP... it's a rare format that we can't guarantee clients will support
 | 
			
		||||
            //save png instead
 | 
			
		||||
            return IMAGETYPE_PNG;
 | 
			
		||||
        } else if($this->type == IMAGETYPE_XBM) {
 | 
			
		||||
            //we don't want to save XBM... it's a rare format that we can't guarantee clients will support
 | 
			
		||||
            //save png instead
 | 
			
		||||
            return IMAGETYPE_PNG;
 | 
			
		||||
        }
 | 
			
		||||
        return $this->type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function unlink()
 | 
			
		||||
    public function unlink()
 | 
			
		||||
    {
 | 
			
		||||
        @unlink($this->filepath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static function maxFileSize()
 | 
			
		||||
    {
 | 
			
		||||
        $value = ImageFile::maxFileSizeInt();
 | 
			
		||||
 | 
			
		||||
        if ($value > 1024 * 1024) {
 | 
			
		||||
            $value = $value/(1024*1024);
 | 
			
		||||
            // TRANS: Number of megabytes. %d is the number.
 | 
			
		||||
            return sprintf(_m('%dMB','%dMB',$value),$value);
 | 
			
		||||
        } else if ($value > 1024) {
 | 
			
		||||
            $value = $value/1024;
 | 
			
		||||
            // TRANS: Number of kilobytes. %d is the number.
 | 
			
		||||
            return sprintf(_m('%dkB','%dkB',$value),$value);
 | 
			
		||||
        } else {
 | 
			
		||||
            // TRANS: Number of bytes. %d is the number.
 | 
			
		||||
            return sprintf(_m('%dB','%dB',$value),$value);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static function maxFileSizeInt()
 | 
			
		||||
    {
 | 
			
		||||
        return min(ImageFile::strToInt(ini_get('post_max_size')),
 | 
			
		||||
                   ImageFile::strToInt(ini_get('upload_max_filesize')),
 | 
			
		||||
                   ImageFile::strToInt(ini_get('memory_limit')));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static function strToInt($str)
 | 
			
		||||
    {
 | 
			
		||||
        $unit = substr($str, -1);
 | 
			
		||||
        $num = substr($str, 0, -1);
 | 
			
		||||
 | 
			
		||||
        switch(strtoupper($unit)){
 | 
			
		||||
         case 'G':
 | 
			
		||||
            $num *= 1024;
 | 
			
		||||
         case 'M':
 | 
			
		||||
            $num *= 1024;
 | 
			
		||||
         case 'K':
 | 
			
		||||
            $num *= 1024;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $num;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function scaleToFit($maxWidth=null, $maxHeight=null, $crop=null)
 | 
			
		||||
    {
 | 
			
		||||
        return self::getScalingValues($this->width, $this->height,
 | 
			
		||||
                                        $maxWidth, $maxHeight, $crop, $this->rotate);
 | 
			
		||||
        return self::getScalingValues(
 | 
			
		||||
            $this->width,
 | 
			
		||||
            $this->height,
 | 
			
		||||
            $maxWidth,
 | 
			
		||||
            $maxHeight,
 | 
			
		||||
            $crop,
 | 
			
		||||
            $this->rotate
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets scaling values for images of various types. Cropping can be enabled.
 | 
			
		||||
     *
 | 
			
		||||
     * Values will scale _up_ to fit max values if cropping is enabled!
 | 
			
		||||
@@ -457,11 +426,18 @@ class ImageFile
 | 
			
		||||
     * @param $maxW     int Resulting max width
 | 
			
		||||
     * @param $maxH     int Resulting max height
 | 
			
		||||
     * @param $crop     int Crop to the size (not preserving aspect ratio)
 | 
			
		||||
     * @param int $rotate
 | 
			
		||||
     * @return array
 | 
			
		||||
     * @throws ServerException
 | 
			
		||||
     */
 | 
			
		||||
    public static function getScalingValues($width, $height,
 | 
			
		||||
                                        $maxW=null, $maxH=null,
 | 
			
		||||
                                        $crop=null, $rotate=0)
 | 
			
		||||
    {
 | 
			
		||||
    public static function getScalingValues(
 | 
			
		||||
        $width,
 | 
			
		||||
        $height,
 | 
			
		||||
        $maxW=null,
 | 
			
		||||
        $maxH=null,
 | 
			
		||||
        $crop=null,
 | 
			
		||||
        $rotate=0
 | 
			
		||||
    ) {
 | 
			
		||||
        $maxW = $maxW ?: common_config('thumbnail', 'width');
 | 
			
		||||
        $maxH = $maxH ?: common_config('thumbnail', 'height');
 | 
			
		||||
 | 
			
		||||
@@ -513,9 +489,9 @@ class ImageFile
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return array(intval($rw), intval($rh),
 | 
			
		||||
                    intval($cx), intval($cy),
 | 
			
		||||
                    is_null($cw) ? $width : intval($cw),
 | 
			
		||||
                    is_null($ch) ? $height : intval($ch));
 | 
			
		||||
                     intval($cx), intval($cy),
 | 
			
		||||
                     is_null($cw) ? $width : intval($cw),
 | 
			
		||||
                     is_null($ch) ? $height : intval($ch));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -539,7 +515,7 @@ class ImageFile
 | 
			
		||||
 | 
			
		||||
        // We read through the file til we reach the end of the file, or we've found
 | 
			
		||||
        // at least 2 frame headers
 | 
			
		||||
        while(!feof($fh) && $count < 2) {
 | 
			
		||||
        while (!feof($fh) && $count < 2) {
 | 
			
		||||
            $chunk = fread($fh, 1024 * 100); //read 100kb at a time
 | 
			
		||||
            $count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00\x2C#s', $chunk, $matches);
 | 
			
		||||
            // rewind in case we ended up in the middle of the header, but avoid
 | 
			
		||||
@@ -560,9 +536,9 @@ class ImageFile
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($width === null) {
 | 
			
		||||
            $width = common_config('thumbnail', 'width');
 | 
			
		||||
            $width  = common_config('thumbnail', 'width');
 | 
			
		||||
            $height = common_config('thumbnail', 'height');
 | 
			
		||||
            $crop = common_config('thumbnail', 'crop');
 | 
			
		||||
            $crop   = common_config('thumbnail', 'crop');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!$upscale) {
 | 
			
		||||
@@ -589,7 +565,7 @@ class ImageFile
 | 
			
		||||
                                            'file_id'=> $this->fileRecord->getID(),
 | 
			
		||||
                                            'width'  => $width,
 | 
			
		||||
                                            'height' => $height,
 | 
			
		||||
                                        ));
 | 
			
		||||
                                         ));
 | 
			
		||||
        if ($thumb instanceof File_thumbnail) {
 | 
			
		||||
            return $thumb;
 | 
			
		||||
        }
 | 
			
		||||
@@ -610,11 +586,16 @@ class ImageFile
 | 
			
		||||
                || $box['w'] < 1 || $box['x'] >= $this->width
 | 
			
		||||
                || $box['h'] < 1 || $box['y'] >= $this->height) {
 | 
			
		||||
            // Fail on bad width parameter. If this occurs, it's due to algorithm in ImageFile->scaleToFit
 | 
			
		||||
            common_debug("Boundary box parameters for resize of {$this->filepath} : ".var_export($box,true));
 | 
			
		||||
            common_debug("Boundary box parameters for resize of {$this->filepath} : ".var_export($box, true));
 | 
			
		||||
            throw new ServerException('Bad thumbnail size parameters.');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        common_debug(sprintf('Generating a thumbnail of File id==%u of size %ux%u', $this->fileRecord->getID(), $width, $height));
 | 
			
		||||
        common_debug(sprintf(
 | 
			
		||||
            'Generating a thumbnail of File id==%u of size %ux%u',
 | 
			
		||||
            $this->fileRecord->getID(),
 | 
			
		||||
            $width,
 | 
			
		||||
            $height
 | 
			
		||||
        ));
 | 
			
		||||
 | 
			
		||||
        // Perform resize and store into file
 | 
			
		||||
        $this->resizeTo($outpath, $box);
 | 
			
		||||
@@ -628,106 +609,14 @@ class ImageFile
 | 
			
		||||
            // $this->getPath() says the file doesn't exist anyway, so no point in trying to delete it!
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return File_thumbnail::saveThumbnail($this->fileRecord->getID(),
 | 
			
		||||
                                      null, // no url since we generated it ourselves and can dynamically generate the url
 | 
			
		||||
                                      $width, $height,
 | 
			
		||||
                                      $outname);
 | 
			
		||||
        return File_thumbnail::saveThumbnail(
 | 
			
		||||
            $this->fileRecord->getID(),
 | 
			
		||||
                                             // no url since we generated it ourselves and can dynamically
 | 
			
		||||
                                             // generate the url
 | 
			
		||||
                                             null,
 | 
			
		||||
            $width,
 | 
			
		||||
            $height,
 | 
			
		||||
            $outname
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//PHP doesn't (as of 2/24/2010) have an imagecreatefrombmp so conditionally define one
 | 
			
		||||
if(!function_exists('imagecreatefrombmp')){
 | 
			
		||||
    //taken shamelessly from http://www.php.net/manual/en/function.imagecreatefromwbmp.php#86214
 | 
			
		||||
    function imagecreatefrombmp($p_sFile)
 | 
			
		||||
    {
 | 
			
		||||
        //    Load the image into a string
 | 
			
		||||
        $file    =    fopen($p_sFile,"rb");
 | 
			
		||||
        $read    =    fread($file,10);
 | 
			
		||||
        while(!feof($file)&&($read<>""))
 | 
			
		||||
            $read    .=    fread($file,1024);
 | 
			
		||||
 | 
			
		||||
        $temp    =    unpack("H*",$read);
 | 
			
		||||
        $hex    =    $temp[1];
 | 
			
		||||
        $header    =    substr($hex,0,108);
 | 
			
		||||
 | 
			
		||||
        //    Process the header
 | 
			
		||||
        //    Structure: http://www.fastgraph.com/help/bmp_header_format.html
 | 
			
		||||
        if (substr($header,0,4)=="424d")
 | 
			
		||||
        {
 | 
			
		||||
            //    Cut it in parts of 2 bytes
 | 
			
		||||
            $header_parts    =    str_split($header,2);
 | 
			
		||||
 | 
			
		||||
            //    Get the width        4 bytes
 | 
			
		||||
            $width            =    hexdec($header_parts[19].$header_parts[18]);
 | 
			
		||||
 | 
			
		||||
            //    Get the height        4 bytes
 | 
			
		||||
            $height            =    hexdec($header_parts[23].$header_parts[22]);
 | 
			
		||||
 | 
			
		||||
            //    Unset the header params
 | 
			
		||||
            unset($header_parts);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //    Define starting X and Y
 | 
			
		||||
        $x                =    0;
 | 
			
		||||
        $y                =    1;
 | 
			
		||||
 | 
			
		||||
        //    Create newimage
 | 
			
		||||
        $image            =    imagecreatetruecolor($width,$height);
 | 
			
		||||
 | 
			
		||||
        //    Grab the body from the image
 | 
			
		||||
        $body            =    substr($hex,108);
 | 
			
		||||
 | 
			
		||||
        //    Calculate if padding at the end-line is needed
 | 
			
		||||
        //    Divided by two to keep overview.
 | 
			
		||||
        //    1 byte = 2 HEX-chars
 | 
			
		||||
        $body_size        =    (strlen($body)/2);
 | 
			
		||||
        $header_size    =    ($width*$height);
 | 
			
		||||
 | 
			
		||||
        //    Use end-line padding? Only when needed
 | 
			
		||||
        $usePadding        =    ($body_size>($header_size*3)+4);
 | 
			
		||||
 | 
			
		||||
        //    Using a for-loop with index-calculation instaid of str_split to avoid large memory consumption
 | 
			
		||||
        //    Calculate the next DWORD-position in the body
 | 
			
		||||
        for ($i=0;$i<$body_size;$i+=3)
 | 
			
		||||
        {
 | 
			
		||||
            //    Calculate line-ending and padding
 | 
			
		||||
            if ($x>=$width)
 | 
			
		||||
            {
 | 
			
		||||
                //    If padding needed, ignore image-padding
 | 
			
		||||
                //    Shift i to the ending of the current 32-bit-block
 | 
			
		||||
                if ($usePadding)
 | 
			
		||||
                    $i    +=    $width%4;
 | 
			
		||||
 | 
			
		||||
                //    Reset horizontal position
 | 
			
		||||
                $x    =    0;
 | 
			
		||||
 | 
			
		||||
                //    Raise the height-position (bottom-up)
 | 
			
		||||
                $y++;
 | 
			
		||||
 | 
			
		||||
                //    Reached the image-height? Break the for-loop
 | 
			
		||||
                if ($y>$height)
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //    Calculation of the RGB-pixel (defined as BGR in image-data)
 | 
			
		||||
            //    Define $i_pos as absolute position in the body
 | 
			
		||||
            $i_pos    =    $i*2;
 | 
			
		||||
            $r        =    hexdec($body[$i_pos+4].$body[$i_pos+5]);
 | 
			
		||||
            $g        =    hexdec($body[$i_pos+2].$body[$i_pos+3]);
 | 
			
		||||
            $b        =    hexdec($body[$i_pos].$body[$i_pos+1]);
 | 
			
		||||
 | 
			
		||||
            //    Calculate and draw the pixel
 | 
			
		||||
            $color    =    imagecolorallocate($image,$r,$g,$b);
 | 
			
		||||
            imagesetpixel($image,$x,$height-$y,$color);
 | 
			
		||||
 | 
			
		||||
            //    Raise the horizontal position
 | 
			
		||||
            $x++;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //    Unset the body / free the memory
 | 
			
		||||
        unset($body);
 | 
			
		||||
 | 
			
		||||
        //    Return image-object
 | 
			
		||||
        return $image;
 | 
			
		||||
    }
 | 
			
		||||
}   // if(!function_exists('imagecreatefrombmp'))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,8 @@
 | 
			
		||||
<?php
 | 
			
		||||
/**
 | 
			
		||||
 * StatusNet, the distributed open-source microblogging tool
 | 
			
		||||
 * GNU social - a federating social network
 | 
			
		||||
 *
 | 
			
		||||
 * Abstraction for media files in general
 | 
			
		||||
 *
 | 
			
		||||
 * TODO: combine with ImageFile?
 | 
			
		||||
 *
 | 
			
		||||
 * PHP version 5
 | 
			
		||||
 * Abstraction for media files
 | 
			
		||||
 *
 | 
			
		||||
 * LICENCE: This program is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
			
		||||
@@ -22,37 +18,78 @@
 | 
			
		||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 *
 | 
			
		||||
 * @category  Media
 | 
			
		||||
 * @package   StatusNet
 | 
			
		||||
 * @package   GNUsocial
 | 
			
		||||
 * @author    Robin Millette <robin@millette.info>
 | 
			
		||||
 * @author    Miguel Dantas <biodantas@gmail.com>
 | 
			
		||||
 * @author    Zach Copley <zach@status.net>
 | 
			
		||||
 * @copyright 2008-2009 StatusNet, Inc.
 | 
			
		||||
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 | 
			
		||||
 * @copyright 2008-2009, 2019 Free Software Foundation http://fsf.org
 | 
			
		||||
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 | 
			
		||||
 * @link      http://status.net/
 | 
			
		||||
 * @link      https://www.gnu.org/software/social/
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
if (!defined('GNUSOCIAL')) { exit(1); }
 | 
			
		||||
if (!defined('GNUSOCIAL')) {
 | 
			
		||||
    exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class responsible for abstracting media files
 | 
			
		||||
 */
 | 
			
		||||
class MediaFile
 | 
			
		||||
{
 | 
			
		||||
    var $filename      = null;
 | 
			
		||||
    var $fileRecord    = null;
 | 
			
		||||
    var $fileurl       = null;
 | 
			
		||||
    var $short_fileurl = null;
 | 
			
		||||
    var $mimetype      = null;
 | 
			
		||||
    public $id            = null;
 | 
			
		||||
    public $filepath      = null;
 | 
			
		||||
    public $filename      = null;
 | 
			
		||||
    public $fileRecord    = null;
 | 
			
		||||
    public $fileurl       = null;
 | 
			
		||||
    public $short_fileurl = null;
 | 
			
		||||
    public $mimetype      = null;
 | 
			
		||||
 | 
			
		||||
    function __construct($filename = null, $mimetype = null, $filehash = null)
 | 
			
		||||
    /**
 | 
			
		||||
     * @param string $filepath The path of the file this media refers to. Required
 | 
			
		||||
     * @param string $mimetype The mimetype of the file. Required
 | 
			
		||||
     * @param $filehash        The hash of the file, if known. Optional
 | 
			
		||||
     * @param int|null $id     The DB id of the file. Int if known, null if not.
 | 
			
		||||
     *                         If null, it searches for it. If -1, it skips all DB
 | 
			
		||||
     *                         interactions (useful for temporary objects)
 | 
			
		||||
     * @throws ClientException
 | 
			
		||||
     * @throws NoResultException
 | 
			
		||||
     * @throws ServerException
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct(string $filepath, string $mimetype, $filehash = null, $id = null)
 | 
			
		||||
    {
 | 
			
		||||
        $this->filename   = $filename;
 | 
			
		||||
        $this->mimetype   = $mimetype;
 | 
			
		||||
        $this->filehash   = $filehash;
 | 
			
		||||
        $this->fileRecord = $this->storeFile();
 | 
			
		||||
        $this->filepath = $filepath;
 | 
			
		||||
        $this->filename = basename($this->filepath);
 | 
			
		||||
        $this->mimetype = $mimetype;
 | 
			
		||||
        $this->filehash = self::getHashOfFile($this->filepath, $filehash);
 | 
			
		||||
        $this->id       = $id;
 | 
			
		||||
 | 
			
		||||
        $this->fileurl = common_local_url('attachment',
 | 
			
		||||
                                    array('attachment' => $this->fileRecord->id));
 | 
			
		||||
        // If id is -1, it means we're dealing with a temporary object and don't want to store it in the DB,
 | 
			
		||||
        // or add redirects
 | 
			
		||||
        if ($this->id !== -1) {
 | 
			
		||||
            if (!empty($this->id)) {
 | 
			
		||||
                // If we have an id, load it
 | 
			
		||||
                $this->fileRecord = new File();
 | 
			
		||||
                $this->fileRecord->id = $this->id;
 | 
			
		||||
                if (!$this->fileRecord->find(true)) {
 | 
			
		||||
                    // If we have set an ID, we need that ID to exist!
 | 
			
		||||
                    throw new NoResultException($this->fileRecord);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // Otherwise, store it
 | 
			
		||||
                $this->fileRecord = $this->storeFile();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        $this->maybeAddRedir($this->fileRecord->id, $this->fileurl);
 | 
			
		||||
        $this->short_fileurl = common_shorten_url($this->fileurl);
 | 
			
		||||
        $this->maybeAddRedir($this->fileRecord->id, $this->short_fileurl);
 | 
			
		||||
            $this->fileurl = common_local_url(
 | 
			
		||||
                'attachment',
 | 
			
		||||
                array('attachment' => $this->fileRecord->id)
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            $this->maybeAddRedir($this->fileRecord->id, $this->fileurl);
 | 
			
		||||
            $this->short_fileurl = common_shorten_url($this->fileurl);
 | 
			
		||||
            $this->maybeAddRedir($this->fileRecord->id, $this->short_fileurl);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function attachToNotice(Notice $notice)
 | 
			
		||||
@@ -65,20 +102,19 @@ class MediaFile
 | 
			
		||||
        return File::path($this->filename);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function shortUrl()
 | 
			
		||||
    public function shortUrl()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->short_fileurl;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getEnclosure()
 | 
			
		||||
    public function getEnclosure()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->getFile()->getEnclosure();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function delete()
 | 
			
		||||
    public function delete()
 | 
			
		||||
    {
 | 
			
		||||
        $filepath = File::path($this->filename);
 | 
			
		||||
        @unlink($filepath);
 | 
			
		||||
        @unlink($this->filepath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getFile()
 | 
			
		||||
@@ -90,18 +126,38 @@ class MediaFile
 | 
			
		||||
        return $this->fileRecord;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function storeFile()
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculate the hash of a file.
 | 
			
		||||
     *
 | 
			
		||||
     * This won't work for files >2GiB because PHP uses only 32bit.
 | 
			
		||||
     * @param string $filepath
 | 
			
		||||
     * @param string|null $filehash
 | 
			
		||||
     * @return string
 | 
			
		||||
     * @throws ServerException
 | 
			
		||||
     */
 | 
			
		||||
    public static function getHashOfFile(string $filepath, $filehash = null)
 | 
			
		||||
    {
 | 
			
		||||
        $filepath       = File::path($this->filename);
 | 
			
		||||
        if (!empty($this->filename) && $this->filehash === null) {
 | 
			
		||||
        assert(!empty($filepath), __METHOD__ . ": filepath cannot be null");
 | 
			
		||||
        if ($filehash === null) {
 | 
			
		||||
            // Calculate if we have an older upload method somewhere (Qvitter) that
 | 
			
		||||
            // doesn't do this before calling new MediaFile on its local files...
 | 
			
		||||
            $this->filehash = hash_file(File::FILEHASH_ALG, $filepath);
 | 
			
		||||
            if ($this->filehash === false) {
 | 
			
		||||
            $filehash = hash_file(File::FILEHASH_ALG, $filepath);
 | 
			
		||||
            if ($filehash === false) {
 | 
			
		||||
                throw new ServerException('Could not read file for hashing');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return $filehash;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieve or insert as a file in the DB
 | 
			
		||||
     *
 | 
			
		||||
     * @return object File
 | 
			
		||||
     * @throws ClientException
 | 
			
		||||
     * @throws ServerException
 | 
			
		||||
     */
 | 
			
		||||
    protected function storeFile()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $file = File::getByHash($this->filehash);
 | 
			
		||||
            // We're done here. Yes. Already. We assume sha256 won't collide on us anytime soon.
 | 
			
		||||
@@ -118,14 +174,13 @@ class MediaFile
 | 
			
		||||
        $file->urlhash  = File::hashurl($fileurl);
 | 
			
		||||
        $file->url      = $fileurl;
 | 
			
		||||
        $file->filehash = $this->filehash;
 | 
			
		||||
        $file->size     = filesize($filepath);
 | 
			
		||||
        $file->size     = filesize($this->filepath);
 | 
			
		||||
        if ($file->size === false) {
 | 
			
		||||
            throw new ServerException('Could not read file to get its size');
 | 
			
		||||
        }
 | 
			
		||||
        $file->date     = time();
 | 
			
		||||
        $file->mimetype = $this->mimetype;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        $file_id = $file->insert();
 | 
			
		||||
 | 
			
		||||
        if ($file_id===false) {
 | 
			
		||||
@@ -158,15 +213,24 @@ class MediaFile
 | 
			
		||||
        return $file;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function rememberFile($file, $short)
 | 
			
		||||
    public function rememberFile($file, $short)
 | 
			
		||||
    {
 | 
			
		||||
        $this->maybeAddRedir($file->id, $short);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function maybeAddRedir($file_id, $url)
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds Redir if needed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param $file_id
 | 
			
		||||
     * @param $url
 | 
			
		||||
     * @return bool false if no need to add, true if added
 | 
			
		||||
     * @throws ClientException If failed adding
 | 
			
		||||
     */
 | 
			
		||||
    public function maybeAddRedir($file_id, $url)
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $file_redir = File_redirection::getByUrl($url);
 | 
			
		||||
            File_redirection::getByUrl($url);
 | 
			
		||||
            return false;
 | 
			
		||||
        } catch (NoResultException $e) {
 | 
			
		||||
            $file_redir = new File_redirection;
 | 
			
		||||
            $file_redir->urlhash = File::hashurl($url);
 | 
			
		||||
@@ -180,10 +244,80 @@ class MediaFile
 | 
			
		||||
                // TRANS: Client exception thrown when a database error was thrown during a file upload operation.
 | 
			
		||||
                throw new ClientException(_('There was a database error while saving your file. Please try again.'));
 | 
			
		||||
            }
 | 
			
		||||
            return $result;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static function fromUpload($param='media', Profile $scoped=null)
 | 
			
		||||
    /**
 | 
			
		||||
     * The maximum allowed file size, as a string
 | 
			
		||||
     */
 | 
			
		||||
    public static function maxFileSize()
 | 
			
		||||
    {
 | 
			
		||||
        $value = self::maxFileSizeInt();
 | 
			
		||||
        if ($value > 1024 * 1024) {
 | 
			
		||||
            $value = $value/(1024*1024);
 | 
			
		||||
            // TRANS: Number of megabytes. %d is the number.
 | 
			
		||||
            return sprintf(_m('%dMB', '%dMB', $value), $value);
 | 
			
		||||
        } elseif ($value > 1024) {
 | 
			
		||||
            $value = $value/1024;
 | 
			
		||||
            // TRANS: Number of kilobytes. %d is the number.
 | 
			
		||||
            return sprintf(_m('%dkB', '%dkB', $value), $value);
 | 
			
		||||
        } else {
 | 
			
		||||
            // TRANS: Number of bytes. %d is the number.
 | 
			
		||||
            return sprintf(_m('%dB', '%dB', $value), $value);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The maximum allowed file size, as an int
 | 
			
		||||
     */
 | 
			
		||||
    public static function maxFileSizeInt()
 | 
			
		||||
    {
 | 
			
		||||
        return min(
 | 
			
		||||
            self::sizeStrToInt(ini_get('post_max_size')),
 | 
			
		||||
            self::sizeStrToInt(ini_get('upload_max_filesize')),
 | 
			
		||||
            self::sizeStrToInt(ini_get('memory_limit'))
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Convert a string representing a file size (with units), to an int
 | 
			
		||||
     * @param $str
 | 
			
		||||
     * @return bool|int|string
 | 
			
		||||
     */
 | 
			
		||||
    public static function sizeStrToInt($str)
 | 
			
		||||
    {
 | 
			
		||||
        $unit = substr($str, -1);
 | 
			
		||||
        $num = substr($str, 0, -1);
 | 
			
		||||
        switch (strtoupper($unit)) {
 | 
			
		||||
        case 'G':
 | 
			
		||||
            $num *= 1024;
 | 
			
		||||
            // no break
 | 
			
		||||
        case 'M':
 | 
			
		||||
            $num *= 1024;
 | 
			
		||||
            // no break
 | 
			
		||||
        case 'K':
 | 
			
		||||
            $num *= 1024;
 | 
			
		||||
        }
 | 
			
		||||
        return $num;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a new MediaFile or ImageFile object from an upload
 | 
			
		||||
     *
 | 
			
		||||
     * Tries to set the mimetype correctly, using the most secure method available and rejects the file otherwise.
 | 
			
		||||
     * In case the upload is an image, this function returns an new ImageFile (which extends MediaFile)
 | 
			
		||||
     * @param string $param
 | 
			
		||||
     * @param Profile|null $scoped
 | 
			
		||||
     * @return ImageFile|MediaFile
 | 
			
		||||
     * @throws ClientException
 | 
			
		||||
     * @throws NoResultException
 | 
			
		||||
     * @throws NoUploadedMediaException
 | 
			
		||||
     * @throws ServerException
 | 
			
		||||
     * @throws UnsupportedMediaException
 | 
			
		||||
     * @throws UseFileAsThumbnailException
 | 
			
		||||
     */
 | 
			
		||||
    public static function fromUpload(string $param='media', Profile $scoped=null)
 | 
			
		||||
    {
 | 
			
		||||
        // The existence of the "error" element means PHP has processed it properly even if it was ok.
 | 
			
		||||
        if (!isset($_FILES[$param]) || !isset($_FILES[$param]['error'])) {
 | 
			
		||||
@@ -194,19 +328,17 @@ class MediaFile
 | 
			
		||||
            case UPLOAD_ERR_OK: // success, jump out
 | 
			
		||||
                break;
 | 
			
		||||
            case UPLOAD_ERR_INI_SIZE:
 | 
			
		||||
                // TRANS: Client exception thrown when an uploaded file is larger than set in php.ini.
 | 
			
		||||
                throw new ClientException(_('The uploaded file exceeds the ' .
 | 
			
		||||
                            'upload_max_filesize directive in php.ini.'));
 | 
			
		||||
            case UPLOAD_ERR_FORM_SIZE:
 | 
			
		||||
                throw new ClientException(
 | 
			
		||||
                        // TRANS: Client exception.
 | 
			
		||||
                        _('The uploaded file exceeds the MAX_FILE_SIZE directive' .
 | 
			
		||||
                            ' that was specified in the HTML form.'));
 | 
			
		||||
                // TRANS: Exception thrown when too large a file is uploaded.
 | 
			
		||||
                // TRANS: %s is the maximum file size, for example "500b", "10kB" or "2MB".
 | 
			
		||||
                throw new ClientException(sprintf(
 | 
			
		||||
                    _('That file is too big. The maximum file size is %s.'),
 | 
			
		||||
                    self::maxFileSize()
 | 
			
		||||
                ));
 | 
			
		||||
            case UPLOAD_ERR_PARTIAL:
 | 
			
		||||
                @unlink($_FILES[$param]['tmp_name']);
 | 
			
		||||
                // TRANS: Client exception.
 | 
			
		||||
                throw new ClientException(_('The uploaded file was only' .
 | 
			
		||||
                            ' partially uploaded.'));
 | 
			
		||||
                throw new ClientException(_('The uploaded file was only partially uploaded.'));
 | 
			
		||||
            case UPLOAD_ERR_NO_FILE:
 | 
			
		||||
                // No file; probably just a non-AJAX submission.
 | 
			
		||||
                throw new NoUploadedMediaException($param);
 | 
			
		||||
@@ -220,39 +352,23 @@ class MediaFile
 | 
			
		||||
                // TRANS: Client exception thrown when a file upload operation has been stopped by an extension.
 | 
			
		||||
                throw new ClientException(_('File upload stopped by extension.'));
 | 
			
		||||
            default:
 | 
			
		||||
                common_log(LOG_ERR, __METHOD__ . ": Unknown upload error " .
 | 
			
		||||
                        $_FILES[$param]['error']);
 | 
			
		||||
                common_log(LOG_ERR, __METHOD__ . ": Unknown upload error " . $_FILES[$param]['error']);
 | 
			
		||||
                // TRANS: Client exception thrown when a file upload operation has failed with an unknown reason.
 | 
			
		||||
                throw new ClientException(_('System error uploading file.'));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // TODO: Make documentation clearer that this won't work for files >2GiB because
 | 
			
		||||
        //       PHP is stupid in its 32bit head. But noone accepts 2GiB files with PHP
 | 
			
		||||
        //       anyway... I hope.
 | 
			
		||||
        $filehash = hash_file(File::FILEHASH_ALG, $_FILES[$param]['tmp_name']);
 | 
			
		||||
        $filehash = strtolower(self::getHashOfFile($_FILES[$param]['tmp_name']));
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            $file = File::getByHash($filehash);
 | 
			
		||||
            // If no exception is thrown the file exists locally, so we'll use that and just add redirections.
 | 
			
		||||
            // but if the _actual_ locally stored file doesn't exist, getPath will throw FileNotFoundException
 | 
			
		||||
            $filename = basename($file->getPath());
 | 
			
		||||
            $filepath = $file->getPath();
 | 
			
		||||
            $mimetype = $file->mimetype;
 | 
			
		||||
 | 
			
		||||
        } catch (FileNotFoundException $e) {
 | 
			
		||||
            // The file does not exist in our local filesystem, so store this upload.
 | 
			
		||||
 | 
			
		||||
            if (!move_uploaded_file($_FILES[$param]['tmp_name'], $e->path)) {
 | 
			
		||||
                // TRANS: Client exception thrown when a file upload operation fails because the file could
 | 
			
		||||
                // TRANS: not be moved from the temporary folder to the permanent file location.
 | 
			
		||||
                throw new ClientException(_('File could not be moved to destination directory.'));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $filename = basename($file->getPath());
 | 
			
		||||
            $mimetype = $file->mimetype;
 | 
			
		||||
 | 
			
		||||
        } catch (NoResultException $e) {
 | 
			
		||||
            // XXX PHP: Upgrade to PHP 7.1
 | 
			
		||||
        // catch (FileNotFoundException | NoResultException $e)
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
            // We have to save the upload as a new local file. This is the normal course of action.
 | 
			
		||||
 | 
			
		||||
            if ($scoped instanceof Profile) {
 | 
			
		||||
                // Throws exception if additional size does not respect quota
 | 
			
		||||
                // This test is only needed, of course, if we're uploading something new.
 | 
			
		||||
@@ -260,24 +376,38 @@ class MediaFile
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $mimetype = self::getUploadedMimeType($_FILES[$param]['tmp_name'], $_FILES[$param]['name']);
 | 
			
		||||
            $media = common_get_mime_media($mimetype);
 | 
			
		||||
 | 
			
		||||
            $basename = basename($_FILES[$param]['name']);
 | 
			
		||||
 | 
			
		||||
            $filename = strtolower($filehash) . '.' . File::guessMimeExtension($mimetype, $basename);
 | 
			
		||||
            $filename = $filehash . '.' . File::guessMimeExtension($mimetype, $basename);
 | 
			
		||||
            $filepath = File::path($filename);
 | 
			
		||||
 | 
			
		||||
            $result = move_uploaded_file($_FILES[$param]['tmp_name'], $filepath);
 | 
			
		||||
 | 
			
		||||
            if (!$result) {
 | 
			
		||||
                // TRANS: Client exception thrown when a file upload operation fails because the file could
 | 
			
		||||
                // TRANS: not be moved from the temporary folder to the permanent file location.
 | 
			
		||||
                // UX: too specific
 | 
			
		||||
                throw new ClientException(_('File could not be moved to destination directory.'));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new MediaFile($filename, $mimetype, $filehash);
 | 
			
		||||
            if ($media === 'image') {
 | 
			
		||||
                // Use -1 for the id to avoid adding this temporary file to the DB
 | 
			
		||||
                $img = new ImageFile(-1, $filepath);
 | 
			
		||||
                // Validate the image by reencoding it. Additionally normalizes old formats to PNG,
 | 
			
		||||
                // keeping JPEG and GIF untouched
 | 
			
		||||
                $outpath = $img->resizeTo($img->filepath);
 | 
			
		||||
                $ext = image_type_to_extension($img->preferredType());
 | 
			
		||||
                $filename = $filehash . $ext;
 | 
			
		||||
                $filepath = File::path($filename);
 | 
			
		||||
                $result = rename($outpath, $filepath);
 | 
			
		||||
                return new ImageFile(null, $filepath);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return new MediaFile($filepath, $mimetype, $filehash);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static function fromFilehandle($fh, Profile $scoped=null) {
 | 
			
		||||
    public static function fromFilehandle($fh, Profile $scoped=null)
 | 
			
		||||
    {
 | 
			
		||||
        $stream = stream_get_meta_data($fh);
 | 
			
		||||
        // So far we're only handling filehandles originating from tmpfile(),
 | 
			
		||||
        // so we can always do hash_file on $stream['uri'] as far as I can tell!
 | 
			
		||||
@@ -310,7 +440,6 @@ class MediaFile
 | 
			
		||||
 | 
			
		||||
            $filename = basename($file->getPath());
 | 
			
		||||
            $mimetype = $file->mimetype;
 | 
			
		||||
 | 
			
		||||
        } catch (NoResultException $e) {
 | 
			
		||||
            if ($scoped instanceof Profile) {
 | 
			
		||||
                File::respectsQuota($scoped, filesize($stream['uri']));
 | 
			
		||||
@@ -327,7 +456,7 @@ class MediaFile
 | 
			
		||||
                common_log(LOG_ERR, 'File could not be moved (or chmodded) from '._ve($stream['uri']) . ' to ' . _ve($filepath));
 | 
			
		||||
                // TRANS: Client exception thrown when a file upload operation fails because the file could
 | 
			
		||||
                // TRANS: not be moved from the temporary folder to the permanent file location.
 | 
			
		||||
                throw new ClientException(_('File could not be moved to destination directory.' ));
 | 
			
		||||
                throw new ClientException(_('File could not be moved to destination directory.'));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -338,17 +467,103 @@ class MediaFile
 | 
			
		||||
     * Attempt to identify the content type of a given file.
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $filepath filesystem path as string (file must exist)
 | 
			
		||||
     * @param string $originalFilename (optional) for extension-based detection
 | 
			
		||||
     * @param bool $originalFilename (optional) for extension-based detection
 | 
			
		||||
     * @return string
 | 
			
		||||
     *
 | 
			
		||||
     * @throws ClientException if type is known, but not supported for local uploads
 | 
			
		||||
     * @throws ServerException
 | 
			
		||||
     * @fixme this seems to tie a front-end error message in, kinda confusing
 | 
			
		||||
     *
 | 
			
		||||
     * @throws ClientException if type is known, but not supported for local uploads
 | 
			
		||||
     */
 | 
			
		||||
    static function getUploadedMimeType($filepath, $originalFilename=false) {
 | 
			
		||||
    public static function getUploadedMimeType(string $filepath, $originalFilename=false)
 | 
			
		||||
    {
 | 
			
		||||
        // We only accept filenames to existing files
 | 
			
		||||
        $mimelookup = new finfo(FILEINFO_MIME_TYPE);
 | 
			
		||||
        $mimetype = $mimelookup->file($filepath);
 | 
			
		||||
 | 
			
		||||
        $mimetype = null;
 | 
			
		||||
 | 
			
		||||
        // From CodeIgniter
 | 
			
		||||
        // We'll need this to validate the MIME info string (e.g. text/plain; charset=us-ascii)
 | 
			
		||||
        $regexp = '/^([a-z\-]+\/[a-z0-9\-\.\+]+)(;\s.+)?$/';
 | 
			
		||||
        /**
 | 
			
		||||
         * Fileinfo extension - most reliable method
 | 
			
		||||
         *
 | 
			
		||||
         * Apparently XAMPP, CentOS, cPanel and who knows what
 | 
			
		||||
         * other PHP distribution channels EXPLICITLY DISABLE
 | 
			
		||||
         * ext/fileinfo, which is otherwise enabled by default
 | 
			
		||||
         * since PHP 5.3 ...
 | 
			
		||||
         */
 | 
			
		||||
        if (function_exists('finfo_file')) {
 | 
			
		||||
            $finfo = @finfo_open(FILEINFO_MIME);
 | 
			
		||||
            // It is possible that a FALSE value is returned, if there is no magic MIME database
 | 
			
		||||
            // file found on the system
 | 
			
		||||
            if (is_resource($finfo)) {
 | 
			
		||||
                $mime = @finfo_file($finfo, $filepath);
 | 
			
		||||
                finfo_close($finfo);
 | 
			
		||||
                /* According to the comments section of the PHP manual page,
 | 
			
		||||
                 * it is possible that this function returns an empty string
 | 
			
		||||
                 * for some files (e.g. if they don't exist in the magic MIME database)
 | 
			
		||||
                 */
 | 
			
		||||
                if (is_string($mime) && preg_match($regexp, $mime, $matches)) {
 | 
			
		||||
                    $mimetype = $matches[1];
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        /* This is an ugly hack, but UNIX-type systems provide a "native" way to detect the file type,
 | 
			
		||||
         * which is still more secure than depending on the value of $_FILES[$field]['type'], and as it
 | 
			
		||||
         * was reported in issue #750 (https://github.com/EllisLab/CodeIgniter/issues/750) - it's better
 | 
			
		||||
         * than mime_content_type() as well, hence the attempts to try calling the command line with
 | 
			
		||||
         * three different functions.
 | 
			
		||||
         *
 | 
			
		||||
         * Notes:
 | 
			
		||||
         *  - the DIRECTORY_SEPARATOR comparison ensures that we're not on a Windows system
 | 
			
		||||
         *  - many system admins would disable the exec(), shell_exec(), popen() and similar functions
 | 
			
		||||
         *    due to security concerns, hence the function_usable() checks
 | 
			
		||||
         */
 | 
			
		||||
        if (DIRECTORY_SEPARATOR !== '\\') {
 | 
			
		||||
            $cmd = 'file --brief --mime '.escapeshellarg($filepath).' 2>&1';
 | 
			
		||||
            if (function_exists('exec')) {
 | 
			
		||||
                /* This might look confusing, as $mime is being populated with all of the output
 | 
			
		||||
                 * when set in the second parameter. However, we only need the last line, which is
 | 
			
		||||
                 * the actual return value of exec(), and as such - it overwrites anything that could
 | 
			
		||||
                 * already be set for $mime previously. This effectively makes the second parameter a
 | 
			
		||||
                 * dummy value, which is only put to allow us to get the return status code.
 | 
			
		||||
                 */
 | 
			
		||||
                $mime = @exec($cmd, $mime, $return_status);
 | 
			
		||||
                if ($return_status === 0 && is_string($mime) && preg_match($regexp, $mime, $matches)) {
 | 
			
		||||
                    $mimetype = $matches[1];
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (function_exists('shell_exec')) {
 | 
			
		||||
                $mime = @shell_exec($cmd);
 | 
			
		||||
                if (strlen($mime) > 0) {
 | 
			
		||||
                    $mime = explode("\n", trim($mime));
 | 
			
		||||
                    if (preg_match($regexp, $mime[(count($mime) - 1)], $matches)) {
 | 
			
		||||
                        $mimetype = $matches[1];
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (function_exists('popen')) {
 | 
			
		||||
                $proc = @popen($cmd, 'r');
 | 
			
		||||
                if (is_resource($proc)) {
 | 
			
		||||
                    $mime = @fread($proc, 512);
 | 
			
		||||
                    @pclose($proc);
 | 
			
		||||
                    if ($mime !== false) {
 | 
			
		||||
                        $mime = explode("\n", trim($mime));
 | 
			
		||||
                        if (preg_match($regexp, $mime[(count($mime) - 1)], $matches)) {
 | 
			
		||||
                            $mimetype = $matches[1];
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // Fall back to mime_content_type(), if available (still better than $_FILES[$field]['type'])
 | 
			
		||||
        if (function_exists('mime_content_type')) {
 | 
			
		||||
            $mimetype = @mime_content_type($filepath);
 | 
			
		||||
            // It's possible that mime_content_type() returns FALSE or an empty string
 | 
			
		||||
            if ($mimetype == false && strlen($mimetype) > 0) {
 | 
			
		||||
                throw new ServerException(_m('Could not determine file\'s MIME type.'));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Unclear types are such that we can't really tell by the auto
 | 
			
		||||
        // detect what they are (.bin, .exe etc. are just "octet-stream")
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user