Passed
Branch master (f2d2e3)
by Michael
18:45
created

JUpload::receive_uploaded_files()   F

Complexity

Conditions 51
Paths > 20000

Size

Total Lines 191
Code Lines 130

Duplication

Lines 0
Ratio 0 %

Importance

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

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

397
                /** @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...
398
            }
399
        }
400
        // remove accumulated file, if any.
401
        @unlink($this->classparams['destdir'] . '/' . $this->classparams['tmp_prefix'] . session_id());
402
        @unlink($this->classparams['destdir'] . '/' . $this->classparams['tmp_prefix'] . 'tmp' . session_id());
403
        // reset session var
404
        $_SESSION[$this->classparams['var_prefix'] . 'size'] = 0;
405
406
        return;
407
    }
408
409
    private function mkdirp($path)
410
    {
411
        // create subdir (hierary) below destdir;
412
        $dirs = explode('/', $path);
413
        $path = $this->classparams['destdir'];
414
        foreach ($dirs as $dir) {
415
            $path .= '/' . $dir;
416
            if (!file_exists($path)) {  // @ does NOT always supress the error!
417
                $_umask = umask(0);    // override the system mask
418
                @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

418
                /** @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...
419
                umask($_umask);
420
            }
421
        }
422
        if (!is_dir($path) && is_writable($path)) {
423
            $this->abort('Destination dir not accessible');
424
        }
425
    }
426
427
    /**
428
     * This method:
429
     * - Replaces some potentially dangerous characters by '_' (in the given name an relative path)
430
     * - Checks if a files of the same name already exists.
431
     *      - If no: no problem.
432
     *      - If yes, and the duplicate class param is set to rename, the file is renamed.
433
     *      - If yes, and the duplicate class param is set to overwrite, the file is not renamed. The existing one will be erased.
434
     *      - If yes, and the duplicate class param is set to reject, an error is thrown.
435
     * @param $name
436
     * @param $subdir
437
     * @return string
438
     */
439
    private function dstfinal(&$name, &$subdir)
440
    {
441
        $name = preg_replace('![`$\\\\/|]!', '_', $name);
442
        if ($this->classparams['allow_subdirs'] && ('' != $subdir)) {
443
            $subdir = trim(preg_replace('!\\\\!', '/', $subdir), '/');
444
            $subdir = preg_replace('![`$|]!', '_', $subdir);
445
            if (!$this->classparams['spaces_in_subdirs']) {
446
                $subdir = str_replace(' ', '_', $subdir);
447
            }
448
            // recursively create subdir
449
            if (!$this->classparams['demo_mode']) {
450
                $this->mkdirp($subdir);
451
            }
452
            // append a slash
453
            $subdir .= '/';
454
        } else {
455
            $subdir = '';
456
        }
457
        $ret = $this->classparams['destdir'] . '/' . $subdir . $name;
458
        if (file_exists($ret)) {
459
            if ('overwrite' === $this->classparams['duplicate']) {
460
                return $ret;
461
            }
462
            if ('reject' === $this->classparams['duplicate']) {
463
                $this->abort('A file with the same name already exists');
464
            }
465
            if ('warning' === $this->classparams['duplicate']) {
466
                $this->warning("File $name already exists - rejected");
467
            }
468
            if ('rename' === $this->classparams['duplicate']) {
469
                $cnt = 1;
470
                $dir = $this->classparams['destdir'] . '/' . $subdir;
471
                $ext = strrchr($name, '.');
472
                if ($ext) {
473
                    $nameWithoutExtension = substr($name, 0, strlen($name) - strlen($ext));
474
                } else {
475
                    $ext                  = '';
476
                    $nameWithoutExtension = $name;
477
                }
478
479
                $rtry = $dir . $nameWithoutExtension . '_' . $cnt . $ext;
480
                while (file_exists($rtry)) {
481
                    ++$cnt;
482
                    $rtry = $dir . $nameWithoutExtension . '._' . $cnt . $ext;
483
                }
484
                //We store the result name in the byReference name parameter.
485
                $name = $nameWithoutExtension . '_' . $cnt . $ext;
486
                $ret  = $rtry;
487
            }
488
        }
489
490
        return $ret;
491
    }
492
493
    /**
494
     * Example function to process the files uploaded.  This one simply displays the files' data.
495
     *
496
     */
497
    public function defaultAfterUploadManagement()
