GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( 6f8b1f...142a48 )
by James
25:51 queued 11:45
created

app/Helpers/Attachments/AttachmentHelper.php (1 issue)

Labels
Severity
1
<?php
2
/**
3
 * AttachmentHelper.php
4
 * Copyright (c) 2017 [email protected]
5
 *
6
 * This file is part of Firefly III.
7
 *
8
 * Firefly III is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License as published by
10
 * the Free Software Foundation, either version 3 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * Firefly III is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
20
 */
21
declare(strict_types=1);
22
23
namespace FireflyIII\Helpers\Attachments;
24
25
use Crypt;
26
use FireflyIII\Exceptions\FireflyException;
27
use FireflyIII\Models\Attachment;
28
use Illuminate\Contracts\Encryption\DecryptException;
29
use Illuminate\Contracts\Filesystem\FileNotFoundException;
30
use Illuminate\Database\Eloquent\Model;
31
use Illuminate\Support\Collection;
32
use Illuminate\Support\Facades\Storage;
33
use Illuminate\Support\MessageBag;
34
use Log;
35
use Symfony\Component\HttpFoundation\File\UploadedFile;
36
37
/**
38
 * Class AttachmentHelper.
39
 */
40
class AttachmentHelper implements AttachmentHelperInterface
41
{
42
    /** @var Collection All attachments */
43
    public $attachments;
44
    /** @var MessageBag All errors */
45
    public $errors;
46
    /** @var MessageBag All messages */
47
    public $messages;
48
    /** @var array Allowed mimes */
49
    protected $allowedMimes = [];
50
    /** @var int Max upload size. */
51
    protected $maxUploadSize = 0;
52
53
    /** @var \Illuminate\Contracts\Filesystem\Filesystem The disk where attachments are stored. */
54
    protected $uploadDisk;
55
56
57
    /**
58
     * AttachmentHelper constructor.
59
     * @codeCoverageIgnore
60
     */
61
    public function __construct()
62
    {
63
        $this->maxUploadSize = (int)config('firefly.maxUploadSize');
64
        $this->allowedMimes  = (array)config('firefly.allowedMimes');
65
        $this->errors        = new MessageBag;
66
        $this->messages      = new MessageBag;
67
        $this->attachments   = new Collection;
68
        $this->uploadDisk    = Storage::disk('upload');
69
70
        if ('testing' === config('app.env')) {
71
            Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this)));
72
        }
73
    }
74
75
76
    /**
77
     * Returns the content of an attachment.
78
     *
79
     * @codeCoverageIgnore
80
     *
81
     * @param Attachment $attachment
82
     *
83
     * @return string
84
     */
85
    public function getAttachmentContent(Attachment $attachment): string
86
    {
87
88
        try {
89
            $content = Crypt::decrypt($this->uploadDisk->get(sprintf('at-%d.data', $attachment->id)));
90
        } catch (DecryptException|FileNotFoundException $e) {
91
            Log::error(sprintf('Could not decrypt data of attachment #%d: %s', $attachment->id, $e->getMessage()));
92
            $content = '';
93
        }
94
95
        return $content;
96
    }
97
98
    /**
99
     * Returns the file path relative to upload disk for an attachment,
100
     *
101
     * @param Attachment $attachment
102
     * @codeCoverageIgnore
103
     * @return string
104
     */
105
    public function getAttachmentLocation(Attachment $attachment): string
106
    {
107
        return sprintf('%sat-%d.data', DIRECTORY_SEPARATOR, (int)$attachment->id);
108
    }
109
110
    /**
111
     * Get all attachments.
112
     * @codeCoverageIgnore
113
     * @return Collection
114
     */
115
    public function getAttachments(): Collection
116
    {
117
        return $this->attachments;
118
    }
119
120
    /**
121
     * Get all errors.
122
     *
123
     * @return MessageBag
124
     * @codeCoverageIgnore
125
     */
126
    public function getErrors(): MessageBag
127
    {
128
        return $this->errors;
129
    }
130
131
    /**
132
     * Get all messages.
133
     *
134
     * @return MessageBag
135
     * @codeCoverageIgnore
136
     */
137
    public function getMessages(): MessageBag
