Passed
Push — scrutinizer-code-quality ( 09f5a1...c4c5fb )
by Adam
56:05 queued 14:08
created

UploadFile::UploadFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 2
Bugs 2 Features 1
Metric Value
cc 2
eloc 7
c 2
b 2
f 1
nc 2
nop 1
dl 0
loc 10
ccs 0
cts 7
cp 0
crap 6
rs 9.4285
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-2013 SugarCRM Inc.
6
7
 * SuiteCRM is an extension to SugarCRM Community Edition developed by Salesagility Ltd.
8
 * Copyright (C) 2011 - 2014 Salesagility Ltd.
9
 *
10
 * This program is free software; you can redistribute it and/or modify it under
11
 * the terms of the GNU Affero General Public License version 3 as published by the
12
 * Free Software Foundation with the addition of the following permission added
13
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
14
 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
15
 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
16
 *
17
 * This program is distributed in the hope that it will be useful, but WITHOUT
18
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19
 * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
20
 * details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License along with
23
 * this program; if not, see http://www.gnu.org/licenses or write to the Free
24
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25
 * 02110-1301 USA.
26
 *
27
 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
28
 * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected].
29
 *
30
 * The interactive user interfaces in modified source and object code versions
31
 * of this program must display Appropriate Legal Notices, as required under
32
 * Section 5 of the GNU Affero General Public License version 3.
33
 *
34
 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
35
 * these Appropriate Legal Notices must retain the display of the "Powered by
36
 * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
37
 * reasonably feasible for  technical reasons, the Appropriate Legal Notices must
38
 * display the words  "Powered by SugarCRM" and "Supercharged by SuiteCRM".
39
 ********************************************************************************/
40
41
/*********************************************************************************
42
43
 * Description:
44
 ********************************************************************************/
45
require_once('include/externalAPI/ExternalAPIFactory.php');
46
47
/**
48
 * @api
49
 * Manage uploaded files
50
 */
