<?php
/* wppa-wrappers.php
* Package: wp-photo-album-plus
*
* Contains wrappers for standard php functions
* For security and bug reasons
*
* Version 8.0.08.001
*
*/

// To fix a bug in PHP as that photos made with the selfie camera of an android smartphone
// erroneously cause the PHP warning 'is not a valid JPEG file' and cause imagecreatefromjpag crash.
function wppa_imagecreatefromjpeg( $file ) {

	if ( ! wppa_is_path_safe( $file ) && ! $_FILES ) {
		wppa_log( 'Err', 'Unsafe from path detected in wppa_imagecreatefromjpeg(): ' . wppa_shortpath(  $file ) );
		return false;
	}
	ini_set( 'gd.jpeg_ignore_warning', true );

	$img = imagecreatefromjpeg( $file );
	if ( ! $img ) {
		wppa_log( 'Err', 'Could not create memoryimage from file ' . wppa_shortpath( $file ) );
	}
	return $img;
}

// Wrapper for imagecreatefromgif( $file ), verifies safe pathnames
function wppa_imagecreatefromgif( $file ) {

	if ( ! wppa_is_path_safe( $file ) && ! $_FILES ) {
		wppa_log( 'Err', 'Unsafe from path detected in wppa_imagecreatefromgif(): ' . wppa_shortpath( $file ) );
		return false;
	}

	$img = imagecreatefromgif( $file );
	if ( ! $img ) {
		wppa_log( 'Err', 'Could not create memoryimage from file ' . wppa_shortpath( $file ) );
	}
	return $img;
}

// Wrapper for imagecreatefrompng( $file ), verifies safe pathnames
function wppa_imagecreatefrompng( $file ) {

	if ( ! wppa_is_path_safe( $file ) && ! $_FILES ) {
		wppa_log( 'Err', 'Unsafe from path detected in wppa_imagecreatefrompng(): ' . wppa_shortpath( $file ) );
		return false;
	}

	$img = imagecreatefrompng( $file );
	if ( ! $img ) {
		wppa_log( 'Err', 'Could not create memoryimage from file ' . wppa_shortpath( $file ) );
	}
	return $img;
}

// Wrapper for getimagesize( $file ), verifies safe pathnames
function wppa_getimagesize( $file ) {

	if ( ! wppa_is_path_safe( $file ) ) {
		wppa_log( 'Err', 'Unsafe from path detected in wppa_getimagesize(): ' . wppa_shortpath( $file ) );
		return false;
	}

	$result = getimagesize( $file );
	if ( ! $result ) {
		wppa_log( 'Err', 'Could not read image size from ' . wppa_shortpath( $file ) );
	}
	return $result;
}

function wppa_imagegif( $image, $file ) {

	$bret = imagegif( $image, $file );
	wppa_chmod( $file );
	return $bret;
}

function wppa_imagejpeg( $image, $file, $prec = 0 ) {

	$ext = wppa_get_ext( $file );
	if ( $ext != 'jpg' ) {
		wppa_log( 'ERR', 'Trying to save a jpg with extension ' . $ext, true );
		return false;
	}
	if ( ! $prec ) {
		$prec = wppa_opt( 'jpeg_quality' );
	}
	$bret = imagejpeg( $image, $file, $prec );
	wppa_chmod( $file );
	return $bret;
}

function wppa_imagepng( $image, $file, $prec = 0 ) {

	if ( ! $prec ) {
		$prec = 6;
	}
	$bret = imagepng( $image, $file, $prec );
	wppa_chmod( $file );
	return $bret;
}



