Version 4.1.19

This commit is contained in:
Neo committed 2025-12-19 05:38:56 -08:00
1 parent 28bd025b35
commit 2bd5025018
2674 files changed
+233518 -77766

No files matched your search

+102 -46
View File
@@ -2,9 +2,9 @@
/**
* @brief File Class
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 19 Feb 2013
* @version SVN_VERSION_NUMBER
*/
@@ -52,12 +52,13 @@ abstract class _File
* @brief When moving files, do not delete the original immediately but log for later deletion
*/
const MOVE_DELETE_NOW = 2;
/**
* Get class
*
* @param string|int $storageExtension Storage extension or configuration ID
* @param string|\IPS\Http\Url|NULL $url URL to validate storage configs with
* @throws \RuntimeException
* @return \IPS\File
*/
public static function getClass( $storageExtension, $url=NULL )
@@ -133,7 +134,7 @@ abstract class _File
/**
* Load storage configurations
*
* @return void
* @return array
*/
public static function getConfigurations()
{
@@ -149,6 +150,8 @@ abstract class _File
\IPS\Data\Store::i()->storageConfigurations = static::$storageConfigurations;
}
}
return static::$storageConfigurations;
}
/**
@@ -181,10 +184,10 @@ abstract class _File
}
$class->storageExtension = $storageExtension;
/* Image-specific stuff */
$ext = mb_substr( $filename, ( mb_strrpos( $filename, '.' ) + 1 ) );
if( in_array( $ext, \IPS\Image::$imageExtensions ) and ( !$isSafe or \IPS\Image::exifSupported() ) )
if( in_array( mb_strtolower( $ext ), \IPS\Image::$imageExtensions ) and ( !$isSafe or \IPS\Image::exifSupported() ) )
{
/* Get contents */
if( $data === NULL AND $filePath !== NULL )
@@ -199,7 +202,7 @@ abstract class _File
{
/* Try to just strip the EXIF */
$image = \IPS\Image::create( $data );
$image->resize( 100, 100 );
$image->resize( $image->width, $image->height );
$data = (string) $image;
/* And if it still fails, throw an error */
@@ -295,7 +298,7 @@ abstract class _File
if( $callback !== NULL )
{
$contents = file_get_contents( $file['tmp_name'] );
$contents = call_user_func( $callback, $contents );
$contents = call_user_func( $callback, $contents, $file['name'] );
$fileObjects[] = static::create( $storageLocation, $file['name'], $contents, $container, FALSE, NULL, $obscure );
}
@@ -465,9 +468,16 @@ abstract class _File
* @param string $storageExtension Storage extension
* @param string|\IPS\Http|Url $url URL to file
* @return \IPS\File
* @throws \OutOfRangeException
*/
public static function get( $storageExtension, $url )
{
/* Don't allow something/../../../../etc/passwd */
if( mb_strpos( rawurldecode( $url ), '../' ) !== FALSE )
{
throw new \OutOfRangeException( 'INVALID_PATH' );
}
$class = static::getClass( $storageExtension, $url );
$class->storageExtension = $storageExtension;
$class->url = $url;
@@ -545,8 +555,6 @@ abstract class _File
foreach( static::$storageConfigurations as $id => $data )
{
$secondTest = NULL;
$thirdTest = NULL;
$class = static::getClass( $data['id'] );
$urlRelative = preg_replace( '#^http(s)?://#', '//', $url );
@@ -654,8 +662,8 @@ abstract class _File
else
{
$pointer = 0;
while( $pointer < \strlen( $contents ) )
$contentsLength = \strlen( $contents );
while( $pointer < $contentsLength )
{
print \substr( $contents, $pointer, $throttle );
$pointer += $throttle;
@@ -685,7 +693,7 @@ abstract class _File
ob_end_clean();
}
if( $throttle === NULL AND !($start AND $length) AND function_exists('readfile') )
if( $throttle === NULL AND $start === NULL AND function_exists('readfile') )
{
readfile( $file );
}
@@ -695,7 +703,7 @@ abstract class _File
{
$read = ( $throttle !== NULL ) ? $throttle : 4096;
if( $start AND $length )
if( $start !== NULL AND $length )
{
fseek( $fh, $start );
@@ -830,8 +838,13 @@ abstract class _File
{
$filename = mb_substr( $filename, 0, ( mb_strrpos( $filename, '.' ) ) ) . '_' . $ext;
}
while( preg_match( '#\.(?!(' . implode( '|', static::$safeFileExtensions ) . '))([a-z0-9]{2,4})(\.|$)#i', $filename, $matches ) )
{
$filename = str_replace( '.' . $matches[2], '_' . $matches[2], $filename );
}
return str_replace( array( ' ', '#' ), '_', $filename ) . '.' . md5( uniqid() ) . ( ( $safe ) ? '.' . $ext : '' );
return str_replace( array( ' ', '#' ), '_', $filename ) . '.' . md5( uniqid( \IPS\SUITE_UNIQUE_KEY ) . mt_rand() ) . ( ( $safe ) ? '.' . $ext : '' );
}
/**
@@ -843,7 +856,6 @@ abstract class _File
protected function unObscureFilename( $filename )
{
$ext = mb_substr( $filename, ( mb_strrpos( $filename, '.' ) + 1 ) );
if ( mb_strlen( $ext ) == 32 )
{
preg_match( '#(.*)_([A-Z0-9\-]{2,10})\.([A-Z0-9]{32})#i', $filename, $matches );
@@ -852,9 +864,19 @@ abstract class _File
{
return $matches[1] . '.' . $matches[2];
}
else
{
/* Fix foo.pdf.hash */
preg_match( '#(.*)\.([A-Z0-9\-]{2,10})\.([A-Z0-9]{32})#i', $filename, $matches );
if ( isset( $matches[1] ) and isset( $matches[2] ) )
{
return $matches[1] . '.' . $matches[2];
}
}
}
if( preg_match( "/([a-zA-Z0-9]{32})\." . preg_quote( $ext ) . "$/", $filename ) )
if( preg_match( "/\.([a-zA-Z0-9]{32})\." . preg_quote( $ext ) . "$/", $filename ) )
{
return mb_substr( $filename, 0, mb_strlen( $filename ) - ( 34 + mb_strlen( $ext ) ) );
}
@@ -881,7 +903,7 @@ abstract class _File
$exploded = explode( '/', $url );
$this->filename = array_pop( $exploded );
$this->originalFilename = $this->unObscureFilename( $this->filename );
$this->url = new \IPS\Http\Url( $this->fullyQualifiedUrl( $this->url ) );
$this->url = \IPS\Http\Url::createFromString( $this->fullyQualifiedUrl( $this->url ), FALSE, TRUE );
/* Upon upgrade we don't rename every file, so we need to account for this */
if( mb_strpos( $this->originalFilename, '.' ) === FALSE )
@@ -933,7 +955,7 @@ abstract class _File
*/
public function __toString()
{
return $this->container . '/' . $this->filename;
return $this->container ? ( $this->container . '/' . $this->filename ) : $this->filename;
}
/**
@@ -975,12 +997,22 @@ abstract class _File
{
if ( mb_substr( $url, 0, 4 ) !== 'http' AND mb_substr( $url, 0, 2 ) !== '//' )
{
$url = $this->baseUrl() . '/' . $url;
$url = $this->baseUrl() . '/' . $this->encodeFileUrl( $url );
}
return $url;
}
/**
* Encode the file name for the fully qualified URL
*
* @return string
*/
public function encodeFileUrl( $filename )
{
return \IPS\Http\Url::encodeComponent( \IPS\Http\Url::COMPONENT_PATH, $filename );
}
/**
* Move file to a different storage location
*
@@ -1094,12 +1126,15 @@ abstract class _File
* Make into an attachment
*
* @param string $postKey Post key
* @param \IPS\Member|NULL Member who uploaded the attachment
* @return array
* @throws \DomainException
*/
public function makeAttachment( $postKey )
public function makeAttachment( $postKey, \IPS\Member $member = NULL )
{
$ext = mb_substr( $this->originalFilename, mb_strrpos( $this->originalFilename, '.' ) + 1 );
$ext = mb_substr( $this->originalFilename, mb_strrpos($this->originalFilename, '.') + 1 );
$memberId = ( $member and $member->member_id ) ? $member->member_id : 0;
$data = array(
'attach_ext' => $ext,
@@ -1112,7 +1147,7 @@ abstract class _File
'attach_hits' => 0,
'attach_date' => time(),
'attach_post_key' => $postKey,
'attach_member_id' => \IPS\Member::loggedIn()->member_id ?: 0,
'attach_member_id' => $memberId,
'attach_filesize' => $this->filesize(),
'attach_img_width' => 0,
'attach_img_height' => 0,
@@ -1184,7 +1219,7 @@ abstract class _File
}
/**
* Claim Attachments
* Claim Attachments and clear autosave content
*
* @param string $autoSaveKey Auto-save key
* @param int|NULL $id1 ID 1
@@ -1208,6 +1243,9 @@ abstract class _File
), array( 'temp=?', md5( $autoSaveKey . $lang->id ) ) );
\IPS\Db::i()->update( 'core_attachments', array( 'attach_post_key' => '' ), array( 'attach_post_key=?', md5( $autoSaveKey . $lang->id . ':' . session_id() ) ) );
\IPS\Request::i()->setClearAutosaveCookie( $autoSaveKey . $lang->id );
}
}
else
@@ -1220,6 +1258,8 @@ abstract class _File
), array( 'temp=?', md5( $autoSaveKey ) ) );
\IPS\Db::i()->update( 'core_attachments', array( 'attach_post_key' => '' ), array( 'attach_post_key=?', md5( $autoSaveKey . ':' . session_id() ) ) );
\IPS\Request::i()->setClearAutosaveCookie( $autoSaveKey );
}
}
@@ -1246,23 +1286,18 @@ abstract class _File
}
}
\IPS\Db::i()->delete( 'core_attachments_map', $where );
/* Cleanup - this is deliberately slightly different to the one in \IPS\Text\Parser as it will only delete attachments which have been "saved" (i.e. are not in the middle of being posted) */
foreach ( \IPS\Db::i()->select( '*', 'core_attachments', array( array( 'attach_id NOT IN(?) AND attach_post_key=?', \IPS\Db::i()->select( 'DISTINCT attachment_id', 'core_attachments_map' ), '' ) ) ) as $attachment )
{
try
{
\IPS\Db::i()->delete( 'core_attachments', array( 'attach_id=?', $attachment['attach_id'] ) );
\IPS\File::get( 'core_Attachment', $attachment['attach_location'] )->delete();
if ( $attachment['attach_thumb_location'] )
{
\IPS\File::get( 'core_Attachment', $attachment['attach_thumb_location'] )->delete();
}
}
catch ( \Exception $e ) { }
}
}
/**
* @brief This can be used to force a thumbnail name
*/
public $thumbnailName = NULL;
/**
* @brief This can be used to force a thumbnail container
*/
public $thumbnailContainer = NULL;
/**
* Make a thumbnail of the file - copies file, resizes and returns new file object
*
@@ -1295,10 +1330,10 @@ abstract class _File
static::$thumbnailDimensions = array( $image->width, $image->height );
/* What are we calling this? */
$thumbnailName = mb_substr( $this->originalFilename, 0, mb_strrpos( $this->originalFilename, '.' ) ) . '.thumb' . mb_substr( $this->originalFilename, mb_strrpos( $this->originalFilename, '.' ) );
$thumbnailName = $this->thumbnailName ?: mb_substr( $this->originalFilename, 0, mb_strrpos( $this->originalFilename, '.' ) ) . '.thumb' . mb_substr( $this->originalFilename, mb_strrpos( $this->originalFilename, '.' ) );
/* Create and return */
return \IPS\File::create( $storageExtension, $thumbnailName, $image );
return \IPS\File::create( $storageExtension, $thumbnailName, $image, $this->thumbnailContainer, FALSE, NULL, $this->thumbnailName ? FALSE : TRUE );
}
/**
@@ -1378,7 +1413,8 @@ abstract class _File
/* We only need to check the first 1kb of the file...some programs will use more, but this is the most common */
$firstBytes = \substr( $data, 0, 1024 );
if( preg_match( '#(<script|<html|<head|<title|<body|<pre|<table|<a\s+href|<img|<plaintext|<cross\-domain\-policy)(\s|=|>)#si', $firstBytes, $matches ) )
/* @see https://mimesniff.spec.whatwg.org/#identifying-a-resource-with-an-unknown-mime-type */
if( preg_match( '#(<\!DOCTYPE\s+HTML|<script|<html|<head|<iframe|<h1|<div|<font|<table|<title|<style|<body|<pre|<table|<br|<a\s+href|<img|<plaintext|<cross\-domain\-policy|<\!\-\-|<\?xml)(\s|=|>)#si', $firstBytes, $matches ) )
{
return TRUE;
}
@@ -1395,9 +1431,9 @@ abstract class _File
{
$extension = mb_strtolower( mb_substr( $filename, mb_strrpos( $filename, '.' ) + 1 ) );
if ( array_key_exists( $extension, self::$mimeTypes ) )
if ( array_key_exists( $extension, static::$mimeTypes ) )
{
return self::$mimeTypes[ $extension ];
return static::$mimeTypes[ $extension ];
}
return 'application/x-unknown'; // This, slightly unusual, type is needed to stop some browsers adding random extensions
@@ -1420,6 +1456,9 @@ abstract class _File
/* Get the last character, which may be 'm' or may be a number */
$last = mb_strtolower( $size[ \strlen( $size ) - 1 ] );
/* Convert $size to a number */
$size = intval( preg_replace( '/[^0-9]/', '', $size ) );
/* Adjust value as necessary - note that we do not break intentionally */
switch( $last )
@@ -1435,6 +1474,23 @@ abstract class _File
return (float) $size;
}
/**
* Get output for API
*
* @return array
* @apiresponse string name The filename
* @apiresponse string url URL to where file is stored
* @apiresponse int size Filesize in bytes
*/
public function apiOutput()
{
return array(
'name' => $this->originalFilename,
'url' => (string) $this->url,
'size' => $this->filesize()
);
}
/* !Mime-Type Map */
/**