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