// Wrapper for copy( $from, $to ) that verifies that the pathnames are safe for our application
// In case of unexpected operation: Generates a warning in the wppa log, and does not perform the copy.
function wppa_copy( $from, $to, $from_upload = false ) {

	// First test if we are uploading
	if ( ! wppa_is_path_safe( $from ) && $_FILES && ! $from_upload ) {
		if ( ! wppa_is_path_safe( $to ) ) {
			wppa_log( 'Err', '1 Unsafe to path detected in wppa_copy(): ' . wppa_shortpath( $to ), true );
			return false;
		}
		$bret = wppa_move_uploaded_file( $from, $to );
		return $bret;
	}

	if ( ! wppa_is_path_safe( $from ) && ! $from_upload ) {
		wppa_log( 'Err', '2 Unsafe from path detected in wppa_copy(): ' . wppa_shortpath( $from ), true );
		return false;	// For diagnostic purposes, no return here yet
	}
	if ( ! wppa_is_path_safe( $to ) ) {
		wppa_log( 'Err', '3 Unsafe to path detected in wppa_copy(): ' . wppa_shortpath( $to ), true );
		return false; // For diagnostic purposes, no return here yet
	}

	$bret = copy( $from, $to );
	wppa_log( 'Fso', wppa_shortpath( $from ) . ' copied to ' . wppa_shortpath( $to ) );
	chmod( $to, 0644 );
	return $bret;
}

function wppa_filesize( $file ) {

	if ( ! wppa_is_path_safe( $file ) ) {
		wppa_log( 'Err', 'Unsafe path detected in wppa_filesize(): ' . wppa_shortpath( $file ), true );
		return false;	// For diagnostic purposes, no return here yet
	}

	return filesize( $file );
}

// Wrapper for move_uploaded_file( $from, $to ) that verifies that the pathnames are safe for our application
function wppa_move_uploaded_file( $from, $to ) {

	if ( ! wppa_is_path_safe( $to ) ) {
		wppa_log( 'Err', 'Unsafe to path detected in wppa_move_uploaded_file(): ' . wppa_shortpath( $to ), true );
		return false;
	}
	if ( strpos( $from, '../' ) !== false ) {
		wppa_log( 'Err', 'Unsafe from path detected in wppa_move_uploaded_file(): ' . $from, true );
		return false;
	}

	$bret = wppa_copy( $from, $to, true );	// Set 'from uploaded file' to prevent inf loop from wppa_copy and wppa_move_uploaded_file

	return $bret;
}

// Wrapper for rename
function wppa_rename( $from, $to ) {

	if ( ! wppa_is_path_safe( $from ) ) {
		wppa_log( 'Err', 'Unsafe from path detected in wppa_rename(): ' . wppa_shortpath( $from ), true );
		return false;
	}
	if ( ! wppa_is_path_safe( $to ) ) {
		wppa_log( 'Err', 'Unsafe to path detected in wppa_rename(): ' . wppa_shortpath( $to ), true );
		return false;
	}

	$bret = false;
	if ( file_exists( $from ) ) {
		$bret = rename( $from, $to );
		if ( $bret ) {
			wppa_log( 'Fso', wppa_shortpath( $from ) . ' renamed to ' . wppa_shortpath( $to ) );
		}
		else {
			wppa_log( 'Fso', 'Could not rename file ' . wppa_shortpath( $from ) . ' to ' . wppa_shortpath( $to ), true );
		}
	}
	else {
		wppa_log( 'Fso', 'Could not rename non existent file ' . wppa_shortpath( $from ) . ' to ' . wppa_shortpath( $to ), true );
	}

	return $bret;
}

// Wrapper for fopen
function wppa_fopen( $file, $mode ) {

	// Is path safe?
	if ( ! wppa_is_path_safe( $file ) ) {
		wppa_log( 'Err', 'Unsafe to path detected in wppa_fopen(): ' . wppa_shortpath( $file ), true );
		return false; // For diagnostic purposes, no return here yet
	}

	// When opening for reading, the file must exist
	if ( strpos( $mode, 'r' ) !== false && ! is_file( $file ) ) {
		return false;
	}
	return fopen( $file, $mode );
}

