syncenginetestutils.cpp 51 KB

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