JUpload::receive_uploaded_files()   F
last analyzed

Complexity

Conditions 51
Paths > 20000

Size

Total Lines 191
Code Lines 130

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 51
eloc 130
c 1
b 0
f 0
nc 136249376
nop 0
dl 0
loc 191
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
class JUpload
54
{
55
    public $appletparams;
56
    public $classparams;
57
    public $files;
58
59
    /**
60
     * JUpload constructor.
61
     * @param array $appletparams
62
     * @param array $classparams
63
     */
64
    public function __construct($appletparams = [], $classparams = [])
65
    {
66
        if ('array' !== gettype($classparams)) {
67
            $this->abort('Invalid type of parameter classparams: Expecting an array');
68
        }
69
        if ('array' !== gettype($appletparams)) {
70
            $this->abort('Invalid type of parameter appletparams: Expecting an array');
71
        }
72
73
        // set some defaults for the applet params
74
        if (!isset($appletparams['afterUploadURL'])) {
75
            $appletparams['afterUploadURL'] = $_SERVER['PHP_SELF'] . '?afterupload=1';
76
        }
77
        if (!isset($appletparams['name'])) {
78
            $appletparams['name'] = 'JUpload';
79
        }
80
        if (!isset($appletparams['archive'])) {
81
            $appletparams['archive'] = 'wjhk.jupload.jar';
82
        }
83
        if (!isset($appletparams['code'])) {
84
            $appletparams['code'] = 'wjhk.jupload2.JUploadApplet';
85
        }
86
        if (!isset($appletparams['debugLevel'])) {
87
            $appletparams['debugLevel'] = 0;
88
        }
89
        if (!isset($appletparams['httpUploadParameterType'])) {
90
            $appletparams['httpUploadParameterType'] = 'array';
91
        }
92
        if (!isset($appletparams['showLogWindow'])) {
93
            $appletparams['showLogWindow'] = ($appletparams['debugLevel'] > 0) ? 'true' : 'false';
94
        }
95
        if (!isset($appletparams['width'])) {
96
            $appletparams['width'] = 640;
97
        }
98
        if (!isset($appletparams['height'])) {
99
            $appletparams['height'] = ('true' === $appletparams['showLogWindow']) ? 500 : 300;
100
        }
101
        if (!isset($appletparams['mayscript'])) {
102
            $appletparams['mayscript'] = 'true';
103
        }
104
        if (!isset($appletparams['scriptable'])) {
105
            $appletparams['scriptable'] = 'false';
106
        }
107
        //if (!isset($appletparams['stringUploadSuccess']))
108
        $appletparams['stringUploadSuccess'] = 'SUCCESS';
109
        //if (!isset($appletparams['stringUploadError']))
110
        $appletparams['stringUploadError'] = 'ERROR: (.*)';
111
        $maxpost = $this->tobytes(ini_get('post_max_size'));
112
        $maxmem = $this->tobytes(ini_get('memory_limit'));
113
        $maxfs = $this->tobytes(ini_get('upload_max_filesize'));
114
        $obd = ini_get('open_basedir');
115
        if (!isset($appletparams['maxChunkSize'])) {
116
            $maxchunk = ($maxpost < $maxmem) ? $maxpost : $maxmem;
117
            $maxchunk = ($maxchunk < $maxfs) ? $maxchunk : $maxfs;
118
            $maxchunk /= 4;
119
            $optchunk = ($maxchunk < 500000) ? $maxchunk : 500000;
120
            $appletparams['maxChunkSize'] = $optchunk;
121
        }
122
        $appletparams['maxChunkSize'] = $this->tobytes($appletparams['maxChunkSize']);
123
        if (!isset($appletparams['maxFileSize'])) {
124
            $appletparams['maxFileSize'] = $maxfs;
125
        }
126
        $appletparams['maxFileSize'] = $this->tobytes($appletparams['maxFileSize']);
127
        if (isset($classparams['errormail'])) {
128
            $appletparams['urlToSendErrorTo'] = $_SERVER['PHP_SELF'] . '?errormail';
129
        }
130
131
        // Same for class parameters
132
        if (!isset($classparams['demo_mode'])) {
133
            $classparams['demo_mode'] = false;
134
        }
135
        if ($classparams['demo_mode']) {
136
            $classparams['create_destdir'] = false;
137
            $classparams['allow_subdirs'] = true;
138
            $classparams['allow_zerosized'] = true;
139
            $classparams['duplicate'] = 'overwrite';
140
        }
141
        if (!isset($classparams['debug_php'])) {                                            // set true to log some messages in PHP log
142
            $classparams['debug_php'] = false;
143
        }
144
        if (!isset($this->classparams['allowed_mime_types'])) {                // array of allowed MIME type
145
            $classparams['allowed_mime_types'] = 'all';
146
        }
147
        if (!isset($this->classparams['allowed_file_extensions'])) {    // array of allowed file extensions
148
            $classparams['allowed_file_extensions'] = 'all';
149
        }
150
        if (!isset($classparams['verbose_errors'])) {                        // shouldn't display server info on a production site!
151
            $classparams['verbose_errors'] = true;
152
        }
153
        if (!isset($classparams['session_regenerate'])) {
154
            $classparams['session_regenerate'] = false;
155
        }
156
        if (!isset($classparams['create_destdir'])) {
157
            $classparams['create_destdir'] = true;
158
        }
159
        if (!isset($classparams['allow_subdirs'])) {
160
            $classparams['allow_subdirs'] = false;
161
        }
162
        if (!isset($classparams['spaces_in_subdirs'])) {
163
            $classparams['spaces_in_subdirs'] = false;
164
        }
165
        if (!isset($classparams['allow_zerosized'])) {
166
            $classparams['allow_zerosized'] = false;
167
        }
168
        if (!isset($classparams['duplicate'])) {
169
            $classparams['duplicate'] = 'rename';
170
        }
171
        if (!isset($classparams['dirperm'])) {
172
            $classparams['dirperm'] = 0755;
173
        }
174
        if (!isset($classparams['fileperm'])) {
175
            $classparams['fileperm'] = 0644;
176
        }
177
        if (!isset($classparams['destdir'])) {
178
            if ('' != $obd) {
179
                $classparams['destdir'] = $obd;
180
            } else {
181
                $classparams['destdir'] = '/var/tmp/jupload_test';
182
            }
183
        } else {
184
            $classparams['destdir'] = str_replace('~', ' ', $classparams['destdir']);
185
        }
186
        if ($classparams['create_destdir']) {
187
            $_umask = umask(0);    // override the system mask
188
            @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

188
            /** @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...
189
            umask($_umask);
190
        }
191
        if (!is_dir($classparams['destdir']) && is_writable($classparams['destdir'])) {
192
            $this->abort('Destination dir not accessible');
193
        }
194
        if (!isset($classparams['tmp_prefix'])) {
195
            $classparams['tmp_prefix'] = 'jutmp.';
196
        }
197
        if (!isset($classparams['var_prefix'])) {
198
            $classparams['var_prefix'] = 'juvar.';
199
        }
200
        if (!isset($classparams['jscript_wrapper'])) {
201
            $classparams['jscript_wrapper'] = 'JUploadSetProperty';
202
        }
203
        if (!isset($classparams['tag_jscript'])) {
204
            $classparams['tag_jscript'] = '<!--JUPLOAD_JSCRIPT-->';
205
        }
206
        if (!isset($classparams['tag_applet'])) {
207
            $classparams['tag_applet'] = '<!--JUPLOAD_APPLET-->';
208
        }
209
        if (!isset($classparams['tag_flist'])) {
210
            $classparams['tag_flist'] = '<!--JUPLOAD_FILES-->';
211
        }
212
        if (!isset($classparams['http_flist_start'])) {
213
            $classparams['http_flist_start'] = "<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>";
214
        }
215
        if (!isset($classparams['http_flist_end'])) {
216
            $classparams['http_flist_end'] = "</table>\n";
217
        }
218
        if (!isset($classparams['http_flist_file_before'])) {
219
            $classparams['http_flist_file_before'] = '<tr><td>';
220
        }
221
        if (!isset($classparams['http_flist_file_between'])) {
222
            $classparams['http_flist_file_between'] = '</td><td>';
223
        }
224
        if (!isset($classparams['http_flist_file_after'])) {
225
            $classparams['http_flist_file_after'] = "</td></tr>\n";
226
        }
227
228
        $this->appletparams = $appletparams;
229
        $this->classparams = $classparams;
230
        $this->page_start();
231
    }
232
233
    /**
234
     * Return an array of uploaded files * The array contains: name, size, tmp_name, error,
235
     * relativePath, md5sum, mimetype, fullName, path
236
     */
237
    public function uploadedfiles()
238
    {
239
        return $this->files;
240
    }
241
242
    /**
243
     * Log a message on the current output, as a HTML comment.
244
     * @param      $function
245
     * @param      $msg
246
     * @param bool $htmlComment
247
     */
248
    protected function logDebug($function, $msg, $htmlComment = true)
249
    {
250
        $output = "[DEBUG] [$function] $msg";
251
        if ($htmlComment) {
252
            echo("<!-- $output -->\r\n");
253
        } else {
254
            echo("$output\r\n");
255
        }
256
    }
257
258
    /**
259
     * Log a message to the PHP log.
260
     * Declared "protected" so it may be Extended if you require customised logging (e.g. particular log file location).
261
     * @param $function
262
     * @param $msg
263
     */
264
    protected function logPHPDebug($function, $msg)
265
    {
266
        if (true === $this->classparams['debug_php']) {
267
            $output = "[DEBUG] [$function] " . $this->arrayexpand($msg);
268
            error_log($output);
269
        }
270
    }
271
272
    /**
273
     * @param $array
274
     * @return string
275
     */
276
    private function arrayexpand($array)
277
    {
278
        $output = '';
279
        if (is_array($array)) {
280
            foreach ($array as $key => $value) {
281
                $output .= "\n " . $key . ' => ' . $this->arrayexpand($value);
282
            }
283
        } else {
284
            $output .= $array;
285
        }
286
287
        return $output;
288
    }
289
290
    /**
291
     * Convert a value ending in 'G','M' or 'K' to bytes
292
     * @param $val
293
     * @return int|string
294
     */
295
    private function tobytes($val)
296
    {
297
        $val = trim($val);
298
        $last = fix_strtolower($val[mb_strlen($val) - 1]);
299
        switch ($last) {
300
            case 'g':
301
                $val *= 1024;
302
            // no break
303
            case 'm':
304
                $val *= 1024;
305
            // no break
306
            case 'k':
307
                $val *= 1024;
308
        }
309
310
        return $val;
311
    }
312
313
    /**
314
     * Build a string, containing a javascript wrapper function
315
     * for setting applet properties via JavaScript. This is necessary,
316
     * because we use the "modern" method of including the applet (using
317
     * <object> resp. <embed> tags) in order to trigger automatic JRE downloading.
318
     * Therefore, in Netscape-like browsers, the applet is accessible via
319
     * the document.embeds[] array while in others, it is accessible via the
320
     * document.applets[] array.
321
     *
322
     * @return A string, containing the necessary wrapper function (named JUploadSetProperty)
323
     */
324
    private function str_jsinit()
325
    {
326
        $N = "\n";
327
        $name = $this->appletparams['name'];
328
        $ret = '<script type="text/javascript">' . $N;
329
        $ret .= '<!--' . $N;
330
        $ret .= 'function ' . $this->classparams['jscript_wrapper'] . '(name, value) {' . $N;
331
        $ret .= '  document.applets["' . $name . '"] == null || document.applets["' . $name . '"].setProperty(name,value);' . $N;
332
        $ret .= '  document.embeds["' . $name . '"] == null || document.embeds["' . $name . '"].setProperty(name,value);' . $N;
333
        $ret .= '}' . $N;
334
        $ret .= '//-->' . $N;
335
        $ret .= '</script>';
336
337
        return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret returns the type string which is incompatible with the documented return type A.
Loading history...
338
    }
339
340
    /**
341
     * Build a string, containing the applet tag with all parameters.
342
     *
343
     * @return A string, containing the applet tag
344
     */
345
    private function str_applet()
346
    {
347
        $N = "\n";
348
        $params = $this->appletparams;
349
        // return the actual applet tag
350
        $ret = '<object classid = "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"' . $N;
351
        $ret .= '  codebase = "http://java.sun.com/update/1.5.0/jinstall-1_5-windows-i586.cab#Version=5,0,0,3"' . $N;
352
        $ret .= '  width = "' . $params['width'] . '"' . $N;
353
        $ret .= '  height = "' . $params['height'] . '"' . $N;
354
        $ret .= '  name = "' . $params['name'] . '">' . $N;
355
        foreach ($params as $key => $val) {
356
            if ('width' !== $key && 'height' !== $key) {
357
                $ret .= '  <param name = "' . $key . '" value = "' . $val . '" />' . $N;
358
            }
359
        }
360
        $ret .= '  <comment>' . $N;
361
        $ret .= '    <embed' . $N;
362
        $ret .= '      type = "application/x-java-applet;version=1.5"' . $N;
363
        foreach ($params as $key => $val) {
364
            $ret .= '      ' . $key . ' = "' . $val . '"' . $N;
365
        }
366
        $ret .= '      pluginspage = "http://java.sun.com/products/plugin/index.html#download">' . $N;
367
        $ret .= '      <noembed>' . $N;
368
        $ret .= '        Java 1.5 or higher plugin required.' . $N;
369
        $ret .= '      </noembed>' . $N;
370
        $ret .= '    </embed>' . $N;
371
        $ret .= '  </comment>' . $N;
372
        $ret .= '</object>';
373
374
        return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret returns the type string which is incompatible with the documented return type A.
Loading history...
375
    }
376
377
    /**
378
     * @param string $msg
379
     */
380
    private function abort($msg = '')
381
    {
382
        $this->cleanup();
383
        if ('' != $msg) {
384
            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...
385
        }
386
        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...
387
    }
388
389
    /**
390
     * @param string $msg
391
     */
392
    private function warning($msg = '')
393
    {
394
        $this->cleanup();
395
        if ('' != $msg) {
396
            echo('WARNING: ' . $msg . "\n");
397
        }
398
        echo $this->appletparams['stringUploadSuccess'] . "\n";
399
        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...
400
    }
401
402
    private function cleanup()
403
    {
404
        // remove all uploaded files of *this* request
405
        if (isset($_FILES)) {
406
            foreach ($_FILES as $key => $val) {
407
                @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

407
                /** @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...
408
            }
409
        }
410
        // remove accumulated file, if any.
411
        @unlink($this->classparams['destdir'] . '/' . $this->classparams['tmp_prefix'] . session_id());
412
        @unlink($this->classparams['destdir'] . '/' . $this->classparams['tmp_prefix'] . 'tmp' . session_id());
413
        // reset session var
414
        $_SESSION[$this->classparams['var_prefix'] . 'size'] = 0;
415
    }
416
417
    /**
418
     * @param $path
419
     */
420
    private function mkdirp($path)
421
    {
422
        // create subdir (hierary) below destdir;
423
        $dirs = explode('/', $path);
424
        $path = $this->classparams['destdir'];
425
        foreach ($dirs as $dir) {
426
            $path .= '/' . $dir;
427
            if (!file_exists($path)) {  // @ does NOT always supress the error!
428
                $_umask = umask(0);    // override the system mask
429
                @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

429
                /** @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...
430
                umask($_umask);
431
            }
432
        }
433
        if (!is_dir($path) && is_writable($path)) {
434
            $this->abort('Destination dir not accessible');
435
        }
436
    }
437
438
    /**
439
     * This method:
440
     * - Replaces some potentially dangerous characters by '_' (in the given name an relative path)
441
     * - Checks if a files of the same name already exists.
442
     *      - If no: no problem.
443
     *      - If yes, and the duplicate class param is set to rename, the file is renamed.
444
     *      - If yes, and the duplicate class param is set to overwrite, the file is not renamed. The existing one will be erased.
445
     *      - If yes, and the duplicate class param is set to reject, an error is thrown.
446
     * @param $name
447
     * @param $subdir
448
     * @return string
449
     */
450
    private function dstfinal(&$name, &$subdir)
451
    {
452
        $name = preg_replace('![`$\\\\/|]!', '_', $name);
453
        if ($this->classparams['allow_subdirs'] && ('' != $subdir)) {
454
            $subdir = trim(preg_replace('!\\\\!', '/', $subdir), '/');
455
            $subdir = preg_replace('![`$|]!', '_', $subdir);
456
            if (!$this->classparams['spaces_in_subdirs']) {
457
                $subdir = str_replace(' ', '_', $subdir);
458
            }
459
            // recursively create subdir
460
            if (!$this->classparams['demo_mode']) {
461
                $this->mkdirp($subdir);
462
            }
463
            // append a slash
464
            $subdir .= '/';
465
        } else {
466
            $subdir = '';
467
        }
468
        $ret = $this->classparams['destdir'] . '/' . $subdir . $name;
469
        if (file_exists($ret)) {
470
            if ('overwrite' === $this->classparams['duplicate']) {
471
                return $ret;
472
            }
473
            if ('reject' === $this->classparams['duplicate']) {
474
                $this->abort('A file with the same name already exists');
475
            }
476
            if ('warning' === $this->classparams['duplicate']) {
477
                $this->warning("File $name already exists - rejected");
478
            }
479
            if ('rename' === $this->classparams['duplicate']) {
480
                $cnt = 1;
481
                $dir = $this->classparams['destdir'] . '/' . $subdir;
482
                $ext = mb_strrchr($name, '.');
483
                if ($ext) {
484
                    $nameWithoutExtension = mb_substr($name, 0, mb_strlen($name) - mb_strlen($ext));
485
                } else {
486
                    $ext = '';
487
                    $nameWithoutExtension = $name;
488
                }
489
490
                $rtry = $dir . $nameWithoutExtension . '_' . $cnt . $ext;
491
                while (file_exists($rtry)) {
492
                    ++$cnt;
493
                    $rtry = $dir . $nameWithoutExtension . '._' . $cnt . $ext;
494
                }
495
                //We store the result name in the byReference name parameter.
496
                $name = $nameWithoutExtension . '_' . $cnt . $ext;
497
                $ret = $rtry;
498
            }
499
        }
500
501
        return $ret;
502
    }
503
504
    /**
505
     * Example function to process the files uploaded.  This one simply displays the files' data.
506
     */
507
    public function defaultAfterUploadManagement()
508
    {
509
        $flist = '[defaultAfterUploadManagement] Nb uploaded files is: ' . count($this->files);
0 ignored issues
show
Unused Code introduced by
The assignment to $flist is dead and can be removed.
Loading history...
510
        $flist = $this->classparams['http_flist_start'];
511
        foreach ($this->files as $f) {
512
            //$f is an array, that contains all info about the uploaded file.
513
            $this->logDebug('defaultAfterUploadManagement', "  Reading file ${f['name']}");
514
            $flist .= $this->classparams['http_flist_file_before'];
515
            $flist .= $f['name'];
516
            $flist .= $this->classparams['http_flist_file_between'];
517
            $flist .= $f['size'];
518
            $flist .= $this->classparams['http_flist_file_between'];
519
            $flist .= $f['relativePath'];
520
            $flist .= $this->classparams['http_flist_file_between'];
521
            $flist .= $f['fullName'];
522
            $flist .= $this->classparams['http_flist_file_between'];
523
            $flist .= $f['md5sum'];
524
            $addBR = false;
525
            foreach ($f as $key => $value) {
526
                //If it's a specific key, let's display it:
527
                if ('name' !== $key && 'size' !== $key && 'relativePath' !== $key && 'fullName' !== $key && 'md5sum' !== $key) {
528
                    if ($addBR) {
529
                        $flist .= '<br>';
530
                    } else {
531
                        // First line. We must add a new 'official' list separator.
532
                        $flist .= $this->classparams['http_flist_file_between'];
533
                        $addBR = true;
534
                    }
535
                    $flist .= "$key => $value";
536
                }
537
            }
538
            $flist .= $this->classparams['http_flist_file_after'];
539
        }
540
        $flist .= $this->classparams['http_flist_end'];
541
542
        return $flist;
543
    }
544
545
    /**
546
     * Generation of the applet tag, and necessary things around (js content). Insertion of this into the content of the
547
     * page.
548
     * See the tag_jscript and tag_applet class parameters.
549
     * @param $str
550
     * @return string|string[]|null
551
     */
552
    private function generateAppletTag($str)
553
    {
554
        $this->logDebug('generateAppletTag', 'Entering function');
555
        $str = preg_replace('/' . $this->classparams['tag_jscript'] . '/', $this->str_jsinit(), $str);
556
557
        return preg_replace('/' . $this->classparams['tag_applet'] . '/', $this->str_applet(), $str);
558
    }
559
560
    /**
561
     * This function is called when constructing the page, when we're not reveiving uploaded files. It 'just' construct
562
     * the applet tag, by calling the relevant function.
563
     *
564
     * This *must* be public, because it is called from PHP's output buffering
565
     * @param $str
566
     * @return string|string[]|null
567
     */
568
    public function interceptBeforeUpload($str)
569
    {
570
        $this->logDebug('interceptBeforeUpload', 'Entering function');
571
572
        return $this->generateAppletTag($str);
573
    }
574
575
    /**
576
     * This function displays the uploaded files description in the current page (see tag_flist class parameter)
577
     *
578
     * This *must* be public, because it is called from PHP's output buffering.
579
     * @param $str
580
     * @return string|string[]|null
581
     */
582
    public function interceptAfterUpload($str)
583
    {
584
        $this->logDebug('interceptAfterUpload', 'Entering function');
585
        $this->logPHPDebug('interceptAfterUpload', $this->files);
586
587
        if (count($this->files) > 0) {
588
            if (isset($this->classparams['callbackAfterUploadManagement'])) {
589
                $this->logDebug('interceptAfterUpload', 'Before call of ' . $this->classparams['callbackAfterUploadManagement']);
590
                $strForFListContent = call_user_func($this->classparams['callbackAfterUploadManagement'], $this, $this->files);
591
            } else {
592
                $strForFListContent = $this->defaultAfterUploadManagement();
593
            }
594
            $str = preg_replace('/' . $this->classparams['tag_flist'] . '/', $strForFListContent, $str);
595
        }
596
597
        return $this->generateAppletTag($str);
598
    }
599
600
    /**
601
     * This method manages the receiving of the debug log, when an error occurs.
602
     */
603
    private function receive_debug_log()
604
    {
605
        // handle error report
606
        if (isset($_POST['description']) && isset($_POST['log'])) {
607
            $msg = $_POST['log'];
608
            mail($this->classparams['errormail'], $_POST['description'], $msg);
609
        } else {
610
            if (isset($_SERVER['SERVER_ADMIN'])) {
611
                mail(
612
                    $_SERVER['SERVER_ADMIN'],
613
                    'Empty jupload error log',
614
                    'An empty log has just been posted.'
615
                );
616
            }
617
            $this->logPHPDebug('receive_debug_log', 'Empty error log received');
618
        }
619
        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...
620
    }
621
622
    /**
623
     * This method is the heart of the system. It manage the files sent by the applet, check the incoming parameters (md5sum) and
624
     * reconstruct the files sent in chunk mode.
625
     *
626
     * The result is stored in the $files array, and can then be managed by the function given in the callbackAfterUploadManagement
627
     * class parameter, or within the page whose URL is given in the afterUploadURL applet parameter.
628
     * Or you can Extend the class and redeclare defaultAfterUploadManagement() to your needs.
629
     */
630
    private function receive_uploaded_files()
631
    {
632
        $this->logDebug('receive_uploaded_files', 'Entering POST management');
633
634
        if ('' == session_id()) {
635
            session_start();
636
        }
637
        // we check for the session *after* handling possible error log
638
        // because an error could have happened because the session-id is missing.
639
        if (!isset($_SESSION[$this->classparams['var_prefix'] . 'size'])) {
640
            $this->abort('Invalid session (in afterupload, POST, check of size)');
641
        }
642
        if (!isset($_SESSION[$this->classparams['var_prefix'] . 'files'])) {
643
            $this->abort('Invalid session (in afterupload, POST, check of files)');
644
        }
645
        $this->files = $_SESSION[$this->classparams['var_prefix'] . 'files'];
646
        if (!is_array($this->files)) {
647
            $this->abort('Invalid session (in afterupload, POST, is_array(files))');
648
        }
649
        if ('true' === $this->appletparams['sendMD5Sum'] && !isset($_POST['md5sum'])) {
650
            $this->abort('Required POST variable md5sum is missing');
651
        }
652
        $cnt = 0;
653
        foreach ($_FILES as $key => $value) {
654
            //Let's read the $_FILES data
655
            if (isset($files_data)) {
656
                unset($files_data);
657
            }
658
            $jupart = (isset($_POST['jupart'])) ? (int)$_POST['jupart'] : 0;
659
            $jufinal = (isset($_POST['jufinal'])) ? (int)$_POST['jufinal'] : 1;
660
            $relpaths = (isset($_POST['relpathinfo'])) ? $_POST['relpathinfo'] : null;
661
            $md5sums = (isset($_POST['md5sum'])) ? $_POST['md5sum'] : null;
662
            $mimetypes = (isset($_POST['mimetype'])) ? $_POST['mimetype'] : null;
663
            //$relpaths = (isset($_POST["relpathinfo$cnt"])) ? $_POST["relpathinfo$cnt"] : null;
664
            //$md5sums = (isset($_POST["md5sum$cnt"])) ? $_POST["md5sum$cnt"] : null;
665
666
            if ('string' === gettype($relpaths)) {
667
                $relpaths = [$relpaths];
668
            }
669
            if ('string' === gettype($md5sums)) {
670
                $md5sums = [$md5sums];
671
            }
672
            if ('true' === $this->appletparams['sendMD5Sum'] && !is_array($md5sums)) {
673
                $this->abort('Expecting an array of MD5 checksums');
674
            }
675
            if (!is_array($relpaths)) {
676
                $this->abort('Expecting an array of relative paths');
677
            }
678
            if (!is_array($mimetypes)) {
679
                $this->abort('Expecting an array of MIME types');
680
            }
681
            // Check the MIME type (note: this is easily forged!)
682
            if (isset($this->classparams['allowed_mime_types']) && is_array($this->classparams['allowed_mime_types'])) {
683
                if (!in_array($mimetypes[$cnt], $this->classparams['allowed_mime_types'])) {
684
                    $this->abort('MIME type ' . $mimetypes[$cnt] . ' not allowed');
685
                }
686
            }
687
            if (isset($this->classparams['allowed_file_extensions']) && is_array($this->classparams['allowed_file_extensions'])) {
688
                $fileExtension = mb_substr(mb_strrchr($value['name'][$cnt], '.'), 1);
689
                if (!in_array($fileExtension, $this->classparams['allowed_file_extensions'])) {
690
                    $this->abort('File extension ' . $fileExtension . ' not allowed');
691
                }
692
            }
693
694
            $dstdir = $this->classparams['destdir'];
695
            $dstname = $dstdir . '/' . $this->classparams['tmp_prefix'] . session_id();
696
            $tmpname = $dstdir . '/' . $this->classparams['tmp_prefix'] . 'tmp' . session_id();
697
698
            // Controls are now done. Let's store the current uploaded files properties in an array, for future use.
699
            $files_data['name'] = $value['name'][$cnt];
700
            $files_data['size'] = 'not calculated yet';
701
            $files_data['tmp_name'] = $value['tmp_name'][$cnt];
702
            $files_data['error'] = $value['error'][$cnt];
703
            $files_data['relativePath'] = $relpaths[$cnt];
704
            $files_data['md5sum'] = $md5sums[$cnt];
705
            $files_data['mimetype'] = $mimetypes[$cnt];
706
707
            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 699. Are you sure it is defined here?
Loading history...
708
                if ($classparams['verbose_errors']) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $classparams seems to be never defined.
Loading history...
709
                    $this->abort("Unable to move uploaded file (from ${files_data['tmp_name']} to $tmpname)");
710
                } else {
711
                    trigger_error("Unable to move uploaded file (from ${files_data['tmp_name']} to $tmpname)", E_USER_WARNING);
712
                    $this->abort('Unable to move uploaded file');
713
                }
714
            }
715
716
            // In demo mode, no file storing is done. We just delete the newly uploaded file.
717
            if ($this->classparams['demo_mode']) {
718
                if ($jufinal || (!$jupart)) {
719
                    if ($jupart) {
720
                        $files_data['size'] = ($jupart - 1) * $this->appletparams['maxChunkSize'] + filesize($tmpname);
721
                    } else {
722
                        $files_data['size'] = filesize($tmpname);
723
                    }
724
                    $files_data['fullName'] = 'Demo mode<BR>No file storing';
725
                    array_push($this->files, $files_data);
726
                }
727
                unlink($tmpname);
728
                ++$cnt;
729
                continue;
730
            }
731
            //If we get here, the upload is a real one (no demo)
732
            if ($jupart) {
733
                // got a chunk of a multi-part upload
734
                $len = filesize($tmpname);
735
                $_SESSION[$this->classparams['var_prefix'] . 'size'] += $len;
736
                if ($len > 0) {
737
                    $src = fopen($tmpname, 'rb');
738
                    $dst = fopen($dstname, (1 == $jupart) ? 'wb' : 'ab');
739
                    while ($len > 0) {
740
                        $rlen = ($len > 8192) ? 8192 : $len;
741
                        $buf = fread($src, $rlen);
742
                        if (!$buf) {
743
                            fclose($src);
744
                            fclose($dst);
745
                            unlink($dstname);
746
                            $this->abort('read IO error');
747
                        }
748
                        if (!fwrite($dst, $buf, $rlen)) {
749
                            fclose($src);
750
                            fclose($dst);
751
                            unlink($dstname);
752
                            $this->abort('write IO error');
753
                        }
754
                        $len -= $rlen;
755
                    }
756
                    fclose($src);
757
                    fclose($dst);
758
                    unlink($tmpname);
759
                }
760
                if ($jufinal) {
761
                    // This is the last chunk. Check total lenght and
762
                    // rename it to it's final name.
763
                    $dlen = filesize($dstname);
764
                    if ($dlen != $_SESSION[$this->classparams['var_prefix'] . 'size']) {
765
                        $this->abort('file size mismatch');
766
                    }
767
                    if ('true' === $this->appletparams['sendMD5Sum']) {
768
                        if ($md5sums[$cnt] != md5_file($dstname)) {
769
                            $this->abort('MD5 checksum mismatch');
770
                        }
771
                    }
772
                    // remove zero sized files
773
                    if (($dlen > 0) || $this->classparams['allow_zerosized']) {
774
                        $dstfinal = $this->dstfinal($files_data['name'], $files_data['relativePath']);
775
                        if (!rename($dstname, $dstfinal)) {
776
                            $this->abort('rename IO error');
777
                        }
778
                        $_umask = umask(0);    // override the system mask
779
                        if (!chmod($dstfinal, $this->classparams['fileperm'])) {
780
                            $this->abort('chmod IO error');
781
                        }
782
                        umask($_umask);
783
                        $files_data['size'] = filesize($dstfinal);
784
                        $files_data['fullName'] = $dstfinal;
785
                        $files_data['path'] = fix_dirname($dstfinal);
786
                        array_push($this->files, $files_data);
787
                    } else {
788
                        unlink($dstname);
789
                    }
790
                    // reset session var
791
                    $_SESSION[$this->classparams['var_prefix'] . 'size'] = 0;
792
                }
793
            } else {
794
                // Got a single file upload. Trivial.
795
                if ('true' === $this->appletparams['sendMD5Sum']) {
796
                    if ($md5sums[$cnt] != md5_file($tmpname)) {
797
                        $this->abort('MD5 checksum mismatch');
798
                    }
799
                }
800
                $dstfinal = $this->dstfinal($files_data['name'], $files_data['relativePath']);
801
                if (!rename($tmpname, $dstfinal)) {
802
                    $this->abort('rename IO error');
803
                }
804
                $_umask = umask(0);    // override the system mask
805
                if (!chmod($dstfinal, $this->classparams['fileperm'])) {
806
                    $this->abort('chmod IO error');
807
                }
808
                umask($_umask);
809
                $files_data['size'] = filesize($dstfinal);
810
                $files_data['fullName'] = $dstfinal;
811
                $files_data['path'] = fix_dirname($dstfinal);
812
                array_push($this->files, $files_data);
813
            }
814
            ++$cnt;
815
        }
816
817
        echo $this->appletparams['stringUploadSuccess'] . "\n";
818
        $_SESSION[$this->classparams['var_prefix'] . 'files'] = $this->files;
819
        session_write_close();
820
        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...
821
    }
822
823
    private function page_start()
824
    {
825
        $this->logDebug('page_start', 'Entering function');
826
827
        // If the applet checks for the serverProtocol, it issues a HEAD request
828
        // -> Simply return an empty doc.
829
        if ('HEAD' === $_SERVER['REQUEST_METHOD']) {
830
            // Nothing to do
831
        } elseif ('GET' === $_SERVER['REQUEST_METHOD']) {
832
            // A GET request means: return upload page
833
            $this->logDebug('page_start', 'Entering GET management');
834
835
            if ('' == session_id()) {
836
                session_start();
837
            }
838
            if (isset($_GET['afterupload'])) {
839
                $this->logDebug('page_start', 'afterupload is set');
840
                if (!isset($_SESSION[$this->classparams['var_prefix'] . 'files'])) {
841
                    $this->abort('Invalid session (in afterupload, GET, check of $_SESSION): files array is not set');
842
                }
843
                $this->files = $_SESSION[$this->classparams['var_prefix'] . 'files'];
844
                if (!is_array($this->files)) {
845
                    $this->abort('Invalid session (in afterupload, GET, check of is_array(files)): files is not an array');
846
                }
847
                // clear session data ready for new upload
848
                $_SESSION[$this->classparams['var_prefix'] . 'files'] = [];
849
850
                // start intercepting the content of the calling page, to display the upload result.
851
                ob_start([&$this, 'interceptAfterUpload']);
852
            } else {
853
                $this->logDebug('page_start', 'afterupload is not set');
854
                if ($this->classparams['session_regenerate']) {
855
                    session_regenerate_id(true);
856
                }
857
                $this->files = [];
858
                $_SESSION[$this->classparams['var_prefix'] . 'size'] = 0;
859
                $_SESSION[$this->classparams['var_prefix'] . 'files'] = $this->files;
860
                // start intercepting the content of the calling page, to display the applet tag.
861
                ob_start([&$this, 'interceptBeforeUpload']);
862
            }
863
        } elseif ('POST' === $_SERVER['REQUEST_METHOD']) {
864
            // If we got a POST request, this is the real work.
865
            if (isset($_GET['errormail'])) {
866
                //Hum, an error occurs on server side. Let's manage the debug log, that we just received.
867
                $this->receive_debug_log();
868
            } else {
869
                $this->receive_uploaded_files();
870
            }
871
        }
872
    }
873
}
874
875
// PHP end tag omitted intentionally!!
876