// Wrapper for glob
// This wrapper never returns the . and .. dirs
// Returns always an array
// Additional flags: WPPA_ONLYDIRS === GLOB_ONLYDIR, WPPA_ONLYFILES
define( 'WPPA_ONLYDIRS', GLOB_ONLYDIR );
define( 'WPPA_ONLYFILES', 1024 );
function wppa_glob( $pattern, $flags = 0, $wp_content = false ) {

	// Is path safe?
	$dir = dirname( $pattern );
	if ( ! wppa_is_path_safe( $dir, $wp_content ) ) {
		wppa_log( 'Err', 'Unsafe path detected in wppa_glob(): ' . wppa_shortpath( $dir ), true );
		return array();
	}

	// Get dirlist
// wppa_log('obs', 'Flags = '.$flags.', filtered = '.intval($flags) and 0xFBFF);
	$dirlist = glob( $pattern, intval($flags) and 0xFBFF );

	// Init result;
	$result = array();

	// Process dirlist
	if ( ! empty( $dirlist ) ) foreach( $dirlist as $item ) {

		if ( ! $flags || 													// if don't care
			 ( ( $flags & WPPA_ONLYDIRS ) && is_dir( $item ) ) || 			// or must be dir and is dir
			 ( ( $flags & WPPA_ONLYFILES ) && is_file( $item ) ) ) { 		// or must be file and is file

			if ( basename( $item ) != '.' && basename( $item ) != '..' ) { 	// if its not a virtual dir
				$result[] = $item;											// Add to result
			}
		}
	}

	return $result;
}

// Wrapper for unlink
function wppa_unlink( $file, $log = true ) {

	if ( ! wppa_is_path_safe( $file ) ) {
		wppa_log( 'Err', 'Unsafe path detected in wppa_unlink(): ' . wppa_shortpath( $file ), true );
		return false;
	}

	if ( is_file( $file ) ) {
		unlink( $file );
		clearstatcache();
		if ( ! is_file ( $file ) && $log ) {
			wppa_log( 'Fso', wppa_shortpath( $file ) . ' removed' );
		}
	}
	return true;
}

// Make directory tree recursively
function wppa_mktree( $path ) {

	$bret = _wppa_mktree( $path );
	if ( ! $bret ) {
		wppa_log( 'Err', 'Could not create ' . $path );
	}
	return $bret;
}

function _wppa_mktree( $path ) {

	if ( wppa_is_dir( $path ) ) {
		wppa_chmod( $path, 0755 );
		return true;
	}

	// To prevent infinite recursion on faulty instalations
	if ( $path == dirname( $path ) ) {

		// We are at the top: /
		return false;
	}

	$bret = _wppa_mktree( dirname( $path ) );
	if ( $bret ) {
		wppa_mkdir( $path );
	}

	return ( is_dir( $path ) );
}

// Wrapper for mkdir
function wppa_mkdir( $dir ) {

	// Path safe?
	if ( ! wppa_is_path_safe( $dir ) ) {
		wppa_log( 'Err', 'Unsafe path detected in wppa_mkdir(): ' . wppa_shortpath( $dir ), true );
		return false;
	}

	// Already exists?
	elseif ( is_dir( $dir ) ) {
		chmod( $dir, 0755 );
		return true;
	}

	// Create dir
	else {
		mkdir( $dir );

		if ( is_dir( $dir ) ) {
			chmod( $dir, 0755 );
			wppa_log( 'Fso', 'Created path ' . wppa_shortpath( $dir ) );
			return true;
		}
		else {
			wppa_log( 'Err', 'Could not create ' . wppa_shortpath( $dir ) );
			return false;
		}
	}
}

