<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/

require_once __DIR__ . '/../../phpBB/includes/functions.php';
require_once __DIR__ . '/../../phpBB/includes/utf/utf_tools.php';
require_once __DIR__ . '/../../phpBB/includes/functions_upload.php';

class phpbb_filespec_test extends phpbb_test_case
{
	const TEST_COUNT = 100;
	const PREFIX = 'phpbb_';
	const MAX_STR_LEN = 50;
	const UPLOAD_MAX_FILESIZE = 1000;

	private $config;
	public $path;

	protected function setUp()
	{
		// Global $config required by unique_id
		// Global $user required by filespec::additional_checks and
		// filespec::move_file
		global $config, $user;

		if (!is_array($config))
		{
			$config = array();
		}

		$config['rand_seed'] = '';
		$config['rand_seed_last_update'] = time() + 600;
		// This config value is normally pulled from the database which is set
		// to this value at install time.
		// See: phpBB/install/schemas/schema_data.sql
		$config['mime_triggers'] = 'body|head|html|img|plaintext|a href|pre|script|table|title';

		$user = new phpbb_mock_user();
		$user->lang = new phpbb_mock_lang();

		$this->config = &$config;
		$this->path = __DIR__ . '/fixture/';

		// Create copies of the files for use in testing move_file
		$iterator = new DirectoryIterator($this->path);
		foreach ($iterator as $fileinfo)
		{
			if ($fileinfo->isDot() || $fileinfo->isDir())
			{
				continue;
			}

			copy($fileinfo->getPathname(), $this->path . 'copies/' . $fileinfo->getFilename() . '_copy');
			if ($fileinfo->getFilename() === 'txt')
			{
				copy($fileinfo->getPathname(), $this->path . 'copies/' . $fileinfo->getFilename() . '_copy_2');
			}
		}
	}

	private function get_filespec($override = array())
	{
		// Initialise a blank filespec object for use with trivial methods
		$upload_ary = array(
			'name' => '',
			'type' => '',
			'size' => '',
			'tmp_name' => '',
			'error' => '',
		);

		return new filespec(array_merge($upload_ary, $override), null);
	}

	protected function tearDown()
	{
		global $user;
		$this->config = array();
		$user = null;

		$iterator = new DirectoryIterator($this->path . 'copies');
		foreach ($iterator as $fileinfo)
		{
			$name = $fileinfo->getFilename();
			if ($name[0] !== '.')
			{
				unlink($fileinfo->getPathname());
			}
		}
	}

	public function additional_checks_variables()
	{
		// False here just indicates the file is too large and fails the
		// filespec::additional_checks method because of it. All other code
		// paths in that method are covered elsewhere.
		return array(
			array('gif', true),
			array('jpg', false),
			array('png', true),
			array('tif', false),
			array('txt', false),
		);
	}

	/**
	 * @dataProvider additional_checks_variables
	 */
	public function test_additional_checks($filename, $expected)
	{
		$upload = new phpbb_mock_fileupload();
		$filespec = $this->get_filespec();
		$filespec->upload = $upload;
		$filespec->file_moved = true;
		$filespec->filesize = $filespec->get_filesize($this->path . $filename);

		$this->assertEquals($expected, $filespec->additional_checks());
	}

	public function check_content_variables()
	{
		// False here indicates that a file is non-binary and contains
		// disallowed content that makes IE report the mimetype incorrectly.
		return array(
			array('gif', true),
			array('jpg', true),
			array('png', true),
			array('tif', true),
			array('txt', false),
		);
	}

	/**
	 * @dataProvider check_content_variables
	 */
	public function test_check_content($filename, $expected)
	{
		$disallowed_content = explode('|', $this->config['mime_triggers']);
		$filespec = $this->get_filespec(array('tmp_name' => $this->path . $filename));
		$this->assertEquals($expected, $filespec->check_content($disallowed_content));
		// All files should pass if $disallowed_content is empty
		$this->assertEquals(true, $filespec->check_content(array()));
	}

	public function clean_filename_variables()
	{
		$chunks = str_split('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\'\\" /:*?<>|[];(){},#+=-_`', 8);
		return array(
			array($chunks[0] . $chunks[7]),
			array($chunks[1] . $chunks[8]),
			array($chunks[2] . $chunks[9]),
			array($chunks[3] . $chunks[4]),
			array($chunks[5] . $chunks[6]),
		);
	}

