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 struct HostedDocumentKind
{
29 const char* mime_type
;
30 const char* extension
;
33 const HostedDocumentKind kHostedDocumentKinds
[] = {
34 {kGoogleDocumentMimeType
, ".gdoc"},
35 {kGoogleSpreadsheetMimeType
, ".gsheet"},
36 {kGooglePresentationMimeType
, ".gslides"},
37 {kGoogleDrawingMimeType
, ".gdraw"},
38 {kGoogleTableMimeType
, ".gtable"},
39 {kGoogleFormMimeType
, ".gform"}
42 const char kUnknownHostedDocumentExtension
[] = ".glink";
46 std::string
EscapeQueryStringValue(const std::string
& str
) {
48 result
.reserve(str
.size());
49 for (size_t i
= 0; i
< str
.size(); ++i
) {
50 if (str
[i
] == '\\' || str
[i
] == '\'') {
51 result
.push_back('\\');
53 result
.push_back(str
[i
]);
58 std::string
TranslateQuery(const std::string
& original_query
) {
59 // In order to handle non-ascii white spaces correctly, convert to UTF16.
60 base::string16 query
= base::UTF8ToUTF16(original_query
);
61 const base::string16
kDelimiter(
62 base::kWhitespaceUTF16
+ base::ASCIIToUTF16("\""));
65 for (size_t index
= query
.find_first_not_of(base::kWhitespaceUTF16
);
66 index
!= base::string16::npos
;
67 index
= query
.find_first_not_of(base::kWhitespaceUTF16
, index
)) {
68 bool is_exclusion
= (query
[index
] == '-');
71 if (index
== query
.length()) {
72 // Here, the token is '-' and it should be ignored.
76 size_t begin_token
= index
;
78 if (query
[begin_token
] == '"') {
81 size_t end_token
= query
.find('"', begin_token
);
82 if (end_token
== base::string16::npos
) {
83 // This is kind of syntax error, since quoted string isn't finished.
84 // However, the query is built by user manually, so here we treat
85 // whole remaining string as a token as a fallback, by appending
86 // a missing double-quote character.
87 end_token
= query
.length();
91 token
= query
.substr(begin_token
, end_token
- begin_token
);
92 index
= end_token
+ 1; // Consume last '"', too.
94 size_t end_token
= query
.find_first_of(kDelimiter
, begin_token
);
95 if (end_token
== base::string16::npos
) {
96 end_token
= query
.length();
99 token
= query
.substr(begin_token
, end_token
- begin_token
);
104 // Just ignore an empty token.
108 if (!result
.empty()) {
109 // If there are two or more tokens, need to connect with "and".
110 result
.append(" and ");
113 // The meaning of "fullText" should include title, description and content.
116 "%sfullText contains \'%s\'",
117 is_exclusion
? "not " : "",
118 EscapeQueryStringValue(base::UTF16ToUTF8(token
)).c_str());
124 std::string
ExtractResourceIdFromUrl(const GURL
& url
) {
125 return net::UnescapeURLComponent(url
.ExtractFileName(),
126 net::UnescapeRule::URL_SPECIAL_CHARS
);
129 std::string
CanonicalizeResourceId(const std::string
& resource_id
) {
130 // If resource ID is in the old WAPI format starting with a prefix like
131 // "document:", strip it and return the remaining part.
132 std::string stripped_resource_id
;
133 if (RE2::FullMatch(resource_id
, "^[a-z-]+(?::|%3A)([\\w-]+)$",
134 &stripped_resource_id
))
135 return stripped_resource_id
;
139 scoped_ptr
<google_apis::ResourceEntry
>
140 ConvertFileResourceToResourceEntry(
141 const google_apis::FileResource
& file_resource
) {
142 scoped_ptr
<google_apis::ResourceEntry
> entry(new google_apis::ResourceEntry
);
145 entry
->set_resource_id(file_resource
.file_id());
146 entry
->set_id(file_resource
.file_id());
147 if (file_resource
.IsDirectory())
148 entry
->set_kind(google_apis::ResourceEntry::ENTRY_KIND_FOLDER
);
149 else if (file_resource
.IsHostedDocument())
150 entry
->set_kind(google_apis::ResourceEntry::ENTRY_KIND_UNKNOWN
);
152 entry
->set_kind(google_apis::ResourceEntry::ENTRY_KIND_FILE
);
153 entry
->set_title(file_resource
.title());
154 entry
->set_published_time(file_resource
.created_date());
156 std::vector
<std::string
> labels
;
157 if (!file_resource
.shared_with_me_date().is_null())
158 labels
.push_back("shared-with-me");
159 if (file_resource
.shared())
160 labels
.push_back("shared");
161 entry
->set_labels(labels
);
163 // This should be the url to download the file_resource.
165 google_apis::Content content
;
166 content
.set_mime_type(file_resource
.mime_type());
167 entry
->set_content(content
);
169 // TODO(kochi): entry->resource_links_
172 entry
->set_filename(file_resource
.title());
173 entry
->set_suggested_filename(file_resource
.title());
174 entry
->set_file_md5(file_resource
.md5_checksum());
175 entry
->set_file_size(file_resource
.file_size());
177 // If file is removed completely, that information is only available in
178 // ChangeResource, and is reflected in |removed_|. If file is trashed, the
179 // file entry still exists but with its "trashed" label true.
180 entry
->set_deleted(file_resource
.labels().is_trashed());
182 // ImageMediaMetadata
183 entry
->set_image_width(file_resource
.image_media_metadata().width());
184 entry
->set_image_height(file_resource
.image_media_metadata().height());
185 entry
->set_image_rotation(file_resource
.image_media_metadata().rotation());
188 entry
->set_etag(file_resource
.etag());
191 ScopedVector
<google_apis::Link
> links
;
192 for (size_t i
= 0; i
< file_resource
.parents().size(); ++i
) {
193 google_apis::Link
* link
= new google_apis::Link
;
194 link
->set_type(google_apis::Link::LINK_PARENT
);
195 link
->set_href(file_resource
.parents()[i
].parent_link());
196 links
.push_back(link
);
198 if (!file_resource
.alternate_link().is_empty()) {
199 google_apis::Link
* link
= new google_apis::Link
;
200 link
->set_type(google_apis::Link::LINK_ALTERNATE
);
201 link
->set_href(file_resource
.alternate_link());
202 links
.push_back(link
);
204 entry
->set_links(links
.Pass());
206 // entry->categories_
207 entry
->set_updated_time(file_resource
.modified_date());
208 entry
->set_last_viewed_time(file_resource
.last_viewed_by_me_date());
210 entry
->FillRemainingFields();
214 scoped_ptr
<google_apis::ResourceEntry
>
215 ConvertChangeResourceToResourceEntry(
216 const google_apis::ChangeResource
& change_resource
) {
217 scoped_ptr
<google_apis::ResourceEntry
> entry
;
218 if (change_resource
.file())
219 entry
= ConvertFileResourceToResourceEntry(*change_resource
.file()).Pass();
221 entry
.reset(new google_apis::ResourceEntry
);
223 entry
->set_resource_id(change_resource
.file_id());
224 // If |is_deleted()| returns true, the file is removed from Drive.
225 entry
->set_removed(change_resource
.is_deleted());
226 entry
->set_changestamp(change_resource
.change_id());
227 entry
->set_modification_date(change_resource
.modification_date());
232 scoped_ptr
<google_apis::ResourceList
>
233 ConvertFileListToResourceList(const google_apis::FileList
& file_list
) {
234 scoped_ptr
<google_apis::ResourceList
> feed(new google_apis::ResourceList
);
236 const ScopedVector
<google_apis::FileResource
>& items
= file_list
.items();
237 ScopedVector
<google_apis::ResourceEntry
> entries
;
238 for (size_t i
= 0; i
< items
.size(); ++i
)
239 entries
.push_back(ConvertFileResourceToResourceEntry(*items
[i
]).release());
240 feed
->set_entries(entries
.Pass());
242 ScopedVector
<google_apis::Link
> links
;
243 if (!file_list
.next_link().is_empty()) {
244 google_apis::Link
* link
= new google_apis::Link
;
245 link
->set_type(google_apis::Link::LINK_NEXT
);
246 link
->set_href(file_list
.next_link());
247 links
.push_back(link
);
249 feed
->set_links(links
.Pass());
254 scoped_ptr
<google_apis::ResourceList
>
255 ConvertChangeListToResourceList(const google_apis::ChangeList
& change_list
) {
256 scoped_ptr
<google_apis::ResourceList
> feed(new google_apis::ResourceList
);
258 const ScopedVector
<google_apis::ChangeResource
>& items
= change_list
.items();
259 ScopedVector
<google_apis::ResourceEntry
> entries
;
260 for (size_t i
= 0; i
< items
.size(); ++i
) {
262 ConvertChangeResourceToResourceEntry(*items
[i
]).release());
264 feed
->set_entries(entries
.Pass());
266 feed
->set_largest_changestamp(change_list
.largest_change_id());
268 ScopedVector
<google_apis::Link
> links
;
269 if (!change_list
.next_link().is_empty()) {
270 google_apis::Link
* link
= new google_apis::Link
;
271 link
->set_type(google_apis::Link::LINK_NEXT
);
272 link
->set_href(change_list
.next_link());
273 links
.push_back(link
);
275 feed
->set_links(links
.Pass());
280 std::string
GetMd5Digest(const base::FilePath
& file_path
) {
281 const int kBufferSize
= 512 * 1024; // 512kB.
283 base::File
file(file_path
, base::File::FLAG_OPEN
| base::File::FLAG_READ
);
285 return std::string();
287 base::MD5Context context
;
288 base::MD5Init(&context
);
291 scoped_ptr
<char[]> buffer(new char[kBufferSize
]);
293 int result
= file
.Read(offset
, buffer
.get(), kBufferSize
);
296 return std::string();
305 base::MD5Update(&context
, base::StringPiece(buffer
.get(), result
));
308 base::MD5Digest digest
;
309 base::MD5Final(&digest
, &context
);
310 return MD5DigestToBase16(digest
);
313 std::string
GetHostedDocumentExtension(const std::string
& mime_type
) {
314 for (size_t i
= 0; i
< arraysize(kHostedDocumentKinds
); ++i
) {
315 if (mime_type
== kHostedDocumentKinds
[i
].mime_type
)
316 return kHostedDocumentKinds
[i
].extension
;
318 return kUnknownHostedDocumentExtension
;
321 bool IsKnownHostedDocumentMimeType(const std::string
& mime_type
) {
322 for (size_t i
= 0; i
< arraysize(kHostedDocumentKinds
); ++i
) {
323 if (mime_type
== kHostedDocumentKinds
[i
].mime_type
)
329 bool HasHostedDocumentExtension(const base::FilePath
& path
) {
330 const std::string extension
= base::FilePath(path
.Extension()).AsUTF8Unsafe();
331 for (size_t i
= 0; i
< arraysize(kHostedDocumentKinds
); ++i
) {
332 if (extension
== kHostedDocumentKinds
[i
].extension
)
335 return extension
== kUnknownHostedDocumentExtension
;