51
class UploadFile
52
{
53
	var $field_name;
54
	var $stored_file_name;
55
	var $uploaded_file_name;
56
	var $original_file_name;
57
	var $temp_file_location;
58
	var $use_soap = false;
59
	var $file;
60
	var $file_ext;
61
	protected static $url = "upload/";
62
63
	/**
64
	 * Upload errors
65
	 * @var array
66
	 */
67
	protected static $filesError = array(
68
			UPLOAD_ERR_OK => 'UPLOAD_ERR_OK - There is no error, the file uploaded with success.',
69
			UPLOAD_ERR_INI_SIZE => 'UPLOAD_ERR_INI_SIZE - The uploaded file exceeds the upload_max_filesize directive in php.ini.',
70
			UPLOAD_ERR_FORM_SIZE => 'UPLOAD_ERR_FORM_SIZE - The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.',
71
			UPLOAD_ERR_PARTIAL => 'UPLOAD_ERR_PARTIAL - The uploaded file was only partially uploaded.',
72
			UPLOAD_ERR_NO_FILE => 'UPLOAD_ERR_NO_FILE - No file was uploaded.',
73
			5 => 'UNKNOWN ERROR',
74
			UPLOAD_ERR_NO_TMP_DIR => 'UPLOAD_ERR_NO_TMP_DIR - Missing a temporary folder.',
75
			UPLOAD_ERR_CANT_WRITE => 'UPLOAD_ERR_CANT_WRITE - Failed to write file to disk.',
76
			UPLOAD_ERR_EXTENSION => 'UPLOAD_ERR_EXTENSION - A PHP extension stopped the file upload.',
77
			);
78
79
	/**
80
	 * Create upload file handler
81
	 * @param string $field_name Form field name
82
	 */
83 4
	public function __construct($field_name = '')
84
	{
85
		// $field_name is the name of your passed file selector field in your form
86
		// i.e., for Emails, it is "email_attachmentX" where X is 0-9
87 4
		$this->field_name = $field_name;
88 4
	}
89
90
    /**
91
     * @deprecated deprecated since version 7.6, PHP4 Style Constructors are deprecated and will be remove in 7.8, please update your code, use __construct instead
92
     */
93
    public function UploadFile($field_name = ''){
94
        $deprecatedMessage = 'PHP4 Style Constructors are deprecated and will be remove in 7.8, please update your code';
95
        if(isset($GLOBALS['log'])) {
96
            $GLOBALS['log']->deprecated($deprecatedMessage);
97
        }
98
        else {
99
            trigger_error($deprecatedMessage, E_USER_DEPRECATED);
100
        }
101
        self::__construct($field_name);
102
    }
103
104
105
	/**
106
	 * Setup for SOAP upload
107
	 * @param string $filename Name for the file
108
	 * @param string $file
109
	 */
110
	function set_for_soap($filename, $file) {
111
		$this->stored_file_name = $filename;
112
		$this->use_soap = true;
113
		$this->file = $file;
114
	}
115
116
	/**
117
	 * Get URL for a document
118
	 * @deprecated
119
	 * @param string stored_file_name File name in filesystem
120
	 * @param string bean_id note bean ID
121
	 * @return string path with file name
122
	 */
123
	public static function get_url($stored_file_name, $bean_id)
124
	{
125
		if ( empty($bean_id) && empty($stored_file_name) ) {
126
            return self::$url;
127
		}
128
129
		return self::$url . $bean_id;
130
	}
131
132
	/**
133
	 * Get URL of the uploaded file related to the document
134
	 * @param SugarBean $document
135
	 * @param string $type Type of the document, if different from $document
136
	 */
137
	public static function get_upload_url($document, $type = null)
138
	{
139
	    if(empty($type)) {
140
	        $type = $document->module_dir;
141
	    }
142
	    return "index.php?entryPoint=download&type=$type&id={$document->id}";
143
	}
144
145
	/**
146
	 * Try renaming a file to bean_id name
147
	 * @param string $filename
148
	 * @param string $bean_id
149
	 */
150
	protected static function tryRename($filename, $bean_id)
151
	{
152
	    $fullname = "upload://$bean_id.$filename";
153
	    if(file_exists($fullname)) {
154
            if(!rename($fullname,  "upload://$bean_id")) {
155
                $GLOBALS['log']->fatal("unable to rename file: $fullname => $bean_id");
156
            }
157
	        return true;
158
	    }
159
	    return false;
160
	}
161
162
	/**
163
	 * builds a URL path for an anchor tag
164
	 * @param string stored_file_name File name in filesystem
165
	 * @param string bean_id note bean ID
166
	 * @return string path with file name
167
	 */
168
	static public function get_file_path($stored_file_name, $bean_id, $skip_rename = false)
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
169
	{
170
		global $locale;
171
172
        // if the parameters are empty strings, just return back the upload_dir
173
		if ( empty($bean_id) && empty($stored_file_name) ) {
174
            return "upload://";
175
		}
176
177
		if(!$skip_rename) {
178
    		self::tryRename(rawurlencode($stored_file_name), $bean_id) ||
179
    		self::tryRename(urlencode($stored_file_name), $bean_id) ||
180
    		self::tryRename($stored_file_name, $bean_id) ||
181
    		self::tryRename($locale->translateCharset( $stored_file_name, 'UTF-8', $locale->getExportCharset()), $bean_id);
182
		}
183
184
		return "upload://$bean_id";
185
	}
186
187
	/**
188
	 * duplicates an already uploaded file in the filesystem.
189
	 * @param string old_id ID of original note
190
	 * @param string new_id ID of new (copied) note
191
	 * @param string filename Filename of file (deprecated)
192
	 */
193
	public static function duplicate_file($old_id, $new_id, $file_name)
194
	{
195
		global $sugar_config;
196
197
		// current file system (GUID)
198
		$source = "upload://$old_id";
199
200
		if(!file_exists($source)) {
201
			// old-style file system (GUID.filename.extension)
202
			$oldStyleSource = $source.$file_name;
203
			if(file_exists($oldStyleSource)) {
204
				// change to new style
205
				if(copy($oldStyleSource, $source)) {
206
					// delete the old
207
					if(!unlink($oldStyleSource)) {
208
						$GLOBALS['log']->error("upload_file could not unlink [ {$oldStyleSource} ]");
209
					}
210
				} else {
211
					$GLOBALS['log']->error("upload_file could not copy [ {$oldStyleSource} ] to [ {$source} ]");
212
				}
213
			}
214
		}
215
216
		$destination = "upload://$new_id";
217
		if(!copy($source, $destination)) {
218
			$GLOBALS['log']->error("upload_file could not copy [ {$source} ] to [ {$destination} ]");
219
		}
220
	}
221
222
	/**
223
	 * Get upload error from system
224
	 * @return string upload error
225
	 */
226 1
	public function get_upload_error()
227
	{
228 1
	    if(isset($this->field_name) && isset($_FILES[$this->field_name]['error'])) {
229
	        return $_FILES[$this->field_name]['error'];
230
	    }
231 1
	    return false;
232
	}
233
234
	/**
235
	 * standard PHP file-upload security measures. all variables accessed in a global context
236
	 * @return bool True on success
237
	 */
238 1
	public function confirm_upload()
239
	{
240 1
		global $sugar_config;
241
242 1
		if(empty($this->field_name) || !isset($_FILES[$this->field_name])) {
243 1
		    return false;
244
		}
245
246
        //check to see if there are any errors from upload
247
		if($_FILES[$this->field_name]['error'] != UPLOAD_ERR_OK) {
248
		    if($_FILES[$this->field_name]['error'] != UPLOAD_ERR_NO_FILE) {
249
                if($_FILES[$this->field_name]['error'] == UPLOAD_ERR_INI_SIZE) {
250
                    //log the error, the string produced will read something like:
251
                    //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
252
                    $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']));
253
                    $GLOBALS['log']->fatal($errMess);
254
                }else{
255
                    //log the error, the string produced will read something like:
256
                    //ERROR: There was an error during upload. Error code: 3 - UPLOAD_ERR_PARTIAL - The uploaded file was only partially uploaded.
257
                    $errMess = string_format($GLOBALS['app_strings']['UPLOAD_ERROR_TEXT'],array($_FILES['filename_file']['error'], self::$filesError[$_FILES['filename_file']['error']]));
258
                    $GLOBALS['log']->fatal($errMess);
259
                }
260
		    }
261
		    return false;
262
		}
263
264
		if(!is_uploaded_file($_FILES[$this->field_name]['tmp_name'])) {
265
			return false;
266
		} elseif($_FILES[$this->field_name]['size'] > $sugar_config['upload_maxsize']) {
267
		    $GLOBALS['log']->fatal("ERROR: uploaded file was too big: max filesize: {$sugar_config['upload_maxsize']}");
268
			return false;
269
		}
270
271
		if(!UploadStream::writable()) {
272
		    $GLOBALS['log']->fatal("ERROR: cannot write to upload directory");
273
			return false;
274
		}
275
276
		$this->mime_type = $this->getMime($_FILES[$this->field_name]);
277
		$this->stored_file_name = $this->create_stored_filename();
278
		$this->temp_file_location = $_FILES[$this->field_name]['tmp_name'];
279
		$this->uploaded_file_name = $_FILES[$this->field_name]['name'];
280
281
		return true;
282
	}
283
284
	/**
285
	 * Guess MIME type for file
286
	 * @param string $filename
287
	 * @return string MIME type
288
	 */
289
	function getMimeSoap($filename){
290
291
		if( function_exists( 'ext2mime' ) )
292
		{
293
			$mime = ext2mime($filename);
294
		}
295
		else
296
		{
297
			$mime = ' application/octet-stream';
298
		}
299
		return $mime;
300
301
	}
302
303
	/**
304
	 * Get MIME type for uploaded file
305
	 * @param array $_FILES_element $_FILES element required
306
	 * @return string MIME type
307
	 */
308
	function getMime($_FILES_element)
309
	{
310
		$filename = $_FILES_element['name'];
311
        $filetype = isset($_FILES_element['type']) ? $_FILES_element['type'] : null;
312
        $file_ext = pathinfo($filename, PATHINFO_EXTENSION);
313
314
        $is_image = strpos($filetype, 'image/') === 0;
315
        // if it's an image, or no file extension is available and the mime is octet-stream
316
        // try to determine the mime type
317
        $recheckMime = $is_image || (empty($file_ext) && $filetype == 'application/octet-stream');
318
319
        $mime = 'application/octet-stream';
320
        if ($filetype && !$recheckMime) {
321
            $mime = $filetype;
322
		} elseif( function_exists( 'mime_content_type' ) ) {
323
			$mime = mime_content_type( $_FILES_element['tmp_name'] );
324
        } elseif ($is_image) {
325
            $info = getimagesize($_FILES_element['tmp_name']);
326
            if ($info) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $info of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
327
                $mime = $info['mime'];
328
            }
329
        } elseif (function_exists('ext2mime')) {
330
            $mime = ext2mime($filename);
331
        }
332
333
        return $mime;
334
	}
