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