Line data Source code
1 : /**
2 : Copyright (c) 2016-2022 Roman Katuntsev <sbkarr@stappler.org>
3 : Copyright (c) 2023-2024 Stappler LLC <admin@stappler.dev>
4 :
5 : Permission is hereby granted, free of charge, to any person obtaining a copy
6 : of this software and associated documentation files (the "Software"), to deal
7 : in the Software without restriction, including without limitation the rights
8 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 : copies of the Software, and to permit persons to whom the Software is
10 : furnished to do so, subject to the following conditions:
11 :
12 : The above copyright notice and this permission notice shall be included in
13 : all copies or substantial portions of the Software.
14 :
15 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 : THE SOFTWARE.
22 : **/
23 :
24 : #include "SPDbFile.h"
25 :
26 : #include "SPDbAdapter.h"
27 : #include "SPIO.h"
28 : #include "SPDbField.h"
29 : #include "SPDbScheme.h"
30 : #include "SPDbTransaction.h"
31 : #include "SPDbWorker.h"
32 : #include "SPFilesystem.h"
33 :
34 : #if MODULE_STAPPLER_BITMAP
35 : #include "SPBitmap.h"
36 : #endif
37 :
38 : namespace STAPPLER_VERSIONIZED stappler::db {
39 :
40 275 : String File::getFilesystemPath(const ApplicationInterface *app, uint64_t oid) {
41 550 : return toString(app->getDocumentRoot(), "/uploads/", oid);
42 : }
43 :
44 100 : static bool File_isImage(const StringView &type) {
45 100 : return type == "image/gif"
46 100 : || type == "image/jpeg"
47 100 : || type == "image/pjpeg"
48 100 : || type == "image/png"
49 75 : || type == "image/tiff"
50 75 : || type == "image/webp"
51 200 : || type == "image/svg+xml";
52 : }
53 :
54 75 : static bool File_validateFileField(const ApplicationInterface *app, const Field &field, size_t writeSize, const StringView &type) {
55 75 : auto ffield = static_cast<const FieldFile *>(field.getSlot());
56 : // check size
57 75 : if (writeSize > ffield->maxSize) {
58 0 : app->error("Storage", "File is larger then max file size in field", Value {
59 0 : std::make_pair("field", Value(field.getName())),
60 0 : std::make_pair("max", Value((int64_t)ffield->maxSize)),
61 0 : std::make_pair("size", Value((int64_t)writeSize))
62 0 : });
63 0 : return false;
64 : }
65 :
66 : // check type
67 75 : auto &types = ffield->allowedTypes;
68 75 : if (!types.empty()) {
69 0 : bool ret = false;
70 0 : for (auto &it : types) {
71 0 : if (type == it) {
72 0 : ret = true;
73 0 : break;
74 : }
75 : }
76 0 : if (!ret) {
77 0 : app->error("Storage", "Invalid file type for field", Value {
78 0 : std::make_pair("field", Value(field.getName())),
79 0 : std::make_pair("type", Value(type))
80 0 : });
81 0 : return false;
82 : }
83 : }
84 75 : return true;
85 : }
86 :
87 25 : static bool File_validateImageField(const ApplicationInterface *app, const Field &field, size_t writeSize, StringView type, stappler::io::Producer file) {
88 : #ifndef MODULE_STAPPLER_BITMAP
89 : app->error("Storage", "MODULE_STAPPLER_BITMAP was not enabled to support bitmaps within storage");
90 : return false;
91 : #else
92 25 : auto ffield = static_cast<const FieldImage *>(field.getSlot());
93 :
94 : // check size
95 25 : if (writeSize > ffield->maxSize) {
96 0 : app->error("Storage", "File is larger then max file size in field", Value{
97 0 : std::make_pair("field", Value(field.getName())),
98 0 : std::make_pair("max", Value((int64_t)ffield->maxSize)),
99 0 : std::make_pair("size", Value((int64_t)writeSize))
100 0 : });
101 0 : return false;
102 : }
103 :
104 25 : if (!File_isImage(type)) {
105 0 : app->error("Storage", "Unknown image type for field", Value {
106 0 : std::make_pair("field", Value(field.getName())),
107 0 : std::make_pair("type", Value(type))
108 0 : });
109 0 : return false;
110 : }
111 :
112 : // check type
113 25 : auto &types = ffield->allowedTypes;
114 25 : if (!types.empty()) {
115 0 : bool ret = false;
116 0 : for (auto &it : types) {
117 0 : if (type == it) {
118 0 : ret = true;
119 0 : break;
120 : }
121 : }
122 0 : if (!ret) {
123 0 : app->error("Storage", "Invalid file type for field", Value{
124 0 : std::make_pair("field", Value(field.getName())),
125 0 : std::make_pair("type", Value(type))
126 0 : });
127 0 : return false;
128 : }
129 : }
130 :
131 25 : uint32_t width = 0, height = 0;
132 25 : if (!bitmap::getImageSize(file, width, height) && width > 0 && height > 0) {
133 0 : app->error("Storage", "Fail to detect file size with");
134 0 : return false;
135 : }
136 :
137 25 : if (ffield->minImageSize.policy == ImagePolicy::Reject) {
138 25 : if (ffield->minImageSize.width > width || ffield->minImageSize.height > height) {
139 0 : app->error("Storage", "Image is to small, rejected by policy rule", Value{
140 0 : std::make_pair("min", Value {
141 0 : std::make_pair("width", Value(ffield->minImageSize.width)),
142 0 : std::make_pair("height", Value(ffield->minImageSize.height))
143 0 : }),
144 0 : std::make_pair("current", Value{
145 0 : std::make_pair("width", Value(width)),
146 0 : std::make_pair("height", Value(height))
147 0 : })
148 0 : });
149 0 : return false;
150 : }
151 : }
152 :
153 25 : if (ffield->maxImageSize.policy == ImagePolicy::Reject) {
154 0 : if (ffield->maxImageSize.width < width || ffield->maxImageSize.height < height) {
155 0 : app->error("Storage", "Image is to large, rejected by policy rule", Value{
156 0 : std::make_pair("max", Value {
157 0 : std::make_pair("width", Value(ffield->maxImageSize.width)),
158 0 : std::make_pair("height", Value(ffield->maxImageSize.height))
159 0 : }),
160 0 : std::make_pair("current", Value{
161 0 : std::make_pair("width", Value(width)),
162 0 : std::make_pair("height", Value(height))
163 0 : })
164 0 : });
165 0 : return false;
166 : }
167 : }
168 25 : return true;
169 : #endif
170 : }
171 :
172 100 : bool File::validateFileField(const ApplicationInterface *app, const Field &field, const InputFile &file) {
173 100 : if (field.getType() == db::Type::File) {
174 75 : return File_validateFileField(app, field, file.writeSize, file.type);
175 25 : } else if (field.getType() == db::Type::Image) {
176 25 : return File_validateImageField(app, field, file.writeSize, file.type, file.file);
177 : }
178 0 : return true;
179 : }
180 :
181 0 : bool File::validateFileField(const ApplicationInterface *app, const Field &field, const StringView &type, const BytesView &data) {
182 0 : if (field.getType() == db::Type::File) {
183 0 : return File_validateFileField(app, field, data.size(), type);
184 0 : } else if (field.getType() == db::Type::Image) {
185 0 : stappler::CoderSource source(data);
186 0 : return File_validateImageField(app, field, data.size(), type, source);
187 : }
188 0 : return true;
189 : }
190 :
191 100 : Value File::createFile(const Transaction &t, const Field &f, InputFile &file) {
192 100 : auto scheme = t.getAdapter().getApplicationInterface()->getFileScheme();
193 100 : Value fileData;
194 100 : fileData.setString(file.type, "type");
195 100 : fileData.setInteger(file.writeSize, "size");
196 :
197 100 : if (f.getType() == db::Type::Image || File_isImage(file.type)) {
198 : #if MODULE_STAPPLER_BITMAP
199 25 : uint32_t width = 0, height = 0;
200 25 : if (bitmap::getImageSize(file.file, width, height)) {
201 25 : auto &val = fileData.emplace("image");
202 25 : val.setInteger(width, "width");
203 25 : val.setInteger(height, "height");
204 : }
205 : #endif
206 : }
207 :
208 100 : fileData = Worker(*scheme, t).create(fileData, true);
209 100 : if (fileData && fileData.isInteger("__oid")) {
210 100 : auto id = fileData.getInteger("__oid");
211 100 : if (file.save(File::getFilesystemPath(t.getAdapter().getApplicationInterface(), id))) {
212 100 : return Value(id);
213 : }
214 : }
215 :
216 0 : file.close();
217 0 : return Value();
218 100 : }
219 :
220 125 : Value File::createFile(const Transaction &t, const StringView &type, const StringView &path, int64_t mtime) {
221 125 : auto scheme = t.getAdapter().getApplicationInterface()->getFileScheme();
222 125 : Value fileData;
223 125 : filesystem::Stat stat;
224 125 : if (filesystem::stat(path, stat)) {
225 125 : fileData.setInteger(stat.size, "size");
226 : }
227 :
228 125 : if (mtime) {
229 0 : fileData.setInteger(mtime, "mtime");
230 : }
231 :
232 : #if MODULE_STAPPLER_BITMAP
233 125 : uint32_t width = 0, height = 0;
234 125 : auto file = filesystem::openForReading(StringView(path));
235 125 : auto fmt = bitmap::detectFormat(file);
236 125 : if ((fmt.second.empty() && fmt.first == bitmap::FileFormat::Custom)
237 125 : || !bitmap::getImageSize(file, width, height)) {
238 0 : fileData.setString(type, "type");
239 : } else {
240 125 : auto &val = fileData.emplace("image");
241 125 : val.setInteger(width, "width");
242 125 : val.setInteger(height, "height");
243 125 : if (fmt.first != bitmap::FileFormat::Custom) {
244 125 : fileData.setString(bitmap::getMimeType(fmt.first), "type");
245 : } else {
246 0 : fileData.setString(type, "type");
247 : }
248 : }
249 : #else
250 : fileData.setString(type, "type");
251 : #endif
252 :
253 125 : fileData = Worker(*scheme, t).create(fileData, true);
254 125 : if (fileData && fileData.isInteger("__oid")) {
255 125 : auto id = fileData.getInteger("__oid");
256 125 : if (stappler::filesystem::move(path, File::getFilesystemPath(t.getAdapter().getApplicationInterface(), id))) {
257 125 : return Value(id);
258 : } else {
259 0 : Worker(*scheme, t).remove(fileData.getInteger("__oid"));
260 : }
261 : }
262 :
263 0 : stappler::filesystem::remove(path);
264 0 : return Value();
265 125 : }
266 :
267 0 : Value File::createFile(const Transaction &t, const StringView &type, const BytesView &data, int64_t mtime) {
268 0 : auto scheme = t.getAdapter().getApplicationInterface()->getFileScheme();
269 0 : auto size = data.size();
270 :
271 0 : Value fileData;
272 0 : fileData.setString(type, "type");
273 0 : fileData.setInteger(size, "size");
274 0 : if (mtime) {
275 0 : fileData.setInteger(mtime, "mtime");
276 : }
277 :
278 : #if MODULE_STAPPLER_BITMAP
279 0 : uint32_t width = 0, height = 0;
280 0 : stappler::CoderSource source(data);
281 0 : if (bitmap::getImageSize(source, width, height)) {
282 0 : auto &val = fileData.emplace("image");
283 0 : val.setInteger(width, "width");
284 0 : val.setInteger(height, "height");
285 : }
286 : #endif
287 :
288 0 : fileData = Worker(*scheme, t).create(fileData, true);
289 0 : if (fileData && fileData.isInteger("__oid")) {
290 0 : auto id = fileData.getInteger("__oid");
291 0 : if (stappler::filesystem::write(File::getFilesystemPath(t.getAdapter().getApplicationInterface(), id), data)) {
292 0 : return Value(id);
293 : } else {
294 0 : Worker(*scheme, t).remove(fileData.getInteger("__oid"));
295 : }
296 : }
297 :
298 0 : return Value();
299 0 : }
300 :
301 : #if MODULE_STAPPLER_BITMAP
302 150 : static bool getTargetImageSize(size_t W, size_t H, const MinImageSize &min, const MaxImageSize &max
303 : , size_t &tW, size_t &tH) {
304 :
305 150 : if (min.width > W || min.height > H) {
306 0 : float scale = 0.0f;
307 0 : if (min.width == 0) {
308 0 : scale = (float)min.height / (float)H;
309 0 : } else if (min.height == 0) {
310 0 : scale = (float)min.width / (float)W;
311 : } else {
312 0 : scale = std::min((float)min.width / (float)W, (float)min.height / (float)H);
313 : }
314 0 : tW = W * scale; tH = H * scale;
315 0 : return true;
316 : }
317 :
318 150 : if ((max.width != 0 && max.width < W) || (max.height != 0 && max.height < H)) {
319 125 : float scale = 0.0f;
320 125 : if (max.width == 0) {
321 0 : scale = (float)max.height / (float)H;
322 125 : } else if (max.height == 0) {
323 0 : scale = (float)max.width / (float)W;
324 : } else {
325 125 : scale = std::min((float)max.width / (float)W, (float)max.height / (float)H);
326 : }
327 125 : tW = (size_t)W * scale; tH = (size_t)H * scale;
328 125 : return true;
329 : }
330 :
331 25 : tW = W; tH = H;
332 25 : return false;
333 : }
334 :
335 125 : static String saveImage(Bitmap &bmp) {
336 125 : filesystem::File file = filesystem::File::open_tmp(config::UPLOAD_TMP_IMAGE_PREFIX, false);
337 125 : String path(file.path());
338 125 : file.close();
339 :
340 125 : if (!path.empty()) {
341 125 : bool ret = false;
342 125 : auto fmt = bmp.getOriginalFormat();
343 125 : if (fmt == bitmap::FileFormat::Custom) {
344 0 : ret = bmp.save(bmp.getOriginalFormatName(), path);
345 : } else {
346 125 : ret = bmp.save(bmp.getOriginalFormat(), path);
347 : }
348 :
349 125 : if (ret) {
350 125 : return String(path);
351 : }
352 : }
353 :
354 0 : return String();
355 125 : }
356 :
357 125 : static String resizeImage(Bitmap &bmp, size_t width, size_t height) {
358 125 : auto newImage = bmp.resample(width, height);
359 125 : if (newImage) {
360 125 : return saveImage(newImage);
361 : }
362 :
363 0 : return String();
364 125 : }
365 :
366 25 : static Map<String, String> writeImages(const ApplicationInterface *app, const Field &f, InputFile &file) {
367 25 : auto field = static_cast<const FieldImage *>(f.getSlot());
368 :
369 25 : uint32_t width = 0, height = 0;
370 : size_t targetWidth, targetHeight;
371 25 : if (!bitmap::getImageSize(file.file, width, height)) {
372 0 : return Map<String, String>();
373 : }
374 :
375 25 : Map<String, String> ret;
376 :
377 25 : bool needResize = getTargetImageSize(width, height, field->minImageSize, field->maxImageSize, targetWidth, targetHeight);
378 25 : if (needResize || field->thumbnails.size() > 0) {
379 25 : BufferTemplate<Interface> data(file.writeSize);
380 25 : stappler::io::Producer prod(file.file);
381 25 : prod.seek(0, stappler::io::Seek::Set);
382 25 : prod.read(data, file.writeSize);
383 :
384 25 : Bitmap bmp(data.data(), data.size());
385 25 : if (!bmp) {
386 0 : app->error("Storage", "Fail to open image");
387 : } else {
388 25 : if (needResize) {
389 0 : auto fpath = resizeImage(bmp, targetWidth, targetHeight);
390 0 : if (!fpath.empty()) {
391 0 : ret.emplace(f.getName().str<Interface>(), std::move(fpath));
392 : }
393 0 : } else {
394 25 : ret.emplace(f.getName().str<Interface>(), file.file.path());
395 : }
396 :
397 25 : if (field->thumbnails.size() > 0) {
398 150 : for (auto &it : field->thumbnails) {
399 125 : getTargetImageSize(width, height, MinImageSize(), MaxImageSize(it.width, it.height),
400 : targetWidth, targetHeight);
401 :
402 125 : auto fpath = resizeImage(bmp, targetWidth, targetHeight);
403 125 : if (!fpath.empty()) {
404 125 : ret.emplace(it.name, std::move(fpath));
405 : }
406 125 : }
407 : }
408 : }
409 25 : } else {
410 0 : ret.emplace(f.getName().str<Interface>(), file.path);
411 : }
412 :
413 25 : return ret;
414 25 : }
415 :
416 0 : static Map<String, String> writeImages(const ApplicationInterface *app, const Field &f, const StringView &type, const BytesView &data) {
417 0 : auto field = static_cast<const FieldImage *>(f.getSlot());
418 :
419 0 : uint32_t width = 0, height = 0;
420 : size_t targetWidth, targetHeight;
421 0 : stappler::CoderSource source(data);
422 0 : if (!bitmap::getImageSize(source, width, height)) {
423 0 : return Map<String, String>();
424 : }
425 :
426 0 : Map<String, String> ret;
427 :
428 0 : bool needResize = getTargetImageSize(width, height, field->minImageSize, field->maxImageSize, targetWidth, targetHeight);
429 0 : if (needResize || field->thumbnails.size() > 0) {
430 0 : Bitmap bmp(data);
431 0 : if (!bmp) {
432 0 : app->error("Storage", "Fail to open image");
433 : } else {
434 0 : if (needResize) {
435 0 : auto fpath = resizeImage(bmp, targetWidth, targetHeight);
436 0 : if (!fpath.empty()) {
437 0 : ret.emplace(f.getName().str<Interface>(), std::move(fpath));
438 : }
439 0 : } else {
440 0 : auto fpath = saveImage(bmp);
441 0 : if (!fpath.empty()) {
442 0 : ret.emplace(f.getName().str<Interface>(), std::move(fpath));
443 : }
444 0 : }
445 :
446 0 : if (field->thumbnails.size() > 0) {
447 0 : for (auto &it : field->thumbnails) {
448 0 : getTargetImageSize(width, height, MinImageSize(), MaxImageSize(it.width, it.height),
449 : targetWidth, targetHeight);
450 :
451 0 : auto fpath = resizeImage(bmp, targetWidth, targetHeight);
452 0 : if (!fpath.empty()) {
453 0 : ret.emplace(it.name, std::move(fpath));
454 : }
455 0 : }
456 : }
457 : }
458 0 : } else {
459 0 : filesystem::File file = filesystem::File::open_tmp(config::UPLOAD_TMP_IMAGE_PREFIX, false);
460 0 : file.xsputn((const char *)data.data(), data.size());
461 0 : ret.emplace(f.getName().str<Interface>(), file.path());
462 0 : file.close();
463 0 : }
464 :
465 0 : return ret;
466 0 : }
467 : #endif
468 :
469 25 : Value File::createImage(const Transaction &t, const Field &f, InputFile &file) {
470 25 : Value ret;
471 :
472 : #if MODULE_STAPPLER_BITMAP
473 25 : auto files = writeImages(t.getAdapter().getApplicationInterface(), f, file);
474 175 : for (auto & it : files) {
475 150 : if (it.first == f.getName() && it.second == file.path) {
476 25 : auto val = createFile(t, f, file);
477 25 : if (val.isInteger()) {
478 25 : ret.setValue(std::move(val), it.first);
479 : }
480 25 : } else {
481 125 : auto &field = it.first;
482 125 : auto &filePath = it.second;
483 :
484 125 : auto val = createFile(t, file.type, filePath);
485 125 : if (val.isInteger()) {
486 125 : ret.setValue(std::move(val), field);
487 : }
488 125 : }
489 : }
490 : #endif
491 50 : return ret;
492 25 : }
493 :
494 0 : Value File::createImage(const Transaction &t, const Field &f, const StringView &type, const BytesView &data, int64_t mtime) {
495 0 : Value ret;
496 :
497 : #if MODULE_STAPPLER_BITMAP
498 0 : auto files = writeImages(t.getAdapter().getApplicationInterface(), f, type, data);
499 0 : for (auto & it : files) {
500 0 : auto &field = it.first;
501 0 : auto &filePath = it.second;
502 :
503 0 : auto val = createFile(t, type, filePath, mtime);
504 0 : if (val.isInteger()) {
505 0 : ret.setValue(std::move(val), field);
506 : }
507 0 : }
508 : #endif
509 0 : return ret;
510 0 : }
511 :
512 0 : bool File::removeFile(const ApplicationInterface *app, const Value &val) {
513 0 : int64_t id = 0;
514 0 : if (val.isInteger()) {
515 0 : id = val.asInteger();
516 0 : } else if (val.isInteger("__oid")) {
517 0 : id = val.getInteger("__oid");
518 : }
519 :
520 0 : return removeFile(app, id);
521 : }
522 0 : bool File::removeFile(const ApplicationInterface *app, int64_t id) {
523 0 : if (id) {
524 0 : filesystem::remove(File::getFilesystemPath(app, id));
525 0 : return true;
526 : }
527 :
528 0 : return false;
529 : }
530 :
531 0 : bool File::purgeFile(const Transaction &t, const Value &val) {
532 0 : int64_t id = 0;
533 0 : if (val.isInteger()) {
534 0 : id = val.asInteger();
535 0 : } else if (val.isInteger("__oid")) {
536 0 : id = val.getInteger("__oid");
537 : }
538 0 : return purgeFile(t, id);
539 : }
540 :
541 0 : bool File::purgeFile(const Transaction &t, int64_t id) {
542 0 : if (id) {
543 0 : if (auto scheme = t.getAdapter().getApplicationInterface()->getFileScheme()) {
544 0 : Worker(*scheme, t).remove(id);
545 0 : filesystem::remove(File::getFilesystemPath(t.getAdapter().getApplicationInterface(), id));
546 0 : return true;
547 : }
548 : }
549 0 : return false;
550 : }
551 :
552 0 : Value File::getData(const Transaction &t, uint64_t id) {
553 0 : if (auto scheme = t.getAdapter().getApplicationInterface()->getFileScheme()) {
554 0 : return Worker(*scheme, t).get(id);
555 : }
556 0 : return Value();
557 : }
558 :
559 0 : void File::setData(const Transaction &t, uint64_t id, const Value &val) {
560 0 : if (auto scheme = t.getAdapter().getApplicationInterface()->getFileScheme()) {
561 0 : Worker(*scheme, t).update(id, val);
562 : }
563 0 : }
564 :
565 : }
|