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