function wppa_rmdir( $dir, $when_empty = false ) {

	// Path safe?
	if ( ! wppa_is_path_safe( $dir ) ) {
		wppa_log( 'Err', 'Unsafe path detected in wppa_rmdir(): ' . wppa_shortpath( $dir ), true );
		return false;
	}

	// If not exists, we're done
	if ( ! wppa_is_dir( $dir ) ) return;

	// Get content of the dir
	$files = wppa_glob( $dir . '/*' );

	// If $when_empty, do not remove when not empty
	if ( $when_empty && ! empty( $files ) ) {
		return;
	}

	// Remove all files
	foreach( $files as $file ) {
		if ( is_file( $file ) ) {
			unlink( $file );
		}
	}

	// Empty all dirs
	foreach( $files as $file ) {
		if ( is_dir( $file ) ) {
			wppa_rmdir( $file );
		}
	}

	// Remove dir
	$files = glob( $dir . '/*' );
	if ( empty( $files ) ) {
		rmdir( $dir );
	}
	if ( wppa_is_dir( $dir ) ) {
		wppa_log( 'Err', 'Could not remove dir ' . wppa_shortpath( $dir ) );
	}
	else {
		wppa_log( 'Fso', 'Successfully removed dir ' . wppa_shortpath( $dir ) );
	}
	return;
}

function wppa_chmod( $fso, $recursive = false ) {

	$fso = rtrim( $fso, '/' );

	// Check for valid path
	if ( ! wppa_is_path_safe( $fso ) ) {
		wppa_log( 'Err', 'Unsafe path detected in wppa_chmod() ' . wppa_shortpath( $fso ), true );
		return;
	}

	// Process file
	if ( is_file( $fso ) ) {
		chmod( $fso, 0644 );
	}

	// Process dir
	else {
		if ( is_dir( $fso ) ) {
			chmod( $fso, 0755 );
			if ( $recursive ) {
				$files = wppa_glob( $fso . '/*' );
				foreach( $files as $file ) {
					wppa_chmod( $file, true );
				}
			}
		}
	}

	return;
}

// Wrapper for is_dir
function wppa_is_dir( $dir ) {

	if ( ! wppa_is_path_safe( $dir ) ) {
		wppa_log( 'Err', 'Unsafe path detected in wppa_is_dir(): ' . wppa_shortpath( $dir ), true );
		return false;
	}

	$bret = is_dir( $dir );
	return $bret;
}

// Wrapper for is_file
function wppa_is_file( $path, $log = true ) {

	if ( ! wppa_is_path_safe( $path ) ) {
		if ( $log ) wppa_log( 'Err', 'Unsafe path detected in wppa_is_file(): ' . wppa_shortpath( $path ), true );
		return false;
	}

	$bret = is_file( $path );
	return $bret;
}

// Write an entire file
function wppa_put_contents( $path, $contents, $log = true ) {

	if ( ! wppa_is_path_safe( $path ) ) {
		if ( $log ) wppa_log( 'Err', 'Unsafe path detected in wppa_put_contents(): ' . wppa_shortpath( $path ), true );
		return false;
	}

	if ( ! is_dir( dirname( $path ) ) ) {
		wppa_mktree( dirname( $path ) );
	}

	$fp = @fopen( $path, 'wb' );
	if ( ! $fp )
		return false;

//	mbstring_binary_safe_encoding();

	$data_length = strlen( $contents );

	$bytes_written = fwrite( $fp, $contents );
	clearstatcache();

//	reset_mbstring_encoding();

	fclose( $fp );

	if ( $data_length !== $bytes_written ) {
		return false;
	}

	chmod( $path, 0644 );

	return true;
}

// Read an entire file
function wppa_get_contents( $file ) {

	if ( ! wppa_is_path_safe( $file ) ) {
		wppa_log( 'Err', 'Unsafe path detected in wppa_get_contents(): ' . wppa_shortpath( $path ), true );
		return false;
	}

	if ( is_file( $file ) ) {
		$result = file_get_contents( $file );
	}
	else {
		$result = false;
	}
	return $result;
}

