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 "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 struct HostedDocumentKind {
     29   const char* mime_type;
     30   const char* extension;
     31 };
     32 
     33 const HostedDocumentKind kHostedDocumentKinds[] = {
     34     {kGoogleDocumentMimeType,     ".gdoc"},
     35     {kGoogleSpreadsheetMimeType,  ".gsheet"},
     36     {kGooglePresentationMimeType, ".gslides"},
     37     {kGoogleDrawingMimeType,      ".gdraw"},
     38     {kGoogleTableMimeType,        ".gtable"},
     39     {kGoogleFormMimeType,         ".gform"}
     40 };
     41 
     42 const char kUnknownHostedDocumentExtension[] = ".glink";
     43 
     44 }  // namespace
     45 
     46 std::string EscapeQueryStringValue(const std::string& str) {
     47   std::string result;
     48   result.reserve(str.size());
     49   for (size_t i = 0; i < str.size(); ++i) {
     50     if (str[i] == '\\' || str[i] == '\'') {
     51       result.push_back('\\');
     52     }
     53     result.push_back(str[i]);
     54   }
     55   return result;
     56 }
     57 
     58 std::string TranslateQuery(const std::string& original_query) {
     59   // In order to handle non-ascii white spaces correctly, convert to UTF16.
     60   base::string16 query = base::UTF8ToUTF16(original_query);
     61   const base::string16 kDelimiter(
     62       base::kWhitespaceUTF16 + base::ASCIIToUTF16("\""));
     63 
     64   std::string result;
     65   for (size_t index = query.find_first_not_of(base::kWhitespaceUTF16);
     66        index != base::string16::npos;
     67        index = query.find_first_not_of(base::kWhitespaceUTF16, index)) {
     68     bool is_exclusion = (query[index] == '-');
     69     if (is_exclusion)
     70       ++index;
     71     if (index == query.length()) {
     72       // Here, the token is '-' and it should be ignored.
     73       continue;
     74     }
     75 
     76     size_t begin_token = index;
     77     base::string16 token;
     78     if (query[begin_token] == '"') {
     79       // Quoted query.
     80       ++begin_token;
     81       size_t end_token = query.find('"', begin_token);
     82       if (end_token == base::string16::npos) {
     83         // This is kind of syntax error, since quoted string isn't finished.
     84         // However, the query is built by user manually, so here we treat
     85         // whole remaining string as a token as a fallback, by appending
     86         // a missing double-quote character.
     87         end_token = query.length();
     88         query.push_back('"');
     89       }
     90 
     91       token = query.substr(begin_token, end_token - begin_token);
     92       index = end_token + 1;  // Consume last '"', too.
     93     } else {
     94       size_t end_token = query.find_first_of(kDelimiter, begin_token);
     95       if (end_token == base::string16::npos) {
     96         end_token = query.length();
     97       }
     98 
     99       token = query.substr(begin_token, end_token - begin_token);
    100       index = end_token;
    101     }
    102 
    103     if (token.empty()) {
    104       // Just ignore an empty token.
    105       continue;
    106     }
    107 
    108     if (!result.empty()) {
    109       // If there are two or more tokens, need to connect with "and".
    110       result.append(" and ");
    111     }
    112 
    113     // The meaning of "fullText" should include title, description and content.
    114     base::StringAppendF(
    115         &result,
    116         "%sfullText contains \'%s\'",
    117         is_exclusion ? "not " : "",
    118         EscapeQueryStringValue(base::UTF16ToUTF8(token)).c_str());
    119   }
    120 
    121   return result;
    122 }
    123 
    124 std::string ExtractResourceIdFromUrl(const GURL& url) {
    125   return net::UnescapeURLComponent(url.ExtractFileName(),
    126                                    net::UnescapeRule::URL_SPECIAL_CHARS);
    127 }
    128 
    129 std::string CanonicalizeResourceId(const std::string& resource_id) {
    130   // If resource ID is in the old WAPI format starting with a prefix like
    131   // "document:", strip it and return the remaining part.
    132   std::string stripped_resource_id;
    133   if (RE2::FullMatch(resource_id, "^[a-z-]+(?::|%3A)([\\w-]+)$",
    134                      &stripped_resource_id))
    135     return stripped_resource_id;
    136   return resource_id;
    137 }
    138 
    139 scoped_ptr<google_apis::ResourceEntry>
    140 ConvertFileResourceToResourceEntry(
    141     const google_apis::FileResource& file_resource) {
    142   scoped_ptr<google_apis::ResourceEntry> entry(new google_apis::ResourceEntry);
    143 
    144   // ResourceEntry
    145   entry->set_resource_id(file_resource.file_id());
    146   entry->set_id(file_resource.file_id());
    147   if (file_resource.IsDirectory())
    148     entry->set_kind(google_apis::ResourceEntry::ENTRY_KIND_FOLDER);
    149   else if (file_resource.IsHostedDocument())
    150     entry->set_kind(google_apis::ResourceEntry::ENTRY_KIND_UNKNOWN);
    151   else
    152     entry->set_kind(google_apis::ResourceEntry::ENTRY_KIND_FILE);
    153   entry->set_title(file_resource.title());
    154   entry->set_published_time(file_resource.created_date());
    155 
    156   std::vector<std::string> labels;
    157   if (!file_resource.shared_with_me_date().is_null())
    158     labels.push_back("shared-with-me");
    159   if (file_resource.shared())
    160     labels.push_back("shared");
    161   entry->set_labels(labels);
    162 
    163   // This should be the url to download the file_resource.
    164   {
    165     google_apis::Content content;
    166     content.set_mime_type(file_resource.mime_type());
    167     entry->set_content(content);
    168   }
    169   // TODO(kochi): entry->resource_links_
    170 
    171   // For file entries
    172   entry->set_filename(file_resource.title());
    173   entry->set_suggested_filename(file_resource.title());
    174   entry->set_file_md5(file_resource.md5_checksum());
    175   entry->set_file_size(file_resource.file_size());
    176 
    177   // If file is removed completely, that information is only available in
    178   // ChangeResource, and is reflected in |removed_|. If file is trashed, the
    179   // file entry still exists but with its "trashed" label true.
    180   entry->set_deleted(file_resource.labels().is_trashed());
    181 
    182   // ImageMediaMetadata
    183   entry->set_image_width(file_resource.image_media_metadata().width());
    184   entry->set_image_height(file_resource.image_media_metadata().height());
    185   entry->set_image_rotation(file_resource.image_media_metadata().rotation());
    186 
    187   // CommonMetadata
    188   entry->set_etag(file_resource.etag());
    189   // entry->authors_
    190   // entry->links_.
    191   ScopedVector<google_apis::Link> links;
    192   for (size_t i = 0; i < file_resource.parents().size(); ++i) {
    193     google_apis::Link* link = new google_apis::Link;
    194     link->set_type(google_apis::Link::LINK_PARENT);
    195     link->set_href(file_resource.parents()[i].parent_link());
    196     links.push_back(link);
    197   }
    198   if (!file_resource.alternate_link().is_empty()) {
    199     google_apis::Link* link = new google_apis::Link;
    200     link->set_type(google_apis::Link::LINK_ALTERNATE);
    201     link->set_href(file_resource.alternate_link());
    202     links.push_back(link);
    203   }
    204   entry->set_links(links.Pass());
    205 
    206   // entry->categories_
    207   entry->set_updated_time(file_resource.modified_date());
    208   entry->set_last_viewed_time(file_resource.last_viewed_by_me_date());
    209 
    210   entry->FillRemainingFields();
    211   return entry.Pass();
    212 }
    213 
    214 scoped_ptr<google_apis::ResourceEntry>
    215 ConvertChangeResourceToResourceEntry(
    216     const google_apis::ChangeResource& change_resource) {
    217   scoped_ptr<google_apis::ResourceEntry> entry;
    218   if (change_resource.file())
    219     entry = ConvertFileResourceToResourceEntry(*change_resource.file()).Pass();
    220   else
    221     entry.reset(new google_apis::ResourceEntry);
    222 
    223   entry->set_resource_id(change_resource.file_id());
    224   // If |is_deleted()| returns true, the file is removed from Drive.
    225   entry->set_removed(change_resource.is_deleted());
    226   entry->set_changestamp(change_resource.change_id());
    227   entry->set_modification_date(change_resource.modification_date());
    228 
    229   return entry.Pass();
    230 }
    231 
    232 scoped_ptr<google_apis::ResourceList>
    233 ConvertFileListToResourceList(const google_apis::FileList& file_list) {
    234   scoped_ptr<google_apis::ResourceList> feed(new google_apis::ResourceList);
    235 
    236   const ScopedVector<google_apis::FileResource>& items = file_list.items();
    237   ScopedVector<google_apis::ResourceEntry> entries;
    238   for (size_t i = 0; i < items.size(); ++i)
    239     entries.push_back(ConvertFileResourceToResourceEntry(*items[i]).release());
    240   feed->set_entries(entries.Pass());
    241 
    242   ScopedVector<google_apis::Link> links;
    243   if (!file_list.next_link().is_empty()) {
    244     google_apis::Link* link = new google_apis::Link;
    245     link->set_type(google_apis::Link::LINK_NEXT);
    246     link->set_href(file_list.next_link());
    247     links.push_back(link);
    248   }
    249   feed->set_links(links.Pass());
    250 
    251   return feed.Pass();
    252 }
    253 
    254 scoped_ptr<google_apis::ResourceList>
    255 ConvertChangeListToResourceList(const google_apis::ChangeList& change_list) {
    256   scoped_ptr<google_apis::ResourceList> feed(new google_apis::ResourceList);
    257 
    258   const ScopedVector<google_apis::ChangeResource>& items = change_list.items();
    259   ScopedVector<google_apis::ResourceEntry> entries;
    260   for (size_t i = 0; i < items.size(); ++i) {
    261     entries.push_back(
    262         ConvertChangeResourceToResourceEntry(*items[i]).release());
    263   }
    264   feed->set_entries(entries.Pass());
    265 
    266   feed->set_largest_changestamp(change_list.largest_change_id());
    267 
    268   ScopedVector<google_apis::Link> links;
    269   if (!change_list.next_link().is_empty()) {
    270     google_apis::Link* link = new google_apis::Link;
    271     link->set_type(google_apis::Link::LINK_NEXT);
    272     link->set_href(change_list.next_link());
    273     links.push_back(link);
    274   }
    275   feed->set_links(links.Pass());
    276 
    277   return feed.Pass();
    278 }
    279 
    280 std::string GetMd5Digest(const base::FilePath& file_path) {
    281   const int kBufferSize = 512 * 1024;  // 512kB.
    282 
    283   base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
    284   if (!file.IsValid())
    285     return std::string();
    286 
    287   base::MD5Context context;
    288   base::MD5Init(&context);
    289 
    290   int64 offset = 0;
    291   scoped_ptr<char[]> buffer(new char[kBufferSize]);
    292   while (true) {
    293     int result = file.Read(offset, buffer.get(), kBufferSize);
    294     if (result < 0) {
    295       // Found an error.
    296       return std::string();
    297     }
    298 
    299     if (result == 0) {
    300       // End of file.
    301       break;
    302     }
    303 
    304     offset += result;
    305     base::MD5Update(&context, base::StringPiece(buffer.get(), result));
    306   }
    307 
    308   base::MD5Digest digest;
    309   base::MD5Final(&digest, &context);
    310   return MD5DigestToBase16(digest);
    311 }
    312 
    313 std::string GetHostedDocumentExtension(const std::string& mime_type) {
    314   for (size_t i = 0; i < arraysize(kHostedDocumentKinds); ++i) {
    315     if (mime_type == kHostedDocumentKinds[i].mime_type)
    316       return kHostedDocumentKinds[i].extension;
    317   }
    318   return kUnknownHostedDocumentExtension;
    319 }
    320 
    321 bool IsKnownHostedDocumentMimeType(const std::string& mime_type) {
    322   for (size_t i = 0; i < arraysize(kHostedDocumentKinds); ++i) {
    323     if (mime_type == kHostedDocumentKinds[i].mime_type)
    324       return true;
    325   }
    326   return false;
    327 }
    328 
    329 bool HasHostedDocumentExtension(const base::FilePath& path) {
    330   const std::string extension = base::FilePath(path.Extension()).AsUTF8Unsafe();
    331   for (size_t i = 0; i < arraysize(kHostedDocumentKinds); ++i) {
    332     if (extension == kHostedDocumentKinds[i].extension)
    333       return true;
    334   }
    335   return extension == kUnknownHostedDocumentExtension;
    336 }
    337 
    338 }  // namespace util
    339 }  // namespace drive
    340