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); | |
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 return base_directory.Append(EncodeString(url)); | |
79 } | |
80 | |
81 base::FilePath GetConsumerCacheDirectory(const base::FilePath& main_cache) { | |
82 return main_cache.Append("consumer_cache"); | |
83 } | |
84 | |
85 base::FilePath GetExtractedSentinel(const base::FilePath& main_cache) { | |
86 return main_cache.Append("extracted_sentinel"); | |
87 } | |
88 | |
89 void RunCallbackWithSucess( | |
DaveMoore
2015/05/06 22:31:21
nit: Should be RunCallbackWithSuccess
qsr
2015/05/07 08:40:54
Done.
| |
90 const ServiceCacheImpl::FilePathPairCallback& callback, | |
91 const base::FilePath& content_path, | |
92 const base::FilePath& cache_dir, | |
93 const base::FilePath& entry_path, | |
94 CacheEntryPtr entry, | |
95 bool success) { | |
96 if (!success) { | |
97 callback.Run(base::FilePath(), base::FilePath()); | |
98 return; | |
99 } | |
100 std::string serialize_entry; | |
101 Serialize(entry.Pass(), &serialize_entry); | |
102 // We can ignore error, as it will just force to clear the cache on the next | |
103 // request. | |
104 WriteFile(entry_path, serialize_entry.data(), serialize_entry.size()); | |
105 callback.Run(content_path, cache_dir); | |
106 } | |
107 | |
108 void RunMojoCallback( | |
109 const Callback<void(Array<uint8_t>, Array<uint8_t>)>& callback, | |
110 const base::FilePath& path1, | |
111 const base::FilePath& path2) { | |
112 callback.Run(PathToArray(path1), PathToArray(path2)); | |
113 } | |
114 | |
115 std::vector<std::string> GetHeaderValues(const std::string& header_name, | |
116 const Array<String>& headers) { | |
117 std::vector<std::string> result; | |
118 for (std::string header : headers.storage()) { | |
119 if (StartsWithASCII(header, header_name, false)) { | |
120 auto begin = header.begin(); | |
121 auto end = header.end(); | |
122 begin += header_name.size(); | |
123 while (begin < end && *begin == ' ') | |
124 begin++; | |
125 if (begin < end && *begin == ':') { | |
126 begin++; | |
127 while (begin < end && *begin == ' ') | |
128 begin++; | |
129 while (end > begin && *(end - 1) == ' ') | |
130 end--; | |
131 if (begin < end) { | |
132 result.push_back(std::string(begin, end)); | |
133 } | |
134 } | |
135 } | |
136 } | |
137 return result; | |
138 } | |
139 | |
140 bool IsCacheEntryValid(const base::FilePath& dir, | |
141 URLResponse* response, | |
142 CacheEntryPtr* output) { | |
143 base::FilePath entry_path = dir.Append("entry"); | |
144 if (!base::PathExists(entry_path)) | |
145 return false; | |
146 std::string serialized_entry; | |
147 if (!ReadFileToString(entry_path, &serialized_entry)) | |
DaveMoore
2015/05/06 22:31:21
Is it worth reading the whole file up front? Doesn
qsr
2015/05/07 08:40:54
I don't expect this file to be very long. In pract
| |
148 return false; | |
149 CacheEntryPtr entry; | |
150 Deserialize(serialized_entry, &entry); | |
151 if (entry->headers.is_null() || response->headers.is_null()) | |
152 return false; | |
153 | |
154 // Only handle etag for the moment. | |
155 std::string etag_header_name = kEtagHeader; | |
156 std::vector<std::string> entry_etags = | |
157 GetHeaderValues(etag_header_name, entry->headers); | |
158 if (entry_etags.size() == 0) | |
159 return false; | |
160 std::vector<std::string> response_etags = | |
161 GetHeaderValues(etag_header_name, response->headers); | |
162 if (response_etags.size() == 0) | |
163 return false; | |
164 | |
165 // Looking for the first etag header. | |
166 bool result = entry_etags[0] == response_etags[0]; | |
167 | |
168 if (output) | |
169 *output = entry.Pass(); | |
170 return result; | |
171 } | |
172 | |
173 } // namespace | |
174 | |
175 ServiceCacheImpl::ServiceCacheImpl( | |
176 scoped_refptr<base::SequencedWorkerPool> worker_pool, | |
177 const std::string& remote_application_url, | |
178 InterfaceRequest<ServiceCache> request) | |
179 : worker_pool_(worker_pool), binding_(this, request.Pass()) { | |
180 base_directory_ = GetBaseDirectory(); | |
181 if (remote_application_url != "") { | |
182 base_directory_ = base_directory_.Append( | |
183 EncodeString(GURL(remote_application_url).GetOrigin().spec())); | |
184 } | |
185 } | |
186 | |
187 ServiceCacheImpl::~ServiceCacheImpl() { | |
188 } | |
189 | |
190 void ServiceCacheImpl::GetFile(URLResponsePtr response, | |
191 const GetFileCallback& callback) { | |
192 return GetFileInternal(response.Pass(), | |
193 base::Bind(&RunMojoCallback, callback)); | |
194 } | |
195 | |
196 void ServiceCacheImpl::GetExtractedContent( | |
197 URLResponsePtr response, | |
198 const GetExtractedContentCallback& callback) { | |
199 base::FilePath dir = GetDirName(base_directory_, response->url); | |
200 if (dir.empty()) { | |
201 callback.Run(Array<uint8_t>(), Array<uint8_t>()); | |
202 return; | |
203 } | |
204 base::FilePath extracted_dir = dir.Append("extracted"); | |
205 if (IsCacheEntryValid(dir, response.get(), nullptr) && | |
206 PathExists(GetExtractedSentinel(dir))) { | |
207 callback.Run(PathToArray(extracted_dir), PathToArray(dir)); | |
208 return; | |
209 } | |
210 | |
211 GetFileInternal( | |
212 response.Pass(), | |
213 base::Bind(&ServiceCacheImpl::GetExtractedContentInternal, | |
214 base::Unretained(this), base::Bind(&RunMojoCallback, callback), | |
215 extracted_dir)); | |
216 } | |
217 | |
218 void ServiceCacheImpl::GetFileInternal(URLResponsePtr response, | |
219 const FilePathPairCallback& callback) { | |
220 base::FilePath dir = GetDirName(base_directory_, response->url); | |
DaveMoore
2015/05/06 22:31:21
I'm not sure about the speed of directory traversa
qsr
2015/05/07 08:40:54
Done.
| |
221 if (dir.empty()) { | |
DaveMoore
2015/05/06 22:31:21
Why would the GetDirName() fail?
qsr
2015/05/07 08:40:54
First version was creating the directory if it did
| |
222 callback.Run(base::FilePath(), base::FilePath()); | |
223 return; | |
224 } | |
225 CacheEntryPtr entry; | |
226 if (IsCacheEntryValid(dir, response.get(), &entry)) { | |
227 callback.Run(base::FilePath(entry->content_path), | |
228 GetConsumerCacheDirectory(dir)); | |
229 return; | |
230 } | |
231 if (base::PathExists(dir)) { | |
232 // Clear cached data. | |
233 base::FilePath to_delete; | |
234 CHECK(CreateTemporaryDirInDir(base_directory_, "to_delete", &to_delete)); | |
235 CHECK(Move(dir, to_delete)); | |
236 worker_pool_->PostTask( | |
237 FROM_HERE, | |
238 base::Bind(base::IgnoreResult(&base::DeleteFile), to_delete, true)); | |
239 } | |
240 if (!response->body.is_valid() || | |
241 !base::CreateDirectoryAndGetError(dir, nullptr) || | |
242 !base::CreateDirectoryAndGetError(GetConsumerCacheDirectory(dir), | |
243 nullptr)) { | |
244 callback.Run(base::FilePath(), base::FilePath()); | |
245 return; | |
246 } | |
247 base::FilePath entry_path = dir.Append("entry"); | |
248 base::FilePath content; | |
249 CHECK(CreateTemporaryFileInDir(dir, &content)); | |
250 entry = CacheEntry::New(); | |
251 entry->url = response->url; | |
252 entry->content_path = content.value(); | |
253 entry->headers = response->headers.Pass(); | |
254 common::CopyToFile(response->body.Pass(), content, worker_pool_.get(), | |
255 base::Bind(&RunCallbackWithSucess, callback, content, | |
256 GetConsumerCacheDirectory(dir), entry_path, | |
257 base::Passed(entry.Pass()))); | |
258 } | |
259 | |
260 void ServiceCacheImpl::GetExtractedContentInternal( | |
261 const FilePathPairCallback& callback, | |
262 const base::FilePath& extracted_dir, | |
263 const base::FilePath& content, | |
264 const base::FilePath& dir) { | |
265 if (content.empty()) { | |
266 callback.Run(base::FilePath(), base::FilePath()); | |
267 return; | |
268 } | |
269 | |
270 zip::ZipReader reader; | |
271 if (!reader.Open(content)) { | |
272 callback.Run(base::FilePath(), base::FilePath()); | |
273 return; | |
274 } | |
275 while (reader.HasMore()) { | |
276 bool success = reader.OpenCurrentEntryInZip(); | |
277 success = success && reader.ExtractCurrentEntryIntoDirectory(extracted_dir); | |
278 success = success && reader.AdvanceToNextEntry(); | |
279 if (!success) { | |
280 callback.Run(base::FilePath(), base::FilePath()); | |
281 return; | |
282 } | |
283 } | |
284 // We can ignore error, as it will just force to clear the cache on the next | |
285 // request. | |
286 WriteFile(GetExtractedSentinel(dir), nullptr, 0); | |
287 callback.Run(extracted_dir, GetConsumerCacheDirectory(dir)); | |
288 } | |
289 | |
290 } // namespace service_cache | |
291 } // namespace mojo | |
OLD | NEW |