Home | History | Annotate | Download | only in drive
      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|.
    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 
    158 Author::Author() {
    159 }
    160 
    161 // static
    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 
    171 Link::Link() : type_(Link::LINK_UNKNOWN) {
    172 }
    173 
    174 Link::~Link() {
    175 }
    176 
    177 // static
    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.
    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
    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 
    240 ResourceLink::ResourceLink() : type_(ResourceLink::FEED_LINK_UNKNOWN) {
    241 }
    242 
    243 // static.
    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
    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 
    268 Category::Category() : type_(CATEGORY_UNKNOWN) {
    269 }
    270 
    271 // Converts category.scheme into CategoryType enum.
    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
    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 
    293 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 
    304 Content::Content() {
    305 }
    306 
    307 // static
    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 
    317 CommonMetadata::CommonMetadata() {
    318 }
    319 
    320 CommonMetadata::~CommonMetadata() {
    321 }
    322 
    323 // static
    324 template<typename CommonMetadataDescendant>
    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 
    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 
    352 ResourceEntry::~ResourceEntry() {
    353 }
    354 
    355 bool ResourceEntry::HasFieldPresent(const base::Value* value,
    356                                     bool* result) {
    357   *result = (value != NULL);
    358   return true;
    359 }
    360 
    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
    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
    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 
    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
    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
    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
    475 std::string ResourceEntry::GetEntryNodeName() {
    476   return kEntryNode;
    477 }
    478 
    479 ////////////////////////////////////////////////////////////////////////////////
    480 // ResourceList implementation
    481 
    482 ResourceList::ResourceList()
    483     : start_index_(0),
    484       items_per_page_(0),
    485       largest_changestamp_(0) {
    486 }
    487 
    488 ResourceList::~ResourceList() {
    489 }
    490 
    491 // static
    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 
    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
    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
    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 
    548 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 
    559 void ResourceList::ReleaseEntries(std::vector<ResourceEntry*>* entries) {
    560   entries_.release(entries);
    561 }
    562 
    563 }  // namespace google_apis
    564