- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / drive / drive_api_util.cc
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/drive/drive_api_util.h"
6
7 #include <string>
8
9 #include "base/command_line.h"
10 #include "base/logging.h"
11 #include "base/strings/string16.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/values.h"
16 #include "chrome/browser/drive/drive_switches.h"
17 #include "chrome/browser/google_apis/drive_api_parser.h"
18 #include "chrome/browser/google_apis/gdata_wapi_parser.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "net/base/escape.h"
21 #include "third_party/re2/re2/re2.h"
22 #include "url/gurl.h"
23
24 namespace drive {
25 namespace util {
26 namespace {
27
28 // Google Apps MIME types:
29 const char kGoogleDocumentMimeType[] = "application/vnd.google-apps.document";
30 const char kGoogleDrawingMimeType[] = "application/vnd.google-apps.drawing";
31 const char kGooglePresentationMimeType[] =
32     "application/vnd.google-apps.presentation";
33 const char kGoogleSpreadsheetMimeType[] =
34     "application/vnd.google-apps.spreadsheet";
35 const char kGoogleTableMimeType[] = "application/vnd.google-apps.table";
36 const char kGoogleFormMimeType[] = "application/vnd.google-apps.form";
37 const char kDriveFolderMimeType[] = "application/vnd.google-apps.folder";
38
39 ScopedVector<std::string> CopyScopedVectorString(
40     const ScopedVector<std::string>& source) {
41   ScopedVector<std::string> result;
42   result.reserve(source.size());
43   for (size_t i = 0; i < source.size(); ++i)
44     result.push_back(new std::string(*source[i]));
45
46   return result.Pass();
47 }
48
49 // Converts AppIcon (of GData WAPI) to DriveAppIcon.
50 scoped_ptr<google_apis::DriveAppIcon>
51 ConvertAppIconToDriveAppIcon(const google_apis::AppIcon& app_icon) {
52   scoped_ptr<google_apis::DriveAppIcon> resource(
53       new google_apis::DriveAppIcon);
54   switch (app_icon.category()) {
55     case google_apis::AppIcon::ICON_UNKNOWN:
56       resource->set_category(google_apis::DriveAppIcon::UNKNOWN);
57       break;
58     case google_apis::AppIcon::ICON_DOCUMENT:
59       resource->set_category(google_apis::DriveAppIcon::DOCUMENT);
60       break;
61     case google_apis::AppIcon::ICON_APPLICATION:
62       resource->set_category(google_apis::DriveAppIcon::APPLICATION);
63       break;
64     case google_apis::AppIcon::ICON_SHARED_DOCUMENT:
65       resource->set_category(google_apis::DriveAppIcon::SHARED_DOCUMENT);
66       break;
67     default:
68       NOTREACHED();
69   }
70
71   resource->set_icon_side_length(app_icon.icon_side_length());
72   resource->set_icon_url(app_icon.GetIconURL());
73   return resource.Pass();
74 }
75
76 // Converts InstalledApp to AppResource.
77 scoped_ptr<google_apis::AppResource>
78 ConvertInstalledAppToAppResource(
79     const google_apis::InstalledApp& installed_app) {
80   scoped_ptr<google_apis::AppResource> resource(new google_apis::AppResource);
81   resource->set_application_id(installed_app.app_id());
82   resource->set_name(installed_app.app_name());
83   resource->set_object_type(installed_app.object_type());
84   resource->set_supports_create(installed_app.supports_create());
85   resource->set_product_url(installed_app.GetProductUrl());
86
87   {
88     ScopedVector<std::string> primary_mimetypes(
89         CopyScopedVectorString(installed_app.primary_mimetypes()));
90     resource->set_primary_mimetypes(primary_mimetypes.Pass());
91   }
92   {
93     ScopedVector<std::string> secondary_mimetypes(
94         CopyScopedVectorString(installed_app.secondary_mimetypes()));
95     resource->set_secondary_mimetypes(secondary_mimetypes.Pass());
96   }
97   {
98     ScopedVector<std::string> primary_file_extensions(
99         CopyScopedVectorString(installed_app.primary_extensions()));
100     resource->set_primary_file_extensions(primary_file_extensions.Pass());
101   }
102   {
103     ScopedVector<std::string> secondary_file_extensions(
104         CopyScopedVectorString(installed_app.secondary_extensions()));
105     resource->set_secondary_file_extensions(secondary_file_extensions.Pass());
106   }
107
108   {
109     const ScopedVector<google_apis::AppIcon>& app_icons =
110         installed_app.app_icons();
111     ScopedVector<google_apis::DriveAppIcon> icons;
112     icons.reserve(app_icons.size());
113     for (size_t i = 0; i < app_icons.size(); ++i) {
114       icons.push_back(ConvertAppIconToDriveAppIcon(*app_icons[i]).release());
115     }
116     resource->set_icons(icons.Pass());
117   }
118
119   // supports_import, installed and authorized are not supported in
120   // InstalledApp.
121
122   return resource.Pass();
123 }
124
125 // Returns the argument string.
126 std::string Identity(const std::string& resource_id) { return resource_id; }
127
128 }  // namespace
129
130
131 bool IsDriveV2ApiEnabled() {
132   const CommandLine* command_line = CommandLine::ForCurrentProcess();
133
134   // Enable Drive API v2 by default.
135   if (!command_line->HasSwitch(switches::kEnableDriveV2Api))
136     return true;
137
138   std::string value =
139       command_line->GetSwitchValueASCII(switches::kEnableDriveV2Api);
140   StringToLowerASCII(&value);
141   // The value must be "" or "true" for true, or "false" for false.
142   DCHECK(value.empty() || value == "true" || value == "false");
143   return value != "false";
144 }
145
146 std::string EscapeQueryStringValue(const std::string& str) {
147   std::string result;
148   result.reserve(str.size());
149   for (size_t i = 0; i < str.size(); ++i) {
150     if (str[i] == '\\' || str[i] == '\'') {
151       result.push_back('\\');
152     }
153     result.push_back(str[i]);
154   }
155   return result;
156 }
157
158 std::string TranslateQuery(const std::string& original_query) {
159   // In order to handle non-ascii white spaces correctly, convert to UTF16.
160   base::string16 query = UTF8ToUTF16(original_query);
161   const base::string16 kDelimiter(
162       kWhitespaceUTF16 + base::string16(1, static_cast<char16>('"')));
163
164   std::string result;
165   for (size_t index = query.find_first_not_of(kWhitespaceUTF16);
166        index != base::string16::npos;
167        index = query.find_first_not_of(kWhitespaceUTF16, index)) {
168     bool is_exclusion = (query[index] == '-');
169     if (is_exclusion)
170       ++index;
171     if (index == query.length()) {
172       // Here, the token is '-' and it should be ignored.
173       continue;
174     }
175
176     size_t begin_token = index;
177     base::string16 token;
178     if (query[begin_token] == '"') {
179       // Quoted query.
180       ++begin_token;
181       size_t end_token = query.find('"', begin_token);
182       if (end_token == base::string16::npos) {
183         // This is kind of syntax error, since quoted string isn't finished.
184         // However, the query is built by user manually, so here we treat
185         // whole remaining string as a token as a fallback, by appending
186         // a missing double-quote character.
187         end_token = query.length();
188         query.push_back('"');
189       }
190
191       token = query.substr(begin_token, end_token - begin_token);
192       index = end_token + 1;  // Consume last '"', too.
193     } else {
194       size_t end_token = query.find_first_of(kDelimiter, begin_token);
195       if (end_token == base::string16::npos) {
196         end_token = query.length();
197       }
198
199       token = query.substr(begin_token, end_token - begin_token);
200       index = end_token;
201     }
202
203     if (token.empty()) {
204       // Just ignore an empty token.
205       continue;
206     }
207
208     if (!result.empty()) {
209       // If there are two or more tokens, need to connect with "and".
210       result.append(" and ");
211     }
212
213     // The meaning of "fullText" should include title, description and content.
214     base::StringAppendF(
215         &result,
216         "%sfullText contains \'%s\'",
217         is_exclusion ? "not " : "",
218         EscapeQueryStringValue(UTF16ToUTF8(token)).c_str());
219   }
220
221   return result;
222 }
223
224 std::string ExtractResourceIdFromUrl(const GURL& url) {
225   return net::UnescapeURLComponent(url.ExtractFileName(),
226                                    net::UnescapeRule::URL_SPECIAL_CHARS);
227 }
228
229 std::string CanonicalizeResourceId(const std::string& resource_id) {
230   // If resource ID is in the old WAPI format starting with a prefix like
231   // "document:", strip it and return the remaining part.
232   std::string stripped_resource_id;
233   if (RE2::FullMatch(resource_id, "^[a-z-]+(?::|%3A)([\\w-]+)$",
234                      &stripped_resource_id))
235     return stripped_resource_id;
236   return resource_id;
237 }
238
239 ResourceIdCanonicalizer GetIdentityResourceIdCanonicalizer() {
240   return base::Bind(&Identity);
241 }
242
243 const char kDocsListScope[] = "https://docs.google.com/feeds/";
244 const char kDriveAppsScope[] = "https://www.googleapis.com/auth/drive.apps";
245
246 void ParseShareUrlAndRun(const google_apis::GetShareUrlCallback& callback,
247                          google_apis::GDataErrorCode error,
248                          scoped_ptr<base::Value> value) {
249   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
250
251   if (!value) {
252     callback.Run(error, GURL());
253     return;
254   }
255
256   // Parsing ResourceEntry is cheap enough to do on UI thread.
257   scoped_ptr<google_apis::ResourceEntry> entry =
258       google_apis::ResourceEntry::ExtractAndParse(*value);
259   if (!entry) {
260     callback.Run(google_apis::GDATA_PARSE_ERROR, GURL());
261     return;
262   }
263
264   const google_apis::Link* share_link =
265       entry->GetLinkByType(google_apis::Link::LINK_SHARE);
266   callback.Run(error, share_link ? share_link->href() : GURL());
267 }
268
269 scoped_ptr<google_apis::AboutResource>
270 ConvertAccountMetadataToAboutResource(
271     const google_apis::AccountMetadata& account_metadata,
272     const std::string& root_resource_id) {
273   scoped_ptr<google_apis::AboutResource> resource(
274       new google_apis::AboutResource);
275   resource->set_largest_change_id(account_metadata.largest_changestamp());
276   resource->set_quota_bytes_total(account_metadata.quota_bytes_total());
277   resource->set_quota_bytes_used(account_metadata.quota_bytes_used());
278   resource->set_root_folder_id(root_resource_id);
279   return resource.Pass();
280 }
281
282 scoped_ptr<google_apis::AppList>
283 ConvertAccountMetadataToAppList(
284     const google_apis::AccountMetadata& account_metadata) {
285   scoped_ptr<google_apis::AppList> resource(new google_apis::AppList);
286
287   const ScopedVector<google_apis::InstalledApp>& installed_apps =
288       account_metadata.installed_apps();
289   ScopedVector<google_apis::AppResource> app_resources;
290   app_resources.reserve(installed_apps.size());
291   for (size_t i = 0; i < installed_apps.size(); ++i) {
292     app_resources.push_back(
293         ConvertInstalledAppToAppResource(*installed_apps[i]).release());
294   }
295   resource->set_items(app_resources.Pass());
296
297   // etag is not supported in AccountMetadata.
298
299   return resource.Pass();
300 }
301
302
303 scoped_ptr<google_apis::FileResource> ConvertResourceEntryToFileResource(
304     const google_apis::ResourceEntry& entry) {
305   scoped_ptr<google_apis::FileResource> file(new google_apis::FileResource);
306
307   file->set_file_id(entry.resource_id());
308   file->set_title(entry.title());
309   file->set_created_date(entry.published_time());
310
311   if (std::find(entry.labels().begin(), entry.labels().end(),
312                 "shared-with-me") == entry.labels().end()) {
313     // Set current time to mark the file is shared_with_me, since ResourceEntry
314     // doesn't have |shared_with_me_date| equivalent.
315     file->set_shared_with_me_date(base::Time::Now());
316   }
317
318   file->set_download_url(entry.download_url());
319   if (entry.is_folder())
320     file->set_mime_type(kDriveFolderMimeType);
321   else
322     file->set_mime_type(entry.content_mime_type());
323
324   file->set_md5_checksum(entry.file_md5());
325   file->set_file_size(entry.file_size());
326
327   file->mutable_labels()->set_trashed(entry.deleted());
328   file->set_etag(entry.etag());
329
330   google_apis::ImageMediaMetadata* image_media_metadata =
331     file->mutable_image_media_metadata();
332   image_media_metadata->set_width(entry.image_width());
333   image_media_metadata->set_height(entry.image_height());
334   image_media_metadata->set_rotation(entry.image_rotation());
335
336   ScopedVector<google_apis::ParentReference> parents;
337   for (size_t i = 0; i < entry.links().size(); ++i) {
338     using google_apis::Link;
339     const Link& link = *entry.links()[i];
340     switch (link.type()) {
341       case Link::LINK_PARENT: {
342         scoped_ptr<google_apis::ParentReference> parent(
343             new google_apis::ParentReference);
344         parent->set_parent_link(link.href());
345
346         std::string file_id =
347             drive::util::ExtractResourceIdFromUrl(link.href());
348         parent->set_file_id(file_id);
349         parent->set_is_root(file_id == kWapiRootDirectoryResourceId);
350         parents.push_back(parent.release());
351         break;
352       }
353       case Link::LINK_EDIT:
354         file->set_self_link(link.href());
355         break;
356       case Link::LINK_THUMBNAIL:
357         file->set_thumbnail_link(link.href());
358         break;
359       case Link::LINK_ALTERNATE:
360         file->set_alternate_link(link.href());
361         break;
362       case Link::LINK_EMBED:
363         file->set_embed_link(link.href());
364         break;
365       default:
366         break;
367     }
368   }
369   file->set_parents(parents.Pass());
370
371   file->set_modified_date(entry.updated_time());
372   file->set_last_viewed_by_me_date(entry.last_viewed_time());
373
374   return file.Pass();
375 }
376
377 google_apis::DriveEntryKind GetKind(
378     const google_apis::FileResource& file_resource) {
379   if (file_resource.IsDirectory())
380     return google_apis::ENTRY_KIND_FOLDER;
381
382   const std::string& mime_type = file_resource.mime_type();
383   if (mime_type == kGoogleDocumentMimeType)
384     return google_apis::ENTRY_KIND_DOCUMENT;
385   if (mime_type == kGoogleSpreadsheetMimeType)
386     return google_apis::ENTRY_KIND_SPREADSHEET;
387   if (mime_type == kGooglePresentationMimeType)
388     return google_apis::ENTRY_KIND_PRESENTATION;
389   if (mime_type == kGoogleDrawingMimeType)
390     return google_apis::ENTRY_KIND_DRAWING;
391   if (mime_type == kGoogleTableMimeType)
392     return google_apis::ENTRY_KIND_TABLE;
393   if (mime_type == kGoogleFormMimeType)
394     return google_apis::ENTRY_KIND_FORM;
395   if (mime_type == "application/pdf")
396     return google_apis::ENTRY_KIND_PDF;
397   return google_apis::ENTRY_KIND_FILE;
398 }
399
400 scoped_ptr<google_apis::ResourceEntry>
401 ConvertFileResourceToResourceEntry(
402     const google_apis::FileResource& file_resource) {
403   scoped_ptr<google_apis::ResourceEntry> entry(new google_apis::ResourceEntry);
404
405   // ResourceEntry
406   entry->set_resource_id(file_resource.file_id());
407   entry->set_id(file_resource.file_id());
408   entry->set_kind(GetKind(file_resource));
409   entry->set_title(file_resource.title());
410   entry->set_published_time(file_resource.created_date());
411   // TODO(kochi): entry->labels_
412   if (!file_resource.shared_with_me_date().is_null()) {
413     std::vector<std::string> labels;
414     labels.push_back("shared-with-me");
415     entry->set_labels(labels);
416   }
417
418   // This should be the url to download the file_resource.
419   {
420     google_apis::Content content;
421     content.set_url(file_resource.download_url());
422     content.set_mime_type(file_resource.mime_type());
423     entry->set_content(content);
424   }
425   // TODO(kochi): entry->resource_links_
426
427   // For file entries
428   entry->set_filename(file_resource.title());
429   entry->set_suggested_filename(file_resource.title());
430   entry->set_file_md5(file_resource.md5_checksum());
431   entry->set_file_size(file_resource.file_size());
432
433   // If file is removed completely, that information is only available in
434   // ChangeResource, and is reflected in |removed_|. If file is trashed, the
435   // file entry still exists but with its "trashed" label true.
436   entry->set_deleted(file_resource.labels().is_trashed());
437
438   // ImageMediaMetadata
439   entry->set_image_width(file_resource.image_media_metadata().width());
440   entry->set_image_height(file_resource.image_media_metadata().height());
441   entry->set_image_rotation(file_resource.image_media_metadata().rotation());
442
443   // CommonMetadata
444   entry->set_etag(file_resource.etag());
445   // entry->authors_
446   // entry->links_.
447   ScopedVector<google_apis::Link> links;
448   if (!file_resource.parents().empty()) {
449     google_apis::Link* link = new google_apis::Link;
450     link->set_type(google_apis::Link::LINK_PARENT);
451     link->set_href(file_resource.parents()[0]->parent_link());
452     links.push_back(link);
453   }
454   if (!file_resource.self_link().is_empty()) {
455     google_apis::Link* link = new google_apis::Link;
456     link->set_type(google_apis::Link::LINK_EDIT);
457     link->set_href(file_resource.self_link());
458     links.push_back(link);
459   }
460   if (!file_resource.thumbnail_link().is_empty()) {
461     google_apis::Link* link = new google_apis::Link;
462     link->set_type(google_apis::Link::LINK_THUMBNAIL);
463     link->set_href(file_resource.thumbnail_link());
464     links.push_back(link);
465   }
466   if (!file_resource.alternate_link().is_empty()) {
467     google_apis::Link* link = new google_apis::Link;
468     link->set_type(google_apis::Link::LINK_ALTERNATE);
469     link->set_href(file_resource.alternate_link());
470     links.push_back(link);
471   }
472   if (!file_resource.embed_link().is_empty()) {
473     google_apis::Link* link = new google_apis::Link;
474     link->set_type(google_apis::Link::LINK_EMBED);
475     link->set_href(file_resource.embed_link());
476     links.push_back(link);
477   }
478   entry->set_links(links.Pass());
479
480   // entry->categories_
481   entry->set_updated_time(file_resource.modified_date());
482   entry->set_last_viewed_time(file_resource.last_viewed_by_me_date());
483
484   entry->FillRemainingFields();
485   return entry.Pass();
486 }
487
488 scoped_ptr<google_apis::ResourceEntry>
489 ConvertChangeResourceToResourceEntry(
490     const google_apis::ChangeResource& change_resource) {
491   scoped_ptr<google_apis::ResourceEntry> entry;
492   if (change_resource.file())
493     entry = ConvertFileResourceToResourceEntry(*change_resource.file()).Pass();
494   else
495     entry.reset(new google_apis::ResourceEntry);
496
497   entry->set_resource_id(change_resource.file_id());
498   // If |is_deleted()| returns true, the file is removed from Drive.
499   entry->set_removed(change_resource.is_deleted());
500   entry->set_changestamp(change_resource.change_id());
501
502   return entry.Pass();
503 }
504
505 scoped_ptr<google_apis::ResourceList>
506 ConvertFileListToResourceList(const google_apis::FileList& file_list) {
507   scoped_ptr<google_apis::ResourceList> feed(new google_apis::ResourceList);
508
509   const ScopedVector<google_apis::FileResource>& items = file_list.items();
510   ScopedVector<google_apis::ResourceEntry> entries;
511   for (size_t i = 0; i < items.size(); ++i)
512     entries.push_back(ConvertFileResourceToResourceEntry(*items[i]).release());
513   feed->set_entries(entries.Pass());
514
515   ScopedVector<google_apis::Link> links;
516   if (!file_list.next_link().is_empty()) {
517     google_apis::Link* link = new google_apis::Link;
518     link->set_type(google_apis::Link::LINK_NEXT);
519     link->set_href(file_list.next_link());
520     links.push_back(link);
521   }
522   feed->set_links(links.Pass());
523
524   return feed.Pass();
525 }
526
527 scoped_ptr<google_apis::ResourceList>
528 ConvertChangeListToResourceList(const google_apis::ChangeList& change_list) {
529   scoped_ptr<google_apis::ResourceList> feed(new google_apis::ResourceList);
530
531   const ScopedVector<google_apis::ChangeResource>& items = change_list.items();
532   ScopedVector<google_apis::ResourceEntry> entries;
533   for (size_t i = 0; i < items.size(); ++i) {
534     entries.push_back(
535         ConvertChangeResourceToResourceEntry(*items[i]).release());
536   }
537   feed->set_entries(entries.Pass());
538
539   feed->set_largest_changestamp(change_list.largest_change_id());
540
541   ScopedVector<google_apis::Link> links;
542   if (!change_list.next_link().is_empty()) {
543     google_apis::Link* link = new google_apis::Link;
544     link->set_type(google_apis::Link::LINK_NEXT);
545     link->set_href(change_list.next_link());
546     links.push_back(link);
547   }
548   feed->set_links(links.Pass());
549
550   return feed.Pass();
551 }
552
553 const char kWapiRootDirectoryResourceId[] = "folder:root";
554
555 }  // namespace util
556 }  // namespace drive