Merge lp:~jamesh/storage-provider-webdav/http-errors into lp:storage-provider-webdav/devel

Proposed by James Henstridge
Status: Merged
Approved by: Michi Henning
Approved revision: 27
Merged at revision: 19
Proposed branch: lp:~jamesh/storage-provider-webdav/http-errors
Merge into: lp:storage-provider-webdav/devel
Diff against target: 1740 lines (+710/-185)
33 files modified
src/CMakeLists.txt (+1/-0)
src/CopyMoveHandler.cpp (+6/-4)
src/CopyMoveHandler.h (+2/-2)
src/CreateFolderHandler.cpp (+9/-7)
src/CreateFolderHandler.h (+3/-3)
src/DavDownloadJob.cpp (+50/-35)
src/DavDownloadJob.h (+9/-6)
src/DavProvider.cpp (+20/-13)
src/DavProvider.h (+3/-0)
src/DavUploadJob.cpp (+9/-19)
src/DavUploadJob.h (+5/-5)
src/DeleteHandler.cpp (+7/-8)
src/DeleteHandler.h (+2/-2)
src/ListHandler.cpp (+3/-3)
src/ListHandler.h (+1/-2)
src/LookupHandler.cpp (+1/-1)
src/LookupHandler.h (+1/-1)
src/MetadataHandler.cpp (+2/-2)
src/MetadataHandler.h (+1/-2)
src/PropFindHandler.cpp (+52/-26)
src/PropFindHandler.h (+9/-4)
src/RetrieveMetadataHandler.cpp (+3/-4)
src/RetrieveMetadataHandler.h (+1/-2)
src/RootsHandler.cpp (+2/-1)
src/RootsHandler.h (+1/-1)
src/http_error.cpp (+163/-0)
src/http_error.h (+12/-0)
tests/CMakeLists.txt (+1/-0)
tests/davprovider/davprovider_test.cpp (+60/-29)
tests/http_error/CMakeLists.txt (+8/-0)
tests/http_error/http_error_test.cpp (+260/-0)
tests/utils/ProviderEnvironment.cpp (+2/-2)
tests/utils/ProviderEnvironment.h (+1/-1)
To merge this branch: bzr merge lp:~jamesh/storage-provider-webdav/http-errors
Reviewer Review Type Date Requested Status
Michi Henning (community) Approve
unity-api-1-bot continuous-integration Approve
Review via email: mp+311222@code.launchpad.net

Commit message

Improve HTTP error handling for all D-Bus methods.

Description of the change

Improve HTTP error handling for all D-Bus methods.

I also changed the various handler classes to store a shared_ptr<DavProvider> rather than a reference. This cleans up the memory management of the QNetworkReply objects by making sure QNetworkAccessManager is guaraneteed to live longer than the unique_ptr's we're using to store the replies.

To post a comment you must log in.
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

PASSED: Continuous integration, rev:27
https://jenkins.canonical.com/unity-api-1/job/lp-storage-provider-webdav-ci/32/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build/1100
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/1107
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/898
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/898/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/898
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=zesty/898/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/898
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/898/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/898
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=zesty/898/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/898
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/898/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/898
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=zesty/898/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-storage-provider-webdav-ci/32/rebuild

review: Approve (continuous-integration)
Revision history for this message
Michi Henning (michihenning) wrote :

