• 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 "google_apis/drive/gdata_wapi_parser.h"
6 
7 #include <algorithm>
8 #include <string>
9 
10 #include "base/basictypes.h"
11 #include "base/files/file_path.h"
12 #include "base/json/json_value_converter.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_piece.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/values.h"
19 #include "google_apis/drive/time_util.h"
20 
21 using base::Value;
22 using base::DictionaryValue;
23 using base::ListValue;
24 
25 namespace google_apis {
26 
27 namespace {
28 
29 // Term values for kSchemeKind category:
30 const char kTermPrefix[] = "http://schemas.google.com/docs/2007#";
31 
32 // Node names.
33 const char kEntryNode[] = "entry";
34 
35 // Field names.
36 const char kAuthorField[] = "author";
37 const char kCategoryField[] = "category";
38 const char kChangestampField[] = "docs$changestamp.value";
39 const char kContentField[] = "content";
40 const char kDeletedField[] = "gd$deleted";
41 const char kETagField[] = "gd$etag";
42 const char kEmailField[] = "email.$t";
43 const char kEntryField[] = "entry";
44 const char kFeedField[] = "feed";
45 const char kFeedLinkField[] = "gd$feedLink";
46 const char kFileNameField[] = "docs$filename.$t";
47 const char kHrefField[] = "href";
48 const char kIDField[] = "id.$t";
49 const char kItemsPerPageField[] = "openSearch$itemsPerPage.$t";
50 const char kLabelField[] = "label";
51 const char kLargestChangestampField[] = "docs$largestChangestamp.value";
52 const char kLastViewedField[] = "gd$lastViewed.$t";
53 const char kLinkField[] = "link";
54 const char kMD5Field[] = "docs$md5Checksum.$t";
55 const char kNameField[] = "name.$t";
56 const char kPublishedField[] = "published.$t";
57 const char kRelField[] = "rel";
58 const char kRemovedField[] = "docs$removed";
59 const char kResourceIdField[] = "gd$resourceId.$t";
60 const char kSchemeField[] = "scheme";
61 const char kSizeField[] = "docs$size.$t";
62 const char kSrcField[] = "src";
63 const char kStartIndexField[] = "openSearch$startIndex.$t";
64 const char kSuggestedFileNameField[] = "docs$suggestedFilename.$t";
65 const char kTermField[] = "term";
66 const char kTitleField[] = "title";
67 const char kTitleTField[] = "title.$t";
68 const char kTypeField[] = "type";
69 const char kUpdatedField[] = "updated.$t";
70 
71 // Link Prefixes
72 const char kOpenWithPrefix[] = "http://schemas.google.com/docs/2007#open-with-";
73 const size_t kOpenWithPrefixSize = arraysize(kOpenWithPrefix) - 1;
74 
75 struct LinkTypeMap {
76   Link::LinkType type;
77   const char* rel;
78 };
79 
80 const LinkTypeMap kLinkTypeMap[] = {
81     { Link::LINK_SELF,
82       "self" },
83     { Link::LINK_NEXT,
84       "next" },
85     { Link::LINK_PARENT,
86       "http://schemas.google.com/docs/2007#parent" },
87     { Link::LINK_ALTERNATE,
88       "alternate"},
89     { Link::LINK_EDIT,
90       "edit" },
91     { Link::LINK_EDIT_MEDIA,
92       "edit-media" },
93     { Link::LINK_ALT_EDIT_MEDIA,
94       "http://schemas.google.com/docs/2007#alt-edit-media" },
95     { Link::LINK_ALT_POST,
96       "http://schemas.google.com/docs/2007#alt-post" },
97     { Link::LINK_FEED,
98       "http://schemas.google.com/g/2005#feed"},
99     { Link::LINK_POST,
100       "http://schemas.google.com/g/2005#post"},
101     { Link::LINK_BATCH,
102       "http://schemas.google.com/g/2005#batch"},
103     { Link::LINK_THUMBNAIL,
104       "http://schemas.google.com/docs/2007/thumbnail"},
105     { Link::LINK_RESUMABLE_EDIT_MEDIA,
106       "http://schemas.google.com/g/2005#resumable-edit-media"},
107     { Link::LINK_RESUMABLE_CREATE_MEDIA,
108       "http://schemas.google.com/g/2005#resumable-create-media"},
109     { Link::LINK_TABLES_FEED,
110       "http://schemas.google.com/spreadsheets/2006#tablesfeed"},
111     { Link::LINK_WORKSHEET_FEED,
112       "http://schemas.google.com/spreadsheets/2006#worksheetsfeed"},
113     { Link::LINK_EMBED,
114       "http://schemas.google.com/docs/2007#embed"},
115     { Link::LINK_PRODUCT,
116       "http://schemas.google.com/docs/2007#product"},
117     { Link::LINK_ICON,
118       "http://schemas.google.com/docs/2007#icon"},
119     { Link::LINK_SHARE,
120       "http://schemas.google.com/docs/2007#share"},
121 };
122 
123 struct ResourceLinkTypeMap {
124   ResourceLink::ResourceLinkType type;
125   const char* rel;
126 };
127 
128 const ResourceLinkTypeMap kFeedLinkTypeMap[] = {
129     { ResourceLink::FEED_LINK_ACL,
130       "http://schemas.google.com/acl/2007#accessControlList" },
131     { ResourceLink::FEED_LINK_REVISIONS,
132       "http://schemas.google.com/docs/2007/revisions" },
133 };
134 
135 struct CategoryTypeMap {
136   Category::CategoryType type;
137   const char* scheme;
138 };
139 
140 const CategoryTypeMap kCategoryTypeMap[] = {
141     { Category::CATEGORY_KIND, "http://schemas.google.com/g/2005#kind" },
142     { Category::CATEGORY_LABEL, "http://schemas.google.com/g/2005/labels" },
143 };
144 
145 // Converts |url_string| to |result|.  Always returns true to be used
146 // for JSONValueConverter::RegisterCustomField method.
147 // TODO(mukai): make it return false in case of invalid |url_string|.
GetGURLFromString(const base::StringPiece & url_string,GURL * result)148 bool GetGURLFromString(const base::StringPiece& url_string, GURL* result) {
149   *result = GURL(url_string.as_string());
150   return true;
151 }
152 
153 }  // namespace
154 
155 ////////////////////////////////////////////////////////////////////////////////
156 // Author implementation
157 
Author()158 Author::Author() {
159 }
160 
161 // static
RegisterJSONConverter(base::JSONValueConverter<Author> * converter)162 void Author::RegisterJSONConverter(
163     base::JSONValueConverter<Author>* converter) {
164   converter->RegisterStringField(kNameField, &Author::name_);
165   converter->RegisterStringField(kEmailField, &Author::email_);
166 }
167 
168 ////////////////////////////////////////////////////////////////////////////////
169 // Link implementation
170 
Link()171 Link::Link() : type_(Link::LINK_UNKNOWN) {
172 }
173 
~Link()174 Link::~Link() {
175 }
176 
177 // static
GetAppID(const base::StringPiece & rel,std::string * app_id)178 bool Link::GetAppID(const base::StringPiece& rel, std::string* app_id) {
179   DCHECK(app_id);
180   // Fast return path if the link clearly isn't an OPEN_WITH link.
181   if (rel.size() < kOpenWithPrefixSize) {
182     app_id->clear();
183     return true;
184   }
185 
186   const std::string kOpenWithPrefixStr(kOpenWithPrefix);
187   if (StartsWithASCII(rel.as_string(), kOpenWithPrefixStr, false)) {
188     *app_id = rel.as_string().substr(kOpenWithPrefixStr.size());
189     return true;
190   }
191 
192   app_id->clear();
193   return true;
194 }
195 
196 // static.
GetLinkType(const base::StringPiece & rel,Link::LinkType * type)197 bool Link::GetLinkType(const base::StringPiece& rel, Link::LinkType* type) {
198   DCHECK(type);
199   for (size_t i = 0; i < arraysize(kLinkTypeMap); i++) {
200     if (rel == kLinkTypeMap[i].rel) {
201       *type = kLinkTypeMap[i].type;
202       return true;
203     }
204   }
205 
206   // OPEN_WITH links have extra information at the end of the rel that is unique
207   // for each one, so we can't just check the usual map. This check is slightly
208   // redundant to provide a quick skip if it's obviously not an OPEN_WITH url.
209   if (rel.size() >= kOpenWithPrefixSize &&
210       StartsWithASCII(rel.as_string(), kOpenWithPrefix, false)) {
211     *type = LINK_OPEN_WITH;
212     return true;
213   }
214 
215   // Let unknown link types through, just report it; if the link type is needed
216   // in the future, add it into LinkType and kLinkTypeMap.
217   DVLOG(1) << "Ignoring unknown link type for rel " << rel;
218   *type = LINK_UNKNOWN;
219   return true;
220 }
221 
222 // static
RegisterJSONConverter(base::JSONValueConverter<Link> * converter)223 void Link::RegisterJSONConverter(base::JSONValueConverter<Link>* converter) {
224   converter->RegisterCustomField<Link::LinkType>(kRelField,
225                                                  &Link::type_,
226                                                  &Link::GetLinkType);
227   // We have to register kRelField twice because we extract two different pieces
228   // of data from the same rel field.
229   converter->RegisterCustomField<std::string>(kRelField,
230                                               &Link::app_id_,
231                                               &Link::GetAppID);
232   converter->RegisterCustomField(kHrefField, &Link::href_, &GetGURLFromString);
233   converter->RegisterStringField(kTitleField, &Link::title_);
234   converter->RegisterStringField(kTypeField, &Link::mime_type_);
235 }
236 
237 ////////////////////////////////////////////////////////////////////////////////
238 // ResourceLink implementation
239 
ResourceLink()240 ResourceLink::ResourceLink() : type_(ResourceLink::FEED_LINK_UNKNOWN) {
241 }
242 
243 // static.
GetFeedLinkType(const base::StringPiece & rel,ResourceLink::ResourceLinkType * result)244 bool ResourceLink::GetFeedLinkType(
245     const base::StringPiece& rel, ResourceLink::ResourceLinkType* result) {
246   for (size_t i = 0; i < arraysize(kFeedLinkTypeMap); i++) {
247     if (rel == kFeedLinkTypeMap[i].rel) {
248       *result = kFeedLinkTypeMap[i].type;
249       return true;
250     }
251   }
252   DVLOG(1) << "Unknown feed link type for rel " << rel;
253   return false;
254 }
255 
256 // static
RegisterJSONConverter(base::JSONValueConverter<ResourceLink> * converter)257 void ResourceLink::RegisterJSONConverter(
258     base::JSONValueConverter<ResourceLink>* converter) {
259   converter->RegisterCustomField<ResourceLink::ResourceLinkType>(
260       kRelField, &ResourceLink::type_, &ResourceLink::GetFeedLinkType);
261   converter->RegisterCustomField(
262       kHrefField, &ResourceLink::href_, &GetGURLFromString);
263 }
264 
265 ////////////////////////////////////////////////////////////////////////////////
266 // Category implementation
267 
Category()268 Category::Category() : type_(CATEGORY_UNKNOWN) {
269 }
270 
271 // Converts category.scheme into CategoryType enum.
GetCategoryTypeFromScheme(const base::StringPiece & scheme,Category::CategoryType * result)272 bool Category::GetCategoryTypeFromScheme(
273     const base::StringPiece& scheme, Category::CategoryType* result) {
274   for (size_t i = 0; i < arraysize(kCategoryTypeMap); i++) {
275     if (scheme == kCategoryTypeMap[i].scheme) {
276       *result = kCategoryTypeMap[i].type;
277       return true;
278     }
279   }
280   DVLOG(1) << "Unknown feed link type for scheme " << scheme;
281   return false;
282 }
283 
284 // static
RegisterJSONConverter(base::JSONValueConverter<Category> * converter)285 void Category::RegisterJSONConverter(
286     base::JSONValueConverter<Category>* converter) {
287   converter->RegisterStringField(kLabelField, &Category::label_);
288   converter->RegisterCustomField<Category::CategoryType>(
289       kSchemeField, &Category::type_, &Category::GetCategoryTypeFromScheme);
290   converter->RegisterStringField(kTermField, &Category::term_);
291 }
292 
GetLinkByType(Link::LinkType type) const293 const Link* CommonMetadata::GetLinkByType(Link::LinkType type) const {
294   for (size_t i = 0; i < links_.size(); ++i) {
295     if (links_[i]->type() == type)
296       return links_[i];
297   }
298   return NULL;
299 }
300 
301 ////////////////////////////////////////////////////////////////////////////////
302 // Content implementation
303 
Content()304 Content::Content() {
305 }
306 
307 // static
RegisterJSONConverter(base::JSONValueConverter<Content> * converter)308 void Content::RegisterJSONConverter(
309     base::JSONValueConverter<Content>* converter) {
310   converter->RegisterCustomField(kSrcField, &Content::url_, &GetGURLFromString);
311   converter->RegisterStringField(kTypeField, &Content::mime_type_);
312 }
313 
314 ////////////////////////////////////////////////////////////////////////////////
315 // CommonMetadata implementation
316 
CommonMetadata()317 CommonMetadata::CommonMetadata() {
318 }
319 
~CommonMetadata()320 CommonMetadata::~CommonMetadata() {
321 }
322 
323 // static
324 template<typename CommonMetadataDescendant>
RegisterJSONConverter(base::JSONValueConverter<CommonMetadataDescendant> * converter)325 void CommonMetadata::RegisterJSONConverter(
326     base::JSONValueConverter<CommonMetadataDescendant>* converter) {
327   converter->RegisterStringField(kETagField, &CommonMetadata::etag_);
328   converter->template RegisterRepeatedMessage<Author>(
329       kAuthorField, &CommonMetadata::authors_);
330   converter->template RegisterRepeatedMessage<Link>(
331       kLinkField, &CommonMetadata::links_);
332   converter->template RegisterRepeatedMessage<Category>(
333       kCategoryField, &CommonMetadata::categories_);
334   converter->template RegisterCustomField<base::Time>(
335       kUpdatedField, &CommonMetadata::updated_time_, &util::GetTimeFromString);
336 }
337 
338 ////////////////////////////////////////////////////////////////////////////////
339 // ResourceEntry implementation
340 
ResourceEntry()341 ResourceEntry::ResourceEntry()
342     : kind_(ENTRY_KIND_UNKNOWN),
343       file_size_(0),
344       deleted_(false),
345       removed_(false),
346       changestamp_(0),
347       image_width_(-1),
348       image_height_(-1),
349       image_rotation_(-1) {
350 }
351 
~ResourceEntry()352 ResourceEntry::~ResourceEntry() {
353 }
354 
HasFieldPresent(const base::Value * value,bool * result)355 bool ResourceEntry::HasFieldPresent(const base::Value* value,
356                                     bool* result) {
357   *result = (value != NULL);
358   return true;
359 }
360 
ParseChangestamp(const base::Value * value,int64 * result)361 bool ResourceEntry::ParseChangestamp(const base::Value* value,
362                                      int64* result) {
363   DCHECK(result);
364   if (!value) {
365     *result = 0;
366     return true;
367   }
368 
369   std::string string_value;
370   if (value->GetAsString(&string_value) &&
371       base::StringToInt64(string_value, result))
372     return true;
373 
374   return false;
375 }
376 
377 // static
RegisterJSONConverter(base::JSONValueConverter<ResourceEntry> * converter)378 void ResourceEntry::RegisterJSONConverter(
379     base::JSONValueConverter<ResourceEntry>* converter) {
380   // Inherit the parent registrations.
381   CommonMetadata::RegisterJSONConverter(converter);
382   converter->RegisterStringField(
383       kResourceIdField, &ResourceEntry::resource_id_);
384   converter->RegisterStringField(kIDField, &ResourceEntry::id_);
385   converter->RegisterStringField(kTitleTField, &ResourceEntry::title_);
386   converter->RegisterCustomField<base::Time>(
387       kPublishedField, &ResourceEntry::published_time_,
388       &util::GetTimeFromString);
389   converter->RegisterCustomField<base::Time>(
390       kLastViewedField, &ResourceEntry::last_viewed_time_,
391       &util::GetTimeFromString);
392   converter->RegisterRepeatedMessage(
393       kFeedLinkField, &ResourceEntry::resource_links_);
394   converter->RegisterNestedField(kContentField, &ResourceEntry::content_);
395 
396   // File properties.  If the resource type is not a normal file, then
397   // that's no problem because those feed must not have these fields
398   // themselves, which does not report errors.
399   converter->RegisterStringField(kFileNameField, &ResourceEntry::filename_);
400   converter->RegisterStringField(kMD5Field, &ResourceEntry::file_md5_);
401   converter->RegisterCustomField<int64>(
402       kSizeField, &ResourceEntry::file_size_, &base::StringToInt64);
403   converter->RegisterStringField(
404       kSuggestedFileNameField, &ResourceEntry::suggested_filename_);
405   // Deleted are treated as 'trashed' items on web client side. Removed files
406   // are gone for good. We treat both cases as 'deleted' for this client.
407   converter->RegisterCustomValueField<bool>(
408       kDeletedField, &ResourceEntry::deleted_, &ResourceEntry::HasFieldPresent);
409   converter->RegisterCustomValueField<bool>(
410       kRemovedField, &ResourceEntry::removed_, &ResourceEntry::HasFieldPresent);
411   converter->RegisterCustomValueField<int64>(
412       kChangestampField, &ResourceEntry::changestamp_,
413       &ResourceEntry::ParseChangestamp);
414   // ImageMediaMetadata fields are not supported by WAPI.
415 }
416 
417 // static
GetEntryKindFromTerm(const std::string & term)418 ResourceEntry::ResourceEntryKind ResourceEntry::GetEntryKindFromTerm(
419     const std::string& term) {
420   if (!StartsWithASCII(term, kTermPrefix, false)) {
421     DVLOG(1) << "Unexpected term prefix term " << term;
422     return ENTRY_KIND_UNKNOWN;
423   }
424 
425   std::string type = term.substr(strlen(kTermPrefix));
426   if (type == "folder")
427     return ENTRY_KIND_FOLDER;
428   if (type == "file" || type == "pdf")
429     return ENTRY_KIND_FILE;
430 
431   DVLOG(1) << "Unknown entry type for term " << term << ", type " << type;
432   return ENTRY_KIND_UNKNOWN;
433 }
434 
FillRemainingFields()435 void ResourceEntry::FillRemainingFields() {
436   // Set |kind_| and |labels_| based on the |categories_| in the class.
437   // JSONValueConverter does not have the ability to catch an element in a list
438   // based on a predicate.  Thus we need to iterate over |categories_| and
439   // find the elements to set these fields as a post-process.
440   for (size_t i = 0; i < categories_.size(); ++i) {
441     const Category* category = categories_[i];
442     if (category->type() == Category::CATEGORY_KIND)
443       kind_ = GetEntryKindFromTerm(category->term());
444     else if (category->type() == Category::CATEGORY_LABEL)
445       labels_.push_back(category->label());
446   }
447 }
448 
449 // static
ExtractAndParse(const base::Value & value)450 scoped_ptr<ResourceEntry> ResourceEntry::ExtractAndParse(
451     const base::Value& value) {
452   const base::DictionaryValue* as_dict = NULL;
453   const base::DictionaryValue* entry_dict = NULL;
454   if (value.GetAsDictionary(&as_dict) &&
455       as_dict->GetDictionary(kEntryField, &entry_dict)) {
456     return ResourceEntry::CreateFrom(*entry_dict);
457   }
458   return scoped_ptr<ResourceEntry>();
459 }
460 
461 // static
CreateFrom(const base::Value & value)462 scoped_ptr<ResourceEntry> ResourceEntry::CreateFrom(const base::Value& value) {
463   base::JSONValueConverter<ResourceEntry> converter;
464   scoped_ptr<ResourceEntry> entry(new ResourceEntry());
465   if (!converter.Convert(value, entry.get())) {
466     DVLOG(1) << "Invalid resource entry!";
467     return scoped_ptr<ResourceEntry>();
468   }
469 
470   entry->FillRemainingFields();
471   return entry.Pass();
472 }
473 
474 // static
GetEntryNodeName()475 std::string ResourceEntry::GetEntryNodeName() {
476   return kEntryNode;
477 }
478 
479 ////////////////////////////////////////////////////////////////////////////////
480 // ResourceList implementation
481 
ResourceList()482 ResourceList::ResourceList()
483     : start_index_(0),
484       items_per_page_(0),
485       largest_changestamp_(0) {
486 }
487 
~ResourceList()488 ResourceList::~ResourceList() {
489 }
490 
491 // static
RegisterJSONConverter(base::JSONValueConverter<ResourceList> * converter)492 void ResourceList::RegisterJSONConverter(
493     base::JSONValueConverter<ResourceList>* converter) {
494   // inheritance
495   CommonMetadata::RegisterJSONConverter(converter);
496   // TODO(zelidrag): Once we figure out where these will be used, we should
497   // check for valid start_index_ and items_per_page_ values.
498   converter->RegisterCustomField<int>(
499       kStartIndexField, &ResourceList::start_index_, &base::StringToInt);
500   converter->RegisterCustomField<int>(
501       kItemsPerPageField, &ResourceList::items_per_page_, &base::StringToInt);
502   converter->RegisterStringField(kTitleTField, &ResourceList::title_);
503   converter->RegisterRepeatedMessage(kEntryField, &ResourceList::entries_);
504   converter->RegisterCustomField<int64>(
505      kLargestChangestampField, &ResourceList::largest_changestamp_,
506      &base::StringToInt64);
507 }
508 
Parse(const base::Value & value)509 bool ResourceList::Parse(const base::Value& value) {
510   base::JSONValueConverter<ResourceList> converter;
511   if (!converter.Convert(value, this)) {
512     DVLOG(1) << "Invalid resource list!";
513     return false;
514   }
515 
516   ScopedVector<ResourceEntry>::iterator iter = entries_.begin();
517   while (iter != entries_.end()) {
518     ResourceEntry* entry = (*iter);
519     entry->FillRemainingFields();
520     ++iter;
521   }
522   return true;
523 }
524 
525 // static
ExtractAndParse(const base::Value & value)526 scoped_ptr<ResourceList> ResourceList::ExtractAndParse(
527     const base::Value& value) {
528   const base::DictionaryValue* as_dict = NULL;
529   const base::DictionaryValue* feed_dict = NULL;
530   if (value.GetAsDictionary(&as_dict) &&
531       as_dict->GetDictionary(kFeedField, &feed_dict)) {
532     return ResourceList::CreateFrom(*feed_dict);
533   }
534   return scoped_ptr<ResourceList>();
535 }
536 
537 // static
CreateFrom(const base::Value & value)538 scoped_ptr<ResourceList> ResourceList::CreateFrom(const base::Value& value) {
539   scoped_ptr<ResourceList> feed(new ResourceList());
540   if (!feed->Parse(value)) {
541     DVLOG(1) << "Invalid resource list!";
542     return scoped_ptr<ResourceList>();
543   }
544 
545   return feed.Pass();
546 }
547 
GetNextFeedURL(GURL * url) const548 bool ResourceList::GetNextFeedURL(GURL* url) const {
549   DCHECK(url);
550   for (size_t i = 0; i < links_.size(); ++i) {
551     if (links_[i]->type() == Link::LINK_NEXT) {
552       *url = links_[i]->href();
553       return true;
554     }
555   }
556   return false;
557 }
558 
ReleaseEntries(std::vector<ResourceEntry * > * entries)559 void ResourceList::ReleaseEntries(std::vector<ResourceEntry*>* entries) {
560   entries_.release(entries);
561 }
562 
563 }  // namespace google_apis
564