335
336
	/**
337
	 * gets note's filename
338
	 * @return string
339
	 */
340
	function get_stored_file_name()
341
	{
342
		return $this->stored_file_name;
343
	}
344
345
	function get_temp_file_location()
346
	{
347
	    return $this->temp_file_location;
348
	}
349
350
	function get_uploaded_file_name()
351
	{
352
	    return $this->uploaded_file_name;
353
	}
354
355
	function get_mime_type()
356
	{
357
	    return $this->mime_type;
358
	}
359
360
	/**
361
	 * Returns the contents of the uploaded file
362
	 */
363
	public function get_file_contents() {
364
365
	    // Need to call
366
	    if ( !isset($this->temp_file_location) ) {
367
	        $this->confirm_upload();
368
	    }
369
370
	    if (($data = @file_get_contents($this->temp_file_location)) === false) {
371
	        return false;
372
        }
373
374
        return $data;
375
	}
376
377
378
379
	/**
380
	 * creates a file's name for preparation for saving
381
	 * @return string
382
	 */
383
	function create_stored_filename()
384
	{
385
		global $sugar_config;
386
387
		if(!$this->use_soap) {
388
			$stored_file_name = $_FILES[$this->field_name]['name'];
389
			$this->original_file_name = $stored_file_name;
390
391
			/**
392
			 * cn: bug 8056 - windows filesystems and IIS do not like utf8.  we are forced to urlencode() to ensure that
393
			 * the file is linkable from the browser.  this will stay broken until we move to a db-storage system
394
			 */
395
			if(is_windows()) {
396
				// create a non UTF-8 name encoding
397
				// 176 + 36 char guid = windows' maximum filename length
398
				$end = (strlen($stored_file_name) > 176) ? 176 : strlen($stored_file_name);
399
				$stored_file_name = substr($stored_file_name, 0, $end);
400
				$this->original_file_name = $_FILES[$this->field_name]['name'];
401
			}
402
		    $stored_file_name = str_replace("\\", "", $stored_file_name);
403
		} else {
404
			$stored_file_name = $this->stored_file_name;
405
			$this->original_file_name = $stored_file_name;
406
		}
407
408
		$this->file_ext = pathinfo($stored_file_name, PATHINFO_EXTENSION);
409
        // cn: bug 6347 - fix file extension detection
410
        foreach($sugar_config['upload_badext'] as $badExt) {
411
            if(strtolower($this->file_ext) == strtolower($badExt)) {
412
                $stored_file_name .= ".txt";
413
                $this->file_ext="txt";
414
                break; // no need to look for more
415
            }
416
        }
417
		return $stored_file_name;
418
	}
