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/files/file.h"
10 #include "base/logging.h"
12 #include "base/strings/string16.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/values.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "google_apis/drive/drive_api_parser.h"
19 #include "google_apis/drive/gdata_wapi_parser.h"
20 #include "net/base/escape.h"
21 #include "third_party/re2/re2/re2.h"
28 std::string
GetMimeTypeFromEntryKind(google_apis::DriveEntryKind kind
) {
30 case google_apis::ENTRY_KIND_DOCUMENT
:
31 return kGoogleDocumentMimeType
;
32 case google_apis::ENTRY_KIND_SPREADSHEET
:
33 return kGoogleSpreadsheetMimeType
;
34 case google_apis::ENTRY_KIND_PRESENTATION
:
35 return kGooglePresentationMimeType
;
36 case google_apis::ENTRY_KIND_DRAWING
:
37 return kGoogleDrawingMimeType
;
38 case google_apis::ENTRY_KIND_TABLE
:
39 return kGoogleTableMimeType
;
40 case google_apis::ENTRY_KIND_FORM
:
41 return kGoogleFormMimeType
;
47 // Returns the argument string.
48 std::string
Identity(const std::string
& resource_id
) { return resource_id
; }
53 std::string
EscapeQueryStringValue(const std::string
& str
) {
55 result
.reserve(str
.size());
56 for (size_t i
= 0; i
< str
.size(); ++i
) {
57 if (str
[i
] == '\\' || str
[i
] == '\'') {
58 result
.push_back('\\');
60 result
.push_back(str
[i
]);
65 std::string
TranslateQuery(const std::string
& original_query
) {
66 // In order to handle non-ascii white spaces correctly, convert to UTF16.
67 base::string16 query
= base::UTF8ToUTF16(original_query
);
68 const base::string16
kDelimiter(
69 base::kWhitespaceUTF16
+
70 base::string16(1, static_cast<base::char16
>('"')));
73 for (size_t index
= query
.find_first_not_of(base::kWhitespaceUTF16
);
74 index
!= base::string16::npos
;
75 index
= query
.find_first_not_of(base::kWhitespaceUTF16
, index
)) {
76 bool is_exclusion
= (query
[index
] == '-');
79 if (index
== query
.length()) {
80 // Here, the token is '-' and it should be ignored.
84 size_t begin_token
= index
;
86 if (query
[begin_token
] == '"') {
89 size_t end_token
= query
.find('"', begin_token
);
90 if (end_token
== base::string16::npos
) {
91 // This is kind of syntax error, since quoted string isn't finished.
92 // However, the query is built by user manually, so here we treat
93 // whole remaining string as a token as a fallback, by appending
94 // a missing double-quote character.
95 end_token
= query
.length();
99 token
= query
.substr(begin_token
, end_token
- begin_token
);
100 index
= end_token
+ 1; // Consume last '"', too.
102 size_t end_token
= query
.find_first_of(kDelimiter
, begin_token
);
103 if (end_token
== base::string16::npos
) {
104 end_token
= query
.length();
107 token
= query
.substr(begin_token
, end_token
- begin_token
);
112 // Just ignore an empty token.
116 if (!result
.empty()) {
117 // If there are two or more tokens, need to connect with "and".
118 result
.append(" and ");
121 // The meaning of "fullText" should include title, description and content.
124 "%sfullText contains \'%s\'",
125 is_exclusion
? "not " : "",
126 EscapeQueryStringValue(base::UTF16ToUTF8(token
)).c_str());
132 std::string
ExtractResourceIdFromUrl(const GURL
& url
) {
133 return net::UnescapeURLComponent(url
.ExtractFileName(),
134 net::UnescapeRule::URL_SPECIAL_CHARS
);
137 std::string
CanonicalizeResourceId(const std::string
& resource_id
) {
138 // If resource ID is in the old WAPI format starting with a prefix like
139 // "document:", strip it and return the remaining part.
140 std::string stripped_resource_id
;
141 if (RE2::FullMatch(resource_id
, "^[a-z-]+(?::|%3A)([\\w-]+)$",
142 &stripped_resource_id
))
143 return stripped_resource_id
;
147 ResourceIdCanonicalizer
GetIdentityResourceIdCanonicalizer() {
148 return base::Bind(&Identity
);
151 const char kDocsListScope
[] = "https://docs.google.com/feeds/";
152 const char kDriveAppsScope
[] = "https://www.googleapis.com/auth/drive.apps";
154 void ParseShareUrlAndRun(const google_apis::GetShareUrlCallback
& callback
,
155 google_apis::GDataErrorCode error
,
156 scoped_ptr
<base::Value
> value
) {
157 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
160 callback
.Run(error
, GURL());
164 // Parsing ResourceEntry is cheap enough to do on UI thread.
165 scoped_ptr
<google_apis::ResourceEntry
> entry
=
166 google_apis::ResourceEntry::ExtractAndParse(*value
);
168 callback
.Run(google_apis::GDATA_PARSE_ERROR
, GURL());
172 const google_apis::Link
* share_link
=
173 entry
->GetLinkByType(google_apis::Link::LINK_SHARE
);
174 callback
.Run(error
, share_link
? share_link
->href() : GURL());
177 scoped_ptr
<google_apis::FileResource
> ConvertResourceEntryToFileResource(
178 const google_apis::ResourceEntry
& entry
) {
179 scoped_ptr
<google_apis::FileResource
> file(new google_apis::FileResource
);
181 file
->set_file_id(entry
.resource_id());
182 file
->set_title(entry
.title());
183 file
->set_created_date(entry
.published_time());
185 if (std::find(entry
.labels().begin(), entry
.labels().end(),
186 "shared-with-me") != entry
.labels().end()) {
187 // Set current time to mark the file is shared_with_me, since ResourceEntry
188 // doesn't have |shared_with_me_date| equivalent.
189 file
->set_shared_with_me_date(base::Time::Now());
192 file
->set_shared(std::find(entry
.labels().begin(), entry
.labels().end(),
193 "shared") != entry
.labels().end());
195 if (entry
.is_folder()) {
196 file
->set_mime_type(kDriveFolderMimeType
);
198 std::string mime_type
= GetMimeTypeFromEntryKind(entry
.kind());
199 if (mime_type
.empty())
200 mime_type
= entry
.content_mime_type();
201 file
->set_mime_type(mime_type
);
204 file
->set_md5_checksum(entry
.file_md5());
205 file
->set_file_size(entry
.file_size());
207 file
->mutable_labels()->set_trashed(entry
.deleted());
208 file
->set_etag(entry
.etag());
210 google_apis::ImageMediaMetadata
* image_media_metadata
=
211 file
->mutable_image_media_metadata();
212 image_media_metadata
->set_width(entry
.image_width());
213 image_media_metadata
->set_height(entry
.image_height());
214 image_media_metadata
->set_rotation(entry
.image_rotation());
216 std::vector
<google_apis::ParentReference
>* parents
= file
->mutable_parents();
217 for (size_t i
= 0; i
< entry
.links().size(); ++i
) {
218 using google_apis::Link
;
219 const Link
& link
= *entry
.links()[i
];
220 switch (link
.type()) {
221 case Link::LINK_PARENT
: {
222 google_apis::ParentReference parent
;
223 parent
.set_parent_link(link
.href());
225 std::string file_id
=
226 drive::util::ExtractResourceIdFromUrl(link
.href());
227 parent
.set_file_id(file_id
);
228 parents
->push_back(parent
);
231 case Link::LINK_ALTERNATE
:
232 file
->set_alternate_link(link
.href());
239 file
->set_modified_date(entry
.updated_time());
240 file
->set_last_viewed_by_me_date(entry
.last_viewed_time());
245 google_apis::DriveEntryKind
GetKind(
246 const google_apis::FileResource
& file_resource
) {
247 if (file_resource
.IsDirectory())
248 return google_apis::ENTRY_KIND_FOLDER
;
250 const std::string
& mime_type
= file_resource
.mime_type();
251 if (mime_type
== kGoogleDocumentMimeType
)
252 return google_apis::ENTRY_KIND_DOCUMENT
;
253 if (mime_type
== kGoogleSpreadsheetMimeType
)
254 return google_apis::ENTRY_KIND_SPREADSHEET
;
255 if (mime_type
== kGooglePresentationMimeType
)
256 return google_apis::ENTRY_KIND_PRESENTATION
;
257 if (mime_type
== kGoogleDrawingMimeType
)
258 return google_apis::ENTRY_KIND_DRAWING
;
259 if (mime_type
== kGoogleTableMimeType
)
260 return google_apis::ENTRY_KIND_TABLE
;
261 if (mime_type
== kGoogleFormMimeType
)
262 return google_apis::ENTRY_KIND_FORM
;
263 if (mime_type
== "application/pdf")
264 return google_apis::ENTRY_KIND_PDF
;
265 return google_apis::ENTRY_KIND_FILE
;
268 scoped_ptr
<google_apis::ResourceEntry
>
269 ConvertFileResourceToResourceEntry(
270 const google_apis::FileResource
& file_resource
) {
271 scoped_ptr
<google_apis::ResourceEntry
> entry(new google_apis::ResourceEntry
);
274 entry
->set_resource_id(file_resource
.file_id());
275 entry
->set_id(file_resource
.file_id());
276 entry
->set_kind(GetKind(file_resource
));
277 entry
->set_title(file_resource
.title());
278 entry
->set_published_time(file_resource
.created_date());
280 std::vector
<std::string
> labels
;
281 if (!file_resource
.shared_with_me_date().is_null())
282 labels
.push_back("shared-with-me");
283 if (file_resource
.shared())
284 labels
.push_back("shared");
285 entry
->set_labels(labels
);
287 // This should be the url to download the file_resource.
289 google_apis::Content content
;
290 content
.set_mime_type(file_resource
.mime_type());
291 entry
->set_content(content
);
293 // TODO(kochi): entry->resource_links_
296 entry
->set_filename(file_resource
.title());
297 entry
->set_suggested_filename(file_resource
.title());
298 entry
->set_file_md5(file_resource
.md5_checksum());
299 entry
->set_file_size(file_resource
.file_size());
301 // If file is removed completely, that information is only available in
302 // ChangeResource, and is reflected in |removed_|. If file is trashed, the
303 // file entry still exists but with its "trashed" label true.
304 entry
->set_deleted(file_resource
.labels().is_trashed());
306 // ImageMediaMetadata
307 entry
->set_image_width(file_resource
.image_media_metadata().width());
308 entry
->set_image_height(file_resource
.image_media_metadata().height());
309 entry
->set_image_rotation(file_resource
.image_media_metadata().rotation());
312 entry
->set_etag(file_resource
.etag());
315 ScopedVector
<google_apis::Link
> links
;
316 for (size_t i
= 0; i
< file_resource
.parents().size(); ++i
) {
317 google_apis::Link
* link
= new google_apis::Link
;
318 link
->set_type(google_apis::Link::LINK_PARENT
);
319 link
->set_href(file_resource
.parents()[i
].parent_link());
320 links
.push_back(link
);
322 if (!file_resource
.alternate_link().is_empty()) {
323 google_apis::Link
* link
= new google_apis::Link
;
324 link
->set_type(google_apis::Link::LINK_ALTERNATE
);
325 link
->set_href(file_resource
.alternate_link());
326 links
.push_back(link
);
328 entry
->set_links(links
.Pass());
330 // entry->categories_
331 entry
->set_updated_time(file_resource
.modified_date());
332 entry
->set_last_viewed_time(file_resource
.last_viewed_by_me_date());
334 entry
->FillRemainingFields();
338 scoped_ptr
<google_apis::ResourceEntry
>
339 ConvertChangeResourceToResourceEntry(
340 const google_apis::ChangeResource
& change_resource
) {
341 scoped_ptr
<google_apis::ResourceEntry
> entry
;
342 if (change_resource
.file())
343 entry
= ConvertFileResourceToResourceEntry(*change_resource
.file()).Pass();
345 entry
.reset(new google_apis::ResourceEntry
);
347 entry
->set_resource_id(change_resource
.file_id());
348 // If |is_deleted()| returns true, the file is removed from Drive.
349 entry
->set_removed(change_resource
.is_deleted());
350 entry
->set_changestamp(change_resource
.change_id());
351 entry
->set_modification_date(change_resource
.modification_date());
356 scoped_ptr
<google_apis::ResourceList
>
357 ConvertFileListToResourceList(const google_apis::FileList
& file_list
) {
358 scoped_ptr
<google_apis::ResourceList
> feed(new google_apis::ResourceList
);
360 const ScopedVector
<google_apis::FileResource
>& items
= file_list
.items();
361 ScopedVector
<google_apis::ResourceEntry
> entries
;
362 for (size_t i
= 0; i
< items
.size(); ++i
)
363 entries
.push_back(ConvertFileResourceToResourceEntry(*items
[i
]).release());
364 feed
->set_entries(entries
.Pass());
366 ScopedVector
<google_apis::Link
> links
;
367 if (!file_list
.next_link().is_empty()) {
368 google_apis::Link
* link
= new google_apis::Link
;
369 link
->set_type(google_apis::Link::LINK_NEXT
);
370 link
->set_href(file_list
.next_link());
371 links
.push_back(link
);
373 feed
->set_links(links
.Pass());
378 scoped_ptr
<google_apis::ResourceList
>
379 ConvertChangeListToResourceList(const google_apis::ChangeList
& change_list
) {
380 scoped_ptr
<google_apis::ResourceList
> feed(new google_apis::ResourceList
);
382 const ScopedVector
<google_apis::ChangeResource
>& items
= change_list
.items();
383 ScopedVector
<google_apis::ResourceEntry
> entries
;
384 for (size_t i
= 0; i
< items
.size(); ++i
) {
386 ConvertChangeResourceToResourceEntry(*items
[i
]).release());
388 feed
->set_entries(entries
.Pass());
390 feed
->set_largest_changestamp(change_list
.largest_change_id());
392 ScopedVector
<google_apis::Link
> links
;
393 if (!change_list
.next_link().is_empty()) {
394 google_apis::Link
* link
= new google_apis::Link
;
395 link
->set_type(google_apis::Link::LINK_NEXT
);
396 link
->set_href(change_list
.next_link());
397 links
.push_back(link
);
399 feed
->set_links(links
.Pass());
404 std::string
GetMd5Digest(const base::FilePath
& file_path
) {
405 const int kBufferSize
= 512 * 1024; // 512kB.
407 base::File
file(file_path
, base::File::FLAG_OPEN
| base::File::FLAG_READ
);
409 return std::string();
411 base::MD5Context context
;
412 base::MD5Init(&context
);
415 scoped_ptr
<char[]> buffer(new char[kBufferSize
]);
417 int result
= file
.Read(offset
, buffer
.get(), kBufferSize
);
420 return std::string();
429 base::MD5Update(&context
, base::StringPiece(buffer
.get(), result
));
432 base::MD5Digest digest
;
433 base::MD5Final(&digest
, &context
);
434 return MD5DigestToBase16(digest
);
437 const char kWapiRootDirectoryResourceId
[] = "folder:root";