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.
5 #include "mojo/shell/network_fetcher.h"
8 #include "base/command_line.h"
9 #include "base/files/file.h"
10 #include "base/files/file_path.h"
11 #include "base/files/file_util.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/process/process.h"
14 #include "base/stl_util.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/trace_event/trace_event.h"
20 #include "crypto/secure_hash.h"
21 #include "crypto/sha2.h"
22 #include "mojo/common/common_type_converters.h"
23 #include "mojo/common/data_pipe_utils.h"
24 #include "mojo/common/url_type_converters.h"
25 #include "mojo/services/network/public/interfaces/network_service.mojom.h"
26 #include "mojo/shell/data_pipe_peek.h"
27 #include "mojo/shell/switches.h"
32 NetworkFetcher::NetworkFetcher(bool disable_cache
,
34 NetworkService
* network_service
,
35 const FetchCallback
& loader_callback
)
36 : Fetcher(loader_callback
),
37 disable_cache_(false),
39 weak_ptr_factory_(this) {
40 StartNetworkRequest(url
, network_service
);
43 NetworkFetcher::~NetworkFetcher() {
46 const GURL
& NetworkFetcher::GetURL() const {
50 GURL
NetworkFetcher::GetRedirectURL() const {
52 return GURL::EmptyGURL();
54 if (response_
->redirect_url
.is_null())
55 return GURL::EmptyGURL();
57 return GURL(response_
->redirect_url
);
60 URLResponsePtr
NetworkFetcher::AsURLResponse(base::TaskRunner
* task_runner
,
63 MojoResult result
= ReadDataRaw(
64 response_
->body
.get(), nullptr, &skip
,
65 MOJO_READ_DATA_FLAG_ALL_OR_NONE
| MOJO_READ_DATA_FLAG_DISCARD
);
66 DCHECK_EQ(result
, MOJO_RESULT_OK
);
68 return response_
.Pass();
71 void NetworkFetcher::RecordCacheToURLMapping(const base::FilePath
& path
,
73 // This is used to extract symbols on android.
74 // TODO(eseidel): All users of this log should move to using the map file.
75 VLOG(1) << "Caching mojo app " << url
<< " at " << path
.value();
77 base::FilePath temp_dir
;
78 base::GetTempDir(&temp_dir
);
79 base::ProcessId pid
= base::Process::Current().Pid();
80 std::string map_name
= base::StringPrintf("mojo_shell.%d.maps", pid
);
81 base::FilePath map_path
= temp_dir
.AppendASCII(map_name
);
83 // TODO(eseidel): Paths or URLs with spaces will need quoting.
84 std::string map_entry
=
85 base::StringPrintf("%s %s\n", path
.value().c_str(), url
.spec().c_str());
86 // TODO(eseidel): AppendToFile is missing O_CREAT, crbug.com/450696
87 if (!PathExists(map_path
)) {
88 base::WriteFile(map_path
, map_entry
.data(),
89 static_cast<int>(map_entry
.length()));
91 base::AppendToFile(map_path
, map_entry
.data(),
92 static_cast<int>(map_entry
.length()));
96 // For remote debugging, GDB needs to be, a apriori, aware of the filename a
97 // library will be loaded from. AppIds should be be both predictable and unique,
98 // but any hash would work. Currently we use sha256 from crypto/secure_hash.h
99 bool NetworkFetcher::ComputeAppId(const base::FilePath
& path
,
100 std::string
* digest_string
) {
101 scoped_ptr
<crypto::SecureHash
> ctx(
102 crypto::SecureHash::Create(crypto::SecureHash::SHA256
));
103 base::File
file(path
, base::File::FLAG_OPEN
| base::File::FLAG_READ
);
104 if (!file
.IsValid()) {
105 LOG(ERROR
) << "Failed to open " << path
.value() << " for computing AppId";
109 while (file
.IsValid()) {
110 int bytes_read
= file
.ReadAtCurrentPos(buf
, sizeof(buf
));
113 ctx
->Update(buf
, bytes_read
);
115 if (!file
.IsValid()) {
116 LOG(ERROR
) << "Error reading " << path
.value();
119 // The output is really a vector of unit8, we're cheating by using a string.
120 std::string
output(crypto::kSHA256Length
, 0);
121 ctx
->Finish(string_as_array(&output
), output
.size());
122 output
= base::HexEncode(output
.c_str(), output
.size());
123 // Using lowercase for compatiblity with sha256sum output.
124 *digest_string
= base::StringToLowerASCII(output
);
128 bool NetworkFetcher::RenameToAppId(const GURL
& url
,
129 const base::FilePath
& old_path
,
130 base::FilePath
* new_path
) {
132 if (!ComputeAppId(old_path
, &app_id
))
135 // Using a hash of the url as a directory to prevent a race when the same
136 // bytes are downloaded from 2 different urls. In particular, if the same
137 // application is connected to twice concurrently with different query
138 // parameters, the directory will be different, which will prevent the
140 std::string dirname
= base::HexEncode(
141 crypto::SHA256HashString(url
.spec()).data(), crypto::kSHA256Length
);
143 base::FilePath temp_dir
;
144 base::GetTempDir(&temp_dir
);
145 base::FilePath app_dir
= temp_dir
.AppendASCII(dirname
);
146 // The directory is leaked, because it can be reused at any time if the same
147 // application is downloaded. Deleting it would be racy. This is only
148 // happening when --predictable-app-filenames is used.
149 bool result
= base::CreateDirectoryAndGetError(app_dir
, nullptr);
151 std::string unique_name
= base::StringPrintf("%s.mojo", app_id
.c_str());
152 *new_path
= app_dir
.AppendASCII(unique_name
);
153 return base::Move(old_path
, *new_path
);
156 void NetworkFetcher::CopyCompleted(
157 base::Callback
<void(const base::FilePath
&, bool)> callback
,
160 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
161 switches::kPredictableAppFilenames
)) {
162 // The copy completed, now move to $TMP/$APP_ID.mojo before the dlopen.
164 base::FilePath new_path
;
165 if (RenameToAppId(url_
, path_
, &new_path
)) {
166 if (base::PathExists(new_path
)) {
175 RecordCacheToURLMapping(path_
, url_
);
177 base::MessageLoop::current()->PostTask(FROM_HERE
,
178 base::Bind(callback
, path_
, success
));
181 void NetworkFetcher::AsPath(
182 base::TaskRunner
* task_runner
,
183 base::Callback
<void(const base::FilePath
&, bool)> callback
) {
184 if (!path_
.empty() || !response_
) {
185 base::MessageLoop::current()->PostTask(
186 FROM_HERE
, base::Bind(callback
, path_
, base::PathExists(path_
)));
190 base::CreateTemporaryFile(&path_
);
191 common::CopyToFile(response_
->body
.Pass(), path_
, task_runner
,
192 base::Bind(&NetworkFetcher::CopyCompleted
,
193 weak_ptr_factory_
.GetWeakPtr(), callback
));
196 std::string
NetworkFetcher::MimeType() {
197 return response_
->mime_type
;
200 bool NetworkFetcher::HasMojoMagic() {
202 return BlockingPeekNBytes(response_
->body
.get(), &magic
, strlen(kMojoMagic
),
207 bool NetworkFetcher::PeekFirstLine(std::string
* line
) {
208 return BlockingPeekLine(response_
->body
.get(), line
, kMaxShebangLength
,
212 void NetworkFetcher::StartNetworkRequest(const GURL
& url
,
213 NetworkService
* network_service
) {
214 TRACE_EVENT_ASYNC_BEGIN1("mojo_shell", "NetworkFetcher::NetworkRequest", this,
216 URLRequestPtr
request(URLRequest::New());
217 request
->url
= String::From(url
);
218 request
->auto_follow_redirects
= false;
219 request
->bypass_cache
= disable_cache_
;
221 network_service
->CreateURLLoader(GetProxy(&url_loader_
));
222 url_loader_
->Start(request
.Pass(),
223 base::Bind(&NetworkFetcher::OnLoadComplete
,
224 weak_ptr_factory_
.GetWeakPtr()));
227 void NetworkFetcher::OnLoadComplete(URLResponsePtr response
) {
228 TRACE_EVENT_ASYNC_END0("mojo_shell", "NetworkFetcher::NetworkRequest", this);
229 scoped_ptr
<Fetcher
> owner(this);
230 if (response
->error
) {
231 LOG(ERROR
) << "Error (" << response
->error
->code
<< ": "
232 << response
->error
->description
<< ") while fetching "
234 loader_callback_
.Run(nullptr);
238 if (response
->status_code
>= 400 && response
->status_code
< 600) {
239 LOG(ERROR
) << "Error (" << response
->status_code
<< ": "
240 << response
->status_line
<< "): "
241 << "while fetching " << response
->url
;
242 loader_callback_
.Run(nullptr);
246 response_
= response
.Pass();
247 loader_callback_
.Run(owner
.Pass());