| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305 |
- /*
- * This software is in the public domain, furnished "as is", without technical
- * support, and with no warranty, express or implied, as to its usefulness for
- * any purpose.
- *
- */
- #include "syncenginetestutils.h"
- #include "httplogger.h"
- #include "accessmanager.h"
- #include <QJsonDocument>
- #include <QJsonArray>
- #include <QJsonObject>
- #include <QJsonValue>
- #include <memory>
- PathComponents::PathComponents(const char *path)
- : PathComponents { QString::fromUtf8(path) }
- {
- }
- PathComponents::PathComponents(const QString &path)
- : QStringList { path.split(QLatin1Char('/'), Qt::SkipEmptyParts) }
- {
- }
- PathComponents::PathComponents(const QStringList &pathComponents)
- : QStringList { pathComponents }
- {
- }
- PathComponents PathComponents::parentDirComponents() const
- {
- return PathComponents { mid(0, size() - 1) };
- }
- PathComponents PathComponents::subComponents() const &
- {
- return PathComponents { mid(1) };
- }
- void DiskFileModifier::remove(const QString &relativePath)
- {
- QFileInfo fi { _rootDir.filePath(relativePath) };
- if (fi.isFile())
- QVERIFY(_rootDir.remove(relativePath));
- else
- QVERIFY(QDir { fi.filePath() }.removeRecursively());
- }
- void DiskFileModifier::insert(const QString &relativePath, qint64 size, char contentChar)
- {
- QFile file { _rootDir.filePath(relativePath) };
- QVERIFY(!file.exists());
- file.open(QFile::WriteOnly);
- QByteArray buf(1024, contentChar);
- for (int x = 0; x < size / buf.size(); ++x) {
- file.write(buf);
- }
- file.write(buf.data(), size % buf.size());
- file.close();
- // Set the mtime 30 seconds in the past, for some tests that need to make sure that the mtime differs.
- OCC::FileSystem::setModTime(file.fileName(), OCC::Utility::qDateTimeToTime_t(QDateTime::currentDateTimeUtc().addSecs(-30)));
- QCOMPARE(file.size(), size);
- }
- void DiskFileModifier::setContents(const QString &relativePath, char contentChar)
- {
- QFile file { _rootDir.filePath(relativePath) };
- QVERIFY(file.exists());
- qint64 size = file.size();
- file.open(QFile::WriteOnly);
- file.write(QByteArray {}.fill(contentChar, size));
- }
- void DiskFileModifier::appendByte(const QString &relativePath)
- {
- QFile file { _rootDir.filePath(relativePath) };
- QVERIFY(file.exists());
- file.open(QFile::ReadWrite);
- QByteArray contents = file.read(1);
- file.seek(file.size());
- file.write(contents);
- }
- void DiskFileModifier::mkdir(const QString &relativePath)
- {
- _rootDir.mkpath(relativePath);
- }
- void DiskFileModifier::rename(const QString &from, const QString &to)
- {
- QVERIFY(_rootDir.exists(from));
- QVERIFY(_rootDir.rename(from, to));
- }
- void DiskFileModifier::setModTime(const QString &relativePath, const QDateTime &modTime)
- {
- OCC::FileSystem::setModTime(_rootDir.filePath(relativePath), OCC::Utility::qDateTimeToTime_t(modTime));
- }
- FileInfo FileInfo::A12_B12_C12_S12()
- {
- FileInfo fi { QString {}, {
- { QStringLiteral("A"), { { QStringLiteral("a1"), 4 }, { QStringLiteral("a2"), 4 } } },
- { QStringLiteral("B"), { { QStringLiteral("b1"), 16 }, { QStringLiteral("b2"), 16 } } },
- { QStringLiteral("C"), { { QStringLiteral("c1"), 24 }, { QStringLiteral("c2"), 24 } } },
- } };
- FileInfo sharedFolder { QStringLiteral("S"), { { QStringLiteral("s1"), 32 }, { QStringLiteral("s2"), 32 } } };
- sharedFolder.isShared = true;
- sharedFolder.children[QStringLiteral("s1")].isShared = true;
- sharedFolder.children[QStringLiteral("s2")].isShared = true;
- fi.children.insert(sharedFolder.name, std::move(sharedFolder));
- return fi;
- }
- FileInfo::FileInfo(const QString &name, const std::initializer_list<FileInfo> &children)
- : name { name }
- {
- for (const auto &source : children)
- addChild(source);
- }
- void FileInfo::addChild(const FileInfo &info)
- {
- auto &dest = this->children[info.name] = info;
- dest.parentPath = path();
- dest.fixupParentPathRecursively();
- }
- void FileInfo::remove(const QString &relativePath)
- {
- const PathComponents pathComponents { relativePath };
- FileInfo *parent = findInvalidatingEtags(pathComponents.parentDirComponents());
- Q_ASSERT(parent);
- parent->children.erase(std::find_if(parent->children.begin(), parent->children.end(),
- [&pathComponents](const FileInfo &fi) { return fi.name == pathComponents.fileName(); }));
- }
- void FileInfo::insert(const QString &relativePath, qint64 size, char contentChar)
- {
- create(relativePath, size, contentChar);
- }
- void FileInfo::setContents(const QString &relativePath, char contentChar)
- {
- FileInfo *file = findInvalidatingEtags(relativePath);
- Q_ASSERT(file);
- file->contentChar = contentChar;
- }
- void FileInfo::appendByte(const QString &relativePath)
- {
- FileInfo *file = findInvalidatingEtags(relativePath);
- Q_ASSERT(file);
- file->size += 1;
- }
- void FileInfo::mkdir(const QString &relativePath)
- {
- createDir(relativePath);
- }
- void FileInfo::rename(const QString &oldPath, const QString &newPath)
- {
- const PathComponents newPathComponents { newPath };
- FileInfo *dir = findInvalidatingEtags(newPathComponents.parentDirComponents());
- Q_ASSERT(dir);
- Q_ASSERT(dir->isDir);
- const PathComponents pathComponents { oldPath };
- FileInfo *parent = findInvalidatingEtags(pathComponents.parentDirComponents());
- Q_ASSERT(parent);
- FileInfo fi = parent->children.take(pathComponents.fileName());
- fi.parentPath = dir->path();
- fi.name = newPathComponents.fileName();
- fi.fixupParentPathRecursively();
- dir->children.insert(newPathComponents.fileName(), std::move(fi));
- }
- void FileInfo::setModTime(const QString &relativePath, const QDateTime &modTime)
- {
- FileInfo *file = findInvalidatingEtags(relativePath);
- Q_ASSERT(file);
- file->lastModified = modTime;
- }
- void FileInfo::setModTimeKeepEtag(const QString &relativePath, const QDateTime &modTime)
- {
- FileInfo *file = find(relativePath);
- Q_ASSERT(file);
- file->lastModified = modTime;
- }
- FileInfo *FileInfo::find(PathComponents pathComponents, const bool invalidateEtags)
- {
- if (pathComponents.isEmpty()) {
- if (invalidateEtags) {
- etag = generateEtag();
- }
- return this;
- }
- QString childName = pathComponents.pathRoot();
- auto it = children.find(childName);
- if (it != children.end()) {
- auto file = it->find(std::move(pathComponents).subComponents(), invalidateEtags);
- if (file && invalidateEtags) {
- // Update parents on the way back
- etag = generateEtag();
- }
- return file;
- }
- return nullptr;
- }
- FileInfo *FileInfo::createDir(const QString &relativePath)
- {
- const PathComponents pathComponents { relativePath };
- FileInfo *parent = findInvalidatingEtags(pathComponents.parentDirComponents());
- Q_ASSERT(parent);
- FileInfo &child = parent->children[pathComponents.fileName()] = FileInfo { pathComponents.fileName() };
- child.parentPath = parent->path();
- child.etag = generateEtag();
- return &child;
- }
- FileInfo *FileInfo::create(const QString &relativePath, qint64 size, char contentChar)
- {
- const PathComponents pathComponents { relativePath };
- FileInfo *parent = findInvalidatingEtags(pathComponents.parentDirComponents());
- Q_ASSERT(parent);
- FileInfo &child = parent->children[pathComponents.fileName()] = FileInfo { pathComponents.fileName(), size };
- child.parentPath = parent->path();
- child.contentChar = contentChar;
- child.etag = generateEtag();
- return &child;
- }
- bool FileInfo::operator==(const FileInfo &other) const
- {
- // Consider files to be equal between local<->remote as a user would.
- return name == other.name
- && isDir == other.isDir
- && size == other.size
- && contentChar == other.contentChar
- && children == other.children;
- }
- QString FileInfo::path() const
- {
- return (parentPath.isEmpty() ? QString() : (parentPath + QLatin1Char('/'))) + name;
- }
- QString FileInfo::absolutePath() const
- {
- if (parentPath.endsWith(QLatin1Char('/'))) {
- return parentPath + name;
- } else {
- return parentPath + QLatin1Char('/') + name;
- }
- }
- void FileInfo::fixupParentPathRecursively()
- {
- auto p = path();
- for (auto it = children.begin(); it != children.end(); ++it) {
- Q_ASSERT(it.key() == it->name);
- it->parentPath = p;
- it->fixupParentPathRecursively();
- }
- }
- FileInfo *FileInfo::findInvalidatingEtags(PathComponents pathComponents)
- {
- return find(std::move(pathComponents), true);
- }
- FakePropfindReply::FakePropfindReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent)
- : FakeReply { parent }
- {
- setRequest(request);
- setUrl(request.url());
- setOperation(op);
- open(QIODevice::ReadOnly);
- QString fileName = getFilePathFromUrl(request.url());
- Q_ASSERT(!fileName.isNull()); // for root, it should be empty
- const FileInfo *fileInfo = remoteRootFileInfo.find(fileName);
- if (!fileInfo) {
- QMetaObject::invokeMethod(this, "respond404", Qt::QueuedConnection);
- return;
- }
- const QString prefix = request.url().path().left(request.url().path().size() - fileName.size());
- // Don't care about the request and just return a full propfind
- const QString davUri { QStringLiteral("DAV:") };
- const QString ocUri { QStringLiteral("http://owncloud.org/ns") };
- const QString ncUri { QStringLiteral("http://nextcloud.org/ns") };
- QBuffer buffer { &payload };
- buffer.open(QIODevice::WriteOnly);
- QXmlStreamWriter xml(&buffer);
- xml.writeNamespace(davUri, QStringLiteral("d"));
- xml.writeNamespace(ocUri, QStringLiteral("oc"));
- xml.writeNamespace(ncUri, QStringLiteral("nc"));
- xml.writeStartDocument();
- xml.writeStartElement(davUri, QStringLiteral("multistatus"));
- auto writeFileResponse = [&](const FileInfo &fileInfo) {
- xml.writeStartElement(davUri, QStringLiteral("response"));
- auto url = QString::fromUtf8(QUrl::toPercentEncoding(fileInfo.absolutePath(), "/"));
- if (!url.endsWith(QChar('/'))) {
- url.append(QChar('/'));
- }
- const auto href = OCC::Utility::concatUrlPath(prefix, url).path();
- xml.writeTextElement(davUri, QStringLiteral("href"), href);
- xml.writeStartElement(davUri, QStringLiteral("propstat"));
- xml.writeStartElement(davUri, QStringLiteral("prop"));
- if (fileInfo.isDir) {
- xml.writeStartElement(davUri, QStringLiteral("resourcetype"));
- xml.writeEmptyElement(davUri, QStringLiteral("collection"));
- xml.writeEndElement(); // resourcetype
- } else
- xml.writeEmptyElement(davUri, QStringLiteral("resourcetype"));
- auto gmtDate = fileInfo.lastModified.toUTC();
- auto stringDate = QLocale::c().toString(gmtDate, QStringLiteral("ddd, dd MMM yyyy HH:mm:ss 'GMT'"));
- xml.writeTextElement(davUri, QStringLiteral("getlastmodified"), stringDate);
- xml.writeTextElement(davUri, QStringLiteral("getcontentlength"), QString::number(fileInfo.size));
- xml.writeTextElement(davUri, QStringLiteral("getetag"), QStringLiteral("\"%1\"").arg(QString::fromLatin1(fileInfo.etag)));
- xml.writeTextElement(ocUri, QStringLiteral("permissions"), !fileInfo.permissions.isNull() ? QString(fileInfo.permissions.toString()) : fileInfo.isShared ? QStringLiteral("SRDNVCKW") : QStringLiteral("RDNVCKW"));
- xml.writeTextElement(ocUri, QStringLiteral("id"), QString::fromUtf8(fileInfo.fileId));
- xml.writeTextElement(ocUri, QStringLiteral("checksums"), QString::fromUtf8(fileInfo.checksums));
- buffer.write(fileInfo.extraDavProperties);
- xml.writeEndElement(); // prop
- xml.writeTextElement(davUri, QStringLiteral("status"), QStringLiteral("HTTP/1.1 200 OK"));
- xml.writeEndElement(); // propstat
- xml.writeEndElement(); // response
- };
- writeFileResponse(*fileInfo);
- foreach (const FileInfo &childFileInfo, fileInfo->children)
- writeFileResponse(childFileInfo);
- xml.writeEndElement(); // multistatus
- xml.writeEndDocument();
- QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
- }
- void FakePropfindReply::respond()
- {
- setHeader(QNetworkRequest::ContentLengthHeader, payload.size());
- setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/xml; charset=utf-8"));
- setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 207);
- setFinished(true);
- emit metaDataChanged();
- if (bytesAvailable())
- emit readyRead();
- emit finished();
- }
- void FakePropfindReply::respond404()
- {
- setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 404);
- setError(InternalServerError, QStringLiteral("Not Found"));
- emit metaDataChanged();
- emit finished();
- }
- qint64 FakePropfindReply::bytesAvailable() const
- {
- return payload.size() + QIODevice::bytesAvailable();
- }
- qint64 FakePropfindReply::readData(char *data, qint64 maxlen)
- {
- qint64 len = std::min(qint64 { payload.size() }, maxlen);
- std::copy(payload.cbegin(), payload.cbegin() + len, data);
- payload.remove(0, static_cast<int>(len));
- return len;
- }
- FakePutReply::FakePutReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QByteArray &putPayload, QObject *parent)
- : FakeReply { parent }
- {
- setRequest(request);
- setUrl(request.url());
- setOperation(op);
- open(QIODevice::ReadOnly);
- fileInfo = perform(remoteRootFileInfo, request, putPayload);
- QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
- }
- FileInfo *FakePutReply::perform(FileInfo &remoteRootFileInfo, const QNetworkRequest &request, const QByteArray &putPayload)
- {
- QString fileName = getFilePathFromUrl(request.url());
- Q_ASSERT(!fileName.isEmpty());
- FileInfo *fileInfo = remoteRootFileInfo.find(fileName);
- if (fileInfo) {
- fileInfo->size = putPayload.size();
- fileInfo->contentChar = putPayload.at(0);
- } else {
- // Assume that the file is filled with the same character
- fileInfo = remoteRootFileInfo.create(fileName, putPayload.size(), putPayload.at(0));
- }
- fileInfo->lastModified = OCC::Utility::qDateTimeFromTime_t(request.rawHeader("X-OC-Mtime").toLongLong());
- remoteRootFileInfo.find(fileName, /*invalidateEtags=*/true);
- return fileInfo;
- }
- void FakePutReply::respond()
- {
- emit uploadProgress(fileInfo->size, fileInfo->size);
- setRawHeader("OC-ETag", fileInfo->etag);
- setRawHeader("ETag", fileInfo->etag);
- setRawHeader("OC-FileID", fileInfo->fileId);
- setRawHeader("X-OC-MTime", "accepted"); // Prevents Q_ASSERT(!_runningNow) since we'll call PropagateItemJob::done twice in that case.
- setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200);
- emit metaDataChanged();
- emit finished();
- }
- void FakePutReply::abort()
- {
- setError(OperationCanceledError, QStringLiteral("abort"));
- emit finished();
- }
- FakePutMultiFileReply::FakePutMultiFileReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QString &contentType, const QByteArray &putPayload, QObject *parent)
- : FakeReply { parent }
- {
- setRequest(request);
- setUrl(request.url());
- setOperation(op);
- open(QIODevice::ReadOnly);
- _allFileInfo = performMultiPart(remoteRootFileInfo, request, putPayload, contentType);
- QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
- }
- QVector<FileInfo *> FakePutMultiFileReply::performMultiPart(FileInfo &remoteRootFileInfo, const QNetworkRequest &request, const QByteArray &putPayload, const QString &contentType)
- {
- QVector<FileInfo *> result;
- auto stringPutPayload = QString::fromUtf8(putPayload);
- constexpr int boundaryPosition = sizeof("multipart/related; boundary=");
- const QString boundaryValue = QStringLiteral("--") + contentType.mid(boundaryPosition, contentType.length() - boundaryPosition - 1) + QStringLiteral("\r\n");
- auto stringPutPayloadRef = QString{stringPutPayload}.left(stringPutPayload.size() - 2 - boundaryValue.size());
- auto allParts = stringPutPayloadRef.split(boundaryValue, Qt::SkipEmptyParts);
- for (const auto &onePart : allParts) {
- auto headerEndPosition = onePart.indexOf(QStringLiteral("\r\n\r\n"));
- auto onePartHeaderPart = onePart.left(headerEndPosition);
- auto onePartBody = onePart.mid(headerEndPosition + 4, onePart.size() - headerEndPosition - 6);
- auto onePartHeaders = onePartHeaderPart.split(QStringLiteral("\r\n"));
- QMap<QString, QString> allHeaders;
- for(auto oneHeader : onePartHeaders) {
- auto headerParts = oneHeader.split(QStringLiteral(": "));
- allHeaders[headerParts.at(0)] = headerParts.at(1);
- }
- const auto fileName = allHeaders[QStringLiteral("X-File-Path")];
- const auto modtime = allHeaders[QByteArrayLiteral("X-File-Mtime")].toLongLong();
- Q_ASSERT(!fileName.isEmpty());
- Q_ASSERT(modtime > 0);
- FileInfo *fileInfo = remoteRootFileInfo.find(fileName);
- if (fileInfo) {
- fileInfo->size = onePartBody.size();
- fileInfo->contentChar = onePartBody.at(0).toLatin1();
- } else {
- // Assume that the file is filled with the same character
- fileInfo = remoteRootFileInfo.create(fileName, onePartBody.size(), onePartBody.at(0).toLatin1());
- }
- fileInfo->lastModified = OCC::Utility::qDateTimeFromTime_t(request.rawHeader("X-OC-Mtime").toLongLong());
- remoteRootFileInfo.find(fileName, /*invalidateEtags=*/true);
- result.push_back(fileInfo);
- }
- return result;
- }
- void FakePutMultiFileReply::respond()
- {
- QJsonDocument reply;
- QJsonObject allFileInfoReply;
- qint64 totalSize = 0;
- std::for_each(_allFileInfo.begin(), _allFileInfo.end(), [&totalSize](const auto &fileInfo) {
- totalSize += fileInfo->size;
- });
- for(auto fileInfo : qAsConst(_allFileInfo)) {
- QJsonObject fileInfoReply;
- fileInfoReply.insert("error", QStringLiteral("false"));
- fileInfoReply.insert("etag", QLatin1String{fileInfo->etag});
- emit uploadProgress(fileInfo->size, totalSize);
- allFileInfoReply.insert(QChar('/') + fileInfo->path(), fileInfoReply);
- }
- reply.setObject(allFileInfoReply);
- _payload = reply.toJson();
- setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200);
- setFinished(true);
- if (bytesAvailable()) {
- emit readyRead();
- }
- emit metaDataChanged();
- emit finished();
- }
- void FakePutMultiFileReply::abort()
- {
- setError(OperationCanceledError, QStringLiteral("abort"));
- emit finished();
- }
- qint64 FakePutMultiFileReply::bytesAvailable() const
- {
- return _payload.size() + QIODevice::bytesAvailable();
- }
- qint64 FakePutMultiFileReply::readData(char *data, qint64 maxlen)
- {
- qint64 len = std::min(qint64 { _payload.size() }, maxlen);
- std::copy(_payload.cbegin(), _payload.cbegin() + len, data);
- _payload.remove(0, static_cast<int>(len));
- return len;
- }
- FakeMkcolReply::FakeMkcolReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent)
- : FakeReply { parent }
- {
- setRequest(request);
- setUrl(request.url());
- setOperation(op);
- open(QIODevice::ReadOnly);
- QString fileName = getFilePathFromUrl(request.url());
- Q_ASSERT(!fileName.isEmpty());
- fileInfo = remoteRootFileInfo.createDir(fileName);
- if (!fileInfo) {
- abort();
- return;
- }
- QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
- }
- void FakeMkcolReply::respond()
- {
- setRawHeader("OC-FileId", fileInfo->fileId);
- setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 201);
- emit metaDataChanged();
- emit finished();
- }
- FakeDeleteReply::FakeDeleteReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent)
- : FakeReply { parent }
- {
- setRequest(request);
- setUrl(request.url());
- setOperation(op);
- open(QIODevice::ReadOnly);
- QString fileName = getFilePathFromUrl(request.url());
- Q_ASSERT(!fileName.isEmpty());
- remoteRootFileInfo.remove(fileName);
- QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
- }
- void FakeDeleteReply::respond()
- {
- setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 204);
- emit metaDataChanged();
- emit finished();
- }
- FakeMoveReply::FakeMoveReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent)
- : FakeReply { parent }
- {
- setRequest(request);
- setUrl(request.url());
- setOperation(op);
- open(QIODevice::ReadOnly);
- QString fileName = getFilePathFromUrl(request.url());
- Q_ASSERT(!fileName.isEmpty());
- QString dest = getFilePathFromUrl(QUrl::fromEncoded(request.rawHeader("Destination")));
- Q_ASSERT(!dest.isEmpty());
- remoteRootFileInfo.rename(fileName, dest);
- QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
- }
- void FakeMoveReply::respond()
- {
- setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 201);
- emit metaDataChanged();
- emit finished();
- }
- FakeGetReply::FakeGetReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent)
- : FakeReply { parent }
- {
- setRequest(request);
- setUrl(request.url());
- setOperation(op);
- open(QIODevice::ReadOnly);
- QString fileName = getFilePathFromUrl(request.url());
- Q_ASSERT(!fileName.isEmpty());
- fileInfo = remoteRootFileInfo.find(fileName);
- if (!fileInfo) {
- qDebug() << "meh;";
- }
- Q_ASSERT_X(fileInfo, Q_FUNC_INFO, "Could not find file on the remote");
- QMetaObject::invokeMethod(this, &FakeGetReply::respond, Qt::QueuedConnection);
- }
- void FakeGetReply::respond()
- {
- if (aborted) {
- setError(OperationCanceledError, QStringLiteral("Operation Canceled"));
- emit metaDataChanged();
- emit finished();
- return;
- }
- payload = fileInfo->contentChar;
- size = fileInfo->size;
- setHeader(QNetworkRequest::ContentLengthHeader, size);
- setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200);
- setRawHeader("OC-ETag", fileInfo->etag);
- setRawHeader("ETag", fileInfo->etag);
- setRawHeader("OC-FileId", fileInfo->fileId);
- emit metaDataChanged();
- if (bytesAvailable())
- emit readyRead();
- emit finished();
- }
- void FakeGetReply::abort()
- {
- setError(OperationCanceledError, QStringLiteral("Operation Canceled"));
- aborted = true;
- }
- qint64 FakeGetReply::bytesAvailable() const
- {
- if (aborted)
- return 0;
- return size + QIODevice::bytesAvailable();
- }
- qint64 FakeGetReply::readData(char *data, qint64 maxlen)
- {
- qint64 len = std::min(qint64 { size }, maxlen);
- std::fill_n(data, len, payload);
- size -= len;
- return len;
- }
- FakeGetWithDataReply::FakeGetWithDataReply(FileInfo &remoteRootFileInfo, const QByteArray &data, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent)
- : FakeReply { parent }
- {
- setRequest(request);
- setUrl(request.url());
- setOperation(op);
- open(QIODevice::ReadOnly);
- Q_ASSERT(!data.isEmpty());
- payload = data;
- QString fileName = getFilePathFromUrl(request.url());
- Q_ASSERT(!fileName.isEmpty());
- fileInfo = remoteRootFileInfo.find(fileName);
- QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
- if (request.hasRawHeader("Range")) {
- const QString range = QString::fromUtf8(request.rawHeader("Range"));
- const QRegularExpression bytesPattern(QStringLiteral("bytes=(?<start>\\d+)-(?<end>\\d+)"));
- const QRegularExpressionMatch match = bytesPattern.match(range);
- if (match.hasMatch()) {
- const int start = match.captured(QStringLiteral("start")).toInt();
- const int end = match.captured(QStringLiteral("end")).toInt();
- payload = payload.mid(start, end - start + 1);
- }
- }
- }
- void FakeGetWithDataReply::respond()
- {
- if (aborted) {
- setError(OperationCanceledError, QStringLiteral("Operation Canceled"));
- emit metaDataChanged();
- emit finished();
- return;
- }
- setHeader(QNetworkRequest::ContentLengthHeader, payload.size());
- setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200);
- setRawHeader("OC-ETag", fileInfo->etag);
- setRawHeader("ETag", fileInfo->etag);
- setRawHeader("OC-FileId", fileInfo->fileId);
- emit metaDataChanged();
- if (bytesAvailable())
- emit readyRead();
- emit finished();
- }
- void FakeGetWithDataReply::abort()
- {
- setError(OperationCanceledError, QStringLiteral("Operation Canceled"));
- aborted = true;
- }
- qint64 FakeGetWithDataReply::bytesAvailable() const
- {
- if (aborted)
- return 0;
- return payload.size() - offset + QIODevice::bytesAvailable();
- }
- qint64 FakeGetWithDataReply::readData(char *data, qint64 maxlen)
- {
- qint64 len = std::min(payload.size() - offset, quint64(maxlen));
- std::memcpy(data, payload.constData() + offset, len);
- offset += len;
- return len;
- }
- FakeChunkMoveReply::FakeChunkMoveReply(FileInfo &uploadsFileInfo, FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent)
- : FakeReply { parent }
- {
- setRequest(request);
- setUrl(request.url());
- setOperation(op);
- open(QIODevice::ReadOnly);
- fileInfo = perform(uploadsFileInfo, remoteRootFileInfo, request);
- if (!fileInfo) {
- QTimer::singleShot(0, this, &FakeChunkMoveReply::respondPreconditionFailed);
- } else {
- QTimer::singleShot(0, this, &FakeChunkMoveReply::respond);
- }
- }
- FileInfo *FakeChunkMoveReply::perform(FileInfo &uploadsFileInfo, FileInfo &remoteRootFileInfo, const QNetworkRequest &request)
- {
- QString source = getFilePathFromUrl(request.url());
- Q_ASSERT(!source.isEmpty());
- Q_ASSERT(source.endsWith(QLatin1String("/.file")));
- source = source.left(source.length() - static_cast<int>(qstrlen("/.file")));
- auto sourceFolder = uploadsFileInfo.find(source);
- Q_ASSERT(sourceFolder);
- Q_ASSERT(sourceFolder->isDir);
- int count = 0;
- qlonglong size = 0;
- char payload = '\0';
- QString fileName = getFilePathFromUrl(QUrl::fromEncoded(request.rawHeader("Destination")));
- Q_ASSERT(!fileName.isEmpty());
- // Compute the size and content from the chunks if possible
- for (auto chunkName : sourceFolder->children.keys()) {
- auto &x = sourceFolder->children[chunkName];
- Q_ASSERT(!x.isDir);
- Q_ASSERT(x.size > 0); // There should not be empty chunks
- size += x.size;
- Q_ASSERT(!payload || payload == x.contentChar);
- payload = x.contentChar;
- ++count;
- }
- Q_ASSERT(sourceFolder->children.count() == count); // There should not be holes or extra files
- // NOTE: This does not actually assemble the file data from the chunks!
- FileInfo *fileInfo = remoteRootFileInfo.find(fileName);
- if (fileInfo) {
- // The client should put this header
- Q_ASSERT(request.hasRawHeader("If"));
- // And it should condition on the destination file
- auto start = QByteArray("<" + request.rawHeader("Destination") + ">");
- Q_ASSERT(request.rawHeader("If").startsWith(start));
- if (request.rawHeader("If") != start + " ([\"" + fileInfo->etag + "\"])") {
- return nullptr;
- }
- fileInfo->size = size;
- fileInfo->contentChar = payload;
- } else {
- Q_ASSERT(!request.hasRawHeader("If"));
- // Assume that the file is filled with the same character
- fileInfo = remoteRootFileInfo.create(fileName, size, payload);
- }
- fileInfo->lastModified = OCC::Utility::qDateTimeFromTime_t(request.rawHeader("X-OC-Mtime").toLongLong());
- remoteRootFileInfo.find(fileName, /*invalidateEtags=*/true);
- return fileInfo;
- }
- void FakeChunkMoveReply::respond()
- {
- setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 201);
- setRawHeader("OC-ETag", fileInfo->etag);
- setRawHeader("ETag", fileInfo->etag);
- setRawHeader("OC-FileId", fileInfo->fileId);
- emit metaDataChanged();
- emit finished();
- }
- void FakeChunkMoveReply::respondPreconditionFailed()
- {
- setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 412);
- setError(InternalServerError, QStringLiteral("Precondition Failed"));
- emit metaDataChanged();
- emit finished();
- }
- void FakeChunkMoveReply::abort()
- {
- setError(OperationCanceledError, QStringLiteral("abort"));
- emit finished();
- }
- FakePayloadReply::FakePayloadReply(QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QByteArray &body, QObject *parent)
- : FakePayloadReply(op, request, body, FakePayloadReply::defaultDelay, parent)
- {
- }
- FakePayloadReply::FakePayloadReply(
- QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QByteArray &body, int delay, QObject *parent)
- : FakeReply{parent}
- , _body(body)
- {
- setRequest(request);
- setUrl(request.url());
- setOperation(op);
- open(QIODevice::ReadOnly);
- QTimer::singleShot(delay, this, &FakePayloadReply::respond);
- }
- void FakePayloadReply::respond()
- {
- setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200);
- setHeader(QNetworkRequest::ContentLengthHeader, _body.size());
- for (auto it = _additionalHeaders.constKeyValueBegin(); it != _additionalHeaders.constKeyValueEnd(); ++it) {
- setHeader(it->first, it->second);
- }
- emit metaDataChanged();
- emit readyRead();
- setFinished(true);
- emit finished();
- }
- qint64 FakePayloadReply::readData(char *buf, qint64 max)
- {
- max = qMin<qint64>(max, _body.size());
- memcpy(buf, _body.constData(), max);
- _body = _body.mid(max);
- return max;
- }
- qint64 FakePayloadReply::bytesAvailable() const
- {
- return _body.size();
- }
- FakeErrorReply::FakeErrorReply(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent, int httpErrorCode, const QByteArray &body)
- : FakeReply { parent }
- , _body(body)
- {
- setRequest(request);
- setUrl(request.url());
- setOperation(op);
- open(QIODevice::ReadOnly);
- setAttribute(QNetworkRequest::HttpStatusCodeAttribute, httpErrorCode);
- setError(InternalServerError, QStringLiteral("Internal Server Fake Error"));
- QMetaObject::invokeMethod(this, &FakeErrorReply::respond, Qt::QueuedConnection);
- }
- void FakeErrorReply::respond()
- {
- emit metaDataChanged();
- emit readyRead();
- // finishing can come strictly after readyRead was called
- QTimer::singleShot(5, this, &FakeErrorReply::slotSetFinished);
- }
- void FakeErrorReply::slotSetFinished()
- {
- setFinished(true);
- emit finished();
- }
- qint64 FakeErrorReply::readData(char *buf, qint64 max)
- {
- max = qMin<qint64>(max, _body.size());
- memcpy(buf, _body.constData(), max);
- _body = _body.mid(max);
- return max;
- }
- qint64 FakeErrorReply::bytesAvailable() const
- {
- return _body.size();
- }
- FakeHangingReply::FakeHangingReply(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent)
- : FakeReply(parent)
- {
- setRequest(request);
- setUrl(request.url());
- setOperation(op);
- open(QIODevice::ReadOnly);
- }
- void FakeHangingReply::abort()
- {
- // Follow more or less the implementation of QNetworkReplyImpl::abort
- close();
- setError(OperationCanceledError, tr("Operation canceled"));
- emit errorOccurred(OperationCanceledError);
- setFinished(true);
- emit finished();
- }
- FakeQNAM::FakeQNAM(FileInfo initialRoot)
- : _remoteRootFileInfo { std::move(initialRoot) }
- {
- setCookieJar(new OCC::CookieJar);
- }
- QJsonObject FakeQNAM::forEachReplyPart(QIODevice *outgoingData,
- const QString &contentType,
- std::function<QJsonObject (const QMap<QString, QByteArray> &)> replyFunction)
- {
- auto fullReply = QJsonObject{};
- auto putPayload = outgoingData->peek(outgoingData->bytesAvailable());
- outgoingData->reset();
- auto stringPutPayload = QString::fromUtf8(putPayload);
- constexpr int boundaryPosition = sizeof("multipart/related; boundary=");
- const QString boundaryValue = QStringLiteral("--") + contentType.mid(boundaryPosition, contentType.length() - boundaryPosition - 1) + QStringLiteral("\r\n");
- auto stringPutPayloadRef = QString{stringPutPayload}.left(stringPutPayload.size() - 2 - boundaryValue.size());
- auto allParts = stringPutPayloadRef.split(boundaryValue, Qt::SkipEmptyParts);
- for (const auto &onePart : qAsConst(allParts)) {
- auto headerEndPosition = onePart.indexOf(QStringLiteral("\r\n\r\n"));
- auto onePartHeaderPart = onePart.left(headerEndPosition);
- auto onePartHeaders = onePartHeaderPart.split(QStringLiteral("\r\n"));
- QMap<QString, QByteArray> allHeaders;
- for(const auto &oneHeader : qAsConst(onePartHeaders)) {
- auto headerParts = oneHeader.split(QStringLiteral(": "));
- allHeaders[headerParts.at(0)] = headerParts.at(1).toLatin1();
- }
- auto reply = replyFunction(allHeaders);
- if (reply.contains(QStringLiteral("error")) &&
- reply.contains(QStringLiteral("etag"))) {
- fullReply.insert(allHeaders[QStringLiteral("X-File-Path")], reply);
- }
- }
- return fullReply;
- }
- QNetworkReply *FakeQNAM::createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData)
- {
- QNetworkReply *reply = nullptr;
- auto newRequest = request;
- newRequest.setRawHeader("X-Request-ID", OCC::AccessManager::generateRequestId());
- auto contentType = request.header(QNetworkRequest::ContentTypeHeader).toString();
- if (_override) {
- if (auto _reply = _override(op, newRequest, outgoingData)) {
- reply = _reply;
- }
- }
- if (!reply) {
- reply = overrideReplyWithError(getFilePathFromUrl(newRequest.url()), op, newRequest);
- }
- if (!reply) {
- const bool isUpload = newRequest.url().path().startsWith(sUploadUrl.path());
- FileInfo &info = isUpload ? _uploadFileInfo : _remoteRootFileInfo;
- auto verb = newRequest.attribute(QNetworkRequest::CustomVerbAttribute);
- if (verb == QLatin1String("PROPFIND")) {
- // Ignore outgoingData always returning somethign good enough, works for now.
- reply = new FakePropfindReply { info, op, newRequest, this };
- } else if (verb == QLatin1String("GET") || op == QNetworkAccessManager::GetOperation) {
- reply = new FakeGetReply { info, op, newRequest, this };
- } else if (verb == QLatin1String("PUT") || op == QNetworkAccessManager::PutOperation) {
- if (request.hasRawHeader(QByteArrayLiteral("X-OC-Mtime")) &&
- request.rawHeader(QByteArrayLiteral("X-OC-Mtime")).toLongLong() <= 0) {
- reply = new FakeErrorReply { op, request, this, 500 };
- } else {
- reply = new FakePutReply { info, op, newRequest, outgoingData->readAll(), this };
- }
- } else if (verb == QLatin1String("MKCOL")) {
- reply = new FakeMkcolReply { info, op, newRequest, this };
- } else if (verb == QLatin1String("DELETE") || op == QNetworkAccessManager::DeleteOperation) {
- reply = new FakeDeleteReply { info, op, newRequest, this };
- } else if (verb == QLatin1String("MOVE") && !isUpload) {
- reply = new FakeMoveReply { info, op, newRequest, this };
- } else if (verb == QLatin1String("MOVE") && isUpload) {
- reply = new FakeChunkMoveReply { info, _remoteRootFileInfo, op, newRequest, this };
- } else if (verb == QLatin1String("POST") || op == QNetworkAccessManager::PostOperation) {
- if (contentType.startsWith(QStringLiteral("multipart/related; boundary="))) {
- reply = new FakePutMultiFileReply { info, op, newRequest, contentType, outgoingData->readAll(), this };
- }
- } else if (verb == QLatin1String("LOCK") || verb == QLatin1String("UNLOCK")) {
- reply = new FakeFileLockReply{info, op, newRequest, this};
- } else {
- qDebug() << verb << outgoingData;
- Q_UNREACHABLE();
- }
- }
- OCC::HttpLogger::logRequest(reply, op, outgoingData);
- return reply;
- }
- QNetworkReply * FakeQNAM::overrideReplyWithError(QString fileName, QNetworkAccessManager::Operation op, QNetworkRequest newRequest)
- {
- QNetworkReply *reply = nullptr;
- Q_ASSERT(!fileName.isNull());
- if (_errorPaths.contains(fileName)) {
- reply = new FakeErrorReply { op, newRequest, this, _errorPaths[fileName] };
- }
- return reply;
- }
- FakeFolder::FakeFolder(const FileInfo &fileTemplate, const OCC::Optional<FileInfo> &localFileInfo, const QString &remotePath)
- : _localModifier(_tempDir.path())
- {
- // Needs to be done once
- OCC::SyncEngine::minimumFileAgeForUpload = std::chrono::milliseconds(0);
- OCC::Logger::instance()->setLogFile(QStringLiteral("-"));
- OCC::Logger::instance()->addLogRule({ QStringLiteral("sync.httplogger=true") });
- QDir rootDir { _tempDir.path() };
- qDebug() << "FakeFolder operating on" << rootDir;
- if (localFileInfo) {
- toDisk(rootDir, *localFileInfo);
- } else {
- toDisk(rootDir, fileTemplate);
- }
- _fakeQnam = new FakeQNAM(fileTemplate);
- _account = OCC::Account::create();
- _account->setUrl(QUrl(QStringLiteral("http://admin:admin@localhost/owncloud")));
- _account->setCredentials(new FakeCredentials { _fakeQnam });
- _account->setDavDisplayName(QStringLiteral("fakename"));
- _account->setServerVersion(QStringLiteral("10.0.0"));
- _journalDb = std::make_unique<OCC::SyncJournalDb>(localPath() + QStringLiteral(".sync_test.db"));
- _syncEngine = std::make_unique<OCC::SyncEngine>(_account, localPath(), OCC::SyncOptions{}, remotePath, _journalDb.get());
- // Ignore temporary files from the download. (This is in the default exclude list, but we don't load it)
- _syncEngine->excludedFiles().addManualExclude(QStringLiteral("]*.~*"));
- // handle aboutToRemoveAllFiles with a timeout in case our test does not handle it
- QObject::connect(_syncEngine.get(), &OCC::SyncEngine::aboutToRemoveAllFiles, _syncEngine.get(), [this](OCC::SyncFileItem::Direction, std::function<void(bool)> callback) {
- QTimer::singleShot(1 * 1000, _syncEngine.get(), [callback] {
- callback(false);
- });
- });
- // Ensure we have a valid VfsOff instance "running"
- switchToVfs(_syncEngine->syncOptions()._vfs);
- // A new folder will update the local file state database on first sync.
- // To have a state matching what users will encounter, we have to a sync
- // using an identical local/remote file tree first.
- ENFORCE(syncOnce());
- }
- void FakeFolder::switchToVfs(QSharedPointer<OCC::Vfs> vfs)
- {
- auto opts = _syncEngine->syncOptions();
- opts._vfs->stop();
- QObject::disconnect(_syncEngine.get(), nullptr, opts._vfs.data(), nullptr);
- opts._vfs = vfs;
- _syncEngine->setSyncOptions(opts);
- OCC::VfsSetupParams vfsParams;
- vfsParams.filesystemPath = localPath();
- vfsParams.remotePath = QLatin1Char('/');
- vfsParams.account = _account;
- vfsParams.journal = _journalDb.get();
- vfsParams.providerName = QStringLiteral("OC-TEST");
- vfsParams.providerVersion = QStringLiteral("0.1");
- QObject::connect(_syncEngine.get(), &QObject::destroyed, vfs.data(), [vfs]() {
- vfs->stop();
- vfs->unregisterFolder();
- });
- vfs->start(vfsParams);
- }
- FileInfo FakeFolder::currentLocalState()
- {
- QDir rootDir { _tempDir.path() };
- FileInfo rootTemplate;
- fromDisk(rootDir, rootTemplate);
- rootTemplate.fixupParentPathRecursively();
- return rootTemplate;
- }
- QString FakeFolder::localPath() const
- {
- // SyncEngine wants a trailing slash
- if (_tempDir.path().endsWith(QLatin1Char('/')))
- return _tempDir.path();
- return _tempDir.path() + QLatin1Char('/');
- }
- void FakeFolder::scheduleSync()
- {
- // Have to be done async, else, an error before exec() does not terminate the event loop.
- QMetaObject::invokeMethod(_syncEngine.get(), "startSync", Qt::QueuedConnection);
- }
- void FakeFolder::execUntilBeforePropagation()
- {
- QSignalSpy spy(_syncEngine.get(), SIGNAL(aboutToPropagate(SyncFileItemVector &)));
- QVERIFY(spy.wait());
- }
- void FakeFolder::execUntilItemCompleted(const QString &relativePath)
- {
- QSignalSpy spy(_syncEngine.get(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
- QElapsedTimer t;
- t.start();
- while (t.elapsed() < 5000) {
- spy.clear();
- QVERIFY(spy.wait());
- for (const QList<QVariant> &args : spy) {
- auto item = args[0].value<OCC::SyncFileItemPtr>();
- if (item->destination() == relativePath)
- return;
- }
- }
- QVERIFY(false);
- }
- void FakeFolder::toDisk(QDir &dir, const FileInfo &templateFi)
- {
- foreach (const FileInfo &child, templateFi.children) {
- if (child.isDir) {
- QDir subDir(dir);
- dir.mkdir(child.name);
- subDir.cd(child.name);
- toDisk(subDir, child);
- } else {
- QFile file { dir.filePath(child.name) };
- file.open(QFile::WriteOnly);
- file.write(QByteArray {}.fill(child.contentChar, child.size));
- file.close();
- OCC::FileSystem::setModTime(file.fileName(), OCC::Utility::qDateTimeToTime_t(child.lastModified));
- }
- }
- }
- void FakeFolder::fromDisk(QDir &dir, FileInfo &templateFi)
- {
- foreach (const QFileInfo &diskChild, dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)) {
- if (diskChild.isDir()) {
- QDir subDir = dir;
- subDir.cd(diskChild.fileName());
- FileInfo &subFi = templateFi.children[diskChild.fileName()] = FileInfo { diskChild.fileName() };
- fromDisk(subDir, subFi);
- } else {
- QFile f { diskChild.filePath() };
- f.open(QFile::ReadOnly);
- auto content = f.read(1);
- if (content.size() == 0) {
- qWarning() << "Empty file at:" << diskChild.filePath();
- continue;
- }
- char contentChar = content.at(0);
- templateFi.children.insert(diskChild.fileName(), FileInfo { diskChild.fileName(), diskChild.size(), contentChar });
- }
- }
- }
- static FileInfo &findOrCreateDirs(FileInfo &base, PathComponents components)
- {
- if (components.isEmpty())
- return base;
- auto childName = components.pathRoot();
- auto it = base.children.find(childName);
- if (it != base.children.end()) {
- return findOrCreateDirs(*it, components.subComponents());
- }
- auto &newDir = base.children[childName] = FileInfo { childName };
- newDir.parentPath = base.path();
- return findOrCreateDirs(newDir, components.subComponents());
- }
- FileInfo FakeFolder::dbState() const
- {
- FileInfo result;
- _journalDb->getFilesBelowPath("", [&](const OCC::SyncJournalFileRecord &record) {
- auto components = PathComponents(record.path());
- auto &parentDir = findOrCreateDirs(result, components.parentDirComponents());
- auto name = components.fileName();
- auto &item = parentDir.children[name];
- item.name = name;
- item.parentPath = parentDir.path();
- item.size = record._fileSize;
- item.isDir = record._type == ItemTypeDirectory;
- item.permissions = record._remotePerm;
- item.etag = record._etag;
- item.lastModified = OCC::Utility::qDateTimeFromTime_t(record._modtime);
- item.fileId = record._fileId;
- item.checksums = record._checksumHeader;
- // item.contentChar can't be set from the db
- });
- return result;
- }
- OCC::SyncFileItemPtr ItemCompletedSpy::findItem(const QString &path) const
- {
- for (const QList<QVariant> &args : *this) {
- auto item = args[0].value<OCC::SyncFileItemPtr>();
- if (item->destination() == path)
- return item;
- }
- return OCC::SyncFileItemPtr::create();
- }
- OCC::SyncFileItemPtr ItemCompletedSpy::findItemWithExpectedRank(const QString &path, int rank) const
- {
- Q_ASSERT(size() > rank);
- Q_ASSERT(!(*this)[rank].isEmpty());
- auto item = (*this)[rank][0].value<OCC::SyncFileItemPtr>();
- if (item->destination() == path) {
- return item;
- } else {
- return OCC::SyncFileItemPtr::create();
- }
- }
- FakeReply::FakeReply(QObject *parent)
- : QNetworkReply(parent)
- {
- setRawHeader(QByteArrayLiteral("Date"), QDateTime::currentDateTimeUtc().toString(Qt::RFC2822Date).toUtf8());
- }
- FakeReply::~FakeReply() = default;
- FakeJsonErrorReply::FakeJsonErrorReply(QNetworkAccessManager::Operation op,
- const QNetworkRequest &request,
- QObject *parent,
- int httpErrorCode,
- const QJsonDocument &reply)
- : FakeErrorReply{ op, request, parent, httpErrorCode, reply.toJson() }
- {
- }
- FakeFileLockReply::FakeFileLockReply(FileInfo &remoteRootFileInfo,
- QNetworkAccessManager::Operation op,
- const QNetworkRequest &request,
- QObject *parent)
- : FakePropfindReply(remoteRootFileInfo, op, request, parent)
- {
- const auto verb = request.attribute(QNetworkRequest::CustomVerbAttribute);
- setRequest(request);
- setUrl(request.url());
- setOperation(op);
- open(QIODevice::ReadOnly);
- QString fileName = getFilePathFromUrl(request.url());
- Q_ASSERT(!fileName.isNull()); // for root, it should be empty
- FileInfo *fileInfo = remoteRootFileInfo.find(fileName);
- if (!fileInfo) {
- QMetaObject::invokeMethod(this, "respond404", Qt::QueuedConnection);
- return;
- }
- const QString prefix = request.url().path().left(request.url().path().size() - fileName.size());
- // Don't care about the request and just return a full propfind
- const QString davUri { QStringLiteral("DAV:") };
- const QString ocUri { QStringLiteral("http://owncloud.org/ns") };
- const QString ncUri { QStringLiteral("http://nextcloud.org/ns") };
- payload.clear();
- QBuffer buffer { &payload };
- buffer.open(QIODevice::WriteOnly);
- QXmlStreamWriter xml(&buffer);
- xml.writeNamespace(davUri, QStringLiteral("d"));
- xml.writeNamespace(ocUri, QStringLiteral("oc"));
- xml.writeNamespace(ncUri, QStringLiteral("nc"));
- xml.writeStartDocument();
- xml.writeStartElement(davUri, QStringLiteral("prop"));
- xml.writeTextElement(ncUri, QStringLiteral("lock"), verb == QStringLiteral("LOCK") ? "1" : "0");
- xml.writeTextElement(ncUri, QStringLiteral("lock-owner-type"), QString::number(0));
- xml.writeTextElement(ncUri, QStringLiteral("lock-owner"), QStringLiteral("admin"));
- xml.writeTextElement(ncUri, QStringLiteral("lock-owner-displayname"), QStringLiteral("John Doe"));
- xml.writeTextElement(ncUri, QStringLiteral("lock-owner-editor"), {});
- xml.writeTextElement(ncUri, QStringLiteral("lock-time"), QString::number(1234560));
- xml.writeTextElement(ncUri, QStringLiteral("lock-timeout"), QString::number(1800));
- xml.writeEndElement(); // prop
- xml.writeEndDocument();
- }
|