Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
65.88% covered (warning)
65.88%
195 / 296
64.52% covered (warning)
64.52%
20 / 31
CRAP
0.00% covered (danger)
0.00%
0 / 1
phpbb_filespec_test
65.88% covered (warning)
65.88%
195 / 296
64.52% covered (warning)
64.52%
20 / 31
125.45
0.00% covered (danger)
0.00%
0 / 1
 setUp
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
6
 set_reflection_property
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 get_filespec
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 tearDown
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 test_empty_upload_ary
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 additional_checks_variables
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 test_additional_checks
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 test_additional_checks_dimensions
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 check_content_variables
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 test_check_content
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 clean_filename_variables
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 test_clean_filename_real
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 test_clean_filename_unique
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 test_clean_filename_unique_ext
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 data_clean_filename_avatar
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 test_clean_filename_avatar
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
 get_extension_variables
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 test_get_extension
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_image_variables
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 test_is_image
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 is_image_get_mimetype
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 test_is_image_get_mimetype
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 move_file_variables
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 test_move_file
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
2
 test_move_file_error
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 data_move_file_copy
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 test_move_file_copy
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
1
 data_move_file_imagesize
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
2
 test_move_file_imagesize
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
1
 test_uploadname
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 test_is_uploaded
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3*
4* This file is part of the phpBB Forum Software package.
5*
6* @copyright (c) phpBB Limited <https://www.phpbb.com>
7* @license GNU General Public License, version 2 (GPL-2.0)
8*
9* For full copyright and license information, please see
10* the docs/CREDITS.txt file.
11*
12*/
13
14class phpbb_filespec_test extends phpbb_test_case
15{
16    const TEST_COUNT = 100;
17    const PREFIX = 'phpbb_';
18    const UPLOAD_MAX_FILESIZE = 1000;
19
20    private $config;
21    private $filesystem;
22    public $path;
23
24    /** @var \phpbb\language\language */
25    protected $language;
26
27    /** @var string phpBB root path */
28    protected $phpbb_root_path;
29
30    protected $mimetype_guesser;
31
32    protected function setUp(): void
33    {
34        // Global $config required by unique_id
35        global $config, $phpbb_root_path, $phpEx;
36
37        if (!is_array($config))
38        {
39            $config = array();
40        }
41
42        $config['rand_seed'] = '';
43        $config['rand_seed_last_update'] = time() + 600;
44        // This config value is normally pulled from the database which is set
45        // to this value at install time.
46        // See: phpBB/install/schemas/schema_data.sql
47        $config['mime_triggers'] = 'body|head|html|img|plaintext|a href|pre|script|table|title';
48
49        $this->config = &$config;
50        $this->path = __DIR__ . '/fixture/';
51
52        // Create copies of the files for use in testing move_file
53        $iterator = new DirectoryIterator($this->path);
54        foreach ($iterator as $fileinfo)
55        {
56            if ($fileinfo->isDot() || $fileinfo->isDir())
57            {
58                continue;
59            }
60
61            copy($fileinfo->getPathname(), $this->path . 'copies/' . $fileinfo->getFilename() . '_copy');
62            if ($fileinfo->getFilename() === 'txt')
63            {
64                copy($fileinfo->getPathname(), $this->path . 'copies/' . $fileinfo->getFilename() . '_copy_2');
65            }
66        }
67
68        $guessers = array(
69            new \Symfony\Component\Mime\FileinfoMimeTypeGuesser(),
70            new \Symfony\Component\Mime\FileBinaryMimeTypeGuesser(),
71            new \phpbb\mimetype\content_guesser(),
72            new \phpbb\mimetype\extension_guesser(),
73        );
74        $guessers[2]->set_priority(-2);
75        $guessers[3]->set_priority(-2);
76        $this->mimetype_guesser = new \phpbb\mimetype\guesser($guessers);
77        $this->language = new \phpbb\language\language(new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx));
78
79        $this->filesystem = new \phpbb\filesystem\filesystem();
80        $this->phpbb_root_path = $phpbb_root_path;
81    }
82
83    private function set_reflection_property($class, $property_name, $value)
84    {
85        $property = new ReflectionProperty($class, $property_name);
86        $property->setValue($class, $value);
87    }
88
89    private function get_filespec($override = array())
90    {
91        // Initialise a blank filespec object for use with trivial methods
92        $upload_ary = array(
93            'name' => '',
94            'type' => '',
95            'size' => '',
96            'tmp_name' => '',
97            'error' => '',
98        );
99
100        $filespec = new \phpbb\files\filespec($this->filesystem, $this->language, new \bantu\IniGetWrapper\IniGetWrapper, new \FastImageSize\FastImageSize(), $this->phpbb_root_path, $this->mimetype_guesser);
101        return $filespec->set_upload_ary(array_merge($upload_ary, $override));
102    }
103
104    protected function tearDown(): void
105    {
106        $this->config = array();
107
108        $iterator = new DirectoryIterator($this->path . 'copies');
109        foreach ($iterator as $fileinfo)
110        {
111            $name = $fileinfo->getFilename();
112            if ($name[0] !== '.')
113            {
114                unlink($fileinfo->getPathname());
115            }
116        }
117    }
118
119    public function test_empty_upload_ary()
120    {
121        $filespec = new \phpbb\files\filespec($this->filesystem, $this->language, new \bantu\IniGetWrapper\IniGetWrapper, new \FastImageSize\FastImageSize(), $this->phpbb_root_path, $this->mimetype_guesser);
122        $this->assertInstanceOf('\phpbb\files\filespec', $filespec->set_upload_ary(array()));
123        $this->assertTrue($filespec->init_error());
124    }
125
126    public static function additional_checks_variables()
127    {
128        // False here just indicates the file is too large and fails the
129        // filespec::additional_checks method because of it. All other code
130        // paths in that method are covered elsewhere.
131        return array(
132            array('gif', true),
133            array('jpg', false),
134            array('png', true),
135            array('tif', false),
136            array('txt', false),
137        );
138    }
139
140    /**
141     * @dataProvider additional_checks_variables
142     */
143    public function test_additional_checks($filename, $expected)
144    {
145        $upload = new phpbb_mock_fileupload();
146        $filespec = $this->get_filespec();
147        $filespec->set_upload_namespace($upload);
148        $this->set_reflection_property($filespec, 'file_moved', true);
149        $this->set_reflection_property($filespec, 'filesize', $filespec->get_filesize($this->path . $filename));
150
151        $this->assertEquals($expected, $filespec->additional_checks());
152    }
153
154    public function test_additional_checks_dimensions()
155    {
156        $upload = new phpbb_mock_fileupload();
157        $filespec = $this->get_filespec();
158        $filespec->set_upload_namespace($upload);
159        $upload->valid_dimensions = false;
160        $this->set_reflection_property($filespec, 'file_moved', true);
161        $upload->max_filesize = 0;
162
163        $this->assertEquals(false, $filespec->additional_checks());
164        $this->assertSame(array('WRONG_SIZE'), $filespec->error);
165    }
166
167    public static function check_content_variables()
168    {
169        // False here indicates that a file is non-binary and contains
170        // disallowed content that makes IE report the mimetype incorrectly.
171        return array(
172            array('gif', true),
173            array('jpg', true),
174            array('png', true),
175            array('tif', true),
176            array('txt', false),
177        );
178    }
179
180    /**
181     * @dataProvider check_content_variables
182     */
183    public function test_check_content($filename, $expected)
184    {
185        $disallowed_content = explode('|', $this->config['mime_triggers']);
186        $filespec = $this->get_filespec(array('tmp_name' => $this->path . $filename));
187        $this->assertEquals($expected, $filespec->check_content($disallowed_content));
188        // All files should pass if $disallowed_content is empty
189        $this->assertEquals(true, $filespec->check_content(array()));
190    }
191
192    public static function clean_filename_variables()
193    {
194        $chunks = str_split('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\'\\" /:*?<>|[];(){},#+=-_`', 8);
195        return array(
196            array($chunks[0] . $chunks[7]),
197            array($chunks[1] . $chunks[8]),
198            array($chunks[2] . $chunks[9]),
199            array($chunks[3] . $chunks[4]),
200            array($chunks[5] . $chunks[6]),
201            array('foobar.png'),
202        );
203    }
204
205    /**
206     * @dataProvider clean_filename_variables
207     */
208    public function test_clean_filename_real($filename)
209    {
210        $bad_chars = array("'", "\\", ' ', '/', ':', '*', '?', '"', '<', '>', '|');
211        $filespec = $this->get_filespec(array('name' => $filename));
212        $filespec->clean_filename('real', self::PREFIX);
213        $name = $filespec->get('realname');
214
215        $this->assertEquals(0, preg_match('/%(\w{2})/', $name));
216        foreach ($bad_chars as $char)
217        {
218            $this->assertFalse(strpos($name, $char));
219        }
220    }
221
222    public function test_clean_filename_unique()
223    {
224        $filenames = array();
225        for ($tests = 0; $tests < self::TEST_COUNT; $tests++)
226        {
227            $filespec = $this->get_filespec();
228            $filespec->clean_filename('unique', self::PREFIX);
229            $name = $filespec->get('realname');
230
231            $this->assertEquals(strlen($name), 32 + strlen(self::PREFIX));
232            $this->assertMatchesRegularExpression('#^[A-Za-z0-9]+$#', substr($name, strlen(self::PREFIX)));
233            $this->assertFalse(isset($filenames[$name]));
234            $filenames[$name] = true;
235        }
236    }
237
238    public function test_clean_filename_unique_ext()
239    {
240        $filenames = array();
241        for ($tests = 0; $tests < self::TEST_COUNT; $tests++)
242        {
243            $filespec = $this->get_filespec(array('name' => 'foobar.jpg'));
244            $filespec->clean_filename('unique_ext', self::PREFIX);
245            $name = $filespec->get('realname');
246
247            $this->assertEquals(strlen($name), 32 + strlen(self::PREFIX) + strlen('.jpg'));
248            $this->assertMatchesRegularExpression('#^[A-Za-z0-9]+\.jpg$#', substr($name, strlen(self::PREFIX)));
249            $this->assertFalse(isset($filenames[$name]));
250            $filenames[$name] = true;
251        }
252    }
253
254    public static function data_clean_filename_avatar()
255    {
256        return array(
257            array(false, false, ''),
258            array('foobar.png', 'u5.png', 'avatar', 'u', 5),
259            array('foobar.png', 'g9.png', 'avatar', 'g', 9),
260
261        );
262    }
263
264    /**
265     * @dataProvider data_clean_filename_avatar
266     */
267    public function test_clean_filename_avatar($filename, $expected, $mode, $prefix = '', $user_id = '')
268    {
269        $filespec = new \phpbb\files\filespec($this->filesystem, $this->language, new \bantu\IniGetWrapper\IniGetWrapper, new \FastImageSize\FastImageSize(), $this->phpbb_root_path, $this->mimetype_guesser);
270
271        if ($filename)
272        {
273            $upload_ary = array(
274                'name' => $filename,
275                'type' => '',
276                'size' => '',
277                'tmp_name' => '',
278                'error' => '',
279            );
280            $filespec->set_upload_ary($upload_ary);
281        }
282        $filespec->clean_filename($mode, $prefix, $user_id);
283
284        $this->assertSame($expected, $filespec->get('realname'));
285    }
286
287    public static function get_extension_variables()
288    {
289        return array(
290            array('file.png', 'png'),
291            array('file.phpbb.gif', 'gif'),
292            array('file..', ''),
293            array('.file..jpg.webp', 'webp'),
294            array('/test.com/file', ''),
295            array('/test.com/file.gif', 'gif'),
296        );
297    }
298
299    /**
300     * @dataProvider get_extension_variables
301     */
302    public function test_get_extension($filename, $expected)
303    {
304        $this->assertEquals($expected, \phpbb\files\filespec::get_extension($filename));
305    }
306
307    public static function is_image_variables()
308    {
309        return array(
310            array('gif', 'image/gif', true),
311            array('jpg', 'image/jpg', true),
312            array('png', 'image/png', true),
313            array('tif', 'image/tif', true),
314            array('txt', 'text/plain', false),
315            array('jpg', 'application/octet-stream', false),
316            array('gif', 'application/octetstream', false),
317            array('png', 'application/mime', false),
318        );
319    }
320
321    /**
322     * @dataProvider is_image_variables
323     */
324    public function test_is_image($filename, $mimetype, $expected)
325    {
326        $filespec = $this->get_filespec(array('tmp_name' => $this->path . $filename, 'type' => $mimetype));
327        $this->assertEquals($expected, $filespec->is_image());
328    }
329
330    public static function is_image_get_mimetype()
331    {
332        return array(
333            array('gif', 'image/gif', true),
334            array('jpg', 'image/jpg', true),
335            array('png', 'image/png', true),
336            array('tif', 'image/tif', true),
337            array('txt', 'text/plain', false),
338            array('jpg', 'application/octet-stream', true),
339            array('gif', 'application/octetstream', true),
340            array('png', 'application/mime', true),
341        );
342    }
343
344    /**
345     * @dataProvider is_image_get_mimetype
346     */
347    public function test_is_image_get_mimetype($filename, $mimetype, $expected)
348    {
349        if (!class_exists('finfo') && strtolower(substr(PHP_OS, 0, 3)) === 'win')
350        {
351            $this->markTestSkipped('Unable to test mimetype guessing without fileinfo support on Windows');
352        }
353
354        $filespec = $this->get_filespec(array('tmp_name' => $this->path . $filename, 'type' => $mimetype));
355        $filespec->get_mimetype($this->path . $filename);
356        $this->assertEquals($expected, $filespec->is_image());
357    }
358
359    public static function move_file_variables()
360    {
361        return array(
362            array('gif_copy', 'gif_moved', 'image/gif', 'gif', false, true),
363            array('non_existant', 'still_non_existant', 'text/plain', 'txt', 'GENERAL_UPLOAD_ERROR', false),
364            array('txt_copy', 'txt_as_img', 'image/jpg', 'txt', false, true),
365            array('txt_copy_2', 'txt_moved', 'text/plain', 'txt', false, true),
366            array('jpg_copy', 'jpg_moved', 'image/png', 'jpg', false, true),
367            array('png_copy', 'png_moved', 'image/png', 'jpg', 'Image file type mismatch: expected extension png but extension jpg given.', true),
368        );
369    }
370
371    /**
372     * @dataProvider move_file_variables
373     */
374    public function test_move_file($tmp_name, $realname, $mime_type, $extension, $error, $expected)
375    {
376        // Global $phpbb_root_path and $phpEx are required by phpbb_chmod
377        global $phpbb_root_path;
378        $this->phpbb_root_path = '';
379
380        $upload = new phpbb_mock_fileupload();
381        $upload->max_filesize = self::UPLOAD_MAX_FILESIZE;
382
383        $filespec = $this->get_filespec(array(
384            'tmp_name' => $this->path . 'copies/' . $tmp_name,
385            'name' => $realname,
386            'type' => $mime_type,
387        ));
388        $this->set_reflection_property($filespec, 'extension', $extension);
389        $filespec->set_upload_namespace($upload);
390        $this->set_reflection_property($filespec, 'local', true);
391
392        $this->assertEquals($expected, $filespec->move_file($this->path . 'copies'));
393        $this->assertEquals($filespec->get('file_moved'), file_exists($this->path . 'copies/' . $realname));
394        if ($error)
395        {
396            $this->assertEquals($error, $filespec->error[0]);
397        }
398
399        $this->phpbb_root_path = $phpbb_root_path;
400    }
401
402    public function test_move_file_error()
403    {
404        $filespec = $this->get_filespec();
405        $this->assertFalse($filespec->move_file('foobar'));
406        $filespec->error[] = 'foo';
407        $this->assertFalse($filespec->move_file('foo'));
408    }
409
410    public static function data_move_file_copy()
411    {
412        return array(
413            array('gif_copy', true, false, array()),
414            array('gif_copy', true, true, array()),
415            array('foo_bar', false, false, array('GENERAL_UPLOAD_ERROR')),
416            array('foo_bar', false, true, array('GENERAL_UPLOAD_ERROR')),
417        );
418    }
419
420    /**
421     * @dataProvider data_move_file_copy
422     */
423    public function test_move_file_copy($tmp_name, $move_success, $open_basedir_on, $expected_error)
424    {
425        // Initialise a blank filespec object for use with trivial methods
426        $upload_ary = array(
427            'name' => 'gif_moved',
428            'type' => 'image/gif',
429            'size' => '',
430            'tmp_name' => $this->path . 'copies/' . $tmp_name,
431            'error' => '',
432        );
433
434        $php_ini = $this->getMockBuilder('\bantu\IniGetWrapper\IniGetWrapper')
435            ->getMock();
436        $php_ini->expects($this->any())
437            ->method('getBool')
438            ->with($this->anything())
439            ->willReturn($open_basedir_on);
440        $upload = new phpbb_mock_fileupload();
441        $upload->max_filesize = self::UPLOAD_MAX_FILESIZE;
442        $filespec = new \phpbb\files\filespec($this->filesystem, $this->language, $php_ini, new \FastImageSize\FastImagesize,  '', $this->mimetype_guesser);
443        $filespec->set_upload_ary($upload_ary);
444        $this->set_reflection_property($filespec, 'local', false);
445        $this->set_reflection_property($filespec, 'extension', 'gif');
446        $filespec->set_upload_namespace($upload);
447
448        $this->assertEquals($move_success, $filespec->move_file($this->path . 'copies'));
449        $this->assertEquals($filespec->get('file_moved'), file_exists($this->path . 'copies/gif_moved'));
450        $this->assertSame($expected_error, $filespec->error);
451    }
452
453    public static function data_move_file_imagesize()
454    {
455        return array(
456            array(
457                array(
458                    'width'        => 2,
459                    'height'    => 2,
460                    'type'        => IMAGETYPE_GIF,
461                ),
462                array()
463            ),
464            array(
465                array(
466                    'width'        => 2,
467                    'height'    => 2,
468                    'type'        => -1,
469                ),
470                array('Image file type -1 for mimetype image/gif not supported.')
471            ),
472            array(
473                array(
474                    'width'        => 0,
475                    'height'    => 0,
476                    'type'        => IMAGETYPE_GIF,
477                ),
478                array('The image file you tried to attach is invalid.')
479            ),
480            array(
481                false,
482                array('It was not possible to determine the dimensions of the image. Please verify that the URL you entered is correct.')
483            )
484        );
485    }
486
487    /**
488     * @dataProvider data_move_file_imagesize
489     */
490    public function test_move_file_imagesize($imagesize_return, $expected_error)
491    {
492        // Initialise a blank filespec object for use with trivial methods
493        $upload_ary = array(
494            'name' => 'gif_moved',
495            'type' => 'image/gif',
496            'size' => '',
497            'tmp_name' => $this->path . 'copies/gif_copy',
498            'error' => '',
499        );
500
501        $imagesize = $this->getMockBuilder('\FastImageSize\FastImageSize')
502            ->getMock();
503        $imagesize->expects($this->any())
504            ->method('getImageSize')
505            ->with($this->anything())
506            ->willReturn($imagesize_return);
507
508        $upload = new phpbb_mock_fileupload();
509        $upload->max_filesize = self::UPLOAD_MAX_FILESIZE;
510        $filespec = new \phpbb\files\filespec($this->filesystem, $this->language, new \bantu\IniGetWrapper\IniGetWrapper, $imagesize,  '', $this->mimetype_guesser);
511        $filespec->set_upload_ary($upload_ary);
512        $this->set_reflection_property($filespec, 'local', false);
513        $this->set_reflection_property($filespec, 'extension', 'gif');
514        $filespec->set_upload_namespace($upload);
515
516        $this->assertEquals(true, $filespec->move_file($this->path . 'copies'));
517        $this->assertEquals($filespec->get('file_moved'), file_exists($this->path . 'copies/gif_moved'));
518        $this->assertSame($expected_error, $filespec->error);
519    }
520
521    /**
522    * @dataProvider clean_filename_variables
523    */
524    public function test_uploadname($filename)
525    {
526        $type_cast_helper = new \phpbb\request\type_cast_helper();
527
528        $upload_name = '';
529        $type_cast_helper->set_var($upload_name, $filename, 'string', true, true);
530        $filespec = $this->get_filespec(array('name'=> $upload_name));
531
532        $this->assertSame(trim(utf8_basename(htmlspecialchars($filename, ENT_COMPAT))), $filespec->get('uploadname'));
533    }
534
535    public function test_is_uploaded()
536    {
537        $filespec = new \phpbb\files\filespec($this->filesystem, $this->language, new \bantu\IniGetWrapper\IniGetWrapper, new \FastImageSize\FastImageSize(), $this->phpbb_root_path, null);
538        $reflection_filespec = new ReflectionClass($filespec);
539        $plupload_property = $reflection_filespec->getProperty('plupload');
540        $plupload_mock = $this->getMockBuilder('\phpbb\plupload\plupload')
541            ->disableOriginalConstructor()
542            ->getMock();
543        $plupload_mock->expects($this->any())
544            ->method('is_active')
545            ->will($this->returnValue(true));
546        $plupload_property->setValue($filespec, $plupload_mock);
547        $is_uploaded = $reflection_filespec->getMethod('is_uploaded');
548
549        // Plupload is active and file does not exist
550        $this->assertFalse($is_uploaded->invoke($filespec));
551
552        // Plupload is not active and file was not uploaded
553        $plupload_property->setValue($filespec, null);
554        $this->assertFalse($is_uploaded->invoke($filespec));
555    }
556}