1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qqmlfile.h" |
5 | |
6 | #include <QtCore/qurl.h> |
7 | #include <QtCore/qobject.h> |
8 | #include <QtCore/qmetaobject.h> |
9 | #include <QtCore/qfile.h> |
10 | #include <private/qqmlengine_p.h> |
11 | #include <private/qqmlglobal_p.h> |
12 | |
13 | /*! |
14 | \class QQmlFile |
15 | \brief The QQmlFile class gives access to local and remote files. |
16 | |
17 | \internal |
18 | |
19 | Supports file:// and qrc:/ uris and whatever QNetworkAccessManager supports. |
20 | */ |
21 | |
22 | #define QQMLFILE_MAX_REDIRECT_RECURSION 16 |
23 | |
24 | QT_BEGIN_NAMESPACE |
25 | |
26 | static char qrc_string[] = "qrc" ; |
27 | static char file_string[] = "file" ; |
28 | |
29 | #if defined(Q_OS_ANDROID) |
30 | static char assets_string[] = "assets" ; |
31 | static char content_string[] = "content" ; |
32 | static char authority_externalstorage[] = "com.android.externalstorage.documents" ; |
33 | static char authority_downloads_documents[] = "com.android.providers.downloads.documents" ; |
34 | static char authority_media_documents[] = "com.android.providers.media.documents" ; |
35 | #endif |
36 | |
37 | class QQmlFilePrivate; |
38 | |
39 | #if QT_CONFIG(qml_network) |
40 | class QQmlFileNetworkReply : public QObject |
41 | { |
42 | Q_OBJECT |
43 | public: |
44 | QQmlFileNetworkReply(QQmlEngine *, QQmlFilePrivate *, const QUrl &); |
45 | ~QQmlFileNetworkReply(); |
46 | |
47 | signals: |
48 | void finished(); |
49 | void downloadProgress(qint64, qint64); |
50 | |
51 | public slots: |
52 | void networkFinished(); |
53 | void networkDownloadProgress(qint64, qint64); |
54 | |
55 | public: |
56 | static int finishedIndex; |
57 | static int downloadProgressIndex; |
58 | static int networkFinishedIndex; |
59 | static int networkDownloadProgressIndex; |
60 | static int replyFinishedIndex; |
61 | static int replyDownloadProgressIndex; |
62 | |
63 | private: |
64 | QQmlEngine *m_engine; |
65 | QQmlFilePrivate *m_p; |
66 | |
67 | int m_redirectCount; |
68 | QNetworkReply *m_reply; |
69 | }; |
70 | #endif |
71 | |
72 | class QQmlFilePrivate |
73 | { |
74 | public: |
75 | QQmlFilePrivate(); |
76 | |
77 | mutable QUrl url; |
78 | mutable QString urlString; |
79 | |
80 | QByteArray data; |
81 | |
82 | enum Error { |
83 | None, NotFound, CaseMismatch, Network |
84 | }; |
85 | |
86 | Error error; |
87 | QString errorString; |
88 | #if QT_CONFIG(qml_network) |
89 | QQmlFileNetworkReply *reply; |
90 | #endif |
91 | }; |
92 | |
93 | #if QT_CONFIG(qml_network) |
94 | int QQmlFileNetworkReply::finishedIndex = -1; |
95 | int QQmlFileNetworkReply::downloadProgressIndex = -1; |
96 | int QQmlFileNetworkReply::networkFinishedIndex = -1; |
97 | int QQmlFileNetworkReply::networkDownloadProgressIndex = -1; |
98 | int QQmlFileNetworkReply::replyFinishedIndex = -1; |
99 | int QQmlFileNetworkReply::replyDownloadProgressIndex = -1; |
100 | |
101 | QQmlFileNetworkReply::QQmlFileNetworkReply(QQmlEngine *e, QQmlFilePrivate *p, const QUrl &url) |
102 | : m_engine(e), m_p(p), m_redirectCount(0), m_reply(nullptr) |
103 | { |
104 | if (finishedIndex == -1) { |
105 | finishedIndex = QMetaMethod::fromSignal(signal: &QQmlFileNetworkReply::finished).methodIndex(); |
106 | downloadProgressIndex = QMetaMethod::fromSignal(signal: &QQmlFileNetworkReply::downloadProgress).methodIndex(); |
107 | const QMetaObject *smo = &staticMetaObject; |
108 | networkFinishedIndex = smo->indexOfMethod(method: "networkFinished()" ); |
109 | networkDownloadProgressIndex = smo->indexOfMethod(method: "networkDownloadProgress(qint64,qint64)" ); |
110 | |
111 | replyFinishedIndex = QMetaMethod::fromSignal(signal: &QNetworkReply::finished).methodIndex(); |
112 | replyDownloadProgressIndex = QMetaMethod::fromSignal(signal: &QNetworkReply::downloadProgress).methodIndex(); |
113 | } |
114 | Q_ASSERT(finishedIndex != -1 && downloadProgressIndex != -1 && |
115 | networkFinishedIndex != -1 && networkDownloadProgressIndex != -1 && |
116 | replyFinishedIndex != -1 && replyDownloadProgressIndex != -1); |
117 | |
118 | QNetworkRequest req(url); |
119 | req.setAttribute(code: QNetworkRequest::HttpPipeliningAllowedAttribute, value: true); |
120 | |
121 | m_reply = m_engine->networkAccessManager()->get(request: req); |
122 | QMetaObject::connect(sender: m_reply, signal_index: replyFinishedIndex, receiver: this, method_index: networkFinishedIndex); |
123 | QMetaObject::connect(sender: m_reply, signal_index: replyDownloadProgressIndex, receiver: this, method_index: networkDownloadProgressIndex); |
124 | } |
125 | |
126 | QQmlFileNetworkReply::~QQmlFileNetworkReply() |
127 | { |
128 | if (m_reply) { |
129 | m_reply->disconnect(); |
130 | m_reply->deleteLater(); |
131 | } |
132 | } |
133 | |
134 | void QQmlFileNetworkReply::networkFinished() |
135 | { |
136 | ++m_redirectCount; |
137 | if (m_redirectCount < QQMLFILE_MAX_REDIRECT_RECURSION) { |
138 | QVariant redirect = m_reply->attribute(code: QNetworkRequest::RedirectionTargetAttribute); |
139 | if (redirect.isValid()) { |
140 | QUrl url = m_reply->url().resolved(relative: redirect.toUrl()); |
141 | |
142 | QNetworkRequest req(url); |
143 | req.setAttribute(code: QNetworkRequest::HttpPipeliningAllowedAttribute, value: true); |
144 | |
145 | m_reply->deleteLater(); |
146 | m_reply = m_engine->networkAccessManager()->get(request: req); |
147 | |
148 | QMetaObject::connect(sender: m_reply, signal_index: replyFinishedIndex, |
149 | receiver: this, method_index: networkFinishedIndex); |
150 | QMetaObject::connect(sender: m_reply, signal_index: replyDownloadProgressIndex, |
151 | receiver: this, method_index: networkDownloadProgressIndex); |
152 | |
153 | return; |
154 | } |
155 | } |
156 | |
157 | if (m_reply->error()) { |
158 | m_p->errorString = m_reply->errorString(); |
159 | m_p->error = QQmlFilePrivate::Network; |
160 | } else { |
161 | m_p->data = m_reply->readAll(); |
162 | } |
163 | |
164 | m_reply->deleteLater(); |
165 | m_reply = nullptr; |
166 | |
167 | m_p->reply = nullptr; |
168 | emit finished(); |
169 | delete this; |
170 | } |
171 | |
172 | void QQmlFileNetworkReply::networkDownloadProgress(qint64 a, qint64 b) |
173 | { |
174 | emit downloadProgress(a, b); |
175 | } |
176 | #endif // qml_network |
177 | |
178 | QQmlFilePrivate::QQmlFilePrivate() |
179 | : error(None) |
180 | #if QT_CONFIG(qml_network) |
181 | , reply(nullptr) |
182 | #endif |
183 | { |
184 | } |
185 | |
186 | QQmlFile::QQmlFile() |
187 | : d(new QQmlFilePrivate) |
188 | { |
189 | } |
190 | |
191 | QQmlFile::QQmlFile(QQmlEngine *e, const QUrl &url) |
192 | : d(new QQmlFilePrivate) |
193 | { |
194 | load(e, url); |
195 | } |
196 | |
197 | QQmlFile::QQmlFile(QQmlEngine *e, const QString &url) |
198 | : QQmlFile(e, QUrl(url)) |
199 | { |
200 | } |
201 | |
202 | QQmlFile::~QQmlFile() |
203 | { |
204 | #if QT_CONFIG(qml_network) |
205 | delete d->reply; |
206 | #endif |
207 | delete d; |
208 | d = nullptr; |
209 | } |
210 | |
211 | bool QQmlFile::isNull() const |
212 | { |
213 | return status() == Null; |
214 | } |
215 | |
216 | bool QQmlFile::isReady() const |
217 | { |
218 | return status() == Ready; |
219 | } |
220 | |
221 | bool QQmlFile::isError() const |
222 | { |
223 | return status() == Error; |
224 | } |
225 | |
226 | bool QQmlFile::isLoading() const |
227 | { |
228 | return status() == Loading; |
229 | } |
230 | |
231 | QUrl QQmlFile::url() const |
232 | { |
233 | if (!d->urlString.isEmpty()) { |
234 | d->url = QUrl(d->urlString); |
235 | d->urlString = QString(); |
236 | } |
237 | return d->url; |
238 | } |
239 | |
240 | QQmlFile::Status QQmlFile::status() const |
241 | { |
242 | if (d->url.isEmpty() && d->urlString.isEmpty()) |
243 | return Null; |
244 | #if QT_CONFIG(qml_network) |
245 | else if (d->reply) |
246 | return Loading; |
247 | #endif |
248 | else if (d->error != QQmlFilePrivate::None) |
249 | return Error; |
250 | else |
251 | return Ready; |
252 | } |
253 | |
254 | QString QQmlFile::error() const |
255 | { |
256 | switch (d->error) { |
257 | default: |
258 | case QQmlFilePrivate::None: |
259 | return QString(); |
260 | case QQmlFilePrivate::NotFound: |
261 | return QLatin1String("File not found" ); |
262 | case QQmlFilePrivate::CaseMismatch: |
263 | return QLatin1String("File name case mismatch" ); |
264 | } |
265 | } |
266 | |
267 | qint64 QQmlFile::size() const |
268 | { |
269 | return d->data.size(); |
270 | } |
271 | |
272 | const char *QQmlFile::data() const |
273 | { |
274 | return d->data.constData(); |
275 | } |
276 | |
277 | QByteArray QQmlFile::dataByteArray() const |
278 | { |
279 | return d->data; |
280 | } |
281 | |
282 | void QQmlFile::load(QQmlEngine *engine, const QUrl &url) |
283 | { |
284 | Q_ASSERT(engine); |
285 | |
286 | clear(); |
287 | d->url = url; |
288 | |
289 | if (isLocalFile(url)) { |
290 | QString lf = urlToLocalFileOrQrc(url); |
291 | |
292 | if (!QQml_isFileCaseCorrect(fileName: lf)) { |
293 | d->error = QQmlFilePrivate::CaseMismatch; |
294 | return; |
295 | } |
296 | |
297 | QFile file(lf); |
298 | if (file.open(flags: QFile::ReadOnly)) { |
299 | d->data = file.readAll(); |
300 | } else { |
301 | d->error = QQmlFilePrivate::NotFound; |
302 | } |
303 | } else { |
304 | #if QT_CONFIG(qml_network) |
305 | d->reply = new QQmlFileNetworkReply(engine, d, url); |
306 | #else |
307 | d->error = QQmlFilePrivate::NotFound; |
308 | #endif |
309 | } |
310 | } |
311 | |
312 | void QQmlFile::load(QQmlEngine *engine, const QString &url) |
313 | { |
314 | Q_ASSERT(engine); |
315 | |
316 | clear(); |
317 | |
318 | d->urlString = url; |
319 | |
320 | if (isLocalFile(url)) { |
321 | QString lf = urlToLocalFileOrQrc(url); |
322 | |
323 | if (!QQml_isFileCaseCorrect(fileName: lf)) { |
324 | d->error = QQmlFilePrivate::CaseMismatch; |
325 | return; |
326 | } |
327 | |
328 | QFile file(lf); |
329 | if (file.open(flags: QFile::ReadOnly)) { |
330 | d->data = file.readAll(); |
331 | } else { |
332 | d->error = QQmlFilePrivate::NotFound; |
333 | } |
334 | } else { |
335 | #if QT_CONFIG(qml_network) |
336 | QUrl qurl(url); |
337 | d->url = qurl; |
338 | d->urlString = QString(); |
339 | d->reply = new QQmlFileNetworkReply(engine, d, qurl); |
340 | #else |
341 | d->error = QQmlFilePrivate::NotFound; |
342 | #endif |
343 | } |
344 | } |
345 | |
346 | void QQmlFile::clear() |
347 | { |
348 | d->url = QUrl(); |
349 | d->urlString = QString(); |
350 | d->data = QByteArray(); |
351 | d->error = QQmlFilePrivate::None; |
352 | } |
353 | |
354 | void QQmlFile::clear(QObject *) |
355 | { |
356 | clear(); |
357 | } |
358 | |
359 | #if QT_CONFIG(qml_network) |
360 | bool QQmlFile::connectFinished(QObject *object, const char *method) |
361 | { |
362 | if (!d || !d->reply) { |
363 | qWarning(msg: "QQmlFile: connectFinished() called when not loading." ); |
364 | return false; |
365 | } |
366 | |
367 | return QObject::connect(sender: d->reply, SIGNAL(finished()), |
368 | receiver: object, member: method); |
369 | } |
370 | |
371 | bool QQmlFile::connectFinished(QObject *object, int method) |
372 | { |
373 | if (!d || !d->reply) { |
374 | qWarning(msg: "QQmlFile: connectFinished() called when not loading." ); |
375 | return false; |
376 | } |
377 | |
378 | return QMetaObject::connect(sender: d->reply, signal_index: QQmlFileNetworkReply::finishedIndex, |
379 | receiver: object, method_index: method); |
380 | } |
381 | |
382 | bool QQmlFile::connectDownloadProgress(QObject *object, const char *method) |
383 | { |
384 | if (!d || !d->reply) { |
385 | qWarning(msg: "QQmlFile: connectDownloadProgress() called when not loading." ); |
386 | return false; |
387 | } |
388 | |
389 | return QObject::connect(sender: d->reply, SIGNAL(downloadProgress(qint64,qint64)), |
390 | receiver: object, member: method); |
391 | } |
392 | |
393 | bool QQmlFile::connectDownloadProgress(QObject *object, int method) |
394 | { |
395 | if (!d || !d->reply) { |
396 | qWarning(msg: "QQmlFile: connectDownloadProgress() called when not loading." ); |
397 | return false; |
398 | } |
399 | |
400 | return QMetaObject::connect(sender: d->reply, signal_index: QQmlFileNetworkReply::downloadProgressIndex, |
401 | receiver: object, method_index: method); |
402 | } |
403 | #endif |
404 | |
405 | /*! |
406 | Returns true if QQmlFile will open \a url synchronously. |
407 | |
408 | Synchronous urls have a qrc:/ or file:// scheme. |
409 | |
410 | \note On Android, urls with assets:/ scheme are also considered synchronous. |
411 | */ |
412 | bool QQmlFile::isSynchronous(const QUrl &url) |
413 | { |
414 | QString scheme = url.scheme(); |
415 | |
416 | if ((scheme.size() == 4 && 0 == scheme.compare(other: QLatin1String(file_string), cs: Qt::CaseInsensitive)) || |
417 | (scheme.size() == 3 && 0 == scheme.compare(other: QLatin1String(qrc_string), cs: Qt::CaseInsensitive))) { |
418 | return true; |
419 | |
420 | #if defined(Q_OS_ANDROID) |
421 | } else if (scheme.length() == 6 && 0 == scheme.compare(QLatin1String(assets_string), Qt::CaseInsensitive)) { |
422 | return true; |
423 | } else if (scheme.length() == 7 && 0 == scheme.compare(QLatin1String(content_string), Qt::CaseInsensitive)) { |
424 | return true; |
425 | #endif |
426 | |
427 | } else { |
428 | return false; |
429 | } |
430 | } |
431 | |
432 | /*! |
433 | Returns true if QQmlFile will open \a url synchronously. |
434 | |
435 | Synchronous urls have a qrc:/ or file:// scheme. |
436 | |
437 | \note On Android, urls with assets:/ scheme are also considered synchronous. |
438 | */ |
439 | bool QQmlFile::isSynchronous(const QString &url) |
440 | { |
441 | if (url.size() < 5 /* qrc:/ */) |
442 | return false; |
443 | |
444 | QChar f = url[0]; |
445 | |
446 | if (f == QLatin1Char('f') || f == QLatin1Char('F')) { |
447 | |
448 | return url.size() >= 7 /* file:// */ && |
449 | url.startsWith(s: QLatin1String(file_string), cs: Qt::CaseInsensitive) && |
450 | url[4] == QLatin1Char(':') && url[5] == QLatin1Char('/') && url[6] == QLatin1Char('/'); |
451 | |
452 | } else if (f == QLatin1Char('q') || f == QLatin1Char('Q')) { |
453 | |
454 | return url.size() >= 5 /* qrc:/ */ && |
455 | url.startsWith(s: QLatin1String(qrc_string), cs: Qt::CaseInsensitive) && |
456 | url[3] == QLatin1Char(':') && url[4] == QLatin1Char('/'); |
457 | |
458 | } |
459 | |
460 | #if defined(Q_OS_ANDROID) |
461 | else if (f == QLatin1Char('a') || f == QLatin1Char('A')) { |
462 | return url.length() >= 8 /* assets:/ */ && |
463 | url.startsWith(QLatin1String(assets_string), Qt::CaseInsensitive) && |
464 | url[6] == QLatin1Char(':') && url[7] == QLatin1Char('/'); |
465 | } else if (f == QLatin1Char('c') || f == QLatin1Char('C')) { |
466 | return url.length() >= 9 /* content:/ */ && |
467 | url.startsWith(QLatin1String(content_string), Qt::CaseInsensitive) && |
468 | url[7] == QLatin1Char(':') && url[8] == QLatin1Char('/'); |
469 | } |
470 | #endif |
471 | |
472 | return false; |
473 | } |
474 | |
475 | #if defined(Q_OS_ANDROID) |
476 | static bool hasLocalContentAuthority(const QUrl &url) |
477 | { |
478 | const QString authority = url.authority(); |
479 | return authority.isEmpty() |
480 | || authority == QLatin1String(authority_externalstorage) |
481 | || authority == QLatin1String(authority_downloads_documents) |
482 | || authority == QLatin1String(authority_media_documents); |
483 | } |
484 | #endif |
485 | |
486 | /*! |
487 | Returns true if \a url is a local file that can be opened with QFile. |
488 | |
489 | Local file urls have either a qrc:/ or file:// scheme. |
490 | |
491 | \note On Android, urls with assets:/ scheme are also considered local files. |
492 | */ |
493 | bool QQmlFile::isLocalFile(const QUrl &url) |
494 | { |
495 | QString scheme = url.scheme(); |
496 | |
497 | // file: URLs with two slashes following the scheme can be interpreted as local files |
498 | // where the slashes are part of the path. Therefore, disregard the authority. |
499 | // See QUrl::toLocalFile(). |
500 | if (scheme.size() == 4 && scheme.startsWith(s: QLatin1String(file_string), cs: Qt::CaseInsensitive)) |
501 | return true; |
502 | |
503 | if (scheme.size() == 3 && scheme.startsWith(s: QLatin1String(qrc_string), cs: Qt::CaseInsensitive)) |
504 | return url.authority().isEmpty(); |
505 | |
506 | #if defined(Q_OS_ANDROID) |
507 | if (scheme.length() == 6 |
508 | && scheme.startsWith(QLatin1String(assets_string), Qt::CaseInsensitive)) |
509 | return url.authority().isEmpty(); |
510 | if (scheme.length() == 7 |
511 | && scheme.startsWith(QLatin1String(content_string), Qt::CaseInsensitive)) |
512 | return hasLocalContentAuthority(url); |
513 | #endif |
514 | |
515 | return false; |
516 | } |
517 | |
518 | static bool hasScheme(const QString &url, const char *scheme, qsizetype schemeLength) |
519 | { |
520 | const qsizetype urlLength = url.size(); |
521 | |
522 | if (urlLength < schemeLength + 1) |
523 | return false; |
524 | |
525 | if (!url.startsWith(s: QLatin1String(scheme, scheme + schemeLength), cs: Qt::CaseInsensitive)) |
526 | return false; |
527 | |
528 | if (url[schemeLength] != QLatin1Char(':')) |
529 | return false; |
530 | |
531 | return true; |
532 | } |
533 | |
534 | static qsizetype authorityOffset(const QString &url, qsizetype schemeLength) |
535 | { |
536 | const qsizetype urlLength = url.size(); |
537 | |
538 | if (urlLength < schemeLength + 3) |
539 | return -1; |
540 | |
541 | const QLatin1Char slash('/'); |
542 | if (url[schemeLength + 1] == slash && url[schemeLength + 2] == slash) { |
543 | // Exactly two slashes denote an authority. |
544 | if (urlLength < schemeLength + 4 || url[schemeLength + 3] != slash) |
545 | return schemeLength + 3; |
546 | } |
547 | |
548 | return -1; |
549 | } |
550 | |
551 | #if defined(Q_OS_ANDROID) |
552 | static bool hasLocalContentAuthority(const QString &url, qsizetype schemeLength) |
553 | { |
554 | const qsizetype offset = authorityOffset(url, schemeLength); |
555 | if (offset == -1) |
556 | return true; // no authority is a local authority. |
557 | |
558 | const QString authorityAndPath = url.sliced(offset); |
559 | return authorityAndPath.startsWith(QLatin1String(authority_externalstorage)) |
560 | || authorityAndPath.startsWith(QLatin1String(authority_downloads_documents)) |
561 | || authorityAndPath.startsWith(QLatin1String(authority_media_documents)); |
562 | } |
563 | |
564 | #endif |
565 | |
566 | /*! |
567 | Returns true if \a url is a local file that can be opened with QFile. |
568 | |
569 | Local file urls have either a qrc: or file: scheme. |
570 | |
571 | \note On Android, urls with assets: or content: scheme are also considered local files. |
572 | */ |
573 | bool QQmlFile::isLocalFile(const QString &url) |
574 | { |
575 | if (url.size() < 4 /* qrc: */) |
576 | return false; |
577 | |
578 | switch (url[0].toLatin1()) { |
579 | case 'f': |
580 | case 'F': { |
581 | // file: URLs with two slashes following the scheme can be interpreted as local files |
582 | // where the slashes are part of the path. Therefore, disregard the authority. |
583 | // See QUrl::toLocalFile(). |
584 | const qsizetype fileLength = strlen(s: file_string); |
585 | return url.startsWith(s: QLatin1String(file_string, file_string + fileLength), |
586 | cs: Qt::CaseInsensitive) |
587 | && url.size() > fileLength |
588 | && url[fileLength] == QLatin1Char(':'); |
589 | } |
590 | case 'q': |
591 | case 'Q': |
592 | return hasScheme(url, scheme: qrc_string, schemeLength: strlen(s: qrc_string)) |
593 | && authorityOffset(url, schemeLength: strlen(s: qrc_string)) == -1; |
594 | #if defined(Q_OS_ANDROID) |
595 | case 'a': |
596 | case 'A': |
597 | return hasScheme(url, assets_string, strlen(assets_string)) |
598 | && authorityOffset(url, strlen(assets_string)) == -1; |
599 | case 'c': |
600 | case 'C': |
601 | return hasScheme(url, content_string, strlen(content_string)) |
602 | && hasLocalContentAuthority(url, strlen(content_string)); |
603 | #endif |
604 | default: |
605 | break; |
606 | } |
607 | |
608 | return false; |
609 | } |
610 | |
611 | /*! |
612 | If \a url is a local file returns a path suitable for passing to QFile. Otherwise returns an |
613 | empty string. |
614 | */ |
615 | QString QQmlFile::urlToLocalFileOrQrc(const QUrl& url) |
616 | { |
617 | if (url.scheme().compare(other: QLatin1String("qrc" ), cs: Qt::CaseInsensitive) == 0) { |
618 | if (url.authority().isEmpty()) |
619 | return QLatin1Char(':') + url.path(); |
620 | return QString(); |
621 | } |
622 | |
623 | #if defined(Q_OS_ANDROID) |
624 | if (url.scheme().compare(QLatin1String("assets" ), Qt::CaseInsensitive) == 0) |
625 | return url.authority().isEmpty() ? url.toString() : QString(); |
626 | if (url.scheme().compare(QLatin1String("content" ), Qt::CaseInsensitive) == 0) { |
627 | if (hasLocalContentAuthority(url)) |
628 | return url.toString(); |
629 | return QString(); |
630 | } |
631 | #endif |
632 | return url.toLocalFile(); |
633 | } |
634 | |
635 | static QString toLocalFile(const QString &url) |
636 | { |
637 | const QUrl file(url); |
638 | if (!file.isLocalFile()) |
639 | return QString(); |
640 | |
641 | // QUrl::toLocalFile() interprets two slashes as part of the path. |
642 | // Therefore windows hostnames like "//servername/path/to/file.txt" are preserved. |
643 | |
644 | return file.toLocalFile(); |
645 | } |
646 | |
647 | static bool isDoubleSlashed(const QString &url, qsizetype offset) |
648 | { |
649 | const qsizetype urlLength = url.size(); |
650 | if (urlLength < offset + 2) |
651 | return false; |
652 | |
653 | const QLatin1Char slash('/'); |
654 | if (url[offset] != slash || url[offset + 1] != slash) |
655 | return false; |
656 | |
657 | if (urlLength < offset + 3) |
658 | return true; |
659 | |
660 | return url[offset + 2] != slash; |
661 | } |
662 | |
663 | /*! |
664 | If \a url is a local file returns a path suitable for passing to QFile. Otherwise returns an |
665 | empty string. |
666 | */ |
667 | QString QQmlFile::urlToLocalFileOrQrc(const QString& url) |
668 | { |
669 | if (url.startsWith(s: QLatin1String("qrc://" ), cs: Qt::CaseInsensitive)) { |
670 | // Exactly two slashes are bad because that's a URL authority. |
671 | // One slash is fine and >= 3 slashes are file. |
672 | if (url.size() == 6 || url[6] != QLatin1Char('/')) { |
673 | Q_ASSERT(isDoubleSlashed(url, strlen("qrc:" ))); |
674 | return QString(); |
675 | } |
676 | Q_ASSERT(!isDoubleSlashed(url, strlen("qrc:" ))); |
677 | return QLatin1Char(':') + QStringView{url}.mid(pos: 6); |
678 | } |
679 | |
680 | if (url.startsWith(s: QLatin1String("qrc:" ), cs: Qt::CaseInsensitive)) { |
681 | Q_ASSERT(!isDoubleSlashed(url, strlen("qrc:" ))); |
682 | if (url.size() > 4) |
683 | return QLatin1Char(':') + QStringView{url}.mid(pos: 4); |
684 | return QStringLiteral(":" ); |
685 | } |
686 | |
687 | #if defined(Q_OS_ANDROID) |
688 | if (url.startsWith(QLatin1String("assets:" ), Qt::CaseInsensitive)) |
689 | return isDoubleSlashed(url, strlen("assets:" )) ? QString() : url; |
690 | if (hasScheme(url, content_string, strlen(content_string))) |
691 | return hasLocalContentAuthority(url, strlen(content_string)) ? url : QString(); |
692 | #endif |
693 | |
694 | return toLocalFile(url); |
695 | } |
696 | |
697 | QT_END_NAMESPACE |
698 | |
699 | #include "qqmlfile.moc" |
700 | |