• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LibreSign / libresign / 20395868261

20 Dec 2025 02:42PM UTC coverage: 43.246%. First build
20395868261

Pull #6242

github

web-flow
Merge b81fd641a into fae2922ee
Pull Request #6242: feat: envelope support

162 of 510 new or added lines in 13 files covered. (31.76%)

6064 of 14022 relevant lines covered (43.25%)

5.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.44
/lib/Service/TFile.php
1
<?php
2

3
declare(strict_types=1);
4
/**
5
 * SPDX-FileCopyrightText: 2020-2024 LibreCode coop and contributors
6
 * SPDX-License-Identifier: AGPL-3.0-or-later
7
 */
8

9
namespace OCA\Libresign\Service;
10

11
use OCA\Libresign\Exception\LibresignException;
12
use OCA\Libresign\Handler\DocMdpHandler;
13
use OCA\Libresign\Helper\FileUploadHelper;
14
use OCA\Libresign\Vendor\setasign\Fpdi\PdfParserService\Type\PdfTypeException;
15
use OCP\Files\Node;
16
use OCP\Http\Client\IClientService;
17

18
trait TFile {
19
        /** @var ?string */
20
        private $mimetype = null;
21
        protected IClientService $client;
22
        protected DocMdpHandler $docMdpHandler;
23
        protected FileUploadHelper $uploadHelper;
24

25
        public function getNodeFromData(array $data): Node {
26
                if (!$this->folderService->getUserId()) {
15✔
27
                        $this->folderService->setUserId($data['userManager']->getUID());
12✔
28
                }
29

30
                if (isset($data['uploadedFile'])) {
15✔
NEW
31
                        return $this->getNodeFromUploadedFile($data);
×
32
                }
33

34
                if (isset($data['file']['fileNode']) && $data['file']['fileNode'] instanceof Node) {
15✔
35
                        return $data['file']['fileNode'];
1✔
36
                }
37
                if (isset($data['file']['fileId'])) {
15✔
38
                        return $this->folderService->getFileById($data['file']['fileId']);
×
39
                }
40
                if (isset($data['file']['path'])) {
15✔
41
                        return $this->folderService->getFileByPath($data['file']['path']);
×
42
                }
43

44
                $content = $this->getFileRaw($data);
15✔
45
                $extension = $this->getExtension($content);
15✔
46

47
                $this->validateFileContent($content, $extension);
15✔
48

49
                $userFolder = $this->folderService->getFolder();
15✔
50
                $folderName = $this->folderService->getFolderName($data, $data['userManager']);
15✔
51
                $folderToFile = $userFolder->newFolder($folderName);
15✔
52
                return $folderToFile->newFile($data['name'] . '.' . $extension, $content);
15✔
53
        }
54

55
        public function getNodeFromUploadedFile(array $data): Node {
NEW
56
                if (!$this->folderService->getUserId()) {
×
NEW
57
                        $this->folderService->setUserId($data['userManager']->getUID());
×
58
                }
59

NEW
60
                $uploadedFile = $data['uploadedFile'];
×
61

NEW
62
                $this->uploadHelper->validateUploadedFile($uploadedFile);
×
NEW
63
                $content = $this->uploadHelper->readUploadedFile($uploadedFile);
×
64

NEW
65
                $extension = $this->getExtension($content);
×
NEW
66
                $this->validateFileContent($content, $extension);
×
67

NEW
68
                $userFolder = $this->folderService->getFolder();
×
NEW
69
                $folderName = $this->folderService->getFolderName($data, $data['userManager']);
×
NEW
70
                $folderToFile = $userFolder->newFolder($folderName);
×
71

NEW
72
                @unlink($uploadedFile['tmp_name']);
×
73

NEW
74
                return $folderToFile->newFile($data['name'] . '.' . $extension, $content);
×
75
        }
76

77
        /**
78
         * @throws \Exception
79
         * @throws LibresignException
80
         */
81
        public function validateFileContent(string $content, string $extension): void {
82
                if ($extension === 'pdf') {
20✔
83
                        $this->validatePdfStringWithFpdi($content);
19✔
84
                        $this->validateDocMdpAllowsSignatures($content);
19✔
85
                }
86
        }
87

88
        private function setMimeType(string $mimetype): void {
89
                $this->validateHelper->validateMimeTypeAcceptedByMime($mimetype);
15✔
90
                $this->mimetype = $mimetype;
15✔
91
        }
92

93
        private function getMimeType(string $content): ?string {
94
                if (!$this->mimetype) {
15✔
95
                        $this->setMimeType($this->mimeTypeDetector->detectString($content));
15✔
96
                }
97
                return $this->mimetype;
15✔
98
        }
99

100
        private function getExtension(string $content): string {
101
                $mimetype = $this->getMimeType($content);
15✔
102
                $mappings = $this->mimeTypeDetector->getAllMappings();
15✔
103
                foreach ($mappings as $ext => $mimetypes) {
15✔
104
                        // Single digit extensions will be treated as integers
105
                        // Let's make sure they are strings
106
                        // https://github.com/nextcloud/server/issues/42902
107
                        $ext = (string)$ext;
15✔
108
                        if ($ext[0] === '_') {
15✔
109
                                // comment
110
                                continue;
15✔
111
                        }
112
                        if (in_array($mimetype, $mimetypes)) {
15✔
113
                                return $ext;
15✔
114
                        }
115
                }
116
                return '';
×
117
        }
118

119
        /**
120
         * @return resource|string
121
         */
122
        private function getFileRaw(array $data) {
123
                if (!empty($data['file']['url'])) {
15✔
124
                        if (!filter_var($data['file']['url'], FILTER_VALIDATE_URL)) {
×
125
                                throw new \Exception($this->l10n->t('Invalid URL file'));
×
126
                        }
127
                        try {
128
                                $response = $this->client->newClient()->get($data['file']['url']);
×
129
                        } catch (\Throwable) {
×
130
                                throw new \Exception($this->l10n->t('Invalid URL file'));
×
131
                        }
132
                        $mimetypeFromHeader = $response->getHeader('Content-Type');
×
133
                        $content = (string)$response->getBody();
×
134
                        if (!$content) {
×
135
                                throw new \Exception($this->l10n->t('Empty file'));
×
136
                        }
137
                        $mimeTypeFromContent = $this->getMimeType($content);
×
138
                        if ($mimetypeFromHeader !== $mimeTypeFromContent) {
×
139
                                throw new \Exception($this->l10n->t('Invalid URL file'));
×
140
                        }
141
                } else {
142
                        $content = $this->getFileFromBase64($data['file']['base64']);
15✔
143
                }
144
                return $content;
15✔
145
        }
146

147
        private function getFileFromBase64(string $base64): string {
148
                $withMime = explode(',', $base64);
15✔
149
                if (count($withMime) === 2) {
15✔
150
                        $withMime[0] = explode(';', $withMime[0]);
×
151
                        $withMime[0][0] = explode(':', $withMime[0][0]);
×
152
                        $mimeTypeFromType = $withMime[0][0][1];
×
153

154
                        $base64 = $withMime[1];
×
155

156
                        $content = base64_decode($base64);
×
157
                        $mimeTypeFromContent = $this->getMimeType($content);
×
158
                        if ($mimeTypeFromType !== $mimeTypeFromContent) {
×
159
                                throw new \Exception($this->l10n->t('Invalid URL file'));
×
160
                        }
161
                        $this->setMimeType($mimeTypeFromContent);
×
162
                } else {
163
                        $content = base64_decode($base64);
15✔
164
                        $this->getMimeType($content);
15✔
165
                }
166
                return $content;
15✔
167
        }
168

169
        /**
170
         * Validates a PDF. Triggers error if invalid.
171
         *
172
         * @param string $string
173
         *
174
         * @throws PdfTypeException
175
         */
176
        private function validatePdfStringWithFpdi($string): void {
177
                try {
178
                        $parser = new \OCA\Libresign\Vendor\Smalot\PdfParser\Parser();
19✔
179
                        $parser->parseContent($string);
19✔
180
                } catch (\Throwable $th) {
×
181
                        $this->logger->error($th->getMessage());
×
182
                        throw new \Exception($this->l10n->t('Invalid PDF'));
×
183
                }
184
        }
185

186
        /**
187
         * @throws LibresignException
188
         */
189
        private function validateDocMdpAllowsSignatures(string $pdfContent): void {
190
                $resource = fopen('php://memory', 'r+');
19✔
191
                if (!is_resource($resource)) {
19✔
192
                        return;
×
193
                }
194

195
                try {
196
                        fwrite($resource, $pdfContent);
19✔
197
                        rewind($resource);
19✔
198

199
                        if (!$this->docMdpHandler->allowsAdditionalSignatures($resource)) {
19✔
200
                                throw new LibresignException(
1✔
201
                                        $this->l10n->t('This document has been certified with no changes allowed, so no additional signatures can be added.')
1✔
202
                                );
1✔
203
                        }
204
                } finally {
205
                        fclose($resource);
19✔
206
                }
207
        }
208
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc