Merge branch 'tizen_4.0' into tizen_5.0
[platform/core/api/webapi-plugins.git] / src / archive / archive_instance.cc
1 /*
2  * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16
17 #include "archive/archive_instance.h"
18
19 //#include "archive_manager.h"
20
21 #include <pkgmgr-info.h>
22 #include <functional>
23 #include <memory>
24 #include "archive_callback_data.h"
25 #include "archive_manager.h"
26 #include "archive_utils.h"
27 #include "common/current_application.h"
28 #include "common/filesystem/filesystem_provider.h"
29 #include "common/logger.h"
30 #include "common/picojson.h"
31 #include "common/tools.h"
32 #include "defs.h"
33
34 namespace extension {
35 namespace archive {
36
37 using common::tools::ReportSuccess;
38 using common::tools::ReportError;
39
40 namespace {
41 const std::string kPrivilegeFilesystemRead = "http://tizen.org/privilege/filesystem.read";
42 const std::string kPrivilegeFilesystemWrite = "http://tizen.org/privilege/filesystem.write";
43
44 const std::string kArchiveFileEntryOptDest = "destination";
45 const std::string kArchiveFileEntryOptStrip = "stripSourceDirectory";
46 const std::string kArchiveFileEntryOptCompressionLevel = "compressionLevel";
47
48 const std::string kNoCompressionStr = "STORE";
49 const std::string kFastCompressionStr = "FAST";
50 const std::string kNormalCompressionStr = "NORMAL";
51 const std::string kBestCompressionStr = "BEST";
52
53 }  // namespace
54
55 ArchiveInstance::ArchiveInstance() {
56   ScopeLogger();
57
58   using std::placeholders::_1;
59   using std::placeholders::_2;
60
61 #define REGISTER_SYNC(c, x) RegisterSyncHandler(c, std::bind(&ArchiveInstance::x, this, _1, _2));
62 #define REGISTER_ASYNC(c, x) RegisterSyncHandler(c, std::bind(&ArchiveInstance::x, this, _1, _2));
63
64   REGISTER_ASYNC("ArchiveManager_open", Open);
65   REGISTER_SYNC("ArchiveManager_abort", Abort);
66
67   REGISTER_ASYNC("ArchiveFile_add", Add);
68   REGISTER_ASYNC("ArchiveFile_extractAll", ExtractAll);
69   REGISTER_ASYNC("ArchiveFile_getEntries", GetEntries);
70   REGISTER_ASYNC("ArchiveFile_getEntryByName", GetEntryByName);
71   REGISTER_SYNC("ArchiveFile_close", Close);
72
73   REGISTER_ASYNC("ArchiveFileEntry_extract", Extract);
74
75   REGISTER_SYNC("Archive_fetchStorages", FetchStorages);
76
77 #undef REGISTER_ASYNC
78 #undef REGISTER_SYNC
79 }
80
81 ArchiveInstance::~ArchiveInstance() {
82   ScopeLogger();
83 }
84
85 void ArchiveInstance::PostError(const PlatformResult& e, double callback_id) {
86   ScopeLogger();
87
88   LoggerE("Posting an error: %d, message: %s", static_cast<int>(e.error_code()),
89           e.message().c_str());
90
91   picojson::value val = picojson::value(picojson::object());
92   picojson::object& obj = val.get<picojson::object>();
93   obj[JSON_CALLBACK_ID] = picojson::value(callback_id);
94
95   ReportError(e, &obj);
96
97   Instance::PostMessage(this, val.serialize().c_str());
98 }
99
100 void ArchiveInstance::Open(const picojson::value& args, picojson::object& out) {
101   ScopeLogger("%s", args.serialize().c_str());
102
103   CHECK_PRIVILEGE_ACCESS(kPrivilegeFilesystemWrite, &out);
104   picojson::object data = args.get<picojson::object>();
105   picojson::value v_file = data.at(PARAM_FILE);
106   CHECK_STORAGE_ACCESS(v_file.get<std::string>(), &out);
107
108   picojson::value v_mode = data.at(PARAM_MODE);
109   picojson::value v_op_id = data.at(PARAM_OPERATION_ID);
110   picojson::object options = data.at(PARAM_OPTIONS).get<picojson::object>();
111   picojson::value v_overwrite = options.at(PARAM_OVERWRITE);
112   const double callbackId = args.get(JSON_CALLBACK_ID).get<double>();
113   const long operationId = static_cast<long>(v_op_id.get<double>());
114   FileMode fm;
115   PlatformResult result = stringToFileMode(v_mode.get<std::string>(), &fm);
116   if (result.error_code() != ErrorCode::NO_ERROR) {
117     LoggerE("File mode conversions error");
118     PostError(result, callbackId);
119     return;
120   }
121
122   OpenCallbackData* callback = new OpenCallbackData(*this);
123
124   FilePtr file_ptr;
125
126   callback->setOperationId(operationId);
127   callback->setCallbackId(callbackId);
128
129   bool overwrite = false;
130   if (v_overwrite.is<bool>()) {
131     overwrite = v_overwrite.get<bool>();
132   }
133
134   std::string location_full_path = v_file.get<std::string>();
135   PathPtr path = Path::create(location_full_path);
136
137   struct stat info;
138   if (lstat(path->getFullPath().c_str(), &info) == 0) {
139     NodePtr node;
140     result = Node::resolve(path, &node);
141     if (result.error_code() != ErrorCode::NO_ERROR) {
142       LoggerE("Filesystem exception - calling error callback");
143       PostError(result, callbackId);
144       delete callback;
145       callback = NULL;
146       return;
147     }
148
149     file_ptr = FilePtr(new File(node, File::PermissionList()));
150     LoggerD("open: %s mode: 0x%d overwrite: %d", location_full_path.c_str(), fm, overwrite);
151     if (FileMode::WRITE == fm || FileMode::READ_WRITE == fm) {
152       if (overwrite) {
153         LoggerD("Deleting existing file: %s", location_full_path.c_str());
154         result = file_ptr->getNode()->remove(OPT_RECURSIVE);
155         if (result.error_code() != ErrorCode::NO_ERROR) {
156           LoggerE("Couldn't remove existing file: %s", location_full_path.c_str());
157           PostError(result, callbackId);
158           delete callback;
159           callback = NULL;
160           return;
161         }
162         file_ptr.reset();  // We need to create new empty file
163       } else if (FileMode::WRITE == fm) {
164         SLoggerE("open: %s with mode: \"w\" file exists and overwrite is FALSE!",
165                  location_full_path.c_str());
166         PostError(PlatformResult(ErrorCode::INVALID_MODIFICATION_ERR, "Zip archive already exists"),
167                   callbackId);
168         delete callback;
169         callback = NULL;
170         return;
171       }
172     }
173   }
174
175   if (!file_ptr) {
176     NodePtr node_ptr;
177
178     if (FileMode::WRITE == fm || FileMode::READ_WRITE == fm || FileMode::ADD == fm) {
179       LoggerD(
180           "Archive file not found - trying to create new one at: "
181           "full: %s",
182           location_full_path.c_str());
183
184       std::string parent_path_string = path->getPath();
185       PathPtr parent_path = Path::create(parent_path_string);
186       LoggerD("Parent path: %s", parent_path_string.c_str());
187
188       NodePtr parent_node;
189       PlatformResult result = Node::resolve(parent_path, &parent_node);
190       if (result.error_code() != ErrorCode::NO_ERROR) {
191         LoggerE("Filesystem exception - calling error callback");
192         PostError(result, callbackId);
193         delete callback;
194         callback = NULL;
195         return;
196       }
197
198       parent_node->setPermissions(PERM_READ | PERM_WRITE);
199       std::string filename = path->getName();
200       LoggerD("File name: %s", filename.c_str());
201       result = parent_node->createChild(Path::create(filename), NT_FILE, &node_ptr);
202       if (result.error_code() != ErrorCode::NO_ERROR) {
203         LoggerE("Filesystem exception - calling error callback");
204         PostError(result, callbackId);
205         delete callback;
206         callback = NULL;
207         return;
208       }
209     } else {
210       LoggerE("Archive file not found");
211       PostError(PlatformResult(ErrorCode::NOT_FOUND_ERR, "Archive file not found"), callbackId);
212       delete callback;
213       callback = NULL;
214       return;
215     }
216     file_ptr = FilePtr(new File(node_ptr, File::PermissionList()));
217   }
218
219   ArchiveFilePtr afp = ArchiveFilePtr(new ArchiveFile(fm));
220   afp->setFile(file_ptr);
221   afp->setOverwrite(overwrite);
222   callback->setArchiveFile(afp);
223
224   result = ArchiveManager::getInstance().open(callback);
225
226   if (result) {
227     ReportSuccess(out);
228   } else {
229     LogAndReportError(result, &out, ("Failed to open archive."));
230   }
231 }
232
233 void ArchiveInstance::Abort(const picojson::value& args, picojson::object& out) {
234   ScopeLogger("%s", args.serialize().c_str());
235
236   picojson::object data = args.get<picojson::object>();
237   picojson::value v_op_id = data.at(PARAM_OPERATION_ID);
238
239   const long op_id = static_cast<long>(v_op_id.get<double>());
240
241   ArchiveManager::getInstance().abort(op_id);
242
243   ReportSuccess(out);
244 }
245
246 unsigned int ConvertStringToCompressionLevel(const std::string& level) {
247   ScopeLogger();
248
249   if (kNoCompressionStr == level) {
250     return Z_NO_COMPRESSION;
251   } else if (kFastCompressionStr == level) {
252     return Z_BEST_SPEED;
253   } else if (kBestCompressionStr == level) {
254     return Z_BEST_COMPRESSION;
255   } else {
256     return Z_DEFAULT_COMPRESSION;
257   }
258 }
259
260 void ArchiveInstance::Add(const picojson::value& args, picojson::object& out) {
261   ScopeLogger("%s", args.serialize().c_str());
262
263   CHECK_PRIVILEGE_ACCESS(kPrivilegeFilesystemWrite, &out);
264   picojson::object data = args.get<picojson::object>();
265   picojson::value v_source = data.at(PARAM_SOURCE_FILE);
266   CHECK_STORAGE_ACCESS(v_source.get<std::string>(), &out);
267
268   picojson::value v_options = data.at(PARAM_OPTIONS);
269   picojson::value v_op_id = data.at(PARAM_OPERATION_ID);
270   picojson::value v_handle = data.at(ARCHIVE_FILE_HANDLE);
271
272   const double callbackId = args.get(JSON_CALLBACK_ID).get<double>();
273   const long operationId = static_cast<long>(v_op_id.get<double>());
274   const long handle = static_cast<long>(v_handle.get<double>());
275
276   AddProgressCallback* callback = new AddProgressCallback(*this);
277
278   NodePtr node;
279   PlatformResult result = Node::resolve(Path::create(v_source.get<std::string>()), &node);
280   if (result.error_code() != ErrorCode::NO_ERROR) {
281     LoggerE("Filesystem exception - calling error callback");
282     PostError(result, callbackId);
283     delete callback;
284     callback = NULL;
285     return;
286   }
287
288   FilePtr file_ptr = FilePtr(new File(node, File::PermissionList()));
289   ArchiveFileEntryPtr afep = ArchiveFileEntryPtr(new ArchiveFileEntry(file_ptr));
290
291   callback->setOperationId(operationId);
292   callback->setCallbackId(callbackId);
293   callback->setFileEntry(afep);
294
295   callback->setBasePath(file_ptr->getNode()->getPath()->getPath());
296
297   // check and set options
298   LoggerD("Processing OPTIONS dictionary: %s", v_options.serialize().c_str());
299   const auto& dest = v_options.get(kArchiveFileEntryOptDest);
300   if (dest.is<std::string>()) {
301     std::string dic_destination = dest.get<std::string>();
302     LoggerD("Setting destination path to: \"%s\"", dic_destination.c_str());
303     afep->setDestination(dic_destination);
304   }
305
306   const auto& strip = v_options.get(kArchiveFileEntryOptStrip);
307   if (strip.is<bool>()) {
308     bool dic_strip = strip.get<bool>();
309     LoggerD("Setting strip option to: %d", dic_strip);
310     afep->setStriped(dic_strip);
311   }
312
313   const auto& level = v_options.get(kArchiveFileEntryOptCompressionLevel);
314   if (level.is<std::string>()) {
315     std::string dic_compression_level = level.get<std::string>();
316     LoggerD("Setting compression level to: \"%s\"", dic_compression_level.c_str());
317     afep->setCompressionLevel(ConvertStringToCompressionLevel(dic_compression_level));
318   }
319
320   LoggerD("base path:%s base virt:%s", callback->getBasePath().c_str(),
321           callback->getBaseVirtualPath().c_str());
322
323   ArchiveFilePtr priv;
324   result = ArchiveManager::getInstance().getPrivData(handle, &priv);
325   if (result.error_code() != ErrorCode::NO_ERROR) {
326     LoggerE("Exception occurred");
327     delete callback;
328     callback = NULL;
329     return;
330   }
331
332   if (!priv->isAllowedOperation(ARCHIVE_FUNCTION_API_ARCHIVE_FILE_ADD)) {
333     LoggerE("Not allowed operation");
334     delete callback;
335     callback = NULL;
336     return;
337   }
338
339   result = priv->add(callback);
340   if (result.error_code() != ErrorCode::NO_ERROR) {
341     LoggerE("Exception occurred");
342     delete callback;
343     callback = NULL;
344     return;
345   }
346 }
347
348 void ArchiveInstance::ExtractAll(const picojson::value& args, picojson::object& out) {
349   ScopeLogger("%s", args.serialize().c_str());
350
351   CHECK_PRIVILEGE_ACCESS(kPrivilegeFilesystemWrite, &out);
352   picojson::object data = args.get<picojson::object>();
353   picojson::value v_dest_dir = data.at(PARAM_DESTINATION_DIR);
354   CHECK_STORAGE_ACCESS(v_dest_dir.get<std::string>(), &out);
355
356   picojson::value v_overwrite = data.at(PARAM_OVERWRITE);
357   picojson::value v_op_id = data.at(PARAM_OPERATION_ID);
358   picojson::value v_handle = data.at(ARCHIVE_FILE_HANDLE);
359
360   const double callbackId = args.get(JSON_CALLBACK_ID).get<double>();
361   const long operationId = static_cast<long>(v_op_id.get<double>());
362   const long handle = static_cast<long>(v_handle.get<double>());
363
364   ExtractAllProgressCallback* callback = new ExtractAllProgressCallback(*this);
365
366   NodePtr node;
367   PlatformResult result = Node::resolve(Path::create(v_dest_dir.get<std::string>()), &node);
368   if (result.error_code() != ErrorCode::NO_ERROR) {
369     LoggerE("Filesystem exception - calling error callback");
370     PostError(result, callbackId);
371     delete callback;
372     callback = NULL;
373     return;
374   }
375
376   FilePtr file_ptr = FilePtr(new File(node, File::PermissionList()));
377
378   callback->setDirectory(file_ptr);
379   callback->setOperationId(operationId);
380   callback->setCallbackId(callbackId);
381
382   if (v_overwrite.is<bool>()) {
383     callback->setOverwrite(v_overwrite.get<bool>());
384   }
385
386   ArchiveFilePtr priv;
387   result = ArchiveManager::getInstance().getPrivData(handle, &priv);
388   if (result.error_code() != ErrorCode::NO_ERROR) {
389     LoggerE("Exception occurred");
390     delete callback;
391     callback = NULL;
392     return;
393   }
394
395   if (!priv->isAllowedOperation(ARCHIVE_FUNCTION_API_ARCHIVE_FILE_EXTRACT_ALL)) {
396     LoggerE("Not allowed operation");
397     delete callback;
398     callback = NULL;
399     return;
400   }
401
402   result = priv->extractAll(callback);
403   if (result.error_code() != ErrorCode::NO_ERROR) {
404     LoggerE("Exception occurred");
405     delete callback;
406     callback = NULL;
407     return;
408   }
409 }
410
411 void ArchiveInstance::GetEntries(const picojson::value& args, picojson::object& out) {
412   ScopeLogger("%s", args.serialize().c_str());
413
414   CHECK_PRIVILEGE_ACCESS(kPrivilegeFilesystemRead, &out);
415
416   picojson::object data = args.get<picojson::object>();
417   picojson::value v_op_id = data.at(PARAM_OPERATION_ID);
418   picojson::value v_handle = data.at(ARCHIVE_FILE_HANDLE);
419
420   const double callbackId = args.get(JSON_CALLBACK_ID).get<double>();
421   const long operationId = static_cast<long>(v_op_id.get<double>());
422   const long handle = static_cast<long>(v_handle.get<double>());
423
424   GetEntriesCallbackData* callback = new GetEntriesCallbackData(*this);
425
426   callback->setOperationId(operationId);
427   callback->setCallbackId(callbackId);
428   callback->setHandle(handle);
429
430   ArchiveFilePtr priv;
431   PlatformResult result = ArchiveManager::getInstance().getPrivData(handle, &priv);
432   if (result.error_code() != ErrorCode::NO_ERROR) {
433     LoggerE("Exception occurred");
434     delete callback;
435     callback = NULL;
436     return;
437   }
438
439   if (!priv->isAllowedOperation(ARCHIVE_FUNCTION_API_ARCHIVE_FILE_GET_ENTRIES)) {
440     LoggerE("Not allowed operation");
441     delete callback;
442     callback = NULL;
443     return;
444   }
445
446   result = priv->getEntries(callback);
447   if (result.error_code() != ErrorCode::NO_ERROR) {
448     LoggerE("Exception occurred");
449     delete callback;
450     callback = NULL;
451     return;
452   }
453 }
454
455 void ArchiveInstance::GetEntryByName(const picojson::value& args, picojson::object& out) {
456   ScopeLogger("%s", args.serialize().c_str());
457
458   CHECK_PRIVILEGE_ACCESS(kPrivilegeFilesystemRead, &out);
459
460   picojson::object data = args.get<picojson::object>();
461   picojson::value v_op_id = data.at(PARAM_OPERATION_ID);
462   picojson::value v_handle = data.at(ARCHIVE_FILE_HANDLE);
463   picojson::value v_name = data.at(PARAM_NAME);
464
465   const double callbackId = args.get(JSON_CALLBACK_ID).get<double>();
466   const long operationId = static_cast<long>(v_op_id.get<double>());
467   const long handle = static_cast<long>(v_handle.get<double>());
468
469   GetEntryByNameCallbackData* callback = new GetEntryByNameCallbackData(*this);
470
471   callback->setOperationId(operationId);
472   callback->setCallbackId(callbackId);
473   callback->setName(v_name.get<std::string>());
474   callback->setHandle(handle);
475
476   ArchiveFilePtr priv;
477   PlatformResult result = ArchiveManager::getInstance().getPrivData(handle, &priv);
478   if (result.error_code() != ErrorCode::NO_ERROR) {
479     LoggerE("Exception occurred");
480     delete callback;
481     callback = NULL;
482     return;
483   }
484
485   if (!priv->isAllowedOperation(ARCHIVE_FUNCTION_API_ARCHIVE_FILE_GET_ENTRY_BY_NAME)) {
486     LoggerE("Not allowed operation");
487     delete callback;
488     callback = NULL;
489     return;
490   }
491
492   result = priv->getEntryByName(callback);
493   if (result.error_code() != ErrorCode::NO_ERROR) {
494     LoggerE("Exception occurred");
495     delete callback;
496     callback = NULL;
497     return;
498   }
499 }
500
501 void ArchiveInstance::Close(const picojson::value& args, picojson::object& out) {
502   ScopeLogger("%s", args.serialize().c_str());
503
504   picojson::object data = args.get<picojson::object>();
505   picojson::value v_handle = data.at(ARCHIVE_FILE_HANDLE);
506
507   const long handle = static_cast<long>(v_handle.get<double>());
508
509   ArchiveFilePtr priv;
510   PlatformResult result = ArchiveManager::getInstance().getPrivData(handle, &priv);
511   if (result.error_code() == ErrorCode::NO_ERROR) {
512     priv->close();
513     ArchiveManager::getInstance().erasePrivData(handle);
514   } else {
515     LoggerD("Close method was called on already closed archive. Just end execution");
516     LoggerD("%s", result.message().c_str());
517   }
518
519   ReportSuccess(out);
520 }
521
522 void ArchiveInstance::Extract(const picojson::value& args, picojson::object& out) {
523   ScopeLogger("%s", args.serialize().c_str());
524
525   CHECK_PRIVILEGE_ACCESS(kPrivilegeFilesystemWrite, &out);
526   picojson::object data = args.get<picojson::object>();
527   picojson::value v_dest_dir = data.at(PARAM_DESTINATION_DIR);
528   CHECK_STORAGE_ACCESS(v_dest_dir.get<std::string>(), &out);
529
530   picojson::value v_strip_name = data.at(PARAM_STRIP_NAME);
531   picojson::value v_overwrite = data.at(PARAM_OVERWRITE);
532   picojson::value v_op_id = data.at(PARAM_OPERATION_ID);
533   picojson::value v_handle = data.at(ARCHIVE_FILE_HANDLE);
534   picojson::value v_entry_name = data.at(PARAM_NAME);
535
536   const double callbackId = args.get(JSON_CALLBACK_ID).get<double>();
537   const auto entry_path = v_entry_name.get<std::string>();
538   if (pathContainsProhibitedSubstrings(entry_path)) {
539     const std::string error_msg = "Entry path " + entry_path + " contains a prohibited substring";
540     LoggerE("%s", error_msg.c_str());
541     PostError(PlatformResult{ErrorCode::UNKNOWN_ERR, error_msg}, callbackId);
542     return;
543   }
544
545   const long operationId = static_cast<long>(v_op_id.get<double>());
546   const long handle = static_cast<long>(v_handle.get<double>());
547
548   ExtractEntryProgressCallback* callback = new ExtractEntryProgressCallback(*this);
549
550   NodePtr node;
551   PlatformResult result = Node::resolve(Path::create(v_dest_dir.get<std::string>()), &node);
552   if (result.error_code() != ErrorCode::NO_ERROR) {
553     LoggerE("Filesystem exception - calling error callback");
554     PostError(result, callbackId);
555     delete callback;
556     callback = NULL;
557     return;
558   }
559
560   FilePtr file_ptr = FilePtr(new File(node, File::PermissionList()));
561
562   callback->setDirectory(file_ptr);
563   callback->setOperationId(operationId);
564   callback->setCallbackId(callbackId);
565
566   if (v_overwrite.is<bool>()) {
567     callback->setOverwrite(v_overwrite.get<bool>());
568   }
569   if (v_strip_name.is<bool>()) {
570     callback->setStripName(v_strip_name.get<bool>());
571   }
572
573   ArchiveFilePtr archive_file_ptr;
574   result = ArchiveManager::getInstance().getPrivData(handle, &archive_file_ptr);
575   if (result.error_code() != ErrorCode::NO_ERROR) {
576     LoggerE("Exception occurred");
577     delete callback;
578     callback = NULL;
579     return;
580   }
581
582   ArchiveFileEntryPtrMapPtr entries = archive_file_ptr->getEntryMap();
583   auto it = entries->find(v_entry_name.get<std::string>());
584
585   // Not found but if our name does not contain '/'
586   // try looking for directory with such name
587   if (entries->end() == it && !isDirectoryPath(v_entry_name.get<std::string>())) {
588     const std::string try_directory = v_entry_name.get<std::string>() + "/";
589     LoggerD("GetEntryByName Trying directory: [%s]", try_directory.c_str());
590     it = entries->find(try_directory);
591   }
592
593   if (entries->end() == it) {
594     LoggerE("Failed to find entry");
595     PostError(PlatformResult(ErrorCode::UNKNOWN_ERR, "Failed to find entry"), callbackId);
596     delete callback;
597     callback = NULL;
598     return;
599   }
600
601   result = it->second->extractTo(callback);
602   if (result.error_code() != ErrorCode::NO_ERROR) {
603     LoggerE("ArchiveFileEntry.extractTo error");
604     PostError(result, callbackId);
605     delete callback;
606     callback = NULL;
607     return;
608   }
609 }
610
611 void ArchiveInstance::FetchStorages(const picojson::value& args, picojson::object& out) {
612   ScopeLogger();
613
614   picojson::array storages;
615   for (const auto& storage : common::FilesystemProvider::Create().GetAllStorages()) {
616     storages.push_back(storage->ToJson());
617   }
618   ReportSuccess(picojson::value(storages), out);
619 }
620
621 }  // namespace archive
622 }  // namespace extension