419
420
	/**
421
	 * moves uploaded temp file to permanent save location
422
	 * @param string bean_id ID of parent bean
423
	 * @return bool True on success
424
	 */
425
	function final_move($bean_id)
426
	{
427
	    $destination = $bean_id;
428
	    if(substr($destination, 0, 9) != "upload://") {
429
            $destination = "upload://$bean_id";
430
	    }
431
        if($this->use_soap) {
432
        	if(!file_put_contents($destination, $this->file)){
433
        	    $GLOBALS['log']->fatal("ERROR: can't save file to $destination");
434
                return false;
435
        	}
436
		} else {
437
			if(!UploadStream::move_uploaded_file($_FILES[$this->field_name]['tmp_name'], $destination)) {
438
			    $GLOBALS['log']->fatal("ERROR: can't move_uploaded_file to $destination. You should try making the directory writable by the webserver");
439
                return false;
440
			}
441
		}
442
		return true;
443
	}
444
445
	/**
446
	 * Upload document to external service
447
	 * @param SugarBean $bean Related bean
448
	 * @param string $bean_id
449
	 * @param string $doc_type
450
	 * @param string $file_name
451
	 * @param string $mime_type
452
	 */
453
	function upload_doc($bean, $bean_id, $doc_type, $file_name, $mime_type)
