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/files/scoped_platform_file_closer.h"
11 #include "base/logging.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"
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
) {
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
;
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
]));
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
);
80 case google_apis::AppIcon::ICON_DOCUMENT
:
81 resource
->set_category(google_apis::DriveAppIcon::DOCUMENT
);
83 case google_apis::AppIcon::ICON_APPLICATION
:
84 resource
->set_category(google_apis::DriveAppIcon::APPLICATION
);
86 case google_apis::AppIcon::ICON_SHARED_DOCUMENT
:
87 resource
->set_category(google_apis::DriveAppIcon::SHARED_DOCUMENT
);
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
143 return resource
.Pass();
146 // Returns the argument string.
147 std::string
Identity(const std::string
& resource_id
) { return resource_id
; }
152 bool IsDriveV2ApiEnabled() {
153 const CommandLine
* command_line
= CommandLine::ForCurrentProcess();
155 // Enable Drive API v2 by default.
156 if (!command_line
->HasSwitch(switches::kEnableDriveV2Api
))
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
) {
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
]);
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
>('"')));
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
] == '-');
193 if (index
== query
.length()) {
194 // Here, the token is '-' and it should be ignored.
198 size_t begin_token
= index
;
199 base::string16 token
;
200 if (query
[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.
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
);
226 // Just ignore an empty token.
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.
238 "%sfullText contains \'%s\'",
239 is_exclusion
? "not " : "",
240 EscapeQueryStringValue(base::UTF16ToUTF8(token
)).c_str());
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
;
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
));
274 callback
.Run(error
, GURL());
278 // Parsing ResourceEntry is cheap enough to do on UI thread.
279 scoped_ptr
<google_apis::ResourceEntry
> entry
=
280 google_apis::ResourceEntry::ExtractAndParse(*value
);
282 callback
.Run(google_apis::GDATA_PARSE_ERROR
, GURL());
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
);
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());
382 case Link::LINK_EDIT
:
383 file
->set_self_link(link
.href());
385 case Link::LINK_THUMBNAIL
:
386 file
->set_thumbnail_link(link
.href());
388 case Link::LINK_ALTERNATE
:
389 file
->set_alternate_link(link
.href());
391 case Link::LINK_EMBED
:
392 file
->set_embed_link(link
.href());
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());
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
);
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_
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());
474 entry
->set_etag(file_resource
.etag());
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();
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();
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());
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());
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
) {
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());
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
,
589 if (file
== base::kInvalidPlatformFileValue
)
590 return std::string();
591 base::ScopedPlatformFileCloser
file_closer(&file
);
593 base::MD5Context context
;
594 base::MD5Init(&context
);
597 scoped_ptr
<char[]> buffer(new char[kBufferSize
]);
599 // Avoid using ReadPlatformFileCurPosNoBestEffort for now.
600 // http://crbug.com/145873
601 int result
= base::ReadPlatformFileNoBestEffort(
602 file
, offset
, buffer
.get(), kBufferSize
);
606 return std::string();
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";