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"},
40 {kGoogleMapMimeType
, ".gmaps"},
43 const char kUnknownHostedDocumentExtension
[] = ".glink";
47 std::string
EscapeQueryStringValue(const std::string
& str
) {
49 result
.reserve(str
.size());
50 for (size_t i
= 0; i
< str
.size(); ++i
) {
51 if (str
[i
] == '\\' || str
[i
] == '\'') {
52 result
.push_back('\\');
54 result
.push_back(str
[i
]);
59 std::string
TranslateQuery(const std::string
& original_query
) {
60 // In order to handle non-ascii white spaces correctly, convert to UTF16.
61 base::string16 query
= base::UTF8ToUTF16(original_query
);
62 const base::string16
kDelimiter(
63 base::kWhitespaceUTF16
+ base::ASCIIToUTF16("\""));
66 for (size_t index
= query
.find_first_not_of(base::kWhitespaceUTF16
);
67 index
!= base::string16::npos
;
68 index
= query
.find_first_not_of(base::kWhitespaceUTF16
, index
)) {
69 bool is_exclusion
= (query
[index
] == '-');
72 if (index
== query
.length()) {
73 // Here, the token is '-' and it should be ignored.
77 size_t begin_token
= index
;
79 if (query
[begin_token
] == '"') {
82 size_t end_token
= query
.find('"', begin_token
);
83 if (end_token
== base::string16::npos
) {
84 // This is kind of syntax error, since quoted string isn't finished.
85 // However, the query is built by user manually, so here we treat
86 // whole remaining string as a token as a fallback, by appending
87 // a missing double-quote character.
88 end_token
= query
.length();
92 token
= query
.substr(begin_token
, end_token
- begin_token
);
93 index
= end_token
+ 1; // Consume last '"', too.
95 size_t end_token
= query
.find_first_of(kDelimiter
, begin_token
);
96 if (end_token
== base::string16::npos
) {
97 end_token
= query
.length();
100 token
= query
.substr(begin_token
, end_token
- begin_token
);
105 // Just ignore an empty token.
109 if (!result
.empty()) {
110 // If there are two or more tokens, need to connect with "and".
111 result
.append(" and ");
114 // The meaning of "fullText" should include title, description and content.
117 "%sfullText contains \'%s\'",
118 is_exclusion
? "not " : "",
119 EscapeQueryStringValue(base::UTF16ToUTF8(token
)).c_str());
125 std::string
ExtractResourceIdFromUrl(const GURL
& url
) {
126 return net::UnescapeURLComponent(url
.ExtractFileName(),
127 net::UnescapeRule::URL_SPECIAL_CHARS
);
130 std::string
CanonicalizeResourceId(const std::string
& resource_id
) {
131 // If resource ID is in the old WAPI format starting with a prefix like
132 // "document:", strip it and return the remaining part.
133 std::string stripped_resource_id
;
134 if (RE2::FullMatch(resource_id
, "^[a-z-]+(?::|%3A)([\\w-]+)$",
135 &stripped_resource_id
))
136 return stripped_resource_id
;
140 scoped_ptr
<google_apis::ResourceEntry
>
141 ConvertFileResourceToResourceEntry(
142 const google_apis::FileResource
& file_resource
) {
143 scoped_ptr
<google_apis::ResourceEntry
> entry(new google_apis::ResourceEntry
);
146 entry
->set_resource_id(file_resource
.file_id());
147 entry
->set_id(file_resource
.file_id());
148 if (file_resource
.IsDirectory())
149 entry
->set_kind(google_apis::ResourceEntry::ENTRY_KIND_FOLDER
);
150 else if (file_resource
.IsHostedDocument())
151 entry
->set_kind(google_apis::ResourceEntry::ENTRY_KIND_UNKNOWN
);
153 entry
->set_kind(google_apis::ResourceEntry::ENTRY_KIND_FILE
);
154 entry
->set_title(file_resource
.title());
155 entry
->set_published_time(file_resource
.created_date());
157 std::vector
<std::string
> labels
;
158 if (!file_resource
.shared_with_me_date().is_null())
159 labels
.push_back("shared-with-me");
160 if (file_resource
.shared())
161 labels
.push_back("shared");
162 entry
->set_labels(labels
);
164 // This should be the url to download the file_resource.
166 google_apis::Content content
;
167 content
.set_mime_type(file_resource
.mime_type());
168 entry
->set_content(content
);
170 // TODO(kochi): entry->resource_links_
173 entry
->set_filename(file_resource
.title());
174 entry
->set_suggested_filename(file_resource
.title());
175 entry
->set_file_md5(file_resource
.md5_checksum());
176 entry
->set_file_size(file_resource
.file_size());
178 // If file is removed completely, that information is only available in
179 // ChangeResource, and is reflected in |removed_|. If file is trashed, the
180 // file entry still exists but with its "trashed" label true.
181 entry
->set_deleted(file_resource
.labels().is_trashed());
183 // ImageMediaMetadata
184 entry
->set_image_width(file_resource
.image_media_metadata().width());
185 entry
->set_image_height(file_resource
.image_media_metadata().height());
186 entry
->set_image_rotation(file_resource
.image_media_metadata().rotation());
189 entry
->set_etag(file_resource
.etag());
192 ScopedVector
<google_apis::Link
> links
;
193 for (size_t i
= 0; i
< file_resource
.parents().size(); ++i
) {
194 google_apis::Link
* link
= new google_apis::Link
;
195 link
->set_type(google_apis::Link::LINK_PARENT
);
196 link
->set_href(file_resource
.parents()[i
].parent_link());
197 links
.push_back(link
);
199 if (!file_resource
.alternate_link().is_empty()) {
200 google_apis::Link
* link
= new google_apis::Link
;
201 link
->set_type(google_apis::Link::LINK_ALTERNATE
);
202 link
->set_href(file_resource
.alternate_link());
203 links
.push_back(link
);
205 entry
->set_links(links
.Pass());
207 // entry->categories_
208 entry
->set_updated_time(file_resource
.modified_date());
209 entry
->set_last_viewed_time(file_resource
.last_viewed_by_me_date());
211 entry
->FillRemainingFields();
215 scoped_ptr
<google_apis::ResourceEntry
>
216 ConvertChangeResourceToResourceEntry(
217 const google_apis::ChangeResource
& change_resource
) {
218 scoped_ptr
<google_apis::ResourceEntry
> entry
;
219 if (change_resource
.file())
220 entry
= ConvertFileResourceToResourceEntry(*change_resource
.file()).Pass();
222 entry
.reset(new google_apis::ResourceEntry
);
224 entry
->set_resource_id(change_resource
.file_id());
225 // If |is_deleted()| returns true, the file is removed from Drive.
226 entry
->set_removed(change_resource
.is_deleted());
227 entry
->set_changestamp(change_resource
.change_id());
228 entry
->set_modification_date(change_resource
.modification_date());
233 scoped_ptr
<google_apis::ResourceList
>
234 ConvertFileListToResourceList(const google_apis::FileList
& file_list
) {
235 scoped_ptr
<google_apis::ResourceList
> feed(new google_apis::ResourceList
);
237 const ScopedVector
<google_apis::FileResource
>& items
= file_list
.items();
238 ScopedVector
<google_apis::ResourceEntry
> entries
;
239 for (size_t i
= 0; i
< items
.size(); ++i
)
240 entries
.push_back(ConvertFileResourceToResourceEntry(*items
[i
]).release());
241 feed
->set_entries(entries
.Pass());
243 ScopedVector
<google_apis::Link
> links
;
244 if (!file_list
.next_link().is_empty()) {
245 google_apis::Link
* link
= new google_apis::Link
;
246 link
->set_type(google_apis::Link::LINK_NEXT
);
247 link
->set_href(file_list
.next_link());
248 links
.push_back(link
);
250 feed
->set_links(links
.Pass());
255 scoped_ptr
<google_apis::ResourceList
>
256 ConvertChangeListToResourceList(const google_apis::ChangeList
& change_list
) {
257 scoped_ptr
<google_apis::ResourceList
> feed(new google_apis::ResourceList
);
259 const ScopedVector
<google_apis::ChangeResource
>& items
= change_list
.items();
260 ScopedVector
<google_apis::ResourceEntry
> entries
;
261 for (size_t i
= 0; i
< items
.size(); ++i
) {
263 ConvertChangeResourceToResourceEntry(*items
[i
]).release());
265 feed
->set_entries(entries
.Pass());
267 feed
->set_largest_changestamp(change_list
.largest_change_id());
269 ScopedVector
<google_apis::Link
> links
;
270 if (!change_list
.next_link().is_empty()) {
271 google_apis::Link
* link
= new google_apis::Link
;
272 link
->set_type(google_apis::Link::LINK_NEXT
);
273 link
->set_href(change_list
.next_link());
274 links
.push_back(link
);
276 feed
->set_links(links
.Pass());
281 std::string
GetMd5Digest(const base::FilePath
& file_path
) {
282 const int kBufferSize
= 512 * 1024; // 512kB.
284 base::File
file(file_path
, base::File::FLAG_OPEN
| base::File::FLAG_READ
);
286 return std::string();
288 base::MD5Context context
;
289 base::MD5Init(&context
);
292 scoped_ptr
<char[]> buffer(new char[kBufferSize
]);
294 int result
= file
.Read(offset
, buffer
.get(), kBufferSize
);
297 return std::string();
306 base::MD5Update(&context
, base::StringPiece(buffer
.get(), result
));
309 base::MD5Digest digest
;
310 base::MD5Final(&digest
, &context
);
311 return MD5DigestToBase16(digest
);
314 std::string
GetHostedDocumentExtension(const std::string
& mime_type
) {
315 for (size_t i
= 0; i
< arraysize(kHostedDocumentKinds
); ++i
) {
316 if (mime_type
== kHostedDocumentKinds
[i
].mime_type
)
317 return kHostedDocumentKinds
[i
].extension
;
319 return kUnknownHostedDocumentExtension
;
322 bool IsKnownHostedDocumentMimeType(const std::string
& mime_type
) {
323 for (size_t i
= 0; i
< arraysize(kHostedDocumentKinds
); ++i
) {
324 if (mime_type
== kHostedDocumentKinds
[i
].mime_type
)
330 bool HasHostedDocumentExtension(const base::FilePath
& path
) {
331 const std::string extension
= base::FilePath(path
.Extension()).AsUTF8Unsafe();
332 for (size_t i
= 0; i
< arraysize(kHostedDocumentKinds
); ++i
) {
333 if (extension
== kHostedDocumentKinds
[i
].extension
)
336 return extension
== kUnknownHostedDocumentExtension
;