498
    {
499
        $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...
500
        $flist = $this->classparams['http_flist_start'];
501
        foreach ($this->files as $f) {
502
            //$f is an array, that contains all info about the uploaded file.
503
            $this->logDebug('defaultAfterUploadManagement', "  Reading file ${f['name']}");
504
            $flist .= $this->classparams['http_flist_file_before'];
505
            $flist .= $f['name'];
506
            $flist .= $this->classparams['http_flist_file_between'];
507
            $flist .= $f['size'];
508
            $flist .= $this->classparams['http_flist_file_between'];
509
            $flist .= $f['relativePath'];
510
            $flist .= $this->classparams['http_flist_file_between'];
511
            $flist .= $f['fullName'];
512
            $flist .= $this->classparams['http_flist_file_between'];
513
            $flist .= $f['md5sum'];
514
            $addBR = false;
515
            foreach ($f as $key => $value) {
516
                //If it's a specific key, let's display it:
517
                if ('name' !== $key && 'size' !== $key && 'relativePath' !== $key && 'fullName' !== $key && 'md5sum' !== $key) {
518
                    if ($addBR) {
519
                        $flist .= '<br>';
520
                    } else {
521
                        // First line. We must add a new 'official' list separator.
522
                        $flist .= $this->classparams['http_flist_file_between'];
523
                        $addBR = true;
524
                    }
525
                    $flist .= "$key => $value";
526
                }
527
            }
528
            $flist .= $this->classparams['http_flist_file_after'];
529
        }
530
        $flist .= $this->classparams['http_flist_end'];
531
532
        return $flist;
533
    }
534
535
    /**
536
     * Generation of the applet tag, and necessary things around (js content). Insertion of this into the content of the
537
     * page.
538
     * See the tag_jscript and tag_applet class parameters.
539
     * @param $str
540
     * @return string|string[]|null
541
     */
542
    private function generateAppletTag($str)
543
    {
544
        $this->logDebug('generateAppletTag', 'Entering function');
545
        $str = preg_replace('/' . $this->classparams['tag_jscript'] . '/', $this->str_jsinit(), $str);
546
547
        return preg_replace('/' . $this->classparams['tag_applet'] . '/', $this->str_applet(), $str);
548
    }
549
550
    /**
551
     * This function is called when constructing the page, when we're not reveiving uploaded files. It 'just' construct
552
     * the applet tag, by calling the relevant function.
553
     *
554
     * This *must* be public, because it is called from PHP's output buffering
555
     * @param $str
556
     * @return string|string[]|null
557
     */
558
    public function interceptBeforeUpload($str)
559
    {
560
        $this->logDebug('interceptBeforeUpload', 'Entering function');
561
562
        return $this->generateAppletTag($str);
563
    }
564
565
    /**
566
     * This function displays the uploaded files description in the current page (see tag_flist class parameter)
567
     *
568
     * This *must* be public, because it is called from PHP's output buffering.
569
     * @param $str
570
     * @return string|string[]|null
571
     */
572
    public function interceptAfterUpload($str)
573
    {
574
        $this->logDebug('interceptAfterUpload', 'Entering function');
575
        $this->logPHPDebug('interceptAfterUpload', $this->files);
576
577
        if (count($this->files) > 0) {
578
            if (isset($this->classparams['callbackAfterUploadManagement'])) {
579
                $this->logDebug('interceptAfterUpload', 'Before call of ' . $this->classparams['callbackAfterUploadManagement']);
580
                $strForFListContent = call_user_func($this->classparams['callbackAfterUploadManagement'], $this, $this->files);
581
            } else {
582
                $strForFListContent = $this->defaultAfterUploadManagement();
583
            }
584
            $str = preg_replace('/' . $this->classparams['tag_flist'] . '/', $strForFListContent, $str);
585
        }
586
587
        return $this->generateAppletTag($str);
588
    }
589
590
    /**
591
     * This method manages the receiving of the debug log, when an error occurs.
592
     */
593
    private function receive_debug_log()
594
    {
595
        // handle error report
596
        if (isset($_POST['description']) && isset($_POST['log'])) {
597
            $msg = $_POST['log'];
598
            mail($this->classparams['errormail'], $_POST['description'], $msg);
599
        } else {
600
            if (isset($_SERVER['SERVER_ADMIN'])) {
601
                mail(
602
                    $_SERVER['SERVER_ADMIN'],
603
                    'Empty jupload error log',
604
                    'An empty log has just been posted.'
605
                );
606
            }
607
            $this->logPHPDebug('receive_debug_log', 'Empty error log received');
608
        }
609
        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...
610
    }