// Read entire file into array
function wppa_get_contents_array( $path, $log = true ) {

	if ( ! wppa_is_path_safe( $path ) ) {
		if ( $log ) wppa_log( 'Err', 'Unsafe path detected in wppa_get_contents_array(): ' . wppa_shortpath( $path ), true );
		return false;
	}
	if ( is_file( $path ) ) {
		$result = file( $path );
	}
	else {
		$result = false;
	}
	return $result;
}

// Utility to check if a given full filepath is safe to manipulate upon
function wppa_is_path_safe( $path, $wp_content = false ) {
global $wppa_lang;
global $wppa_log_file;

	// Unsafe protocols
	if ( stripos( $path, 'phar://' ) !== false ) {
		return false;
	}

	// Safe protocols
	if ( strpos( strtolower( $path ), 'http://' ) === 0 ) {
		return true;
	}
	if ( strpos( strtolower( $path ), 'https://' ) === 0 ) {
		return true;
	}

	// During activation/setup
	if ( ! defined( 'WPPA_UPLOAD_PATH' ) ) return true;

	// The following files are safe to read or write to
	$safe_files = array( WPPA_PATH . '/index.php',
						 WPPA_PATH . '/wppa-dump.txt',
						 WPPA_CONTENT_PATH . '/uploads/index.php',
						 $wppa_log_file,
						 WPPA_CONTENT_PATH . '/plugins/wp-photo-album-plus/img/audiostub.jpg',
						 WPPA_CONTENT_PATH . '/plugins/wp-photo-album-plus/img/documentstub.png',
						 );

	// Verify specific files
	if ( in_array( $path, $safe_files ) ) {
		return true;
	}

	// wp-content is only safe if explixitely asked for (glob in import proc)
	if ( $wp_content ) {
		if ( strpos( $path, WPPA_CONTENT_PATH ) === 0 ) {
			return true;
		}
	}

	// The following root dirs are safe, including all their subdirs, to read/write into
	$safe_roots = array( /*WPPA_CONTENT_PATH . '/uploads',
						 WPPA_CONTENT_PATH . '/wppa-depot',
						 WPPA_CONTENT_PATH . '/' . wppa_opt( 'pl_dirname' ),
						 WPPA_CONTENT_PATH . '/' . wppa_opt( 'cache_root' ),
						 WPPA_CONTENT_PATH . '/blogs.dir',
						 WPPA_CONTENT_PATH . '/cache',
						 WPPA_CONTENT_PATH . '/wppa-pl',*/
						 WPPA_CONTENT_PATH,
						 WPPA_PATH . '/fonts',
						 WPPA_PATH . '/watermarks',
						 WPPA_PATH . '/wppa-dump.txt',
						 WPPA_UPLOAD_PATH . '/dynamic',
						 WPPA_PATH . '/img',
						 WPPA_PATH . '/theme',
						 WPPA_UPLOAD_PATH . '/temp',
						 WPPA_UPLOAD_PATH . '/zips',
						 WPPA_UPLOAD_PATH . '/qr',
						 WPPA_UPLOAD_PATH . '/cache',
						 WPPA_UPLOAD_PATH . '/icons',
						 wppa_opt( 'source_dir' ),
						 );

	// Verify roots
	foreach( array_keys( $safe_roots ) as $key ) {

		if ( $path == $safe_roots[$key] ) {
			return true;
		}

		// Starts the path with a safe root?
		if ( strpos( $path, $safe_roots[$key] ) === 0 ) {

			// Path traversal attempt?
			if ( strpos( $path, '../' ) !== false || strpos( $path, '/..' ) !== false ) {
				return false;
			}

			// Passed tests
			return true;
		}
	}

	// No safe root
	return false;
}

