Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / drive / drive_api_util.cc
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.
4
5 #include "chrome/browser/drive/drive_api_util.h"
6
7 #include <string>
8
9 #include "base/files/file.h"
10 #include "base/logging.h"
11 #include "base/md5.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"
22 #include "url/gurl.h"
23
24 namespace drive {
25 namespace util {
26 namespace {
27
28 std::string GetMimeTypeFromEntryKind(google_apis::DriveEntryKind kind) {
29   switch (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;
42     default:
43       return std::string();
44   }
45 }
46
47 // Returns the argument string.
48 std::string Identity(const std::string& resource_id) { return resource_id; }
49
50 }  // namespace
51
52
53 std::string EscapeQueryStringValue(const std::string& str) {
54   std::string result;
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('\\');
59     }
60     result.push_back(str[i]);
61   }
62   return result;
63 }
64
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>('"')));
71
72   std::string result;
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] == '-');
77     if (is_exclusion)
78       ++index;
79     if (index == query.length()) {
80       // Here, the token is '-' and it should be ignored.
81       continue;
82     }
83
84     size_t begin_token = index;
85     base::string16 token;
86     if (query[begin_token] == '"') {
87       // Quoted query.
88       ++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();
96         query.push_back('"');
97       }
98
99       token = query.substr(begin_token, end_token - begin_token);
100       index = end_token + 1;  // Consume last '"', too.
101     } else {
102       size_t end_token = query.find_first_of(kDelimiter, begin_token);
103       if (end_token == base::string16::npos) {
104         end_token = query.length();
105       }
106
107       token = query.substr(begin_token, end_token - begin_token);
108       index = end_token;
109     }
110
111     if (token.empty()) {
112       // Just ignore an empty token.
113       continue;
114     }
115
116     if (!result.empty()) {
117       // If there are two or more tokens, need to connect with "and".
118       result.append(" and ");
119     }
120
121     // The meaning of "fullText" should include title, description and content.
122     base::StringAppendF(
123         &result,
124         "%sfullText contains \'%s\'",
125         is_exclusion ? "not " : "",
126         EscapeQueryStringValue(base::UTF16ToUTF8(token)).c_str());
127   }
128
129   return result;
130 }
131
132 std::string ExtractResourceIdFromUrl(const GURL& url) {
133   return net::UnescapeURLComponent(url.ExtractFileName(),
134                                    net::UnescapeRule::URL_SPECIAL_CHARS);
135 }
136
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;
144   return resource_id;
145 }
146
147 ResourceIdCanonicalizer GetIdentityResourceIdCanonicalizer() {
148   return base::Bind(&Identity);
149 }
150
151 const char kDocsListScope[] = "https://docs.google.com/feeds/";
152 const char kDriveAppsScope[] = "https://www.googleapis.com/auth/drive.apps";
153
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));
158
159   if (!value) {
160     callback.Run(error, GURL());
161     return;
162   }
163
164   // Parsing ResourceEntry is cheap enough to do on UI thread.
165   scoped_ptr<google_apis::ResourceEntry> entry =
166       google_apis::ResourceEntry::ExtractAndParse(*value);
167   if (!entry) {
168     callback.Run(google_apis::GDATA_PARSE_ERROR, GURL());
169     return;
170   }
171
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());
175 }
176
177 scoped_ptr<google_apis::FileResource> ConvertResourceEntryToFileResource(
178     const google_apis::ResourceEntry& entry) {
179   scoped_ptr<google_apis::FileResource> file(new google_apis::FileResource);
180
181   file->set_file_id(entry.resource_id());
182   file->set_title(entry.title());
183   file->set_created_date(entry.published_time());
184
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());
190   }
191
192   file->set_shared(std::find(entry.labels().begin(), entry.labels().end(),
193                              "shared") != entry.labels().end());
194
195   if (entry.is_folder()) {
196     file->set_mime_type(kDriveFolderMimeType);
197   } else {
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);
202   }
203
204   file->set_md5_checksum(entry.file_md5());
205   file->set_file_size(entry.file_size());
206
207   file->mutable_labels()->set_trashed(entry.deleted());
208   file->set_etag(entry.etag());
209
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());
215
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());
224
225         std::string file_id =
226             drive::util::ExtractResourceIdFromUrl(link.href());
227         parent.set_file_id(file_id);
228         parents->push_back(parent);
229         break;
230       }
231       case Link::LINK_ALTERNATE:
232         file->set_alternate_link(link.href());
233         break;
234       default:
235         break;
236     }
237   }
238
239   file->set_modified_date(entry.updated_time());
240   file->set_last_viewed_by_me_date(entry.last_viewed_time());
241
242   return file.Pass();
243 }
244
245 google_apis::DriveEntryKind GetKind(
246     const google_apis::FileResource& file_resource) {
247   if (file_resource.IsDirectory())
248     return google_apis::ENTRY_KIND_FOLDER;
249
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;
266 }
267
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);
272
273   // 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());
279
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);
286
287   // This should be the url to download the file_resource.
288   {
289     google_apis::Content content;
290     content.set_mime_type(file_resource.mime_type());
291     entry->set_content(content);
292   }
293   // TODO(kochi): entry->resource_links_
294
295   // For file entries
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());
300
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());
305
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());
310
311   // CommonMetadata
312   entry->set_etag(file_resource.etag());
313   // entry->authors_
314   // entry->links_.
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);
321   }
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);
327   }
328   entry->set_links(links.Pass());
329
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());
333
334   entry->FillRemainingFields();
335   return entry.Pass();
336 }
337
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();
344   else
345     entry.reset(new google_apis::ResourceEntry);
346
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());
352
353   return entry.Pass();
354 }
355
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);
359
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());
365
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);
372   }
373   feed->set_links(links.Pass());
374
375   return feed.Pass();
376 }
377
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);
381
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) {
385     entries.push_back(
386         ConvertChangeResourceToResourceEntry(*items[i]).release());
387   }
388   feed->set_entries(entries.Pass());
389
390   feed->set_largest_changestamp(change_list.largest_change_id());
391
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);
398   }
399   feed->set_links(links.Pass());
400
401   return feed.Pass();
402 }
403
404 std::string GetMd5Digest(const base::FilePath& file_path) {
405   const int kBufferSize = 512 * 1024;  // 512kB.
406
407   base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
408   if (!file.IsValid())
409     return std::string();
410
411   base::MD5Context context;
412   base::MD5Init(&context);
413
414   int64 offset = 0;
415   scoped_ptr<char[]> buffer(new char[kBufferSize]);
416   while (true) {
417     int result = file.Read(offset, buffer.get(), kBufferSize);
418     if (result < 0) {
419       // Found an error.
420       return std::string();
421     }
422
423     if (result == 0) {
424       // End of file.
425       break;
426     }
427
428     offset += result;
429     base::MD5Update(&context, base::StringPiece(buffer.get(), result));
430   }
431
432   base::MD5Digest digest;
433   base::MD5Final(&digest, &context);
434   return MD5DigestToBase16(digest);
435 }
436
437 const char kWapiRootDirectoryResourceId[] = "folder:root";
438
439 }  // namespace util
440 }  // namespace drive