454
	{
455
		if(!empty($doc_type)&&$doc_type!='Sugar') {
456
			global $sugar_config;
457
	        $destination = $this->get_upload_path($bean_id);
458
	        sugar_rename($destination, str_replace($bean_id, $bean_id.'_'.$file_name, $destination));
459
	        $new_destination = $this->get_upload_path($bean_id.'_'.$file_name);
460
461
		    try{
462
                $this->api = ExternalAPIFactory::loadAPI($doc_type);
463
464
                if ( isset($this->api) && $this->api !== false ) {
465
                    $result = $this->api->uploadDoc(
0 ignored issues
show
Bug introduced by
The method uploadDoc() does not seem to exist on object<ExternalAPIBase>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
466
                        $bean,
467
                        $new_destination,
468
                        $file_name,
469
                        $mime_type
470
                        );
471
                } else {
472
                    $result['success'] = FALSE;
473
                    // FIXME: Translate
474
                    $GLOBALS['log']->error("Could not load the requested API (".$doc_type.")");
475
                    $result['errorMessage'] = 'Could not find a proper API';
476
                }
477
            }catch(Exception $e){
478
                $result['success'] = FALSE;
479
                $result['errorMessage'] = $e->getMessage();
480
                $GLOBALS['log']->error("Caught exception: (".$e->getMessage().") ");
481
            }
482
            if ( !$result['success'] ) {
483
                sugar_rename($new_destination, str_replace($bean_id.'_'.$file_name, $bean_id, $new_destination));
484
                $bean->doc_type = 'Sugar';
485
                // FIXME: Translate
486
                if ( ! is_array($_SESSION['user_error_message']) )
487
                    $_SESSION['user_error_message'] = array();
488
489
                $error_message = isset($result['errorMessage']) ? $result['errorMessage'] : $GLOBALS['app_strings']['ERR_EXTERNAL_API_SAVE_FAIL'];
490
                $_SESSION['user_error_message'][] = $error_message;
491
492
            }
493
            else {
494
                unlink($new_destination);
495
            }
496
        }
497
498
	}
499
500
	/**
501
	 * returns the path with file name to save an uploaded file
502
	 * @param string bean_id ID of the parent bean
503
	 * @return string
504
	 */
505
	function get_upload_path($bean_id)
506
	{
507
		$file_name = $bean_id;
508
509
		// cn: bug 8056 - mbcs filename in urlencoding > 212 chars in Windows fails
510
		$end = (strlen($file_name) > 212) ? 212 : strlen($file_name);
511
		$ret_file_name = substr($file_name, 0, $end);
512
513
		return "upload://$ret_file_name";
514
	}
515
516
	/**
517
	 * deletes a file
518
	 * @param string bean_id ID of the parent bean
519
	 * @param string file_name File's name
520
	 */
521
	static public function unlink_file($bean_id,$file_name = '')
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
522
	{
523
	    if(file_exists("upload://$bean_id$file_name")) {
524
            return unlink("upload://$bean_id$file_name");
525
	    }
526
    }
527
528
    /**
529
     * Get upload file location prefix
530
     * @return string prefix
531
     */
532
    public function get_upload_dir()
533
    {
534
        return "upload://";
535
    }
536
537
    /**
538
     * Return real FS path of the file
539
     * @param string $path
540
     */
541 4
    public static function realpath($path)
542
    {
543 4
       if(substr($path, 0, 9) == "upload://") {
544
           $path = UploadStream::path($path);
545
       }
546 4
       $ret = realpath($path);
547 4
       return $ret?$ret:$path;
548
    }
