Merge lp:~mandel/ubuntu-download-manager/support-data-uri into lp:ubuntu-download-manager
- support-data-uri
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Michael Sheldon |
Approved revision: | 340 |
Merged at revision: | 335 |
Proposed branch: | lp:~mandel/ubuntu-download-manager/support-data-uri |
Merge into: | lp:ubuntu-download-manager |
Diff against target: |
1154 lines (+613/-232) 5 files modified
src/downloads/priv/ubuntu/downloads/file_download.cpp (+319/-188) src/downloads/priv/ubuntu/downloads/file_download.h (+3/-4) tests/downloads/daemon/CMakeLists.txt (+28/-28) tests/downloads/daemon/test_download.cpp (+254/-12) tests/downloads/daemon/test_download.h (+9/-0) |
To merge this branch: | bzr merge lp:~mandel/ubuntu-download-manager/support-data-uri |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Sheldon (community) | Approve | ||
PS Jenkins bot | continuous-integration | Approve | |
Review via email: mp+247317@code.launchpad.net |
Commit message
Add support for data uris.
Description of the change
Add support to data uri to download manager with out changing the client API/ABI. The idea is that a client can provide a data uri and the download manager will extract as much information as possible from it regarding the mime type etc.. and will write the data in the file system following the exact same security checks and supporting things such as the post processing of the files and its deflation.
- 336. By Manuel de la Peña
-
Remove no needed header added by the IDE. Remove some left over debug msgs.
PS Jenkins bot (ps-jenkins) wrote : | # |
- 337. By Manuel de la Peña
-
Deal with the case in which http is in the data uri.
- 338. By Manuel de la Peña
-
Ensure we use the entire path.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:337
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:338
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 339. By Manuel de la Peña
-
Remove log that brings not info.
- 340. By Manuel de la Peña
-
Add a comment stating the hack.
Michael Sheldon (michael-sheldon) wrote : | # |
Looks good :)
Preview Diff
1 | === modified file 'src/downloads/priv/ubuntu/downloads/file_download.cpp' |
2 | --- src/downloads/priv/ubuntu/downloads/file_download.cpp 2014-11-17 20:34:24 +0000 |
3 | +++ src/downloads/priv/ubuntu/downloads/file_download.cpp 2015-01-23 11:59:29 +0000 |
4 | @@ -16,20 +16,22 @@ |
5 | * Boston, MA 02110-1301, USA. |
6 | */ |
7 | |
8 | +#include <map> |
9 | + |
10 | +#include <glog/logging.h> |
11 | + |
12 | +#include <QBuffer> |
13 | +#include <QCryptographicHash> |
14 | #include <QDBusConnection> |
15 | #include <QDBusMessage> |
16 | -#include <QBuffer> |
17 | -#include <QCryptographicHash> |
18 | #include <QDir> |
19 | -#include <QStringList> |
20 | #include <QFile> |
21 | #include <QFileInfo> |
22 | +#include <QMimeDatabase> |
23 | +#include <QMimeType> |
24 | +#include <QStringList> |
25 | #include <QSslError> |
26 | |
27 | -#include <glog/logging.h> |
28 | - |
29 | -#include <map> |
30 | - |
31 | #include <ubuntu/transfers/metadata.h> |
32 | #include <ubuntu/transfers/system/dbus_connection.h> |
33 | #include <ubuntu/transfers/system/hash_algorithm.h> |
34 | @@ -45,15 +47,6 @@ |
35 | |
36 | #define DOWN_LOG(LEVEL) LOG(LEVEL) << ((parent() != nullptr)?"GroupDownload {" + parent()->objectName() + " } ":"") << "Download ID{" << objectName() << " } " |
37 | |
38 | -namespace { |
39 | - const QString PROPERTIES_INTERFACE = "org.freedesktop.DBus.Properties"; |
40 | - const QString DOWNLOAD_INTERFACE = "com.canonical.applications.Download"; |
41 | - const QString PROPERTIES_CHANGED = "PropertiesChanged"; |
42 | - const QString CLICK_PACKAGE_PROPERTY = "ClickPackage"; |
43 | - const QString SHOW_INDICATOR_PROPERTY = "ShowInIndicator"; |
44 | - const QString TITLE_PROPERTY = "Title"; |
45 | -} |
46 | - |
47 | std::ostream& operator<<(std::ostream &out, QNetworkReply::NetworkError err) { |
48 | static std::map<QNetworkReply::NetworkError, std::string> errorsMap { |
49 | {QNetworkReply::ConnectionRefusedError, "ConnectionRefusedError: the remote " |
50 | @@ -148,7 +141,12 @@ |
51 | } |
52 | |
53 | namespace { |
54 | - |
55 | + const QString PROPERTIES_INTERFACE = "org.freedesktop.DBus.Properties"; |
56 | + const QString DOWNLOAD_INTERFACE = "com.canonical.applications.Download"; |
57 | + const QString PROPERTIES_CHANGED = "PropertiesChanged"; |
58 | + const QString CLICK_PACKAGE_PROPERTY = "ClickPackage"; |
59 | + const QString SHOW_INDICATOR_PROPERTY = "ShowInIndicator"; |
60 | + const QString TITLE_PROPERTY = "Title"; |
61 | const QString DATA_FILE_NAME = "data.download"; |
62 | const QString NETWORK_ERROR = "NETWORK ERROR"; |
63 | const QString HASH_ERROR = "HASH ERROR"; |
64 | @@ -161,6 +159,7 @@ |
65 | const QString UNEXPECTED_ERROR = "UNEXPECTED_ERROR"; |
66 | const QByteArray CONTENT_DISPOSITION = "Content-Disposition"; |
67 | const QByteArray CONTENT_TYPE = "Content-Type"; |
68 | + const QString DATA_URI_PREFIX = "data:"; |
69 | } |
70 | |
71 | namespace Ubuntu { |
72 | @@ -245,31 +244,37 @@ |
73 | void |
74 | FileDownload::pauseTransfer() { |
75 | TRACE << _url; |
76 | - if (_reply == nullptr) { |
77 | - // cannot pause because is not running |
78 | - DOWN_LOG(INFO) << "Cannot pause download because reply is NULL"; |
79 | - DOWN_LOG(INFO) << "EMIT paused(false)"; |
80 | - emit paused(false); |
81 | - return; |
82 | - } |
83 | - |
84 | - DOWN_LOG(INFO) << "Pausing download" << _url; |
85 | - // we need to disconnect the signals to ensure that they are not |
86 | - // emitted due to the operation we are going to perform. We read |
87 | - // the data in the reply and store it in a file |
88 | - disconnectFromReplySignals(); |
89 | - |
90 | - // do abort before reading |
91 | - _reply->abort(); |
92 | - _currentData->write(_reply->readAll()); |
93 | - if (!flushFile()) { |
94 | + if (_url.toString().contains(DATA_URI_PREFIX)) { |
95 | + DOWN_LOG(INFO) << "EMIT paused(false) since we are using a data uri"; |
96 | + _downloading = false; |
97 | emit paused(false); |
98 | } else { |
99 | - _reply->deleteLater(); |
100 | - _reply = nullptr; |
101 | - DOWN_LOG(INFO) << "EMIT paused(true)"; |
102 | - _downloading = false; |
103 | - emit paused(true); |
104 | + if (_reply == nullptr) { |
105 | + // cannot pause because is not running |
106 | + DOWN_LOG(INFO) << "Cannot pause download because reply is NULL"; |
107 | + DOWN_LOG(INFO) << "EMIT paused(false)"; |
108 | + emit paused(false); |
109 | + return; |
110 | + } |
111 | + |
112 | + DOWN_LOG(INFO) << "Pausing download" << _url; |
113 | + // we need to disconnect the signals to ensure that they are not |
114 | + // emitted due to the operation we are going to perform. We read |
115 | + // the data in the reply and store it in a file |
116 | + disconnectFromReplySignals(); |
117 | + |
118 | + // do abort before reading |
119 | + _reply->abort(); |
120 | + _currentData->write(_reply->readAll()); |
121 | + if (!flushFile()) { |
122 | + emit paused(false); |
123 | + } else { |
124 | + _reply->deleteLater(); |
125 | + _reply = nullptr; |
126 | + DOWN_LOG(INFO) << "EMIT paused(true)"; |
127 | + _downloading = false; |
128 | + emit paused(true); |
129 | + } |
130 | } |
131 | } |
132 | |
133 | @@ -285,23 +290,32 @@ |
134 | return; |
135 | } |
136 | |
137 | - DOWN_LOG(INFO) << "Resuming download."; |
138 | - QNetworkRequest request = buildRequest(); |
139 | - |
140 | - // overrides the range header, we do not let clients set the range!!! |
141 | - qint64 currentDataSize = _currentData->size(); |
142 | - QByteArray rangeHeaderValue = "bytes=" + |
143 | - QByteArray::number(currentDataSize) + "-"; |
144 | - request.setRawHeader("Range", rangeHeaderValue); |
145 | - |
146 | - _reply = _requestFactory->get(request); |
147 | - _reply->setReadBufferSize(throttle()); |
148 | - |
149 | - connectToReplySignals(); |
150 | - |
151 | - DOWN_LOG(INFO) << "EMIT resumed(true)"; |
152 | - _downloading = true; |
153 | - emit resumed(true); |
154 | + // it is not very probable, yet possible that we do reach this point with a data uri |
155 | + |
156 | + if (_url.toString().contains(DATA_URI_PREFIX)) { |
157 | + DOWN_LOG(INFO) << "EMIT resumed(true)"; |
158 | + _downloading = true; |
159 | + emit resumed(true); |
160 | + writeDataUri(); |
161 | + } else { |
162 | + DOWN_LOG(INFO) << "Resuming download."; |
163 | + QNetworkRequest request = buildRequest(); |
164 | + |
165 | + // overrides the range header, we do not let clients set the range!!! |
166 | + qint64 currentDataSize = _currentData->size(); |
167 | + QByteArray rangeHeaderValue = "bytes=" + |
168 | + QByteArray::number(currentDataSize) + "-"; |
169 | + request.setRawHeader("Range", rangeHeaderValue); |
170 | + |
171 | + _reply = _requestFactory->get(request); |
172 | + _reply->setReadBufferSize(throttle()); |
173 | + |
174 | + connectToReplySignals(); |
175 | + |
176 | + DOWN_LOG(INFO) << "EMIT resumed(true)"; |
177 | + _downloading = true; |
178 | + emit resumed(true); |
179 | + } |
180 | } |
181 | |
182 | void |
183 | @@ -326,16 +340,29 @@ |
184 | return; |
185 | } |
186 | |
187 | - DOWN_LOG(INFO) << "Network is accessible, performing download request"; |
188 | - // signals should take care of calling deleteLater on the |
189 | - // NetworkReply object |
190 | - _reply = _requestFactory->get(buildRequest()); |
191 | - _reply->setReadBufferSize(throttle()); |
192 | - |
193 | - connectToReplySignals(); |
194 | - DOWN_LOG(INFO) << "EMIT started(true)"; |
195 | - _downloading = true; |
196 | - emit started(true); |
197 | + // make a diff between a data uri and a "normal" download |
198 | + if (_url.toString().contains(DATA_URI_PREFIX)) { |
199 | + DOWN_LOG(INFO) << "Performing a data uri download."; |
200 | + // we emit the signals and then write the data in the file system, which will emit the download finished signal |
201 | + // the signals are queued so they will happen in the correct order |
202 | + |
203 | + DOWN_LOG(INFO) << "EMIT started(true)"; |
204 | + _downloading = true; |
205 | + emit started(true); |
206 | + |
207 | + writeDataUri(); |
208 | + } else { |
209 | + DOWN_LOG(INFO) << "Performing a network download."; |
210 | + // signals should take care of calling deleteLater on the |
211 | + // NetworkReply object |
212 | + _reply = _requestFactory->get(buildRequest()); |
213 | + _reply->setReadBufferSize(throttle()); |
214 | + |
215 | + connectToReplySignals(); |
216 | + DOWN_LOG(INFO) << "EMIT started(true)"; |
217 | + _downloading = true; |
218 | + emit started(true); |
219 | + } |
220 | } |
221 | |
222 | qulonglong |
223 | @@ -472,7 +499,7 @@ |
224 | if (!flushFile()) { |
225 | return; |
226 | } |
227 | - qulonglong received = _currentData->size(); |
228 | + auto received = static_cast<qulonglong>(_currentData->size()); |
229 | |
230 | if (bytesTotal == -1) { |
231 | // we do not know the size of the download, simply return |
232 | @@ -481,12 +508,11 @@ |
233 | return; |
234 | } else { |
235 | if (_totalSize == 0) { |
236 | - qlonglong uBytestTotal = bytesTotal; |
237 | // bytesTotal is different when we have resumed because we |
238 | // are not counting the size that we already downloaded, |
239 | // therefore we only do this once |
240 | // update the metadata |
241 | - _totalSize = uBytestTotal; |
242 | + _totalSize = static_cast<qulonglong>(bytesTotal); |
243 | } |
244 | emit Download::progress(received, _totalSize); |
245 | return; |
246 | @@ -579,122 +605,10 @@ |
247 | // ensure that if content-disposition is present we will use it |
248 | updateFileNamePerContentDisposition(); |
249 | |
250 | - if(!hashIsValid()) { |
251 | - emitError(HASH_ERROR); |
252 | - return; |
253 | - } |
254 | - |
255 | - // if the download was set to be deflated and the content type is correct |
256 | - // we will do our best to deflate the file |
257 | - if (_metadata.contains(Metadata::DEFLATE_KEY) |
258 | - && _metadata[Metadata::DEFLATE_KEY].toBool() |
259 | - && _reply->hasRawHeader(CONTENT_TYPE) |
260 | - && HeaderParser::isDeflatableContentType( |
261 | - _reply->rawHeader(CONTENT_TYPE))){ |
262 | - } |
263 | - |
264 | - |
265 | - // there are three possible cases, in the first case we are requested |
266 | - // to extract the file, in which case we start a special helper process. |
267 | - // In the second case we are requested to execute a specific command, in |
268 | - // either of these two cases we only raise the finished signal once the |
269 | - // processing is complete. Or in the third case we have no special requests |
270 | - // and we indicate that the download is finished. |
271 | - if (_metadata.contains(Metadata::EXTRACT_KEY) |
272 | - && _metadata[Metadata::EXTRACT_KEY].toBool() |
273 | - && _reply->hasRawHeader(CONTENT_TYPE) |
274 | - && _reply->rawHeader(CONTENT_TYPE) == "application/zip") { |
275 | - |
276 | - Process* postDownloadProcess = |
277 | - ProcessFactory::instance()->createProcess(); |
278 | - |
279 | - CHECK(connect(postDownloadProcess, &Process::finished, |
280 | - this, &FileDownload::onProcessFinished)) |
281 | - << "Could not connect to signal"; |
282 | - CHECK(connect(postDownloadProcess, &Process::error, |
283 | - this, &FileDownload::onProcessError)) |
284 | - << "Could not connect to signal"; |
285 | - |
286 | - DOWN_LOG(INFO) << "Renaming '" << _tempFilePath << "'" |
287 | - << "' to '" << _filePath << "'"; |
288 | - |
289 | - auto fileMan = FileManager::instance(); |
290 | - if (fileMan->exists(_tempFilePath)) { |
291 | - LOG(INFO) << "Renaming: '" << _tempFilePath << "' to '" |
292 | - << _filePath << "'"; |
293 | - fileMan->rename(_tempFilePath, _filePath); |
294 | - } |
295 | - |
296 | - QFileInfo fileInfo(filePath()); |
297 | - |
298 | - QString command = HELPER_DIR + QString(QDir::separator()) + "udm-extractor"; |
299 | - QStringList args; |
300 | - args << "--unzip"; |
301 | - args << "--path"; |
302 | - args << filePath(); |
303 | - args << "--destination"; |
304 | - args << fileInfo.dir().absolutePath(); |
305 | - |
306 | - DOWN_LOG(INFO) << "Executing" << command << args; |
307 | - postDownloadProcess->start(command, args); |
308 | - return; |
309 | - } else if (_metadata.contains(Metadata::COMMAND_KEY)) { |
310 | - // just emit processing if we DO NOT have a hash because else we |
311 | - // already emitted it. |
312 | - if (_hash.isEmpty()) { |
313 | - emit processing(filePath()); |
314 | - } |
315 | - // toStringList will return an empty list if it cannot be converted |
316 | - QStringList commandData = |
317 | - _metadata[Metadata::COMMAND_KEY].toStringList(); |
318 | - if (commandData.count() == 0) { |
319 | - DOWN_LOG(ERROR) << "COMMAND DATA MISSING"; |
320 | - emitError(COMMAND_ERROR); |
321 | - return; |
322 | - } else { |
323 | - // first item of the string list is the command |
324 | - // the rest is the arguments |
325 | - DOWN_LOG(INFO) << "Renaming '" << _tempFilePath << "'" |
326 | - << "' to '" << _filePath << "'"; |
327 | - |
328 | - auto fileMan = FileManager::instance(); |
329 | - if (fileMan->exists(_tempFilePath)) { |
330 | - LOG(INFO) << "Renaming: '" << _tempFilePath << "' to '" |
331 | - << _filePath << "'"; |
332 | - fileMan->rename(_tempFilePath, _filePath); |
333 | - } |
334 | - |
335 | - QString command = commandData.at(0); |
336 | - commandData.removeAt(0); |
337 | - QStringList args; |
338 | - |
339 | - foreach(const QString& arg, commandData) { |
340 | - if (arg == Metadata::COMMAND_FILE_KEY) |
341 | - args << filePath(); |
342 | - else |
343 | - args << arg; |
344 | - } |
345 | - |
346 | - Process* postDownloadProcess = |
347 | - ProcessFactory::instance()->createProcess(); |
348 | - |
349 | - // connect to signals so that we can tell the clients that |
350 | - // the operation succeed |
351 | - |
352 | - CHECK(connect(postDownloadProcess, &Process::finished, |
353 | - this, &FileDownload::onProcessFinished)) |
354 | - << "Could not connect to signal"; |
355 | - CHECK(connect(postDownloadProcess, &Process::error, |
356 | - this, &FileDownload::onProcessError)) |
357 | - << "Could not connect to signal"; |
358 | - |
359 | - DOWN_LOG(INFO) << "Executing" << command << args; |
360 | - postDownloadProcess->start(command, args); |
361 | - return; |
362 | - } |
363 | - } else { |
364 | - emitFinished(); |
365 | - } |
366 | + auto contentType = (_reply->hasRawHeader(CONTENT_TYPE))? |
367 | + QString(_reply->rawHeader(CONTENT_TYPE)) : QString(); |
368 | + |
369 | + downloadPostProcessing(contentType); |
370 | |
371 | // clean the reply |
372 | _reply->deleteLater(); |
373 | @@ -702,6 +616,114 @@ |
374 | } |
375 | |
376 | void |
377 | +FileDownload::writeDataUri() { |
378 | + // the expected format of the data is as follows: |
379 | + // |
380 | + // data:[<MIME-type>][;charset=<encoding>][;base64],<data> |
381 | + // |
382 | + // The encoding is indicated by ;base64. If it is present the data is encoded as base64. Without it the data |
383 | + // (as a sequence of octets) is represented using ASCII encoding for octets inside the range of safe URL |
384 | + // characters. If <MIME-type> is omitted, it defaults to text/plain;charset=US-ASCII. |
385 | + // An example uri is: |
386 | + // |
387 | + // data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== |
388 | + |
389 | + // HACK: |
390 | + // it can also be seen with the url prefixed as it happens in google images: |
391 | + // |
392 | + // http://images.google.com/data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== |
393 | + // |
394 | + // this is due to a bug found in oxide: https://bugs.launchpad.net/oxide/+bug/1413964 and should be removed |
395 | + // whenever asap |
396 | + QString urlString = QString::null; |
397 | + auto urlStringParts = _url.toString().split(DATA_URI_PREFIX); |
398 | + if (urlStringParts.count() > 1) { |
399 | + urlString = urlStringParts[1]; |
400 | + } else { |
401 | + urlString = urlStringParts[0]; |
402 | + } |
403 | + |
404 | + QByteArray data; |
405 | + QMimeDatabase db; |
406 | + QMimeType mimeType; // init to an invalid type |
407 | + QString extension = QString::null; |
408 | + bool isBase64 = false; |
409 | + |
410 | + foreach(const QMimeType& type, db.allMimeTypes()) { |
411 | + // we need to check for each of the aliases that a mime type has |
412 | + foreach(const QString& alias, type.aliases()) { |
413 | + if (urlString.startsWith(alias)) { |
414 | + DOWN_LOG(INFO) << "Data mime type is " << alias.toStdString(); |
415 | + mimeType = type; |
416 | + urlString = urlString.replace(alias + ";", ""); |
417 | + break; |
418 | + } |
419 | + } |
420 | + |
421 | + // we could have set the mime type and jumped outside the previous loop |
422 | + if (mimeType.isValid()) { |
423 | + break; |
424 | + } |
425 | + } |
426 | + if (!mimeType.isValid()) { |
427 | + LOG(INFO) << "MIME TYPE IS NOT VALID"; |
428 | + if (!urlString.startsWith(';')) { |
429 | + DOWN_LOG(INFO) << "Database does not have type yet it is present."; |
430 | + auto parts = urlString.split(';'); |
431 | + extension = parts[0].split('/')[1]; |
432 | + urlString = parts[1]; |
433 | + } else { |
434 | + //defaults to text/plain;charset=US-ASCII. |
435 | + mimeType = db.mimeTypeForData("text/plain"); |
436 | + urlString = urlString.split(';')[1]; |
437 | + } |
438 | + } |
439 | + |
440 | + if (urlString.startsWith("base64,")) { |
441 | + DOWN_LOG(INFO) << "Data uri is base64 encoded"; |
442 | + isBase64 = true; |
443 | + urlString = urlString.replace("base64,", ""); |
444 | + } else { |
445 | + auto parts = urlString.split(','); |
446 | + if (parts.count() != 2) { |
447 | + DOWN_LOG(WARNING) << "Data uri malformed."; |
448 | + } else { |
449 | + urlString = parts[1]; |
450 | + } |
451 | + } |
452 | + // load the data from the string and write it in the file system, the uuid will be used as the |
453 | + // file name + the mime type extension |
454 | + if (isBase64) { |
455 | + QByteArray encoded; |
456 | + encoded.append(urlString); |
457 | + data = QByteArray::fromBase64(encoded); |
458 | + } else { |
459 | + QByteArray encoded; |
460 | + encoded.append(urlString); |
461 | + data = QByteArray::fromPercentEncoding(encoded); |
462 | + } |
463 | + |
464 | + _totalSize = static_cast<qulonglong>(data.size()); |
465 | + |
466 | + // we need to update the file name using the mime type extension (the file name should be ok already), nevertheless |
467 | + // we are going to write the data in the temp file so that the post processing of the download works correctky |
468 | + QFileInfo fiFilePath(_filePath); |
469 | + auto currentFileName = fiFilePath.fileName(); |
470 | + extension = (mimeType.isValid())?mimeType.suffixes()[0]:extension; |
471 | + auto newPath = _filePath.replace(currentFileName, transferId() + "." + extension); |
472 | + |
473 | + // unlock the old path and lock the new one |
474 | + _fileNameMutex->unlockFileName(_filePath); |
475 | + _filePath = _fileNameMutex->lockFileName(newPath); |
476 | + |
477 | + DOWN_LOG(INFO) << "Data uri file path is '" << _filePath << "'"; |
478 | + _currentData->write(data); |
479 | + |
480 | + // deal with the post processing of the created file |
481 | + downloadPostProcessing(mimeType.aliases()[0]); |
482 | +} |
483 | + |
484 | +void |
485 | FileDownload::onFinished() { |
486 | TRACE << _url; |
487 | // the reply has finished but the resource might have been moved |
488 | @@ -907,10 +929,10 @@ |
489 | // addData is smart enough to not load the entire file in memory |
490 | hash->addData(_currentData->device()); |
491 | QString fileSig = QString(hash->result().toHex()); |
492 | - if (fileSig != _hash) { |
493 | + if (fileSig != _hash) { |
494 | DOWN_LOG(ERROR) << HASH_ERROR << fileSig << "!=" << _hash; |
495 | return false; |
496 | - } |
497 | + } |
498 | } |
499 | return true; |
500 | } |
501 | @@ -948,6 +970,115 @@ |
502 | } |
503 | |
504 | void |
505 | +FileDownload::downloadPostProcessing(const QString& contentType) { |
506 | + TRACE << _url; |
507 | + |
508 | + if(!hashIsValid()) { |
509 | + emitError(HASH_ERROR); |
510 | + return; |
511 | + } |
512 | + |
513 | + // there are three possible cases, in the first case we are requested |
514 | + // to extract the file, in which case we start a special helper process. |
515 | + // In the second case we are requested to execute a specific command, in |
516 | + // either of these two cases we only raise the finished signal once the |
517 | + // processing is complete. Or in the third case we have no special requests |
518 | + // and we indicate that the download is finished. |
519 | + if (_metadata.contains(Metadata::EXTRACT_KEY) |
520 | + && _metadata[Metadata::EXTRACT_KEY].toBool() |
521 | + && contentType == "application/zip") { |
522 | + |
523 | + auto postDownloadProcess = ProcessFactory::instance()->createProcess(); |
524 | + |
525 | + CHECK(connect(postDownloadProcess, &Process::finished, |
526 | + this, &FileDownload::onProcessFinished)) |
527 | + << "Could not connect to signal"; |
528 | + CHECK(connect(postDownloadProcess, &Process::error, |
529 | + this, &FileDownload::onProcessError)) |
530 | + << "Could not connect to signal"; |
531 | + |
532 | + DOWN_LOG(INFO) << "Renaming '" << _tempFilePath << "'" |
533 | + << "' to '" << _filePath << "'"; |
534 | + |
535 | + auto fileMan = FileManager::instance(); |
536 | + if (fileMan->exists(_tempFilePath)) { |
537 | + LOG(INFO) << "Renaming: '" << _tempFilePath << "' to '" |
538 | + << _filePath << "'"; |
539 | + fileMan->rename(_tempFilePath, _filePath); |
540 | + } |
541 | + |
542 | + QFileInfo fileInfo(filePath()); |
543 | + |
544 | + QString command = HELPER_DIR + QString(QDir::separator()) + "udm-extractor"; |
545 | + QStringList args; |
546 | + args << "--unzip"; |
547 | + args << "--path"; |
548 | + args << filePath(); |
549 | + args << "--destination"; |
550 | + args << fileInfo.dir().absolutePath(); |
551 | + |
552 | + DOWN_LOG(INFO) << "Executing" << command << args; |
553 | + postDownloadProcess->start(command, args); |
554 | + return; |
555 | + } else if (_metadata.contains(Metadata::COMMAND_KEY)) { |
556 | + // just emit processing if we DO NOT have a hash because else we |
557 | + // already emitted it. |
558 | + if (_hash.isEmpty()) { |
559 | + emit processing(filePath()); |
560 | + } |
561 | + // toStringList will return an empty list if it cannot be converted |
562 | + QStringList commandData = |
563 | + _metadata[Metadata::COMMAND_KEY].toStringList(); |
564 | + if (commandData.count() == 0) { |
565 | + DOWN_LOG(ERROR) << "COMMAND DATA MISSING"; |
566 | + emitError(COMMAND_ERROR); |
567 | + return; |
568 | + } else { |
569 | + // first item of the string list is the command |
570 | + // the rest is the arguments |
571 | + DOWN_LOG(INFO) << "Renaming '" << _tempFilePath << "'" |
572 | + << "' to '" << _filePath << "'"; |
573 | + |
574 | + auto fileMan = FileManager::instance(); |
575 | + if (fileMan->exists(_tempFilePath)) { |
576 | + LOG(INFO) << "Renaming: '" << _tempFilePath << "' to '" |
577 | + << _filePath << "'"; |
578 | + fileMan->rename(_tempFilePath, _filePath); |
579 | + } |
580 | + |
581 | + QString command = commandData.at(0); |
582 | + commandData.removeAt(0); |
583 | + QStringList args; |
584 | + |
585 | + foreach(const QString& arg, commandData) { |
586 | + if (arg == Metadata::COMMAND_FILE_KEY) |
587 | + args << filePath(); |
588 | + else |
589 | + args << arg; |
590 | + } |
591 | + |
592 | + auto postDownloadProcess = ProcessFactory::instance()->createProcess(); |
593 | + |
594 | + // connect to signals so that we can tell the clients that |
595 | + // the operation succeed |
596 | + |
597 | + CHECK(connect(postDownloadProcess, &Process::finished, |
598 | + this, &FileDownload::onProcessFinished)) |
599 | + << "Could not connect to signal"; |
600 | + CHECK(connect(postDownloadProcess, &Process::error, |
601 | + this, &FileDownload::onProcessError)) |
602 | + << "Could not connect to signal"; |
603 | + |
604 | + DOWN_LOG(INFO) << "Executing" << command << args; |
605 | + postDownloadProcess->start(command, args); |
606 | + return; |
607 | + } |
608 | + } else { |
609 | + emitFinished(); |
610 | + } |
611 | +} |
612 | + |
613 | +void |
614 | FileDownload::emitFinished() { |
615 | auto fileMan = FileManager::instance(); |
616 | |
617 | |
618 | === modified file 'src/downloads/priv/ubuntu/downloads/file_download.h' |
619 | --- src/downloads/priv/ubuntu/downloads/file_download.h 2014-10-22 23:40:53 +0000 |
620 | +++ src/downloads/priv/ubuntu/downloads/file_download.h 2015-01-23 11:59:29 +0000 |
621 | @@ -16,8 +16,7 @@ |
622 | * Boston, MA 02110-1301, USA. |
623 | */ |
624 | |
625 | -#ifndef DOWNLOADER_LIB_SINGLE_DOWNLOAD_H |
626 | -#define DOWNLOADER_LIB_SINGLE_DOWNLOAD_H |
627 | +#pragma once |
628 | |
629 | #include <QDBusContext> |
630 | #include <QFile> |
631 | @@ -123,8 +122,10 @@ |
632 | bool hashIsValid(); |
633 | void init(); |
634 | void initFileNames(); |
635 | + void downloadPostProcessing(const QString& contentType); |
636 | void unlockFilePath(); |
637 | void updateFileNamePerContentDisposition(); |
638 | + void writeDataUri(); |
639 | |
640 | // slots used to react to signals |
641 | void onDownloadProgress(qint64 currentProgress, qint64); |
642 | @@ -161,5 +162,3 @@ |
643 | } // DownloadManager |
644 | |
645 | } // Ubuntu |
646 | - |
647 | -#endif // DOWNLOADER_LIB_SINGLE_DOWNLOAD_H |
648 | |
649 | === modified file 'tests/downloads/daemon/CMakeLists.txt' |
650 | --- tests/downloads/daemon/CMakeLists.txt 2014-12-01 09:20:00 +0000 |
651 | +++ tests/downloads/daemon/CMakeLists.txt 2015-01-23 11:59:29 +0000 |
652 | @@ -15,26 +15,26 @@ |
653 | # Authored by: Manuel de la Peña <manuel.delapena@canonical.com> |
654 | |
655 | set(DAEMON_TESTS |
656 | - test_apn_request_factory |
657 | - test_apparmor |
658 | + test_apn_request_factory |
659 | + test_apparmor |
660 | test_base_download |
661 | - test_cancel_download_transition |
662 | + test_cancel_download_transition |
663 | test_daemon |
664 | - test_download |
665 | + test_download |
666 | test_download_factory |
667 | - test_download_manager |
668 | - test_downloads_db |
669 | - test_file_download_sm |
670 | - test_filename_mutex |
671 | - test_final_state |
672 | + test_download_manager |
673 | + test_downloads_db |
674 | + test_file_download_sm |
675 | + test_filename_mutex |
676 | + test_final_state |
677 | test_group_download |
678 | test_metadata |
679 | - test_mms_download |
680 | - test_network_error_transition |
681 | - test_resume_download_transition |
682 | - test_ssl_error_transition |
683 | - test_start_download_transition |
684 | - test_stop_request_transition |
685 | + test_mms_download |
686 | + test_network_error_transition |
687 | + test_resume_download_transition |
688 | + test_ssl_error_transition |
689 | + test_start_download_transition |
690 | + test_stop_request_transition |
691 | test_transfers_queue |
692 | ) |
693 | |
694 | @@ -47,14 +47,14 @@ |
695 | set(${test}_DAEMON_HEADERS |
696 | application.h |
697 | cryptographic_hash.h |
698 | - database.h |
699 | - dbus_proxy.h |
700 | - dbus_proxy_factory.h |
701 | - download.h |
702 | + database.h |
703 | + dbus_proxy.h |
704 | + dbus_proxy_factory.h |
705 | + download.h |
706 | factory.h |
707 | filename_mutex.h |
708 | - manager.h |
709 | - pending_reply.h |
710 | + manager.h |
711 | + pending_reply.h |
712 | queue.h |
713 | sm_file_download.h |
714 | timer.h |
715 | @@ -86,18 +86,18 @@ |
716 | link_directories(${GLOG_DBUS_LIBDIR}) |
717 | |
718 | set(DAEMON_TESTS_LIBS |
719 | - ${NIH_DBUS_LIBRARIES} |
720 | - ${GLOG_LIBRARIES} |
721 | + ${NIH_DBUS_LIBRARIES} |
722 | + ${GLOG_LIBRARIES} |
723 | ${Qt5Core_LIBRARIES} |
724 | ${Qt5Sql_LIBRARIES} |
725 | ${Qt5DBus_LIBRARIES} |
726 | - ${Qt5Test_LIBRARIES} |
727 | + ${Qt5Test_LIBRARIES} |
728 | ${GMOCK_LIBRARY} |
729 | ${GTEST_BOTH_LIBRARIES} |
730 | - udm-common |
731 | - udm-priv-common |
732 | - ubuntu-download-manager-common |
733 | - ubuntu-download-manager-priv |
734 | + udm-common |
735 | + udm-priv-common |
736 | + ubuntu-download-manager-common |
737 | + ubuntu-download-manager-priv |
738 | ubuntu-download-manager-test-lib |
739 | ) |
740 | |
741 | |
742 | === modified file 'tests/downloads/daemon/test_download.cpp' |
743 | --- tests/downloads/daemon/test_download.cpp 2014-11-17 20:34:24 +0000 |
744 | +++ tests/downloads/daemon/test_download.cpp 2015-01-23 11:59:29 +0000 |
745 | @@ -1262,7 +1262,8 @@ |
746 | .WillOnce(Return(QVariant(200))); |
747 | |
748 | EXPECT_CALL(*reply.data(), hasRawHeader(_)) |
749 | - .Times(1) |
750 | + .Times(2) |
751 | + .WillOnce(Return(false)) |
752 | .WillOnce(Return(false)); |
753 | |
754 | // file system expectations |
755 | @@ -1331,7 +1332,8 @@ |
756 | .WillOnce(Return(QVariant(200))); |
757 | |
758 | EXPECT_CALL(*reply.data(), hasRawHeader(_)) |
759 | - .Times(1) |
760 | + .Times(2) |
761 | + .WillOnce(Return(false)) |
762 | .WillOnce(Return(false)); |
763 | |
764 | // file system expectations |
765 | @@ -1421,7 +1423,8 @@ |
766 | .WillOnce(Return(QVariant(200))); |
767 | |
768 | EXPECT_CALL(*reply.data(), hasRawHeader(_)) |
769 | - .Times(1) |
770 | + .Times(2) |
771 | + .WillOnce(Return(false)) |
772 | .WillOnce(Return(false)); |
773 | |
774 | // file system expectations |
775 | @@ -2143,7 +2146,8 @@ |
776 | .WillOnce(Return(QVariant(200))); |
777 | |
778 | EXPECT_CALL(*reply, hasRawHeader(_)) |
779 | - .Times(1) |
780 | + .Times(2) |
781 | + .WillOnce(Return(false)) |
782 | .WillOnce(Return(false)); |
783 | |
784 | // file system expectations |
785 | @@ -2196,6 +2200,7 @@ |
786 | QVERIFY(Mock::VerifyAndClearExpectations(reply)); |
787 | QVERIFY(Mock::VerifyAndClearExpectations(process.data())); |
788 | verifyMocks(); |
789 | + delete reply; |
790 | } |
791 | |
792 | void |
793 | @@ -2254,7 +2259,8 @@ |
794 | .WillOnce(Return(QVariant(200))); |
795 | |
796 | EXPECT_CALL(*reply, hasRawHeader(_)) |
797 | - .Times(1) |
798 | + .Times(2) |
799 | + .WillOnce(Return(false)) |
800 | .WillOnce(Return(false)); |
801 | |
802 | // file system expectations |
803 | @@ -2307,6 +2313,8 @@ |
804 | QVERIFY(Mock::VerifyAndClearExpectations(reply)); |
805 | QVERIFY(Mock::VerifyAndClearExpectations(process.data())); |
806 | verifyMocks(); |
807 | + |
808 | + delete reply; |
809 | } |
810 | |
811 | void |
812 | @@ -2365,7 +2373,8 @@ |
813 | .WillOnce(Return(QVariant(200))); |
814 | |
815 | EXPECT_CALL(*reply, hasRawHeader(_)) |
816 | - .Times(1) |
817 | + .Times(2) |
818 | + .WillOnce(Return(false)) |
819 | .WillOnce(Return(false)); |
820 | |
821 | // file system expectations |
822 | @@ -2426,6 +2435,8 @@ |
823 | QVERIFY(Mock::VerifyAndClearExpectations(reply)); |
824 | QVERIFY(Mock::VerifyAndClearExpectations(process.data())); |
825 | verifyMocks(); |
826 | + |
827 | + delete reply; |
828 | } |
829 | |
830 | void |
831 | @@ -2458,7 +2469,8 @@ |
832 | .WillOnce(Return(QVariant(200))); |
833 | |
834 | EXPECT_CALL(*reply.data(), hasRawHeader(_)) |
835 | - .Times(1) |
836 | + .Times(2) |
837 | + .WillOnce(Return(false)) |
838 | .WillOnce(Return(false)); |
839 | |
840 | // file system expectations |
841 | @@ -2572,7 +2584,8 @@ |
842 | .WillOnce(Return(QVariant(200))); |
843 | |
844 | EXPECT_CALL(*reply.data(), hasRawHeader(_)) |
845 | - .Times(1) |
846 | + .Times(2) |
847 | + .WillOnce(Return(false)) |
848 | .WillOnce(Return(false)); |
849 | |
850 | // file system expectations |
851 | @@ -2677,7 +2690,8 @@ |
852 | .WillOnce(Return(QVariant(200))); |
853 | |
854 | EXPECT_CALL(*reply.data(), hasRawHeader(_)) |
855 | - .Times(1) |
856 | + .Times(2) |
857 | + .WillOnce(Return(false)) |
858 | .WillOnce(Return(false)); |
859 | |
860 | // file system expectations |
861 | @@ -3169,7 +3183,8 @@ |
862 | .WillOnce(Return(QVariant(200))); |
863 | |
864 | EXPECT_CALL(*reply, hasRawHeader(_)) |
865 | - .Times(1) |
866 | + .Times(2) |
867 | + .WillOnce(Return(false)) |
868 | .WillOnce(Return(false)); |
869 | |
870 | // file system expectations |
871 | @@ -3234,6 +3249,8 @@ |
872 | QVERIFY(Mock::VerifyAndClearExpectations(file)); |
873 | QVERIFY(Mock::VerifyAndClearExpectations(reply)); |
874 | verifyMocks(); |
875 | + |
876 | + delete reply; |
877 | } |
878 | |
879 | void |
880 | @@ -3508,7 +3525,8 @@ |
881 | .WillOnce(Return(QVariant())); |
882 | |
883 | EXPECT_CALL(*secondReply.data(), hasRawHeader(_)) |
884 | - .Times(1) |
885 | + .Times(2) |
886 | + .WillOnce(Return(false)) |
887 | .WillOnce(Return(false)); |
888 | |
889 | // file system expectations |
890 | @@ -3667,7 +3685,8 @@ |
891 | .WillOnce(Return(QVariant(200))); |
892 | |
893 | EXPECT_CALL(*reply, hasRawHeader(_)) |
894 | - .Times(1) |
895 | + .Times(2) |
896 | + .WillOnce(Return(false)) |
897 | .WillOnce(Return(false)); |
898 | |
899 | // file system expectations |
900 | @@ -3722,6 +3741,8 @@ |
901 | QVERIFY(Mock::VerifyAndClearExpectations(mutex)); |
902 | verifyMocks(); |
903 | FileNameMutex::deleteInstance(); |
904 | + |
905 | + delete reply; |
906 | } |
907 | |
908 | void |
909 | @@ -3930,4 +3951,225 @@ |
910 | verifyMocks(); |
911 | } |
912 | |
913 | +void |
914 | +TestDownload::testDataUriIsValid() { |
915 | + EXPECT_CALL(*_networkSession, isOnline()) |
916 | + .WillRepeatedly(Return(true)); |
917 | + |
918 | + QUrl url("data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="); |
919 | + QScopedPointer<FileDownload> download(new FileDownload(_id, _appId, _path, |
920 | + _isConfined, _rootPath, url, _metadata, _headers)); |
921 | + |
922 | + QVERIFY(download->isValid()); |
923 | + verifyMocks(); |
924 | +} |
925 | + |
926 | +void |
927 | +TestDownload::testDataUriIsValidWithHttpPrefix() { |
928 | + // perform the download and assert that the mime extension used is txt |
929 | + auto file = new MockFile("test"); |
930 | + EXPECT_CALL(*_networkSession, isOnline()) |
931 | + .WillRepeatedly(Return(true)); |
932 | + |
933 | + QUrl url("http://images.google.com/data:;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="); |
934 | + QScopedPointer<FileDownload> download(new FileDownload(_id, _appId, _path, |
935 | + _isConfined, _rootPath, url, _metadata, _headers)); |
936 | + |
937 | + QVERIFY(download->isValid()); |
938 | + |
939 | + // file system expectations |
940 | + EXPECT_CALL(*_fileManager, createFile(_)) |
941 | + .Times(1) |
942 | + .WillOnce(Return(file)); |
943 | + |
944 | + EXPECT_CALL(*file, open(QIODevice::ReadWrite | QFile::Append)) |
945 | + .Times(1) |
946 | + .WillOnce(Return(true)); |
947 | + |
948 | + EXPECT_CALL(*file, write(_)) |
949 | + .Times(1) |
950 | + .WillOnce(Return(0)); |
951 | + |
952 | + EXPECT_CALL(*file, close()) |
953 | + .Times(1); |
954 | + |
955 | + download->startTransfer(); |
956 | + QVERIFY(download->filePath().endsWith("txt")); |
957 | +} |
958 | + |
959 | +void |
960 | +TestDownload::testDataUriMissingMimeType() { |
961 | + // perform the download and assert that the mime extension used is txt |
962 | + auto file = new MockFile("test"); |
963 | + EXPECT_CALL(*_networkSession, isOnline()) |
964 | + .WillRepeatedly(Return(true)); |
965 | + |
966 | + QUrl url("data:;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="); |
967 | + QScopedPointer<FileDownload> download(new FileDownload(_id, _appId, _path, |
968 | + _isConfined, _rootPath, url, _metadata, _headers)); |
969 | + |
970 | + QVERIFY(download->isValid()); |
971 | + |
972 | + // file system expectations |
973 | + EXPECT_CALL(*_fileManager, createFile(_)) |
974 | + .Times(1) |
975 | + .WillOnce(Return(file)); |
976 | + |
977 | + EXPECT_CALL(*file, open(QIODevice::ReadWrite | QFile::Append)) |
978 | + .Times(1) |
979 | + .WillOnce(Return(true)); |
980 | + |
981 | + EXPECT_CALL(*file, write(_)) |
982 | + .Times(1) |
983 | + .WillOnce(Return(0)); |
984 | + |
985 | + EXPECT_CALL(*file, close()) |
986 | + .Times(1); |
987 | + |
988 | + download->startTransfer(); |
989 | + QVERIFY(download->filePath().endsWith("txt")); |
990 | +} |
991 | + |
992 | +void |
993 | +TestDownload::testDataUriMimeType_data() { |
994 | + QTest::addColumn<QString>("mime"); |
995 | + QTest::addColumn<QString>("extension"); |
996 | + |
997 | + QTest::newRow("Image gif") << "image/gif" << "gif"; |
998 | + QTest::newRow("Image ief") << "image/ief" << "ief"; |
999 | + QTest::newRow("Image jpg") << "image/jpeg" << "jpeg"; |
1000 | + QTest::newRow("Image tiff") << "image/tiff" << "tiff"; |
1001 | + QTest::newRow("Image rgb") << "image/x-rgb" << "rgb"; |
1002 | + QTest::newRow("Image bitmap") << "image/x-xbitmap" << "x-xbitmap"; |
1003 | + QTest::newRow("View MPG") << "video/mpeg" << "mpeg"; |
1004 | + |
1005 | +} |
1006 | + |
1007 | +void |
1008 | +TestDownload::testDataUriMimeType() { |
1009 | + QFETCH(QString, mime); |
1010 | + QFETCH(QString, extension); |
1011 | + auto file = new MockFile("test"); |
1012 | + EXPECT_CALL(*_networkSession, isOnline()) |
1013 | + .WillRepeatedly(Return(true)); |
1014 | + |
1015 | + QUrl url("data:" + mime + ";base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="); |
1016 | + QScopedPointer<FileDownload> download(new FileDownload(_id, _appId, _path, |
1017 | + _isConfined, _rootPath, url, _metadata, _headers)); |
1018 | + |
1019 | + QVERIFY(download->isValid()); |
1020 | + |
1021 | + // file system expectations |
1022 | + EXPECT_CALL(*_fileManager, createFile(_)) |
1023 | + .Times(1) |
1024 | + .WillOnce(Return(file)); |
1025 | + |
1026 | + EXPECT_CALL(*file, open(QIODevice::ReadWrite | QFile::Append)) |
1027 | + .Times(1) |
1028 | + .WillOnce(Return(true)); |
1029 | + |
1030 | + EXPECT_CALL(*file, write(_)) |
1031 | + .Times(1) |
1032 | + .WillOnce(Return(0)); |
1033 | + |
1034 | + EXPECT_CALL(*file, close()) |
1035 | + .Times(1); |
1036 | + |
1037 | + download->startTransfer(); |
1038 | + QVERIFY(download->filePath().endsWith(extension)); |
1039 | +} |
1040 | + |
1041 | +void |
1042 | +TestDownload::testDataUriPostProcessing_data() { |
1043 | + QTest::addColumn<QString>("command"); |
1044 | + QTest::addColumn<QVariantMap>("metadata"); |
1045 | + QVariantMap first, second, third; |
1046 | + QStringList firstCommand, secondCommand, thirdCommand; |
1047 | + |
1048 | + firstCommand << "touch"; |
1049 | + first["post-download-command"] = firstCommand; |
1050 | + |
1051 | + QTest::newRow("First row") << firstCommand[0] << first; |
1052 | + |
1053 | + secondCommand << "sudo"; |
1054 | + second["post-download-command"] = secondCommand; |
1055 | + |
1056 | + QTest::newRow("Second row") << secondCommand[0] << second; |
1057 | + |
1058 | + thirdCommand << "grep"; |
1059 | + third["post-download-command"] = thirdCommand; |
1060 | + |
1061 | + QTest::newRow("Third row") << thirdCommand[0] << third; |
1062 | +} |
1063 | + |
1064 | +void |
1065 | +TestDownload::testDataUriPostProcessing() { |
1066 | + QFETCH(QString, command); |
1067 | + QFETCH(QVariantMap, metadata); |
1068 | + QStringList args; // not args |
1069 | + |
1070 | + QUrl url("data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="); |
1071 | + QScopedPointer<MockFile> file(new MockFile("test")); |
1072 | + QScopedPointer<MockProcess> process(new MockProcess()); |
1073 | + |
1074 | + // write the expectations of the reply which is what we are |
1075 | + // really testing |
1076 | + |
1077 | + EXPECT_CALL(*_networkSession, isOnline()) |
1078 | + .WillRepeatedly(Return(true)); |
1079 | + |
1080 | + // file system expectations |
1081 | + EXPECT_CALL(*_fileManager, createFile(_)) |
1082 | + .Times(1) |
1083 | + .WillOnce(Return(file.data())); |
1084 | + |
1085 | + EXPECT_CALL(*file.data(), open(QIODevice::ReadWrite | QFile::Append)) |
1086 | + .Times(1) |
1087 | + .WillOnce(Return(true)); |
1088 | + |
1089 | + EXPECT_CALL(*file, write(_)) |
1090 | + .Times(1) |
1091 | + .WillOnce(Return(0)); |
1092 | + |
1093 | + EXPECT_CALL(*file.data(), remove()) |
1094 | + .Times(1) |
1095 | + .WillOnce(Return(true)); |
1096 | + |
1097 | + EXPECT_CALL(*_cryptoFactory, createCryptographicHash(_, _)) |
1098 | + .Times(0); |
1099 | + |
1100 | + // process factory and process expectation |
1101 | + EXPECT_CALL(*_processFactory, createProcess()) |
1102 | + .Times(1) |
1103 | + .WillOnce(Return(process.data())); |
1104 | + |
1105 | + EXPECT_CALL(*process.data(), start(command, StringListEq(args), _)) |
1106 | + .Times(1); |
1107 | + |
1108 | + auto download = new FileDownload(_id, _appId, _path, |
1109 | + _isConfined, _rootPath, url, metadata, _headers); |
1110 | + |
1111 | + SignalBarrier spy(download, SIGNAL(finished(QString))); |
1112 | + SignalBarrier startedSpy(download, SIGNAL(started(bool))); |
1113 | + SignalBarrier processingSpy(download, SIGNAL(processing(QString))); |
1114 | + |
1115 | + download->start(); // change state |
1116 | + download->startTransfer(); |
1117 | + |
1118 | + QVERIFY(startedSpy.ensureSignalEmitted()); |
1119 | + |
1120 | + // emit the finish signal and expect it to be raised |
1121 | + emit process->finished(0, QProcess::NormalExit); |
1122 | + |
1123 | + QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 20000); |
1124 | + QTRY_COMPARE_WITH_TIMEOUT(processingSpy.count(), 1, 20000); |
1125 | + QCOMPARE(download->state(), Download::FINISH); |
1126 | + |
1127 | + delete download; |
1128 | + |
1129 | + QVERIFY(Mock::VerifyAndClearExpectations(file.data())); |
1130 | + QVERIFY(Mock::VerifyAndClearExpectations(process.data())); |
1131 | + verifyMocks(); |
1132 | +} |
1133 | + |
1134 | QTEST_MAIN(TestDownload) |
1135 | |
1136 | === modified file 'tests/downloads/daemon/test_download.h' |
1137 | --- tests/downloads/daemon/test_download.h 2014-11-17 20:34:24 +0000 |
1138 | +++ tests/downloads/daemon/test_download.h 2015-01-23 11:59:29 +0000 |
1139 | @@ -186,6 +186,15 @@ |
1140 | void testDeflateConstructorNoError(); |
1141 | void testDeflateOnRequest(); |
1142 | |
1143 | + // void data uri tests |
1144 | + void testDataUriIsValid(); |
1145 | + void testDataUriIsValidWithHttpPrefix(); |
1146 | + void testDataUriMissingMimeType(); |
1147 | + void testDataUriMimeType_data(); |
1148 | + void testDataUriMimeType(); |
1149 | + void testDataUriPostProcessing_data(); |
1150 | + void testDataUriPostProcessing(); |
1151 | + |
1152 | private: |
1153 | QString _id; |
1154 | QString _appId; |
PASSED: Continuous integration, rev:336 jenkins. qa.ubuntu. com/job/ ubuntu- download- manager- ci/748/ jenkins. qa.ubuntu. com/job/ ubuntu- download- manager- vivid-amd64- ci/8 jenkins. qa.ubuntu. com/job/ ubuntu- download- manager- vivid-armhf- ci/8 jenkins. qa.ubuntu. com/job/ ubuntu- download- manager- vivid-armhf- ci/8/artifact/ work/output/ *zip*/output. zip
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/ubuntu- download- manager- ci/748/ rebuild
http://