syncenginetestutils.cpp 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305
  1. /*
  2. * This software is in the public domain, furnished "as is", without technical
  3. * support, and with no warranty, express or implied, as to its usefulness for
  4. * any purpose.
  5. *
  6. */
  7. #include "syncenginetestutils.h"
  8. #include "httplogger.h"
  9. #include "accessmanager.h"
  10. #include <QJsonDocument>
  11. #include <QJsonArray>
  12. #include <QJsonObject>
  13. #include <QJsonValue>
  14. #include <memory>
  15. PathComponents::PathComponents(const char *path)
  16. : PathComponents { QString::fromUtf8(path) }
  17. {
  18. }
  19. PathComponents::PathComponents(const QString &path)
  20. : QStringList { path.split(QLatin1Char('/'), Qt::SkipEmptyParts) }
  21. {
  22. }
  23. PathComponents::PathComponents(const QStringList &pathComponents)
  24. : QStringList { pathComponents }
  25. {
  26. }
  27. PathComponents PathComponents::parentDirComponents() const
  28. {
  29. return PathComponents { mid(0, size() - 1) };
  30. }
  31. PathComponents PathComponents::subComponents() const &
  32. {
  33. return PathComponents { mid(1) };
  34. }
  35. void DiskFileModifier::remove(const QString &relativePath)
  36. {
  37. QFileInfo fi { _rootDir.filePath(relativePath) };
  38. if (fi.isFile())
  39. QVERIFY(_rootDir.remove(relativePath));
  40. else
  41. QVERIFY(QDir { fi.filePath() }.removeRecursively());
  42. }
  43. void DiskFileModifier::insert(const QString &relativePath, qint64 size, char contentChar)
  44. {
  45. QFile file { _rootDir.filePath(relativePath) };
  46. QVERIFY(!file.exists());
  47. file.open(QFile::WriteOnly);
  48. QByteArray buf(1024, contentChar);
  49. for (int x = 0; x < size / buf.size(); ++x) {
  50. file.write(buf);
  51. }
  52. file.write(buf.data(), size % buf.size());
  53. file.close();
  54. // Set the mtime 30 seconds in the past, for some tests that need to make sure that the mtime differs.
  55. OCC::FileSystem::setModTime(file.fileName(), OCC::Utility::qDateTimeToTime_t(QDateTime::currentDateTimeUtc().addSecs(-30)));
  56. QCOMPARE(file.size(), size);
  57. }
  58. void DiskFileModifier::setContents(const QString &relativePath, char contentChar)
  59. {
  60. QFile file { _rootDir.filePath(relativePath) };
  61. QVERIFY(file.exists());
  62. qint64 size = file.size();
  63. file.open(QFile::WriteOnly);
  64. file.write(QByteArray {}.fill(contentChar, size));
  65. }
  66. void DiskFileModifier::appendByte(const QString &relativePath)
  67. {
  68. QFile file { _rootDir.filePath(relativePath) };
  69. QVERIFY(file.exists());
  70. file.open(QFile::ReadWrite);
  71. QByteArray contents = file.read(1);
  72. file.seek(file.size());
  73. file.write(contents);
  74. }
  75. void DiskFileModifier::mkdir(const QString &relativePath)
  76. {
  77. _rootDir.mkpath(relativePath);
  78. }
  79. void DiskFileModifier::rename(const QString &from, const QString &to)
  80. {
  81. QVERIFY(_rootDir.exists(from));
  82. QVERIFY(_rootDir.rename(from, to));
  83. }
  84. void DiskFileModifier::setModTime(const QString &relativePath, const QDateTime &modTime)
  85. {
  86. OCC::FileSystem::setModTime(_rootDir.filePath(relativePath), OCC::Utility::qDateTimeToTime_t(modTime));
  87. }
  88. FileInfo FileInfo::A12_B12_C12_S12()
  89. {
  90. FileInfo fi { QString {}, {
  91. { QStringLiteral("A"), { { QStringLiteral("a1"), 4 }, { QStringLiteral("a2"), 4 } } },
  92. { QStringLiteral("B"), { { QStringLiteral("b1"), 16 }, { QStringLiteral("b2"), 16 } } },
  93. { QStringLiteral("C"), { { QStringLiteral("c1"), 24 }, { QStringLiteral("c2"), 24 } } },
  94. } };
  95. FileInfo sharedFolder { QStringLiteral("S"), { { QStringLiteral("s1"), 32 }, { QStringLiteral("s2"), 32 } } };
  96. sharedFolder.isShared = true;
  97. sharedFolder.children[QStringLiteral("s1")].isShared = true;
  98. sharedFolder.children[QStringLiteral("s2")].isShared = true;
  99. fi.children.insert(sharedFolder.name, std::move(sharedFolder));
  100. return fi;
  101. }
  102. FileInfo::FileInfo(const QString &name, const std::initializer_list<FileInfo> &children)
  103. : name { name }
  104. {
  105. for (const auto &source : children)
  106. addChild(source);
  107. }
  108. void FileInfo::addChild(const FileInfo &info)
  109. {
  110. auto &dest = this->children[info.name] = info;
  111. dest.parentPath = path();
  112. dest.fixupParentPathRecursively();
  113. }
  114. void FileInfo::remove(const QString &relativePath)
  115. {
  116. const PathComponents pathComponents { relativePath };
  117. FileInfo *parent = findInvalidatingEtags(pathComponents.parentDirComponents());
  118. Q_ASSERT(parent);
  119. parent->children.erase(std::find_if(parent->children.begin(), parent->children.end(),
  120. [&pathComponents](const FileInfo &fi) { return fi.name == pathComponents.fileName(); }));
  121. }
  122. void FileInfo::insert(const QString &relativePath, qint64 size, char contentChar)
  123. {
  124. create(relativePath, size, contentChar);
  125. }
  126. void FileInfo::setContents(const QString &relativePath, char contentChar)
  127. {
  128. FileInfo *file = findInvalidatingEtags(relativePath);
  129. Q_ASSERT(file);
  130. file->contentChar = contentChar;
  131. }
  132. void FileInfo::appendByte(const QString &relativePath)
  133. {
  134. FileInfo *file = findInvalidatingEtags(relativePath);
  135. Q_ASSERT(file);
  136. file->size += 1;
  137. }
  138. void FileInfo::mkdir(const QString &relativePath)
  139. {
  140. createDir(relativePath);
  141. }
  142. void FileInfo::rename(const QString &oldPath, const QString &newPath)
  143. {
  144. const PathComponents newPathComponents { newPath };
  145. FileInfo *dir = findInvalidatingEtags(newPathComponents.parentDirComponents());
  146. Q_ASSERT(dir);
  147. Q_ASSERT(dir->isDir);
  148. const PathComponents pathComponents { oldPath };
  149. FileInfo *parent = findInvalidatingEtags(pathComponents.parentDirComponents());
  150. Q_ASSERT(parent);
  151. FileInfo fi = parent->children.take(pathComponents.fileName());
  152. fi.parentPath = dir->path();
  153. fi.name = newPathComponents.fileName();
  154. fi.fixupParentPathRecursively();
  155. dir->children.insert(newPathComponents.fileName(), std::move(fi));
  156. }
  157. void FileInfo::setModTime(const QString &relativePath, const QDateTime &modTime)
  158. {
  159. FileInfo *file = findInvalidatingEtags(relativePath);
  160. Q_ASSERT(file);
  161. file->lastModified = modTime;
  162. }
  163. void FileInfo::setModTimeKeepEtag(const QString &relativePath, const QDateTime &modTime)
  164. {
  165. FileInfo *file = find(relativePath);
  166. Q_ASSERT(file);
  167. file->lastModified = modTime;
  168. }
  169. FileInfo *FileInfo::find(PathComponents pathComponents, const bool invalidateEtags)
  170. {
  171. if (pathComponents.isEmpty()) {
  172. if (invalidateEtags) {
  173. etag = generateEtag();
  174. }
  175. return this;
  176. }
  177. QString childName = pathComponents.pathRoot();
  178. auto it = children.find(childName);
  179. if (it != children.end()) {
  180. auto file = it->find(std::move(pathComponents).subComponents(), invalidateEtags);
  181. if (file && invalidateEtags) {
  182. // Update parents on the way back
  183. etag = generateEtag();
  184. }
  185. return file;
  186. }
  187. return nullptr;
  188. }
  189. FileInfo *FileInfo::createDir(const QString &relativePath)
  190. {
  191. const PathComponents pathComponents { relativePath };
  192. FileInfo *parent = findInvalidatingEtags(pathComponents.parentDirComponents());
  193. Q_ASSERT(parent);
  194. FileInfo &child = parent->children[pathComponents.fileName()] = FileInfo { pathComponents.fileName() };
  195. child.parentPath = parent->path();
  196. child.etag = generateEtag();
  197. return &child;
  198. }
  199. FileInfo *FileInfo::create(const QString &relativePath, qint64 size, char contentChar)
  200. {
  201. const PathComponents pathComponents { relativePath };
  202. FileInfo *parent = findInvalidatingEtags(pathComponents.parentDirComponents());
  203. Q_ASSERT(parent);
  204. FileInfo &child = parent->children[pathComponents.fileName()] = FileInfo { pathComponents.fileName(), size };
  205. child.parentPath = parent->path();
  206. child.contentChar = contentChar;
  207. child.etag = generateEtag();
  208. return &child;
  209. }
  210. bool FileInfo::operator==(const FileInfo &other) const
  211. {
  212. // Consider files to be equal between local<->remote as a user would.
  213. return name == other.name
  214. && isDir == other.isDir
  215. && size == other.size
  216. && contentChar == other.contentChar
  217. && children == other.children;
  218. }
  219. QString FileInfo::path() const
  220. {
  221. return (parentPath.isEmpty() ? QString() : (parentPath + QLatin1Char('/'))) + name;
  222. }
  223. QString FileInfo::absolutePath() const
  224. {
  225. if (parentPath.endsWith(QLatin1Char('/'))) {
  226. return parentPath + name;
  227. } else {
  228. return parentPath + QLatin1Char('/') + name;
  229. }
  230. }
  231. void FileInfo::fixupParentPathRecursively()
  232. {
  233. auto p = path();
  234. for (auto it = children.begin(); it != children.end(); ++it) {
  235. Q_ASSERT(it.key() == it->name);
  236. it->parentPath = p;
  237. it->fixupParentPathRecursively();
  238. }
  239. }
  240. FileInfo *FileInfo::findInvalidatingEtags(PathComponents pathComponents)
  241. {
  242. return find(std::move(pathComponents), true);
  243. }
  244. FakePropfindReply::FakePropfindReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent)
  245. : FakeReply { parent }
  246. {
  247. setRequest(request);
  248. setUrl(request.url());
  249. setOperation(op);
  250. open(QIODevice::ReadOnly);
  251. QString fileName = getFilePathFromUrl(request.url());
  252. Q_ASSERT(!fileName.isNull()); // for root, it should be empty
  253. const FileInfo *fileInfo = remoteRootFileInfo.find(fileName);
  254. if (!fileInfo) {
  255. QMetaObject::invokeMethod(this, "respond404", Qt::QueuedConnection);
  256. return;
  257. }
  258. const QString prefix = request.url().path().left(request.url().path().size() - fileName.size());
  259. // Don't care about the request and just return a full propfind
  260. const QString davUri { QStringLiteral("DAV:") };
  261. const QString ocUri { QStringLiteral("http://owncloud.org/ns") };
  262. const QString ncUri { QStringLiteral("http://nextcloud.org/ns") };
  263. QBuffer buffer { &payload };
  264. buffer.open(QIODevice::WriteOnly);
  265. QXmlStreamWriter xml(&buffer);
  266. xml.writeNamespace(davUri, QStringLiteral("d"));
  267. xml.writeNamespace(ocUri, QStringLiteral("oc"));
  268. xml.writeNamespace(ncUri, QStringLiteral("nc"));
  269. xml.writeStartDocument();
  270. xml.writeStartElement(davUri, QStringLiteral("multistatus"));
  271. auto writeFileResponse = [&](const FileInfo &fileInfo) {
  272. xml.writeStartElement(davUri, QStringLiteral("response"));
  273. auto url = QString::fromUtf8(QUrl::toPercentEncoding(fileInfo.absolutePath(), "/"));
  274. if (!url.endsWith(QChar('/'))) {
  275. url.append(QChar('/'));
  276. }
  277. const auto href = OCC::Utility::concatUrlPath(prefix, url).path();
  278. xml.writeTextElement(davUri, QStringLiteral("href"), href);
  279. xml.writeStartElement(davUri, QStringLiteral("propstat"));
  280. xml.writeStartElement(davUri, QStringLiteral("prop"));
  281. if (fileInfo.isDir) {
  282. xml.writeStartElement(davUri, QStringLiteral("resourcetype"));
  283. xml.writeEmptyElement(davUri, QStringLiteral("collection"));
  284. xml.writeEndElement(); // resourcetype
  285. } else
  286. xml.writeEmptyElement(davUri, QStringLiteral("resourcetype"));
  287. auto gmtDate = fileInfo.lastModified.toUTC();
  288. auto stringDate = QLocale::c().toString(gmtDate, QStringLiteral("ddd, dd MMM yyyy HH:mm:ss 'GMT'"));
  289. xml.writeTextElement(davUri, QStringLiteral("getlastmodified"), stringDate);
  290. xml.writeTextElement(davUri, QStringLiteral("getcontentlength"), QString::number(fileInfo.size));
  291. xml.writeTextElement(davUri, QStringLiteral("getetag"), QStringLiteral("\"%1\"").arg(QString::fromLatin1(fileInfo.etag)));
  292. xml.writeTextElement(ocUri, QStringLiteral("permissions"), !fileInfo.permissions.isNull() ? QString(fileInfo.permissions.toString()) : fileInfo.isShared ? QStringLiteral("SRDNVCKW") : QStringLiteral("RDNVCKW"));
  293. xml.writeTextElement(ocUri, QStringLiteral("id"), QString::fromUtf8(fileInfo.fileId));
  294. xml.writeTextElement(ocUri, QStringLiteral("checksums"), QString::fromUtf8(fileInfo.checksums));
  295. buffer.write(fileInfo.extraDavProperties);
  296. xml.writeEndElement(); // prop
  297. xml.writeTextElement(davUri, QStringLiteral("status"), QStringLiteral("HTTP/1.1 200 OK"));
  298. xml.writeEndElement(); // propstat
  299. xml.writeEndElement(); // response
  300. };
  301. writeFileResponse(*fileInfo);
  302. foreach (const FileInfo &childFileInfo, fileInfo->children)
  303. writeFileResponse(childFileInfo);
  304. xml.writeEndElement(); // multistatus
  305. xml.writeEndDocument();
  306. QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
  307. }
  308. void FakePropfindReply::respond()
  309. {
  310. setHeader(QNetworkRequest::ContentLengthHeader, payload.size());
  311. setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/xml; charset=utf-8"));
  312. setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 207);
  313. setFinished(true);
  314. emit metaDataChanged();
  315. if (bytesAvailable())
  316. emit readyRead();
  317. emit finished();
  318. }
  319. void FakePropfindReply::respond404()
  320. {
  321. setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 404);
  322. setError(InternalServerError, QStringLiteral("Not Found"));
  323. emit metaDataChanged();
  324. emit finished();
  325. }
  326. qint64 FakePropfindReply::bytesAvailable() const
  327. {
  328. return payload.size() + QIODevice::bytesAvailable();
  329. }
  330. qint64 FakePropfindReply::readData(char *data, qint64 maxlen)
  331. {
  332. qint64 len = std::min(qint64 { payload.size() }, maxlen);
  333. std::copy(payload.cbegin(), payload.cbegin() + len, data);
  334. payload.remove(0, static_cast<int>(len));
  335. return len;
  336. }
  337. FakePutReply::FakePutReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QByteArray &putPayload, QObject *parent)
  338. : FakeReply { parent }
  339. {
  340. setRequest(request);
  341. setUrl(request.url());
  342. setOperation(op);
  343. open(QIODevice::ReadOnly);
  344. fileInfo = perform(remoteRootFileInfo, request, putPayload);
  345. QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
  346. }
  347. FileInfo *FakePutReply::perform(FileInfo &remoteRootFileInfo, const QNetworkRequest &request, const QByteArray &putPayload)
  348. {
  349. QString fileName = getFilePathFromUrl(request.url());
  350. Q_ASSERT(!fileName.isEmpty());
  351. FileInfo *fileInfo = remoteRootFileInfo.find(fileName);
  352. if (fileInfo) {
  353. fileInfo->size = putPayload.size();
  354. fileInfo->contentChar = putPayload.at(0);
  355. } else {
  356. // Assume that the file is filled with the same character
  357. fileInfo = remoteRootFileInfo.create(fileName, putPayload.size(), putPayload.at(0));
  358. }
  359. fileInfo->lastModified = OCC::Utility::qDateTimeFromTime_t(request.rawHeader("X-OC-Mtime").toLongLong());
  360. remoteRootFileInfo.find(fileName, /*invalidateEtags=*/true);
  361. return fileInfo;
  362. }
  363. void FakePutReply::respond()
  364. {
  365. emit uploadProgress(fileInfo->size, fileInfo->size);
  366. setRawHeader("OC-ETag", fileInfo->etag);
  367. setRawHeader("ETag", fileInfo->etag);
  368. setRawHeader("OC-FileID", fileInfo->fileId);
  369. setRawHeader("X-OC-MTime", "accepted"); // Prevents Q_ASSERT(!_runningNow) since we'll call PropagateItemJob::done twice in that case.
  370. setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200);
  371. emit metaDataChanged();
  372. emit finished();
  373. }
  374. void FakePutReply::abort()
  375. {
  376. setError(OperationCanceledError, QStringLiteral("abort"));
  377. emit finished();
  378. }
  379. FakePutMultiFileReply::FakePutMultiFileReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QString &contentType, const QByteArray &putPayload, QObject *parent)
  380. : FakeReply { parent }
  381. {
  382. setRequest(request);
  383. setUrl(request.url());
  384. setOperation(op);
  385. open(QIODevice::ReadOnly);
  386. _allFileInfo = performMultiPart(remoteRootFileInfo, request, putPayload, contentType);
  387. QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
  388. }
  389. QVector<FileInfo *> FakePutMultiFileReply::performMultiPart(FileInfo &remoteRootFileInfo, const QNetworkRequest &request, const QByteArray &putPayload, const QString &contentType)
  390. {
  391. QVector<FileInfo *> result;
  392. auto stringPutPayload = QString::fromUtf8(putPayload);
  393. constexpr int boundaryPosition = sizeof("multipart/related; boundary=");
  394. const QString boundaryValue = QStringLiteral("--") + contentType.mid(boundaryPosition, contentType.length() - boundaryPosition - 1) + QStringLiteral("\r\n");
  395. auto stringPutPayloadRef = QString{stringPutPayload}.left(stringPutPayload.size() - 2 - boundaryValue.size());
  396. auto allParts = stringPutPayloadRef.split(boundaryValue, Qt::SkipEmptyParts);
  397. for (const auto &onePart : allParts) {
  398. auto headerEndPosition = onePart.indexOf(QStringLiteral("\r\n\r\n"));
  399. auto onePartHeaderPart = onePart.left(headerEndPosition);
  400. auto onePartBody = onePart.mid(headerEndPosition + 4, onePart.size() - headerEndPosition - 6);
  401. auto onePartHeaders = onePartHeaderPart.split(QStringLiteral("\r\n"));
  402. QMap<QString, QString> allHeaders;
  403. for(auto oneHeader : onePartHeaders) {
  404. auto headerParts = oneHeader.split(QStringLiteral(": "));
  405. allHeaders[headerParts.at(0)] = headerParts.at(1);
  406. }
  407. const auto fileName = allHeaders[QStringLiteral("X-File-Path")];
  408. const auto modtime = allHeaders[QByteArrayLiteral("X-File-Mtime")].toLongLong();
  409. Q_ASSERT(!fileName.isEmpty());
  410. Q_ASSERT(modtime > 0);
  411. FileInfo *fileInfo = remoteRootFileInfo.find(fileName);
  412. if (fileInfo) {
  413. fileInfo->size = onePartBody.size();
  414. fileInfo->contentChar = onePartBody.at(0).toLatin1();
  415. } else {
  416. // Assume that the file is filled with the same character
  417. fileInfo = remoteRootFileInfo.create(fileName, onePartBody.size(), onePartBody.at(0).toLatin1());
  418. }
  419. fileInfo->lastModified = OCC::Utility::qDateTimeFromTime_t(request.rawHeader("X-OC-Mtime").toLongLong());
  420. remoteRootFileInfo.find(fileName, /*invalidateEtags=*/true);
  421. result.push_back(fileInfo);
  422. }
  423. return result;
  424. }
  425. void FakePutMultiFileReply::respond()
  426. {
  427. QJsonDocument reply;
  428. QJsonObject allFileInfoReply;
  429. qint64 totalSize = 0;
  430. std::for_each(_allFileInfo.begin(), _allFileInfo.end(), [&totalSize](const auto &fileInfo) {
  431. totalSize += fileInfo->size;
  432. });
  433. for(auto fileInfo : qAsConst(_allFileInfo)) {
  434. QJsonObject fileInfoReply;
  435. fileInfoReply.insert("error", QStringLiteral("false"));
  436. fileInfoReply.insert("etag", QLatin1String{fileInfo->etag});
  437. emit uploadProgress(fileInfo->size, totalSize);
  438. allFileInfoReply.insert(QChar('/') + fileInfo->path(), fileInfoReply);
  439. }
  440. reply.setObject(allFileInfoReply);
  441. _payload = reply.toJson();
  442. setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200);
  443. setFinished(true);
  444. if (bytesAvailable()) {
  445. emit readyRead();
  446. }
  447. emit metaDataChanged();
  448. emit finished();
  449. }
  450. void FakePutMultiFileReply::abort()
  451. {
  452. setError(OperationCanceledError, QStringLiteral("abort"));
  453. emit finished();
  454. }
  455. qint64 FakePutMultiFileReply::bytesAvailable() const
  456. {
  457. return _payload.size() + QIODevice::bytesAvailable();
  458. }
  459. qint64 FakePutMultiFileReply::readData(char *data, qint64 maxlen)
  460. {
  461. qint64 len = std::min(qint64 { _payload.size() }, maxlen);
  462. std::copy(_payload.cbegin(), _payload.cbegin() + len, data);
  463. _payload.remove(0, static_cast<int>(len));
  464. return len;
  465. }
  466. FakeMkcolReply::FakeMkcolReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent)
  467. : FakeReply { parent }
  468. {
  469. setRequest(request);
  470. setUrl(request.url());
  471. setOperation(op);
  472. open(QIODevice::ReadOnly);
  473. QString fileName = getFilePathFromUrl(request.url());
  474. Q_ASSERT(!fileName.isEmpty());
  475. fileInfo = remoteRootFileInfo.createDir(fileName);
  476. if (!fileInfo) {
  477. abort();
  478. return;
  479. }
  480. QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
  481. }
  482. void FakeMkcolReply::respond()
  483. {
  484. setRawHeader("OC-FileId", fileInfo->fileId);
  485. setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 201);
  486. emit metaDataChanged();
  487. emit finished();
  488. }
  489. FakeDeleteReply::FakeDeleteReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent)
  490. : FakeReply { parent }
  491. {
  492. setRequest(request);
  493. setUrl(request.url());
  494. setOperation(op);
  495. open(QIODevice::ReadOnly);
  496. QString fileName = getFilePathFromUrl(request.url());
  497. Q_ASSERT(!fileName.isEmpty());
  498. remoteRootFileInfo.remove(fileName);
  499. QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
  500. }
  501. void FakeDeleteReply::respond()
  502. {
  503. setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 204);
  504. emit metaDataChanged();
  505. emit finished();
  506. }
  507. FakeMoveReply::FakeMoveReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent)
  508. : FakeReply { parent }
  509. {
  510. setRequest(request);
  511. setUrl(request.url());
  512. setOperation(op);
  513. open(QIODevice::ReadOnly);
  514. QString fileName = getFilePathFromUrl(request.url());
  515. Q_ASSERT(!fileName.isEmpty());
  516. QString dest = getFilePathFromUrl(QUrl::fromEncoded(request.rawHeader("Destination")));
  517. Q_ASSERT(!dest.isEmpty());
  518. remoteRootFileInfo.rename(fileName, dest);
  519. QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
  520. }
  521. void FakeMoveReply::respond()
  522. {
  523. setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 201);
  524. emit metaDataChanged();
  525. emit finished();
  526. }
  527. FakeGetReply::FakeGetReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent)
  528. : FakeReply { parent }
  529. {
  530. setRequest(request);
  531. setUrl(request.url());
  532. setOperation(op);
  533. open(QIODevice::ReadOnly);
  534. QString fileName = getFilePathFromUrl(request.url());
  535. Q_ASSERT(!fileName.isEmpty());
  536. fileInfo = remoteRootFileInfo.find(fileName);
  537. if (!fileInfo) {
  538. qDebug() << "meh;";
  539. }
  540. Q_ASSERT_X(fileInfo, Q_FUNC_INFO, "Could not find file on the remote");
  541. QMetaObject::invokeMethod(this, &FakeGetReply::respond, Qt::QueuedConnection);
  542. }
  543. void FakeGetReply::respond()
  544. {
  545. if (aborted) {
  546. setError(OperationCanceledError, QStringLiteral("Operation Canceled"));
  547. emit metaDataChanged();
  548. emit finished();
  549. return;
  550. }
  551. payload = fileInfo->contentChar;
  552. size = fileInfo->size;
  553. setHeader(QNetworkRequest::ContentLengthHeader, size);
  554. setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200);
  555. setRawHeader("OC-ETag", fileInfo->etag);
  556. setRawHeader("ETag", fileInfo->etag);
  557. setRawHeader("OC-FileId", fileInfo->fileId);
  558. emit metaDataChanged();
  559. if (bytesAvailable())
  560. emit readyRead();
  561. emit finished();
  562. }
  563. void FakeGetReply::abort()
  564. {
  565. setError(OperationCanceledError, QStringLiteral("Operation Canceled"));
  566. aborted = true;
  567. }
  568. qint64 FakeGetReply::bytesAvailable() const
  569. {
  570. if (aborted)
  571. return 0;
  572. return size + QIODevice::bytesAvailable();
  573. }
  574. qint64 FakeGetReply::readData(char *data, qint64 maxlen)
  575. {
  576. qint64 len = std::min(qint64 { size }, maxlen);
  577. std::fill_n(data, len, payload);
  578. size -= len;
  579. return len;
  580. }
  581. FakeGetWithDataReply::FakeGetWithDataReply(FileInfo &remoteRootFileInfo, const QByteArray &data, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent)
  582. : FakeReply { parent }
  583. {
  584. setRequest(request);
  585. setUrl(request.url());
  586. setOperation(op);
  587. open(QIODevice::ReadOnly);
  588. Q_ASSERT(!data.isEmpty());
  589. payload = data;
  590. QString fileName = getFilePathFromUrl(request.url());
  591. Q_ASSERT(!fileName.isEmpty());
  592. fileInfo = remoteRootFileInfo.find(fileName);
  593. QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
  594. if (request.hasRawHeader("Range")) {
  595. const QString range = QString::fromUtf8(request.rawHeader("Range"));
  596. const QRegularExpression bytesPattern(QStringLiteral("bytes=(?<start>\\d+)-(?<end>\\d+)"));
  597. const QRegularExpressionMatch match = bytesPattern.match(range);
  598. if (match.hasMatch()) {
  599. const int start = match.captured(QStringLiteral("start")).toInt();
  600. const int end = match.captured(QStringLiteral("end")).toInt();
  601. payload = payload.mid(start, end - start + 1);
  602. }
  603. }
  604. }
  605. void FakeGetWithDataReply::respond()
  606. {
  607. if (aborted) {
  608. setError(OperationCanceledError, QStringLiteral("Operation Canceled"));
  609. emit metaDataChanged();
  610. emit finished();
  611. return;
  612. }
  613. setHeader(QNetworkRequest::ContentLengthHeader, payload.size());
  614. setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200);
  615. setRawHeader("OC-ETag", fileInfo->etag);
  616. setRawHeader("ETag", fileInfo->etag);
  617. setRawHeader("OC-FileId", fileInfo->fileId);
  618. emit metaDataChanged();
  619. if (bytesAvailable())
  620. emit readyRead();
  621. emit finished();
  622. }
  623. void FakeGetWithDataReply::abort()
  624. {
  625. setError(OperationCanceledError, QStringLiteral("Operation Canceled"));
  626. aborted = true;
  627. }
  628. qint64 FakeGetWithDataReply::bytesAvailable() const
  629. {
  630. if (aborted)
  631. return 0;
  632. return payload.size() - offset + QIODevice::bytesAvailable();
  633. }
  634. qint64 FakeGetWithDataReply::readData(char *data, qint64 maxlen)
  635. {
  636. qint64 len = std::min(payload.size() - offset, quint64(maxlen));
  637. std::memcpy(data, payload.constData() + offset, len);
  638. offset += len;
  639. return len;
  640. }
  641. FakeChunkMoveReply::FakeChunkMoveReply(FileInfo &uploadsFileInfo, FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent)
  642. : FakeReply { parent }
  643. {
  644. setRequest(request);
  645. setUrl(request.url());
  646. setOperation(op);
  647. open(QIODevice::ReadOnly);
  648. fileInfo = perform(uploadsFileInfo, remoteRootFileInfo, request);
  649. if (!fileInfo) {
  650. QTimer::singleShot(0, this, &FakeChunkMoveReply::respondPreconditionFailed);
  651. } else {
  652. QTimer::singleShot(0, this, &FakeChunkMoveReply::respond);
  653. }
  654. }
  655. FileInfo *FakeChunkMoveReply::perform(FileInfo &uploadsFileInfo, FileInfo &remoteRootFileInfo, const QNetworkRequest &request)
  656. {
  657. QString source = getFilePathFromUrl(request.url());
  658. Q_ASSERT(!source.isEmpty());
  659. Q_ASSERT(source.endsWith(QLatin1String("/.file")));
  660. source = source.left(source.length() - static_cast<int>(qstrlen("/.file")));
  661. auto sourceFolder = uploadsFileInfo.find(source);
  662. Q_ASSERT(sourceFolder);
  663. Q_ASSERT(sourceFolder->isDir);
  664. int count = 0;
  665. qlonglong size = 0;
  666. char payload = '\0';
  667. QString fileName = getFilePathFromUrl(QUrl::fromEncoded(request.rawHeader("Destination")));
  668. Q_ASSERT(!fileName.isEmpty());
  669. // Compute the size and content from the chunks if possible
  670. for (auto chunkName : sourceFolder->children.keys()) {
  671. auto &x = sourceFolder->children[chunkName];
  672. Q_ASSERT(!x.isDir);
  673. Q_ASSERT(x.size > 0); // There should not be empty chunks
  674. size += x.size;
  675. Q_ASSERT(!payload || payload == x.contentChar);
  676. payload = x.contentChar;
  677. ++count;
  678. }
  679. Q_ASSERT(sourceFolder->children.count() == count); // There should not be holes or extra files
  680. // NOTE: This does not actually assemble the file data from the chunks!
  681. FileInfo *fileInfo = remoteRootFileInfo.find(fileName);
  682. if (fileInfo) {
  683. // The client should put this header
  684. Q_ASSERT(request.hasRawHeader("If"));
  685. // And it should condition on the destination file
  686. auto start = QByteArray("<" + request.rawHeader("Destination") + ">");
  687. Q_ASSERT(request.rawHeader("If").startsWith(start));
  688. if (request.rawHeader("If") != start + " ([\"" + fileInfo->etag + "\"])") {
  689. return nullptr;
  690. }
  691. fileInfo->size = size;
  692. fileInfo->contentChar = payload;
  693. } else {
  694. Q_ASSERT(!request.hasRawHeader("If"));
  695. // Assume that the file is filled with the same character
  696. fileInfo = remoteRootFileInfo.create(fileName, size, payload);
  697. }
  698. fileInfo->lastModified = OCC::Utility::qDateTimeFromTime_t(request.rawHeader("X-OC-Mtime").toLongLong());
  699. remoteRootFileInfo.find(fileName, /*invalidateEtags=*/true);
  700. return fileInfo;
  701. }
  702. void FakeChunkMoveReply::respond()
  703. {
  704. setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 201);
  705. setRawHeader("OC-ETag", fileInfo->etag);
  706. setRawHeader("ETag", fileInfo->etag);
  707. setRawHeader("OC-FileId", fileInfo->fileId);
  708. emit metaDataChanged();
  709. emit finished();
  710. }
  711. void FakeChunkMoveReply::respondPreconditionFailed()
  712. {
  713. setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 412);
  714. setError(InternalServerError, QStringLiteral("Precondition Failed"));
  715. emit metaDataChanged();
  716. emit finished();
  717. }
  718. void FakeChunkMoveReply::abort()
  719. {
  720. setError(OperationCanceledError, QStringLiteral("abort"));
  721. emit finished();
  722. }
  723. FakePayloadReply::FakePayloadReply(QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QByteArray &body, QObject *parent)
  724. : FakePayloadReply(op, request, body, FakePayloadReply::defaultDelay, parent)
  725. {
  726. }
  727. FakePayloadReply::FakePayloadReply(
  728. QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QByteArray &body, int delay, QObject *parent)
  729. : FakeReply{parent}
  730. , _body(body)
  731. {
  732. setRequest(request);
  733. setUrl(request.url());
  734. setOperation(op);
  735. open(QIODevice::ReadOnly);
  736. QTimer::singleShot(delay, this, &FakePayloadReply::respond);
  737. }
  738. void FakePayloadReply::respond()
  739. {
  740. setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200);
  741. setHeader(QNetworkRequest::ContentLengthHeader, _body.size());
  742. for (auto it = _additionalHeaders.constKeyValueBegin(); it != _additionalHeaders.constKeyValueEnd(); ++it) {
  743. setHeader(it->first, it->second);
  744. }
  745. emit metaDataChanged();
  746. emit readyRead();
  747. setFinished(true);
  748. emit finished();
  749. }
  750. qint64 FakePayloadReply::readData(char *buf, qint64 max)
  751. {
  752. max = qMin<qint64>(max, _body.size());
  753. memcpy(buf, _body.constData(), max);
  754. _body = _body.mid(max);
  755. return max;
  756. }
  757. qint64 FakePayloadReply::bytesAvailable() const
  758. {
  759. return _body.size();
  760. }
  761. FakeErrorReply::FakeErrorReply(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent, int httpErrorCode, const QByteArray &body)
  762. : FakeReply { parent }
  763. , _body(body)
  764. {
  765. setRequest(request);
  766. setUrl(request.url());
  767. setOperation(op);
  768. open(QIODevice::ReadOnly);
  769. setAttribute(QNetworkRequest::HttpStatusCodeAttribute, httpErrorCode);
  770. setError(InternalServerError, QStringLiteral("Internal Server Fake Error"));
  771. QMetaObject::invokeMethod(this, &FakeErrorReply::respond, Qt::QueuedConnection);
  772. }
  773. void FakeErrorReply::respond()
  774. {
  775. emit metaDataChanged();
  776. emit readyRead();
  777. // finishing can come strictly after readyRead was called
  778. QTimer::singleShot(5, this, &FakeErrorReply::slotSetFinished);
  779. }
  780. void FakeErrorReply::slotSetFinished()
  781. {
  782. setFinished(true);
  783. emit finished();
  784. }
  785. qint64 FakeErrorReply::readData(char *buf, qint64 max)
  786. {
  787. max = qMin<qint64>(max, _body.size());
  788. memcpy(buf, _body.constData(), max);
  789. _body = _body.mid(max);
  790. return max;
  791. }
  792. qint64 FakeErrorReply::bytesAvailable() const
  793. {
  794. return _body.size();
  795. }
  796. FakeHangingReply::FakeHangingReply(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent)
  797. : FakeReply(parent)
  798. {
  799. setRequest(request);
  800. setUrl(request.url());
  801. setOperation(op);
  802. open(QIODevice::ReadOnly);
  803. }
  804. void FakeHangingReply::abort()
  805. {
  806. // Follow more or less the implementation of QNetworkReplyImpl::abort
  807. close();
  808. setError(OperationCanceledError, tr("Operation canceled"));
  809. emit errorOccurred(OperationCanceledError);
  810. setFinished(true);
  811. emit finished();
  812. }
  813. FakeQNAM::FakeQNAM(FileInfo initialRoot)
  814. : _remoteRootFileInfo { std::move(initialRoot) }
  815. {
  816. setCookieJar(new OCC::CookieJar);
  817. }
  818. QJsonObject FakeQNAM::forEachReplyPart(QIODevice *outgoingData,
  819. const QString &contentType,
  820. std::function<QJsonObject (const QMap<QString, QByteArray> &)> replyFunction)
  821. {
  822. auto fullReply = QJsonObject{};
  823. auto putPayload = outgoingData->peek(outgoingData->bytesAvailable());
  824. outgoingData->reset();
  825. auto stringPutPayload = QString::fromUtf8(putPayload);
  826. constexpr int boundaryPosition = sizeof("multipart/related; boundary=");
  827. const QString boundaryValue = QStringLiteral("--") + contentType.mid(boundaryPosition, contentType.length() - boundaryPosition - 1) + QStringLiteral("\r\n");
  828. auto stringPutPayloadRef = QString{stringPutPayload}.left(stringPutPayload.size() - 2 - boundaryValue.size());
  829. auto allParts = stringPutPayloadRef.split(boundaryValue, Qt::SkipEmptyParts);
  830. for (const auto &onePart : qAsConst(allParts)) {
  831. auto headerEndPosition = onePart.indexOf(QStringLiteral("\r\n\r\n"));
  832. auto onePartHeaderPart = onePart.left(headerEndPosition);
  833. auto onePartHeaders = onePartHeaderPart.split(QStringLiteral("\r\n"));
  834. QMap<QString, QByteArray> allHeaders;
  835. for(const auto &oneHeader : qAsConst(onePartHeaders)) {
  836. auto headerParts = oneHeader.split(QStringLiteral(": "));
  837. allHeaders[headerParts.at(0)] = headerParts.at(1).toLatin1();
  838. }
  839. auto reply = replyFunction(allHeaders);
  840. if (reply.contains(QStringLiteral("error")) &&
  841. reply.contains(QStringLiteral("etag"))) {
  842. fullReply.insert(allHeaders[QStringLiteral("X-File-Path")], reply);
  843. }
  844. }
  845. return fullReply;
  846. }
  847. QNetworkReply *FakeQNAM::createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData)
  848. {
  849. QNetworkReply *reply = nullptr;
  850. auto newRequest = request;
  851. newRequest.setRawHeader("X-Request-ID", OCC::AccessManager::generateRequestId());
  852. auto contentType = request.header(QNetworkRequest::ContentTypeHeader).toString();
  853. if (_override) {
  854. if (auto _reply = _override(op, newRequest, outgoingData)) {
  855. reply = _reply;
  856. }
  857. }
  858. if (!reply) {
  859. reply = overrideReplyWithError(getFilePathFromUrl(newRequest.url()), op, newRequest);
  860. }
  861. if (!reply) {
  862. const bool isUpload = newRequest.url().path().startsWith(sUploadUrl.path());
  863. FileInfo &info = isUpload ? _uploadFileInfo : _remoteRootFileInfo;
  864. auto verb = newRequest.attribute(QNetworkRequest::CustomVerbAttribute);
  865. if (verb == QLatin1String("PROPFIND")) {
  866. // Ignore outgoingData always returning somethign good enough, works for now.
  867. reply = new FakePropfindReply { info, op, newRequest, this };
  868. } else if (verb == QLatin1String("GET") || op == QNetworkAccessManager::GetOperation) {
  869. reply = new FakeGetReply { info, op, newRequest, this };
  870. } else if (verb == QLatin1String("PUT") || op == QNetworkAccessManager::PutOperation) {
  871. if (request.hasRawHeader(QByteArrayLiteral("X-OC-Mtime")) &&
  872. request.rawHeader(QByteArrayLiteral("X-OC-Mtime")).toLongLong() <= 0) {
  873. reply = new FakeErrorReply { op, request, this, 500 };
  874. } else {
  875. reply = new FakePutReply { info, op, newRequest, outgoingData->readAll(), this };
  876. }
  877. } else if (verb == QLatin1String("MKCOL")) {
  878. reply = new FakeMkcolReply { info, op, newRequest, this };
  879. } else if (verb == QLatin1String("DELETE") || op == QNetworkAccessManager::DeleteOperation) {
  880. reply = new FakeDeleteReply { info, op, newRequest, this };
  881. } else if (verb == QLatin1String("MOVE") && !isUpload) {
  882. reply = new FakeMoveReply { info, op, newRequest, this };
  883. } else if (verb == QLatin1String("MOVE") && isUpload) {
  884. reply = new FakeChunkMoveReply { info, _remoteRootFileInfo, op, newRequest, this };
  885. } else if (verb == QLatin1String("POST") || op == QNetworkAccessManager::PostOperation) {
  886. if (contentType.startsWith(QStringLiteral("multipart/related; boundary="))) {
  887. reply = new FakePutMultiFileReply { info, op, newRequest, contentType, outgoingData->readAll(), this };
  888. }
  889. } else if (verb == QLatin1String("LOCK") || verb == QLatin1String("UNLOCK")) {
  890. reply = new FakeFileLockReply{info, op, newRequest, this};
  891. } else {
  892. qDebug() << verb << outgoingData;
  893. Q_UNREACHABLE();
  894. }
  895. }
  896. OCC::HttpLogger::logRequest(reply, op, outgoingData);
  897. return reply;
  898. }
  899. QNetworkReply * FakeQNAM::overrideReplyWithError(QString fileName, QNetworkAccessManager::Operation op, QNetworkRequest newRequest)
  900. {
  901. QNetworkReply *reply = nullptr;
  902. Q_ASSERT(!fileName.isNull());
  903. if (_errorPaths.contains(fileName)) {
  904. reply = new FakeErrorReply { op, newRequest, this, _errorPaths[fileName] };
  905. }
  906. return reply;
  907. }
  908. FakeFolder::FakeFolder(const FileInfo &fileTemplate, const OCC::Optional<FileInfo> &localFileInfo, const QString &remotePath)
  909. : _localModifier(_tempDir.path())
  910. {
  911. // Needs to be done once
  912. OCC::SyncEngine::minimumFileAgeForUpload = std::chrono::milliseconds(0);
  913. OCC::Logger::instance()->setLogFile(QStringLiteral("-"));
  914. OCC::Logger::instance()->addLogRule({ QStringLiteral("sync.httplogger=true") });
  915. QDir rootDir { _tempDir.path() };
  916. qDebug() << "FakeFolder operating on" << rootDir;
  917. if (localFileInfo) {
  918. toDisk(rootDir, *localFileInfo);
  919. } else {
  920. toDisk(rootDir, fileTemplate);
  921. }
  922. _fakeQnam = new FakeQNAM(fileTemplate);
  923. _account = OCC::Account::create();
  924. _account->setUrl(QUrl(QStringLiteral("http://admin:admin@localhost/owncloud")));
  925. _account->setCredentials(new FakeCredentials { _fakeQnam });
  926. _account->setDavDisplayName(QStringLiteral("fakename"));
  927. _account->setServerVersion(QStringLiteral("10.0.0"));
  928. _journalDb = std::make_unique<OCC::SyncJournalDb>(localPath() + QStringLiteral(".sync_test.db"));
  929. _syncEngine = std::make_unique<OCC::SyncEngine>(_account, localPath(), OCC::SyncOptions{}, remotePath, _journalDb.get());
  930. // Ignore temporary files from the download. (This is in the default exclude list, but we don't load it)
  931. _syncEngine->excludedFiles().addManualExclude(QStringLiteral("]*.~*"));
  932. // handle aboutToRemoveAllFiles with a timeout in case our test does not handle it
  933. QObject::connect(_syncEngine.get(), &OCC::SyncEngine::aboutToRemoveAllFiles, _syncEngine.get(), [this](OCC::SyncFileItem::Direction, std::function<void(bool)> callback) {
  934. QTimer::singleShot(1 * 1000, _syncEngine.get(), [callback] {
  935. callback(false);
  936. });
  937. });
  938. // Ensure we have a valid VfsOff instance "running"
  939. switchToVfs(_syncEngine->syncOptions()._vfs);
  940. // A new folder will update the local file state database on first sync.
  941. // To have a state matching what users will encounter, we have to a sync
  942. // using an identical local/remote file tree first.
  943. ENFORCE(syncOnce());
  944. }
  945. void FakeFolder::switchToVfs(QSharedPointer<OCC::Vfs> vfs)
  946. {
  947. auto opts = _syncEngine->syncOptions();
  948. opts._vfs->stop();
  949. QObject::disconnect(_syncEngine.get(), nullptr, opts._vfs.data(), nullptr);
  950. opts._vfs = vfs;
  951. _syncEngine->setSyncOptions(opts);
  952. OCC::VfsSetupParams vfsParams;
  953. vfsParams.filesystemPath = localPath();
  954. vfsParams.remotePath = QLatin1Char('/');
  955. vfsParams.account = _account;
  956. vfsParams.journal = _journalDb.get();
  957. vfsParams.providerName = QStringLiteral("OC-TEST");
  958. vfsParams.providerVersion = QStringLiteral("0.1");
  959. QObject::connect(_syncEngine.get(), &QObject::destroyed, vfs.data(), [vfs]() {
  960. vfs->stop();
  961. vfs->unregisterFolder();
  962. });
  963. vfs->start(vfsParams);
  964. }
  965. FileInfo FakeFolder::currentLocalState()
  966. {
  967. QDir rootDir { _tempDir.path() };
  968. FileInfo rootTemplate;
  969. fromDisk(rootDir, rootTemplate);
  970. rootTemplate.fixupParentPathRecursively();
  971. return rootTemplate;
  972. }
  973. QString FakeFolder::localPath() const
  974. {
  975. // SyncEngine wants a trailing slash
  976. if (_tempDir.path().endsWith(QLatin1Char('/')))
  977. return _tempDir.path();
  978. return _tempDir.path() + QLatin1Char('/');
  979. }
  980. void FakeFolder::scheduleSync()
  981. {
  982. // Have to be done async, else, an error before exec() does not terminate the event loop.
  983. QMetaObject::invokeMethod(_syncEngine.get(), "startSync", Qt::QueuedConnection);
  984. }
  985. void FakeFolder::execUntilBeforePropagation()
  986. {
  987. QSignalSpy spy(_syncEngine.get(), SIGNAL(aboutToPropagate(SyncFileItemVector &)));
  988. QVERIFY(spy.wait());
  989. }
  990. void FakeFolder::execUntilItemCompleted(const QString &relativePath)
  991. {
  992. QSignalSpy spy(_syncEngine.get(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
  993. QElapsedTimer t;
  994. t.start();
  995. while (t.elapsed() < 5000) {
  996. spy.clear();
  997. QVERIFY(spy.wait());
  998. for (const QList<QVariant> &args : spy) {
  999. auto item = args[0].value<OCC::SyncFileItemPtr>();
  1000. if (item->destination() == relativePath)
  1001. return;
  1002. }
  1003. }
  1004. QVERIFY(false);
  1005. }
  1006. void FakeFolder::toDisk(QDir &dir, const FileInfo &templateFi)
  1007. {
  1008. foreach (const FileInfo &child, templateFi.children) {
  1009. if (child.isDir) {
  1010. QDir subDir(dir);
  1011. dir.mkdir(child.name);
  1012. subDir.cd(child.name);
  1013. toDisk(subDir, child);
  1014. } else {
  1015. QFile file { dir.filePath(child.name) };
  1016. file.open(QFile::WriteOnly);
  1017. file.write(QByteArray {}.fill(child.contentChar, child.size));
  1018. file.close();
  1019. OCC::FileSystem::setModTime(file.fileName(), OCC::Utility::qDateTimeToTime_t(child.lastModified));
  1020. }
  1021. }
  1022. }
  1023. void FakeFolder::fromDisk(QDir &dir, FileInfo &templateFi)
  1024. {
  1025. foreach (const QFileInfo &diskChild, dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)) {
  1026. if (diskChild.isDir()) {
  1027. QDir subDir = dir;
  1028. subDir.cd(diskChild.fileName());
  1029. FileInfo &subFi = templateFi.children[diskChild.fileName()] = FileInfo { diskChild.fileName() };
  1030. fromDisk(subDir, subFi);
  1031. } else {
  1032. QFile f { diskChild.filePath() };
  1033. f.open(QFile::ReadOnly);
  1034. auto content = f.read(1);
  1035. if (content.size() == 0) {
  1036. qWarning() << "Empty file at:" << diskChild.filePath();
  1037. continue;
  1038. }
  1039. char contentChar = content.at(0);
  1040. templateFi.children.insert(diskChild.fileName(), FileInfo { diskChild.fileName(), diskChild.size(), contentChar });
  1041. }
  1042. }
  1043. }
  1044. static FileInfo &findOrCreateDirs(FileInfo &base, PathComponents components)
  1045. {
  1046. if (components.isEmpty())
  1047. return base;
  1048. auto childName = components.pathRoot();
  1049. auto it = base.children.find(childName);
  1050. if (it != base.children.end()) {
  1051. return findOrCreateDirs(*it, components.subComponents());
  1052. }
  1053. auto &newDir = base.children[childName] = FileInfo { childName };
  1054. newDir.parentPath = base.path();
  1055. return findOrCreateDirs(newDir, components.subComponents());
  1056. }
  1057. FileInfo FakeFolder::dbState() const
  1058. {
  1059. FileInfo result;
  1060. _journalDb->getFilesBelowPath("", [&](const OCC::SyncJournalFileRecord &record) {
  1061. auto components = PathComponents(record.path());
  1062. auto &parentDir = findOrCreateDirs(result, components.parentDirComponents());
  1063. auto name = components.fileName();
  1064. auto &item = parentDir.children[name];
  1065. item.name = name;
  1066. item.parentPath = parentDir.path();
  1067. item.size = record._fileSize;
  1068. item.isDir = record._type == ItemTypeDirectory;
  1069. item.permissions = record._remotePerm;
  1070. item.etag = record._etag;
  1071. item.lastModified = OCC::Utility::qDateTimeFromTime_t(record._modtime);
  1072. item.fileId = record._fileId;
  1073. item.checksums = record._checksumHeader;
  1074. // item.contentChar can't be set from the db
  1075. });
  1076. return result;
  1077. }
  1078. OCC::SyncFileItemPtr ItemCompletedSpy::findItem(const QString &path) const
  1079. {
  1080. for (const QList<QVariant> &args : *this) {
  1081. auto item = args[0].value<OCC::SyncFileItemPtr>();
  1082. if (item->destination() == path)
  1083. return item;
  1084. }
  1085. return OCC::SyncFileItemPtr::create();
  1086. }
  1087. OCC::SyncFileItemPtr ItemCompletedSpy::findItemWithExpectedRank(const QString &path, int rank) const
  1088. {
  1089. Q_ASSERT(size() > rank);
  1090. Q_ASSERT(!(*this)[rank].isEmpty());
  1091. auto item = (*this)[rank][0].value<OCC::SyncFileItemPtr>();
  1092. if (item->destination() == path) {
  1093. return item;
  1094. } else {
  1095. return OCC::SyncFileItemPtr::create();
  1096. }
  1097. }
  1098. FakeReply::FakeReply(QObject *parent)
  1099. : QNetworkReply(parent)
  1100. {
  1101. setRawHeader(QByteArrayLiteral("Date"), QDateTime::currentDateTimeUtc().toString(Qt::RFC2822Date).toUtf8());
  1102. }
  1103. FakeReply::~FakeReply() = default;
  1104. FakeJsonErrorReply::FakeJsonErrorReply(QNetworkAccessManager::Operation op,
  1105. const QNetworkRequest &request,
  1106. QObject *parent,
  1107. int httpErrorCode,
  1108. const QJsonDocument &reply)
  1109. : FakeErrorReply{ op, request, parent, httpErrorCode, reply.toJson() }
  1110. {
  1111. }
  1112. FakeFileLockReply::FakeFileLockReply(FileInfo &remoteRootFileInfo,
  1113. QNetworkAccessManager::Operation op,
  1114. const QNetworkRequest &request,
  1115. QObject *parent)
  1116. : FakePropfindReply(remoteRootFileInfo, op, request, parent)
  1117. {
  1118. const auto verb = request.attribute(QNetworkRequest::CustomVerbAttribute);
  1119. setRequest(request);
  1120. setUrl(request.url());
  1121. setOperation(op);
  1122. open(QIODevice::ReadOnly);
  1123. QString fileName = getFilePathFromUrl(request.url());
  1124. Q_ASSERT(!fileName.isNull()); // for root, it should be empty
  1125. FileInfo *fileInfo = remoteRootFileInfo.find(fileName);
  1126. if (!fileInfo) {
  1127. QMetaObject::invokeMethod(this, "respond404", Qt::QueuedConnection);
  1128. return;
  1129. }
  1130. const QString prefix = request.url().path().left(request.url().path().size() - fileName.size());
  1131. // Don't care about the request and just return a full propfind
  1132. const QString davUri { QStringLiteral("DAV:") };
  1133. const QString ocUri { QStringLiteral("http://owncloud.org/ns") };
  1134. const QString ncUri { QStringLiteral("http://nextcloud.org/ns") };
  1135. payload.clear();
  1136. QBuffer buffer { &payload };
  1137. buffer.open(QIODevice::WriteOnly);
  1138. QXmlStreamWriter xml(&buffer);
  1139. xml.writeNamespace(davUri, QStringLiteral("d"));
  1140. xml.writeNamespace(ocUri, QStringLiteral("oc"));
  1141. xml.writeNamespace(ncUri, QStringLiteral("nc"));
  1142. xml.writeStartDocument();
  1143. xml.writeStartElement(davUri, QStringLiteral("prop"));
  1144. xml.writeTextElement(ncUri, QStringLiteral("lock"), verb == QStringLiteral("LOCK") ? "1" : "0");
  1145. xml.writeTextElement(ncUri, QStringLiteral("lock-owner-type"), QString::number(0));
  1146. xml.writeTextElement(ncUri, QStringLiteral("lock-owner"), QStringLiteral("admin"));
  1147. xml.writeTextElement(ncUri, QStringLiteral("lock-owner-displayname"), QStringLiteral("John Doe"));
  1148. xml.writeTextElement(ncUri, QStringLiteral("lock-owner-editor"), {});
  1149. xml.writeTextElement(ncUri, QStringLiteral("lock-time"), QString::number(1234560));
  1150. xml.writeTextElement(ncUri, QStringLiteral("lock-timeout"), QString::number(1800));
  1151. xml.writeEndElement(); // prop
  1152. xml.writeEndDocument();
  1153. }