Issues (4069)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

include/upload_file.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
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
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
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