138
    {
139
        return $this->messages;
140
    }
141
142
    /**
143
     * Uploads a file as a string.
144
     *
145
     * @param Attachment $attachment
146
     * @param string     $content
147
     *
148
     * @return bool
149
     */
150
    public function saveAttachmentFromApi(Attachment $attachment, string $content): bool
151
    {
152
        $resource = tmpfile();
153
        if (false === $resource) {
154
            // @codeCoverageIgnoreStart
155
            Log::error('Cannot create temp-file for file upload.');
156
157
            return false;
158
            // @codeCoverageIgnoreEnd
159
        }
160
        $path = stream_get_meta_data($resource)['uri'];
161
        fwrite($resource, $content);
162
        $finfo       = finfo_open(FILEINFO_MIME_TYPE);
163
        $mime        = finfo_file($finfo, $path);
164
        $allowedMime = config('firefly.allowedMimes');
165
        if (!in_array($mime, $allowedMime, true)) {
166
            Log::error(sprintf('Mime type %s is not allowed for API file upload.', $mime));
167
168
            return false;
169
        }
170
        // is allowed? Save the file!
171
        $encrypted = Crypt::encrypt($content);
172
        $this->uploadDisk->put($attachment->fileName(), $encrypted);
173
174
        // update attachment.
175
        $attachment->md5      = md5_file($path);
176
        $attachment->mime     = $mime;
177
        $attachment->size     = strlen($content);
178
        $attachment->uploaded = true;
179
        $attachment->save();
180
181
        return true;
182
    }
183
184
    /**
185
     * Save attachments that get uploaded with models, through the app.
186
     *
187
     * @param object     $model
188
     * @param array|null $files
189
     *
190
     * @return bool
191
     * @throws FireflyException
192
     */
193
    public function saveAttachmentsForModel(object $model, ?array $files): bool
194
    {
195
        if (!($model instanceof Model)) {
196
            return false; // @codeCoverageIgnore
197
        }
198
        Log::debug(sprintf('Now in saveAttachmentsForModel for model %s', get_class($model)));
199
        if (is_array($files)) {
200
            Log::debug('$files is an array.');
201
            /** @var UploadedFile $entry */
202
            foreach ($files as $entry) {
203
                if (null !== $entry) {
204
                    $this->processFile($entry, $model);
205
                }
206
            }
207
            Log::debug('Done processing uploads.');
208
        }
209
        if (!is_array($files) || (is_array($files) && 0 === count($files))) {
210
            Log::debug('Array of files is not an array. Probably nothing uploaded. Will not store attachments.');
211
        }
212
213
        return true;
214
    }
215
216
    /**
217
     * Check if a model already has this file attached.
218
     *
219
     * @param UploadedFile $file
220
     * @param Model        $model
221
     *
222
     * @return bool
223
     */
224
    protected function hasFile(UploadedFile $file, Model $model): bool
225
    {
226
        $md5   = md5_file($file->getRealPath());
227
        $name  = $file->getClientOriginalName();
228
        $class = get_class($model);
229
        /** @noinspection PhpUndefinedFieldInspection */
230
        $count  = $model->user->attachments()->where('md5', $md5)->where('attachable_id', $model->id)->where('attachable_type', $class)->count();
0 ignored issues
show
The method attachments() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

230
        $count  = $model->user->/** @scrutinizer ignore-call */ attachments()->where('md5', $md5)->where('attachable_id', $model->id)->where('attachable_type', $class)->count();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
231
        $result = false;
232
        if ($count > 0) {
233
            $msg = (string)trans('validation.file_already_attached', ['name' => $name]);
234
            $this->errors->add('attachments', $msg);
235
            Log::error($msg);
236
            $result = true;
237
        }
238
239
        return $result;
240
    }
241
242
    /**
243
     * Process the upload of a file.
244
     *
245
     * @param UploadedFile $file
246
     * @param Model        $model
247
     *
248
     * @return Attachment|null
249
     * @throws \Illuminate\Contracts\Encryption\EncryptException
250
     * @throws FireflyException
251
     */
252
    protected function processFile(UploadedFile $file, Model $model): ?Attachment
