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/url_response_disk_cache/url_response_disk_cache_impl.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/files/file_util.h" | |
9 #include "base/files/important_file_writer.h" | |
10 #include "base/location.h" | |
11 #include "base/logging.h" | |
12 #include "base/strings/string_util.h" | |
13 #include "base/strings/stringprintf.h" | |
14 #include "mojo/common/data_pipe_utils.h" | |
15 #include "mojo/public/cpp/application/application_connection.h" | |
16 #include "mojo/public/cpp/bindings/lib/fixed_buffer.h" | |
17 #include "services/url_response_disk_cache/url_response_disk_cache_entry.mojom.h " | |
18 #include "third_party/zlib/google/zip_reader.h" | |
19 #include "url/gurl.h" | |
20 | |
21 namespace mojo { | |
22 namespace url_response_disk_cache { | |
23 | |
24 namespace { | |
25 | |
26 const char kEtagHeader[] = "etag"; | |
27 | |
28 template <typename T> | |
29 void Serialize(T input, std::string* output) { | |
30 typedef typename mojo::internal::WrapperTraits<T>::DataType DataType; | |
31 size_t size = GetSerializedSize_(input); | |
32 mojo::internal::FixedBuffer buf(size); | |
33 DataType data_type; | |
34 Serialize_(input.Pass(), &buf, &data_type); | |
35 std::vector<Handle> handles; | |
36 data_type->EncodePointersAndHandles(&handles); | |
37 void* serialized_data = buf.Leak(); | |
38 *output = std::string(static_cast<char*>(serialized_data), size); | |
39 free(serialized_data); | |
40 } | |
41 | |
42 template <typename T> | |
43 void Deserialize(std::string input, T* output) { | |
44 typedef typename mojo::internal::WrapperTraits<T>::DataType DataType; | |
45 DataType data_type = reinterpret_cast<DataType>(&input[0]); | |
46 std::vector<Handle> handles; | |
47 data_type->DecodePointersAndHandles(&handles); | |
48 Deserialize_(data_type, output); | |
49 } | |
50 | |
51 Array<uint8_t> PathToArray(const base::FilePath& path) { | |
52 if (path.empty()) | |
53 return Array<uint8_t>(); | |
54 const std::string& string = path.value(); | |
55 Array<uint8_t> result(string.size()); | |
56 memcpy(&result.front(), string.data(), string.size()); | |
57 return result.Pass(); | |
58 } | |
59 | |
60 // Encode a string in ascii. This uses # as an escape character. It also escapes | |
61 // ':' because it is an usual path separator. | |
62 std::string EncodeString(const std::string& string) { | |
63 std::string result = ""; | |
64 for (size_t i = 0; i < string.size(); ++i) { | |
65 unsigned char c = string[i]; | |
66 if (c >= 32 && c < 128 && c != '#' && c != ':') { | |
67 result += c; | |
68 } else { | |
69 result += base::StringPrintf("#%02x", c); | |
70 } | |
71 } | |
72 return result; | |
73 } | |
74 | |
75 // This service use a directory under HOME to store all of its data, | |
76 base::FilePath GetBaseDirectory() { | |
77 return base::FilePath(getenv("HOME")).Append(".mojo_url_response_disk_cache"); | |
78 } | |
79 | |
80 // Returns the directory used store cached data for the given |url|, under | |
81 // |base_directory|. | |
82 base::FilePath GetDirName(base::FilePath base_directory, | |
83 const std::string& url) { | |
84 // TODO(qsr): If the speed of directory traversal is problematic, this might | |
85 // need to change to use less directories. | |
86 return base_directory.Append(EncodeString(url)); | |
87 } | |
88 | |
89 // Returns the directory that the consumer can use to cache its own data. | |
90 base::FilePath GetConsumerCacheDirectory(const base::FilePath& main_cache) { | |
91 return main_cache.Append("consumer_cache"); | |
92 } | |
93 | |
94 // Returns the path of the sentinel used to keep track of a zipped response has | |
95 // already been extracted. | |
96 base::FilePath GetExtractedSentinel(const base::FilePath& main_cache) { | |
97 return main_cache.Append("extracted_sentinel"); | |
98 } | |
99 | |
100 // Runs the given callback. If |success| is false, call back with an error. | |
101 // Otherwise, store |entry| in |entry_path|, then call back with the given | |
102 // paths. | |
103 void RunCallbackWithSuccess( | |
104 const URLResponseDiskCacheImpl::FilePathPairCallback& callback, | |
105 const base::FilePath& content_path, | |
106 const base::FilePath& cache_dir, | |
107 const base::FilePath& entry_path, | |
108 CacheEntryPtr entry, | |
109 bool success) { | |
110 if (!success) { | |
111 callback.Run(base::FilePath(), base::FilePath()); | |
112 return; | |
113 } | |
114 std::string serialized_entry; | |
115 Serialize(entry.Pass(), &serialized_entry); | |
116 // We can ignore write error, as it will just force to clear the cache on the | |
117 // next request. | |
118 base::ImportantFileWriter::WriteFileAtomically(entry_path, serialized_entry); | |
119 callback.Run(content_path, cache_dir); | |
120 } | |
121 | |
122 // Run the given mojo callback with the given paths. | |
123 void RunMojoCallback( | |
124 const Callback<void(Array<uint8_t>, Array<uint8_t>)>& callback, | |
125 const base::FilePath& path1, | |
126 const base::FilePath& path2) { | |
127 callback.Run(PathToArray(path1), PathToArray(path2)); | |
128 } | |
129 | |
130 // Returns the list of values for the given |header_name| in the given list of | |
131 // headers. | |
132 std::vector<std::string> GetHeaderValues(const std::string& header_name, | |
133 const Array<String>& headers) { | |
134 std::vector<std::string> result; | |
135 for (std::string header : headers.storage()) { | |
136 if (StartsWithASCII(header, header_name, false)) { | |
137 auto begin = header.begin(); | |
138 auto end = header.end(); | |
139 begin += header_name.size(); | |
140 // Extract the content of the header by finding the remaining string after | |
141 // the ':' and stripping all spaces. | |
142 while (begin < end && *begin != ':') | |
143 begin++; | |
144 if (begin < end) { | |
145 begin++; | |
146 while (begin < end && *begin == ' ') | |
147 begin++; | |
148 while (end > begin && *(end - 1) == ' ') | |
149 end--; | |
150 if (begin < end) { | |
151 result.push_back(std::string(begin, end)); | |
152 } | |
153 } | |
154 } | |
155 } | |
156 return result; | |
157 } | |
158 | |
159 // Returns whether the given directory |dir| constains a valid entry file for | |
160 // the given |response|. If this is the case and |output| is not |nullptr|, then | |
161 // the deserialized entry is returned in |*output|. | |
162 bool IsCacheEntryValid(const base::FilePath& dir, | |
163 URLResponse* response, | |
164 CacheEntryPtr* output) { | |
165 // Find the entry file, and deserialize it. | |
166 base::FilePath entry_path = dir.Append("entry"); | |
167 if (!base::PathExists(entry_path)) | |
168 return false; | |
169 std::string serialized_entry; | |
170 if (!ReadFileToString(entry_path, &serialized_entry)) | |
171 return false; | |
172 CacheEntryPtr entry; | |
173 Deserialize(serialized_entry, &entry); | |
174 | |
175 // If |entry| or |response| has not headers, it is not possible to check if | |
176 // the entry is valid, so returns |false|. | |
177 if (entry->headers.is_null() || response->headers.is_null()) | |
178 return false; | |
179 | |
180 // Only handle etag for the moment. | |
181 std::string etag_header_name = kEtagHeader; | |
182 std::vector<std::string> entry_etags = | |
183 GetHeaderValues(etag_header_name, entry->headers); | |
184 if (entry_etags.size() == 0) | |
185 return false; | |
186 std::vector<std::string> response_etags = | |
187 GetHeaderValues(etag_header_name, response->headers); | |
188 if (response_etags.size() == 0) | |
189 return false; | |
190 | |
191 // Looking for the first etag header. | |
192 bool result = entry_etags[0] == response_etags[0]; | |
193 | |
194 // Returns |entry| is requested. | |
blundell
2015/05/07 13:57:59
nit: s/is/if
qsr
2015/05/07 14:18:07
Done.
| |
195 if (output) | |
196 *output = entry.Pass(); | |
197 | |
198 return result; | |
199 } | |
200 | |
201 } // namespace | |
202 | |
203 URLResponseDiskCacheImpl::URLResponseDiskCacheImpl( | |
204 scoped_refptr<base::SequencedWorkerPool> worker_pool, | |
205 const std::string& remote_application_url, | |
206 InterfaceRequest<URLResponseDiskCache> request) | |
207 : worker_pool_(worker_pool), binding_(this, request.Pass()) { | |
208 base_directory_ = GetBaseDirectory(); | |
209 // The cached files are shared only for application of the same origin. | |
210 if (remote_application_url != "") { | |
211 base_directory_ = base_directory_.Append( | |
212 EncodeString(GURL(remote_application_url).GetOrigin().spec())); | |
213 } | |
214 } | |
215 | |
216 URLResponseDiskCacheImpl::~URLResponseDiskCacheImpl() { | |
217 } | |
218 | |
219 void URLResponseDiskCacheImpl::GetFile(URLResponsePtr response, | |
220 const GetFileCallback& callback) { | |
221 return GetFileInternal(response.Pass(), | |
222 base::Bind(&RunMojoCallback, callback)); | |
223 } | |
224 | |
225 void URLResponseDiskCacheImpl::GetExtractedContent( | |
226 URLResponsePtr response, | |
227 const GetExtractedContentCallback& callback) { | |
228 base::FilePath dir = GetDirName(base_directory_, response->url); | |
229 base::FilePath extracted_dir = dir.Append("extracted"); | |
230 if (IsCacheEntryValid(dir, response.get(), nullptr) && | |
231 PathExists(GetExtractedSentinel(dir))) { | |
232 callback.Run(PathToArray(extracted_dir), PathToArray(dir)); | |
233 return; | |
234 } | |
235 | |
236 GetFileInternal( | |
237 response.Pass(), | |
238 base::Bind(&URLResponseDiskCacheImpl::GetExtractedContentInternal, | |
239 base::Unretained(this), base::Bind(&RunMojoCallback, callback), | |
240 extracted_dir)); | |
241 } | |
242 | |
243 void URLResponseDiskCacheImpl::GetFileInternal( | |
244 URLResponsePtr response, | |
245 const FilePathPairCallback& callback) { | |
246 base::FilePath dir = GetDirName(base_directory_, response->url); | |
247 | |
248 // Check if the response is cached and valid. If that's the case, returns the | |
249 // cached value. | |
250 CacheEntryPtr entry; | |
251 if (IsCacheEntryValid(dir, response.get(), &entry)) { | |
252 callback.Run(base::FilePath(entry->content_path), | |
253 GetConsumerCacheDirectory(dir)); | |
254 return; | |
255 } | |
256 | |
257 // As the response was either not cached or the cached value is not valid, if | |
258 // the cache directory for the response exists, it needs to be cleaned. | |
259 if (base::PathExists(dir)) { | |
260 base::FilePath to_delete; | |
261 CHECK(CreateTemporaryDirInDir(base_directory_, "to_delete", &to_delete)); | |
262 CHECK(Move(dir, to_delete)); | |
263 worker_pool_->PostTask( | |
264 FROM_HERE, | |
265 base::Bind(base::IgnoreResult(&base::DeleteFile), to_delete, true)); | |
266 } | |
267 | |
268 // If the response has not a valid body, and it is not possible to create | |
269 // either the cache directory or the consumer cache directory, returns an | |
270 // error. | |
271 if (!response->body.is_valid() || | |
272 !base::CreateDirectoryAndGetError(dir, nullptr) || | |
273 !base::CreateDirectoryAndGetError(GetConsumerCacheDirectory(dir), | |
274 nullptr)) { | |
275 callback.Run(base::FilePath(), base::FilePath()); | |
276 return; | |
277 } | |
278 | |
279 // Fill the entry values for the request. | |
280 base::FilePath entry_path = dir.Append("entry"); | |
281 base::FilePath content; | |
282 CHECK(CreateTemporaryFileInDir(dir, &content)); | |
283 entry = CacheEntry::New(); | |
284 entry->url = response->url; | |
285 entry->content_path = content.value(); | |
286 entry->headers = response->headers.Pass(); | |
287 // Asynchronously copy the response body to the cached file. The entry is send | |
288 // to the callback so that it is saved on disk only if the copy of the body | |
289 // succeded. | |
290 common::CopyToFile(response->body.Pass(), content, worker_pool_.get(), | |
291 base::Bind(&RunCallbackWithSuccess, callback, content, | |
292 GetConsumerCacheDirectory(dir), entry_path, | |
293 base::Passed(entry.Pass()))); | |
294 } | |
295 | |
296 void URLResponseDiskCacheImpl::GetExtractedContentInternal( | |
297 const FilePathPairCallback& callback, | |
298 const base::FilePath& extracted_dir, | |
299 const base::FilePath& content, | |
300 const base::FilePath& dir) { | |
301 // If it is not possible to get the cached file, returns an error. | |
302 if (content.empty()) { | |
303 callback.Run(base::FilePath(), base::FilePath()); | |
304 return; | |
305 } | |
306 | |
307 // Unzip the content to the extracted directory. In case of any error, returns | |
308 // an error. | |
309 zip::ZipReader reader; | |
310 if (!reader.Open(content)) { | |
311 callback.Run(base::FilePath(), base::FilePath()); | |
312 return; | |
313 } | |
314 while (reader.HasMore()) { | |
315 bool success = reader.OpenCurrentEntryInZip(); | |
316 success = success && reader.ExtractCurrentEntryIntoDirectory(extracted_dir); | |
317 success = success && reader.AdvanceToNextEntry(); | |
318 if (!success) { | |
319 callback.Run(base::FilePath(), base::FilePath()); | |
320 return; | |
321 } | |
322 } | |
323 // We can ignore write error, as it will just force to clear the cache on the | |
324 // next request. | |
325 WriteFile(GetExtractedSentinel(dir), nullptr, 0); | |
326 callback.Run(extracted_dir, GetConsumerCacheDirectory(dir)); | |
327 } | |
328 | |
329 } // namespace url_response_disk_cache | |
330 } // namespace mojo | |
OLD | NEW |