1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtDeclarative 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 http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://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 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "qdeclarativeimport_p.h"
43 
44 #include <QtCore/qdebug.h>
45 #include <QtCore/qdir.h>
46 #include <QtCore/qfileinfo.h>
47 #include <QtCore/qpluginloader.h>
48 #include <QtCore/qlibraryinfo.h>
49 #include <QtCore/qalgorithms.h>
50 #include <QtDeclarative/qdeclarativeextensioninterface.h>
51 #include <private/qdeclarativeglobal_p.h>
52 #include <private/qdeclarativetypenamecache_p.h>
53 #include <private/qdeclarativeengine_p.h>
54 
55 #ifdef Q_OS_SYMBIAN
56 #include "private/qcore_symbian_p.h"
57 #endif
58 
59 QT_BEGIN_NAMESPACE
60 
DEFINE_BOOL_CONFIG_OPTION(qmlImportTrace,QML_IMPORT_TRACE)61 DEFINE_BOOL_CONFIG_OPTION(qmlImportTrace, QML_IMPORT_TRACE)
62 DEFINE_BOOL_CONFIG_OPTION(qmlCheckTypes, QML_CHECK_TYPES)
63 
64 static bool greaterThan(const QString &s1, const QString &s2)
65 {
66     return s1 > s2;
67 }
68 
69 typedef QMap<QString, QString> StringStringMap;
70 Q_GLOBAL_STATIC(StringStringMap, qmlEnginePluginsWithRegisteredTypes); // stores the uri
71 
72 class QDeclarativeImportedNamespace
73 {
74 public:
75     QStringList uris;
76     QStringList urls;
77     QList<int> majversions;
78     QList<int> minversions;
79     QList<bool> isLibrary;
80     QList<QDeclarativeDirComponents> qmlDirComponents;
81 
82 
83     bool find_helper(QDeclarativeTypeLoader *typeLoader, int i, const QByteArray& type, int *vmajor, int *vminor,
84                                  QDeclarativeType** type_return, QUrl* url_return,
85                                  QUrl *base = 0, bool *typeRecursionDetected = 0);
86     bool find(QDeclarativeTypeLoader *typeLoader, const QByteArray& type, int *vmajor, int *vminor, QDeclarativeType** type_return,
87               QUrl* url_return, QUrl *base = 0, QString *errorString = 0);
88 };
89 
90 class QDeclarativeImportsPrivate {
91 public:
92     QDeclarativeImportsPrivate(QDeclarativeTypeLoader *loader);
93     ~QDeclarativeImportsPrivate();
94 
95     bool importExtension(const QString &absoluteFilePath, const QString &uri,
96                          QDeclarativeImportDatabase *database, QDeclarativeDirComponents* components,
97                          QString *errorString);
98 
99     QString resolvedUri(const QString &dir_arg, QDeclarativeImportDatabase *database);
100     bool add(const QDeclarativeDirComponents &qmldircomponentsnetwork,
101              const QString& uri_arg, const QString& prefix,
102              int vmaj, int vmin, QDeclarativeScriptParser::Import::Type importType,
103              QDeclarativeImportDatabase *database, QString *errorString);
104     bool find(const QByteArray& type, int *vmajor, int *vminor,
105               QDeclarativeType** type_return, QUrl* url_return, QString *errorString);
106 
107     QDeclarativeImportedNamespace *findNamespace(const QString& type);
108 
109     QUrl base;
110     int ref;
111 
112     QSet<QString> qmlDirFilesForWhichPluginsHaveBeenLoaded;
113     QDeclarativeImportedNamespace unqualifiedset;
114     QHash<QString,QDeclarativeImportedNamespace* > set;
115     QDeclarativeTypeLoader *typeLoader;
116 };
117 
118 /*!
119 \class QDeclarativeImports
120 \brief The QDeclarativeImports class encapsulates one QML document's import statements.
121 \internal
122 */
QDeclarativeImports(const QDeclarativeImports & copy)123 QDeclarativeImports::QDeclarativeImports(const QDeclarativeImports &copy)
124 : d(copy.d)
125 {
126     ++d->ref;
127 }
128 
129 QDeclarativeImports &
operator =(const QDeclarativeImports & copy)130 QDeclarativeImports::operator =(const QDeclarativeImports &copy)
131 {
132     ++copy.d->ref;
133     if (--d->ref == 0)
134         delete d;
135     d = copy.d;
136     return *this;
137 }
138 
QDeclarativeImports()139 QDeclarativeImports::QDeclarativeImports()
140     : d(new QDeclarativeImportsPrivate(0)){
141 }
142 
QDeclarativeImports(QDeclarativeTypeLoader * typeLoader)143 QDeclarativeImports::QDeclarativeImports(QDeclarativeTypeLoader *typeLoader)
144     : d(new QDeclarativeImportsPrivate(typeLoader)){
145 }
146 
~QDeclarativeImports()147 QDeclarativeImports::~QDeclarativeImports()
148 {
149     if (--d->ref == 0)
150         delete d;
151 }
152 
153 /*!
154   Sets the base URL to be used for all relative file imports added.
155 */
setBaseUrl(const QUrl & url)156 void QDeclarativeImports::setBaseUrl(const QUrl& url)
157 {
158     d->base = url;
159 }
160 
161 /*!
162   Returns the base URL to be used for all relative file imports added.
163 */
baseUrl() const164 QUrl QDeclarativeImports::baseUrl() const
165 {
166     return d->base;
167 }
168 
169 static QDeclarativeTypeNameCache *
cacheForNamespace(QDeclarativeEngine * engine,const QDeclarativeImportedNamespace & set,QDeclarativeTypeNameCache * cache)170 cacheForNamespace(QDeclarativeEngine *engine, const QDeclarativeImportedNamespace &set,
171                   QDeclarativeTypeNameCache *cache)
172 {
173     if (!cache)
174         cache = new QDeclarativeTypeNameCache(engine);
175 
176     QList<QDeclarativeType *> types = QDeclarativeMetaType::qmlTypes();
177 
178     for (int ii = 0; ii < set.uris.count(); ++ii) {
179         QByteArray base = set.uris.at(ii).toUtf8() + '/';
180         int major = set.majversions.at(ii);
181         int minor = set.minversions.at(ii);
182 
183         foreach (QDeclarativeType *type, types) {
184             if (type->qmlTypeName().startsWith(base) &&
185                 type->qmlTypeName().lastIndexOf('/') == (base.length() - 1) &&
186                 type->availableInVersion(major,minor))
187             {
188                 QString name = QString::fromUtf8(type->qmlTypeName().mid(base.length()));
189 
190                 cache->add(name, type);
191             }
192         }
193     }
194 
195     return cache;
196 }
197 
populateCache(QDeclarativeTypeNameCache * cache,QDeclarativeEngine * engine) const198 void QDeclarativeImports::populateCache(QDeclarativeTypeNameCache *cache, QDeclarativeEngine *engine) const
199 {
200     const QDeclarativeImportedNamespace &set = d->unqualifiedset;
201 
202     for (QHash<QString,QDeclarativeImportedNamespace* >::ConstIterator iter = d->set.begin();
203          iter != d->set.end(); ++iter) {
204 
205         QDeclarativeTypeNameCache::Data *d = cache->data(iter.key());
206         if (d) {
207             if (!d->typeNamespace)
208                 cacheForNamespace(engine, *(*iter), d->typeNamespace);
209         } else {
210             QDeclarativeTypeNameCache *nc = cacheForNamespace(engine, *(*iter), 0);
211             cache->add(iter.key(), nc);
212             nc->release();
213         }
214     }
215 
216     cacheForNamespace(engine, set, cache);
217 }
218 
219 /*!
220   \internal
221 
222   The given (namespace qualified) \a type is resolved to either
223   \list
224   \o a QDeclarativeImportedNamespace stored at \a ns_return,
225   \o a QDeclarativeType stored at \a type_return, or
226   \o a component located at \a url_return.
227   \endlist
228 
229   If any return pointer is 0, the corresponding search is not done.
230 
231   \sa addImport()
232 */
resolveType(const QByteArray & type,QDeclarativeType ** type_return,QUrl * url_return,int * vmaj,int * vmin,QDeclarativeImportedNamespace ** ns_return,QString * errorString) const233 bool QDeclarativeImports::resolveType(const QByteArray& type,
234                                       QDeclarativeType** type_return, QUrl* url_return, int *vmaj, int *vmin,
235                                       QDeclarativeImportedNamespace** ns_return, QString *errorString) const
236 {
237     QDeclarativeImportedNamespace* ns = d->findNamespace(QString::fromUtf8(type));
238     if (ns) {
239         if (ns_return)
240             *ns_return = ns;
241         return true;
242     }
243     if (type_return || url_return) {
244         if (d->find(type,vmaj,vmin,type_return,url_return, errorString)) {
245             if (qmlImportTrace()) {
246                 if (type_return && *type_return && url_return && !url_return->isEmpty())
247                     qDebug().nospace() << "QDeclarativeImports(" << qPrintable(baseUrl().toString()) << ")" << "::resolveType: "
248                                        << type << " => " << (*type_return)->typeName() << " " << *url_return;
249                 if (type_return && *type_return)
250                     qDebug().nospace() << "QDeclarativeImports(" << qPrintable(baseUrl().toString()) << ")" << "::resolveType: "
251                                        << type << " => " << (*type_return)->typeName();
252                 if (url_return && !url_return->isEmpty())
253                     qDebug().nospace() << "QDeclarativeImports(" << qPrintable(baseUrl().toString()) << ")" << "::resolveType: "
254                                        << type << " => " << *url_return;
255             }
256             return true;
257         }
258     }
259     return false;
260 }
261 
262 /*!
263   \internal
264 
265   Searching \e only in the namespace \a ns (previously returned in a call to
266   resolveType(), \a type is found and returned to either
267   a QDeclarativeType stored at \a type_return, or
268   a component located at \a url_return.
269 
270   If either return pointer is 0, the corresponding search is not done.
271 */
resolveType(QDeclarativeImportedNamespace * ns,const QByteArray & type,QDeclarativeType ** type_return,QUrl * url_return,int * vmaj,int * vmin) const272 bool QDeclarativeImports::resolveType(QDeclarativeImportedNamespace* ns, const QByteArray& type,
273                                       QDeclarativeType** type_return, QUrl* url_return,
274                                       int *vmaj, int *vmin) const
275 {
276     Q_ASSERT(d->typeLoader);
277     return ns->find(d->typeLoader,type,vmaj,vmin,type_return,url_return);
278 }
279 
find_helper(QDeclarativeTypeLoader * typeLoader,int i,const QByteArray & type,int * vmajor,int * vminor,QDeclarativeType ** type_return,QUrl * url_return,QUrl * base,bool * typeRecursionDetected)280 bool QDeclarativeImportedNamespace::find_helper(QDeclarativeTypeLoader *typeLoader, int i, const QByteArray& type, int *vmajor, int *vminor,
281                                  QDeclarativeType** type_return, QUrl* url_return,
282                                  QUrl *base, bool *typeRecursionDetected)
283 {
284     int vmaj = majversions.at(i);
285     int vmin = minversions.at(i);
286 
287     QByteArray qt = uris.at(i).toUtf8();
288     qt += '/';
289     qt += type;
290 
291     QDeclarativeType *t = QDeclarativeMetaType::qmlType(qt,vmaj,vmin);
292     if (t) {
293         if (vmajor) *vmajor = vmaj;
294         if (vminor) *vminor = vmin;
295         if (type_return)
296             *type_return = t;
297         return true;
298     }
299 
300     QDeclarativeDirComponents qmldircomponents = qmlDirComponents.at(i);
301 
302     bool typeWasDeclaredInQmldir = false;
303     if (!qmldircomponents.isEmpty()) {
304         const QString typeName = QString::fromUtf8(type);
305         foreach (const QDeclarativeDirParser::Component &c, qmldircomponents) {
306             if (c.typeName == typeName) {
307                 typeWasDeclaredInQmldir = true;
308 
309                 // importing version -1 means import ALL versions
310                 if ((vmaj == -1) || (c.majorVersion < vmaj || (c.majorVersion == vmaj && vmin >= c.minorVersion))) {
311                     QUrl url = QUrl(urls.at(i) + QLatin1Char('/') + QString::fromUtf8(type) + QLatin1String(".qml"));
312                     QUrl candidate = url.resolved(QUrl(c.fileName));
313                     if (c.internal && base) {
314                         if (base->resolved(QUrl(c.fileName)) != candidate)
315                             continue; // failed attempt to access an internal type
316                     }
317                     if (base && *base == candidate) {
318                         if (typeRecursionDetected)
319                             *typeRecursionDetected = true;
320                         continue; // no recursion
321                     }
322                     if (url_return)
323                         *url_return = candidate;
324                     return true;
325                 }
326             }
327         }
328     }
329 
330     if (!typeWasDeclaredInQmldir  && !isLibrary.at(i)) {
331         // XXX search non-files too! (eg. zip files, see QT-524)
332         QUrl url = QUrl(urls.at(i) + QLatin1Char('/') + QString::fromUtf8(type) + QLatin1String(".qml"));
333         QString file = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(url);
334         if (!typeLoader->absoluteFilePath(file).isEmpty()) {
335             if (base && *base == url) { // no recursion
336                 if (typeRecursionDetected)
337                     *typeRecursionDetected = true;
338             } else {
339                 if (url_return)
340                     *url_return = url;
341                 return true;
342             }
343         }
344     }
345     return false;
346 }
347 
QDeclarativeImportsPrivate(QDeclarativeTypeLoader * loader)348 QDeclarativeImportsPrivate::QDeclarativeImportsPrivate(QDeclarativeTypeLoader *loader)
349     : ref(1), typeLoader(loader){
350 }
351 
~QDeclarativeImportsPrivate()352 QDeclarativeImportsPrivate::~QDeclarativeImportsPrivate()
353 {
354     foreach (QDeclarativeImportedNamespace* s, set.values())
355         delete s;
356 }
357 
importExtension(const QString & absoluteFilePath,const QString & uri,QDeclarativeImportDatabase * database,QDeclarativeDirComponents * components,QString * errorString)358 bool QDeclarativeImportsPrivate::importExtension(const QString &absoluteFilePath, const QString &uri,
359                                                  QDeclarativeImportDatabase *database,
360                                                  QDeclarativeDirComponents* components, QString *errorString)
361 {
362     Q_ASSERT(typeLoader);
363     const QDeclarativeDirParser *qmldirParser = typeLoader->qmlDirParser(absoluteFilePath);
364     if (qmldirParser->hasError()) {
365         if (errorString) {
366             const QList<QDeclarativeError> qmldirErrors = qmldirParser->errors(uri);
367             for (int i = 0; i < qmldirErrors.size(); ++i)
368                 *errorString += qmldirErrors.at(i).description();
369         }
370         return false;
371     }
372 
373     if (! qmlDirFilesForWhichPluginsHaveBeenLoaded.contains(absoluteFilePath)) {
374         qmlDirFilesForWhichPluginsHaveBeenLoaded.insert(absoluteFilePath);
375 
376         QDir dir = QFileInfo(absoluteFilePath).dir();
377         foreach (const QDeclarativeDirParser::Plugin &plugin, qmldirParser->plugins()) {
378 
379             QString resolvedFilePath = database->resolvePlugin(dir, plugin.path, plugin.name);
380 #if defined(QT_LIBINFIX) && defined(Q_OS_SYMBIAN)
381             if (resolvedFilePath.isEmpty()) {
382                 // In case of libinfixed build, attempt to load libinfixed version, too.
383                 QString infixedPluginName = plugin.name + QLatin1String(QT_LIBINFIX);
384                 resolvedFilePath = database->resolvePlugin(dir, plugin.path, infixedPluginName);
385             }
386 #endif
387             if (!resolvedFilePath.isEmpty()) {
388                 if (!database->importPlugin(resolvedFilePath, uri, errorString)) {
389                     if (errorString)
390                         *errorString = QDeclarativeImportDatabase::tr("plugin cannot be loaded for module \"%1\": %2").arg(uri).arg(*errorString);
391                     return false;
392                 }
393             } else {
394                 if (errorString)
395                     *errorString = QDeclarativeImportDatabase::tr("module \"%1\" plugin \"%2\" not found").arg(uri).arg(plugin.name);
396                 return false;
397             }
398         }
399     }
400 
401     if (components)
402         *components = qmldirParser->components();
403 
404     return true;
405 }
406 
resolvedUri(const QString & dir_arg,QDeclarativeImportDatabase * database)407 QString QDeclarativeImportsPrivate::resolvedUri(const QString &dir_arg, QDeclarativeImportDatabase *database)
408 {
409     QString dir = dir_arg;
410     if (dir.endsWith(QLatin1Char('/')) || dir.endsWith(QLatin1Char('\\')))
411         dir.chop(1);
412 
413     QStringList paths = database->fileImportPath;
414     qSort(paths.begin(), paths.end(), greaterThan); // Ensure subdirs preceed their parents.
415 
416     QString stableRelativePath = dir;
417     foreach(const QString &path, paths) {
418         if (dir.startsWith(path)) {
419             stableRelativePath = dir.mid(path.length()+1);
420             break;
421         }
422     }
423 
424     stableRelativePath.replace(QLatin1Char('\\'), QLatin1Char('/'));
425 
426     // remove optional versioning in dot notation from uri
427     int lastSlash = stableRelativePath.lastIndexOf(QLatin1Char('/'));
428     if (lastSlash >= 0) {
429         int versionDot = stableRelativePath.indexOf(QLatin1Char('.'), lastSlash);
430         if (versionDot >= 0)
431             stableRelativePath = stableRelativePath.left(versionDot);
432     }
433 
434     stableRelativePath.replace(QLatin1Char('/'), QLatin1Char('.'));
435     return stableRelativePath;
436 }
437 
add(const QDeclarativeDirComponents & qmldircomponentsnetwork,const QString & uri_arg,const QString & prefix,int vmaj,int vmin,QDeclarativeScriptParser::Import::Type importType,QDeclarativeImportDatabase * database,QString * errorString)438 bool QDeclarativeImportsPrivate::add(const QDeclarativeDirComponents &qmldircomponentsnetwork,
439                                      const QString& uri_arg, const QString& prefix, int vmaj, int vmin,
440                                      QDeclarativeScriptParser::Import::Type importType,
441                                      QDeclarativeImportDatabase *database, QString *errorString)
442 {
443     Q_ASSERT(typeLoader);
444     QDeclarativeDirComponents qmldircomponents = qmldircomponentsnetwork;
445     QString uri = uri_arg;
446     QDeclarativeImportedNamespace *s;
447     if (prefix.isEmpty()) {
448         s = &unqualifiedset;
449     } else {
450         s = set.value(prefix);
451         if (!s)
452             set.insert(prefix,(s=new QDeclarativeImportedNamespace));
453     }
454 
455     bool appendInstead = false;
456     if (importType == QDeclarativeScriptParser::Import::Implicit) {
457         //Treat same as a File import, but lower precedence
458         appendInstead = true;
459         importType = QDeclarativeScriptParser::Import::File;
460     }
461 
462     QString url = uri;
463     bool versionFound = false;
464     if (importType == QDeclarativeScriptParser::Import::Library) {
465         url.replace(QLatin1Char('.'), QLatin1Char('/'));
466         bool found = false;
467         QString dir;
468         QString qmldir;
469 
470         // step 1: search for extension with fully encoded version number
471         if (vmaj >= 0 && vmin >= 0) {
472             foreach (const QString &p, database->fileImportPath) {
473                 dir = p+QLatin1Char('/')+url;
474                 qmldir = dir+QString(QLatin1String(".%1.%2")).arg(vmaj).arg(vmin)+QLatin1String("/qmldir");
475 
476                 QString absoluteFilePath = typeLoader->absoluteFilePath(qmldir);
477                 if (!absoluteFilePath.isEmpty()) {
478                     found = true;
479 
480                     QString absolutePath = absoluteFilePath.left(absoluteFilePath.lastIndexOf(QLatin1Char('/')));
481                     url = QUrl::fromLocalFile(absolutePath).toString();
482                     uri = resolvedUri(dir, database);
483                     if (!importExtension(absoluteFilePath, uri, database, &qmldircomponents, errorString))
484                         return false;
485                     break;
486                 }
487             }
488         }
489         // step 2: search for extension with encoded version major
490         if (vmaj >= 0 && vmin >= 0) {
491             foreach (const QString &p, database->fileImportPath) {
492                 dir = p+QLatin1Char('/')+url;
493                 qmldir = dir+QString(QLatin1String(".%1")).arg(vmaj)+QLatin1String("/qmldir");
494 
495                 QString absoluteFilePath = typeLoader->absoluteFilePath(qmldir);
496                 if (!absoluteFilePath.isEmpty()) {
497                     found = true;
498 
499                     QString absolutePath = absoluteFilePath.left(absoluteFilePath.lastIndexOf(QLatin1Char('/')));
500                     url = QUrl::fromLocalFile(absolutePath).toString();
501                     uri = resolvedUri(dir, database);
502                     if (!importExtension(absoluteFilePath, uri, database, &qmldircomponents, errorString))
503                         return false;
504                     break;
505                 }
506             }
507         }
508         if (!found) {
509             // step 3: search for extension without version number
510 
511             foreach (const QString &p, database->fileImportPath) {
512                 dir = p+QLatin1Char('/')+url;
513                 qmldir = dir+QLatin1String("/qmldir");
514 
515                 QString absoluteFilePath = typeLoader->absoluteFilePath(qmldir);
516                 if (!absoluteFilePath.isEmpty()) {
517                     found = true;
518 
519                     QString absolutePath = absoluteFilePath.left(absoluteFilePath.lastIndexOf(QLatin1Char('/')));
520                     url = QUrl::fromLocalFile(absolutePath).toString();
521                     uri = resolvedUri(dir, database);
522                     if (!importExtension(absoluteFilePath, uri, database, &qmldircomponents, errorString))
523                         return false;
524                     break;
525                 }
526             }
527         }
528 
529         if (QDeclarativeMetaType::isModule(uri.toUtf8(), vmaj, vmin))
530             versionFound = true;
531 
532         //Load any type->file mappings registered for this uri
533         qmldircomponents << QDeclarativeMetaType::qmlComponents(uri.toUtf8(), vmaj, vmin);
534 
535         if (!versionFound && qmldircomponents.isEmpty()) {
536             if (errorString) {
537                 bool anyversion = QDeclarativeMetaType::isModule(uri.toUtf8(), -1, -1);
538                 if (anyversion)
539                     *errorString = QDeclarativeImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri_arg).arg(vmaj).arg(vmin);
540                 else
541                     *errorString = QDeclarativeImportDatabase::tr("module \"%1\" is not installed").arg(uri_arg);
542             }
543             return false;
544         }
545     } else {
546 
547         if (importType == QDeclarativeScriptParser::Import::File && qmldircomponents.isEmpty()) {
548             QUrl importUrl = base.resolved(QUrl(uri + QLatin1String("/qmldir")));
549             QString localFileOrQrc = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(importUrl);
550             if (!localFileOrQrc.isEmpty()) {
551                 QString dir = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(base.resolved(QUrl(uri)));
552                 QFileInfo dirinfo(dir);
553                 if (dir.isEmpty() || !dirinfo.exists() || !dirinfo.isDir()) {
554                     if (errorString)
555                         *errorString = QDeclarativeImportDatabase::tr("\"%1\": no such directory").arg(uri_arg);
556                     return false; // local import dirs must exist
557                 }
558                 uri = resolvedUri(QDeclarativeEnginePrivate::urlToLocalFileOrQrc(base.resolved(QUrl(uri))), database);
559                 if (uri.endsWith(QLatin1Char('/')))
560                     uri.chop(1);
561                 if (!typeLoader->absoluteFilePath(localFileOrQrc).isEmpty()) {
562                     if (!importExtension(localFileOrQrc,uri,database,&qmldircomponents,errorString))
563                         return false;
564                 }
565             } else {
566                 if (prefix.isEmpty()) {
567                     // directory must at least exist for valid import
568                     QString localFileOrQrc = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(base.resolved(QUrl(uri)));
569                     QFileInfo dirinfo(localFileOrQrc);
570                     if (localFileOrQrc.isEmpty() || !dirinfo.exists() || !dirinfo.isDir()) {
571                         if (errorString) {
572                             if (localFileOrQrc.isEmpty())
573                                 *errorString = QDeclarativeImportDatabase::tr("import \"%1\" has no qmldir and no namespace").arg(uri);
574                             else
575                                 *errorString = QDeclarativeImportDatabase::tr("\"%1\": no such directory").arg(uri);
576                         }
577                         return false;
578                     }
579                 }
580             }
581         }
582 
583         url = base.resolved(QUrl(url)).toString();
584         if (url.endsWith(QLatin1Char('/')))
585             url.chop(1);
586     }
587 
588     if (!versionFound && vmaj > -1 && vmin > -1 && !qmldircomponents.isEmpty()) {
589         QList<QDeclarativeDirParser::Component>::ConstIterator it = qmldircomponents.begin();
590         int lowest_maj = INT_MAX;
591         int lowest_min = INT_MAX;
592         int highest_maj = INT_MIN;
593         int highest_min = INT_MIN;
594         for (; it != qmldircomponents.end(); ++it) {
595             if (it->majorVersion > highest_maj || (it->majorVersion == highest_maj && it->minorVersion > highest_min)) {
596                 highest_maj = it->majorVersion;
597                 highest_min = it->minorVersion;
598             }
599             if (it->majorVersion < lowest_maj || (it->majorVersion == lowest_maj && it->minorVersion < lowest_min)) {
600                 lowest_maj = it->majorVersion;
601                 lowest_min = it->minorVersion;
602             }
603         }
604         if (lowest_maj > vmaj || (lowest_maj == vmaj && lowest_min > vmin)
605             || highest_maj < vmaj || (highest_maj == vmaj && highest_min < vmin))
606         {
607             *errorString = QDeclarativeImportDatabase::tr("module \"%1\" version %2.%3 is not installed").arg(uri_arg).arg(vmaj).arg(vmin);
608             return false;
609         }
610     }
611 
612     if (appendInstead) {
613         s->uris.append(uri);
614         s->urls.append(url);
615         s->majversions.append(vmaj);
616         s->minversions.append(vmin);
617         s->isLibrary.append(importType == QDeclarativeScriptParser::Import::Library);
618         s->qmlDirComponents.append(qmldircomponents);
619     } else {
620         s->uris.prepend(uri);
621         s->urls.prepend(url);
622         s->majversions.prepend(vmaj);
623         s->minversions.prepend(vmin);
624         s->isLibrary.prepend(importType == QDeclarativeScriptParser::Import::Library);
625         s->qmlDirComponents.prepend(qmldircomponents);
626     }
627     return true;
628 }
629 
find(const QByteArray & type,int * vmajor,int * vminor,QDeclarativeType ** type_return,QUrl * url_return,QString * errorString)630 bool QDeclarativeImportsPrivate::find(const QByteArray& type, int *vmajor, int *vminor, QDeclarativeType** type_return,
631                                       QUrl* url_return, QString *errorString)
632 {
633     Q_ASSERT(typeLoader);
634     QDeclarativeImportedNamespace *s = 0;
635     int slash = type.indexOf('/');
636     if (slash >= 0) {
637         QString namespaceName = QString::fromUtf8(type.left(slash));
638         s = set.value(namespaceName);
639         if (!s) {
640             if (errorString)
641                 *errorString = QDeclarativeImportDatabase::tr("- %1 is not a namespace").arg(namespaceName);
642             return false;
643         }
644         int nslash = type.indexOf('/',slash+1);
645         if (nslash > 0) {
646             if (errorString)
647                 *errorString = QDeclarativeImportDatabase::tr("- nested namespaces not allowed");
648             return false;
649         }
650     } else {
651         s = &unqualifiedset;
652     }
653     QByteArray unqualifiedtype = slash < 0 ? type : type.mid(slash+1); // common-case opt (QString::mid works fine, but slower)
654     if (s) {
655         if (s->find(typeLoader, unqualifiedtype,vmajor,vminor,type_return,url_return, &base, errorString))
656             return true;
657         if (s->urls.count() == 1 && !s->isLibrary[0] && url_return && s != &unqualifiedset) {
658             // qualified, and only 1 url
659             *url_return = QUrl(s->urls[0]+QLatin1Char('/')).resolved(QUrl(QString::fromUtf8(unqualifiedtype) + QLatin1String(".qml")));
660             return true;
661         }
662     }
663 
664     return false;
665 }
666 
findNamespace(const QString & type)667 QDeclarativeImportedNamespace *QDeclarativeImportsPrivate::findNamespace(const QString& type)
668 {
669     return set.value(type);
670 }
671 
find(QDeclarativeTypeLoader * typeLoader,const QByteArray & type,int * vmajor,int * vminor,QDeclarativeType ** type_return,QUrl * url_return,QUrl * base,QString * errorString)672 bool QDeclarativeImportedNamespace::find(QDeclarativeTypeLoader *typeLoader, const QByteArray& type, int *vmajor, int *vminor, QDeclarativeType** type_return,
673           QUrl* url_return, QUrl *base, QString *errorString)
674 {
675     bool typeRecursionDetected = false;
676     for (int i=0; i<urls.count(); ++i) {
677         if (find_helper(typeLoader, i, type, vmajor, vminor, type_return, url_return, base, &typeRecursionDetected)) {
678             if (qmlCheckTypes()) {
679                 // check for type clashes
680                 for (int j = i+1; j<urls.count(); ++j) {
681                     if (find_helper(typeLoader, j, type, vmajor, vminor, 0, 0, base)) {
682                         if (errorString) {
683                             QString u1 = urls.at(i);
684                             QString u2 = urls.at(j);
685                             if (base) {
686                                 QString b = base->toString();
687                                 int slash = b.lastIndexOf(QLatin1Char('/'));
688                                 if (slash >= 0) {
689                                     b = b.left(slash+1);
690                                     QString l = b.left(slash);
691                                     if (u1.startsWith(b))
692                                         u1 = u1.mid(b.count());
693                                     else if (u1 == l)
694                                         u1 = QDeclarativeImportDatabase::tr("local directory");
695                                     if (u2.startsWith(b))
696                                         u2 = u2.mid(b.count());
697                                     else if (u2 == l)
698                                         u2 = QDeclarativeImportDatabase::tr("local directory");
699                                 }
700                             }
701 
702                             if (u1 != u2)
703                                 *errorString
704                                         = QDeclarativeImportDatabase::tr("is ambiguous. Found in %1 and in %2")
705                                 .arg(u1).arg(u2);
706                             else
707                                 *errorString
708                                         = QDeclarativeImportDatabase::tr("is ambiguous. Found in %1 in version %2.%3 and %4.%5")
709                                           .arg(u1)
710                                           .arg(majversions.at(i)).arg(minversions.at(i))
711                                           .arg(majversions.at(j)).arg(minversions.at(j));
712                         }
713                         return false;
714                     }
715                 }
716             }
717             return true;
718         }
719     }
720     if (errorString) {
721         if (typeRecursionDetected)
722             *errorString = QDeclarativeImportDatabase::tr("is instantiated recursively");
723         else
724             *errorString = QDeclarativeImportDatabase::tr("is not a type");
725     }
726     return false;
727 }
728 
729 /*!
730 \class QDeclarativeImportDatabase
731 \brief The QDeclarativeImportDatabase class manages the QML imports for a QDeclarativeEngine.
732 \internal
733 */
QDeclarativeImportDatabase(QDeclarativeEngine * e)734 QDeclarativeImportDatabase::QDeclarativeImportDatabase(QDeclarativeEngine *e)
735 : engine(e)
736 {
737     filePluginPath << QLatin1String(".");
738 
739     // Search order is applicationDirPath(), $QML_IMPORT_PATH, QLibraryInfo::ImportsPath
740 
741 #ifndef QT_NO_SETTINGS
742     QString installImportsPath =  QLibraryInfo::location(QLibraryInfo::ImportsPath);
743 
744 #if defined(Q_OS_SYMBIAN)
745     // Append imports path for all available drives in Symbian
746     if (installImportsPath.at(1) != QChar(QLatin1Char(':'))) {
747         QString tempPath = installImportsPath;
748         if (tempPath.at(tempPath.length() - 1) != QDir::separator()) {
749             tempPath += QDir::separator();
750         }
751         RFs& fs = qt_s60GetRFs();
752         TPtrC tempPathPtr(reinterpret_cast<const TText*> (tempPath.constData()));
753         // Symbian searches should start from Y:. Fix start drive otherwise TFindFile starts from the session drive
754         _LIT(KStartDir, "Y:");
755         TFileName dirPath(KStartDir);
756         dirPath.Append(tempPathPtr);
757         TFindFile finder(fs);
758         TInt err = finder.FindByDir(tempPathPtr, dirPath);
759         while (err == KErrNone) {
760             QString foundDir(reinterpret_cast<const QChar *>(finder.File().Ptr()),
761                              finder.File().Length());
762             foundDir = QDir(foundDir).canonicalPath();
763             addImportPath(foundDir);
764             err = finder.Find();
765         }
766         // TFindFile found the directories in the order we want, but addImportPath reverses it.
767         // Reverse the order again to get it right.
768         QAlgorithmsPrivate::qReverse(fileImportPath.begin(), fileImportPath.end());
769     } else {
770         addImportPath(installImportsPath);
771     }
772 #else
773     addImportPath(installImportsPath);
774 #endif
775 
776 #endif // QT_NO_SETTINGS
777 
778     // env import paths
779     QByteArray envImportPath = qgetenv("QML_IMPORT_PATH");
780     if (!envImportPath.isEmpty()) {
781 #if defined(Q_OS_WIN) || defined(Q_OS_SYMBIAN)
782         QLatin1Char pathSep(';');
783 #else
784         QLatin1Char pathSep(':');
785 #endif
786         QStringList paths = QString::fromLatin1(envImportPath).split(pathSep, QString::SkipEmptyParts);
787         for (int ii = paths.count() - 1; ii >= 0; --ii)
788             addImportPath(paths.at(ii));
789     }
790 
791     addImportPath(QCoreApplication::applicationDirPath());
792 }
793 
~QDeclarativeImportDatabase()794 QDeclarativeImportDatabase::~QDeclarativeImportDatabase()
795 {
796 }
797 
798 /*!
799   \internal
800 
801   Adds information to \a imports such that subsequent calls to resolveType()
802   will resolve types qualified by \a prefix by considering types found at the given \a uri.
803 
804   The uri is either a directory (if importType is FileImport), or a URI resolved using paths
805   added via addImportPath() (if importType is LibraryImport).
806 
807   The \a prefix may be empty, in which case the import location is considered for
808   unqualified types.
809 
810   The base URL must already have been set with Import::setBaseUrl().
811 */
addImport(QDeclarativeImportDatabase * importDb,const QString & uri,const QString & prefix,int vmaj,int vmin,QDeclarativeScriptParser::Import::Type importType,const QDeclarativeDirComponents & qmldircomponentsnetwork,QString * errorString)812 bool QDeclarativeImports::addImport(QDeclarativeImportDatabase *importDb,
813                                     const QString& uri, const QString& prefix, int vmaj, int vmin,
814                                     QDeclarativeScriptParser::Import::Type importType,
815                                     const QDeclarativeDirComponents &qmldircomponentsnetwork,
816                                     QString *errorString)
817 {
818     if (qmlImportTrace())
819         qDebug().nospace() << "QDeclarativeImports(" << qPrintable(baseUrl().toString()) << ")" << "::addImport: "
820                            << uri << " " << vmaj << '.' << vmin << " "
821                            << (importType==QDeclarativeScriptParser::Import::Library? "Library" : "File")
822                            << " as " << prefix;
823 
824     return d->add(qmldircomponentsnetwork, uri, prefix, vmaj, vmin, importType, importDb, errorString);
825 }
826 
827 /*!
828   \internal
829 
830   Returns the result of the merge of \a baseName with \a path, \a suffixes, and \a prefix.
831   The \a prefix must contain the dot.
832 
833   \a qmldirPath is the location of the qmldir file.
834  */
resolvePlugin(const QDir & qmldirPath,const QString & qmldirPluginPath,const QString & baseName,const QStringList & suffixes,const QString & prefix)835 QString QDeclarativeImportDatabase::resolvePlugin(const QDir &qmldirPath, const QString &qmldirPluginPath,
836                                                   const QString &baseName, const QStringList &suffixes,
837                                                   const QString &prefix)
838 {
839     QStringList searchPaths = filePluginPath;
840     bool qmldirPluginPathIsRelative = QDir::isRelativePath(qmldirPluginPath);
841     if (!qmldirPluginPathIsRelative)
842         searchPaths.prepend(qmldirPluginPath);
843 
844     foreach (const QString &pluginPath, searchPaths) {
845 
846         QString resolvedPath;
847 
848         if (pluginPath == QLatin1String(".")) {
849             if (qmldirPluginPathIsRelative)
850                 resolvedPath = qmldirPath.absoluteFilePath(qmldirPluginPath);
851             else
852                 resolvedPath = qmldirPath.absolutePath();
853         } else {
854             resolvedPath = pluginPath;
855         }
856 
857         // hack for resources, should probably go away
858         if (resolvedPath.startsWith(QLatin1Char(':')))
859             resolvedPath = QCoreApplication::applicationDirPath();
860 
861         QDir dir(resolvedPath);
862         foreach (const QString &suffix, suffixes) {
863             QString pluginFileName = prefix;
864 
865             pluginFileName += baseName;
866             pluginFileName += suffix;
867 
868             QFileInfo fileInfo(dir, pluginFileName);
869 
870             if (fileInfo.exists())
871                 return fileInfo.absoluteFilePath();
872         }
873     }
874 
875     if (qmlImportTrace())
876         qDebug() << "QDeclarativeImportDatabase::resolvePlugin: Could not resolve plugin" << baseName
877                  << "in" << qmldirPath.absolutePath();
878 
879     return QString();
880 }
881 
882 /*!
883   \internal
884 
885   Returns the result of the merge of \a baseName with \a dir and the platform suffix.
886 
887   \table
888   \header \i Platform \i Valid suffixes
889   \row \i Windows     \i \c .dll
890   \row \i Unix/Linux  \i \c .so
891   \row \i AIX  \i \c .a
892   \row \i HP-UX       \i \c .sl, \c .so (HP-UXi)
893   \row \i Mac OS X    \i \c .dylib, \c .bundle, \c .so
894   \row \i Symbian     \i \c .dll
895   \endtable
896 
897   Version number on unix are ignored.
898 */
resolvePlugin(const QDir & qmldirPath,const QString & qmldirPluginPath,const QString & baseName)899 QString QDeclarativeImportDatabase::resolvePlugin(const QDir &qmldirPath, const QString &qmldirPluginPath,
900                                                   const QString &baseName)
901 {
902 #if defined(Q_OS_WIN32) || defined(Q_OS_WINCE)
903     return resolvePlugin(qmldirPath, qmldirPluginPath, baseName,
904                          QStringList()
905 # ifdef QT_DEBUG
906                          << QLatin1String("d.dll") // try a qmake-style debug build first
907 # endif
908                          << QLatin1String(".dll"));
909 #elif defined(Q_OS_SYMBIAN)
910     return resolvePlugin(qmldirPath, qmldirPluginPath, baseName,
911                          QStringList()
912                          << QLatin1String(".dll")
913                          << QLatin1String(".qtplugin"));
914 #else
915 
916 # if defined(Q_OS_DARWIN)
917 
918     return resolvePlugin(qmldirPath, qmldirPluginPath, baseName,
919                          QStringList()
920 # ifdef QT_DEBUG
921                          << QLatin1String("_debug.dylib") // try a qmake-style debug build first
922                          << QLatin1String(".dylib")
923 # else
924                          << QLatin1String(".dylib")
925                          << QLatin1String("_debug.dylib") // try a qmake-style debug build after
926 # endif
927                          << QLatin1String(".so")
928                          << QLatin1String(".bundle"),
929                          QLatin1String("lib"));
930 # else  // Generic Unix
931     QStringList validSuffixList;
932 
933 #  if defined(Q_OS_HPUX)
934 /*
935     See "HP-UX Linker and Libraries User's Guide", section "Link-time Differences between PA-RISC and IPF":
936     "In PA-RISC (PA-32 and PA-64) shared libraries are suffixed with .sl. In IPF (32-bit and 64-bit),
937     the shared libraries are suffixed with .so. For compatibility, the IPF linker also supports the .sl suffix."
938  */
939     validSuffixList << QLatin1String(".sl");
940 #   if defined __ia64
941     validSuffixList << QLatin1String(".so");
942 #   endif
943 #  elif defined(Q_OS_AIX)
944     validSuffixList << QLatin1String(".a") << QLatin1String(".so");
945 #  elif defined(Q_OS_UNIX)
946     validSuffixList << QLatin1String(".so");
947 #  endif
948 
949     // Examples of valid library names:
950     //  libfoo.so
951 
952     return resolvePlugin(qmldirPath, qmldirPluginPath, baseName, validSuffixList, QLatin1String("lib"));
953 # endif
954 
955 #endif
956 }
957 
958 /*!
959     \internal
960 */
pluginPathList() const961 QStringList QDeclarativeImportDatabase::pluginPathList() const
962 {
963     return filePluginPath;
964 }
965 
966 /*!
967     \internal
968 */
setPluginPathList(const QStringList & paths)969 void QDeclarativeImportDatabase::setPluginPathList(const QStringList &paths)
970 {
971     filePluginPath = paths;
972 }
973 
974 /*!
975     \internal
976 */
addPluginPath(const QString & path)977 void QDeclarativeImportDatabase::addPluginPath(const QString& path)
978 {
979     if (qmlImportTrace())
980         qDebug().nospace() << "QDeclarativeImportDatabase::addPluginPath: " << path;
981 
982     QUrl url = QUrl(path);
983     if (url.isRelative() || url.scheme() == QLatin1String("file")
984             || (url.scheme().length() == 1 && QFile::exists(path)) ) {  // windows path
985         QDir dir = QDir(path);
986         filePluginPath.prepend(dir.canonicalPath());
987     } else {
988         filePluginPath.prepend(path);
989     }
990 }
991 
992 /*!
993     \internal
994 */
addImportPath(const QString & path)995 void QDeclarativeImportDatabase::addImportPath(const QString& path)
996 {
997     if (qmlImportTrace())
998         qDebug().nospace() << "QDeclarativeImportDatabase::addImportPath: " << path;
999 
1000     if (path.isEmpty())
1001         return;
1002 
1003     QUrl url = QUrl(path);
1004     QString cPath;
1005 
1006     if (url.isRelative() || url.scheme() == QLatin1String("file")
1007             || (url.scheme().length() == 1 && QFile::exists(path)) ) {  // windows path
1008         QDir dir = QDir(path);
1009         cPath = dir.canonicalPath();
1010     } else {
1011         cPath = path;
1012         cPath.replace(QLatin1Char('\\'), QLatin1Char('/'));
1013     }
1014 
1015     if (!cPath.isEmpty()
1016         && !fileImportPath.contains(cPath))
1017         fileImportPath.prepend(cPath);
1018 }
1019 
1020 /*!
1021     \internal
1022 */
importPathList() const1023 QStringList QDeclarativeImportDatabase::importPathList() const
1024 {
1025     return fileImportPath;
1026 }
1027 
1028 /*!
1029     \internal
1030 */
setImportPathList(const QStringList & paths)1031 void QDeclarativeImportDatabase::setImportPathList(const QStringList &paths)
1032 {
1033     fileImportPath = paths;
1034 }
1035 
1036 /*!
1037     \internal
1038 */
importPlugin(const QString & filePath,const QString & uri,QString * errorString)1039 bool QDeclarativeImportDatabase::importPlugin(const QString &filePath, const QString &uri, QString *errorString)
1040 {
1041     if (qmlImportTrace())
1042         qDebug().nospace() << "QDeclarativeImportDatabase::importPlugin: " << uri << " from " << filePath;
1043 
1044 #ifndef QT_NO_LIBRARY
1045     QFileInfo fileInfo(filePath);
1046     const QString absoluteFilePath = fileInfo.absoluteFilePath();
1047 
1048     bool engineInitialized = initializedPlugins.contains(absoluteFilePath);
1049     bool typesRegistered = qmlEnginePluginsWithRegisteredTypes()->contains(absoluteFilePath);
1050 
1051     if (typesRegistered) {
1052         Q_ASSERT_X(qmlEnginePluginsWithRegisteredTypes()->value(absoluteFilePath) == uri,
1053                    "QDeclarativeImportDatabase::importExtension",
1054                    "Internal error: Plugin imported previously with different uri");
1055     }
1056 
1057     if (!engineInitialized || !typesRegistered) {
1058         if (!QDeclarative_isFileCaseCorrect(absoluteFilePath)) {
1059             if (errorString)
1060                 *errorString = tr("File name case mismatch for \"%1\"").arg(absoluteFilePath);
1061             return false;
1062         }
1063         QPluginLoader loader(absoluteFilePath);
1064 
1065         if (!loader.load()) {
1066             if (errorString)
1067                 *errorString = loader.errorString();
1068             return false;
1069         }
1070 
1071         if (QDeclarativeExtensionInterface *iface = qobject_cast<QDeclarativeExtensionInterface *>(loader.instance())) {
1072 
1073             const QByteArray bytes = uri.toUtf8();
1074             const char *moduleId = bytes.constData();
1075             if (!typesRegistered) {
1076 
1077                 // ### this code should probably be protected with a mutex.
1078                 qmlEnginePluginsWithRegisteredTypes()->insert(absoluteFilePath, uri);
1079                 iface->registerTypes(moduleId);
1080             }
1081             if (!engineInitialized) {
1082                 // things on the engine (eg. adding new global objects) have to be done for every engine.
1083 
1084                 // protect against double initialization
1085                 initializedPlugins.insert(absoluteFilePath);
1086                 iface->initializeEngine(engine, moduleId);
1087             }
1088         } else {
1089             if (errorString)
1090                 *errorString = loader.errorString();
1091             return false;
1092         }
1093     }
1094 
1095     return true;
1096 #else
1097     return false;
1098 #endif
1099 }
1100 
1101 
1102 QT_END_NAMESPACE
1103