/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "file.h" #include namespace { class FileError { public: static const QString kEncodingErr; static const QString kTypeMismatchErr; static const QString kNotFoundErr; static const QString kSecurityErr; static const QString kAbortErr; static const QString kNotReadableErr; static const QString kNoModificationAllowedErr; static const QString kInvalidStateErr; static const QString kSyntaxErr; static const QString kInvalidModificationErr; static const QString kQuotaExceededErr; static const QString kPathExistsErr; }; bool checkFileName(const QString &name) { if (name.contains(":")){ return false; } return true; } }; const QString FileError::kEncodingErr("FileError.ENCODING_ERR"); const QString FileError::kTypeMismatchErr("FileError.TYPE_MISMATCH_ERR"); const QString FileError::kNotFoundErr("FileError.NOT_FOUND_ERR"); const QString FileError::kSecurityErr("FileError.SECURITY_ERR"); const QString FileError::kAbortErr("FileError.ABORT_ERR"); const QString FileError::kNotReadableErr("FileError.NOT_READABLE_ERR"); const QString FileError::kNoModificationAllowedErr("FileError.NO_MODIFICATION_ALLOWED_ERR"); const QString FileError::kInvalidStateErr("FileError.INVALID_STATE_ERR"); const QString FileError::kSyntaxErr("FileError.SYNTAX_ERR"); const QString FileError::kInvalidModificationErr("FileError.INVALID_MODIFICATION_ERR"); const QString FileError::kQuotaExceededErr("FileError.QUOTA_EXCEEDED_ERR"); const QString FileError::kPathExistsErr("FileError.PATH_EXISTS_ERR"); File::File(Cordova *cordova) : CPlugin(cordova), _persistentDir(QString("%1/.local/share/%2/persistent").arg(QDir::homePath()).arg(QCoreApplication::applicationName())) { QDir::root().mkpath(_persistentDir.absolutePath()); } QVariantMap File::file2map(const QFileInfo &fileInfo) { QVariantMap res; res.insert("name", fileInfo.fileName()); QPair r = GetRelativePath(fileInfo); res.insert("fullPath", QString("/") + r.second); res.insert("filesystemName", r.first); res.insert("nativeURL", QString("file://localhost") + fileInfo.absoluteFilePath()); res.insert("isDirectory", (int)fileInfo.isDir()); res.insert("isFile", (int)fileInfo.isFile()); return res; } QVariantMap File::dir2map(const QDir &dir) { return file2map(QFileInfo(dir.absolutePath())); } QPair File::GetRelativePath(const QFileInfo &fileInfo) { QString fullPath = fileInfo.isDir() ? QDir::cleanPath(fileInfo.absoluteFilePath()) : fileInfo.absoluteFilePath(); QString relativePath1 = _persistentDir.relativeFilePath(fullPath); QString relativePath2 = QDir::temp().relativeFilePath(fullPath); if (!(relativePath1[0] != '.' || relativePath2[0] != '.')) { if (relativePath1.size() > relativePath2.size()) { return QPair("temporary", relativePath2); } else { return QPair("persistent", relativePath1); } } if (relativePath1[0] != '.') return QPair("persistent", relativePath1); return QPair("temporary", relativePath2); } void File::requestFileSystem(int scId, int ecId, unsigned short type, unsigned long long size) { QDir dir; if (size >= 1000485760){ this->callback(ecId, FileError::kQuotaExceededErr); return; } if (type == 0) dir = QDir::temp(); else dir = _persistentDir; if (type > 1) { this->callback(ecId, FileError::kSyntaxErr); return; } else { QVariantMap res; res.insert("root", dir2map(dir)); if (type == 0) res.insert("name", "temporary"); else res.insert("name", "persistent"); this->cb(scId, res); } } QPair File::resolveURI(int ecId, const QString &uri) { QPair result; result.first = false; QUrl url = QUrl::fromUserInput(uri); if (url.scheme() == "file" && url.isValid()) { result.first = true; result.second = QFileInfo(url.path()); return result; } if (url.scheme() != "cdvfile") { if (ecId) this->callback(ecId, FileError::kTypeMismatchErr); return result; } QString path = url.path().replace("//", "/"); //NOTE: colon is not safe in url, it is not a valid path in Win and Mac, simple disable it here. if (path.contains(":") || !url.isValid()){ if (ecId) this->callback(ecId, FileError::kEncodingErr); return result; } if (!path.startsWith("/persistent/") && !path.startsWith("/temporary/")) { if (ecId) this->callback(ecId, FileError::kEncodingErr); return result; } result.first = true; if (path.startsWith("/persistent/")) { QString relativePath = path.mid(QString("/persistent/").size()); result.second = QFileInfo(_persistentDir.filePath(relativePath)); } else { QString relativePath = path.mid(QString("/temporary/").size()); result.second = QFileInfo(QDir::temp().filePath(relativePath)); } return result; } QPair File::resolveURI(const QString &uri) { return resolveURI(0, uri); } void File::_getLocalFilesystemPath(int scId, int ecId, const QString& uri) { QPair f1 = resolveURI(ecId, uri); if (!f1.first) return; this->cb(scId, f1.second.absoluteFilePath()); } void File::resolveLocalFileSystemURI(int scId, int ecId, const QString &uri) { if (uri[0] == '/' || uri[0] == '.') { this->callback(ecId, FileError::kEncodingErr); return; } QPair f1 = resolveURI(ecId, uri); if (!f1.first) return; QFileInfo fileInfo = f1.second; if (!fileInfo.exists()) { this->callback(ecId, FileError::kNotFoundErr); return; } this->cb(scId, file2map(fileInfo)); } void File::getFile(int scId, int ecId, const QString &parentPath, const QString &rpath, const QVariantMap &options) { QPair f1 = resolveURI(ecId, parentPath + "/" + rpath); if (!f1.first) return; bool create = options.value("create").toBool(); bool exclusive = options.value("exclusive").toBool(); QFile file(f1.second.absoluteFilePath()); // if create is false and the path represents a directory, return error QFileInfo fileInfo = f1.second; if ((!create) && fileInfo.isDir()) { this->callback(ecId, FileError::kTypeMismatchErr); return; } // if file does exist, and create is true and exclusive is true, return error if (file.exists()) { if (create && exclusive) { this->callback(ecId, FileError::kPathExistsErr); return; } } else { // if file does not exist and create is false, return error if (!create) { this->callback(ecId, FileError::kNotFoundErr); return; } file.open(QIODevice::WriteOnly); file.close(); // Check if creation was successfull if (!file.exists()) { this->callback(ecId, FileError::kNoModificationAllowedErr); return; } } this->cb(scId, file2map(QFileInfo(file))); } void File::getDirectory(int scId, int ecId, const QString &parentPath, const QString &rpath, const QVariantMap &options) { QPair f1 = resolveURI(ecId, parentPath + "/" + rpath); if (!f1.first) return; bool create = options.value("create").toBool(); bool exclusive = options.value("exclusive").toBool(); QDir dir(f1.second.absoluteFilePath()); QFileInfo &fileInfo = f1.second; if ((!create) && fileInfo.isFile()) { this->callback(ecId, FileError::kTypeMismatchErr); return; } if (dir.exists()) { if (create && exclusive) { this->callback(ecId, FileError::kPathExistsErr); return; } } else { if (!create) { this->callback(ecId, FileError::kNotFoundErr); return; } QString folderName = dir.dirName(); dir.cdUp(); dir.mkdir(folderName); dir.cd(folderName); if (!dir.exists()) { this->callback(ecId, FileError::kNoModificationAllowedErr); return; } } this->cb(scId, dir2map(dir)); } void File::removeRecursively(int scId, int ecId, const QString &uri) { QPair f1 = resolveURI(ecId, uri); if (!f1.first) return; QDir dir(f1.second.absoluteFilePath()); if (File::rmDir(dir)) this->cb(scId); else this->callback(ecId, FileError::kNoModificationAllowedErr); } void File::write(int scId, int ecId, const QString &uri, const QString &_data, unsigned long long position, bool binary) { QPair f1 = resolveURI(ecId, uri); if (!f1.first) return; QFile file(f1.second.absoluteFilePath()); file.open(QIODevice::WriteOnly); file.close(); if (!file.exists()) { this->callback(ecId, FileError::kNotFoundErr); return; } QFileInfo fileInfo(file); if (!file.open(QIODevice::ReadWrite)) { this->callback(ecId, FileError::kNoModificationAllowedErr); return; } if (!binary) { QTextStream textStream(&file); textStream.setCodec("UTF-8"); textStream.setAutoDetectUnicode(true); if (!textStream.seek(position)) { file.close(); fileInfo.refresh(); this->callback(ecId, FileError::kInvalidModificationErr); return; } textStream << _data; textStream.flush(); } else { QByteArray data(_data.toUtf8()); if (!file.seek(position)) { file.close(); fileInfo.refresh(); this->callback(ecId, FileError::kInvalidModificationErr); return; } file.write(data.data(), data.length()); } file.flush(); file.close(); fileInfo.refresh(); this->cb(scId, fileInfo.size() - position); } void File::truncate(int scId, int ecId, const QString &uri, unsigned long long size) { QPair f1 = resolveURI(ecId, uri); if (!f1.first) return; QFile file(f1.second.absoluteFilePath()); if (!file.exists()) { this->callback(ecId, FileError::kNotFoundErr); return; } if (!file.resize(size)) { this->callback(ecId, FileError::kNoModificationAllowedErr); return; } this->cb(scId, size); } void File::getParent(int scId, int ecId, const QString &uri) { QPair f1 = resolveURI(ecId, uri); if (!f1.first) return; QDir dir(f1.second.absoluteFilePath()); //can't cdup more than app's root // Try to change into upper directory if (dir != _persistentDir && dir != QDir::temp()){ if (!dir.cdUp()) { this->callback(ecId, FileError::kNotFoundErr); return; } } this->cb(scId, dir2map(dir)); } void File::remove(int scId, int ecId, const QString &uri) { QPair f1 = resolveURI(ecId, uri); if (!f1.first) return; QFileInfo &fileInfo = f1.second; //TODO: fix if (!fileInfo.exists() || (fileInfo.absoluteFilePath() == _persistentDir.absolutePath()) || (QDir::temp() == fileInfo.absoluteFilePath())) { this->callback(ecId, FileError::kNoModificationAllowedErr); return; } if (fileInfo.isDir()) { QDir dir(fileInfo.absoluteFilePath()); if (dir.rmdir(dir.absolutePath())) { this->cb(scId); return; } } else { QFile file(fileInfo.absoluteFilePath()); if (file.remove()) { this->cb(scId); return; } } this->callback(ecId, FileError::kInvalidModificationErr); } void File::getFileMetadata(int scId, int ecId, const QString &uri) { QPair f1 = resolveURI(ecId, uri); if (!f1.first) return; QFileInfo &fileInfo = f1.second; if (!fileInfo.exists()) { this->callback(ecId, FileError::kNotFoundErr); } else { QMimeType mime = _db.mimeTypeForFile(fileInfo.fileName()); QString args = QString("{name: %1, fullPath: %2, type: %3, lastModifiedDate: new Date(%4), size: %5}") .arg(CordovaInternal::format(fileInfo.fileName())).arg(CordovaInternal::format(fileInfo.absoluteFilePath())) .arg(CordovaInternal::format(mime.name())).arg(fileInfo.lastModified().toMSecsSinceEpoch()) .arg(fileInfo.size()); this->callback(scId, args); } } void File::getMetadata(int scId, int ecId, const QString &uri) { QPair f1 = resolveURI(ecId, uri); if (!f1.first) return; QFileInfo &fileInfo = f1.second; if (!fileInfo.exists()) this->callback(ecId, FileError::kNotFoundErr); else { QVariantMap obj; obj.insert("modificationTime", fileInfo.lastModified().toMSecsSinceEpoch()); obj.insert("size", fileInfo.isDir() ? 0 : fileInfo.size()); this->cb(scId, obj); } } void File::readEntries(int scId, int ecId, const QString &uri) { QPair f1 = resolveURI(ecId, uri); if (!f1.first) return; QDir dir(f1.second.absoluteFilePath()); QString entriesList; if (!dir.exists()) { this->callback(ecId, FileError::kNotFoundErr); return; } for (const QFileInfo &fileInfo: dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)) { entriesList += CordovaInternal::format(file2map(fileInfo)) + ","; } // Remove trailing comma if (entriesList.size() > 0) entriesList.remove(entriesList.size() - 1, 1); entriesList = "new Array(" + entriesList + ")"; this->callback(scId, entriesList); } void File::readAsText(int scId, int ecId, const QString &uri, const QString &/*encoding*/, int sliceStart, int sliceEnd) { QPair f1 = resolveURI(ecId, uri); if (!f1.first) return; QFile file(f1.second.absoluteFilePath()); if (!file.exists()) { this->callback(ecId, FileError::kNotFoundErr); return; } if (!file.open(QIODevice::ReadOnly)) { this->callback(ecId, FileError::kNotReadableErr); return; } QByteArray content = file.readAll(); if (sliceEnd == -1) sliceEnd = content.size(); if (sliceEnd < 0) { sliceEnd++; sliceEnd = std::max(0, content.size() + sliceEnd); } if (sliceEnd > content.size()) sliceEnd = content.size(); if (sliceStart < 0) sliceStart = std::max(0, content.size() + sliceStart); if (sliceStart > content.size()) sliceStart = content.size(); if (sliceStart > sliceEnd) sliceEnd = sliceStart; //FIXME: encoding content = content.mid(sliceStart, sliceEnd - sliceStart); this->cb(scId, content); } void File::readAsArrayBuffer(int scId, int ecId, const QString &uri, int sliceStart, int sliceEnd) { const QString str2array("\ (function strToArray(str) { \ var res = new Uint8Array(str.length); \ for (var i = 0; i < str.length; i++) { \ res[i] = str.charCodeAt(i); \ } \ return res; \ })(\"%1\")"); QPair f1 = resolveURI(ecId, uri); if (!f1.first) return; QFile file(f1.second.absoluteFilePath()); if (!file.exists()) { this->callback(ecId, FileError::kNotFoundErr); return; } if (!file.open(QIODevice::ReadOnly)) { this->callback(ecId, FileError::kNotReadableErr); return; } QString res; QByteArray content = file.readAll(); if (sliceEnd == -1) sliceEnd = content.size(); if (sliceEnd < 0) { sliceEnd++; sliceEnd = std::max(0, content.size() + sliceEnd); } if (sliceEnd > content.size()) sliceEnd = content.size(); if (sliceStart < 0) sliceStart = std::max(0, content.size() + sliceStart); if (sliceStart > content.size()) sliceStart = content.size(); if (sliceStart > sliceEnd) sliceEnd = sliceStart; content = content.mid(sliceStart, sliceEnd - sliceStart); res.reserve(content.length() * 6); for (uchar c: content) { res += "\\x"; res += QString::number(c, 16).rightJustified(2, '0').toUpper(); } this->callback(scId, str2array.arg(res)); } void File::readAsBinaryString(int scId, int ecId, const QString &uri, int sliceStart, int sliceEnd) { QPair f1 = resolveURI(ecId, uri); if (!f1.first) return; QFile file(f1.second.absoluteFilePath()); if (!file.exists()) { this->callback(ecId, FileError::kNotFoundErr); return; } if (!file.open(QIODevice::ReadOnly)) { this->callback(ecId, FileError::kNotReadableErr); return; } QString res; QByteArray content = file.readAll(); if (sliceEnd == -1) sliceEnd = content.size(); if (sliceEnd < 0) { sliceEnd++; sliceEnd = std::max(0, content.size() + sliceEnd); } if (sliceEnd > content.size()) sliceEnd = content.size(); if (sliceStart < 0) sliceStart = std::max(0, content.size() + sliceStart); if (sliceStart > content.size()) sliceStart = content.size(); if (sliceStart > sliceEnd) sliceEnd = sliceStart; content = content.mid(sliceStart, sliceEnd - sliceStart); res.reserve(content.length() * 6); for (uchar c: content) { res += "\\x"; res += QString::number(c, 16).rightJustified(2, '0').toUpper(); } this->callback(scId, "\"" + res + "\""); } void File::readAsDataURL(int scId, int ecId, const QString &uri, int sliceStart, int sliceEnd) { QPair f1 = resolveURI(ecId, uri); if (!f1.first) return; QFile file(f1.second.absoluteFilePath()); QFileInfo &fileInfo = f1.second; if (!file.exists()) { this->callback(ecId, FileError::kNotReadableErr); return; } if (!file.open(QIODevice::ReadOnly)) { this->callback(ecId, FileError::kNotReadableErr); return; } QByteArray content = file.readAll(); QString contentType(_db.mimeTypeForFile(fileInfo.fileName()).name()); if (sliceEnd == -1) sliceEnd = content.size(); if (sliceEnd < 0) { sliceEnd++; sliceEnd = std::max(0, content.size() + sliceEnd); } if (sliceEnd > content.size()) sliceEnd = content.size(); if (sliceStart < 0) sliceStart = std::max(0, content.size() + sliceStart); if (sliceStart > content.size()) sliceStart = content.size(); if (sliceStart > sliceEnd) sliceEnd = sliceStart; content = content.mid(sliceStart, sliceEnd - sliceStart); this->cb(scId, QString("data:%1;base64,").arg(contentType) + content.toBase64()); } bool File::rmDir(const QDir &dir) { if (dir == _persistentDir || dir == QDir::temp()) {//can't remove root dir return false; } bool result = true; if (dir.exists()) { // Iterate over entries and remove them Q_FOREACH(const QFileInfo &fileInfo, dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)) { if (fileInfo.isDir()) { result = rmDir(fileInfo.absoluteFilePath()); } else { result = QFile::remove(fileInfo.absoluteFilePath()); } if (!result) { return result; } } // Finally remove the current dir return dir.rmdir(dir.absolutePath()); } return result; } bool File::copyFile(int scId, int ecId,const QString& sourceUri, const QString& destinationUri, const QString& newName) { QPair destDir = resolveURI(ecId, destinationUri); QPair sourceFile = resolveURI(ecId, sourceUri); if (!destDir.first || !sourceFile.first) return false; if (!checkFileName(newName)) { this->callback(ecId, FileError::kEncodingErr); return false; } if (destDir.second.isFile()) { this->callback(ecId, FileError::kInvalidModificationErr); return false; } if (!destDir.second.isDir()) { this->callback(ecId, FileError::kNotFoundErr); return false; } QFileInfo &fileInfo = sourceFile.second; QString fileName((newName.isEmpty()) ? fileInfo.fileName() : newName); QString destinationFile(QDir(destDir.second.absoluteFilePath()).filePath(fileName)); if (QFile::copy(fileInfo.absoluteFilePath(), destinationFile)){ this->cb(scId, file2map(QFileInfo(destinationFile))); return true; } this->callback(ecId, FileError::kInvalidModificationErr); return false; } void File::copyDir(int scId, int ecId,const QString& sourceUri, const QString& destinationUri, const QString& newName) { QPair destDir = resolveURI(ecId, destinationUri); QPair sourceDir = resolveURI(ecId, sourceUri); if (!destDir.first || !sourceDir.first) return; if (!checkFileName(newName)) { this->callback(ecId, FileError::kEncodingErr); return; } QString targetName = ((newName.isEmpty()) ? sourceDir.second.fileName() : newName); QString target(QDir(destDir.second.absoluteFilePath()).filePath(targetName)); if (QFileInfo(target).isFile()){ this->callback(ecId, FileError::kInvalidModificationErr); return; } // check: copy directory into itself if (QDir(sourceDir.second.absoluteFilePath()).relativeFilePath(target)[0] != '.'){ this->callback(ecId, FileError::kInvalidModificationErr); return; } if (!QDir(target).exists()){ QDir(destDir.second.absoluteFilePath()).mkdir(target);; } else{ this->callback(ecId, FileError::kInvalidModificationErr); return; } if (copyFolder(sourceDir.second.absoluteFilePath(), target)){ this->cb(scId, dir2map(QDir(target))); return; } this->callback(ecId, FileError::kInvalidModificationErr); return; } void File::copyTo(int scId, int ecId, const QString& source, const QString& destinationDir, const QString& newName) { QPair f1 = resolveURI(ecId, source); if (!f1.first) return; if (f1.second.isDir()) copyDir(scId, ecId, source, destinationDir, newName); else copyFile(scId, ecId, source, destinationDir, newName); } void File::moveFile(int scId, int ecId,const QString& sourceUri, const QString& destinationUri, const QString& newName) { QPair sourceFile = resolveURI(ecId, sourceUri); QPair destDir = resolveURI(ecId, destinationUri); if (!destDir.first || !sourceFile.first) return; if (!checkFileName(newName)) { this->callback(ecId, FileError::kEncodingErr); return; } QString fileName = ((newName.isEmpty()) ? sourceFile.second.fileName() : newName); QString target = QDir(destDir.second.absoluteFilePath()).filePath(fileName); if (sourceFile.second == QFileInfo(target)) { this->callback(ecId, FileError::kInvalidModificationErr); return; } if (!destDir.second.exists()) { this->callback(ecId, FileError::kNotFoundErr); return; } if (!destDir.second.isDir()){ this->callback(ecId, FileError::kInvalidModificationErr); return; } if (QFileInfo(target).exists()) { if (!QFile::remove(target)) { this->callback(ecId, FileError::kInvalidModificationErr); return; } } QFile::rename(sourceFile.second.absoluteFilePath(), target); this->cb(scId, file2map(QFileInfo(target))); } void File::moveDir(int scId, int ecId,const QString& sourceUri, const QString& destinationUri, const QString& newName){ QPair sourceDir = resolveURI(ecId, sourceUri); QPair destDir = resolveURI(ecId, destinationUri); if (!destDir.first || !sourceDir.first) return; if (!checkFileName(newName)) { this->callback(ecId, FileError::kEncodingErr); return; } QString fileName = ((newName.isEmpty()) ? sourceDir.second.fileName() : newName); QString target = QDir(destDir.second.absoluteFilePath()).filePath(fileName); if (!destDir.second.exists()){ this->callback(ecId, FileError::kNotFoundErr); return; } if (destDir.second.isFile()){ this->callback(ecId, FileError::kInvalidModificationErr); return; } // check: copy directory into itself if (QDir(sourceDir.second.absoluteFilePath()).relativeFilePath(target)[0] != '.'){ this->callback(ecId, FileError::kInvalidModificationErr); return; } if (QFileInfo(target).exists() && !QDir(destDir.second.absoluteFilePath()).rmdir(fileName)) { this->callback(ecId, FileError::kInvalidModificationErr); return; } if (copyFolder(sourceDir.second.absoluteFilePath(), target)) { rmDir(sourceDir.second.absoluteFilePath()); this->cb(scId, file2map(QFileInfo(target))); } else { this->callback(ecId, FileError::kNoModificationAllowedErr); } } void File::moveTo(int scId, int ecId, const QString& source, const QString& destinationDir, const QString& newName) { QPair f1 = resolveURI(ecId, source); if (!f1.first) return; if (f1.second.isDir()) moveDir(scId, ecId, source, destinationDir, newName); else moveFile(scId, ecId, source, destinationDir, newName); } bool File::copyFolder(const QString& sourceFolder, const QString& destFolder) { QDir sourceDir(sourceFolder); if (!sourceDir.exists()) return false; QDir destDir(destFolder); if (!destDir.exists()){ destDir.mkdir(destFolder); } QStringList files = sourceDir.entryList(QDir::Files); for (int i = 0; i< files.count(); i++) { QString srcName = sourceFolder + "/" + files[i]; QString destName = destFolder + "/" + files[i]; QFile::copy(srcName, destName); } files.clear(); files = sourceDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); for (int i = 0; i< files.count(); i++) { QString srcName = sourceFolder + "/" + files[i]; QString destName = destFolder + "/" + files[i]; copyFolder(srcName, destName); } return true; }