Line data Source code
1 : /**
2 : Copyright (c) 2022 Roman Katuntsev <sbkarr@stappler.org>
3 : Copyright (c) 2023 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 "SPFilepath.h"
25 : #include "SPFilesystem.h"
26 :
27 : namespace STAPPLER_VERSIONIZED stappler::filepath {
28 :
29 25004 : SPUNUSED static bool inAppBundle(StringView path) {
30 25004 : if (filepath::isAbsolute(path)) {
31 24979 : return false;
32 : }
33 50 : if (filepath::isBundled(path) ||
34 25 : (!filepath::isAboveRoot(path) && filesystem::platform::_exists(path))) {
35 0 : return true;
36 : }
37 25 : return false;
38 : }
39 :
40 : // check if filepath is absolute
41 58712 : bool isAbsolute(StringView path) {
42 58712 : if (path.empty()) {
43 0 : return true;
44 : }
45 58712 : return path[0] == '/';
46 : }
47 :
48 0 : bool isCanonical(StringView path) {
49 0 : if (path.empty()) {
50 0 : return false;
51 : }
52 0 : return path[0] == '%';
53 : }
54 :
55 : // check if filepath is in application bundle
56 757 : bool isBundled(StringView path) {
57 757 : if (path.size() >= "%PLATFORM%:"_len) {
58 757 : return path.starts_with("%PLATFORM%:");
59 : }
60 0 : return false;
61 : }
62 :
63 : // check if filepath above it's current root
64 238 : bool isAboveRoot(StringView path) {
65 238 : size_t components = 0;
66 238 : StringView r(path);
67 664 : while (!r.empty()) {
68 476 : auto str = r.readUntil<StringView::Chars<'/'>>();
69 476 : if (str == ".." && str.size() == 2) {
70 0 : if (components == 0) {
71 50 : return true;
72 : }
73 0 : -- components;
74 476 : } else if ((str == "." && str.size() == 1) || str.size() == 0) {return false;
75 : } else {
76 426 : ++ components;
77 : }
78 426 : if (r.is('/')) {
79 238 : ++ r;
80 : }
81 : }
82 188 : return false;
83 : }
84 :
85 : // check for ".", ".." and double slashes in path
86 27248 : bool validatePath(StringView path) {
87 27248 : StringView r(path);
88 27248 : if (r.is('/')) {
89 27104 : ++ r;
90 : }
91 233915 : while (!r.empty()) {
92 206692 : auto str = r.readUntil<StringView::Chars<'/'>>();
93 206692 : if ((str == ".." && str.size() == 2) || (str == "." && str.size() == 1) || str.size() == 0) {
94 25 : return false;
95 : }
96 206667 : if (r.is('/')) {
97 179444 : ++ r;
98 : }
99 : }
100 27223 : return true;
101 : }
102 :
103 : template <typename Interface>
104 27248 : auto absolute_fn(StringView path, bool writable) -> typename Interface::StringType {
105 27248 : if (path.empty()) {
106 0 : return typename Interface::StringType();
107 : }
108 27248 : if (path.front() == '%') {
109 0 : if (path.starts_with("%CACHE%")) {
110 0 : return filesystem::cachesPath<Interface>(path.sub(7), true);
111 0 : } else if (path.starts_with("%DOCUMENTS%")) {
112 0 : return filesystem::documentsPath<Interface>(path.sub(11), true);
113 0 : } else if (path.starts_with("%WRITEABLE%")) {
114 0 : return filesystem::writablePath<Interface>(path.sub(11), true);
115 0 : } else if (path.starts_with("%CURRENT%")) {
116 0 : return filesystem::currentDir<Interface>(path.sub(9), true);
117 0 : } else if (path.starts_with("%PLATFORM%:")) {
118 0 : writable = false;
119 : }
120 : }
121 :
122 27248 : if (isAbsolute(path)) {
123 27104 : return validatePath(path)?path.str<Interface>():reconstructPath<Interface>(path);
124 : }
125 :
126 144 : if (!writable && !isAboveRoot(path)) {
127 119 : if (validatePath(path)) {
128 94 : return filesystem::platform::_exists(path)?path.str<Interface>():filesystem::writablePath<Interface>(path);
129 : } else {
130 25 : auto ret = reconstructPath<Interface>(path);
131 25 : return filesystem::platform::_exists(ret)?ret:filesystem::writablePath<Interface>(ret);
132 25 : }
133 : }
134 :
135 25 : return validatePath(path)
136 : ? filesystem::writablePath<Interface>(path)
137 25 : : reconstructPath<Interface>(filesystem::writablePath<Interface>(path));
138 : }
139 :
140 : template<>
141 27223 : auto absolute<memory::StandartInterface>(StringView path, bool writable) -> memory::StandartInterface::StringType {
142 27223 : return absolute_fn<memory::StandartInterface>(path, writable);
143 : }
144 :
145 : template<>
146 25 : auto absolute<memory::PoolInterface>(StringView path, bool writable) -> memory::PoolInterface::StringType {
147 25 : return absolute_fn<memory::PoolInterface>(path, writable);
148 : }
149 :
150 : template <typename Interface>
151 0 : auto canonical_fn(StringView path) -> typename Interface::StringType {
152 0 : if (path.empty()) {
153 0 : return typename Interface::StringType();
154 : }
155 0 : if (path.front() == '%') {
156 0 : return path.str<Interface>();
157 : }
158 :
159 0 : bool isPlatform = filepath::isBundled(path);
160 0 : if (!isPlatform && inAppBundle(path)) {
161 0 : return StringView::merge<Interface>("%PLATFORM%:", path);
162 0 : } else if (isPlatform) {
163 0 : return path.str<Interface>();
164 : }
165 :
166 0 : auto cachePath = filesystem::cachesPath<Interface>();
167 0 : if (path.starts_with(StringView(cachePath))) {
168 0 : return merge<Interface>("%CACHE%", path.sub(cachePath.size()));
169 : }
170 :
171 0 : auto documentsPath = filesystem::documentsPath<Interface>();
172 0 : if (path.starts_with(StringView(documentsPath)) == 0) {
173 0 : return merge<Interface>("%DOCUMENTS%", path.sub(documentsPath.size()));
174 : }
175 :
176 0 : auto writablePath = filesystem::writablePath<Interface>();
177 0 : if (path.starts_with(StringView(writablePath)) == 0) {
178 0 : return merge<Interface>("%WRITEABLE%", path.sub(writablePath.size()));
179 : }
180 :
181 0 : auto currentDir = filesystem::currentDir<Interface>();
182 0 : if (path.starts_with(StringView(currentDir)) == 0) {
183 0 : return merge<Interface>("%CURRENT%", path.sub(currentDir.size()));
184 : }
185 :
186 0 : return path.str<Interface>();
187 0 : }
188 :
189 : template<>
190 0 : auto canonical<memory::StandartInterface>(StringView path) -> memory::StandartInterface::StringType {
191 0 : return canonical_fn<memory::StandartInterface>(path);
192 : }
193 :
194 : template<>
195 0 : auto canonical<memory::PoolInterface>(StringView path) -> memory::PoolInterface::StringType {
196 0 : return canonical_fn<memory::PoolInterface>(path);
197 : }
198 :
199 250 : StringView root(StringView path) {
200 250 : size_t pos = path.rfind('/');
201 250 : if (pos == maxOf<size_t>()) {
202 0 : return StringView();
203 : } else {
204 250 : if (pos == 0) {
205 0 : return StringView("/");
206 : } else {
207 250 : return path.sub(0, pos);
208 : }
209 : }
210 : }
211 :
212 25700 : StringView lastComponent(StringView path) {
213 25700 : size_t pos = path.rfind('/');
214 25700 : if (pos != maxOf<size_t>()) {
215 25675 : return path.sub(pos + 1);
216 : } else {
217 25 : return path;
218 : }
219 : }
220 :
221 0 : StringView lastComponent(StringView path, size_t allowedComponents) {
222 0 : if (allowedComponents == 0) {
223 0 : return "";
224 : }
225 0 : size_t pos = path.rfind('/');
226 0 : allowedComponents --;
227 0 : if (pos == 0) {
228 0 : pos = maxOf<size_t>();
229 : }
230 :
231 0 : while (pos != maxOf<size_t>() && allowedComponents > 0) {
232 0 : pos = path.rfind('/', pos - 1);
233 0 : allowedComponents --;
234 0 : if (pos == 0) {
235 0 : pos = maxOf<size_t>();
236 : }
237 : }
238 :
239 0 : if (pos != maxOf<size_t>()) {
240 0 : return path.sub(pos + 1);
241 : } else {
242 0 : return path;
243 : }
244 : }
245 :
246 0 : StringView fullExtension(StringView path) {
247 0 : auto cmp = lastComponent(path);
248 :
249 0 : size_t pos = cmp.find('.');
250 0 : if (pos == maxOf<size_t>()) {
251 0 : return StringView();
252 : } else {
253 0 : return cmp.sub(pos + 1);
254 : }
255 : }
256 :
257 17250 : StringView lastExtension(StringView path) {
258 17250 : auto cmp = lastComponent(path);
259 :
260 17250 : size_t pos = cmp.rfind('.');
261 17250 : if (pos == maxOf<size_t>()) {
262 0 : return "";
263 : } else {
264 17250 : return cmp.sub(pos + 1);
265 : }
266 : }
267 :
268 7925 : StringView name(StringView path) {
269 7925 : auto cmp = lastComponent(path);
270 :
271 7925 : size_t pos = cmp.find('.');
272 7925 : if (pos == maxOf<size_t>()) {
273 0 : return cmp;
274 : } else {
275 7925 : return cmp.sub(0, pos);
276 : }
277 : }
278 :
279 : template <typename Interface>
280 23184 : auto do_merge(StringView root, StringView path) -> typename Interface::StringType {
281 23184 : if (path.empty()) {
282 0 : return root.str<Interface>();
283 : }
284 23184 : return StringView::merge<Interface, '/'>(root, path);
285 : }
286 :
287 :
288 : template <typename SourceVector>
289 0 : static size_t getMergeSize(const SourceVector &vec) {
290 0 : size_t ret = vec.size();
291 0 : for (auto it = vec.begin(); it != vec.end(); it ++) {
292 0 : ret += it->size();
293 : }
294 0 : return ret;
295 : }
296 :
297 : template <typename Buf, typename SourceVector>
298 0 : static void doMerge(Buf &out, const SourceVector &vec) {
299 0 : auto stripSeps = [] (auto str) {
300 0 : StringView tmp(str);
301 0 : tmp.trimChars<StringView::Chars<'/'>>();
302 0 : return tmp;
303 : };
304 :
305 0 : bool hasSeparator = true;
306 0 : auto it = vec.begin();
307 0 : for (; it != vec.end(); it ++) {
308 0 : if (it->empty()) {
309 0 : continue;
310 : }
311 :
312 0 : if (!hasSeparator) {
313 0 : out.push_back('/');
314 : } else {
315 0 : hasSeparator = false;
316 : }
317 :
318 0 : auto tmp = stripSeps(*it);
319 0 : out.append(tmp.data(), tmp.size());
320 : }
321 0 : }
322 :
323 : template <>
324 22109 : auto _merge<memory::StandartInterface>(StringView root, StringView path) -> memory::StandartInterface::StringType {
325 22109 : return do_merge<memory::StandartInterface>(root, path);
326 : }
327 :
328 : template <>
329 1075 : auto _merge<memory::PoolInterface>(StringView root, StringView path) -> memory::PoolInterface::StringType {
330 1075 : return do_merge<memory::PoolInterface>(root, path);
331 : }
332 :
333 : template <>
334 0 : auto merge<memory::StandartInterface>(SpanView<std::string> vec) -> memory::StandartInterface::StringType {
335 0 : memory::StandartInterface::StringType ret; ret.reserve(getMergeSize(vec));
336 0 : doMerge(ret, vec);
337 0 : return ret;
338 0 : }
339 :
340 : template <>
341 0 : auto merge<memory::PoolInterface>(SpanView<std::string> vec) -> memory::PoolInterface::StringType {
342 0 : memory::PoolInterface::StringType ret; ret.reserve(getMergeSize(vec));
343 0 : doMerge(ret, vec);
344 0 : return ret;
345 0 : }
346 :
347 :
348 : template <>
349 0 : auto merge<memory::StandartInterface>(SpanView<memory::string> vec) -> memory::StandartInterface::StringType {
350 0 : memory::StandartInterface::StringType ret; ret.reserve(getMergeSize(vec));
351 0 : doMerge(ret, vec);
352 0 : return ret;
353 0 : }
354 :
355 : template <>
356 0 : auto merge<memory::PoolInterface>(SpanView<memory::string> vec) -> memory::PoolInterface::StringType {
357 0 : memory::PoolInterface::StringType ret; ret.reserve(getMergeSize(vec));
358 0 : doMerge(ret, vec);
359 0 : return ret;
360 0 : }
361 :
362 :
363 : template <>
364 0 : auto merge<memory::StandartInterface>(SpanView<StringView> vec) -> memory::StandartInterface::StringType {
365 0 : memory::StandartInterface::StringType ret; ret.reserve(getMergeSize(vec));
366 0 : doMerge(ret, vec);
367 0 : return ret;
368 0 : }
369 :
370 : template <>
371 0 : auto merge<memory::PoolInterface>(SpanView<StringView> vec) -> memory::PoolInterface::StringType {
372 0 : memory::PoolInterface::StringType ret; ret.reserve(getMergeSize(vec));
373 0 : doMerge(ret, vec);
374 0 : return ret;
375 0 : }
376 :
377 :
378 : template <>
379 22109 : auto merge<memory::StandartInterface>(stappler::memory::StandartInterface::StringType &&str) -> memory::StandartInterface::StringType {
380 22109 : return str;
381 : }
382 :
383 : template <>
384 0 : auto merge<memory::PoolInterface>(stappler::memory::StandartInterface::StringType &&str) -> memory::PoolInterface::StringType {
385 0 : return StringView(str).str<memory::PoolInterface>();
386 : }
387 :
388 :
389 : template <>
390 0 : auto merge<memory::StandartInterface>(stappler::memory::PoolInterface::StringType &&str) -> memory::StandartInterface::StringType {
391 0 : return StringView(str).str<memory::StandartInterface>();
392 : }
393 :
394 : template <>
395 1075 : auto merge<memory::PoolInterface>(stappler::memory::PoolInterface::StringType &&str) -> memory::PoolInterface::StringType {
396 1075 : return str;
397 : }
398 :
399 0 : size_t extensionCount(StringView path) {
400 0 : size_t ret = 0;
401 0 : auto cmp = lastComponent(path);
402 0 : for (auto c : cmp) {
403 0 : if (c == '.') { ret ++; }
404 : }
405 0 : return ret;
406 : }
407 :
408 0 : StringView extensionForContentType(StringView ct) {
409 : // TODO: reimplement with list from Serenity
410 0 : if (ct.equals("application/pdf") == 0 || ct.equals("application/x-pdf") == 0) {
411 0 : return ".pdf";
412 0 : } else if (ct.equals("image/jpeg") == 0 || ct.equals("image/pjpeg") == 0) {
413 0 : return ".jpeg";
414 0 : } else if (ct.equals("image/png") == 0) {
415 0 : return ".png";
416 0 : } else if (ct.equals("image/gif") == 0) {
417 0 : return ".gif";
418 0 : } else if (ct.equals("image/tiff") == 0) {
419 0 : return ".tiff";
420 0 : } else if (ct.equals("application/json") == 0) {
421 0 : return ".json";
422 0 : } else if (ct.equals("application/zip") == 0) {
423 0 : return ".zip";
424 : }
425 0 : return StringView();
426 : }
427 :
428 : }
|