	/**
	 * @dataProvider clean_filename_variables
	 */
	public function test_clean_filename_real($filename)
	{
		$bad_chars = array("'", "\\", ' ', '/', ':', '*', '?', '"', '<', '>', '|');
		$filespec = $this->get_filespec(array('name' => $filename));
		$filespec->clean_filename('real', self::PREFIX);
		$name = $filespec->realname;

		$this->assertEquals(0, preg_match('/%(\w{2})/', $name));
		foreach ($bad_chars as $char)
		{
			$this->assertFalse(strpos($name, $char));
		}
	}

	public function test_clean_filename_unique()
	{
		$filenames = array();
		for ($tests = 0; $tests < self::TEST_COUNT; $tests++)
		{
			$filespec = $this->get_filespec();
			$filespec->clean_filename('unique', self::PREFIX);
			$name = $filespec->realname;
			
			$this->assertEquals(strlen($name), 32 + strlen(self::PREFIX));
			$this->assertRegExp('#^[A-Za-z0-9]+$#', substr($name, strlen(self::PREFIX)));
			$this->assertFalse(isset($filenames[$name]));
			$filenames[$name] = true;
		}
	}

	public function get_extension_variables()
	{
		return array(
			array('file.png', 'png'),
			array('file.phpbb.gif', 'gif'),
			array('file..', ''),
			array('.file..jpg.webp', 'webp'),
		);
	}

	/**
	 * @dataProvider get_extension_variables
	 */
	public function test_get_extension($filename, $expected)
	{
		$this->assertEquals($expected, filespec::get_extension($filename));
	}

	public function is_image_variables()
	{
		return array(
			array('gif', 'image/gif', true),
			array('jpg', 'image/jpg', true),
			array('png', 'image/png', true),
			array('tif', 'image/tif', true),
			array('txt', 'text/plain', false),
		);
	}

	/**
	 * @dataProvider is_image_variables
	 */
	public function test_is_image($filename, $mimetype, $expected)
	{
		$filespec = $this->get_filespec(array('tmp_name' => $this->path . $filename, 'type' => $mimetype));
		$this->assertEquals($expected, $filespec->is_image());
	}

	public function move_file_variables()
	{
		return array(
			array('gif_copy', 'gif_moved', 'image/gif', 'gif', false, true),
			array('non_existant', 'still_non_existant', 'text/plain', 'txt', 'GENERAL_UPLOAD_ERROR', false),
			array('txt_copy', 'txt_as_img', 'image/jpg', 'txt', false, true),
			array('txt_copy_2', 'txt_moved', 'text/plain', 'txt', false, true),
			array('jpg_copy', 'jpg_moved', 'image/png', 'jpg', false, true),
			array('png_copy', 'png_moved', 'image/png', 'jpg', 'IMAGE_FILETYPE_MISMATCH', true),
		);
	}

	/**
	 * @dataProvider move_file_variables
	 */
	public function test_move_file($tmp_name, $realname, $mime_type, $extension, $error, $expected)
	{
		// Global $phpbb_root_path and $phpEx are required by phpbb_chmod
		global $phpbb_root_path, $phpEx;
		$phpbb_root_path = '';
		$phpEx = 'php';

		$upload = new phpbb_mock_fileupload();
		$upload->max_filesize = self::UPLOAD_MAX_FILESIZE;

		$filespec = $this->get_filespec(array(
			'tmp_name' => $this->path . 'copies/' . $tmp_name,
			'name' => $realname,
			'type' => $mime_type,
		));
		$filespec->extension = $extension;
		$filespec->upload = $upload;
		$filespec->local = true;

		$this->assertEquals($expected, $filespec->move_file($this->path . 'copies'));
		$this->assertEquals($filespec->file_moved, file_exists($this->path . 'copies/' . $realname));
		if ($error)
		{
			$this->assertEquals($error, $filespec->error[0]);
		}

		$phpEx = '';
	}

	/**
	* @dataProvider clean_filename_variables
	*/
	public function test_uploadname($filename)
	{
		$type_cast_helper = new \phpbb\request\type_cast_helper();

		$upload_name = '';
		$type_cast_helper->set_var($upload_name, $filename, 'string', true, true);
		$filespec = $this->get_filespec(array('name'=> $upload_name));

		$this->assertSame(trim(utf8_basename(htmlspecialchars($filename))), $filespec->uploadname);
	}
}