// PHP unserialize() is unsafe because it can produce dangerous objects
// This function unserializes arrays only, except when scabn is on board
// In case of error or dangerous data, returns an empty array
function wppa_unserialize( $xstring, $is_session = false ) {

	if ( version_compare( PHP_VERSION, '7.0.0') >= 0 ) {
		if ( $is_session && wppa_get_option( 'wppa_use_scabn' ) == 'yes' ) {
			return unserialize( $xstring, array( 'allowed_classes' => array( 'wfCart' ) ) );
		}
		else {
			return unserialize( $xstring, array( 'allowed_classes' => false ) );
		}
	}
	else {

		$string = $xstring;
		$result = array();

		// Assume its an array, else return the input string
		$type 	= substr( $string, 0, 2 );
		$string	= substr( $string, 2 );

		$cpos 	= strpos( $string, ':' );
		$count 	= substr( $string, 0, $cpos );
		$string = substr( $string, $cpos + 1 );
		$string	= trim( $string, '{}' );

		if ( $type != 'a:' ) {
//			wppa_log( 'Err', 'Not serialized arraydata encountered in wppa_unserialize()' );
			return array();
		}

		// Process data items
		while ( strlen( $string ) ) {

			// Decode the key
			$keytype = substr( $string, 0, 2 );
			$string  = substr( $string, 2 );
			switch ( $keytype ) {

				// Integer key
				case 'i:':
					$cpos 	 = strpos( $string, ';' );
					$key 	= intval( substr( $string, 0, $cpos ) );
					$string = substr( $string, $cpos + 1 );
					break;

				// String key
				case 's:':
					$cpos 	= strpos( $string, ':' );
					$keylen	= intval( substr( $string, 0, $cpos ) );
					$string = substr( $string, $cpos + 1 );
					$cpos 	= strpos( $string, ';' );
					$key 	= substr( $string, 1, $keylen );
					$string = substr( $string, $cpos + 1 );
					break;

				// Unimplemented key type
				default:
//					wppa_log( 'Err', 'Unimplemented keytype ' . $keytype . ' encountered in wppa_unserialize(' . $xstring . ')', true );
					return array();
			}

			// Decode the data
			$datatype = substr( $string, 0, 2 );
			$string   = substr( $string, 2 );

			switch ( $datatype ) {

				// Integer data
				case 'i:':
					$cpos 	= strpos( $string, ';' );
					$data 	= intval( substr( $string, 0, $cpos ) );
					$string = substr( $string, $cpos + 1 );
					break;

				// String data
				case 's:':
					$cpos 	 = strpos( $string, ':' );
					$datalen = intval( substr( $string, 0, $cpos ) );
					$string  = substr( $string, $cpos + 1 );
					$data 	 = substr( $string, 1, $datalen );
					$string  = substr( $string, $datalen + 3 );
					break;

				// Boolean
				case 'b:':
					$data 	 = substr( $string, 0, 1 ) == '1';
					$string  = substr( $string, 2 );
					break;

				// NULL
				case 'N;':
					$data 	 = NULL;
					break;

				// Array data
				case 'a:':
					$cbpos  = strpos( $string, '}' );
					$data 	= wppa_unserialize( 'a:' . substr( $string, 0, $cbpos + 1 ) );
					$string = substr( $string, $cbpos + 1 );
					break;

				// Unimplemented data type
				default:
//					wppa_log( 'Err', 'Unimplemented data type ' . $datatype . ' encountered in wppa_unserialize(' . $xstring . ')', true );
					return array();
			}

			// Add to result array
			$result[$key] = $data;
		}

		return $result;
	}
}

function wppa_shortpath( $path ) {

	$result = str_replace( WPPA_ABSPATH, '.../', $path );
	return $result;
}

function wppa_filetime( $path, $log = true ) {

	clearstatcache();
	if ( ! wppa_is_path_safe( $path ) ) {
		if ( $log ) wppa_log( 'Err', 'Unsafe path detected in wppa_filetime(): ' . wppa_shortpath( $path ), true );
		return false;
	}

	return filemtime( $path );
}