Failed Conditions
Pull Request — 4.0 (#4836)
by
unknown
06:09
created

FileController::getNowDirWithNomalize()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 8
ccs 0
cts 0
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of EC-CUBE
5
 *
6
 * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.ec-cube.co.jp/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Eccube\Controller\Admin\Content;
15
16
use Eccube\Controller\AbstractController;
17
use Eccube\Util\FilesystemUtil;
18
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
19
use Symfony\Component\Filesystem\Exception\IOException;
20
use Symfony\Component\Filesystem\Filesystem;
21
use Symfony\Component\Finder\Finder;
22
use Symfony\Component\Form\Extension\Core\Type\FileType;
23
use Symfony\Component\Form\Extension\Core\Type\FormType;
24
use Symfony\Component\Form\Extension\Core\Type\TextType;
25
use Symfony\Component\HttpFoundation\BinaryFileResponse;
26
use Symfony\Component\HttpFoundation\File\Exception\FileException;
27
use Symfony\Component\HttpFoundation\Request;
28
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
29
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
30
use Symfony\Component\Routing\Annotation\Route;
31
use Symfony\Component\Validator\Constraints as Assert;
32
33
class FileController extends AbstractController
34
{
35
    const SJIS = 'sjis-win';
36
    const UTF = 'UTF-8';
37
    private $errors = [];
38
    private $encode = '';
39
40
    /**
41
     * FileController constructor.
42
     */
43
    public function __construct()
44 6
    {
45
        $this->encode = self::UTF;
46 6
        if ('\\' === DIRECTORY_SEPARATOR) {
47 6
            $this->encode = self::SJIS;
48
        }
49
    }
50
51
    /**
52
     * @Route("/%eccube_admin_route%/content/file_manager", name="admin_content_file")
53
     * @Template("@admin/Content/file.twig")
54
     */
55
    public function index(Request $request)
56 3
    {
57
        $form = $this->formFactory->createBuilder(FormType::class)
58 3
            ->add('file', FileType::class, [
59 3
                'multiple' => true,
60 3
                'attr' => [
61 3
                    'multiple' => 'multiple'
62
                ],
63
            ])
64 3
            ->add('create_file', TextType::class)
65 3
            ->getForm();
66
67
        // user_data_dir
68 3
        $topDir = $this->normalizePath($this->getUserDataDir());
69
        //        $topDir = '/';
70
        // user_data_dirの親ディレクトリ
71 3
        $htmlDir = $this->normalizePath($this->getUserDataDir().'/../');
72 3
73 3
        // カレントディレクトリ
74
        $nowDir = $this->getNowDirWithNomalize($request->get('tree_select_file'));
75
76 3
        // パンくず表示用データ
77 3
        $nowDirList = json_encode(explode('/', trim(str_replace($htmlDir, '', $nowDir), '/')));
78 3
        $jailNowDir = $this->getJailDir($nowDir);
79 3
        $isTopDir = ($topDir === $jailNowDir);
80
        $parentDir = substr($nowDir, 0, strrpos($nowDir, '/'));
81 3
82 2
        if ('POST' === $request->getMethod()) {
83
            switch ($request->get('mode')) {
84 1
                case 'create':
85 1
                    $this->create($request);
86
                    break;
87 1
                case 'upload':
88 1
                    $this->upload($request);
89
                    break;
90
                default:
91
                    break;
92
            }
93 3
        }
94 3
        $tree = $this->getTree($this->getUserDataDir(), $request);
95 3
        $arrFileList = $this->getFileList($nowDir);
96 3
        $paths = $this->getPathsToArray($tree);
97
        $tree = $this->getTreeToArray($tree);
98
99 3
        return [
100 3
            'form' => $form->createView(),
101 3
            'tpl_javascript' => json_encode($tree),
102 3
            'top_dir' => $this->getJailDir($topDir),
103 3
            'tpl_is_top_dir' => $isTopDir,
104 3
            'tpl_now_dir' => $jailNowDir,
105 3
            'html_dir' => $this->getJailDir($htmlDir),
106 3
            'now_dir_list' => $nowDirList,
107 3
            'tpl_parent_dir' => $this->getJailDir($parentDir),
108 3
            'arrFileList' => $arrFileList,
109 3
            'errors' => $this->errors,
110
            'paths' => json_encode($paths),
111
        ];
112
    }
113
114
    /**
115
     * @Route("/%eccube_admin_route%/content/file_view", name="admin_content_file_view")
116 1
     */
117
    public function view(Request $request)
118 1
    {
119 1
        $file = $this->convertStrToServer($this->getUserDataDir($request->get('file')));
120 1
        if ($this->checkDir($file, $this->getUserDataDir())) {
121
            setlocale(LC_ALL, 'ja_JP.UTF-8');
122 1
123
            return new BinaryFileResponse($file);
124
        }
125
126
        throw new NotFoundHttpException();
127
    }
128
129
    /**
130
     * Create directory
131
     *
132
     * @param Request $request
133 1
     */
134
    public function create(Request $request)
135 1
    {
136 1
        $form = $this->formFactory->createBuilder(FormType::class)
137 1
            ->add('file', FileType::class, [
138
                'multiple' => true,
139 1
                'attr' => [
140 1
                    'multiple' => 'multiple'
141
                ],
142 1
            ])
143 1
            ->add('create_file', TextType::class, [
144
                'constraints' => [
145
                    new Assert\NotBlank(),
146
                    new Assert\Regex([
147 1
                        'pattern' => '/[^[:alnum:]_.\\-]/',
148 1
                        'match' => false,
149
                        'message' => 'file.text.error.folder_symbol',
150
                    ]),
151
                    new Assert\Regex([
152
                        'pattern' => "/^\.(.*)$/",
153
                        'match' => false,
154 1
                        'message' => 'file.text.error.folder_period',
155
                    ]),
156 1
                ],
157 1
            ])
158
            ->getForm();
159
160
        $form->handleRequest($request);
161 View Code Duplication
        if (!$form->isValid()) {
162
            foreach ($form->getErrors(true) as $error) {
163
                $this->errors[] = ['message' => $error->getMessage()];
0 ignored issues
show
Bug introduced by
The method getMessage does only exist in Symfony\Component\Form\FormError, but not in Symfony\Component\Form\FormErrorIterator.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
164
            }
165 1
166 1
            return;
167
        }
168
169 1
        $nowDir = $this->getNowDirWithNomalize($request->get('now_dir'));
170 1
        $filename = $form->get('create_file')->getData();
171 1
        if ($this->checkFileExistDir($nowDir, $filename)) {
172
            $this->errors[] = ['message' => 'admin.content.file.dir_exists'];
173 1
            return;
174 1
        }
175
176 1
        try {
177
            $fs = new Filesystem();
178
            $fs->mkdir($nowDir.'/'.$filename);
179
            $this->addSuccess('admin.common.create_complete', 'admin');
180
        } catch (IOException $e) {
181
            $this->errors[] = ['message' => $e->getMessage()];
182
        }
183
    }
184
185
    /**
186 1
     * @Route("/%eccube_admin_route%/content/file_delete", name="admin_content_file_delete", methods={"DELETE"})
187
     */
188 1
    public function delete(Request $request)
189
    {
190 1
        $this->isTokenValid();
191 1
192 1
        $selectFile = $request->get('select_file');
193 1
        if (is_null($selectFile) || $selectFile == '/') {
194 1
            return $this->redirectToRoute('admin_content_file');
195 1
        }
196 1
197
        $topDir = $this->getUserDataDir();
198
        $file = $this->convertStrToServer($this->getUserDataDir($selectFile));
199
        if ($this->checkDir($file, $topDir)) {
200 1
            $fs = new Filesystem();
201
            if ($fs->exists($file)) {
202
                $fs->remove($file);
203
                $this->addSuccess('admin.common.delete_complete', 'admin');
204
            }
205
        }
206 1
207
        // 削除実行時のカレントディレクトリを表示させる
208 1
        return $this->redirectToRoute('admin_content_file', array('tree_select_file' => dirname($selectFile)));
209 1
    }
210 1
211 1
    /**
212 1
     * @Route("/%eccube_admin_route%/content/file_download", name="admin_content_file_download")
213 1
     */
214
    public function download(Request $request)
215
    {
216 1
        $topDir = $this->getUserDataDir();
217
        $file = $this->convertStrToServer($this->getUserDataDir($request->get('select_file')));
218
        if ($this->checkDir($file, $topDir)) {
219
            if (!is_dir($file)) {
220
                setlocale(LC_ALL, 'ja_JP.UTF-8');
221 1
                $pathParts = pathinfo($file);
222 1
223 1
                $patterns = [
224
                    '/[a-zA-Z0-9!"#$%&()=~^|@`:*;+{}]/',
225
                    '/[- ,.<>?_[\]\/\\\\]/',
226
                    "/['\r\n\t\v\f]/",
227
                ];
228
229
                $str = preg_replace($patterns, '', $pathParts['basename']);
230
                if (strlen($str) === 0) {
231
                    return (new BinaryFileResponse($file))->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT);
232
                } else {
233
                    return new BinaryFileResponse($file, 200, [
234
                        'Content-Type' => 'aplication/octet-stream;',
235 1
                        'Content-Disposition' => "attachment; filename*=UTF-8\'\'".rawurlencode($this->convertStrFromServer($pathParts['basename'])),
236
                    ]);
237 1
                }
238 1
            }
239
        }
240 1
        throw new NotFoundHttpException();
241 1
    }
242
243
    public function upload(Request $request)
244
    {
245 1
        $form = $this->formFactory->createBuilder(FormType::class)
246 1
            ->add('file', FileType::class, [
247
                'multiple' => true,
248 1
                'constraints' => [
249
                    new Assert\NotBlank([
250 1
                        'message' => 'admin.common.file_select_empty',
251
                    ]),
252
                ],
253
            ])
254
            ->add('create_file', TextType::class)
255
            ->getForm();
256
257
        $form->handleRequest($request);
258 1
259 1 View Code Duplication
        if (!$form->isValid()) {
260 1
            foreach ($form->getErrors(true) as $error) {
261
                $this->errors[] = ['message' => $error->getMessage()];
0 ignored issues
show
Bug introduced by
The method getMessage does only exist in Symfony\Component\Form\FormError, but not in Symfony\Component\Form\FormErrorIterator.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
262 1
            }
263
264
            return;
265
        }
266
267
        $data = $form->getData();
268 1
        $topDir = $this->getUserDataDir();
269
        $nowDir = $this->getUserDataDir($request->get('now_dir'));
270 1
271 1
        if (!$this->checkDir($nowDir, $topDir)) {
272
            $this->errors[] = ['message' => 'file.text.error.invalid_upload_folder'];
273
            return;
274
        }
275
276
        $uploadCount = count($data['file']);
277 3
        $successCount = 0;
278
279 3
        foreach ($data['file'] as $file) {
280 3
            $filename = $this->convertStrToServer($file->getClientOriginalName());
281 3
            try {
282 3
                $file->move($nowDir, $filename);
283 3
                $successCount ++;
284 3
            } catch (FileException $e) {
285 3
                $this->errors[] = ['message' => trans('admin.content.file.upload_error', [
286 3
                    '%file_name%' => $filename,
287 3
                    '%error%' => $e->getMessage()
288
                ])];
289
            }
290
        }
291 3
        if ($successCount > 0) {
292
            $this->addSuccess(trans('admin.content.file.upload_complete', [
293
                '%success%' => $successCount,
294 3
                '%count%' => $uploadCount
295
            ]), 'admin');
296 3
        }
297 3
    }
298 3
299
    private function getTreeToArray($tree)
300
    {
301 3
        $arrTree = [];
302
        foreach ($tree as $key => $val) {
303
            $path = $this->getJailDir($val['path']);
304
            $arrTree[$key] = [
305
                $key,
306
                $val['type'],
307
                $path,
308 3
                $val['depth'],
309
                $val['open'] ? 'true' : 'false',
310 3
            ];
311 3
        }
312 3
313
        return $arrTree;
314 3
    }
315 3
316 3
    private function getPathsToArray($tree)
317 3
    {
318 3
        $paths = [];
319
        foreach ($tree as $val) {
320
            $paths[] = $this->getJailDir($val['path']);
321
        }
322 3
323
        return $paths;
324 3
    }
325 3
326
    /**
327
     * @param string $topDir
328
     * @param Request $request
329 3
     */
330 1
    private function getTree($topDir, $request)
331 1
    {
332 1
        $finder = Finder::create()->in($topDir)
333 1
            ->directories()
334 1
            ->sortByName();
335 1
336 1
        $tree = [];
337 1
        $tree[] = [
338
            'path' => $topDir,
339
            'type' => '_parent',
340
            'depth' => 0,
341 3
            'open' => true,
342
        ];
343
344
        $defaultDepth = count(explode('/', $topDir));
345
346
        $openDirs = [];
347 3
        if ($request->get('tree_status')) {
348
            $openDirs = explode('|', $request->get('tree_status'));
349 3
        }
350 3
351 3
        foreach ($finder as $dirs) {
352 3
            $path = $this->normalizePath($dirs->getRealPath());
353
            $type = (iterator_count(Finder::create()->in($path)->directories())) ? '_parent' : '_child';
354 3
            $depth = count(explode('/', $path)) - $defaultDepth;
355 3
            $tree[] = [
356
                'path' => $path,
357 3
                'type' => $type,
358 3
                'depth' => $depth,
359 3
                'open' => (in_array($path, $openDirs)) ? true : false,
360 3
            ];
361 3
        }
362 3
363 3
        return $tree;
364
    }
365 3
366
    /**
367
     * @param string $nowDir
368
     */
369
    private function getFileList($nowDir)
370 3
    {
371
        $topDir = $this->getuserDataDir();
372 3
        $filter = function (\SplFileInfo $file) use ($topDir) {
373
            $acceptPath = realpath($topDir);
374
            $targetPath = $file->getRealPath();
375
376
            return strpos($targetPath, $acceptPath) === 0;
377 3
        };
378 3
379 1
        $finder = Finder::create()
380 1
            ->filter($filter)
381 1
            ->in($nowDir)
382 1
            ->ignoreDotFiles(false)
383 1
            ->sortByName()
384 1
            ->depth(0);
385 1
        $dirFinder = $finder->directories();
386 1
        try {
387 1
            $dirs = $dirFinder->getIterator();
388 1
        } catch (\Exception $e) {
389 1
            $dirs = [];
390 1
        }
391 1
392 1
        $fileFinder = $finder->files();
393 1
        try {
394 1
            $files = $fileFinder->getIterator();
395 1
        } catch (\Exception $e) {
396
            $files = [];
397 1
        }
398
399
        $arrFileList = [];
400 3
        foreach ($dirs as $dir) {
401 3
            $dirPath = $this->normalizePath($dir->getRealPath());
402 3
            $childDir = Finder::create()
403 3
                ->in($dirPath)
404 3
                ->ignoreDotFiles(false)
405 3
                ->directories()
406
                ->depth(0);
407
            $childFile = Finder::create()
408 3
                ->in($dirPath)
409
                ->ignoreDotFiles(false)
410
                ->files()
411
                ->depth(0);
412 3
            $countNumber = $childDir->count() + $childFile->count();
413
            $arrFileList[] = [
414
                'file_name' => $this->convertStrFromServer($dir->getFilename()),
415 3
                'file_path' => $this->convertStrFromServer($this->getJailDir($dirPath)),
416
                'file_size' => FilesystemUtil::sizeToHumanReadable($dir->getSize()),
417 3
                'file_time' => $dir->getmTime(),
418
                'is_dir' => true,
419
                'is_empty' => $countNumber == 0 ? true : false,
420
            ];
421
        }
422
        foreach ($files as $file) {
423 6
            $arrFileList[] = [
424
                'file_name' => $this->convertStrFromServer($file->getFilename()),
425 6
                'file_path' => $this->convertStrFromServer($this->getJailDir($this->normalizePath($file->getRealPath()))),
426 6
                'file_size' => FilesystemUtil::sizeToHumanReadable($file->getSize()),
427
                'file_time' => $file->getmTime(),
428 6
                'is_dir' => false,
429
                'is_empty' => false,
430
                'extension' => $file->getExtension(),
431
            ];
432
        }
433
434 3
        return $arrFileList;
435
    }
436 3
437
    /**
438
     * 指定のディレクトリに指定の名前のファイルが存在するか調べる
439
     * @param string $dir
440 3
     * @param string $filename
441
     * @return bool
442
     */
443 4
    private function checkFileExistDir(string $dir, string $filename): bool
444
    {
445 4
        $file_list = $this->getFileList($dir);
446
        foreach ($file_list as $file) {
447
            if ($file['file_name'] === $filename) {
448
                return true;
449 4
            }
450
        }
451
        return false;
452 6
    }
453
454 6
    protected function normalizePath($path)
455
    {
456
        return str_replace('\\', '/', realpath($path));
457 3
    }
458
459 3
    /**
460 3
     * @param string $topDir
461
     */
462 3
    protected function checkDir($targetDir, $topDir)
463
    {
464
        $targetDir = realpath($targetDir);
465
        $topDir = realpath($topDir);
466
467
        return strpos($targetDir, $topDir) === 0;
468
    }
469
470
    /**
471
     * @return string
472
     */
473 View Code Duplication
    private function convertStrFromServer($target)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
474
    {
475
        if ($this->encode == self::SJIS) {
476
            return mb_convert_encoding($target, self::UTF, self::SJIS);
477
        }
478
479
        return $target;
480
    }
481
482 View Code Duplication
    private function convertStrToServer($target)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
483
    {
484
        if ($this->encode == self::SJIS) {
485
            return mb_convert_encoding($target, self::SJIS, self::UTF);
486
        }
487
488
        return $target;
489
    }
490
491
    private function getUserDataDir($nowDir = null)
492
    {
493
        return rtrim($this->getParameter('kernel.project_dir').'/html/user_data'.$nowDir, '/');
494
    }
495
496
    private function getNowDirWithNomalize($now_dir_string)
497
    {
498
        $topDir = $this->getUserDataDir();
499
        $nowDir = $this->getUserDataDir($now_dir_string);
500
        return $this->checkDir($nowDir, $topDir)
501
            ? $this->normalizePath($nowDir)
502
            : $topDir;
503
    }
504
505
    private function getJailDir($path)
506
    {
507
        $realpath = realpath($path);
508
        $jailPath = str_replace(realpath($this->getUserDataDir()), '', $realpath);
509
510
        return $jailPath ? $jailPath : '/';
511
    }
512
}
513