NaCl: Update revision in DEPS, r12770 -> r12773
[chromium-blink-merge.git] / chrome / browser / drive / drive_api_util.cc
blob8a2569dd38400946d268990952c14c1f288785d3
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"
7 #include <string>
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"
27 namespace drive {
28 namespace util {
29 namespace {
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";
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();
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]));
68 return result.Pass();
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();
93 resource->set_icon_side_length(app_icon.icon_side_length());
94 resource->set_icon_url(app_icon.GetIconURL());
95 return resource.Pass();
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());
109 ScopedVector<std::string> primary_mimetypes(
110 CopyScopedVectorString(installed_app.primary_mimetypes()));
111 resource->set_primary_mimetypes(primary_mimetypes.Pass());
114 ScopedVector<std::string> secondary_mimetypes(
115 CopyScopedVectorString(installed_app.secondary_mimetypes()));
116 resource->set_secondary_mimetypes(secondary_mimetypes.Pass());
119 ScopedVector<std::string> primary_file_extensions(
120 CopyScopedVectorString(installed_app.primary_extensions()));
121 resource->set_primary_file_extensions(primary_file_extensions.Pass());
124 ScopedVector<std::string> secondary_file_extensions(
125 CopyScopedVectorString(installed_app.secondary_extensions()));
126 resource->set_secondary_file_extensions(secondary_file_extensions.Pass());
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());
137 resource->set_icons(icons.Pass());
140 // supports_import, installed and authorized are not supported in
141 // InstalledApp.
143 return resource.Pass();
146 // Returns the argument string.
147 std::string Identity(const std::string& resource_id) { return resource_id; }
149 } // namespace
152 bool IsDriveV2ApiEnabled() {
153 const CommandLine* command_line = CommandLine::ForCurrentProcess();
155 // Enable Drive API v2 by default.
156 if (!command_line->HasSwitch(switches::kEnableDriveV2Api))
157 return true;
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";
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('\\');
174 result.push_back(str[i]);
176 return result;
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>('"')));
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;
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('"');
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();
221 token = query.substr(begin_token, end_token - begin_token);
222 index = end_token;
225 if (token.empty()) {
226 // Just ignore an empty token.
227 continue;
230 if (!result.empty()) {
231 // If there are two or more tokens, need to connect with "and".
232 result.append(" and ");
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());
243 return result;
246 std::string ExtractResourceIdFromUrl(const GURL& url) {
247 return net::UnescapeURLComponent(url.ExtractFileName(),
248 net::UnescapeRule::URL_SPECIAL_CHARS);
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;
261 ResourceIdCanonicalizer GetIdentityResourceIdCanonicalizer() {
262 return base::Bind(&Identity);
265 const char kDocsListScope[] = "https://docs.google.com/feeds/";
266 const char kDriveAppsScope[] = "https://www.googleapis.com/auth/drive.apps";
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));
273 if (!value) {
274 callback.Run(error, GURL());
275 return;
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;
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());
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();
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);
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());
317 resource->set_items(app_resources.Pass());
319 // etag is not supported in AccountMetadata.
321 return resource.Pass();
325 scoped_ptr<google_apis::FileResource> ConvertResourceEntryToFileResource(
326 const google_apis::ResourceEntry& entry) {
327 scoped_ptr<google_apis::FileResource> file(new google_apis::FileResource);
329 file->set_file_id(entry.resource_id());
330 file->set_title(entry.title());
331 file->set_created_date(entry.published_time());
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());
340 file->set_shared(std::find(entry.labels().begin(), entry.labels().end(),
341 "shared") != entry.labels().end());
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);
353 file->set_md5_checksum(entry.file_md5());
354 file->set_file_size(entry.file_size());
356 file->mutable_labels()->set_trashed(entry.deleted());
357 file->set_etag(entry.etag());
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());
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());
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;
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;
398 file->set_parents(parents.Pass());
400 file->set_modified_date(entry.updated_time());
401 file->set_last_viewed_by_me_date(entry.last_viewed_time());
403 return file.Pass();
406 google_apis::DriveEntryKind GetKind(
407 const google_apis::FileResource& file_resource) {
408 if (file_resource.IsDirectory())
409 return google_apis::ENTRY_KIND_FOLDER;
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;
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);
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());
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);
448 // This should be the url to download the file_resource.
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);
455 // TODO(kochi): entry->resource_links_
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());
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());
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());
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);
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);
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);
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);
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);
508 entry->set_links(links.Pass());
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());
514 entry->FillRemainingFields();
515 return entry.Pass();
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);
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());
532 return entry.Pass();
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);
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());
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);
552 feed->set_links(links.Pass());
554 return feed.Pass();
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);
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());
567 feed->set_entries(entries.Pass());
569 feed->set_largest_changestamp(change_list.largest_change_id());
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);
578 feed->set_links(links.Pass());
580 return feed.Pass();
583 std::string GetMd5Digest(const base::FilePath& file_path) {
584 const int kBufferSize = 512 * 1024; // 512kB.
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);
593 base::MD5Context context;
594 base::MD5Init(&context);
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);
604 if (result < 0) {
605 // Found an error.
606 return std::string();
609 if (result == 0) {
610 // End of file.
611 break;
614 offset += result;
615 base::MD5Update(&context, base::StringPiece(buffer.get(), result));
618 base::MD5Digest digest;
619 base::MD5Final(&digest, &context);
620 return MD5DigestToBase16(digest);
623 const char kWapiRootDirectoryResourceId[] = "folder:root";
625 } // namespace util
626 } // namespace drive