253
    {
254
        Log::debug('Now in processFile()');
255
        $validation = $this->validateUpload($file, $model);
256
        $attachment = null;
257
        if (false !== $validation) {
258
            $attachment = new Attachment; // create Attachment object.
259
            /** @noinspection PhpUndefinedFieldInspection */
260
            $attachment->user()->associate($model->user);
261
            $attachment->attachable()->associate($model);
262
            $attachment->md5      = md5_file($file->getRealPath());
263
            $attachment->filename = $file->getClientOriginalName();
264
            $attachment->mime     = $file->getMimeType();
265
            $attachment->size     = $file->getSize();
266
            $attachment->uploaded = false;
267
            $attachment->save();
268
            Log::debug('Created attachment:', $attachment->toArray());
269
270
            $fileObject = $file->openFile();
271
            $fileObject->rewind();
272
273
            if (0 === $file->getSize()) {
274
                throw new FireflyException('Cannot upload empty or non-existent file.'); // @codeCoverageIgnore
275
            }
276
277
            $content   = $fileObject->fread($file->getSize());
278
            $encrypted = Crypt::encrypt($content);
279
            Log::debug(sprintf('Full file length is %d and upload size is %d.', strlen($content), $file->getSize()));
280
            Log::debug(sprintf('Encrypted content is %d', strlen($encrypted)));
281
282
            // store it:
283
            $this->uploadDisk->put($attachment->fileName(), $encrypted);
284
            $attachment->uploaded = true; // update attachment
285
            $attachment->save();
286
            $this->attachments->push($attachment);
287
288
            $name = e($file->getClientOriginalName()); // add message:
289
            $msg  = (string)trans('validation.file_attached', ['name' => $name]);
290
            $this->messages->add('attachments', $msg);
291
        }
292
293
        return $attachment;
294
    }
295
296
    /**
297
     * Verify if the mime of a file is valid.
298
     *
299
     * @param UploadedFile $file
300
     *
301
     * @return bool
302
     */
303
    protected function validMime(UploadedFile $file): bool
304
    {
305
        Log::debug('Now in validMime()');
306
        $mime = e($file->getMimeType());
307
        $name = e($file->getClientOriginalName());
308
        Log::debug(sprintf('Name is %s, and mime is %s', $name, $mime));
309
        Log::debug('Valid mimes are', $this->allowedMimes);
310
        $result = true;
311
312
        if (!in_array($mime, $this->allowedMimes, true)) {
313
            $msg = (string)trans('validation.file_invalid_mime', ['name' => $name, 'mime' => $mime]);
314
            $this->errors->add('attachments', $msg);
315
            Log::error($msg);
316
317
            $result = false;
318
        }
319
320
        return $result;
321
    }
322
323
    /**
324
     * Verify if the size of a file is valid.
325
     *
326
     * @codeCoverageIgnore
327
     *
328
     * @param UploadedFile $file
329
     *
330
     * @return bool
331
     */
332
    protected function validSize(UploadedFile $file): bool
333
    {
334
        $size   = $file->getSize();
335
        $name   = e($file->getClientOriginalName());
336
        $result = true;
337
        if ($size > $this->maxUploadSize) {
338
            $msg = (string)trans('validation.file_too_large', ['name' => $name]);
339
            $this->errors->add('attachments', $msg);
340
            Log::error($msg);
341
342
            $result = false;
343
        }
344
345
        return $result;
346
    }
347
348
    /**
349
     * Verify if the file was uploaded correctly.
350
     *
351
     * @param UploadedFile $file
352
     * @param Model        $model
353
     *
354
     * @return bool
355
     */
356
    protected function validateUpload(UploadedFile $file, Model $model): bool
357
    {
358
        Log::debug('Now in validateUpload()');
359
        $result = true;
360
        if (!$this->validMime($file)) {
361
            $result = false;
362
        }
363
        // @codeCoverageIgnoreStart
364
        // can't seem to reach this point.
365
        if (true === $result && !$this->validSize($file)) {
366
            $result = false;
367
        }
368
        // @codeCoverageIgnoreEnd
369
        if (true === $result && $this->hasFile($file, $model)) {
370
            $result = false;
371
        }
372
373
        return $result;
374
    }
375
}
376