549
550
    /**
551
     * Return path of uploaded file relative to uploads dir
552
     * @param string $path
553
     */
554
    public static function relativeName($path)
555
    {
556
        if(substr($path, 0, 9) == "upload://") {
557
            $path = substr($path, 9);
558
        }
559
        return $path;
560
    }
561
}
562
563
/**
564
 * @internal
565
 * Upload file stream handler
566
 */
567
class UploadStream
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
568
{
569
    const STREAM_NAME = "upload";
570
    protected static $upload_dir;
571
572
    /**
573
     * Method checks Suhosin restrictions to use streams in php
574
     *
575
     * @static
576
     * @return bool is allowed stream or not
577
     */
578
    public static function getSuhosinStatus()
579
    {
580
        // looks like suhosin patch doesn't block protocols, only suhosin extension (tested on FreeBSD)
581
        // if suhosin is not installed it is okay for us
582
        if (extension_loaded('suhosin') == false)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
583
        {
584
            return true;
585
        }
586
        $configuration = ini_get_all('suhosin', false);
587
588
        // suhosin simulation is okay for us
589
        if ($configuration['suhosin.simulation'] == true)
590
        {
591
            return true;
592
        }
593
594
        // checking that UploadStream::STREAM_NAME is allowed by white list
595
        $streams = $configuration['suhosin.executor.include.whitelist'];
596
        if ($streams != '')
597
        {
598
            $streams = explode(',', $streams);
599
            foreach($streams as $stream)
600
            {
601
                $stream = explode('://', $stream, 2);
602
                if (count($stream) == 1)
603
                {
604
                    if ($stream[0] == UploadStream::STREAM_NAME)
605
                    {
606
                        return true;
607
                    }
608
                }
609
                elseif ($stream[1] == '' && $stream[0] == UploadStream::STREAM_NAME)
610
                {
611
                    return true;
612
                }
613
            }
614
615
            $GLOBALS['log']->fatal('Stream ' . UploadStream::STREAM_NAME . ' is not listed in suhosin.executor.include.whitelist and blocked because of it');
616
            return false;
617
        }
618
619
        // checking that UploadStream::STREAM_NAME is not blocked by black list
620
        $streams = $configuration['suhosin.executor.include.blacklist'];
621
        if ($streams != '')
622
        {
623
            $streams = explode(',', $streams);
624
            foreach($streams as $stream)
625
            {
626
                $stream = explode('://', $stream, 2);
627
                if ($stream[0] == UploadStream::STREAM_NAME)
628
                {
629
                    $GLOBALS['log']->fatal('Stream ' . UploadStream::STREAM_NAME . 'is listed in suhosin.executor.include.blacklist and blocked because of it');
630
                    return false;
631
                }
632
            }
633
            return true;
634
        }
635
636
        $GLOBALS['log']->fatal('Suhosin blocks all streams, please define ' . UploadStream::STREAM_NAME . ' stream in suhosin.executor.include.whitelist');
637
        return false;
638
    }
639
640
    /**
641
     * Get upload directory
642
     * @return string
643
     */
644 5
    public static function getDir()
645
    {
646 5
        if(empty(self::$upload_dir)) {
647 1
            self::$upload_dir = rtrim($GLOBALS['sugar_config']['upload_dir'], '/\\');
648 1
            if(empty(self::$upload_dir)) {
649
                self::$upload_dir = "upload";
650
            }
651 1
            if(!file_exists(self::$upload_dir)) {
652
                sugar_mkdir(self::$upload_dir, 0755, true);
653
            }
654
        }
655 5
        return self::$upload_dir;
656
    }
657
658
    /**
659
     * Check if upload dir is writable
660
     * @return bool
661
     */
662
    public static function writable()
663
    {
664
        return is_writable(self::getDir());
665
    }
666
667
    /**
668
     * Register the stream
669
     */
670
    public static function register()
671
    {
672
        stream_register_wrapper(self::STREAM_NAME, __CLASS__);
673
    }
674
675
    /**
676
     * Get real FS path of the upload stream file
677
     * @param string $path Upload stream path (with upload://)
678
     * @return string FS path
679
     */
680 5
    public static function path($path)
681
    {
682 5
    	$path = substr($path, strlen(self::STREAM_NAME)+3); // cut off upload://
683 5
    	$path = str_replace("\\", "/", $path); // canonicalize path
684 5
    	if($path == ".." || substr($path, 0, 3) == "../" || substr($path, -3, 3) == "/.." || strstr($path, "/../")) {
685
    		return null;
686
    	}
687 5
        return self::getDir()."/".$path;
688
    }
689
690
    /**
691
     * Ensure upload subdir exists
692
     * @param string $path Upload stream path (with upload://)
693
     * @param bool $writable
694
     * @return boolean
695
     */
696
    public static function ensureDir($path, $writable = true)
697
    {
698
        $path = self::path($path);
699
        if(!is_dir($path)) {
700
           return sugar_mkdir($path, 0755, true);
701
        }
702
        return true;
703
    }
704
705
    public function dir_closedir()
706
    {
707
        closedir($this->dirp);
708
    }
709
710
    public function dir_opendir ($path, $options )
711
    {
712
        $this->dirp = opendir(self::path($path));
713
        return !empty($this->dirp);
714
    }
715
716
    public function dir_readdir()
717
    {
718
        return readdir($this->dirp);
719
    }
720
721
    public function dir_rewinddir()
722
    {
723
        return rewinddir($this->dirp);
724
    }
725
726 2
    public function mkdir($path, $mode, $options)
727
    {
728 2
        return mkdir(self::path($path), $mode, ($options&STREAM_MKDIR_RECURSIVE) != 0);
729
    }
730
731
    public function rename($path_from, $path_to)
732
    {
733
        return rename(self::path($path_from), self::path($path_to));
734
    }
735
736
    public function rmdir($path, $options)
737
    {
738
        return rmdir(self::path($path));
739
    }
740
741
    public function stream_cast ($cast_as)
742
    {
743
        return $this->fp;
744
    }
745
746
    public function stream_close ()
747
    {
748
        fclose($this->fp);
749
        return true;
750
    }
751
752
    public function stream_eof ()
753
    {
754
        return feof($this->fp);
755
    }
756
   public function stream_flush ()
757
    {
758
        return fflush($this->fp);
759
    }
760
761
    public function stream_lock($operation)
762
    {
763
        return flock($this->fp, $operation);
764
    }
765
766
    public function stream_open($path, $mode)
767
    {
768
        $fullpath = self::path($path);
769
        if(empty($fullpath)) return false;
770
        if($mode == 'r') {
771
            $this->fp = fopen($fullpath, $mode);
772
        } else {
773
            // if we will be writing, try to transparently create the directory
774
            $this->fp = @fopen($fullpath, $mode);
775
            if(!$this->fp && !file_exists(dirname($fullpath))) {
776
                mkdir(dirname($fullpath), 0755, true);
777
                $this->fp = fopen($fullpath, $mode);
778
            }
779
        }
780
        return !empty($this->fp);
781
    }
782
783
    public function stream_read($count)
784
    {
785
        return fread($this->fp, $count);
786
    }
787
788
    public function stream_seek($offset, $whence = SEEK_SET)
789
    {
790
        return fseek($this->fp, $offset, $whence) == 0;
791
    }
792
793
    public function stream_set_option($option, $arg1, $arg2)
794
    {
795
        return true;
796
    }
797
798
    public function stream_stat()
799
    {
800
        return fstat($this->fp);
801
    }
802
803
    public function stream_tell()
804
    {
805
        return ftell($this->fp);
806
    }
807
    public function stream_write($data)
808
    {
809
        return fwrite($this->fp, $data);
810
    }
811
812
    public function unlink($path)
813
    {
814
        unlink(self::path($path));
815
        return true;
816
    }
817
818 5
    public function url_stat($path, $flags)
819
    {
820 5
        return @stat(self::path($path));
821
    }
822
823
    public static function move_uploaded_file($upload, $path)
824
    {
825
        return move_uploaded_file($upload, self::path($path));
826
    }
827
}
828
829