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 "components/drive/drive_api_util.h"
6 
7 #include <string>
8 
9 #include "base/files/file.h"
10 #include "base/hash/md5.h"
11 #include "base/stl_util.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/synchronization/atomic_flag.h"
17 #include "third_party/re2/src/re2/re2.h"
18 
19 namespace drive {
20 namespace util {
21 namespace {
22 
23 struct HostedDocumentKind {
24   const char* mime_type;
25   const char* extension;
26 };
27 
28 const HostedDocumentKind kHostedDocumentKinds[] = {
29     {kGoogleDocumentMimeType,     ".gdoc"},
30     {kGoogleSpreadsheetMimeType,  ".gsheet"},
31     {kGooglePresentationMimeType, ".gslides"},
32     {kGoogleDrawingMimeType,      ".gdraw"},
33     {kGoogleTableMimeType,        ".gtable"},
34     {kGoogleFormMimeType,         ".gform"},
35     {kGoogleMapMimeType,          ".gmaps"},
36     {kGoogleSiteMimeType,         ".gsite"},
37 };
38 
39 const char kUnknownHostedDocumentExtension[] = ".glink";
40 
41 const int kMd5DigestBufferSize = 512 * 1024;  // 512 kB.
42 
43 }  // namespace
44 
EscapeQueryStringValue(const std::string & str)45 std::string EscapeQueryStringValue(const std::string& str) {
46   std::string result;
47   result.reserve(str.size());
48   for (size_t i = 0; i < str.size(); ++i) {
49     if (str[i] == '\\' || str[i] == '\'') {
50       result.push_back('\\');
51     }
52     result.push_back(str[i]);
53   }
54   return result;
55 }
56 
TranslateQuery(const std::string & original_query)57 std::string TranslateQuery(const std::string& original_query) {
58   // In order to handle non-ascii white spaces correctly, convert to UTF16.
59   base::string16 query = base::UTF8ToUTF16(original_query);
60   const base::string16 kDelimiter(
61       base::kWhitespaceUTF16 + base::ASCIIToUTF16("\""));
62 
63   std::string result;
64   for (size_t index = query.find_first_not_of(base::kWhitespaceUTF16);
65        index != base::string16::npos;
66        index = query.find_first_not_of(base::kWhitespaceUTF16, index)) {
67     bool is_exclusion = (query[index] == '-');
68     if (is_exclusion)
69       ++index;
70     if (index == query.length()) {
71       // Here, the token is '-' and it should be ignored.
72       continue;
73     }
74 
75     size_t begin_token = index;
76     base::string16 token;
77     if (query[begin_token] == '"') {
78       // Quoted query.
79       ++begin_token;
80       size_t end_token = query.find('"', begin_token);
81       if (end_token == base::string16::npos) {
82         // This is kind of syntax error, since quoted string isn't finished.
83         // However, the query is built by user manually, so here we treat
84         // whole remaining string as a token as a fallback, by appending
85         // a missing double-quote character.
86         end_token = query.length();
87         query.push_back('"');
88       }
89 
90       token = query.substr(begin_token, end_token - begin_token);
91       index = end_token + 1;  // Consume last '"', too.
92     } else {
93       size_t end_token = query.find_first_of(kDelimiter, begin_token);
94       if (end_token == base::string16::npos) {
95         end_token = query.length();
96       }
97 
98       token = query.substr(begin_token, end_token - begin_token);
99       index = end_token;
100     }
101 
102     if (token.empty()) {
103       // Just ignore an empty token.
104       continue;
105     }
106 
107     if (!result.empty()) {
108       // If there are two or more tokens, need to connect with "and".
109       result.append(" and ");
110     }
111 
112     // The meaning of "fullText" should include title, description and content.
113     base::StringAppendF(
114         &result,
115         "%sfullText contains \'%s\'",
116         is_exclusion ? "not " : "",
117         EscapeQueryStringValue(base::UTF16ToUTF8(token)).c_str());
118   }
119 
120   return result;
121 }
122 
CanonicalizeResourceId(const std::string & resource_id)123 std::string CanonicalizeResourceId(const std::string& resource_id) {
124   // If resource ID is in the old WAPI format starting with a prefix like
125   // "document:", strip it and return the remaining part.
126   std::string stripped_resource_id;
127   if (RE2::FullMatch(resource_id, "^[a-z-]+(?::|%3A)([\\w-]+)$",
128                      &stripped_resource_id))
129     return stripped_resource_id;
130   return resource_id;
131 }
132 
GetMd5Digest(const base::FilePath & file_path,const base::AtomicFlag * cancellation_flag)133 std::string GetMd5Digest(const base::FilePath& file_path,
134                          const base::AtomicFlag* cancellation_flag) {
135   base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
136   if (!file.IsValid())
137     return std::string();
138 
139   base::MD5Context context;
140   base::MD5Init(&context);
141 
142   int64_t offset = 0;
143   std::unique_ptr<char[]> buffer(new char[kMd5DigestBufferSize]);
144   while (true) {
145     if (cancellation_flag && cancellation_flag->IsSet()) {  // Cancelled.
146       return std::string();
147     }
148     int result = file.Read(offset, buffer.get(), kMd5DigestBufferSize);
149     if (result < 0) {
150       // Found an error.
151       return std::string();
152     }
153 
154     if (result == 0) {
155       // End of file.
156       break;
157     }
158 
159     offset += result;
160     base::MD5Update(&context, base::StringPiece(buffer.get(), result));
161   }
162 
163   base::MD5Digest digest;
164   base::MD5Final(&digest, &context);
165   return base::MD5DigestToBase16(digest);
166 }
167 
IsKnownHostedDocumentMimeType(const std::string & mime_type)168 bool IsKnownHostedDocumentMimeType(const std::string& mime_type) {
169   for (size_t i = 0; i < base::size(kHostedDocumentKinds); ++i) {
170     if (mime_type == kHostedDocumentKinds[i].mime_type)
171       return true;
172   }
173   return false;
174 }
175 
HasHostedDocumentExtension(const base::FilePath & path)176 bool HasHostedDocumentExtension(const base::FilePath& path) {
177   const std::string extension = base::FilePath(path.Extension()).AsUTF8Unsafe();
178   for (size_t i = 0; i < base::size(kHostedDocumentKinds); ++i) {
179     if (extension == kHostedDocumentKinds[i].extension)
180       return true;
181   }
182   return extension == kUnknownHostedDocumentExtension;
183 }
184 
185 
186 }  // namespace util
187 }  // namespace drive
188