Server IP : 184.154.167.98 / Your IP : 3.22.75.92 Web Server : Apache System : Linux pink.dnsnetservice.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 User : puertode ( 1767) PHP Version : 7.2.34 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /home/puertode/www/mesa/include/ |
Upload File : |
<?php /********************************************************************* class.file.php Peter Rotich <peter@osticket.com> Copyright (c) 2006-2013 osTicket http://www.osticket.com Released under the GNU General Public License WITHOUT ANY WARRANTY. See LICENSE.TXT for details. vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ require_once(INCLUDE_DIR.'class.signal.php'); require_once(INCLUDE_DIR.'class.error.php'); /** * FileObject Interface * * Methods File Objects should support **/ interface FileObjectInterface { function getUId(); function getKey(); function getName(); function getData(); function getMimeType(); } /** * Represents a file stored in a storage backend. It is generally attached * to something; however company logos, login page backdrops, and other * items are also stored in the database for various purposes. * * FileType-Definitions: * The `ft` field is used to represent the type or purpose of the file * with respect to the system. These are the defined file types (placed * here as the definitions are not needed in code). * * - 'T' => Attachments * - 'L' => Logo * - 'B' => Backdrop */ class AttachmentFile extends VerySimpleModel implements FileObjectInterface { static $meta = array( 'table' => FILE_TABLE, 'pk' => array('id'), 'joins' => array( 'attachments' => array( 'reverse' => 'Attachment.file' ), ), ); static $keyCache = array(); function __onload() { // Cache for lookup in the ::lookupByHash method below static::$keyCache[$this->key] = $this; } function getHashtable() { return $this->ht; } function getInfo() { return $this->getHashtable(); } function getNumEntries() { return $this->attachments->count(); } function isCanned() { return $this->getNumEntries(); } function isInUse() { return $this->getNumEntries(); } function getId() { return $this->id; } function getUId() { return $this->getId(); } function getType() { return $this->type; } function getMimeType() { return $this->getType(); } function getBackend() { return $this->bk; } function getSize() { return $this->size; } function getName() { return $this->name; } function getKey() { return $this->key; } function getAttrs() { return $this->attrs; } function getSignature($cascade=false) { $sig = $this->signature; if (!$sig && $cascade) return $this->getKey(); return $sig; } function lastModified() { return $this->created; } function open() { return FileStorageBackend::getInstance($this); } function sendData($redirect=true, $ttl=false, $disposition='inline') { $bk = $this->open(); if ($redirect && $bk->sendRedirectUrl($disposition, $ttl)) return; @ini_set('zlib.output_compression', 'Off'); try { $bk->passthru(); } catch (IOException $ex) { Http::response(404, 'File not found'); } } function getData() { # XXX: This is horrible, and is subject to php's memory # restrictions, etc. Don't use this function! ob_start(); try { $this->sendData(false); } catch (IOException $ex) { Http::response(404, 'File not found'); } $data = &ob_get_contents(); ob_end_clean(); return $data; } function delete() { if (!parent::delete()) return false; if ($bk = $this->open()) $bk->unlink(); return true; } function makeCacheable($ttl=86400) { Http::cacheable($this->getSignature(true), $this->lastModified(), $ttl); } function display($scale=false, $ttl=86400) { $this->makeCacheable($ttl); if ($scale && extension_loaded('gd') && ($image = imagecreatefromstring($this->getData()))) { $width = imagesx($image); if ($scale <= $width) { $height = imagesy($image); if ($width > $height) { $heightp = $height * (int)$scale / $width; $widthp = $scale; } else { $widthp = $width * (int)$scale / $height; $heightp = $scale; } $thumb = imagecreatetruecolor($widthp, $heightp); $white = imagecolorallocate($thumb, 255,255,255); imagefill($thumb, 0, 0, $white); imagecopyresized($thumb, $image, 0, 0, 0, 0, $widthp, $heightp, $width, $height); header('Content-Type: image/png'); imagepng($thumb); return; } } header('Content-Type: '.($this->getType()?$this->getType():'application/octet-stream')); header('Content-Length: '.$this->getSize()); header("Content-Security-Policy: default-src 'self'"); $this->sendData(); exit(); } function getDownloadUrl($options=array()) { // Add attachment ref id if object type is set if (isset($options['type']) && !isset($options['id']) && ($a=$this->attachments->findFirst(array( 'type' => $options['type'])))) $options['id'] = $a->getId(); return static::generateDownloadUrl($this->getId(), strtolower($this->getKey()), $this->getSignature(), $options); } // Generates full download URL for external sources. // e.g. https://domain.tld/file.php?args=123 function getExternalDownloadUrl($options=array()) { global $cfg; $download = $this->getDownloadUrl($options); // Separate URL handle and args list($handle, $args) = explode('file.php?', $download); return (string) rtrim($cfg->getBaseUrl(), '/').'/file.php?'.$args; } static function generateDownloadUrl($id, $key, $hash, $options = array()) { // Expire at the nearest midnight, allow at least12 hrs access $minage = @$options['minage'] ?: 43200; $gmnow = Misc::gmtime() + $options['minage']; $expires = $gmnow + 86400 - ($gmnow % 86400); // Generate a signature based on secret content $signature = static::_genUrlSignature($id, $key, $hash, $expires); // Handler / base url $handler = @$options['handler'] ?: ROOT_PATH . 'file.php'; // Return sanitized query string $args = array( 'key' => $key, 'expires' => $expires, 'signature' => $signature, ); if (isset($options['disposition'])) $args['disposition'] = $options['disposition']; if (isset($options['id'])) $args['id'] = $options['id']; return sprintf('%s?%s', $handler, http_build_query($args)); } function verifySignature($signature, $expires) { $gmnow = Misc::gmtime(); if ($expires < $gmnow) return false; $check = static::_genUrlSignature($this->getId(), $this->getKey(), $this->getSignature(), $expires); return $signature == $check; } static function _genUrlSignature($id, $key, $signature, $expires) { $pieces = array( 'Host='.$_SERVER['HTTP_HOST'], 'Path='.ROOT_PATH, 'Id='.$id, 'Key='.strtolower($key), 'Hash='.$signature, 'Expires='.$expires, ); return hash_hmac('sha1', implode("\n", $pieces), SECRET_SALT); } function download($name=false, $disposition=false, $expires=false) { $thisstaff = StaffAuthenticationBackend::getUser(); $inline = ($thisstaff ? ($thisstaff->getImageAttachmentView() === 'inline') : false); $disposition = ((($disposition && strcasecmp($disposition, 'inline') == 0) || $inline) && strpos($this->getType(), 'image/') !== false) ? 'inline' : 'attachment'; $ttl = ($expires) ? $expires - Misc::gmtime() : false; $bk = $this->open(); if ($bk->sendRedirectUrl($disposition, $ttl)) return; $this->makeCacheable($ttl); $type = $this->getType() ?: 'application/octet-stream'; Http::download($name ?: $this->getName(), $type, null, $disposition); header('Content-Length: '.$this->getSize()); $this->sendData(false); exit(); } static function _getKeyAndHash($data=false, $file=false) { if ($file) { $sha1 = base64_encode(sha1_file($data, true)); $md5 = base64_encode(md5_file($data, true)); } else { $sha1 = base64_encode(sha1($data, true)); $md5 = base64_encode(md5($data, true)); } // Use 5 chars from the microtime() prefix and 27 chars from the // sha1 hash. This should make a sufficiently strong unique key for // file content. In the event there is a sha1 collision for data, it // should be unlikely that there will be a collision for the // microtime hash coincidently. Remove =, change + and / to chars // better suited for URLs and filesystem paths $prefix = base64_encode(sha1(microtime(), true)); $key = str_replace( array('=','+','/'), array('','-','_'), substr($prefix, 0, 5) . $sha1); // The hash is a 32-char value where the first half is from the last // 16 chars from the SHA1 hash and the last 16 chars are the last 16 // chars from the MD5 hash. This should provide for better // resiliance against hash collisions and attacks against any one // hash algorithm. Since we're using base64 encoding, with 6-bits // per char, we should have a total hash strength of 192 bits. $hash = str_replace( array('=','+','/'), array('','-','_'), substr($sha1, 0, 16) . substr($md5, 0, 16)); return array($key, $hash); } /* Function assumes the files types have been validated */ static function upload($file, $ft='T', $deduplicate=true) { if(!$file['name'] || $file['error'] || !is_uploaded_file($file['tmp_name'])) return false; list($key, $sig) = self::_getKeyAndHash($file['tmp_name'], true); $info=array('type'=>$file['type'], 'filetype'=>$ft, 'size'=>$file['size'], 'name'=>$file['name'], 'key'=>$key, 'signature'=>$sig, 'tmp_name'=>$file['tmp_name'], ); return static::create($info, $ft, $deduplicate); } static function uploadBackdrop(array $file, &$error) { if (extension_loaded('gd')) { $source_path = $file['tmp_name']; list($source_width, $source_height, $source_type) = getimagesize($source_path); switch ($source_type) { case IMAGETYPE_GIF: case IMAGETYPE_JPEG: case IMAGETYPE_PNG: break; default: $error = __('Invalid image file type'); return false; } } return self::upload($file, 'B', false); } static function uploadLogo($file, &$error, $aspect_ratio=2) { /* Borrowed in part from * http://salman-w.blogspot.com/2009/04/crop-to-fit-image-using-aspphp.html */ if (extension_loaded('gd')) { $source_path = $file['tmp_name']; list($source_width, $source_height, $source_type) = getimagesize($source_path); switch ($source_type) { case IMAGETYPE_GIF: case IMAGETYPE_JPEG: case IMAGETYPE_PNG: break; default: $error = __('Invalid image file type'); return false; } $source_aspect_ratio = $source_width / $source_height; if ($source_aspect_ratio < $aspect_ratio) { $error = __('Image is too square. Upload a wider image'); return false; } } return self::upload($file, 'L', false); } static function create(&$file, $ft='T', $deduplicate=true) { if (isset($file['encoding'])) { switch ($file['encoding']) { case 'base64': $file['data'] = base64_decode($file['data']); } } if (!isset($file['data']) && isset($file['data_cbk']) && is_callable($file['data_cbk'])) { // Allow a callback function to delay or avoid reading or // fetching ihe file contents $file['data'] = $file['data_cbk'](); } if (isset($file['data'])) { list($key, $file['signature']) = self::_getKeyAndHash($file['data']); if (!$file['key']) $file['key'] = $key; } if (isset($file['size']) && $file['size'] > 0) { // Check and see if the file is already on record $existing = static::objects()->filter(array( 'signature' => $file['signature'], 'size' => $file['size'] ))->first(); // If the record exists in the database already, a file with // the same hash and size is already on file -- just return // the file if ($deduplicate && $existing) { $file['key'] = $existing->key; return $existing; } } elseif (!isset($file['data'])) { // Unable to determine the file's size return false; } if (!$file['type'] && extension_loaded('fileinfo')) { $finfo = new finfo(FILEINFO_MIME_TYPE); if ($file['data']) $type = $finfo->buffer($file['data']); elseif ($file['tmp_name']) $type = $finfo->file($file['tmp_name']); if ($type) $file['type'] = $type; } if (!$file['type']) $file['type'] = 'application/octet-stream'; $f = new static(array( 'type' => strtolower($file['type']), 'name' => $file['name'], 'key' => $file['key'], 'ft' => $ft ?: 'T', 'signature' => $file['signature'], 'created' => SqlFunction::NOW(), )); if (isset($file['size'])) $f->size = $file['size']; if (!$f->save()) return false; // Note that this is preferred over $f->open() because the file does // not have a valid backend configured yet. ::getBackendForFile() // will consider the system configuration for storing the file $bks = array(self::getBackendForFile($f)); if (!$bks[0]->getBkChar() !== 'D') $bks[] = new AttachmentChunkedData($f); // Consider the selected backen first and then save to database // otherwise. $succeeded = false; foreach ($bks as $bk) { try { if (isset($file['tmp_name'])) { if ($bk->upload($file['tmp_name'])) { $succeeded = true; break; } } elseif ($bk->write($file['data']) && $bk->flush()) { $succeeded = true; break; } } catch (Throwable $t) { // Try next backend // Backends can throw an exception or error. // TODO: Log any exceptions and errors for debugging // purposes. } // Fallthrough to default backend if different? } if (!$succeeded) { // Unable to save data (weird) return false; } $f->bk = $bk->getBkChar(); $f->attrs = $bk->getAttrs() ?: NULL; if (!isset($file['size'])) { if ($size = $bk->getSize()) $f->size = $size; // Prefer mb_strlen, because mbstring.func_overload will // automatically prefer it if configured. elseif (extension_loaded('mbstring')) $f->size = mb_strlen($file['data'], '8bit'); // bootstrap.php include a compat version of mb_strlen else $f->size = strlen($file['data']); } $f->save(); return $f; } static function __create($file, &$errors) { return static::create($file); } /** * Migrate this file from the current backend to the backend specified. * * Parameters: * $bk - (string) type char of the target storage backend. Use * AttachmentStorageBackend::allRegistered() to get a list of type * chars and associated class names * * Returns: * True if the migration was successful and false otherwise. */ function migrate($bk) { // Copy the file to the new backend and hash the contents $target = FileStorageBackend::lookup($bk, $this); $source = $this->open(); // Initialize hashing algorithm to verify uploaded contents $algos = $target->getNativeHashAlgos(); $common_algo = 'sha1'; if ($algos && is_array($algos)) { $supported = hash_algos(); foreach ($algos as $a) { if (in_array(strtolower($a), $supported)) { $common_algo = strtolower($a); break; } } } $before = hash_init($common_algo); // TODO: Make this resumable so that if the file cannot be migrated // in the max_execution_time, the migration can be continued // the next time the cron runs try { while ($block = $source->read($target->getBlockSize())) { hash_update($before, $block); $target->write($block); } $target->flush(); } catch (Exception $e) { // Migration failed return false; } // Ask the backend to generate its own hash if at all possible if (!($target_hash = $target->getHashDigest($common_algo))) { $after = hash_init($common_algo); // Verify that the hash of the target file matches the hash of // the source file $target = FileStorageBackend::lookup($bk, $this); while ($block = $target->read()) hash_update($after, $block); $target_hash = hash_final($after); } if (hash_final($before) != $target_hash) { $target->unlink(); return false; } $this->bk = $target->getBkChar(); $this->attrs = $target->getAttrs() ?: NULL; if (!$this->save()) return false; return $source->unlink(); } /** * Considers the system's configuration for file storage selection based * on the file information and purpose (FAQ attachment, image, etc). * * Parameters: * $file - (hasharray) file information which would be passed to * ::save() for instance. * * Returns: * Instance<FileStorageBackend> backend selected based on the file * received. */ static function getBackendForFile($file) { global $cfg; $char = null; if ($cfg) { $char = $cfg->getDefaultStorageBackendChar(); } try { return FileStorageBackend::lookup($char ?: 'D', $file); } catch (Exception $x) { return new AttachmentChunkedData($file); } } static function lookupByHash($hash) { if (isset(static::$keyCache[$hash])) return static::$keyCache[$hash]; // Cache a negative lookup if no such file exists try { return parent::lookup(array('key' => $hash)); } catch (ObjectNotUnique $e) { // TODO: Figure out why key collission might be happening AND // make key (hash) unique field in the file table as a // protection measure. For now we're returning null to avoid possible wrong file // being displayed. return null; } } static function lookup($id) { return is_string($id) ? static::lookupByHash($id) : parent::lookup($id); } /* Method formats http based $_FILE uploads - plus basic validation. */ static function format($files) { global $ost; if(!$files || !is_array($files)) return null; //Reformat $_FILE for the sane. $attachments = array(); foreach($files as $k => $a) { if(is_array($a)) foreach($a as $i => $v) $attachments[$i][$k] = $v; } //Basic validation. foreach($attachments as $i => &$file) { $file['name'] = Format::sanitize($file['name']); //skip no file upload "error" - why PHP calls it an error is beyond me. if($file['error'] && $file['error']==UPLOAD_ERR_NO_FILE) { unset($attachments[$i]); continue; } if($file['error']) //PHP defined error! $file['error'] = 'File upload error #'.$file['error']; elseif(!$file['tmp_name'] || !is_uploaded_file($file['tmp_name'])) $file['error'] = 'Invalid or bad upload POST'; } unset($file); return array_filter($attachments); } /** * Removes files and associated meta-data for files which no ticket, * canned-response, or faq point to any more. */ static function deleteOrphans() { // XXX: Allow plugins to define filetypes which do not represent // files attached to tickets or other things in the attachment // table and are not logos $files = static::objects() ->filter(array( 'attachments__object_id__isnull' => true, 'ft' => 'T', 'created__lt' => SqlFunction::NOW()->minus(SqlInterval::DAY(1)), )); foreach ($files as $f) { if (!$f->delete()) break; } return true; } static function allLogos() { return static::objects() ->filter(array('ft' => 'L')) ->order_by('created'); } static function allBackdrops() { return static::objects() ->filter(array('ft' => 'B')) ->order_by('created'); } } class FileStorageBackend { var $meta; static $desc = false; static $registry; static $blocksize = 131072; static $private = false; /** * All storage backends should call this function during the request * bootstrap phase. */ static function register($typechar, $class) { self::$registry[$typechar] = $class; } static function allRegistered($private=false) { $R = self::$registry; if (!$private) { foreach ($R as $i=>$bk) { if ($bk::$private) unset($R[$i]); } } return $R; } /** * Retrieves the type char registered for this storage backend's class. * Null is returned if the backend is not properly registered. */ function getBkChar() { foreach (self::$registry as $tc=>$class) if ($this instanceof $class) return $tc; } static function isRegistered($type) { return isset(self::$registry[$type]); } static function lookup($type, $file=null) { if (!isset(self::$registry[$type])) throw new Exception("No such backend registered"); $class = self::$registry[$type]; return new $class($file); } static function getInstance($file) { if (!isset(self::$registry[$file->getBackend()])) throw new Exception("No such backend registered"); $class = self::$registry[$file->getBackend()]; return new $class($file); } /** * Returns the optimal block size for the backend. When migrating, this * size blocks would be best for sending to the ::write() method */ function getBlockSize() { return static::$blocksize; } /** * Create an instance of the storage backend linking the related file. * Information about the file metadata is accessible via the received * filed object. */ function __construct($meta) { $this->meta = $meta; } /** * Commit file to the storage backend. This method is used if the * backend cannot support writing a file directly. Otherwise, the * ::upload($file) method is preferred. * * Parameters: * $data - (string|binary) file contents to be written to the backend */ function write($data) { return false; } /** * Called after all the blocks are sent to the ::write() method. This * method should return boolean FALSE if flushing the data was * somehow inhibited. */ function flush() { return true; } /** * Upload a file to the backend. This method is preferred over ::write() * for files which are uploaded or are otherwise available out of * memory. The backend is encouraged to avoid reading the entire * contents into memory. */ function upload($filepath) { return $this->write(file_get_contents($filepath)); } /** * Returns data from the backend, optionally returning only the number * of bytes indicated at the specified offset. If the data is available * in chunks, one chunk may be returned at a time. The backend should * return boolean false when no more chunks are available. */ function read($amount=0, $offset=0) { return false; } /** * Convenience method to send all the file to standard output */ function passthru() { while ($block = $this->read()) echo $block; } /** * If the data is not stored or not available locally, a redirect * response can be sent to the user agent indicating the actual HTTP * location of the data. * * If the data is available locally, this method should return boolean * false to indicate that the read() method should be used to retrieve * the data and broker it to the user agent. */ function sendRedirectUrl($disposition='inline', $ttl=false) { return false; } /** * Requests the backend to remove the file contents. */ function unlink() { return false; } /** * Fetches a list of hash algorithms that are supported transparently * through the ::write() and ::upload() methods. After writing or * uploading file content, the ::getHashDigest($algo) method can be * called to get a hash of the remote content without fetching the * entire data stream to verify the content locally. */ function getNativeHashAlgos() { return array(); } /** * Returns a hash of the content calculated remotely by the storage * backend. If this method fails, the hash chould be calculated by * downloading the content and hashing locally */ function getHashDigest($algo) { return false; } /** * getSize * * Retrieves the size of the contents written or available to be read. * The backend should optimize this process if possible by keeping track * of the bytes written in a way apart from `strlen`. This value will be * used instead of inspecting the contents using `strlen`. */ function getSize() { return false; } /** * getAttrs * * Get backend storage attributes. * */ function getAttrs() { return false; } } /** * Attachments stored in the database are cut into 500kB chunks and stored * in the FILE_CHUNK_TABLE to overcome the max_allowed_packet limitation of * LOB fields in the MySQL database */ define('CHUNK_SIZE', 500*1024); # Beware if you change this... class AttachmentFileChunk extends VerySimpleModel { static $meta = array( 'table' => FILE_CHUNK_TABLE, 'pk' => array('file_id', 'chunk_id'), 'joins' => array( 'file' => array( 'constraint' => array('file_id' => 'AttachmentFile.id'), ), ), ); } class AttachmentChunkedData extends FileStorageBackend { static $desc = /* @trans */ "In the database"; static $blocksize = CHUNK_SIZE; function __construct($file) { $this->file = $file; $this->_chunk = 0; $this->_buffer = false; $this->eof = false; } function getSize() { $row = AttachmentFileChunk::objects() ->filter(array('file' => $this->file)) ->aggregate(array('length' => SqlAggregate::SUM(SqlFunction::LENGTH(new SqlField('filedata'))))) ->one(); return $row['length']; } function read($amount=CHUNK_SIZE, $offset=0) { # Read requested length of data from attachment chunks if ($this->eof) return false; while (strlen($this->_buffer) < $amount + $offset) { try { list($buf) = AttachmentFileChunk::objects() ->filter(array('file' => $this->file, 'chunk_id' => $this->_chunk++)) ->values_flat('filedata') ->one(); } catch (DoesNotExist $e) { $this->eof = true; break; } $this->_buffer .= $buf; } $chunk = substr($this->_buffer, $offset, $amount); $this->_buffer = substr($this->_buffer, $offset + $amount); return $chunk; } function write($what, $chunk_size=CHUNK_SIZE) { $offset=0; while ($block = substr($what, $offset, $chunk_size)) { // Chunks are considered immutable. Importing chunks should // forceable remove the contents of a file before write()ing new // chunks. Therefore, inserts should be safe. $chunk = new AttachmentFileChunk(array( 'file' => $this->file, 'chunk_id' => $this->_chunk++, 'filedata' => $block )); if (!$chunk->save()) return false; $offset += strlen($block); } return $this->_chunk; } function unlink() { return AttachmentFileChunk::objects() ->filter(array('file' => $this->file)) ->delete(); } } FileStorageBackend::register('D', 'AttachmentChunkedData'); /** * This class provides an interface for files attached on the filesystem in * versions previous to v1.7. The upgrader will keep the attachments on the * disk where they were and write the path into the `attrs` field of the * %file table. This module will continue to serve those files until they * are migrated with the `file` cli app */ class OneSixAttachments extends FileStorageBackend { static $desc = "upload_dir folder (from osTicket v1.6)"; static $private = true; function read($bytes=32768, $offset=false) { $filename = $this->meta->attrs; if (!$this->fp) $this->fp = @fopen($filename, 'rb'); if (!$this->fp) throw new IOException($filename.': Unable to open for reading'); if ($offset) fseek($this->fp, $offset); if (($status = @fread($this->fp, $bytes)) === false) throw new IOException($filename.': Unable to read from file'); return $status; } function passthru() { $filename = $this->meta->attrs; if (($status = @readfile($filename)) === false) throw new IOException($filename.': Unable to read from file'); return $status; } function write($data) { throw new IOException('This backend does not support new files'); } function upload($filepath) { throw new IOException('This backend does not support new files'); } function unlink() { $filename = $this->meta->attrs; if (!@unlink($filename)) throw new IOException($filename.': Unable to delete file'); // Drop usage of the `attrs` field $this->meta->attrs = null; $this->meta->save(); return true; } } FileStorageBackend::register('6', 'OneSixAttachments'); // FileObject - wrapper for SplFileObject class class FileObject extends SplFileObject implements FileObjectInterface { protected $_key; protected $_sig; protected $_filename; function __construct($file, $mode='r') { parent::__construct($file, $mode); } function getUId() { return $this->getKey(); } function getKey() { if (!isset($this->_key)) list($this->_key, $this->_sig) = AttachmentFile::_getKeyAndHash( $this->getContents()); return $this->_key; } /* This allows us to set REAL file name as opposed to basename of the * FS file in question */ function setFilename($filename) { $this->_filename = $filename; } function getFilename() { return $this->_filename ?: parent::getFilename(); } function getName() { return $this->getFilename(); } /* * Set mime type - well formated mime is expected. */ function setMimeType($type) { $this->_mimetype = $type; } function getMimeType() { if (!isset($this->_mimetype)) $this->_mimetype = self::mime_type($this->getRealPath()); return $this->_mimetype; } function getContents() { $this->fseek(0); return $this->fread($this->getSize()); } /* * XXX: Needed for mailer attachments interface */ function getData() { return $this->getContents(); } /* * Given a filepath - auto detect the mime type * */ static function mime_type($filepath) { // Try to to auto-detect mime type $type = null; if (function_exists('finfo_open')) { $finfo = finfo_open(FILEINFO_MIME_TYPE); $type = finfo_file($finfo, $filepath); finfo_close($finfo); } return $type ?: mime_content_type($filepath); } /* * Compare mime type of file content to a given mime */ static function mimecmp($filepath, $mime) { return strcasecmp(self::mime_type($filepath), $mime) !== 0; } } ?>