611
612
    /**
613
     * This method is the heart of the system. It manage the files sent by the applet, check the incoming parameters (md5sum) and
614
     * reconstruct the files sent in chunk mode.
615
     *
616
     * The result is stored in the $files array, and can then be managed by the function given in the callbackAfterUploadManagement
617
     * class parameter, or within the page whose URL is given in the afterUploadURL applet parameter.
618
     * Or you can Extend the class and redeclare defaultAfterUploadManagement() to your needs.
619
     */
620
    private function receive_uploaded_files()
621
    {
622
        $this->logDebug('receive_uploaded_files', 'Entering POST management');
623
624
        if ('' == session_id()) {
625
            session_start();
626
        }
627
        // we check for the session *after* handling possible error log
628
        // because an error could have happened because the session-id is missing.
629
        if (!isset($_SESSION[$this->classparams['var_prefix'] . 'size'])) {
630
            $this->abort('Invalid session (in afterupload, POST, check of size)');
631
        }
632
        if (!isset($_SESSION[$this->classparams['var_prefix'] . 'files'])) {
633
            $this->abort('Invalid session (in afterupload, POST, check of files)');
634
        }
635
        $this->files = $_SESSION[$this->classparams['var_prefix'] . 'files'];
636
        if (!is_array($this->files)) {
637
            $this->abort('Invalid session (in afterupload, POST, is_array(files))');
638
        }
639
        if ('true' === $this->appletparams['sendMD5Sum'] && !isset($_POST['md5sum'])) {
640
            $this->abort('Required POST variable md5sum is missing');
641
        }
642
        $cnt = 0;
643
        foreach ($_FILES as $key => $value) {
644
            //Let's read the $_FILES data
645
            if (isset($files_data)) {
646
                unset($files_data);
647
            }
648
            $jupart    = (isset($_POST['jupart'])) ? (int)$_POST['jupart'] : 0;
649
            $jufinal   = (isset($_POST['jufinal'])) ? (int)$_POST['jufinal'] : 1;
650
            $relpaths  = (isset($_POST['relpathinfo'])) ? $_POST['relpathinfo'] : null;
651
            $md5sums   = (isset($_POST['md5sum'])) ? $_POST['md5sum'] : null;
652
            $mimetypes = (isset($_POST['mimetype'])) ? $_POST['mimetype'] : null;
653
            //$relpaths = (isset($_POST["relpathinfo$cnt"])) ? $_POST["relpathinfo$cnt"] : null;
654
            //$md5sums = (isset($_POST["md5sum$cnt"])) ? $_POST["md5sum$cnt"] : null;
655
656
            if ('string' === gettype($relpaths)) {
657
                $relpaths = [$relpaths];
658
            }
659
            if ('string' === gettype($md5sums)) {
660
                $md5sums = [$md5sums];
661
            }
662
            if ('true' === $this->appletparams['sendMD5Sum'] && !is_array($md5sums)) {
663
                $this->abort('Expecting an array of MD5 checksums');
664
            }
665
            if (!is_array($relpaths)) {
666
                $this->abort('Expecting an array of relative paths');
667
            }
668
            if (!is_array($mimetypes)) {
669
                $this->abort('Expecting an array of MIME types');
670
            }
671
            // Check the MIME type (note: this is easily forged!)
672
            if (isset($this->classparams['allowed_mime_types']) && is_array($this->classparams['allowed_mime_types'])) {
673
                if (!in_array($mimetypes[$cnt], $this->classparams['allowed_mime_types'])) {
674
                    $this->abort('MIME type ' . $mimetypes[$cnt] . ' not allowed');
675
                }
676
            }
677
            if (isset($this->classparams['allowed_file_extensions']) && is_array($this->classparams['allowed_file_extensions'])) {
678
                $fileExtension = substr(strrchr($value['name'][$cnt], '.'), 1);
679
                if (!in_array($fileExtension, $this->classparams['allowed_file_extensions'])) {
680
                    $this->abort('File extension ' . $fileExtension . ' not allowed');
681
                }
682
            }
683
684
            $dstdir  = $this->classparams['destdir'];
685
            $dstname = $dstdir . '/' . $this->classparams['tmp_prefix'] . session_id();
686
            $tmpname = $dstdir . '/' . $this->classparams['tmp_prefix'] . 'tmp' . session_id();
687
688
            // Controls are now done. Let's store the current uploaded files properties in an array, for future use.
689
            $files_data['name']         = $value['name'][$cnt];
690
            $files_data['size']         = 'not calculated yet';
691
            $files_data['tmp_name']     = $value['tmp_name'][$cnt];
692
            $files_data['error']        = $value['error'][$cnt];
693
            $files_data['relativePath'] = $relpaths[$cnt];
694
            $files_data['md5sum']       = $md5sums[$cnt];
695
            $files_data['mimetype']     = $mimetypes[$cnt];
696
697
            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 689. Are you sure it is defined here?
Loading history...
698
                if ($classparams['verbose_errors']) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $classparams seems to be never defined.
Loading history...
699
                    $this->abort("Unable to move uploaded file (from ${files_data['tmp_name']} to $tmpname)");
700
                } else {
701
                    trigger_error("Unable to move uploaded file (from ${files_data['tmp_name']} to $tmpname)", E_USER_WARNING);
702
                    $this->abort('Unable to move uploaded file');
703
                }
704
            }
