• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GetMimeTypeFromEntryKind(google_apis::DriveEntryKind kind)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.
Identity(const std::string & resource_id)48 std::string Identity(const std::string& resource_id) { return resource_id; }
49 
50 }  // namespace
51 
52 
EscapeQueryStringValue(const std::string & str)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 
TranslateQuery(const std::string & original_query)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 + base::ASCIIToUTF16("\""));
70 
71   std::string result;
72   for (size_t index = query.find_first_not_of(base::kWhitespaceUTF16);
73        index != base::string16::npos;
74        index = query.find_first_not_of(base::kWhitespaceUTF16, index)) {
75     bool is_exclusion = (query[index] == '-');
76     if (is_exclusion)
77       ++index;
78     if (index == query.length()) {
79       // Here, the token is '-' and it should be ignored.
80       continue;
81     }
82 
83     size_t begin_token = index;
84     base::string16 token;
85     if (query[begin_token] == '"') {
86       // Quoted query.
87       ++begin_token;
88       size_t end_token = query.find('"', begin_token);
89       if (end_token == base::string16::npos) {
90         // This is kind of syntax error, since quoted string isn't finished.
91         // However, the query is built by user manually, so here we treat
92         // whole remaining string as a token as a fallback, by appending
93         // a missing double-quote character.
94         end_token = query.length();
95         query.push_back('"');
96       }
97 
98       token = query.substr(begin_token, end_token - begin_token);
99       index = end_token + 1;  // Consume last '"', too.
100     } else {
101       size_t end_token = query.find_first_of(kDelimiter, begin_token);
102       if (end_token == base::string16::npos) {
103         end_token = query.length();
104       }
105 
106       token = query.substr(begin_token, end_token - begin_token);
107       index = end_token;
108     }
109 
110     if (token.empty()) {
111       // Just ignore an empty token.
112       continue;
113     }
114 
115     if (!result.empty()) {
116       // If there are two or more tokens, need to connect with "and".
117       result.append(" and ");
118     }
119 
120     // The meaning of "fullText" should include title, description and content.
121     base::StringAppendF(
122         &result,
123         "%sfullText contains \'%s\'",
124         is_exclusion ? "not " : "",
125         EscapeQueryStringValue(base::UTF16ToUTF8(token)).c_str());
126   }
127 
128   return result;
129 }
130 
ExtractResourceIdFromUrl(const GURL & url)131 std::string ExtractResourceIdFromUrl(const GURL& url) {
132   return net::UnescapeURLComponent(url.ExtractFileName(),
133                                    net::UnescapeRule::URL_SPECIAL_CHARS);
134 }
135 
CanonicalizeResourceId(const std::string & resource_id)136 std::string CanonicalizeResourceId(const std::string& resource_id) {
137   // If resource ID is in the old WAPI format starting with a prefix like
138   // "document:", strip it and return the remaining part.
139   std::string stripped_resource_id;
140   if (RE2::FullMatch(resource_id, "^[a-z-]+(?::|%3A)([\\w-]+)$",
141                      &stripped_resource_id))
142     return stripped_resource_id;
143   return resource_id;
144 }
145 
GetIdentityResourceIdCanonicalizer()146 ResourceIdCanonicalizer GetIdentityResourceIdCanonicalizer() {
147   return base::Bind(&Identity);
148 }
149 
150 const char kDocsListScope[] = "https://docs.google.com/feeds/";
151 const char kDriveAppsScope[] = "https://www.googleapis.com/auth/drive.apps";
152 
ParseShareUrlAndRun(const google_apis::GetShareUrlCallback & callback,google_apis::GDataErrorCode error,scoped_ptr<base::Value> value)153 void ParseShareUrlAndRun(const google_apis::GetShareUrlCallback& callback,
154                          google_apis::GDataErrorCode error,
155                          scoped_ptr<base::Value> value) {
156   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
157 
158   if (!value) {
159     callback.Run(error, GURL());
160     return;
161   }
162 
163   // Parsing ResourceEntry is cheap enough to do on UI thread.
164   scoped_ptr<google_apis::ResourceEntry> entry =
165       google_apis::ResourceEntry::ExtractAndParse(*value);
166   if (!entry) {
167     callback.Run(google_apis::GDATA_PARSE_ERROR, GURL());
168     return;
169   }
170 
171   const google_apis::Link* share_link =
172       entry->GetLinkByType(google_apis::Link::LINK_SHARE);
173   callback.Run(error, share_link ? share_link->href() : GURL());
174 }
175 
ConvertResourceEntryToFileResource(const google_apis::ResourceEntry & entry)176 scoped_ptr<google_apis::FileResource> ConvertResourceEntryToFileResource(
177     const google_apis::ResourceEntry& entry) {
178   scoped_ptr<google_apis::FileResource> file(new google_apis::FileResource);
179 
180   file->set_file_id(entry.resource_id());
181   file->set_title(entry.title());
182   file->set_created_date(entry.published_time());
183 
184   if (std::find(entry.labels().begin(), entry.labels().end(),
185                 "shared-with-me") != entry.labels().end()) {
186     // Set current time to mark the file is shared_with_me, since ResourceEntry
187     // doesn't have |shared_with_me_date| equivalent.
188     file->set_shared_with_me_date(base::Time::Now());
189   }
190 
191   file->set_shared(std::find(entry.labels().begin(), entry.labels().end(),
192                              "shared") != entry.labels().end());
193 
194   if (entry.is_folder()) {
195     file->set_mime_type(kDriveFolderMimeType);
196   } else {
197     std::string mime_type = GetMimeTypeFromEntryKind(entry.kind());
198     if (mime_type.empty())
199       mime_type = entry.content_mime_type();
200     file->set_mime_type(mime_type);
201   }
202 
203   file->set_md5_checksum(entry.file_md5());
204   file->set_file_size(entry.file_size());
205 
206   file->mutable_labels()->set_trashed(entry.deleted());
207   file->set_etag(entry.etag());
208 
209   google_apis::ImageMediaMetadata* image_media_metadata =
210     file->mutable_image_media_metadata();
211   image_media_metadata->set_width(entry.image_width());
212   image_media_metadata->set_height(entry.image_height());
213   image_media_metadata->set_rotation(entry.image_rotation());
214 
215   std::vector<google_apis::ParentReference>* parents = file->mutable_parents();
216   for (size_t i = 0; i < entry.links().size(); ++i) {
217     using google_apis::Link;
218     const Link& link = *entry.links()[i];
219     switch (link.type()) {
220       case Link::LINK_PARENT: {
221         google_apis::ParentReference parent;
222         parent.set_parent_link(link.href());
223 
224         std::string file_id =
225             drive::util::ExtractResourceIdFromUrl(link.href());
226         parent.set_file_id(file_id);
227         parents->push_back(parent);
228         break;
229       }
230       case Link::LINK_ALTERNATE:
231         file->set_alternate_link(link.href());
232         break;
233       default:
234         break;
235     }
236   }
237 
238   file->set_modified_date(entry.updated_time());
239   file->set_last_viewed_by_me_date(entry.last_viewed_time());
240 
241   return file.Pass();
242 }
243 
GetKind(const google_apis::FileResource & file_resource)244 google_apis::DriveEntryKind GetKind(
245     const google_apis::FileResource& file_resource) {
246   if (file_resource.IsDirectory())
247     return google_apis::ENTRY_KIND_FOLDER;
248 
249   const std::string& mime_type = file_resource.mime_type();
250   if (mime_type == kGoogleDocumentMimeType)
251     return google_apis::ENTRY_KIND_DOCUMENT;
252   if (mime_type == kGoogleSpreadsheetMimeType)
253     return google_apis::ENTRY_KIND_SPREADSHEET;
254   if (mime_type == kGooglePresentationMimeType)
255     return google_apis::ENTRY_KIND_PRESENTATION;
256   if (mime_type == kGoogleDrawingMimeType)
257     return google_apis::ENTRY_KIND_DRAWING;
258   if (mime_type == kGoogleTableMimeType)
259     return google_apis::ENTRY_KIND_TABLE;
260   if (mime_type == kGoogleFormMimeType)
261     return google_apis::ENTRY_KIND_FORM;
262   if (mime_type == "application/pdf")
263     return google_apis::ENTRY_KIND_PDF;
264   return google_apis::ENTRY_KIND_FILE;
265 }
266 
267 scoped_ptr<google_apis::ResourceEntry>
ConvertFileResourceToResourceEntry(const google_apis::FileResource & file_resource)268 ConvertFileResourceToResourceEntry(
269     const google_apis::FileResource& file_resource) {
270   scoped_ptr<google_apis::ResourceEntry> entry(new google_apis::ResourceEntry);
271 
272   // ResourceEntry
273   entry->set_resource_id(file_resource.file_id());
274   entry->set_id(file_resource.file_id());
275   entry->set_kind(GetKind(file_resource));
276   entry->set_title(file_resource.title());
277   entry->set_published_time(file_resource.created_date());
278 
279   std::vector<std::string> labels;
280   if (!file_resource.shared_with_me_date().is_null())
281     labels.push_back("shared-with-me");
282   if (file_resource.shared())
283     labels.push_back("shared");
284   entry->set_labels(labels);
285 
286   // This should be the url to download the file_resource.
287   {
288     google_apis::Content content;
289     content.set_mime_type(file_resource.mime_type());
290     entry->set_content(content);
291   }
292   // TODO(kochi): entry->resource_links_
293 
294   // For file entries
295   entry->set_filename(file_resource.title());
296   entry->set_suggested_filename(file_resource.title());
297   entry->set_file_md5(file_resource.md5_checksum());
298   entry->set_file_size(file_resource.file_size());
299 
300   // If file is removed completely, that information is only available in
301   // ChangeResource, and is reflected in |removed_|. If file is trashed, the
302   // file entry still exists but with its "trashed" label true.
303   entry->set_deleted(file_resource.labels().is_trashed());
304 
305   // ImageMediaMetadata
306   entry->set_image_width(file_resource.image_media_metadata().width());
307   entry->set_image_height(file_resource.image_media_metadata().height());
308   entry->set_image_rotation(file_resource.image_media_metadata().rotation());
309 
310   // CommonMetadata
311   entry->set_etag(file_resource.etag());
312   // entry->authors_
313   // entry->links_.
314   ScopedVector<google_apis::Link> links;
315   for (size_t i = 0; i < file_resource.parents().size(); ++i) {
316     google_apis::Link* link = new google_apis::Link;
317     link->set_type(google_apis::Link::LINK_PARENT);
318     link->set_href(file_resource.parents()[i].parent_link());
319     links.push_back(link);
320   }
321   if (!file_resource.alternate_link().is_empty()) {
322     google_apis::Link* link = new google_apis::Link;
323     link->set_type(google_apis::Link::LINK_ALTERNATE);
324     link->set_href(file_resource.alternate_link());
325     links.push_back(link);
326   }
327   entry->set_links(links.Pass());
328 
329   // entry->categories_
330   entry->set_updated_time(file_resource.modified_date());
331   entry->set_last_viewed_time(file_resource.last_viewed_by_me_date());
332 
333   entry->FillRemainingFields();
334   return entry.Pass();
335 }
336 
337 scoped_ptr<google_apis::ResourceEntry>
ConvertChangeResourceToResourceEntry(const google_apis::ChangeResource & change_resource)338 ConvertChangeResourceToResourceEntry(
339     const google_apis::ChangeResource& change_resource) {
340   scoped_ptr<google_apis::ResourceEntry> entry;
341   if (change_resource.file())
342     entry = ConvertFileResourceToResourceEntry(*change_resource.file()).Pass();
343   else
344     entry.reset(new google_apis::ResourceEntry);
345 
346   entry->set_resource_id(change_resource.file_id());
347   // If |is_deleted()| returns true, the file is removed from Drive.
348   entry->set_removed(change_resource.is_deleted());
349   entry->set_changestamp(change_resource.change_id());
350   entry->set_modification_date(change_resource.modification_date());
351 
352   return entry.Pass();
353 }
354 
355 scoped_ptr<google_apis::ResourceList>
ConvertFileListToResourceList(const google_apis::FileList & file_list)356 ConvertFileListToResourceList(const google_apis::FileList& file_list) {
357   scoped_ptr<google_apis::ResourceList> feed(new google_apis::ResourceList);
358 
359   const ScopedVector<google_apis::FileResource>& items = file_list.items();
360   ScopedVector<google_apis::ResourceEntry> entries;
361   for (size_t i = 0; i < items.size(); ++i)
362     entries.push_back(ConvertFileResourceToResourceEntry(*items[i]).release());
363   feed->set_entries(entries.Pass());
364 
365   ScopedVector<google_apis::Link> links;
366   if (!file_list.next_link().is_empty()) {
367     google_apis::Link* link = new google_apis::Link;
368     link->set_type(google_apis::Link::LINK_NEXT);
369     link->set_href(file_list.next_link());
370     links.push_back(link);
371   }
372   feed->set_links(links.Pass());
373 
374   return feed.Pass();
375 }
376 
377 scoped_ptr<google_apis::ResourceList>
ConvertChangeListToResourceList(const google_apis::ChangeList & change_list)378 ConvertChangeListToResourceList(const google_apis::ChangeList& change_list) {
379   scoped_ptr<google_apis::ResourceList> feed(new google_apis::ResourceList);
380 
381   const ScopedVector<google_apis::ChangeResource>& items = change_list.items();
382   ScopedVector<google_apis::ResourceEntry> entries;
383   for (size_t i = 0; i < items.size(); ++i) {
384     entries.push_back(
385         ConvertChangeResourceToResourceEntry(*items[i]).release());
386   }
387   feed->set_entries(entries.Pass());
388 
389   feed->set_largest_changestamp(change_list.largest_change_id());
390 
391   ScopedVector<google_apis::Link> links;
392   if (!change_list.next_link().is_empty()) {
393     google_apis::Link* link = new google_apis::Link;
394     link->set_type(google_apis::Link::LINK_NEXT);
395     link->set_href(change_list.next_link());
396     links.push_back(link);
397   }
398   feed->set_links(links.Pass());
399 
400   return feed.Pass();
401 }
402 
GetMd5Digest(const base::FilePath & file_path)403 std::string GetMd5Digest(const base::FilePath& file_path) {
404   const int kBufferSize = 512 * 1024;  // 512kB.
405 
406   base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
407   if (!file.IsValid())
408     return std::string();
409 
410   base::MD5Context context;
411   base::MD5Init(&context);
412 
413   int64 offset = 0;
414   scoped_ptr<char[]> buffer(new char[kBufferSize]);
415   while (true) {
416     int result = file.Read(offset, buffer.get(), kBufferSize);
417     if (result < 0) {
418       // Found an error.
419       return std::string();
420     }
421 
422     if (result == 0) {
423       // End of file.
424       break;
425     }
426 
427     offset += result;
428     base::MD5Update(&context, base::StringPiece(buffer.get(), result));
429   }
430 
431   base::MD5Digest digest;
432   base::MD5Final(&digest, &context);
433   return MD5DigestToBase16(digest);
434 }
435 
436 const char kWapiRootDirectoryResourceId[] = "folder:root";
437 
438 }  // namespace util
439 }  // namespace drive
440