OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2015 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 "services/service_cache/service_cache_impl.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/files/file_util.h" | |
9 #include "base/location.h" | |
10 #include "base/logging.h" | |
11 #include "base/strings/string_util.h" | |
12 #include "base/strings/stringprintf.h" | |
13 #include "mojo/common/data_pipe_utils.h" | |
14 #include "mojo/public/cpp/application/application_connection.h" | |
15 #include "mojo/public/cpp/bindings/lib/fixed_buffer.h" | |
16 #include "services/service_cache/service_cache_entry.mojom.h" | |
17 #include "third_party/zlib/google/zip_reader.h" | |
18 #include "url/gurl.h" | |
19 | |
20 namespace mojo { | |
21 namespace service_cache { | |
22 | |
23 namespace { | |
24 | |
25 const char kEtagHeader[] = "etag"; | |
26 | |
27 template <typename T> | |
28 void Serialize(T input, std::string* output) { | |
29 typedef typename mojo::internal::WrapperTraits<T>::DataType DataType; | |
30 size_t size = GetSerializedSize_(input); | |
31 mojo::internal::FixedBuffer buf(size); | |
32 DataType data_type; | |
33 Serialize_(input.Pass(), &buf, &data_type); | |
blundell
2015/05/07 11:29:21
The usage of these internal SDK functions is a lit
qsr
2015/05/07 12:49:02
Versioning might be useful.
Also, we have plan t
| |
34 std::vector<Handle> handles; | |
35 data_type->EncodePointersAndHandles(&handles); | |
36 void* serialized_data = buf.Leak(); | |
37 *output = std::string(static_cast<char*>(serialized_data), size); | |
38 free(serialized_data); | |
39 } | |
40 | |
41 template <typename T> | |
42 void Deserialize(std::string input, T* output) { | |
43 typedef typename mojo::internal::WrapperTraits<T>::DataType DataType; | |
44 DataType data_type = reinterpret_cast<DataType>(&input[0]); | |
45 std::vector<Handle> handles; | |
46 data_type->DecodePointersAndHandles(&handles); | |
47 Deserialize_(data_type, output); | |
48 } | |
49 | |
50 Array<uint8_t> PathToArray(const base::FilePath& path) { | |
51 if (path.empty()) | |
52 return Array<uint8_t>(); | |
53 const std::string& string = path.value(); | |
54 Array<uint8_t> result(string.size()); | |
55 memcpy(&result.front(), string.data(), string.size()); | |
56 return result.Pass(); | |
57 } | |
58 | |
59 std::string EncodeString(const std::string& string) { | |
60 std::string result = ""; | |
61 for (size_t i = 0; i < string.size(); ++i) { | |
62 unsigned char c = string[i]; | |
63 if (c >= 32 && c < 128 && c != '#' && c != ':') { | |
64 result += c; | |
65 } else { | |
66 result += base::StringPrintf("#%02x", c); | |
67 } | |
68 } | |
69 return result; | |
70 } | |
71 | |
72 base::FilePath GetBaseDirectory() { | |
73 return base::FilePath(getenv("HOME")).Append(".mojo_service_cache"); | |
74 } | |
75 | |
76 base::FilePath GetDirName(base::FilePath base_directory, | |
77 const std::string& url) { | |
78 // TODO(qsr): If the speed of directory traversal is problematic, this might | |
79 // need to change to use less directories. | |
80 return base_directory.Append(EncodeString(url)); | |
81 } | |
82 | |
83 base::FilePath GetConsumerCacheDirectory(const base::FilePath& main_cache) { | |
84 return main_cache.Append("consumer_cache"); | |
85 } | |
86 | |
87 base::FilePath GetExtractedSentinel(const base::FilePath& main_cache) { | |
88 return main_cache.Append("extracted_sentinel"); | |
89 } | |
90 | |
91 void RunCallbackWithSuccess( | |
92 const ServiceCacheImpl::FilePathPairCallback& callback, | |
93 const base::FilePath& content_path, | |
94 const base::FilePath& cache_dir, | |
95 const base::FilePath& entry_path, | |
96 CacheEntryPtr entry, | |
97 bool success) { | |
98 if (!success) { | |
99 callback.Run(base::FilePath(), base::FilePath()); | |
100 return; | |
101 } | |
102 std::string serialize_entry; | |
blundell
2015/05/07 11:29:21
nit: s/serialize_entry/serialized_entry
qsr
2015/05/07 12:49:02
Done.
| |
103 Serialize(entry.Pass(), &serialize_entry); | |
104 // We can ignore error, as it will just force to clear the cache on the next | |
105 // request. | |
106 WriteFile(entry_path, serialize_entry.data(), serialize_entry.size()); | |
107 callback.Run(content_path, cache_dir); | |
108 } | |
109 | |
110 void RunMojoCallback( | |
111 const Callback<void(Array<uint8_t>, Array<uint8_t>)>& callback, | |
112 const base::FilePath& path1, | |
113 const base::FilePath& path2) { | |
114 callback.Run(PathToArray(path1), PathToArray(path2)); | |
115 } | |
116 | |
117 std::vector<std::string> GetHeaderValues(const std::string& header_name, | |
118 const Array<String>& headers) { | |
119 std::vector<std::string> result; | |
120 for (std::string header : headers.storage()) { | |
121 if (StartsWithASCII(header, header_name, false)) { | |
122 auto begin = header.begin(); | |
123 auto end = header.end(); | |
124 begin += header_name.size(); | |
125 while (begin < end && *begin == ' ') | |
blundell
2015/05/07 11:29:21
Can you use //base/string_util's TrimString() func
qsr
2015/05/07 12:49:02
I could. But this would be a lot less efficient, a
| |
126 begin++; | |
127 if (begin < end && *begin == ':') { | |
128 begin++; | |
129 while (begin < end && *begin == ' ') | |
130 begin++; | |
131 while (end > begin && *(end - 1) == ' ') | |
132 end--; | |
133 if (begin < end) { | |
134 result.push_back(std::string(begin, end)); | |
135 } | |
136 } | |
137 } | |
138 } | |
139 return result; | |
140 } | |
141 | |
142 bool IsCacheEntryValid(const base::FilePath& dir, | |
143 URLResponse* response, | |
144 CacheEntryPtr* output) { | |
145 base::FilePath entry_path = dir.Append("entry"); | |
146 if (!base::PathExists(entry_path)) | |
147 return false; | |
148 std::string serialized_entry; | |
149 if (!ReadFileToString(entry_path, &serialized_entry)) | |
150 return false; | |
151 CacheEntryPtr entry; | |
152 Deserialize(serialized_entry, &entry); | |
153 if (entry->headers.is_null() || response->headers.is_null()) | |
154 return false; | |
155 | |
156 // Only handle etag for the moment. | |
157 std::string etag_header_name = kEtagHeader; | |
158 std::vector<std::string> entry_etags = | |
159 GetHeaderValues(etag_header_name, entry->headers); | |
160 if (entry_etags.size() == 0) | |
161 return false; | |
162 std::vector<std::string> response_etags = | |
163 GetHeaderValues(etag_header_name, response->headers); | |
164 if (response_etags.size() == 0) | |
165 return false; | |
166 | |
167 // Looking for the first etag header. | |
blundell
2015/05/07 11:29:21
Will there ever be more than 1 etag header? What's
qsr
2015/05/07 12:49:02
There is no defined semantic when there is more th
| |
168 bool result = entry_etags[0] == response_etags[0]; | |
169 | |
170 if (output) | |
171 *output = entry.Pass(); | |
172 return result; | |
173 } | |
174 | |
175 } // namespace | |
176 | |
177 ServiceCacheImpl::ServiceCacheImpl( | |
178 scoped_refptr<base::SequencedWorkerPool> worker_pool, | |
179 const std::string& remote_application_url, | |
180 InterfaceRequest<ServiceCache> request) | |
181 : worker_pool_(worker_pool), binding_(this, request.Pass()) { | |
182 base_directory_ = GetBaseDirectory(); | |
183 if (remote_application_url != "") { | |
184 base_directory_ = base_directory_.Append( | |
185 EncodeString(GURL(remote_application_url).GetOrigin().spec())); | |
186 } | |
187 } | |
188 | |
189 ServiceCacheImpl::~ServiceCacheImpl() { | |
190 } | |
191 | |
192 void ServiceCacheImpl::GetFile(URLResponsePtr response, | |
193 const GetFileCallback& callback) { | |
194 return GetFileInternal(response.Pass(), | |
195 base::Bind(&RunMojoCallback, callback)); | |
196 } | |
197 | |
198 void ServiceCacheImpl::GetExtractedContent( | |
199 URLResponsePtr response, | |
200 const GetExtractedContentCallback& callback) { | |
201 base::FilePath dir = GetDirName(base_directory_, response->url); | |
202 if (dir.empty()) { | |
blundell
2015/05/07 11:29:21
Why won't this be the case the first time that thi
blundell
2015/05/07 11:33:20
OK, in the case of a content-handled application i
qsr
2015/05/07 12:49:02
Removed this test as this cannot happen. empty() h
qsr
2015/05/07 12:49:02
Acknowledged.
| |
203 callback.Run(Array<uint8_t>(), Array<uint8_t>()); | |
204 return; | |
205 } | |
206 base::FilePath extracted_dir = dir.Append("extracted"); | |
207 if (IsCacheEntryValid(dir, response.get(), nullptr) && | |
208 PathExists(GetExtractedSentinel(dir))) { | |
209 callback.Run(PathToArray(extracted_dir), PathToArray(dir)); | |
210 return; | |
211 } | |
212 | |
213 GetFileInternal( | |
214 response.Pass(), | |
215 base::Bind(&ServiceCacheImpl::GetExtractedContentInternal, | |
216 base::Unretained(this), base::Bind(&RunMojoCallback, callback), | |
217 extracted_dir)); | |
218 } | |
219 | |
220 void ServiceCacheImpl::GetFileInternal(URLResponsePtr response, | |
221 const FilePathPairCallback& callback) { | |
222 base::FilePath dir = GetDirName(base_directory_, response->url); | |
223 CacheEntryPtr entry; | |
224 if (IsCacheEntryValid(dir, response.get(), &entry)) { | |
225 callback.Run(base::FilePath(entry->content_path), | |
226 GetConsumerCacheDirectory(dir)); | |
227 return; | |
228 } | |
229 if (base::PathExists(dir)) { | |
230 // Clear cached data. | |
231 base::FilePath to_delete; | |
232 CHECK(CreateTemporaryDirInDir(base_directory_, "to_delete", &to_delete)); | |
233 CHECK(Move(dir, to_delete)); | |
234 worker_pool_->PostTask( | |
235 FROM_HERE, | |
236 base::Bind(base::IgnoreResult(&base::DeleteFile), to_delete, true)); | |
237 } | |
238 if (!response->body.is_valid() || | |
239 !base::CreateDirectoryAndGetError(dir, nullptr) || | |
240 !base::CreateDirectoryAndGetError(GetConsumerCacheDirectory(dir), | |
241 nullptr)) { | |
242 callback.Run(base::FilePath(), base::FilePath()); | |
243 return; | |
244 } | |
245 base::FilePath entry_path = dir.Append("entry"); | |
246 base::FilePath content; | |
247 CHECK(CreateTemporaryFileInDir(dir, &content)); | |
248 entry = CacheEntry::New(); | |
249 entry->url = response->url; | |
250 entry->content_path = content.value(); | |
251 entry->headers = response->headers.Pass(); | |
252 common::CopyToFile(response->body.Pass(), content, worker_pool_.get(), | |
253 base::Bind(&RunCallbackWithSuccess, callback, content, | |
254 GetConsumerCacheDirectory(dir), entry_path, | |
255 base::Passed(entry.Pass()))); | |
256 } | |
257 | |
258 void ServiceCacheImpl::GetExtractedContentInternal( | |
259 const FilePathPairCallback& callback, | |
260 const base::FilePath& extracted_dir, | |
261 const base::FilePath& content, | |
262 const base::FilePath& dir) { | |
263 if (content.empty()) { | |
264 callback.Run(base::FilePath(), base::FilePath()); | |
265 return; | |
266 } | |
267 | |
268 zip::ZipReader reader; | |
269 if (!reader.Open(content)) { | |
270 callback.Run(base::FilePath(), base::FilePath()); | |
271 return; | |
272 } | |
273 while (reader.HasMore()) { | |
274 bool success = reader.OpenCurrentEntryInZip(); | |
275 success = success && reader.ExtractCurrentEntryIntoDirectory(extracted_dir); | |
276 success = success && reader.AdvanceToNextEntry(); | |
277 if (!success) { | |
278 callback.Run(base::FilePath(), base::FilePath()); | |
279 return; | |
280 } | |
281 } | |
282 // We can ignore error, as it will just force to clear the cache on the next | |
283 // request. | |
284 WriteFile(GetExtractedSentinel(dir), nullptr, 0); | |
285 callback.Run(extracted_dir, GetConsumerCacheDirectory(dir)); | |
286 } | |
287 | |
288 } // namespace service_cache | |
289 } // namespace mojo | |
OLD | NEW |