File.php 6.64 KB
Newer Older
Carsten  Rose's avatar
Carsten Rose committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 4/25/16
 * Time: 10:39 PM
 */

namespace qfq;

require_once(__DIR__ . '/store/Store.php');
require_once(__DIR__ . '/Constants.php');

class File {

16
17
    private $uploadErrMsg = array();

Carsten  Rose's avatar
Carsten Rose committed
18
    /**
19
     * @var Store
Carsten  Rose's avatar
Carsten Rose committed
20
21
22
     */
    private $store = null;

23
24
25
26
27
28
29
30
    /**
     * @var Session
     */
    private $session = null;

    /**
     * @param bool|false $phpUnit
     */
Carsten  Rose's avatar
Carsten Rose committed
31
    public function __construct($phpUnit = false) {
32
        $this->session = Session::getInstance($phpUnit);
33

34
        $this->store = Store::getInstance('', $phpUnit);
35

36
        $this->uploadErrMsg = [
Carsten  Rose's avatar
Carsten Rose committed
37
            UPLOAD_ERR_INI_SIZE => "The uploaded file exceeds the upload_max_filesize directive in php.ini",
38
            UPLOAD_ERR_FORM_SIZE => "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form",
Carsten  Rose's avatar
Carsten Rose committed
39
40
            UPLOAD_ERR_PARTIAL => "The uploaded file was only partially uploaded",
            UPLOAD_ERR_NO_FILE => "No file was uploaded",
41
42
            UPLOAD_ERR_NO_TMP_DIR => "Missing a temporary folder",
            UPLOAD_ERR_CANT_WRITE => "Failed to write file to disk",
Carsten  Rose's avatar
Carsten Rose committed
43
            UPLOAD_ERR_EXTENSION => "File upload stopped by extension",
44
        ];
Carsten  Rose's avatar
Carsten Rose committed
45
46
    }

47
48
49
    /**
     * @throws UserFormException
     */
Carsten  Rose's avatar
Carsten Rose committed
50
51
    public function process() {

52
53
54
55
        $sipUpload = $this->store->getVar(SIP_SIP, STORE_SIP);
        if ($sipUpload === false) {
            throw new UserFormException('SIP invalid: ' . $sipUpload, ERROR_SIP_INVALID);
        }
56
        $statusUpload = $this->store->getVar($sipUpload, STORE_EXTRA, SANITIZE_ALLOW_ALL);
57
58
        if ($statusUpload === false) {
            $statusUpload = array();
Carsten  Rose's avatar
Carsten Rose committed
59
60
        }

61
62
63
64
65
66
67
68
69
        $action = $this->store->getVar(FILE_ACTION, STORE_CLIENT, SANITIZE_ALLOW_ALNUMX);
        switch ($action) {
            case FILE_ACTION_UPLOAD:
                $this->doUpload($sipUpload, $statusUpload);
                break;
            case FILE_ACTION_DELETE:
                $this->doDelete($sipUpload, $statusUpload);
                break;
            default:
Carsten  Rose's avatar
Carsten Rose committed
70
                throw new UserFormException("Unknown FILE_ACTION: $action", ERROR_UPLOAD_UNKNOWN_ACTION);
71
72
        }
    }
Carsten  Rose's avatar
Carsten Rose committed
73

74
    /**
75
     * @param string $sipUpload
Carsten  Rose's avatar
Carsten Rose committed
76
     * @param array $statusUpload
Carsten  Rose's avatar
Carsten Rose committed
77
     *
78
79
80
     * @throws CodeException
     * @throws UserFormException
     */
81
    private function doUpload($sipUpload, array $statusUpload) {
82
83

        list($dummy, $newArr) = each($_FILES);
84
        $statusUpload = array_merge($statusUpload, $newArr);
85

86
        if ($statusUpload[FILES_ERROR] !== UPLOAD_ERR_OK) {
87
            throw new UserFormException($this->uploadErrMsg[$newArr[FILES_ERROR]], ERROR_UPLOAD);
Carsten  Rose's avatar
Carsten Rose committed
88
89
        }

90
91
92
93
        $maxFileSize = $this->store->getVar(FE_FILE_MAX_FILE_SIZE, STORE_SIP);
        if ($statusUpload['size'] >= $maxFileSize) {
            throw new UserFormException('File to big. Max size allowed: ' . $maxFileSize, ERROR_UPLOAD_TOO_BIG);
        }
94

95
96
97
98
        $accept = $this->store->getVar(FE_FILE_MIME_TYPE_ACCEPT, STORE_SIP);
        if (!$this->checkFileType($statusUpload['tmp_name'], $statusUpload['name'], $accept)) {
            throw new UserFormException('Filetype not allowed. Allowed: ' . $accept, ERROR_UPLOAD_FILE_TYPE);
        }
99
100
101

        // rename uploaded file: ?.cached
        $filenameCached = Support::extendFilename($statusUpload[FILES_TMP_NAME], UPLOAD_CACHED);
Carsten  Rose's avatar
Carsten Rose committed
102
        move_uploaded_file($newArr[FILES_TMP_NAME], $filenameCached);
Carsten  Rose's avatar
Carsten Rose committed
103

104
        $this->store->setVar($sipUpload, $statusUpload, STORE_EXTRA);
Carsten  Rose's avatar
Carsten Rose committed
105
    }
Carsten  Rose's avatar
Carsten Rose committed
106

107
    /**
108
109
     * Checks the file filetype against the allowed mimetype definition. Return true as soon as one match is found.
     * Types recognized:
Carsten  Rose's avatar
Carsten Rose committed
110
111
     *   * 'mime type' as delivered by `file` which matches a definition on
     *   http://www.iana.org/assignments/media-types/media-types.xhtml
112
113
114
115
116
117
     *   * Joker based: audio/*, video/*, image/*
     *   * Filename extension based: .pdf,.doc,..
     *
     * @param string $tmp_name
     * @param string $name
     * @param string $accept
Carsten  Rose's avatar
Carsten Rose committed
118
     *
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
     * @return bool
     * @throws UserFormException
     */
    private function checkFileType($tmp_name, $name, $accept) {

        $return_var = 0;

        // E.g.: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=binary'
        $fileMimeType = exec('file --brief --mime ' . $tmp_name, $output, $return_var);
        if ($return_var != 0) {
            throw new UserFormException('Error get mime type of upload.', ERROR_UPLOAD_GET_MIME_TYPE);
        }
        // Strip optional '; charset=binary'
        $arr = explode(';', $fileMimeType, 2);
        $fileMimeType = $arr[0];

        // Split between 'Media Type' and 'Media Subtype'
        $fileMimeTypeSplitted = explode('/', $arr[0], 2);

        $path_parts = pathinfo($name); // to extract the filename extension of the uploaded file.

        // Process all listed mimetypes (incl. filename extension and joker)
        // $accept e.g.: 'image/*,application/pdf,.pdf'
        $arr = explode(',', $accept); // Split multiple defined mimetypes/extensions in single chunks.
        foreach ($arr as $listElementMimeType) {
            $listElementMimeType = trim($listElementMimeType);
            if ($listElementMimeType == '') {
                continue; // will be skipped
147
            } elseif ($listElementMimeType[0] == '.') { // Check for definition 'filename extension'
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
                if ('.' . $path_parts['extension'] == $listElementMimeType) {
                    return true;
                }
            } else {
                // Check for Joker, e.g.: 'image/*'
                $splitted = explode('/', $listElementMimeType, 2);

                if ($splitted[1] == '*') {
                    if ($splitted[0] == $fileMimeTypeSplitted[0]) {
                        return true;
                    }
                } elseif ($fileMimeType == $listElementMimeType) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * @param $sipUpload
     * @param $statusUpload
Carsten  Rose's avatar
Carsten Rose committed
171
     *
172
173
     * @throws CodeException
     * @throws UserFormException
174
     * @internal param string $keyStoreExtra
175
176
177
178
179
180
181
182
183
184
185
186
     */
    private function doDelete($sipUpload, $statusUpload) {

        if (isset($statusUpload[FILES_TMP_NAME]) && $statusUpload[FILES_TMP_NAME] != '') {
            $file = Support::extendFilename($statusUpload[FILES_TMP_NAME], UPLOAD_CACHED);
            if (file_exists($file)) {
                if (!unlink($file)) {
                    throw new UserFormException('unlink file: ' . $file, ERROR_IO_UNLINK);
                }
            }
            $statusUpload[FILES_TMP_NAME] = '';
        }
187

188
189
190
        $statusUpload[FILES_FLAG_DELETE] = '1';
        $this->store->setVar($sipUpload, $statusUpload, STORE_EXTRA);
    }
Carsten  Rose's avatar
Carsten Rose committed
191
}