Looks bloody awesome, thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/CMakeLists.txt'
2--- src/CMakeLists.txt 2016-11-12 03:15:39 +0000
3+++ src/CMakeLists.txt 2016-11-18 02:39:30 +0000
4@@ -4,6 +4,7 @@
5 DavDownloadJob.cpp
6 DavUploadJob.cpp
7 MultiStatusParser.cpp
8+ http_error.cpp
9 item_id.cpp
10 CopyMoveHandler.cpp
11 CreateFolderHandler.cpp
12
13=== modified file 'src/CopyMoveHandler.cpp'
14--- src/CopyMoveHandler.cpp 2016-11-14 06:18:18 +0000
15+++ src/CopyMoveHandler.cpp 2016-11-18 02:39:30 +0000
16@@ -1,11 +1,12 @@
17 #include "CopyMoveHandler.h"
18 #include "RetrieveMetadataHandler.h"
19 #include "item_id.h"
20+#include "http_error.h"
21
22 using namespace std;
23 using namespace unity::storage::provider;
24
25-CopyMoveHandler::CopyMoveHandler(DavProvider const& provider,
26+CopyMoveHandler::CopyMoveHandler(shared_ptr<DavProvider> const& provider,
27 string const& item_id,
28 string const& new_parent_id,
29 string const& new_name,
30@@ -15,7 +16,7 @@
31 new_item_id_(make_child_id(new_parent_id, new_name, is_folder(item_id))),
32 context_(ctx)
33 {
34- QUrl const base_url = provider.base_url(ctx);
35+ QUrl const base_url = provider->base_url(ctx);
36 QNetworkRequest request(id_to_url(item_id_, base_url));
37 request.setRawHeader(QByteArrayLiteral("Destination"),
38 id_to_url(new_item_id_, base_url).toEncoded());
39@@ -23,7 +24,7 @@
40 request.setRawHeader(QByteArrayLiteral("Overwrite"),
41 QByteArrayLiteral("F"));
42
43- reply_.reset(provider.send_request(
44+ reply_.reset(provider->send_request(
45 request, copy ? QByteArrayLiteral("COPY") : QByteArrayLiteral("MOVE"),
46 nullptr, ctx));
47 connect(reply_.get(), &QNetworkReply::finished,
48@@ -43,7 +44,8 @@
49
50 if (status != 201 && status != 204)
51 {
52- promise_.set_exception(RemoteCommsException("Error from request: " + to_string(status)));
53+ promise_.set_exception(
54+ translate_http_error(reply_.get(), QByteArray(), item_id_));
55 deleteLater();
56 return;
57 }
58
59=== modified file 'src/CopyMoveHandler.h'
60--- src/CopyMoveHandler.h 2016-11-12 03:15:39 +0000
61+++ src/CopyMoveHandler.h 2016-11-18 02:39:30 +0000
62@@ -13,7 +13,7 @@
63 class CopyMoveHandler : public QObject {
64 Q_OBJECT
65 public:
66- CopyMoveHandler(DavProvider const& provider,
67+ CopyMoveHandler(std::shared_ptr<DavProvider> const& provider,
68 std::string const& item_id,
69 std::string const& new_parent_id,
70 std::string const& new_name,
71@@ -29,7 +29,7 @@
72 private:
73 boost::promise<unity::storage::provider::Item> promise_;
74
75- DavProvider const& provider_;
76+ std::shared_ptr<DavProvider> const provider_;
77 std::string const item_id_;
78 std::string const new_item_id_;
79 unity::storage::provider::Context const context_;
80
81=== modified file 'src/CreateFolderHandler.cpp'
82--- src/CreateFolderHandler.cpp 2016-11-12 03:15:39 +0000
83+++ src/CreateFolderHandler.cpp 2016-11-18 02:39:30 +0000
84@@ -1,22 +1,23 @@
85 #include "CreateFolderHandler.h"
86 #include "RetrieveMetadataHandler.h"
87 #include "item_id.h"
88+#include "http_error.h"
89
90 using namespace std;
91 using namespace unity::storage::provider;
92
93-CreateFolderHandler::CreateFolderHandler(DavProvider const& provider,
94+CreateFolderHandler::CreateFolderHandler(shared_ptr<DavProvider> const& provider,
95 string const& parent_id,
96 string const& name,
97 Context const& ctx)
98 : provider_(provider), item_id_(make_child_id(parent_id, name, true)),
99 context_(ctx)
100 {
101- QUrl const base_url = provider.base_url(ctx);
102+ QUrl const base_url = provider->base_url(ctx);
103 QNetworkRequest request(id_to_url(item_id_, base_url));
104- mkcol_.reset(provider.send_request(request, QByteArrayLiteral("MKCOL"),
105- nullptr, ctx));
106- connect(mkcol_.get(), &QNetworkReply::finished,
107+ reply_.reset(provider->send_request(request, QByteArrayLiteral("MKCOL"),
108+ nullptr, ctx));
109+ connect(reply_.get(), &QNetworkReply::finished,
110 this, &CreateFolderHandler::onFinished);
111 }
112
113@@ -29,11 +30,12 @@
114
115 void CreateFolderHandler::onFinished()
116 {
117- auto status = mkcol_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
118+ auto status = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
119
120 if (status != 201)
121 {
122- promise_.set_exception(RemoteCommsException("Error from MKCOL: " + to_string(status)));
123+ promise_.set_exception(
124+ translate_http_error(reply_.get(), QByteArray(), item_id_));
125 deleteLater();
126 return;
127 }
128
129=== modified file 'src/CreateFolderHandler.h'
130--- src/CreateFolderHandler.h 2016-11-10 09:41:35 +0000
131+++ src/CreateFolderHandler.h 2016-11-18 02:39:30 +0000
132@@ -13,7 +13,7 @@
133 class CreateFolderHandler : public QObject {
134 Q_OBJECT
135 public:
136- CreateFolderHandler(DavProvider const& provider,
137+ CreateFolderHandler(std::shared_ptr<DavProvider> const& provider,
138 std::string const& parent_id, std::string const& name,
139 unity::storage::provider::Context const& ctx);
140 ~CreateFolderHandler();
141@@ -26,10 +26,10 @@
142 private:
143 boost::promise<unity::storage::provider::Item> promise_;
144
145- DavProvider const& provider_;
146+ std::shared_ptr<DavProvider> const provider_;
147 std::string const item_id_;
148 unity::storage::provider::Context const context_;
149
150- std::unique_ptr<QNetworkReply> mkcol_;
151+ std::unique_ptr<QNetworkReply> reply_;
152 std::unique_ptr<RetrieveMetadataHandler> metadata_;
153 };
154
155=== modified file 'src/DavDownloadJob.cpp'
156--- src/DavDownloadJob.cpp 2016-11-15 11:32:58 +0000
157+++ src/DavDownloadJob.cpp 2016-11-18 02:39:30 +0000
158@@ -2,6 +2,7 @@
159 #include "DavProvider.h"
160 #include "RetrieveMetadataHandler.h"
161 #include "item_id.h"
162+#include "http_error.h"
163
164 #include <unity/storage/provider/Exceptions.h>
165
166@@ -27,14 +28,14 @@
167
168 }
169
170-DavDownloadJob::DavDownloadJob(DavProvider const& provider,
171+DavDownloadJob::DavDownloadJob(shared_ptr<DavProvider> const& provider,
172 string const& item_id,
173 string const& match_etag,
174 Context const& ctx)
175 : QObject(), DownloadJob(make_download_id()), provider_(provider),
176 item_id_(item_id)
177 {
178- QUrl base_url = provider.base_url(ctx);
179+ QUrl base_url = provider->base_url(ctx);
180 QNetworkRequest request(id_to_url(item_id, base_url));
181 if (!match_etag.empty())
182 {
183@@ -42,15 +43,15 @@
184 QByteArray::fromStdString(match_etag));
185 }
186
187- reply_ = provider.send_request(
188- request, QByteArrayLiteral("GET"), nullptr, ctx);
189- assert(reply_ != nullptr);
190+ reply_.reset(provider->send_request(
191+ request, QByteArrayLiteral("GET"), nullptr, ctx));
192+ assert(reply_.get() != nullptr);
193 reply_->setReadBufferSize(CHUNK_SIZE);
194- connect(reply_, &QNetworkReply::finished,
195+ connect(reply_.get(), &QNetworkReply::finished,
196 this, &DavDownloadJob::onReplyFinished);
197- connect(reply_, &QIODevice::readyRead,
198+ connect(reply_.get(), &QIODevice::readyRead,
199 this, &DavDownloadJob::onReplyReadyRead);
200- connect(reply_, &QIODevice::readChannelFinished,
201+ connect(reply_.get(), &QIODevice::readChannelFinished,
202 this, &DavDownloadJob::onReplyReadChannelFinished);
203 writer_.setSocketDescriptor(
204 dup(write_socket()), QLocalSocket::ConnectedState, QIODevice::WriteOnly);
205@@ -58,50 +59,56 @@
206 this, &DavDownloadJob::onSocketBytesWritten);
207 }
208
209-DavDownloadJob::~DavDownloadJob()
210-{
211- if (!reply_.isNull())
212- {
213- reply_->deleteLater();
214- }
215-}
216+DavDownloadJob::~DavDownloadJob() = default;
217
218 void DavDownloadJob::onReplyFinished()
219 {
220- // If we haven't seen HTTP response headers and are in an error
221- // state, report that.
222- if (!seen_header_ && reply_->error() != QNetworkReply::NoError)
223+ if (!seen_header_ || is_error_)
224 {
225- handle_error(RemoteCommsException("Error connecting to server: " +
226- reply_->errorString().toStdString()));
227+ try
228+ {
229+ boost::rethrow_exception(
230+ translate_http_error(reply_.get(), error_body_, item_id_));
231+ }
232+ catch (...)
233+ {
234+ handle_error(std::current_exception());
235+ }
236 }
237 }
238
239 void DavDownloadJob::onReplyReadyRead()
240 {
241- if (error_)
242- {
243- return;
244- }
245-
246 if (!seen_header_)
247 {
248 seen_header_ = true;
249 auto status = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
250 if (status != 200)
251 {
252- handle_error(RemoteCommsException("Unexpected status code: " +
253- to_string(status)));
254- return;
255+ is_error_ = true;
256 }
257 }
258
259- maybe_send_chunk();
260+ if (is_error_)
261+ {
262+ if (error_body_.size() < MAX_ERROR_BODY_LENGTH)
263+ {
264+ error_body_.append(reply_->readAll());
265+ }
266+ else
267+ {
268+ reply_->close();
269+ }
270+ }
271+ else
272+ {
273+ maybe_send_chunk();
274+ }
275 }
276
277 void DavDownloadJob::onReplyReadChannelFinished()
278 {
279- if (error_)
280+ if (is_error_)
281 {
282 return;
283 }
284@@ -112,7 +119,7 @@
285
286 void DavDownloadJob::onSocketBytesWritten(int64_t bytes)
287 {
288- if (error_)
289+ if (is_error_)
290 {
291 return;
292 }
293@@ -146,7 +153,8 @@
294 int n_read = reply_->read(buffer, CHUNK_SIZE);
295 if (n_read < 0)
296 {
297- handle_error(RemoteCommsException("Failed to read from server"));
298+ handle_error(RemoteCommsException("Failed to read from server: " +
299+ reply_->errorString().toStdString()));
300 return;
301 }
302 bytes_read_ += n_read;
303@@ -154,17 +162,24 @@
304 int n_written = writer_.write(buffer, n_read);
305 if (n_written < 0)
306 {
307- handle_error(ResourceException("Error writing to socket", 0));
308+ handle_error(ResourceException(
309+ "Error writing to socket: "
310+ + writer_.errorString().toStdString(), 0));
311 return;
312 }
313 }
314
315 void DavDownloadJob::handle_error(StorageException const& exc)
316 {
317- error_ = true;
318+ handle_error(std::make_exception_ptr(exc));
319+}
320+
321+void DavDownloadJob::handle_error(std::exception_ptr ep)
322+{
323+ is_error_ = true;
324 reply_->close();
325 writer_.close();
326- report_error(std::make_exception_ptr(exc));
327+ report_error(ep);
328 }
329
330
331
332=== modified file 'src/DavDownloadJob.h'
333--- src/DavDownloadJob.h 2016-11-09 11:48:39 +0000
334+++ src/DavDownloadJob.h 2016-11-18 02:39:30 +0000
335@@ -1,9 +1,9 @@
336 #pragma once
337
338+#include <QByteArray>
339 #include <QLocalSocket>
340 #include <QNetworkReply>
341 #include <QObject>
342-#include <QPointer>
343 #include <QUrl>
344 #include <unity/storage/provider/Exceptions.h>
345 #include <unity/storage/provider/ProviderBase.h>
346@@ -19,8 +19,8 @@
347 {
348 Q_OBJECT
349 public:
350- DavDownloadJob(DavProvider const& provider, std::string const& item_id,
351- std::string const& match_etag,
352+ DavDownloadJob(std::shared_ptr<DavProvider> const& provider,
353+ std::string const& item_id, std::string const& match_etag,
354 unity::storage::provider::Context const& ctx);
355 ~DavDownloadJob();
356
357@@ -37,15 +37,18 @@
358 private:
359 void maybe_send_chunk();
360 void handle_error(unity::storage::provider::StorageException const& exc);
361+ void handle_error(std::exception_ptr ep);
362
363- DavProvider const& provider_;
364+ std::shared_ptr<DavProvider> const provider_;
365 std::string const item_id_;
366 QLocalSocket writer_;
367- QPointer<QNetworkReply> reply_;
368+ std::unique_ptr<QNetworkReply> reply_;
369
370 bool seen_header_ = false;
371- bool error_ = false;
372 bool read_channel_finished_ = false;
373 int64_t bytes_read_ = 0;
374 int64_t bytes_written_ = 0;
375+
376+ bool is_error_ = false;
377+ QByteArray error_body_;
378 };
379
380=== modified file 'src/DavProvider.cpp'
381--- src/DavProvider.cpp 2016-11-16 02:58:18 +0000
382+++ src/DavProvider.cpp 2016-11-18 02:39:30 +0000
383@@ -31,11 +31,16 @@
384
385 DavProvider::~DavProvider() = default;
386
387+std::shared_ptr<DavProvider> DavProvider::shared_from_this()
388+{
389+ return static_pointer_cast<DavProvider>(ProviderBase::shared_from_this());
390+}
391+
392 boost::future<ItemList> DavProvider::roots(
393 vector<string> const& metadata_keys, Context const& ctx)
394 {
395 Q_UNUSED(metadata_keys);
396- auto handler = new RootsHandler(*this, ctx);
397+ auto handler = new RootsHandler(shared_from_this(), ctx);
398 return handler->get_future();
399 }
400
401@@ -48,7 +53,7 @@
402 {
403 throw InvalidArgumentException("Invalid paging token: " + page_token);
404 }
405- auto handler = new ListHandler(*this, item_id, ctx);
406+ auto handler = new ListHandler(shared_from_this(), item_id, ctx);
407 return handler->get_future();
408 }
409
410@@ -58,7 +63,7 @@
411 {
412 Q_UNUSED(metadata_keys);
413 string item_id = make_child_id(parent_id, name);
414- auto handler = new LookupHandler(*this, item_id, ctx);
415+ auto handler = new LookupHandler(shared_from_this(), item_id, ctx);
416 return handler->get_future();
417 }
418
419@@ -67,7 +72,7 @@
420 Context const& ctx)
421 {
422 Q_UNUSED(metadata_keys);
423- auto handler = new MetadataHandler(*this, item_id, ctx);
424+ auto handler = new MetadataHandler(shared_from_this(), item_id, ctx);
425 return handler->get_future();
426 }
427
428@@ -76,7 +81,8 @@
429 vector<string> const& metadata_keys, Context const& ctx)
430 {
431 Q_UNUSED(metadata_keys);
432- auto handler = new CreateFolderHandler(*this, parent_id, name, ctx);
433+ auto handler = new CreateFolderHandler(
434+ shared_from_this(), parent_id, name, ctx);
435 return handler->get_future();
436 }
437
438@@ -89,7 +95,8 @@
439 string item_id = make_child_id(parent_id, name);
440 boost::promise<unique_ptr<UploadJob>> p;
441 p.set_value(unique_ptr<UploadJob>(new DavUploadJob(
442- *this, item_id, size, content_type, allow_overwrite, string(), ctx)));
443+ shared_from_this(), item_id, size, content_type, allow_overwrite,
444+ string(), ctx)));
445 return p.get_future();
446 }
447
448@@ -100,7 +107,7 @@
449 Q_UNUSED(metadata_keys);
450 boost::promise<unique_ptr<UploadJob>> p;
451 p.set_value(unique_ptr<UploadJob>(new DavUploadJob(
452- *this, item_id, size, string(), true, old_etag, ctx)));
453+ shared_from_this(), item_id, size, string(), true, old_etag, ctx)));
454 return p.get_future();
455 }
456
457@@ -109,14 +116,14 @@
458 {
459 boost::promise<unique_ptr<DownloadJob>> p;
460 p.set_value(unique_ptr<DownloadJob>(new DavDownloadJob(
461- *this, item_id, match_etag, ctx)));
462+ shared_from_this(), item_id, match_etag, ctx)));
463 return p.get_future();
464 }
465
466 boost::future<void> DavProvider::delete_item(
467 string const& item_id, Context const& ctx)
468 {
469- auto handler = new DeleteHandler(*this, item_id, ctx);
470+ auto handler = new DeleteHandler(shared_from_this(), item_id, ctx);
471 return handler->get_future();
472 }
473
474@@ -125,8 +132,8 @@
475 vector<string> const& metadata_keys, Context const& ctx)
476 {
477 Q_UNUSED(metadata_keys);
478- auto handler = new CopyMoveHandler(*this, item_id, new_parent_id,
479- new_name, false, ctx);
480+ auto handler = new CopyMoveHandler(
481+ shared_from_this(), item_id, new_parent_id, new_name, false, ctx);
482 return handler->get_future();
483 }
484
485@@ -135,8 +142,8 @@
486 vector<string> const& metadata_keys, Context const& ctx)
487 {
488 Q_UNUSED(metadata_keys);
489- auto handler = new CopyMoveHandler(*this, item_id, new_parent_id,
490- new_name, true, ctx);
491+ auto handler = new CopyMoveHandler(
492+ shared_from_this(), item_id, new_parent_id, new_name, true, ctx);
493 return handler->get_future();
494 }
495
496
497=== modified file 'src/DavProvider.h'
498--- src/DavProvider.h 2016-11-15 11:32:58 +0000
499+++ src/DavProvider.h 2016-11-18 02:39:30 +0000
500@@ -78,4 +78,7 @@
501
502 protected:
503 std::unique_ptr<QNetworkAccessManager> const network_;
504+
505+private:
506+ inline std::shared_ptr<DavProvider> shared_from_this();
507 };
508
509=== modified file 'src/DavUploadJob.cpp'
510--- src/DavUploadJob.cpp 2016-11-15 11:32:58 +0000
511+++ src/DavUploadJob.cpp 2016-11-18 02:39:30 +0000
512@@ -2,6 +2,7 @@
513 #include "DavProvider.h"
514 #include "RetrieveMetadataHandler.h"
515 #include "item_id.h"
516+#include "http_error.h"
517
518 #include <unity/storage/provider/Exceptions.h>
519
520@@ -25,12 +26,12 @@
521
522 }
523
524-DavUploadJob::DavUploadJob(DavProvider const& provider, string const& item_id,
525- int64_t size, string const& content_type,
526- bool allow_overwrite, string const& old_etag,
527- Context const& ctx)
528+DavUploadJob::DavUploadJob(shared_ptr<DavProvider> const& provider,
529+ string const& item_id, int64_t size,
530+ string const& content_type, bool allow_overwrite,
531+ string const& old_etag, Context const& ctx)
532 : QObject(), UploadJob(make_upload_id()), provider_(provider),
533- item_id_(item_id), base_url_(provider.base_url(ctx)), size_(size),
534+ item_id_(item_id), base_url_(provider->base_url(ctx)), size_(size),
535 context_(ctx)
536 {
537 QNetworkRequest request(id_to_url(item_id, base_url_));
538@@ -54,27 +55,15 @@
539
540 reader_.setSocketDescriptor(
541 dup(read_socket()), QLocalSocket::ConnectedState, QIODevice::ReadOnly);
542- reply_.reset(provider.send_request(
543+ reply_.reset(provider->send_request(
544 request, QByteArrayLiteral("PUT"), &reader_, ctx));
545 assert(reply_.get() != nullptr);
546- connect(reply_.get(), static_cast<void(QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error),
547- this, &DavUploadJob::onReplyError);
548 connect(reply_.get(), &QNetworkReply::finished,
549 this, &DavUploadJob::onReplyFinished);
550 }
551
552 DavUploadJob::~DavUploadJob() = default;
553
554-void DavUploadJob::onReplyError(QNetworkReply::NetworkError code)
555-{
556- if (promise_set_)
557- {
558- return;
559- }
560- promise_.set_exception(RemoteCommsException("Error from QNetworkReply: " + to_string(code)));
561- promise_set_ = true;
562-}
563-
564 void DavUploadJob::onReplyFinished()
565 {
566 if (promise_set_)
567@@ -85,7 +74,8 @@
568 // Is this a success status code?
569 if (status / 100 != 2)
570 {
571- promise_.set_exception(RemoteCommsException("Error from PUT: " + to_string(status)));
572+ promise_.set_exception(
573+ translate_http_error(reply_.get(), QByteArray(), item_id_));
574 promise_set_ = true;
575 return;
576 }
577
578=== modified file 'src/DavUploadJob.h'
579--- src/DavUploadJob.h 2016-11-01 02:53:02 +0000
580+++ src/DavUploadJob.h 2016-11-18 02:39:30 +0000
581@@ -18,9 +18,10 @@
582 {
583 Q_OBJECT
584 public:
585- DavUploadJob(DavProvider const& provider, std::string const& item_id,
586- int64_t size, std::string const& content_type,
587- bool allow_overwrite, std::string const& old_etag,
588+ DavUploadJob(std::shared_ptr<DavProvider> const& provider,
589+ std::string const& item_id, int64_t size,
590+ std::string const& content_type, bool allow_overwrite,
591+ std::string const& old_etag,
592 unity::storage::provider::Context const& ctx);
593 ~DavUploadJob();
594
595@@ -28,11 +29,10 @@
596 boost::future<unity::storage::provider::Item> finish() override;
597
598 private Q_SLOTS:
599- void onReplyError(QNetworkReply::NetworkError code);
600 void onReplyFinished();
601
602 private:
603- DavProvider const& provider_;
604+ std::shared_ptr<DavProvider> const provider_;
605 std::string const item_id_;
606 QUrl const base_url_;
607 int64_t const size_;
608
609=== modified file 'src/DeleteHandler.cpp'
610--- src/DeleteHandler.cpp 2016-11-11 07:06:07 +0000
611+++ src/DeleteHandler.cpp 2016-11-18 02:39:30 +0000
612@@ -1,22 +1,20 @@
613 #include "DeleteHandler.h"
614-
615-#include <unity/storage/provider/Exceptions.h>
616-
617 #include "DavProvider.h"
618 #include "item_id.h"
619+#include "http_error.h"
620
621 using namespace std;
622 using namespace unity::storage::provider;
623
624-DeleteHandler::DeleteHandler(DavProvider const& provider,
625+DeleteHandler::DeleteHandler(shared_ptr<DavProvider> const& provider,
626 string const& item_id,
627 Context const& ctx)
628 : provider_(provider), item_id_(item_id)
629 {
630- QUrl const base_url = provider.base_url(ctx);
631+ QUrl const base_url = provider->base_url(ctx);
632 QNetworkRequest request(id_to_url(item_id_, base_url));
633- reply_.reset(provider.send_request(request, QByteArrayLiteral("DELETE"),
634- nullptr, ctx));
635+ reply_.reset(provider->send_request(request, QByteArrayLiteral("DELETE"),
636+ nullptr, ctx));
637 connect(reply_.get(), &QNetworkReply::finished,
638 this, &DeleteHandler::onFinished);
639 }
640@@ -38,7 +36,8 @@
641 }
642 else
643 {
644- promise_.set_exception(RemoteCommsException("Error from DELETE: " + to_string(status)));
645+ promise_.set_exception(
646+ translate_http_error(reply_.get(), QByteArray(), item_id_));
647 }
648
649 deleteLater();
650
651=== modified file 'src/DeleteHandler.h'
652--- src/DeleteHandler.h 2016-11-11 03:53:48 +0000
653+++ src/DeleteHandler.h 2016-11-18 02:39:30 +0000
654@@ -12,7 +12,7 @@
655 class DeleteHandler : public QObject {
656 Q_OBJECT
657 public:
658- DeleteHandler(DavProvider const& provider, std::string const& item_id,
659+ DeleteHandler(std::shared_ptr<DavProvider> const& provider, std::string const& item_id,
660 unity::storage::provider::Context const& ctx);
661 ~DeleteHandler();
662
663@@ -24,7 +24,7 @@
664 private:
665 boost::promise<void> promise_;
666
667- DavProvider const& provider_;
668+ std::shared_ptr<DavProvider> const provider_;
669 std::string const item_id_;
670
671 std::unique_ptr<QNetworkReply> reply_;
672
673=== modified file 'src/ListHandler.cpp'
674--- src/ListHandler.cpp 2016-10-31 01:40:28 +0000
675+++ src/ListHandler.cpp 2016-11-18 02:39:30 +0000
676@@ -8,9 +8,9 @@
677 using namespace std;
678 using namespace unity::storage::provider;
679
680-ListHandler::ListHandler(DavProvider const& provider,
681+ListHandler::ListHandler(std::shared_ptr<DavProvider> const& provider,
682 string const& parent_id, Context const& ctx)
683- : PropFindHandler(provider, parent_id, 1, ctx), parent_id_(parent_id)
684+ : PropFindHandler(provider, parent_id, 1, ctx)
685 {
686 }
687
688@@ -34,7 +34,7 @@
689 // itself, so remove it from the list.
690 items_.erase(remove_if(items_.begin(), items_.end(),
691 [&](Item const& item) -> bool {
692- return item.item_id == parent_id_;
693+ return item.item_id == item_id_;
694 }), items_.end());
695
696 promise_.set_value(make_tuple(move(items_), string()));
697
698=== modified file 'src/ListHandler.h'
699--- src/ListHandler.h 2016-10-31 07:32:53 +0000
700+++ src/ListHandler.h 2016-11-18 02:39:30 +0000
701@@ -10,7 +10,7 @@
702 class ListHandler : public PropFindHandler {
703 Q_OBJECT
704 public:
705- ListHandler(DavProvider const& provider,
706+ ListHandler(std::shared_ptr<DavProvider> const& provider,
707 std::string const& parent_id,
708 unity::storage::provider::Context const& ctx);
709 ~ListHandler();
710@@ -18,7 +18,6 @@
711 boost::future<std::tuple<unity::storage::provider::ItemList,std::string>> get_future();
712
713 private:
714- std::string const parent_id_;
715 boost::promise<std::tuple<unity::storage::provider::ItemList,std::string>> promise_;
716
717 protected:
718
719=== modified file 'src/LookupHandler.cpp'
720--- src/LookupHandler.cpp 2016-09-28 01:30:31 +0000
721+++ src/LookupHandler.cpp 2016-11-18 02:39:30 +0000
722@@ -5,7 +5,7 @@
723 using namespace std;
724 using namespace unity::storage::provider;
725
726-LookupHandler::LookupHandler(DavProvider const& provider,
727+LookupHandler::LookupHandler(std::shared_ptr<DavProvider> const& provider,
728 string const& item_id, Context const& ctx)
729 : PropFindHandler(provider, item_id, 0, ctx)
730 {
731
732=== modified file 'src/LookupHandler.h'
733--- src/LookupHandler.h 2016-10-31 07:32:53 +0000
734+++ src/LookupHandler.h 2016-11-18 02:39:30 +0000
735@@ -9,7 +9,7 @@
736 class LookupHandler : public PropFindHandler {
737 Q_OBJECT
738 public:
739- LookupHandler(DavProvider const& provider,
740+ LookupHandler(std::shared_ptr<DavProvider> const& provider,
741 std::string const& item_id,
742 unity::storage::provider::Context const& ctx);
743 ~LookupHandler();
744
745=== modified file 'src/MetadataHandler.cpp'
746--- src/MetadataHandler.cpp 2016-10-31 01:40:28 +0000
747+++ src/MetadataHandler.cpp 2016-11-18 02:39:30 +0000
748@@ -5,9 +5,9 @@
749 using namespace std;
750 using namespace unity::storage::provider;
751
752-MetadataHandler::MetadataHandler(DavProvider const& provider,
753+MetadataHandler::MetadataHandler(shared_ptr<DavProvider> const& provider,
754 string const& item_id, Context const& ctx)
755- : PropFindHandler(provider, item_id, 0, ctx), item_id_(item_id)
756+ : PropFindHandler(provider, item_id, 0, ctx)
757 {
758 }
759
760
761=== modified file 'src/MetadataHandler.h'
762--- src/MetadataHandler.h 2016-10-31 07:32:53 +0000
763+++ src/MetadataHandler.h 2016-11-18 02:39:30 +0000
764@@ -9,7 +9,7 @@
765 class MetadataHandler : public PropFindHandler {
766 Q_OBJECT
767 public:
768- MetadataHandler(DavProvider const& provider,
769+ MetadataHandler(std::shared_ptr<DavProvider> const& provider,
770 std::string const& item_id,
771 unity::storage::provider::Context const& ctx);
772 ~MetadataHandler();
773@@ -17,7 +17,6 @@
774 boost::future<unity::storage::provider::Item> get_future();
775
776 private:
777- std::string const item_id_;
778 boost::promise<unity::storage::provider::Item> promise_;
779
780 protected:
781
782=== modified file 'src/PropFindHandler.cpp'
783--- src/PropFindHandler.cpp 2016-10-31 07:32:08 +0000
784+++ src/PropFindHandler.cpp 2016-11-18 02:39:30 +0000
785@@ -1,5 +1,6 @@
786 #include "PropFindHandler.h"
787 #include "item_id.h"
788+#include "http_error.h"
789
790 #include <cassert>
791
792@@ -21,10 +22,12 @@
793 </D:propfind>)");
794 }
795
796-PropFindHandler::PropFindHandler(DavProvider const& provider, string const& item_id, int depth, Context const& ctx)
797- : provider_(provider), base_url_(provider.base_url(ctx))
798+PropFindHandler::PropFindHandler(shared_ptr<DavProvider> const& provider,
799+ string const& item_id, int depth,
800+ Context const& ctx)
801+ : provider_(provider), base_url_(provider->base_url(ctx)), item_id_(item_id)
802 {
803- QNetworkRequest request(id_to_url(item_id, base_url_));
804+ QNetworkRequest request(id_to_url(item_id_, base_url_));
805 request.setRawHeader(QByteArrayLiteral("Depth"), QByteArray::number(depth));
806 request.setHeader(QNetworkRequest::ContentTypeHeader,
807 QStringLiteral("application/xml; charset=\"utf-8\""));
808@@ -32,14 +35,14 @@
809 request_body_.setData(PROPFIND_BODY);
810 request_body_.open(QIODevice::ReadOnly);
811
812- reply_.reset(provider.send_request(request, QByteArrayLiteral("PROPFIND"),
813- &request_body_, ctx));
814+ reply_.reset(provider->send_request(request, QByteArrayLiteral("PROPFIND"),
815+ &request_body_, ctx));
816 assert(reply_.get() != nullptr);
817
818- connect(reply_.get(), static_cast<void(QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error),
819- this, &PropFindHandler::onError);
820 connect(reply_.get(), &QIODevice::readyRead,
821- this, &PropFindHandler::onReadyRead);
822+ this, &PropFindHandler::onReplyReadyRead);
823+ connect(reply_.get(), &QNetworkReply::finished,
824+ this, &PropFindHandler::onReplyFinished);
825 }
826
827 PropFindHandler::~PropFindHandler()
828@@ -58,12 +61,17 @@
829
830 void PropFindHandler::reportError(StorageException const& error)
831 {
832+ reportError(boost::copy_exception(error));
833+}
834+
835+void PropFindHandler::reportError(boost::exception_ptr const& ep)
836+{
837 if (finished_)
838 {
839 return;
840 }
841
842- error_ = boost::copy_exception(error);
843+ error_ = ep;
844 finished_ = true;
845 finish();
846 }
847@@ -79,28 +87,46 @@
848 finish();
849 }
850
851-void PropFindHandler::onError(QNetworkReply::NetworkError code)
852+void PropFindHandler::onReplyReadyRead()
853 {
854- reportError(RemoteCommsException("Error from QNetworkReply: " +
855- to_string(code)));
856+ if (!seen_headers_)
857+ {
858+ seen_headers_ = true;
859+ auto status = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
860+ if (status == 207)
861+ {
862+ disconnect(reply_.get(), &QIODevice::readyRead,
863+ this, &PropFindHandler::onReplyReadyRead);
864+ parser_.reset(new MultiStatusParser(reply_->request().url(), reply_.get()));
865+ connect(parser_.get(), &MultiStatusParser::response,
866+ this, &PropFindHandler::onParserResponse);
867+ connect(parser_.get(), &MultiStatusParser::finished,
868+ this, &PropFindHandler::onParserFinished);
869+ }
870+ else
871+ {
872+ is_error_ = true;
873+ }
874+ }
875+ if (is_error_)
876+ {
877+ if (error_body_.size() < MAX_ERROR_BODY_LENGTH)
878+ {
879+ error_body_.append(reply_->readAll());
880+ }
881+ else
882+ {
883+ reply_->close();
884+ }
885+ }
886 }
887
888-void PropFindHandler::onReadyRead()
889+void PropFindHandler::onReplyFinished()
890 {
891- disconnect(reply_.get(), &QIODevice::readyRead,
892- this, &PropFindHandler::onReadyRead);
893- auto status = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
894-
895- if (status != 207)
896+ if (!seen_headers_ || is_error_)
897 {
898- reportError(RemoteCommsException("Expected 207 response, but got " + to_string(status)));
899- return;
900+ reportError(translate_http_error(reply_.get(), error_body_, item_id_));
901 }
902- parser_.reset(new MultiStatusParser(reply_->request().url(), reply_.get()));
903- connect(parser_.get(), &MultiStatusParser::response,
904- this, &PropFindHandler::onParserResponse);
905- connect(parser_.get(), &MultiStatusParser::finished,
906- this, &PropFindHandler::onParserFinished);
907 }
908
909 void PropFindHandler::onParserResponse(QUrl const& href, vector<MultiStatusProperty> const& properties, int status)
910@@ -112,7 +138,7 @@
911 }
912 try
913 {
914- Item item = provider_.make_item(href, base_url_, properties);
915+ Item item = provider_->make_item(href, base_url_, properties);
916 items_.emplace_back(move(item));
917 }
918 catch (StorageException const& error)
919
920=== modified file 'src/PropFindHandler.h'
921--- src/PropFindHandler.h 2016-10-31 07:32:08 +0000
922+++ src/PropFindHandler.h 2016-11-18 02:39:30 +0000
923@@ -13,7 +13,7 @@
924 class PropFindHandler : public QObject {
925 Q_OBJECT
926 public:
927- PropFindHandler(DavProvider const& provider,
928+ PropFindHandler(std::shared_ptr<DavProvider> const& provider,
929 std::string const& item_id, int depth,
930 unity::storage::provider::Context const& ctx);
931 ~PropFindHandler();
932@@ -22,8 +22,8 @@
933
934 private Q_SLOTS:
935 // From QNetworkReply
936- void onError(QNetworkReply::NetworkError code);
937- void onReadyRead();
938+ void onReplyReadyRead();
939+ void onReplyFinished();
940
941 // From MultiStatusParser
942 void onParserResponse(QUrl const& href, std::vector<MultiStatusProperty> const& properties, int status);
943@@ -31,20 +31,25 @@
944
945 private:
946 void reportError(unity::storage::provider::StorageException const& error);
947+ void reportError(boost::exception_ptr const& ep);
948 void reportSuccess();
949
950+ bool seen_headers_ = false;
951+ bool is_error_ = false;
952 bool finished_ = false;
953 boost::promise<unity::storage::provider::ItemList> promise_;
954
955- DavProvider const& provider_;
956+ std::shared_ptr<DavProvider> const provider_;
957 QUrl base_url_;
958 QBuffer request_body_;
959 std::unique_ptr<QNetworkReply> reply_;
960 std::unique_ptr<MultiStatusParser> parser_;
961+ QByteArray error_body_;
962
963 protected:
964 virtual void finish() = 0;
965
966+ std::string const item_id_;
967 unity::storage::provider::ItemList items_;
968 boost::exception_ptr error_;
969 };
970
971=== modified file 'src/RetrieveMetadataHandler.cpp'
972--- src/RetrieveMetadataHandler.cpp 2016-11-01 02:53:02 +0000
973+++ src/RetrieveMetadataHandler.cpp 2016-11-18 02:39:30 +0000
974@@ -7,12 +7,11 @@
975 using namespace std;
976 using namespace unity::storage::provider;
977
978-RetrieveMetadataHandler::RetrieveMetadataHandler(DavProvider const& provider,
979+RetrieveMetadataHandler::RetrieveMetadataHandler(shared_ptr<DavProvider> const& provider,
980 string const& item_id,
981 Context const& ctx,
982 Callback callback)
983- : PropFindHandler(provider, item_id, 0, ctx), item_id_(item_id),
984- callback_(callback)
985+ : PropFindHandler(provider, item_id, 0, ctx), callback_(callback)
986 {
987 }
988
989@@ -22,7 +21,7 @@
990 {
991 Item item;
992 boost::exception_ptr ex = error_;
993-
994+
995 if (items_.size() != 1)
996 {
997 ex = boost::copy_exception(RemoteCommsException("Unexpectedly received " + to_string(items_.size()) + " items from PROPFIND request"));
998
999=== modified file 'src/RetrieveMetadataHandler.h'
1000--- src/RetrieveMetadataHandler.h 2016-11-01 02:53:02 +0000
1001+++ src/RetrieveMetadataHandler.h 2016-11-18 02:39:30 +0000
1002@@ -13,14 +13,13 @@
1003 typedef std::function<void(unity::storage::provider::Item const& item,
1004 boost::exception_ptr const& error)> Callback;
1005
1006- RetrieveMetadataHandler(DavProvider const& provider,
1007+ RetrieveMetadataHandler(std::shared_ptr<DavProvider> const& provider,
1008 std::string const& item_id,
1009 unity::storage::provider::Context const& ctx,
1010 Callback callback);
1011 ~RetrieveMetadataHandler();
1012
1013 private:
1014- std::string const item_id_;
1015 Callback const callback_;
1016
1017 protected:
1018
1019=== modified file 'src/RootsHandler.cpp'
1020--- src/RootsHandler.cpp 2016-10-31 01:40:28 +0000
1021+++ src/RootsHandler.cpp 2016-11-18 02:39:30 +0000
1022@@ -5,7 +5,8 @@
1023 using namespace std;
1024 using namespace unity::storage::provider;
1025
1026-RootsHandler::RootsHandler(DavProvider const& provider, Context const& ctx)
1027+RootsHandler::RootsHandler(shared_ptr<DavProvider> const& provider,
1028+ Context const& ctx)
1029 : PropFindHandler(provider, ".", 0, ctx)
1030 {
1031 }
1032
1033=== modified file 'src/RootsHandler.h'
1034--- src/RootsHandler.h 2016-10-31 07:32:53 +0000
1035+++ src/RootsHandler.h 2016-11-18 02:39:30 +0000
1036@@ -9,7 +9,7 @@
1037 class RootsHandler : public PropFindHandler {
1038 Q_OBJECT
1039 public:
1040- RootsHandler(DavProvider const& provider,
1041+ RootsHandler(std::shared_ptr<DavProvider> const& provider,
1042 unity::storage::provider::Context const& ctx);
1043 ~RootsHandler();
1044
1045
1046=== added file 'src/http_error.cpp'
1047--- src/http_error.cpp 1970-01-01 00:00:00 +0000
1048+++ src/http_error.cpp 2016-11-18 02:39:30 +0000
1049@@ -0,0 +1,163 @@
1050+#include "http_error.h"
1051+
1052+#include <QNetworkReply>
1053+#include <QXmlDefaultHandler>
1054+#include <QXmlInputSource>
1055+#include <QXmlSimpleReader>
1056+#include <unity/storage/provider/Exceptions.h>
1057+
1058+#include <cassert>
1059+
1060+using namespace std;
1061+using namespace unity::storage::provider;
1062+
1063+namespace {
1064+
1065+class ErrorParser : public QXmlDefaultHandler
1066+{
1067+public:
1068+ bool startElement(QString const& namespace_uri, QString const& local_name,
1069+ QString const& qname, QXmlAttributes const& attrs) override;
1070+ bool endElement(QString const& namespace_uri, QString const& local_name,
1071+ QString const& qname) override;
1072+ bool characters(QString const& data) override;
1073+
1074+ string exception_;
1075+ string message_;
1076+
1077+private:
1078+ static QString const SABREDAV_NS;
1079+
1080+ QString char_data_;
1081+};
1082+
1083+QString const ErrorParser::SABREDAV_NS("http://sabredav.org/ns");
1084+
1085+bool ErrorParser::startElement(QString const& namespace_uri,
1086+ QString const& local_name,
1087+ QString const& qname,
1088+ QXmlAttributes const& attrs)
1089+{
1090+ Q_UNUSED(namespace_uri);
1091+ Q_UNUSED(local_name);
1092+ Q_UNUSED(qname);
1093+ Q_UNUSED(attrs);
1094+
1095+ char_data_.clear();
1096+ return true;
1097+}
1098+
1099+bool ErrorParser::endElement(QString const& namespace_uri,
1100+ QString const& local_name,
1101+ QString const& qname)
1102+{
1103+ Q_UNUSED(qname);
1104+ if (namespace_uri == SABREDAV_NS)
1105+ {
1106+ if (local_name == "exception")
1107+ {
1108+ exception_ = char_data_.toStdString();
1109+ }
1110+ else if (local_name == "message")
1111+ {
1112+ message_ = char_data_.toStdString();
1113+ }
1114+ }
1115+ char_data_.clear();
1116+ return true;
1117+}
1118+
1119+bool ErrorParser::characters(QString const& data)
1120+{
1121+ char_data_ += data;
1122+ return true;
1123+}
1124+
1125+std::string make_message(QNetworkReply *reply,
1126+ QByteArray body)
1127+{
1128+ if (body.isEmpty())
1129+ {
1130+ body = reply->readAll();
1131+ }
1132+ auto content_type = reply->header(QNetworkRequest::ContentTypeHeader).toString();
1133+ int semicolon = content_type.indexOf(';');
1134+ if (semicolon >= 0)
1135+ {
1136+ content_type = content_type.left(semicolon);
1137+ }
1138+ if (content_type == "text/plain")
1139+ {
1140+ return body.toStdString();
1141+ }
1142+ else if (content_type == "application/xml")
1143+ {
1144+ ErrorParser parser;
1145+ QXmlSimpleReader reader;
1146+ QXmlInputSource source;
1147+ reader.setContentHandler(&parser);
1148+ source.setData(body);
1149+ if (reader.parse(source)) {
1150+ return parser.exception_ + ": " + parser.message_;
1151+ }
1152+ }
1153+ // Fall back to returning the HTTP status string
1154+ return reply->attribute(
1155+ QNetworkRequest::HttpReasonPhraseAttribute).toString().toStdString();
1156+}
1157+
1158+}
1159+
1160+boost::exception_ptr translate_http_error(QNetworkReply *reply,
1161+ QByteArray const& body,
1162+ string const& item_id)
1163+{
1164+ assert(reply != nullptr);
1165+ assert(reply->isFinished());
1166+
1167+ auto status = reply->attribute(
1168+ QNetworkRequest::HttpStatusCodeAttribute).toInt();
1169+ if (status == 0)
1170+ {
1171+ return boost::copy_exception(
1172+ RemoteCommsException(reply->errorString().toStdString()));
1173+ }
1174+
1175+ auto message = make_message(reply, body);
1176+
1177+ switch (status)
1178+ {
1179+ case 400: // Bad Request
1180+ return boost::copy_exception(RemoteCommsException(message));
1181+
1182+ case 401: // Unauthorised
1183+ // This should be separate from Forbidden, triggering reauth.
1184+ case 403: // Forbidden
1185+ case 451: // Unavailable for Legal Reasons
1186+ return boost::copy_exception(PermissionException(message));
1187+
1188+ case 404: // Not found
1189+ case 410: // Gone
1190+ return boost::copy_exception(NotExistsException(message, item_id));
1191+
1192+ case 405: // Method Not Allowed
1193+ // MKCOL on an existing resource results in a 405 error
1194+ if (reply->request().attribute(QNetworkRequest::CustomVerbAttribute) == "MKCOL")
1195+ {
1196+ return boost::copy_exception(ExistsException(message, item_id, "fixme"));
1197+ }
1198+ break;
1199+
1200+ case 409: // Conflict
1201+ case 412: // Precondition failed
1202+ return boost::copy_exception(ConflictException(message));
1203+
1204+ case 507: // Insufficient Storage
1205+ return boost::copy_exception(QuotaException(message));
1206+
1207+ default:
1208+ break;
1209+ }
1210+ return boost::copy_exception(
1211+ UnknownException("HTTP " + to_string(status) + ": " + message));
1212+}
1213
1214=== added file 'src/http_error.h'
1215--- src/http_error.h 1970-01-01 00:00:00 +0000
1216+++ src/http_error.h 2016-11-18 02:39:30 +0000
1217@@ -0,0 +1,12 @@
1218+#pragma once
1219+
1220+#include <boost/thread.hpp>
1221+
1222+class QByteArray;
1223+class QNetworkReply;
1224+
1225+static constexpr int MAX_ERROR_BODY_LENGTH = 64 * 1024;
1226+
1227+boost::exception_ptr translate_http_error(QNetworkReply *reply,
1228+ QByteArray const& body,
1229+ std::string const& item_id={});
1230
1231=== modified file 'tests/CMakeLists.txt'
1232--- tests/CMakeLists.txt 2016-09-23 03:28:37 +0000
1233+++ tests/CMakeLists.txt 2016-11-18 02:39:30 +0000
1234@@ -15,6 +15,7 @@
1235 multistatus
1236 item_id
1237 davprovider
1238+ http_error
1239 )
1240
1241 set(UNIT_TEST_TARGETS "")
1242
1243=== modified file 'tests/davprovider/davprovider_test.cpp'
1244--- tests/davprovider/davprovider_test.cpp 2016-11-12 08:19:57 +0000
1245+++ tests/davprovider/davprovider_test.cpp 2016-11-18 02:39:30 +0000
1246@@ -95,7 +95,7 @@
1247
1248 dav_env_.reset(new DavEnvironment(tmp_dir_->path()));
1249 provider_env_.reset(new ProviderEnvironment(
1250- unique_ptr<provider::ProviderBase>(new TestDavProvider(dav_env_->base_url())),
1251+ make_shared<TestDavProvider>(dav_env_->base_url()),
1252 1, *dbus_env_));
1253 }
1254
1255@@ -171,8 +171,7 @@
1256 auto account = get_client();
1257 make_file("foo.txt");
1258 make_file("bar.txt");
1259- //make_dir("folder");
1260- make_file("folder");
1261+ make_dir("folder");
1262 make_file("I\u00F1t\u00EBrn\u00E2ti\u00F4n\u00E0liz\u00E6ti\u00F8n");
1263
1264 shared_ptr<Root> root;
1265@@ -217,16 +216,10 @@
1266 EXPECT_EQ("bar.txt", items[1]->name());
1267 EXPECT_EQ(ItemType::file, items[1]->type());
1268
1269- // FIXME: SabreDAV doesn't provide an ETag for folders, which
1270- // currently trips up storage-framework's client side metadata
1271- // validation.
1272-
1273- //EXPECT_EQ("folder/", items[2]->native_identity());
1274- EXPECT_EQ("folder", items[2]->native_identity());
1275+ EXPECT_EQ("folder/", items[2]->native_identity());
1276 EXPECT_EQ(".", items[2]->parent_ids().at(0));
1277 EXPECT_EQ("folder", items[2]->name());
1278- //EXPECT_EQ(ItemType::folder, items[2]->type());
1279- EXPECT_EQ(ItemType::file, items[2]->type());
1280+ EXPECT_EQ(ItemType::folder, items[2]->type());
1281
1282 EXPECT_EQ("foo.txt", items[3]->native_identity());
1283 EXPECT_EQ(".", items[3]->parent_ids().at(0));
1284@@ -309,6 +302,46 @@
1285 EXPECT_EQ(ItemType::file, item->type());
1286 }
1287
1288+TEST_F(DavProviderTests, metadata_not_found)
1289+{
1290+ auto account = get_client();
1291+
1292+ shared_ptr<Root> root;
1293+ {
1294+ QFutureWatcher<QVector<shared_ptr<Root>>> watcher;
1295+ QSignalSpy spy(&watcher, &decltype(watcher)::finished);
1296+ watcher.setFuture(account->roots());
1297+ if (spy.count() == 0)
1298+ {
1299+ ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
1300+ }
1301+ auto roots = watcher.result();
1302+ ASSERT_EQ(1, roots.size());
1303+ root = roots[0];
1304+ }
1305+
1306+ shared_ptr<Item> item;
1307+ {
1308+ QFutureWatcher<shared_ptr<Item>> watcher;
1309+ QSignalSpy spy(&watcher, &decltype(watcher)::finished);
1310+ watcher.setFuture(root->get("foo.txt"));
1311+ if (spy.count() == 0)
1312+ {
1313+ ASSERT_TRUE(spy.wait(SIGNAL_WAIT_TIME));
1314+ }
1315+ try
1316+ {
1317+ watcher.result();
1318+ FAIL();
1319+ }
1320+ catch (NotExistsException const& e)
1321+ {
1322+ EXPECT_TRUE(e.error_message().startsWith("Sabre\\DAV\\Exception\\NotFound: "))
1323+ << e.error_message().toStdString();
1324+ }
1325+ }
1326+}
1327+
1328 TEST_F(DavProviderTests, create_folder)
1329 {
1330 auto account = get_client();
1331@@ -327,11 +360,6 @@
1332 root = roots[0];
1333 }
1334
1335- // FIXME: the test webdav server returns no ETag for folders, so
1336- // the client throws away the create_folder() response. We can
1337- // reenable this when porting to storage-framework 0.2.
1338- return;
1339-
1340 shared_ptr<Folder> folder;
1341 {
1342 QFutureWatcher<shared_ptr<Folder>> watcher;
1343@@ -382,9 +410,9 @@
1344 watcher.result();
1345 FAIL();
1346 }
1347- catch (RemoteCommsException const& e)
1348+ catch (ExistsException const& e)
1349 {
1350- EXPECT_EQ("Error from MKCOL: 405", e.error_message());
1351+ EXPECT_EQ("Sabre\\DAV\\Exception\\MethodNotAllowed: The resource you tried to create already exists", e.error_message());
1352 }
1353 }
1354 }
1355@@ -421,9 +449,9 @@
1356 watcher.result();
1357 FAIL();
1358 }
1359- catch (RemoteCommsException const& e)
1360+ catch (ExistsException const& e)
1361 {
1362- EXPECT_EQ("Error from MKCOL: 405", e.error_message());
1363+ EXPECT_EQ("Sabre\\DAV\\Exception\\MethodNotAllowed: The resource you tried to create already exists", e.error_message());
1364 }
1365 }
1366 }
1367@@ -537,9 +565,9 @@
1368 watcher.result();
1369 FAIL();
1370 }
1371- catch (RemoteCommsException const& e)
1372+ catch (ConflictException const& e)
1373 {
1374- EXPECT_EQ("Error from QNetworkReply: 299", e.error_message());
1375+ EXPECT_EQ("Sabre\\DAV\\Exception\\PreconditionFailed: An If-None-Match header was specified, but the ETag matched (or * was specified).", e.error_message());
1376 }
1377 }
1378 }
1379@@ -686,9 +714,9 @@
1380 watcher.result();
1381 FAIL();
1382 }
1383- catch (RemoteCommsException const& e)
1384+ catch (ConflictException const& e)
1385 {
1386- EXPECT_EQ("Error from QNetworkReply: 299", e.error_message());
1387+ EXPECT_EQ("Sabre\\DAV\\Exception\\PreconditionFailed: An If-Match header was specified, but none of the specified the ETags matched.", e.error_message());
1388 }
1389 }
1390 }
1391@@ -738,7 +766,7 @@
1392 }
1393 catch (RemoteCommsException const& e)
1394 {
1395- EXPECT_EQ("Error from QNetworkReply: 99", e.error_message());
1396+ EXPECT_EQ("Unknown error", e.error_message());
1397 }
1398 }
1399 }
1400@@ -1017,9 +1045,10 @@
1401 watcher.waitForFinished(); // to check for errors
1402 FAIL();
1403 }
1404- catch (RemoteCommsException const& e)
1405+ catch (NotExistsException const& e)
1406 {
1407- EXPECT_EQ("Unexpected status code: 404", e.error_message());
1408+ EXPECT_TRUE(e.error_message().startsWith("Sabre\\DAV\\Exception\\NotFound: "))
1409+ << e.error_message().toStdString();
1410 }
1411 }
1412 }
1413@@ -1117,9 +1146,11 @@
1414 watcher.waitForFinished(); // to catch any errors
1415 FAIL();
1416 }
1417- catch (RemoteCommsException const& e)
1418+ catch (NotExistsException const& e)
1419 {
1420- EXPECT_EQ("Error from DELETE: 404", e.error_message());
1421+ EXPECT_TRUE(e.error_message().startsWith(
1422+ "Sabre\\DAV\\Exception\\NotFound: "))
1423+ << e.error_message().toStdString();
1424 }
1425 }
1426 }
1427
1428=== added directory 'tests/http_error'
1429=== added file 'tests/http_error/CMakeLists.txt'
1430--- tests/http_error/CMakeLists.txt 1970-01-01 00:00:00 +0000
1431+++ tests/http_error/CMakeLists.txt 2016-11-18 02:39:30 +0000
1432@@ -0,0 +1,8 @@
1433+add_executable(http_error_test http_error_test.cpp)
1434+target_link_libraries(http_error_test
1435+ dav-provider-lib
1436+ testutils
1437+ Qt5::Test
1438+ gtest
1439+)
1440+add_test(http_error_test http_error_test)
1441
1442=== added file 'tests/http_error/http_error_test.cpp'
1443--- tests/http_error/http_error_test.cpp 1970-01-01 00:00:00 +0000
1444+++ tests/http_error/http_error_test.cpp 2016-11-18 02:39:30 +0000
1445@@ -0,0 +1,260 @@
1446+#include "../../src/http_error.h"
1447+
1448+#include <gtest/gtest.h>
1449+#include <QBuffer>
1450+#include <QByteArray>
1451+#include <QCoreApplication>
1452+#include <QNetworkReply>
1453+#include <QNetworkRequest>
1454+#include <unity/storage/provider/Exceptions.h>
1455+
1456+using namespace std;
1457+using namespace unity::storage::provider;
1458+
1459+class FakeReply : public QNetworkReply
1460+{
1461+public:
1462+ FakeReply(QString const& method, int status, QString const& reason,
1463+ QString const& content_type, QByteArray const& body)
1464+ {
1465+ QNetworkRequest request;
1466+ request.setAttribute(QNetworkRequest::CustomVerbAttribute, method);
1467+ setRequest(request);
1468+
1469+ setAttribute(QNetworkRequest::HttpStatusCodeAttribute, status);
1470+ setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, reason);
1471+
1472+ setHeader(QNetworkRequest::ContentTypeHeader, content_type);
1473+ setHeader(QNetworkRequest::ContentLengthHeader, body.size());
1474+ buffer_.setData(body);
1475+ buffer_.open(QIODevice::ReadOnly);
1476+ open(QIODevice::ReadOnly);
1477+ setFinished(true);
1478+ }
1479+
1480+ FakeReply(QNetworkReply::NetworkError code, QString const& message)
1481+ {
1482+ setError(code, message);
1483+ setFinished(true);
1484+ }
1485+
1486+ void abort() override
1487+ {
1488+ close();
1489+ }
1490+
1491+ qint64 bytesAvailable() const override
1492+ {
1493+ return QNetworkReply::bytesAvailable() + buffer_.bytesAvailable();
1494+ }
1495+
1496+ bool isSequential() const override
1497+ {
1498+ return true;
1499+ }
1500+
1501+ qint64 size() const override
1502+ {
1503+ return buffer_.size();
1504+ }
1505+
1506+ qint64 readData(char *data, qint64 length) override
1507+ {
1508+ return buffer_.read(data, length);
1509+ }
1510+
1511+private:
1512+ QBuffer buffer_;
1513+};
1514+
1515+template <typename E>
1516+E expect_throw(boost::exception_ptr p)
1517+{
1518+ try
1519+ {
1520+ boost::rethrow_exception(p);
1521+ }
1522+ catch (E const& e)
1523+ {
1524+ return e;
1525+ }
1526+ throw std::runtime_error("No exception thrown");
1527+}
1528+
1529+
1530+TEST(HttpErrorTests, network_error)
1531+{
1532+ FakeReply reply(QNetworkReply::HostNotFoundError, "Host not found");
1533+
1534+ auto e = expect_throw<RemoteCommsException>(
1535+ translate_http_error(&reply, QByteArray()));
1536+ EXPECT_EQ("Host not found", e.error_message());
1537+}
1538+
1539+TEST(HttpErrorTests, text_plain_body)
1540+{
1541+ FakeReply reply("GET", 400, "Bad Request",
1542+ "text/plain;charset=US-ASCII", "error message");
1543+ auto e = expect_throw<RemoteCommsException>(
1544+ translate_http_error(&reply, QByteArray()));
1545+ EXPECT_EQ("error message", e.error_message());
1546+}
1547+
1548+TEST(HttpErrorTests, application_xml_body)
1549+{
1550+ static const char xml[] = R"(<?xml version="1.0" encoding="utf-8"?>
1551+<d:error xmlns:d='DAV:' xmlns:s='http://sabredav.org/ns'>
1552+ <s:exception>exception_name</s:exception>
1553+ <s:message>error message</s:message>
1554+ <s:sabredav-version>1.8.12</s:sabredav-version>
1555+</d:error>
1556+)";
1557+ FakeReply reply("GET", 400, "Bad Request",
1558+ "application/xml;charset=utf-8", xml);
1559+
1560+ auto e = expect_throw<RemoteCommsException>(
1561+ translate_http_error(&reply, QByteArray()));
1562+ EXPECT_EQ("exception_name: error message", e.error_message());
1563+}
1564+
1565+TEST(HttpErrorTests, fallback_body)
1566+{
1567+ FakeReply reply("GET", 400, "Bad Request",
1568+ "application/octet-stream", "foo");
1569+ auto e = expect_throw<RemoteCommsException>(
1570+ translate_http_error(&reply, QByteArray()));
1571+ EXPECT_EQ("Bad Request", e.error_message());
1572+}
1573+
1574+TEST(HttpErrorTests, body_provided_separately)
1575+{
1576+ static const char xml[] = R"(<?xml version="1.0" encoding="utf-8"?>
1577+<d:error xmlns:d='DAV:' xmlns:s='http://sabredav.org/ns'>
1578+ <s:exception>exception_name</s:exception>
1579+ <s:message>error message</s:message>
1580+ <s:sabredav-version>1.8.12</s:sabredav-version>
1581+</d:error>
1582+)";
1583+ FakeReply reply("GET", 400, "Bad Request",
1584+ "application/xml;charset=utf-8", "foo");
1585+
1586+ auto e = expect_throw<RemoteCommsException>(
1587+ translate_http_error(&reply, xml));
1588+ EXPECT_EQ("exception_name: error message", e.error_message());
1589+}
1590+
1591+TEST(HttpErrorTests, 401)
1592+{
1593+ FakeReply reply("GET", 401, "Unauthorised", "text/plain", "message");
1594+
1595+ auto e = expect_throw<PermissionException>(
1596+ translate_http_error(&reply, QByteArray()));
1597+ EXPECT_EQ("message", e.error_message());
1598+}
1599+
1600+TEST(HttpErrorTests, 403)
1601+{
1602+ FakeReply reply("GET", 403, "Forbidden", "text/plain", "message");
1603+
1604+ auto e = expect_throw<PermissionException>(
1605+ translate_http_error(&reply, QByteArray()));
1606+ EXPECT_EQ("message", e.error_message());
1607+}
1608+
1609+TEST(HttpErrorTests, 451)
1610+{
1611+ FakeReply reply("GET", 451, "Unavailable for Legal Reasons",
1612+ "text/plain", "message");
1613+
1614+ auto e = expect_throw<PermissionException>(
1615+ translate_http_error(&reply, QByteArray()));
1616+ EXPECT_EQ("message", e.error_message());
1617+}
1618+
1619+TEST(HttpErrorTests, 404)
1620+{
1621+ FakeReply reply("GET", 404, "Not Found", "text/plain", "File not found");
1622+
1623+ auto e = expect_throw<NotExistsException>(
1624+ translate_http_error(&reply, QByteArray(), "item_id"));
1625+ EXPECT_EQ("File not found", e.error_message());
1626+ EXPECT_EQ("item_id", e.key());
1627+}
1628+
1629+TEST(HttpErrorTests, 410)
1630+{
1631+ FakeReply reply("GET", 410, "Gone", "text/plain", "File not found");
1632+
1633+ auto e = expect_throw<NotExistsException>(
1634+ translate_http_error(&reply, QByteArray(), "item_id"));
1635+ EXPECT_EQ("File not found", e.error_message());
1636+ EXPECT_EQ("item_id", e.key());
1637+}
1638+
1639+TEST(HttpErrorTests, 405)
1640+{
1641+ // 405 for MKCOL means the target exists
1642+ {
1643+ FakeReply reply("MKCOL", 405, "Method Not Allowed",
1644+ "text/plain", "message");
1645+
1646+ auto e = expect_throw<ExistsException>(
1647+ translate_http_error(&reply, QByteArray(), "item_id"));
1648+ EXPECT_EQ("message", e.error_message());
1649+ EXPECT_EQ("item_id", e.native_identity());
1650+ }
1651+ // Otherwise, it is unknown
1652+ {
1653+ FakeReply reply("GET", 405, "Method Not Allowed",
1654+ "text/plain", "message");
1655+
1656+ auto e = expect_throw<UnknownException>(
1657+ translate_http_error(&reply, QByteArray(), "item_id"));
1658+ EXPECT_EQ("HTTP 405: message", e.error_message());
1659+ }
1660+}
1661+
1662+TEST(HttpErrorTests, 409)
1663+{
1664+ FakeReply reply("GET", 409, "Conflict", "text/plain", "message");
1665+
1666+ auto e = expect_throw<ConflictException>(
1667+ translate_http_error(&reply, QByteArray()));
1668+ EXPECT_EQ("message", e.error_message());
1669+}
1670+
1671+TEST(HttpErrorTests, 412)
1672+{
1673+ FakeReply reply("GET", 412, "Precondition failed", "text/plain", "message");
1674+
1675+ auto e = expect_throw<ConflictException>(
1676+ translate_http_error(&reply, QByteArray()));
1677+ EXPECT_EQ("message", e.error_message());
1678+}
1679+
1680+TEST(HttpErrorTests, 507)
1681+{
1682+ FakeReply reply("GET", 507, "Insufficient Storage",
1683+ "text/plain", "message");
1684+
1685+ auto e = expect_throw<QuotaException>(
1686+ translate_http_error(&reply, QByteArray()));
1687+ EXPECT_EQ("message", e.error_message());
1688+}
1689+
1690+TEST(HttpErrorTests, other)
1691+{
1692+ FakeReply reply("GET", 500, "Internal Server Error",
1693+ "text/plain", "message");
1694+
1695+ auto e = expect_throw<UnknownException>(
1696+ translate_http_error(&reply, QByteArray()));
1697+ EXPECT_EQ("HTTP 500: message", e.error_message());
1698+}
1699+
1700+int main(int argc, char**argv)
1701+{
1702+ QCoreApplication app(argc, argv);
1703+ ::testing::InitGoogleTest(&argc, argv);
1704+ return RUN_ALL_TESTS();
1705+}
1706
1707=== modified file 'tests/utils/ProviderEnvironment.cpp'
1708--- tests/utils/ProviderEnvironment.cpp 2016-09-28 15:26:03 +0000
1709+++ tests/utils/ProviderEnvironment.cpp 2016-11-18 02:39:30 +0000
1710@@ -16,7 +16,7 @@
1711
1712 }
1713
1714-ProviderEnvironment::ProviderEnvironment(unique_ptr<ProviderBase>&& provider,
1715+ProviderEnvironment::ProviderEnvironment(shared_ptr<ProviderBase> const& provider,
1716 OnlineAccounts::AccountId account_id,
1717 DBusEnvironment const& dbus_env)
1718 {
1719@@ -29,7 +29,7 @@
1720 account_manager_->waitForReady();
1721 OnlineAccounts::Account* account = account_manager_->account(account_id);
1722 assert(account != nullptr);
1723- server_.reset(new testing::TestServer(move(provider), account,
1724+ server_.reset(new testing::TestServer(provider, account,
1725 *server_connection_,
1726 OBJECT_PATH.toStdString()));
1727
1728
1729=== modified file 'tests/utils/ProviderEnvironment.h'
1730--- tests/utils/ProviderEnvironment.h 2016-09-19 11:01:16 +0000
1731+++ tests/utils/ProviderEnvironment.h 2016-11-18 02:39:30 +0000
1732@@ -12,7 +12,7 @@
1733 class ProviderEnvironment
1734 {
1735 public:
1736- ProviderEnvironment(std::unique_ptr<unity::storage::provider::ProviderBase>&& provider,
1737+ ProviderEnvironment(std::shared_ptr<unity::storage::provider::ProviderBase> const& provider,
1738 OnlineAccounts::AccountId account_id,
1739 DBusEnvironment const& dbus_env);
1740 ~ProviderEnvironment();

Subscribers

People subscribed via source and target branches