1<?php
2/**
3 * Copyright since 2007 PrestaShop SA and Contributors
4 * PrestaShop is an International Registered Trademark & Property of PrestaShop SA
5 *
6 * NOTICE OF LICENSE
7 *
8 * This source file is subject to the Open Software License (OSL 3.0)
9 * that is bundled with this package in the file LICENSE.md.
10 * It is also available through the world-wide-web at this URL:
11 * https://opensource.org/licenses/OSL-3.0
12 * If you did not receive a copy of the license and are unable to
13 * obtain it through the world-wide-web, please send an email
14 * to license@prestashop.com so we can send you a copy immediately.
15 *
16 * DISCLAIMER
17 *
18 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
19 * versions in the future. If you wish to customize PrestaShop for your
20 * needs please refer to https://devdocs.prestashop.com/ for more information.
21 *
22 * @author    PrestaShop SA and Contributors <contact@prestashop.com>
23 * @copyright Since 2007 PrestaShop SA and Contributors
24 * @license   https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
25 */
26
27declare(strict_types=1);
28
29namespace PrestaShop\PrestaShop\Core\File;
30
31use ImageManager;
32use InvalidArgumentException;
33use PrestaShop\PrestaShop\Core\File\Exception\FileUploadException;
34use PrestaShop\PrestaShop\Core\File\Exception\InvalidFileException;
35use PrestaShop\PrestaShop\Core\File\Exception\MaximumSizeExceededException;
36
37/**
38 * Class is responsible to uploaded file through HTTP form or binary content
39 */
40class FileUploader implements FileUploaderInterface
41{
42    /**
43     * @var int
44     */
45    protected $maximumSize;
46
47    /**
48     * @var string
49     */
50    protected $downloadDirectory;
51
52    /**
53     * @param string $downloadDirectory Server path where the file will be uploaded
54     * @param int $maximumSize Maximum accepted file size
55     */
56    public function __construct(
57        string $downloadDirectory,
58        int $maximumSize
59    ) {
60        $this->downloadDirectory = $downloadDirectory;
61        $this->maximumSize = $maximumSize;
62    }
63
64    /**
65     * {@inheritdoc}
66     */
67    public function upload($file): array
68    {
69        if (is_array($file)) {
70            return $this->uploadFromHttpPost($file);
71        }
72
73        if (is_string($file)) {
74            return $this->uploadFromBinaryFile($file);
75        }
76
77        throw new InvalidArgumentException();
78    }
79
80    /**
81     * Validate file size
82     *
83     * @param array $file
84     *
85     * @throws InvalidFileException
86     * @throws MaximumSizeExceededException
87     */
88    protected function validateSize(array $file): void
89    {
90        if (!isset($file['size'])) {
91            throw new InvalidFileException();
92        }
93
94        if ($file['size'] > $this->maximumSize) {
95            throw new MaximumSizeExceededException((string) $file['size']);
96        }
97    }
98
99    /**
100     * Validate if file is an uploaded file
101     *
102     * @param array $file
103     *
104     * @throws InvalidFileException
105     * @throws FileUploadException
106     */
107    protected function validateIsUploadedFile(array $file): void
108    {
109        if (!isset($file['tmp_name'])
110            || !isset($file['type'])
111            || !isset($file['name'])
112        ) {
113            throw new InvalidFileException();
114        }
115
116        if (!is_uploaded_file($file['tmp_name'])) {
117            throw new FileUploadException();
118        }
119    }
120
121    /**
122     * Generate file name from uniqid
123     *
124     * @return string
125     */
126    protected function generateFileName(): string
127    {
128        do {
129            $uniqid = sha1(uniqid()); // must be a sha1
130        } while (file_exists($this->downloadDirectory . $uniqid));
131
132        return $uniqid;
133    }
134
135    /**
136     * Upload file from Http Post
137     *
138     * @param array{tmp_name: string, type: string, name: string} $file the $_FILES content
139     *
140     * @return array{id: string, file_name: string, mime_type: string}
141     *
142     * @throws FileUploadException
143     */
144    public function uploadFromHttpPost(array $file): array
145    {
146        $this->validateSize($file);
147        $this->validateIsUploadedFile($file);
148
149        $fileName = $this->generateFileName();
150        if (!move_uploaded_file($file['tmp_name'], $this->downloadDirectory . $fileName)) {
151            throw new FileUploadException();
152        }
153
154        return [
155            'id' => $fileName,
156            'file_name' => $file['name'],
157            'mime_type' => $file['type'],
158        ];
159    }
160
161    /**
162     * Upload file from binary request
163     *
164     * @param string $content The binary string
165     *
166     * @return array{id: string, file_name: string, mime_type: string}
167     *
168     * @throws FileUploadException
169     */
170    public function uploadFromBinaryFile(string $content): array
171    {
172        $file = [
173            // It returns the number of bytes rather than the number of characters
174            'size' => strlen($content),
175        ];
176        $this->validateSize($file);
177
178        $fileName = $this->generateFileName();
179
180        // Ignore warning, we only need to know if everything is ok
181        if (@file_put_contents($this->downloadDirectory . $fileName, $content) === false) {
182            throw new FileUploadException();
183        }
184
185        return [
186            'id' => $fileName,
187            'file_name' => uniqid('', true),
188            'mime_type' => ImageManager::getMimeType($this->downloadDirectory . $fileName),
189        ];
190    }
191}
192