Completed
Pull Request — master (#563)
by Richard
08:33
created

JUpload::defaultAfterUploadManagement()   D

Complexity

Conditions 9
Paths 5

Size

Total Lines 35
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 26
nc 5
nop 0
dl 0
loc 35
rs 4.909
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This class manage upload, with use of the JUpload applet. It's both a sample to show how to use the applet, and
5
 * a class you can use directly into your own application.
6
 *
7
 * Recommandation: Don't update its code !
8
 *
9
 * By doing this, you'll be able to reuse directly any update coming from the JUpload project, instead of reporting your
10
 * modifications into any new version of this class. This guarantees you that your project can use the last version of
11
 * JUpload, without any code modification. We work so that the applet behavior remains unchanged, but from time to time,
12
 * a change can appear.
13
 *
14
 * Sample:
15
 * - See the index.php samples, in the same folder.
16
 *
17
 * Notes:
18
 * - maxChunkSize: this class use a default maxChunkSize of 500K (or less, depending on the script max size). This allows
19
 * upload of FILES OF ANY SIZE on quite all ISP hosting. If it's too big for you (the max upload size of your ISP is less
20
 * than 500K), or if you want no chunk at all, you can, of course, override this default value.
21
 *
22
 *
23
 *
24
 * Parameters:
25
 * - $appletparams contains a map for applet parameters: key is the applet parameter name. The value is the value to transmit
26
 * 		to the applet. See the applet documentation for information on all applet parameters.
27
 * - $classparams contains the parameter specific for the JUpload class below. Here are the main class parameters:
28
 * 		- demo_mode. Files are uploaded to the server, but not stored on its hard drive. That is: you can simulate the global
29
 * 		behavior, but won't consume hard drive space. This mode is used on sourceforge web site.
30
 *
31
 *
32
 * Output generated for uploaded files:
33
 * - $files is an array of array. This can be managed by (a) the function given in the callbackAfterUploadManagement class
34
 * 		parameter, or (b) within the page whose URL is given in the afterUploadURL applet parameter, or (c) you can Extend the
35
 * 		class and redeclare defaultAfterUploadManagement() to your needs.
36
 * 	See the defaultAfterUploadManagement() for a sample on howto manage this array.
37
 *
38
 *   This array contains:
39
 *     - One entry per file. Each entry is an array, that contains all files properties, stored as $key=>$value.
40
 * 		The available keys are:
41
 * 		  - name: the filename, as it is now stored on the system.
42
 * 		  - size: the file size
43
 * 		  - path: the absolute path, where the file has been stored.
44
 * 			- fullName: the canonical file name (i.e. including the absolute path)
45
 * 		  - md5sum: the md5sum of the file, if further control is needed.
46
 * 			- mimetype: the calculated mime type of the file
47
 * 		  - If the formData applet parameter is used: all attributes (key and value) uploaded by the applet, are put here,
48
 * 			repeated for each file.
49
 *
50
 * 		Note: if you are using a callback function (i.e. callbackAfterUploadManagement) and you do not see a global 'object' you
51
 * 					are expecting then it might have been destroyed by PHP - c.f. http://bugs.php.net/bug.php?id=39693
52
 *
53
 */