705
706
            // In demo mode, no file storing is done. We just delete the newly uploaded file.
707
            if ($this->classparams['demo_mode']) {
708
                if ($jufinal || (!$jupart)) {
709
                    if ($jupart) {
710
                        $files_data['size'] = ($jupart - 1) * $this->appletparams['maxChunkSize'] + filesize($tmpname);
711
                    } else {
712
                        $files_data['size'] = filesize($tmpname);
713
                    }
714
                    $files_data['fullName'] = 'Demo mode<BR>No file storing';
715
                    array_push($this->files, $files_data);
716
                }
717
                unlink($tmpname);
718
                ++$cnt;
719
                continue;
720
            }
721
            //If we get here, the upload is a real one (no demo)
722
            if ($jupart) {
723
                // got a chunk of a multi-part upload
724
                $len                                                 = filesize($tmpname);
725
                $_SESSION[$this->classparams['var_prefix'] . 'size'] += $len;
726
                if ($len > 0) {
727
                    $src = fopen($tmpname, 'rb');
728
                    $dst = fopen($dstname, (1 == $jupart) ? 'wb' : 'ab');
729
                    while ($len > 0) {
730
                        $rlen = ($len > 8192) ? 8192 : $len;
731
                        $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

731
                        $buf  = fread(/** @scrutinizer ignore-type */ $src, $rlen);
Loading history...
732
                        if (!$buf) {
733
                            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

733
                            fclose(/** @scrutinizer ignore-type */ $src);
Loading history...
734
                            fclose($dst);
735
                            unlink($dstname);
736
                            $this->abort('read IO error');
737
                        }
738
                        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

738
                        if (!fwrite(/** @scrutinizer ignore-type */ $dst, $buf, $rlen)) {
Loading history...
739
                            fclose($src);
740
                            fclose($dst);
741
                            unlink($dstname);
742
                            $this->abort('write IO error');
743
                        }
744
                        $len -= $rlen;
745
                    }
746
                    fclose($src);
747
                    fclose($dst);
748
                    unlink($tmpname);
749
                }
750
                if ($jufinal) {
751
                    // This is the last chunk. Check total lenght and
752
                    // rename it to it's final name.
753
                    $dlen = filesize($dstname);
754
                    if ($dlen != $_SESSION[$this->classparams['var_prefix'] . 'size']) {
755
                        $this->abort('file size mismatch');
756
                    }
757
                    if ('true' === $this->appletparams['sendMD5Sum']) {
758
                        if ($md5sums[$cnt] != md5_file($dstname)) {
759
                            $this->abort('MD5 checksum mismatch');
760
                        }
761
                    }
762
                    // remove zero sized files
763
                    if (($dlen > 0) || $this->classparams['allow_zerosized']) {
764
                        $dstfinal = $this->dstfinal($files_data['name'], $files_data['relativePath']);
765
                        if (!rename($dstname, $dstfinal)) {
766
                            $this->abort('rename IO error');
767
                        }
768
                        $_umask = umask(0);    // override the system mask
769
                        if (!chmod($dstfinal, $this->classparams['fileperm'])) {
770
                            $this->abort('chmod IO error');
771
                        }
772
                        umask($_umask);
773
                        $files_data['size']     = filesize($dstfinal);
774
                        $files_data['fullName'] = $dstfinal;
775
                        $files_data['path']     = fix_dirname($dstfinal);
776
                        array_push($this->files, $files_data);
777
                    } else {
778
                        unlink($dstname);
779
                    }
780
                    // reset session var
781
                    $_SESSION[$this->classparams['var_prefix'] . 'size'] = 0;
782
                }
783
            } else {
784
                // Got a single file upload. Trivial.
785
                if ('true' === $this->appletparams['sendMD5Sum']) {
786
                    if ($md5sums[$cnt] != md5_file($tmpname)) {
787
                        $this->abort('MD5 checksum mismatch');
788
                    }
789
                }
790
                $dstfinal = $this->dstfinal($files_data['name'], $files_data['relativePath']);
791
                if (!rename($tmpname, $dstfinal)) {
792
                    $this->abort('rename IO error');
793
                }
794
                $_umask = umask(0);    // override the system mask
795
                if (!chmod($dstfinal, $this->classparams['fileperm'])) {
796
                    $this->abort('chmod IO error');
797
                }
798
                umask($_umask);
799
                $files_data['size']     = filesize($dstfinal);
800
                $files_data['fullName'] = $dstfinal;
801
                $files_data['path']     = fix_dirname($dstfinal);
802
                array_push($this->files, $files_data);
803
            }
