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.
5 #include "chrome/browser/drive/drive_api_util.h"
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"
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";
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]));
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);
58 case google_apis::AppIcon::ICON_DOCUMENT:
59 resource->set_category(google_apis::DriveAppIcon::DOCUMENT);
61 case google_apis::AppIcon::ICON_APPLICATION:
62 resource->set_category(google_apis::DriveAppIcon::APPLICATION);
64 case google_apis::AppIcon::ICON_SHARED_DOCUMENT:
65 resource->set_category(google_apis::DriveAppIcon::SHARED_DOCUMENT);
71 resource->set_icon_side_length(app_icon.icon_side_length());
72 resource->set_icon_url(app_icon.GetIconURL());
73 return resource.Pass();
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());
88 ScopedVector<std::string> primary_mimetypes(
89 CopyScopedVectorString(installed_app.primary_mimetypes()));
90 resource->set_primary_mimetypes(primary_mimetypes.Pass());
93 ScopedVector<std::string> secondary_mimetypes(
94 CopyScopedVectorString(installed_app.secondary_mimetypes()));
95 resource->set_secondary_mimetypes(secondary_mimetypes.Pass());
98 ScopedVector<std::string> primary_file_extensions(
99 CopyScopedVectorString(installed_app.primary_extensions()));
100 resource->set_primary_file_extensions(primary_file_extensions.Pass());
103 ScopedVector<std::string> secondary_file_extensions(
104 CopyScopedVectorString(installed_app.secondary_extensions()));
105 resource->set_secondary_file_extensions(secondary_file_extensions.Pass());
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());
116 resource->set_icons(icons.Pass());
119 // supports_import, installed and authorized are not supported in
122 return resource.Pass();
125 // Returns the argument string.
126 std::string Identity(const std::string& resource_id) { return resource_id; }
131 bool IsDriveV2ApiEnabled() {
132 const CommandLine* command_line = CommandLine::ForCurrentProcess();
134 // Enable Drive API v2 by default.
135 if (!command_line->HasSwitch(switches::kEnableDriveV2Api))
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";
146 std::string EscapeQueryStringValue(const std::string& str) {
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('\\');
153 result.push_back(str[i]);
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>('"')));
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] == '-');
171 if (index == query.length()) {
172 // Here, the token is '-' and it should be ignored.
176 size_t begin_token = index;
177 base::string16 token;
178 if (query[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('"');
191 token = query.substr(begin_token, end_token - begin_token);
192 index = end_token + 1; // Consume last '"', too.
194 size_t end_token = query.find_first_of(kDelimiter, begin_token);
195 if (end_token == base::string16::npos) {
196 end_token = query.length();
199 token = query.substr(begin_token, end_token - begin_token);
204 // Just ignore an empty token.
208 if (!result.empty()) {
209 // If there are two or more tokens, need to connect with "and".
210 result.append(" and ");
213 // The meaning of "fullText" should include title, description and content.
216 "%sfullText contains \'%s\'",
217 is_exclusion ? "not " : "",
218 EscapeQueryStringValue(UTF16ToUTF8(token)).c_str());
224 std::string ExtractResourceIdFromUrl(const GURL& url) {
225 return net::UnescapeURLComponent(url.ExtractFileName(),
226 net::UnescapeRule::URL_SPECIAL_CHARS);
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;
239 ResourceIdCanonicalizer GetIdentityResourceIdCanonicalizer() {
240 return base::Bind(&Identity);
243 const char kDocsListScope[] = "https://docs.google.com/feeds/";
244 const char kDriveAppsScope[] = "https://www.googleapis.com/auth/drive.apps";
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));
252 callback.Run(error, GURL());
256 // Parsing ResourceEntry is cheap enough to do on UI thread.
257 scoped_ptr<google_apis::ResourceEntry> entry =
258 google_apis::ResourceEntry::ExtractAndParse(*value);
260 callback.Run(google_apis::GDATA_PARSE_ERROR, GURL());
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());
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();
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);
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());
295 resource->set_items(app_resources.Pass());
297 // etag is not supported in AccountMetadata.
299 return resource.Pass();
303 scoped_ptr<google_apis::FileResource> ConvertResourceEntryToFileResource(
304 const google_apis::ResourceEntry& entry) {
305 scoped_ptr<google_apis::FileResource> file(new google_apis::FileResource);
307 file->set_file_id(entry.resource_id());
308 file->set_title(entry.title());
309 file->set_created_date(entry.published_time());
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());
318 file->set_download_url(entry.download_url());
319 if (entry.is_folder())
320 file->set_mime_type(kDriveFolderMimeType);
322 file->set_mime_type(entry.content_mime_type());
324 file->set_md5_checksum(entry.file_md5());
325 file->set_file_size(entry.file_size());
327 file->mutable_labels()->set_trashed(entry.deleted());
328 file->set_etag(entry.etag());
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());
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());
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());
353 case Link::LINK_EDIT:
354 file->set_self_link(link.href());
356 case Link::LINK_THUMBNAIL:
357 file->set_thumbnail_link(link.href());
359 case Link::LINK_ALTERNATE:
360 file->set_alternate_link(link.href());
362 case Link::LINK_EMBED:
363 file->set_embed_link(link.href());
369 file->set_parents(parents.Pass());
371 file->set_modified_date(entry.updated_time());
372 file->set_last_viewed_by_me_date(entry.last_viewed_time());
377 google_apis::DriveEntryKind GetKind(
378 const google_apis::FileResource& file_resource) {
379 if (file_resource.IsDirectory())
380 return google_apis::ENTRY_KIND_FOLDER;
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;
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);
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);
418 // This should be the url to download the file_resource.
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);
425 // TODO(kochi): entry->resource_links_
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());
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());
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());
444 entry->set_etag(file_resource.etag());
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);
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);
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);
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);
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);
478 entry->set_links(links.Pass());
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());
484 entry->FillRemainingFields();
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();
495 entry.reset(new google_apis::ResourceEntry);
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());
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);
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());
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);
522 feed->set_links(links.Pass());
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);
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) {
535 ConvertChangeResourceToResourceEntry(*items[i]).release());
537 feed->set_entries(entries.Pass());
539 feed->set_largest_changestamp(change_list.largest_change_id());
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);
548 feed->set_links(links.Pass());
553 const char kWapiRootDirectoryResourceId[] = "folder:root";