54
55
class JUpload {
56
57
	var $appletparams;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $appletparams.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
58
	var $classparams;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $classparams.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
59
	var $files;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $files.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
60
61
	public function JUpload($appletparams = array(), $classparams = array()) {
0 ignored issues
show
Coding Style Best Practice introduced by
Please use __construct() instead of a PHP4-style constructor that is named after the class.
Loading history...
62
		if (gettype($classparams) !== 'array')
63
		$this->abort('Invalid type of parameter classparams: Expecting an array');
64
		if (gettype($appletparams) !== 'array')
65
		$this->abort('Invalid type of parameter appletparams: Expecting an array');
66
67
		// set some defaults for the applet params
68
		if (!isset($appletparams['afterUploadURL']))
69
		$appletparams['afterUploadURL'] = $_SERVER['PHP_SELF'] . '?afterupload=1';
70
		if (!isset($appletparams['name']))
71
		$appletparams['name'] = 'JUpload';
72
		if (!isset($appletparams['archive']))
73
		$appletparams['archive'] = 'wjhk.jupload.jar';
74
		if (!isset($appletparams['code']))
75
		$appletparams['code'] = 'wjhk.jupload2.JUploadApplet';
76
		if (!isset($appletparams['debugLevel']))
77
		$appletparams['debugLevel'] = 0;
78
		if (!isset($appletparams['httpUploadParameterType']))
79
		$appletparams['httpUploadParameterType'] = 'array';
80
		if (!isset($appletparams['showLogWindow']))
81
		$appletparams['showLogWindow'] = ($appletparams['debugLevel'] > 0) ? 'true' : 'false';
82
		if (!isset($appletparams['width']))
83
		$appletparams['width'] = 640;
84
		if (!isset($appletparams['height']))
85
		$appletparams['height'] = ($appletparams['showLogWindow'] === 'true') ? 500 : 300;
86
		if (!isset($appletparams['mayscript']))
87
		$appletparams['mayscript'] = 'true';
88
		if (!isset($appletparams['scriptable']))
89
		$appletparams['scriptable'] = 'false';
90
		//if (!isset($appletparams['stringUploadSuccess']))
91
		$appletparams['stringUploadSuccess'] = 'SUCCESS';
92
		//if (!isset($appletparams['stringUploadError']))
93
		$appletparams['stringUploadError'] = 'ERROR: (.*)';
94
		$maxpost = $this->tobytes(ini_get('post_max_size'));
95
		$maxmem = $this->tobytes(ini_get('memory_limit'));
96
		$maxfs = $this->tobytes(ini_get('upload_max_filesize'));
97
		$obd = ini_get('open_basedir');
98
		if (!isset($appletparams['maxChunkSize'])) {
99
			$maxchunk = ($maxpost < $maxmem) ? $maxpost : $maxmem;
100
			$maxchunk = ($maxchunk < $maxfs) ? $maxchunk : $maxfs;
101
			$maxchunk /= 4;
102
			$optchunk = (500000 > $maxchunk) ? $maxchunk : 500000;
103
			$appletparams['maxChunkSize'] = $optchunk;
104
		}
105
		$appletparams['maxChunkSize'] = $this->tobytes($appletparams['maxChunkSize']);
106
		if (!isset($appletparams['maxFileSize']))
107
		$appletparams['maxFileSize'] = $maxfs;
108
		$appletparams['maxFileSize'] = $this->tobytes($appletparams['maxFileSize']);
109
		if (isset($classparams['errormail'])) {
110
			$appletparams['urlToSendErrorTo'] = $_SERVER["PHP_SELF"] . '?errormail';
111
		}
112
113
		// Same for class parameters
114
		if (!isset($classparams['demo_mode']))
115
		$classparams['demo_mode'] = false;
116
		if ($classparams['demo_mode']) {
117
			$classparams['create_destdir'] = false;
118
			$classparams['allow_subdirs'] = true;
119
			$classparams['allow_zerosized'] = true;
120
			$classparams['duplicate'] = 'overwrite';
121
		}
122
		if (!isset($classparams['debug_php']))											// set true to log some messages in PHP log
123
		$classparams['debug_php'] = false;
124
		if (!isset($this->classparams['allowed_mime_types']))				// array of allowed MIME type
125
		$classparams['allowed_mime_types'] = 'all';
126
		if (!isset($this->classparams['allowed_file_extensions'])) 	// array of allowed file extensions
127
		$classparams['allowed_file_extensions'] = 'all';
128
		if (!isset($classparams['verbose_errors']))						// shouldn't display server info on a production site!
129
		$classparams['verbose_errors'] = true;
130
		if (!isset($classparams['session_regenerate']))
131
		$classparams['session_regenerate'] = false;
132
		if (!isset($classparams['create_destdir']))
133
		$classparams['create_destdir'] = true;
134
		if (!isset($classparams['allow_subdirs']))
135
		$classparams['allow_subdirs'] = false;
136
		if (!isset($classparams['spaces_in_subdirs']))
137
		$classparams['spaces_in_subdirs'] = false;
138
		if (!isset($classparams['allow_zerosized']))
139
		$classparams['allow_zerosized'] = false;
140
		if (!isset($classparams['duplicate']))
141
		$classparams['duplicate'] = 'rename';
142
		if (!isset($classparams['dirperm']))
143
		$classparams['dirperm'] = 0755;
144
		if (!isset($classparams['fileperm']))
145
		$classparams['fileperm'] = 0644;
146
		if (!isset($classparams['destdir'])) {
147
			if ($obd != '')
148
			$classparams['destdir'] = $obd;
149
			else
150
			$classparams['destdir'] = '/var/tmp/jupload_test';
151
		}else{
152
			$classparams['destdir']=str_replace('~',' ',$classparams['destdir']);
153
		}
154
		if ($classparams['create_destdir']) {
155
			$_umask = umask(0); 	// override the system mask
156
			@mkdir($classparams['destdir'], $classparams['dirperm']);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mkdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

156
			/** @scrutinizer ignore-unhandled */ @mkdir($classparams['destdir'], $classparams['dirperm']);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
157
			umask($_umask);
158
		}
159
		if (!is_dir($classparams['destdir']) && is_writable($classparams['destdir']))
160
		$this->abort('Destination dir not accessible');
161
		if (!isset($classparams['tmp_prefix']))
162
		$classparams['tmp_prefix'] = 'jutmp.';
163
		if (!isset($classparams['var_prefix']))
164
		$classparams['var_prefix'] = 'juvar.';
165
		if (!isset($classparams['jscript_wrapper']))
166
		$classparams['jscript_wrapper'] = 'JUploadSetProperty';
167
		if (!isset($classparams['tag_jscript']))
168
		$classparams['tag_jscript'] = '<!--JUPLOAD_JSCRIPT-->';
169
		if (!isset($classparams['tag_applet']))
170
		$classparams['tag_applet'] = '<!--JUPLOAD_APPLET-->';
171
		if (!isset($classparams['tag_flist']))
172
		$classparams['tag_flist'] = '<!--JUPLOAD_FILES-->';
173
		if (!isset($classparams['http_flist_start']))
174
		$classparams['http_flist_start'] =
175
            		"<table border='1'><TR><TH>Filename</TH><TH>file size</TH><TH>Relative path</TH><TH>Full name</TH><TH>md5sum</TH><TH>Specific parameters</TH></TR>";
176
		if (!isset($classparams['http_flist_end']))
177
		$classparams['http_flist_end'] = "</table>\n";
178
		if (!isset($classparams['http_flist_file_before']))
179
		$classparams['http_flist_file_before'] = "<tr><td>";
180
		if (!isset($classparams['http_flist_file_between']))
181
		$classparams['http_flist_file_between'] = "</td><td>";
182
		if (!isset($classparams['http_flist_file_after']))
183
		$classparams['http_flist_file_after'] = "</td></tr>\n";
184
185
		$this->appletparams = $appletparams;
186
		$this->classparams = $classparams;
187
		$this->page_start();
188
	}
189
190
	/**
191
	 * Return an array of uploaded files * The array contains: name, size, tmp_name, error,
192
	 * relativePath, md5sum, mimetype, fullName, path
193
	 */
194
	public function uploadedfiles() {
195
		return $this->files;
196
	}
197
198
	/**
199
	 * Log a message on the current output, as a HTML comment.
200
	 */
201
	protected function logDebug($function, $msg, $htmlComment=true) {
202
		$output = "[DEBUG] [$function] $msg";
203
		if ($htmlComment) {
204
			echo("<!-- $output -->\r\n");
205
		} else {
206
			echo("$output\r\n");
207
		}
208
	}
209
210
	/**
211
	 * Log a message to the PHP log.
212
	 * Declared "protected" so it may be Extended if you require customised logging (e.g. particular log file location).
213
	 */
214
	protected function logPHPDebug($function, $msg) {
215
		if ($this->classparams['debug_php'] === true) {
216
			$output = "[DEBUG] [$function] ".$this->arrayexpand($msg);
217
			error_log($output);
218
		}
219
	}
220
221
	private function arrayexpand($array) {
222
		$output = '';
223
		if (is_array($array)) {
224
			foreach ($array as $key => $value) {
225
				$output .= "\n ".$key.' => '.$this->arrayexpand($value);
226
			}
227
		} else {
228
			$output .= $array;
229
		}
230
		return $output;
231
	}
232
233
234
	/**
235
	 * Convert a value ending in 'G','M' or 'K' to bytes
236
	 *
237
	 */
238
	private function tobytes($val) {
239
		$val = trim($val);
240
		$last = fix_strtolower($val{strlen($val)-1});
241
		switch($last) {
242
			case 'g':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
243
				$val *= 1024;
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
244
			case 'm':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
245
				$val *= 1024;
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
246
			case 'k':
247
				$val *= 1024;
248
		}
249
		return $val;
250
	}
251
252
	/**
253
	 * Build a string, containing a javascript wrapper function
254
	 * for setting applet properties via JavaScript. This is necessary,
255
	 * because we use the "modern" method of including the applet (using
256
	 * <object> resp. <embed> tags) in order to trigger automatic JRE downloading.
257
	 * Therefore, in Netscape-like browsers, the applet is accessible via
258
	 * the document.embeds[] array while in others, it is accessible via the
259
	 * document.applets[] array.
260
	 *
261
	 * @return A string, containing the necessary wrapper function (named JUploadSetProperty)
262
	 */
263
	private function str_jsinit() {
264
		$N = "\n";
265
		$name = $this->appletparams['name'];
266
		$ret = '<script type="text/javascript">'.$N;
267
		$ret .= '<!--'.$N;
268
		$ret .= 'function '.$this->classparams['jscript_wrapper'].'(name, value) {'.$N;
269
		$ret .= '  document.applets["'.$name.'"] == null || document.applets["'.$name.'"].setProperty(name,value);'.$N;
270
		$ret .= '  document.embeds["'.$name.'"] == null || document.embeds["'.$name.'"].setProperty(name,value);'.$N;
271
		$ret .= '}'.$N;
272
		$ret .= '//-->'.$N;
273
		$ret .= '</script>';
274
		return $ret;
275
	}
276
277
	/**
278
	 * Build a string, containing the applet tag with all parameters.
279
	 *
280
	 * @return A string, containing the applet tag
281
	 */
282
	private function str_applet() {
283
		$N = "\n";
284
		$params = $this->appletparams;
285
		// return the actual applet tag
286
		$ret = '<object classid = "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"'.$N;
287
		$ret .= '  codebase = "http://java.sun.com/update/1.5.0/jinstall-1_5-windows-i586.cab#Version=5,0,0,3"'.$N;
288
		$ret .= '  width = "'.$params['width'].'"'.$N;
289
		$ret .= '  height = "'.$params['height'].'"'.$N;
290
		$ret .= '  name = "'.$params['name'].'">'.$N;
291
		foreach ($params as $key => $val) {
292
			if ($key !== 'width' && $key !== 'height')
293
			$ret .= '  <param name = "'.$key.'" value = "'.$val.'" />'.$N;
294
		}
295
		$ret .= '  <comment>'.$N;
296
		$ret .= '    <embed'.$N;
297
		$ret .= '      type = "application/x-java-applet;version=1.5"'.$N;
298
		foreach ($params as $key => $val)
299
		$ret .= '      '.$key.' = "'.$val.'"'.$N;
300
		$ret .= '      pluginspage = "http://java.sun.com/products/plugin/index.html#download">'.$N;
301
		$ret .= '      <noembed>'.$N;
302
		$ret .= '        Java 1.5 or higher plugin required.'.$N;
303
		$ret .= '      </noembed>'.$N;
304
		$ret .= '    </embed>'.$N;
305
		$ret .= '  </comment>'.$N;
306
		$ret .= '</object>';
307
		return $ret;
308
	}
309
310
	private function abort($msg = '') {
311
		$this->cleanup();
312
		if ($msg != '')
313
		die(str_replace('(.*)',$msg,$this->appletparams['stringUploadError'])."\n");
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
314
		exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
315
	}
316
317
	private function warning($msg = '') {
318
		$this->cleanup();
319
		if ($msg != '')
320
		echo('WARNING: '.$msg."\n");
321
		echo $this->appletparams['stringUploadSuccess']."\n";
322
		exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
323
	}
324
325
	private function cleanup() {
326
		// remove all uploaded files of *this* request
327
		if (isset($_FILES)) {
328
			foreach ($_FILES as $key => $val)
329
			@unlink($val['tmp_name']);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

329
			/** @scrutinizer ignore-unhandled */ @unlink($val['tmp_name']);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
330
		}
331
		// remove accumulated file, if any.
332
		@unlink($this->classparams['destdir'].'/'.$this->classparams['tmp_prefix'].session_id());
333
		@unlink($this->classparams['destdir'].'/'.$this->classparams['tmp_prefix'].'tmp'.session_id());
334
		// reset session var
335
		$_SESSION['RF'][$this->classparams['var_prefix'].'size'] = 0;
336
		return;
337
	}
338
339
	private function mkdirp($path) {
340
		// create subdir (hierary) below destdir;
341
		$dirs = explode('/', $path);
342
		$path = $this->classparams['destdir'];
343
		foreach ($dirs as $dir) {
344
			$path .= '/'.$dir;
345
			if (!file_exists($path)) {  // @ does NOT always supress the error!
346
				$_umask = umask(0); 	// override the system mask
347
				@mkdir($path, $this->classparams['dirperm']);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mkdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

347
				/** @scrutinizer ignore-unhandled */ @mkdir($path, $this->classparams['dirperm']);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
348
				umask($_umask);
349
			}
350
		}
351
		if (!is_dir($path) && is_writable($path))
352
		$this->abort('Destination dir not accessible');
353
	}
354
355
	/**
356
	 * This method:
357
	 * - Replaces some potentially dangerous characters by '_' (in the given name an relative path)
358
	 * - Checks if a files of the same name already exists.
359
	 * 		- If no: no problem.
360
	 * 		- If yes, and the duplicate class param is set to rename, the file is renamed.
361
	 * 		- If yes, and the duplicate class param is set to overwrite, the file is not renamed. The existing one will be erased.
362
	 * 		- If yes, and the duplicate class param is set to reject, an error is thrown.
363
	 */
364
	private function dstfinal(&$name, &$subdir) {
365
		$name = preg_replace('![`$\\\\/|]!', '_', $name);
366
		if ($this->classparams['allow_subdirs'] && ($subdir != '')) {
367
			$subdir = trim(preg_replace('!\\\\!','/',$subdir),'/');
368
			$subdir = preg_replace('![`$|]!', '_', $subdir);
369
			if (!$this->classparams['spaces_in_subdirs']) {
370
				$subdir = str_replace(' ','_',$subdir);
371
			}
372
			// recursively create subdir
373
			if (!$this->classparams['demo_mode'])
374
			$this->mkdirp($subdir);
375
			// append a slash
376
			$subdir .= '/';
377
		} else {
378
			$subdir = '';
379
		}
380
		$ret = $this->classparams['destdir'].'/'.$subdir.$name;
381
		if (file_exists($ret)) {
382
			if ($this->classparams['duplicate'] === 'overwrite') {
383
				return $ret;
384
			}
385
			if ($this->classparams['duplicate'] === 'reject') {
386
				$this->abort('A file with the same name already exists');
387
			}
388
			if ($this->classparams['duplicate'] === 'warning') {
389
				$this->warning("File $name already exists - rejected");
390
			}
391
			if ($this->classparams['duplicate'] === 'rename') {
392
				$cnt = 1;
393
				$dir = $this->classparams['destdir'].'/'.$subdir;
394
				$ext = strrchr($name, '.');
395
				if ($ext) {
396
					$nameWithoutExtension = substr($name, 0, strlen($name) - strlen($ext));
397
				} else {
398
					$ext = '';
399
					$nameWithoutExtension = $name;
400
				}
401
402
				$rtry = $dir.$nameWithoutExtension.'_'.$cnt.$ext;
403
				while (file_exists($rtry)) {
404
					$cnt++;
405
					$rtry = $dir.$nameWithoutExtension.'._'.$cnt.$ext;
406
				}
407
				//We store the result name in the byReference name parameter.
408
				$name = $nameWithoutExtension.'_'.$cnt.$ext;
409
				$ret = $rtry;
410
			}
411
		}
412
		return $ret;
413
	}
414
415
	/**
416
	 * Example function to process the files uploaded.  This one simply displays the files' data.
417
	 *
418
	 */
419
	public function defaultAfterUploadManagement() {
420
		$flist = '[defaultAfterUploadManagement] Nb uploaded files is: ' . sizeof($this->files);
0 ignored issues
show
Unused Code introduced by
The assignment to $flist is dead and can be removed.
Loading history...
421
		$flist = $this->classparams['http_flist_start'];
422
		foreach ($this->files as $f) {
423
			//$f is an array, that contains all info about the uploaded file.
424
			$this->logDebug('defaultAfterUploadManagement', "  Reading file ${f['name']}");
425
			$flist .= $this->classparams['http_flist_file_before'];
426
			$flist .= $f['name'];
427
			$flist .= $this->classparams['http_flist_file_between'];
428
			$flist .= $f['size'];
429
			$flist .= $this->classparams['http_flist_file_between'];
430
			$flist .= $f['relativePath'];
431
			$flist .= $this->classparams['http_flist_file_between'];
432
			$flist .= $f['fullName'];
433
			$flist .= $this->classparams['http_flist_file_between'];
434
			$flist .= $f['md5sum'];
435
			$addBR = false;
436
			foreach ($f as $key=>$value) {
437
				//If it's a specific key, let's display it:
438
				if ($key !== 'name' && $key !== 'size' && $key !== 'relativePath' && $key !== 'fullName' && $key !== 'md5sum') {
439
					if ($addBR) {
440
						$flist .= "<br>";
441
					} else {
442
						// First line. We must add a new 'official' list separator.
443
						$flist .= $this->classparams['http_flist_file_between'];
444
						$addBR = true;
445
					}
446
					$flist .= "$key => $value";
447
				}
448
			}
449
			$flist .= $this->classparams['http_flist_file_after'];
450
	}
451
	$flist .= $this->classparams['http_flist_end'];
452
453
	return $flist;
454
}
455
456
/**
457
 * Generation of the applet tag, and necessary things around (js content). Insertion of this into the content of the
458
 * page.
459
 * See the tag_jscript and tag_applet class parameters.
460
 */
461
private function generateAppletTag($str) {
462
	$this->logDebug('generateAppletTag', 'Entering function');
463
	$str = preg_replace('/'.$this->classparams['tag_jscript'].'/', $this->str_jsinit(), $str);
0 ignored issues
show
Bug introduced by
$this->str_jsinit() of type A is incompatible with the type string|string[] expected by parameter $replacement of preg_replace(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

463
	$str = preg_replace('/'.$this->classparams['tag_jscript'].'/', /** @scrutinizer ignore-type */ $this->str_jsinit(), $str);
Loading history...
464
	return preg_replace('/'.$this->classparams['tag_applet'].'/', $this->str_applet(), $str);
465
}
466
467
/**
468
 * This function is called when constructing the page, when we're not reveiving uploaded files. It 'just' construct
469
 * the applet tag, by calling the relevant function.
470
 *
471
 * This *must* be public, because it is called from PHP's output buffering
472
 */
473
public function interceptBeforeUpload($str) {
474
	$this->logDebug('interceptBeforeUpload', 'Entering function');
475
	return $this->generateAppletTag($str);
476
}
477
478
/**
479
 * This function displays the uploaded files description in the current page (see tag_flist class parameter)
480
 *
481
 * This *must* be public, because it is called from PHP's output buffering.
482
 */
483
public function interceptAfterUpload($str) {
484
	$this->logDebug('interceptAfterUpload', 'Entering function');
485
	$this->logPHPDebug('interceptAfterUpload', $this->files);
486
487
	if (count($this->files) > 0) {
488
		if (isset($this->classparams['callbackAfterUploadManagement'])) {
489
			$this->logDebug('interceptAfterUpload', 'Before call of ' .$this->classparams['callbackAfterUploadManagement']);
490
			$strForFListContent = call_user_func($this->classparams['callbackAfterUploadManagement'], $this, $this->files);
491
		} else {
492
			$strForFListContent = $this->defaultAfterUploadManagement();
493
		}
494
		$str = preg_replace('/'.$this->classparams['tag_flist'].'/', $strForFListContent, $str);
495
	}
496
	return $this->generateAppletTag($str);
497
}
498
499
/**
500
 * This method manages the receiving of the debug log, when an error occurs.
501
 */
502
private function receive_debug_log() {
503
	// handle error report
504
	if (isset($_POST['description']) && isset($_POST['log'])) {
505
		$msg = $_POST['log'];
506
		mail($this->classparams['errormail'], $_POST['description'], $msg);
507
	} else {
508
		if (isset($_SERVER['SERVER_ADMIN']))
509
		mail($_SERVER['SERVER_ADMIN'], 'Empty jupload error log',
510
                    'An empty log has just been posted.');
511
		$this->logPHPDebug('receive_debug_log', 'Empty error log received');
512
	}
513
	exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
514
}
515
516
/**
517
 * This method is the heart of the system. It manage the files sent by the applet, check the incoming parameters (md5sum) and
518
 * reconstruct the files sent in chunk mode.
519
 *
520
 * The result is stored in the $files array, and can then be managed by the function given in the callbackAfterUploadManagement
521
 * class parameter, or within the page whose URL is given in the afterUploadURL applet parameter.
522
 * Or you can Extend the class and redeclare defaultAfterUploadManagement() to your needs.
523
 */
524
private function receive_uploaded_files() {
525
	$this->logDebug('receive_uploaded_files', 'Entering POST management');
526
527
	if (session_id() == '') {
528
		session_start();
529
	}
530
	// we check for the session *after* handling possible error log
531
	// because an error could have happened because the session-id is missing.
532
	if (!isset($_SESSION['RF'][$this->classparams['var_prefix'].'size'])) {
533
		$this->abort('Invalid session (in afterupload, POST, check of size)');
534
	}
535
	if (!isset($_SESSION['RF'][$this->classparams['var_prefix'].'files'])) {
536
		$this->abort('Invalid session (in afterupload, POST, check of files)');
537
	}
538
	$this->files = $_SESSION['RF'][$this->classparams['var_prefix'].'files'];
539
	if (!is_array($this->files)) {
540
		$this->abort('Invalid session (in afterupload, POST, is_array(files))');
541
	}
542
	if ($this->appletparams['sendMD5Sum'] === 'true'  &&  !isset($_POST['md5sum'])) {
543
		$this->abort('Required POST variable md5sum is missing');
544
	}
545
	$cnt = 0;
546
	foreach ($_FILES as $key => $value) {
547
		//Let's read the $_FILES data
548
		if (isset($files_data)) {
549
			unset($files_data);
550
		}
551
		$jupart			= (isset($_POST['jupart']))		 		? (int)$_POST['jupart']		: 0;
552
		$jufinal		= (isset($_POST['jufinal']))	 		? (int)$_POST['jufinal']	: 1;
553
		$relpaths		= (isset($_POST['relpathinfo'])) 	? $_POST['relpathinfo']		: null;
554
		$md5sums		= (isset($_POST['md5sum']))				? $_POST['md5sum']				: null;
555
		$mimetypes 	= (isset($_POST['mimetype'])) 	 	? $_POST['mimetype'] 			: null;
556
		//$relpaths = (isset($_POST["relpathinfo$cnt"])) ? $_POST["relpathinfo$cnt"] : null;
557
		//$md5sums = (isset($_POST["md5sum$cnt"])) ? $_POST["md5sum$cnt"] : null;
558
559
		if (gettype($relpaths) === 'string') {
560
			$relpaths = array($relpaths);
561
		}
562
		if (gettype($md5sums) === 'string') {
563
			$md5sums = array($md5sums);
564
		}
565
		if ($this->appletparams['sendMD5Sum'] === 'true'  && !is_array($md5sums)) {
566
			$this->abort('Expecting an array of MD5 checksums');
567
		}
568
		if (!is_array($relpaths)) {
569
			$this->abort('Expecting an array of relative paths');
570
		}
571
		if (!is_array($mimetypes)) {
572
			$this->abort('Expecting an array of MIME types');
573
		}
574
		// Check the MIME type (note: this is easily forged!)
575
		if (isset($this->classparams['allowed_mime_types']) && is_array($this->classparams['allowed_mime_types'])) {
576
			if (!in_array($mimetypes[$cnt], $this->classparams['allowed_mime_types'])) {
577
				$this->abort('MIME type '.$mimetypes[$cnt].' not allowed');
578
			}
579
		}
580
		if (isset($this->classparams['allowed_file_extensions']) && is_array($this->classparams['allowed_file_extensions'])) {
581
			$fileExtension = substr(strrchr($value['name'][$cnt], "."), 1);
582
			if (!in_array($fileExtension, $this->classparams['allowed_file_extensions'])) {
583
				$this->abort('File extension '.$fileExtension.' not allowed');
584
			}
585
		}
586
587
		$dstdir = $this->classparams['destdir'];
588
		$dstname = $dstdir.'/'.$this->classparams['tmp_prefix'].session_id();
589
		$tmpname = $dstdir.'/'.$this->classparams['tmp_prefix'].'tmp'.session_id();
590
591
		// Controls are now done. Let's store the current uploaded files properties in an array, for future use.
592
		$files_data['name']					= $value['name'][$cnt];
593
		$files_data['size']					= 'not calculated yet';
594
		$files_data['tmp_name']			= $value['tmp_name'][$cnt];
595
		$files_data['error']    		= $value['error'][$cnt];
596
		$files_data['relativePath'] = $relpaths[$cnt];
597
		$files_data['md5sum']  			= $md5sums[$cnt];
598
		$files_data['mimetype']  		= $mimetypes[$cnt];
599
600
		if (!move_uploaded_file($files_data['tmp_name'], $tmpname)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $files_data seems to be defined later in this foreach loop on line 592. Are you sure it is defined here?
Loading history...
601
			if ($classparams['verbose_errors']) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $classparams seems to be never defined.
Loading history...
602
				$this->abort("Unable to move uploaded file (from ${files_data['tmp_name']} to $tmpname)");
603
		} else {
604
			trigger_error("Unable to move uploaded file (from ${files_data['tmp_name']} to $tmpname)",E_USER_WARNING);
605
			$this->abort("Unable to move uploaded file");
606
	}
607
}
608
609
// In demo mode, no file storing is done. We just delete the newly uploaded file.
610
if ($this->classparams['demo_mode']) {
611
	if ($jufinal || (!$jupart)) {
612
		if ($jupart) {
613
			$files_data['size']		= ($jupart-1) * $this->appletparams['maxChunkSize'] + filesize($tmpname);
614
		} else {
615
			$files_data['size']		= filesize($tmpname);
616
		}
617
		$files_data['fullName']	= 'Demo mode<BR>No file storing';
618
		array_push($this->files, $files_data);
619
	}
620
	unlink($tmpname);
621
	$cnt++;
622
	continue;
623
}
624
//If we get here, the upload is a real one (no demo)
625
if ($jupart) {
626
	// got a chunk of a multi-part upload
627
	$len = filesize($tmpname);
628
	$_SESSION['RF'][$this->classparams['var_prefix'].'size'] += $len;
629
	if ($len > 0) {
630
		$src = fopen($tmpname, 'rb');
631
		$dst = fopen($dstname, ($jupart == 1) ? 'wb' : 'ab');
632
		while ($len > 0) {
633
			$rlen = ($len > 8192) ? 8192 : $len;
634
			$buf = fread($src, $rlen);
0 ignored issues
show
Bug introduced by
It seems like $src can also be of type false; however, parameter $handle of fread() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

634
			$buf = fread(/** @scrutinizer ignore-type */ $src, $rlen);
Loading history...
635
			if (!$buf) {
636
				fclose($src);
0 ignored issues
show
Bug introduced by
It seems like $src can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

636
				fclose(/** @scrutinizer ignore-type */ $src);
Loading history...
637
				fclose($dst);
638
				unlink($dstname);
639
				$this->abort('read IO error');
640
			}
641
			if (!fwrite($dst, $buf, $rlen)) {
0 ignored issues
show
Bug introduced by
It seems like $dst can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

641
			if (!fwrite(/** @scrutinizer ignore-type */ $dst, $buf, $rlen)) {
Loading history...
642
				fclose($src);
643
				fclose($dst);
644
				unlink($dstname);
645
				$this->abort('write IO error');
646
			}
647
			$len -= $rlen;
648
		}
649
		fclose($src);
650
		fclose($dst);
651
		unlink($tmpname);
652
	}
653
	if ($jufinal) {
654
		// This is the last chunk. Check total lenght and
655
		// rename it to it's final name.
656
		$dlen = filesize($dstname);
657
		if ($dlen != $_SESSION['RF'][$this->classparams['var_prefix'].'size'])
658
		$this->abort('file size mismatch');
659
		if ($this->appletparams['sendMD5Sum'] === 'true' ) {
660
			if ($md5sums[$cnt] != md5_file($dstname))
661
			$this->abort('MD5 checksum mismatch');
662
		}
663
		// remove zero sized files
664
		if (($dlen > 0) || $this->classparams['allow_zerosized']) {
665
			$dstfinal = $this->dstfinal($files_data['name'],$files_data['relativePath']);
666
			if (!rename($dstname, $dstfinal))
667
			$this->abort('rename IO error');
668
			$_umask = umask(0); 	// override the system mask
669
			if (!chmod($dstfinal, $this->classparams['fileperm']))
670
				$this->abort('chmod IO error');
671
			umask($_umask);
672
			$files_data['size']		= filesize($dstfinal);
673
			$files_data['fullName']	= $dstfinal;
674
			$files_data['path']	= fix_dirname($dstfinal);
675
			array_push($this->files, $files_data);
676
		} else {
677
			unlink($dstname);
678
		}
679
		// reset session var
680
		$_SESSION['RF'][$this->classparams['var_prefix'].'size'] = 0;
681
	}
682
} else {
683
	// Got a single file upload. Trivial.
684
	if ($this->appletparams['sendMD5Sum'] === 'true' ) {
685
		if ($md5sums[$cnt] != md5_file($tmpname))
686
			$this->abort('MD5 checksum mismatch');
687
	}
688
	$dstfinal = $this->dstfinal($files_data['name'],$files_data['relativePath']);
689
	if (!rename($tmpname, $dstfinal))
690
	$this->abort('rename IO error');
691
	$_umask = umask(0); 	// override the system mask
692
	if (!chmod($dstfinal, $this->classparams['fileperm']))
693
		$this->abort('chmod IO error');
694
	umask($_umask);
695
	$files_data['size']		= filesize($dstfinal);
696
	$files_data['fullName']	= $dstfinal;
697
	$files_data['path']	= fix_dirname($dstfinal);
698
	array_push($this->files, $files_data);
699
}
700
$cnt++;
701
}
702
703
echo $this->appletparams['stringUploadSuccess']."\n";
704
$_SESSION['RF'][$this->classparams['var_prefix'].'files'] = $this->files;
705
session_write_close();
706
exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
707
}
708
709
/**
710
 *
711
 *
712
 */
713
private function page_start() {
714
	$this->logDebug('page_start', 'Entering function');
715
716
	// If the applet checks for the serverProtocol, it issues a HEAD request
717
	// -> Simply return an empty doc.
718
	if ($_SERVER['REQUEST_METHOD'] === 'HEAD') {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
719
		// Nothing to do
720
721
	} else if ($_SERVER['REQUEST_METHOD'] === 'GET') {
722
		// A GET request means: return upload page
723
		$this->logDebug('page_start', 'Entering GET management');
724
725
		if (session_id() == '') {
726
			session_start();
727
		}
728
		if (isset($_GET['afterupload'])) {
729
			$this->logDebug('page_start', 'afterupload is set');
730
			if (!isset($_SESSION['RF'][$this->classparams['var_prefix'].'files'])) {
731
				$this->abort('Invalid session (in afterupload, GET, check of $_SESSION["RF"]): files array is not set');
732
			}
733
			$this->files = $_SESSION['RF'][$this->classparams['var_prefix'].'files'];
734
			if (!is_array($this->files)) {
735
				$this->abort('Invalid session (in afterupload, GET, check of is_array(files)): files is not an array');
736
			}
737
			// clear session data ready for new upload
738
			$_SESSION['RF'][$this->classparams['var_prefix'].'files'] = array();
739
740
			// start intercepting the content of the calling page, to display the upload result.
741
			ob_start(array(& $this, 'interceptAfterUpload'));
742
743
		} else {
744
			$this->logDebug('page_start', 'afterupload is not set');
745
			if ($this->classparams['session_regenerate']) {
746
				session_regenerate_id(true);
747
			}
748
			$this->files = array();
749
			$_SESSION['RF'][$this->classparams['var_prefix'].'size'] = 0;
750
			$_SESSION['RF'][$this->classparams['var_prefix'].'files'] = $this->files;
751
			// start intercepting the content of the calling page, to display the applet tag.
752
			ob_start(array(& $this, 'interceptBeforeUpload'));
753
		}
754
755
	} else if ($_SERVER['REQUEST_METHOD'] === 'POST') {
756
		// If we got a POST request, this is the real work.
757
		if (isset($_GET['errormail'])) {
758
			//Hum, an error occurs on server side. Let's manage the debug log, that we just received.
759
			$this->receive_debug_log();
760
		} else {
761
			$this->receive_uploaded_files();
762
		}
763
	}
764
}
765
}
766
767
// PHP end tag omitted intentionally!!
768