Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qandroidapkfileengine.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 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// Qt-Security score:critical reason:file-handling
4
6
7#include <QtCore/QCoreApplication>
8#include <QtCore/QJniEnvironment>
9#include <QtCore/QReadWriteLock>
10
11#include <private/qjnihelpers_p.h>
12
14
15Q_DECLARE_JNI_CLASS(JFileInfo, "org/qtproject/qt/android/JFileInfo")
16Q_DECLARE_JNI_CLASS(MappedByteBuffer, "java/nio/MappedByteBuffer")
17
18using namespace Qt::StringLiterals;
19using namespace QtJniTypes;
20using namespace QNativeInterface;
21
23namespace {
24struct ApkFileInfosGlobalData
25{
26 QReadWriteLock apkInfosLock;
27 ApkFileInfos apkFileInfos;
28};
29}
30Q_GLOBAL_STATIC(ApkFileInfosGlobalData, g_apkFileInfosGlobal)
31
33{
34 {
35 QReadLocker lock(&g_apkFileInfosGlobal->apkInfosLock);
36 if (!g_apkFileInfosGlobal->apkFileInfos.isEmpty())
37 return &g_apkFileInfosGlobal->apkFileInfos;
38 }
39
40 QWriteLocker lock(&g_apkFileInfosGlobal->apkInfosLock);
41 ArrayList arrayList = QtApkFileEngine::callStaticMethod<ArrayList>(
42 "getApkFileInfos", QAndroidApkFileEngine::apkPath());
43
44 for (int i = 0; i < arrayList.callMethod<int>("size"); ++i) {
45 JFileInfo jInfo = arrayList.callMethod<jobject>("get", i);
47 info.relativePath = jInfo.getField<QString>("relativePath");
48 info.size = jInfo.getField<jlong>("size");
49 info.isDir = jInfo.getField<jboolean>("isDir");
50 g_apkFileInfosGlobal->apkFileInfos.append(info);
51 }
52
53 return &g_apkFileInfosGlobal->apkFileInfos;
54}
55
58{
59 setFileName(fileName);
60
61 QString relativePath = QAndroidApkFileEngine::relativePath(m_fileName);
62 for (QAndroidApkFileEngine::FileInfo &info : *apkFileInfos()) {
63 if (info.relativePath == relativePath) {
64 m_fileInfo = &info;
65 break;
66 }
67 }
68}
69
74
75QString QAndroidApkFileEngine::apkPath()
76{
77 static QString apkPath = QtApkFileEngine::callStaticMethod<QString>("getAppApkFilePath");
78 return apkPath;
79}
80
81QString QAndroidApkFileEngine::relativePath(const QString &filePath)
82{
83 const static int apkPathPrefixSize = apkPath().size() + 2;
84 return filePath.right(filePath.size() - apkPathPrefixSize);
85}
86
87bool QAndroidApkFileEngine::open(QIODevice::OpenMode openMode,
88 std::optional<QFile::Permissions> permissions)
89{
90 Q_UNUSED(permissions);
91
92 if (!(openMode & QIODevice::ReadOnly))
93 return false;
94
95 if (!m_apkFileEngine.isValid() || !m_fileInfo)
96 return false;
97
98 if (m_fileInfo->relativePath.isEmpty())
99 return false;
100
101 return m_apkFileEngine.callMethod<bool>("open", m_fileInfo->relativePath);
102}
103
105{
106 return m_apkFileEngine.isValid() ? m_apkFileEngine.callMethod<bool>("close") : false;
107}
108
110{
111 return m_fileInfo ? m_fileInfo->size : -1;
112}
113
115{
116 return m_apkFileEngine.isValid() ? m_apkFileEngine.callMethod<jlong>("pos") : -1;
117}
118
120{
121 return m_apkFileEngine.isValid() ? m_apkFileEngine.callMethod<bool>("seek", jint(pos)) : false;
122}
123
124qint64 QAndroidApkFileEngine::read(char *data, qint64 maxlen)
125{
126 if (!m_apkFileEngine.isValid())
127 return -1;
128
129 QJniArray<jbyte> byteArray = m_apkFileEngine.callMethod<jbyte[]>("read", jlong(maxlen));
130 if (!byteArray.isValid())
131 return -1;
132
133 QJniEnvironment env;
134 env->GetByteArrayRegion(byteArray.arrayObject(), 0, byteArray.size(), (jbyte*)data);
135
136 if (env.checkAndClearExceptions())
137 return -1;
138
139 return byteArray.size();
140}
141
143{
144 if (m_fileInfo) {
145 FileFlags commonFlags(ReadOwnerPerm|ReadUserPerm|ReadGroupPerm|ReadOtherPerm|ExistsFlag);
146 if (m_fileInfo->isDir)
147 return type & (DirectoryType | commonFlags);
148 else
149 return type & (FileType | commonFlags);
150 }
151
152 return {};
153}
154
156{
157 switch (file) {
158 case CanonicalPathName:
159 case CanonicalName:
160 // An entry missing from the APK has no canonical name.
161 if (!m_fileInfo)
162 return QString();
163 Q_FALLTHROUGH();
164 case PathName:
165 case AbsolutePathName:
166 case DefaultName:
167 case AbsoluteName:
168 return m_fileName;
169 case BaseName:
170 return m_fileName.mid(m_fileName.lastIndexOf(u'/') + 1);
171 default:
172 return QString();
173 }
174}
175
176void QAndroidApkFileEngine::setFileName(const QString &file)
177{
178 m_fileName = file;
179 if (m_fileName.endsWith(u'/'))
180 m_fileName.chop(1);
181}
182
183uchar *QAndroidApkFileEngine::map(qint64 offset, qint64 size, QFileDevice::MemoryMapFlags flags)
184{
185 if (flags & QFile::MapPrivateOption) {
186 qCritical() << "Mapping an in-APK file with private mode is not supported.";
187 return nullptr;
188 }
189 if (!m_apkFileEngine.isValid())
190 return nullptr;
191
192 const MappedByteBuffer mappedBuffer = m_apkFileEngine.callMethod<MappedByteBuffer>(
193 "getMappedByteBuffer", jlong(offset), jlong(size));
194
195 if (!mappedBuffer.isValid())
196 return nullptr;
197
198 void *address = QJniEnvironment::getJniEnv()->GetDirectBufferAddress(mappedBuffer.object());
199
200 return const_cast<uchar *>(reinterpret_cast<const uchar *>(address));
201}
202
203bool QAndroidApkFileEngine::extension(Extension extension, const ExtensionOption *option,
204 ExtensionReturn *output)
205{
206 if (extension == MapExtension) {
207 const auto *options = static_cast<const MapExtensionOption *>(option);
208 auto *returnValue = static_cast<MapExtensionReturn *>(output);
209 returnValue->address = map(options->offset, options->size, options->flags);
210 return (returnValue->address != nullptr);
211 }
212 return false;
213}
214
215bool QAndroidApkFileEngine::supportsExtension(Extension extension) const
216{
217 if (extension == MapExtension)
218 return true;
219 return false;
220}
221
222#ifndef QT_NO_FILESYSTEMITERATOR
224 const QString &path, QDirListing::IteratorFlags filters, const QStringList &filterNames)
225{
226 return std::make_unique<QAndroidApkFileEngineIterator>(path, filters, filterNames);
227}
228
230 const QString &path, QDirListing::IteratorFlags filters, const QStringList &filterNames)
232{
233 const QString relativePath = QAndroidApkFileEngine::relativePath(path);
234 m_prefixLength = relativePath.size() + 1;
235 for (QAndroidApkFileEngine::FileInfo &info : *apkFileInfos()) {
236 if (info.relativePath.startsWith(relativePath))
237 m_infos.append(&info);
238 }
239}
240
242
244{
245 if (!m_infos.isEmpty() && m_index < m_infos.size() - 1) {
246 ++m_index;
247 return true;
248 }
249
250 return false;
251}
252
254{
255 return m_infos.at(m_index)->relativePath.mid(m_prefixLength);
256}
257#endif
258
260QAndroidApkFileEngineHandler::create(const QString &fileName) const
261{
262 if (QtAndroidPrivate::resolveApkPath(fileName).isEmpty())
263 return {};
264
265 return std::make_unique<QAndroidApkFileEngine>(fileName);
266}
267
268QT_END_NAMESPACE
std::unique_ptr< QAbstractFileEngine > create(const QString &fileName) const override
If this file handler can handle fileName, this method creates a file engine and returns it wrapped in...
QString currentFileName() const override
This pure virtual function returns the name of the current directory entry, excluding the path.
bool advance() override
This pure virtual function advances the iterator to the next directory entry; if the operation was su...
QAndroidApkFileEngineIterator(const QString &path, QDirListing::IteratorFlags filters, const QStringList &filterNames)
IteratorUniquePtr beginEntryList(const QString &, QDirListing::IteratorFlags filters, const QStringList &filterNames) override
Returns a QAbstractFileEngine::IteratorUniquePtr, that can be used to iterate over the entries in pat...
FileFlags fileFlags(FileFlags type=FileInfoAll) const override
This function should return the set of OR'd flags that are true for the file engine's file,...
qint64 size() const override
Returns the size of the file.
void setFileName(const QString &file) override
Sets the file engine's file name to file.
bool supportsExtension(Extension extension) const override
QString fileName(FileName file=DefaultName) const override
Return the file engine's current file name in the format specified by file.
bool close() override
Closes the file, returning true if successful; otherwise returns false.
bool seek(qint64 pos) override
Sets the file position to the given offset.
bool extension(Extension extension, const ExtensionOption *option=nullptr, ExtensionReturn *output=nullptr) override
QAndroidApkFileEngine(const QString &fileName)
qint64 pos() const override
Returns the current file position.
uchar * map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags)
qint64 read(char *data, qint64 maxlen) override
Reads a number of characters from the file into data.
Combined button and popup list for selecting options.
QList< QAndroidApkFileEngine::FileInfo > ApkFileInfos
static ApkFileInfos * apkFileInfos()