804
            ++$cnt;
805
        }
806
807
        echo $this->appletparams['stringUploadSuccess'] . "\n";
808
        $_SESSION[$this->classparams['var_prefix'] . 'files'] = $this->files;
809
        session_write_close();
810
        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...
811
    }
812
813
    /**
814
     *
815
     *
816
     */
817
    private function page_start()
818
    {
819
        $this->logDebug('page_start', 'Entering function');
820
821
        // If the applet checks for the serverProtocol, it issues a HEAD request
822
        // -> Simply return an empty doc.
823
        if ('HEAD' === $_SERVER['REQUEST_METHOD']) {
824
            // Nothing to do
825
826
        } elseif ('GET' === $_SERVER['REQUEST_METHOD']) {
827
            // A GET request means: return upload page
828
            $this->logDebug('page_start', 'Entering GET management');
829
830
            if ('' == session_id()) {
831
                session_start();
832
            }
833
            if (isset($_GET['afterupload'])) {
834
                $this->logDebug('page_start', 'afterupload is set');
835
                if (!isset($_SESSION[$this->classparams['var_prefix'] . 'files'])) {
836
                    $this->abort('Invalid session (in afterupload, GET, check of $_SESSION): files array is not set');
837
                }
838
                $this->files = $_SESSION[$this->classparams['var_prefix'] . 'files'];
839
                if (!is_array($this->files)) {
840
                    $this->abort('Invalid session (in afterupload, GET, check of is_array(files)): files is not an array');
841
                }
842
                // clear session data ready for new upload
843
                $_SESSION[$this->classparams['var_prefix'] . 'files'] = [];
844
845
                // start intercepting the content of the calling page, to display the upload result.
846
                ob_start([& $this, 'interceptAfterUpload']);
847
            } else {
848
                $this->logDebug('page_start', 'afterupload is not set');
849
                if ($this->classparams['session_regenerate']) {
850
                    session_regenerate_id(true);
851
                }
852
                $this->files                                          = [];
853
                $_SESSION[$this->classparams['var_prefix'] . 'size']  = 0;
854
                $_SESSION[$this->classparams['var_prefix'] . 'files'] = $this->files;
855
                // start intercepting the content of the calling page, to display the applet tag.
856
                ob_start([& $this, 'interceptBeforeUpload']);
857
            }
858
        } elseif ('POST' === $_SERVER['REQUEST_METHOD']) {
859
            // If we got a POST request, this is the real work.
860
            if (isset($_GET['errormail'])) {
861
                //Hum, an error occurs on server side. Let's manage the debug log, that we just received.
862
                $this->receive_debug_log();
863
            } else {
864
                $this->receive_uploaded_files();
865
            }
866
        }
867
    }
868
}
869
870
// PHP end tag omitted intentionally!!
871