]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/upload_file.php
Release 6.5.6
[Github/sugarcrm.git] / include / upload_file.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM Community Edition is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2012 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38 /*********************************************************************************
39
40  * Description:
41  ********************************************************************************/
42 require_once('include/externalAPI/ExternalAPIFactory.php');
43
44 /**
45  * @api
46  * Manage uploaded files
47  */
48 class UploadFile
49 {
50         var $field_name;
51         var $stored_file_name;
52         var $original_file_name;
53         var $temp_file_location;
54         var $use_soap = false;
55         var $file;
56         var $file_ext;
57         protected static $url = "upload/";
58
59         /**
60          * Upload errors
61          * @var array
62          */
63         protected static $filesError = array(
64                         UPLOAD_ERR_OK => 'UPLOAD_ERR_OK - There is no error, the file uploaded with success.',
65                         UPLOAD_ERR_INI_SIZE => 'UPLOAD_ERR_INI_SIZE - The uploaded file exceeds the upload_max_filesize directive in php.ini.',
66                         UPLOAD_ERR_FORM_SIZE => 'UPLOAD_ERR_FORM_SIZE - The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.',
67                         UPLOAD_ERR_PARTIAL => 'UPLOAD_ERR_PARTIAL - The uploaded file was only partially uploaded.',
68                         UPLOAD_ERR_NO_FILE => 'UPLOAD_ERR_NO_FILE - No file was uploaded.',
69                         5 => 'UNKNOWN ERROR',
70                         UPLOAD_ERR_NO_TMP_DIR => 'UPLOAD_ERR_NO_TMP_DIR - Missing a temporary folder.',
71                         UPLOAD_ERR_CANT_WRITE => 'UPLOAD_ERR_CANT_WRITE - Failed to write file to disk.',
72                         UPLOAD_ERR_EXTENSION => 'UPLOAD_ERR_EXTENSION - A PHP extension stopped the file upload.',
73                         );
74
75         /**
76          * Create upload file handler
77          * @param string $field_name Form field name
78          */
79         function UploadFile ($field_name = '')
80         {
81                 // $field_name is the name of your passed file selector field in your form
82                 // i.e., for Emails, it is "email_attachmentX" where X is 0-9
83                 $this->field_name = $field_name;
84         }
85
86         /**
87          * Setup for SOAP upload
88          * @param string $filename Name for the file
89          * @param string $file
90          */
91         function set_for_soap($filename, $file) {
92                 $this->stored_file_name = $filename;
93                 $this->use_soap = true;
94                 $this->file = $file;
95         }
96
97         /**
98          * Get URL for a document
99          * @deprecated
100          * @param string stored_file_name File name in filesystem
101          * @param string bean_id note bean ID
102          * @return string path with file name
103          */
104         public static function get_url($stored_file_name, $bean_id)
105         {
106                 if ( empty($bean_id) && empty($stored_file_name) ) {
107             return self::$url;
108                 }
109
110                 return self::$url . $bean_id;
111         }
112
113         /**
114          * Get URL of the uploaded file related to the document
115          * @param SugarBean $document
116          * @param string $type Type of the document, if different from $document
117          */
118         public static function get_upload_url($document, $type = null)
119         {
120             if(empty($type)) {
121                 $type = $document->module_dir;
122             }
123             return "index.php?entryPoint=download&type=$type&id={$document->id}";
124         }
125
126         /**
127          * Try renaming a file to bean_id name
128          * @param string $filename
129          * @param string $bean_id
130          */
131         protected static function tryRename($filename, $bean_id)
132         {
133             $fullname = "upload://$bean_id.$filename";
134             if(file_exists($fullname)) {
135             if(!rename($fullname,  "upload://$bean_id")) {
136                 $GLOBALS['log']->fatal("unable to rename file: $fullname => $bean_id");
137             }
138                 return true;
139             }
140             return false;
141         }
142
143         /**
144          * builds a URL path for an anchor tag
145          * @param string stored_file_name File name in filesystem
146          * @param string bean_id note bean ID
147          * @return string path with file name
148          */
149         static public function get_file_path($stored_file_name, $bean_id, $skip_rename = false)
150         {
151                 global $locale;
152
153         // if the parameters are empty strings, just return back the upload_dir
154                 if ( empty($bean_id) && empty($stored_file_name) ) {
155             return "upload://";
156                 }
157
158                 if(!$skip_rename) {
159                 self::tryRename(rawurlencode($stored_file_name), $bean_id) ||
160                 self::tryRename(urlencode($stored_file_name), $bean_id) ||
161                 self::tryRename($stored_file_name, $bean_id) ||
162                 self::tryRename($locale->translateCharset( $stored_file_name, 'UTF-8', $locale->getExportCharset()), $bean_id);
163                 }
164
165                 return "upload://$bean_id";
166         }
167
168         /**
169          * duplicates an already uploaded file in the filesystem.
170          * @param string old_id ID of original note
171          * @param string new_id ID of new (copied) note
172          * @param string filename Filename of file (deprecated)
173          */
174         public static function duplicate_file($old_id, $new_id, $file_name)
175         {
176                 global $sugar_config;
177
178                 // current file system (GUID)
179                 $source = "upload://$old_id";
180
181                 if(!file_exists($source)) {
182                         // old-style file system (GUID.filename.extension)
183                         $oldStyleSource = $source.$file_name;
184                         if(file_exists($oldStyleSource)) {
185                                 // change to new style
186                                 if(copy($oldStyleSource, $source)) {
187                                         // delete the old
188                                         if(!unlink($oldStyleSource)) {
189                                                 $GLOBALS['log']->error("upload_file could not unlink [ {$oldStyleSource} ]");
190                                         }
191                                 } else {
192                                         $GLOBALS['log']->error("upload_file could not copy [ {$oldStyleSource} ] to [ {$source} ]");
193                                 }
194                         }
195                 }
196
197                 $destination = "upload://$new_id";
198                 if(!copy($source, $destination)) {
199                         $GLOBALS['log']->error("upload_file could not copy [ {$source} ] to [ {$destination} ]");
200                 }
201         }
202
203         /**
204          * Get upload error from system
205          * @return string upload error
206          */
207         public function get_upload_error()
208         {
209             if(isset($this->field_name) && isset($_FILES[$this->field_name]['error'])) {
210                 return $_FILES[$this->field_name]['error'];
211             }
212             return false;
213         }
214
215         /**
216          * standard PHP file-upload security measures. all variables accessed in a global context
217          * @return bool True on success
218          */
219         public function confirm_upload()
220         {
221                 global $sugar_config;
222
223                 if(empty($this->field_name) || !isset($_FILES[$this->field_name])) {
224                     return false;
225                 }
226
227         //check to see if there are any errors from upload
228                 if($_FILES[$this->field_name]['error'] != UPLOAD_ERR_OK) {
229                     if($_FILES[$this->field_name]['error'] != UPLOAD_ERR_NO_FILE) {
230                 if($_FILES[$this->field_name]['error'] == UPLOAD_ERR_INI_SIZE) {
231                     //log the error, the string produced will read something like:
232                     //ERROR: There was an error during upload. Error code: 1 - UPLOAD_ERR_INI_SIZE - The uploaded file exceeds the upload_max_filesize directive in php.ini. upload_maxsize is 16
233                     $errMess = string_format($GLOBALS['app_strings']['UPLOAD_ERROR_TEXT_SIZEINFO'],array($_FILES['filename_file']['error'], self::$filesError[$_FILES['filename_file']['error']],$sugar_config['upload_maxsize']));
234                     $GLOBALS['log']->fatal($errMess);
235                 }else{
236                     //log the error, the string produced will read something like:
237                     //ERROR: There was an error during upload. Error code: 3 - UPLOAD_ERR_PARTIAL - The uploaded file was only partially uploaded.
238                     $errMess = string_format($GLOBALS['app_strings']['UPLOAD_ERROR_TEXT'],array($_FILES['filename_file']['error'], self::$filesError[$_FILES['filename_file']['error']]));
239                     $GLOBALS['log']->fatal($errMess);
240                 }
241                     }
242                     return false;
243                 }
244
245                 if(!is_uploaded_file($_FILES[$this->field_name]['tmp_name'])) {
246                         return false;
247                 } elseif($_FILES[$this->field_name]['size'] > $sugar_config['upload_maxsize']) {
248                     $GLOBALS['log']->fatal("ERROR: uploaded file was too big: max filesize: {$sugar_config['upload_maxsize']}");
249                         return false;
250                 }
251
252                 if(!UploadStream::writable()) {
253                     $GLOBALS['log']->fatal("ERROR: cannot write to upload directory");
254                         return false;
255                 }
256
257                 $this->mime_type = $this->getMime($_FILES[$this->field_name]);
258                 $this->stored_file_name = $this->create_stored_filename();
259                 $this->temp_file_location = $_FILES[$this->field_name]['tmp_name'];
260
261                 return true;
262         }
263
264         /**
265          * Guess MIME type for file
266          * @param string $filename
267          * @return string MIME type
268          */
269         function getMimeSoap($filename){
270
271                 if( function_exists( 'ext2mime' ) )
272                 {
273                         $mime = ext2mime($filename);
274                 }
275                 else
276                 {
277                         $mime = ' application/octet-stream';
278                 }
279                 return $mime;
280
281         }
282
283         /**
284          * Get MIME type for uploaded file
285          * @param array $_FILES_element $_FILES element required
286          * @return string MIME type
287          */
288         function getMime($_FILES_element)
289         {
290                 $filename = $_FILES_element['name'];
291         $file_ext = pathinfo($filename, PATHINFO_EXTENSION);
292
293         //If no file extension is available and the mime is octet-stream try to determine the mime type.
294         $recheckMime = empty($file_ext) && !empty($_FILES_element['type']) && ($_FILES_element['type']  == 'application/octet-stream');
295
296                 if (!empty($_FILES_element['type']) && !$recheckMime) {
297                         $mime = $_FILES_element['type'];
298                 } elseif( function_exists( 'mime_content_type' ) ) {
299                         $mime = mime_content_type( $_FILES_element['tmp_name'] );
300                 } elseif( function_exists( 'ext2mime' ) ) {
301                         $mime = ext2mime( $_FILES_element['name'] );
302                 } else {
303                         $mime = 'application/octet-stream';
304                 }
305                 return $mime;
306         }
307
308         /**
309          * gets note's filename
310          * @return string
311          */
312         function get_stored_file_name()
313         {
314                 return $this->stored_file_name;
315         }
316
317         /**
318          * creates a file's name for preparation for saving
319          * @return string
320          */
321         function create_stored_filename()
322         {
323                 global $sugar_config;
324
325                 if(!$this->use_soap) {
326                         $stored_file_name = $_FILES[$this->field_name]['name'];
327                         $this->original_file_name = $stored_file_name;
328
329                         /**
330                          * cn: bug 8056 - windows filesystems and IIS do not like utf8.  we are forced to urlencode() to ensure that
331                          * the file is linkable from the browser.  this will stay broken until we move to a db-storage system
332                          */
333                         if(is_windows()) {
334                                 // create a non UTF-8 name encoding
335                                 // 176 + 36 char guid = windows' maximum filename length
336                                 $end = (strlen($stored_file_name) > 176) ? 176 : strlen($stored_file_name);
337                                 $stored_file_name = substr($stored_file_name, 0, $end);
338                                 $this->original_file_name = $_FILES[$this->field_name]['name'];
339                         }
340                     $stored_file_name = str_replace("\\", "", $stored_file_name);
341                 } else {
342                         $stored_file_name = $this->stored_file_name;
343                         $this->original_file_name = $stored_file_name;
344                 }
345
346                 $this->file_ext = pathinfo($stored_file_name, PATHINFO_EXTENSION);
347         // cn: bug 6347 - fix file extension detection
348         foreach($sugar_config['upload_badext'] as $badExt) {
349             if(strtolower($this->file_ext) == strtolower($badExt)) {
350                 $stored_file_name .= ".txt";
351                 $this->file_ext="txt";
352                 break; // no need to look for more
353             }
354         }
355                 return $stored_file_name;
356         }
357
358         /**
359          * moves uploaded temp file to permanent save location
360          * @param string bean_id ID of parent bean
361          * @return bool True on success
362          */
363         function final_move($bean_id)
364         {
365             $destination = $bean_id;
366             if(substr($destination, 0, 9) != "upload://") {
367             $destination = "upload://$bean_id";
368             }
369         if($this->use_soap) {
370                 if(!file_put_contents($destination, $this->file)){
371                     $GLOBALS['log']->fatal("ERROR: can't save file to $destination");
372                 return false;
373                 }
374                 } else {
375                         if(!UploadStream::move_uploaded_file($_FILES[$this->field_name]['tmp_name'], $destination)) {
376                             $GLOBALS['log']->fatal("ERROR: can't move_uploaded_file to $destination. You should try making the directory writable by the webserver");
377                 return false;
378                         }
379                 }
380                 return true;
381         }
382
383         /**
384          * Upload document to external service
385          * @param SugarBean $bean Related bean
386          * @param string $bean_id
387          * @param string $doc_type
388          * @param string $file_name
389          * @param string $mime_type
390          */
391         function upload_doc($bean, $bean_id, $doc_type, $file_name, $mime_type)
392         {
393                 if(!empty($doc_type)&&$doc_type!='Sugar') {
394                         global $sugar_config;
395                 $destination = $this->get_upload_path($bean_id);
396                 sugar_rename($destination, str_replace($bean_id, $bean_id.'_'.$file_name, $destination));
397                 $new_destination = $this->get_upload_path($bean_id.'_'.$file_name);
398
399                     try{
400                 $this->api = ExternalAPIFactory::loadAPI($doc_type);
401
402                 if ( isset($this->api) && $this->api !== false ) {
403                     $result = $this->api->uploadDoc(
404                         $bean,
405                         $new_destination,
406                         $file_name,
407                         $mime_type
408                         );
409                 } else {
410                     $result['success'] = FALSE;
411                     // FIXME: Translate
412                     $GLOBALS['log']->error("Could not load the requested API (".$doc_type.")");
413                     $result['errorMessage'] = 'Could not find a proper API';
414                 }
415             }catch(Exception $e){
416                 $result['success'] = FALSE;
417                 $result['errorMessage'] = $e->getMessage();
418                 $GLOBALS['log']->error("Caught exception: (".$e->getMessage().") ");
419             }
420             if ( !$result['success'] ) {
421                 sugar_rename($new_destination, str_replace($bean_id.'_'.$file_name, $bean_id, $new_destination));
422                 $bean->doc_type = 'Sugar';
423                 // FIXME: Translate
424                 if ( ! is_array($_SESSION['user_error_message']) )
425                     $_SESSION['user_error_message'] = array();
426
427                 $error_message = isset($result['errorMessage']) ? $result['errorMessage'] : $GLOBALS['app_strings']['ERR_EXTERNAL_API_SAVE_FAIL'];
428                 $_SESSION['user_error_message'][] = $error_message;
429
430             }
431             else {
432                 unlink($new_destination);
433             }
434         }
435
436         }
437
438         /**
439          * returns the path with file name to save an uploaded file
440          * @param string bean_id ID of the parent bean
441          * @return string
442          */
443         function get_upload_path($bean_id)
444         {
445                 $file_name = $bean_id;
446
447                 // cn: bug 8056 - mbcs filename in urlencoding > 212 chars in Windows fails
448                 $end = (strlen($file_name) > 212) ? 212 : strlen($file_name);
449                 $ret_file_name = substr($file_name, 0, $end);
450
451                 return "upload://$ret_file_name";
452         }
453
454         /**
455          * deletes a file
456          * @param string bean_id ID of the parent bean
457          * @param string file_name File's name
458          */
459         static public function unlink_file($bean_id,$file_name = '')
460         {
461             if(file_exists("upload://$bean_id$file_name")) {
462             return unlink("upload://$bean_id$file_name");
463             }
464     }
465
466     /**
467      * Get upload file location prefix
468      * @return string prefix
469      */
470     public function get_upload_dir()
471     {
472         return "upload://";
473     }
474
475     /**
476      * Return real FS path of the file
477      * @param string $path
478      */
479     public static function realpath($path)
480     {
481        if(substr($path, 0, 9) == "upload://") {
482            $path = UploadStream::path($path);
483        }
484        $ret = realpath($path);
485        return $ret?$ret:$path;
486     }
487
488     /**
489      * Return path of uploaded file relative to uploads dir
490      * @param string $path
491      */
492     public static function relativeName($path)
493     {
494         if(substr($path, 0, 9) == "upload://") {
495             $path = substr($path, 9);
496         }
497         return $path;
498     }
499 }
500
501 /**
502  * @internal
503  * Upload file stream handler
504  */
505 class UploadStream
506 {
507     const STREAM_NAME = "upload";
508     protected static $upload_dir;
509
510     /**
511      * Method checks Suhosin restrictions to use streams in php
512      *
513      * @static
514      * @return bool is allowed stream or not
515      */
516     public static function getSuhosinStatus()
517     {
518         // looks like suhosin patch doesn't block protocols, only suhosin extension (tested on FreeBSD)
519         // if suhosin is not installed it is okay for us
520         if (extension_loaded('suhosin') == false)
521         {
522             return true;
523         }
524         $configuration = ini_get_all('suhosin', false);
525
526         // suhosin simulation is okay for us
527         if ($configuration['suhosin.simulation'] == true)
528         {
529             return true;
530         }
531
532         // checking that UploadStream::STREAM_NAME is allowed by white list
533         $streams = $configuration['suhosin.executor.include.whitelist'];
534         if ($streams != '')
535         {
536             $streams = explode(',', $streams);
537             foreach($streams as $stream)
538             {
539                 $stream = explode('://', $stream, 2);
540                 if (count($stream) == 1)
541                 {
542                     if ($stream[0] == UploadStream::STREAM_NAME)
543                     {
544                         return true;
545                     }
546                 }
547                 elseif ($stream[1] == '' && $stream[0] == UploadStream::STREAM_NAME)
548                 {
549                     return true;
550                 }
551             }
552
553             $GLOBALS['log']->fatal('Stream ' . UploadStream::STREAM_NAME . ' is not listed in suhosin.executor.include.whitelist and blocked because of it');
554             return false;
555         }
556
557         // checking that UploadStream::STREAM_NAME is not blocked by black list
558         $streams = $configuration['suhosin.executor.include.blacklist'];
559         if ($streams != '')
560         {
561             $streams = explode(',', $streams);
562             foreach($streams as $stream)
563             {
564                 $stream = explode('://', $stream, 2);
565                 if ($stream[0] == UploadStream::STREAM_NAME)
566                 {
567                     $GLOBALS['log']->fatal('Stream ' . UploadStream::STREAM_NAME . 'is listed in suhosin.executor.include.blacklist and blocked because of it');
568                     return false;
569                 }
570             }
571             return true;
572         }
573
574         $GLOBALS['log']->fatal('Suhosin blocks all streams, please define ' . UploadStream::STREAM_NAME . ' stream in suhosin.executor.include.whitelist');
575         return false;
576     }
577
578     /**
579      * Get upload directory
580      * @return string
581      */
582     public static function getDir()
583     {
584         if(empty(self::$upload_dir)) {
585             self::$upload_dir = rtrim($GLOBALS['sugar_config']['upload_dir'], '/\\');
586             if(empty(self::$upload_dir)) {
587                 self::$upload_dir = "upload";
588             }
589             if(!file_exists(self::$upload_dir)) {
590                 sugar_mkdir(self::$upload_dir, 0755, true);
591             }
592         }
593         return self::$upload_dir;
594     }
595
596     /**
597      * Check if upload dir is writable
598      * @return bool
599      */
600     public static function writable()
601     {
602         return is_writable(self::getDir());
603     }
604
605     /**
606      * Register the stream
607      */
608     public function register()
609     {
610         stream_register_wrapper(self::STREAM_NAME, __CLASS__);
611     }
612
613     /**
614      * Get real FS path of the upload stream file
615      * @param string $path Upload stream path (with upload://)
616      * @return string FS path
617      */
618     public static function path($path)
619     {
620         $path = substr($path, strlen(self::STREAM_NAME)+3); // cut off upload://
621         $path = str_replace("\\", "/", $path); // canonicalize path
622         if($path == ".." || substr($path, 0, 3) == "../" || substr($path, -3, 3) == "/.." || strstr($path, "/../")) {
623                 $GLOBALS['log']->fatal("Invalid uploaded file name supplied: $path");
624                 return null;
625         }
626         return self::getDir()."/".$path;
627     }
628
629     /**
630      * Ensure upload subdir exists
631      * @param string $path Upload stream path (with upload://)
632      * @param bool $writable
633      * @return boolean
634      */
635     public static function ensureDir($path, $writable = true)
636     {
637         $path = self::path($path);
638         if(!is_dir($path)) {
639            return sugar_mkdir($path, 0755, true);
640         }
641         return true;
642     }
643
644     public function dir_closedir()
645     {
646         closedir($this->dirp);
647     }
648
649     public function dir_opendir ($path, $options )
650     {
651         $this->dirp = opendir(self::path($path));
652         return !empty($this->dirp);
653     }
654
655     public function dir_readdir()
656     {
657         return readdir($this->dirp);
658     }
659
660     public function dir_rewinddir()
661     {
662         return rewinddir($this->dirp);
663     }
664
665     public function mkdir($path, $mode, $options)
666     {
667         return mkdir(self::path($path), $mode, ($options&STREAM_MKDIR_RECURSIVE) != 0);
668     }
669
670     public function rename($path_from, $path_to)
671     {
672         return rename(self::path($path_from), self::path($path_to));
673     }
674
675     public function rmdir($path, $options)
676     {
677         return rmdir(self::path($path));
678     }
679
680     public function stream_cast ($cast_as)
681     {
682         return $this->fp;
683     }
684
685     public function stream_close ()
686     {
687         fclose($this->fp);
688         return true;
689     }
690
691     public function stream_eof ()
692     {
693         return feof($this->fp);
694     }
695    public function stream_flush ()
696     {
697         return fflush($this->fp);
698     }
699
700     public function stream_lock($operation)
701     {
702         return flock($this->fp, $operation);
703     }
704
705     public function stream_open($path, $mode)
706     {
707         $fullpath = self::path($path);
708         if(empty($fullpath)) return false;
709         if($mode == 'r') {
710             $this->fp = fopen($fullpath, $mode);
711         } else {
712             // if we will be writing, try to transparently create the directory
713             $this->fp = @fopen($fullpath, $mode);
714             if(!$this->fp && !file_exists(dirname($fullpath))) {
715                 mkdir(dirname($fullpath), 0755, true);
716                 $this->fp = fopen($fullpath, $mode);
717             }
718         }
719         return !empty($this->fp);
720     }
721
722     public function stream_read($count)
723     {
724         return fread($this->fp, $count);
725     }
726
727     public function stream_seek($offset, $whence = SEEK_SET)
728     {
729         return fseek($this->fp, $offset, $whence) == 0;
730     }
731
732     public function stream_set_option($option, $arg1, $arg2)
733     {
734         return true;
735     }
736
737     public function stream_stat()
738     {
739         return fstat($this->fp);
740     }
741
742     public function stream_tell()
743     {
744         return ftell($this->fp);
745     }
746     public function stream_write($data)
747     {
748         return fwrite($this->fp, $data);
749     }
750
751     public function unlink($path)
752     {
753         unlink(self::path($path));
754         return true;
755     }
756
757     public function url_stat($path, $flags)
758     {
759         return @stat(self::path($path));
760     }
761
762     public static function move_uploaded_file($upload, $path)
763     {
764         return move_uploaded_file($upload, self::path($path));
765     }
766 }
767