blob: 05aba7ab3605b5837b5acdb53e789b03f50c0246 [file] [log] [blame] [edit]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shell/application_manager/network_fetcher.h"
#include "base/bind.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/message_loop/message_loop.h"
#include "base/process/process.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "crypto/secure_hash.h"
#include "crypto/sha2.h"
#include "mojo/common/common_type_converters.h"
#include "mojo/common/data_pipe_utils.h"
#include "mojo/services/network/public/interfaces/network_service.mojom.h"
#include "shell/application_manager/data_pipe_peek.h"
namespace mojo {
namespace shell {
NetworkFetcher::NetworkFetcher(bool disable_cache,
const GURL& url,
NetworkService* network_service,
const FetchCallback& loader_callback)
: Fetcher(loader_callback),
disable_cache_(false),
url_(url),
weak_ptr_factory_(this) {
StartNetworkRequest(url, network_service);
}
NetworkFetcher::~NetworkFetcher() {
}
const GURL& NetworkFetcher::GetURL() const {
return url_;
}
GURL NetworkFetcher::GetRedirectURL() const {
if (!response_)
return GURL::EmptyGURL();
if (response_->redirect_url.is_null())
return GURL::EmptyGURL();
return GURL(response_->redirect_url);
}
URLResponsePtr NetworkFetcher::AsURLResponse(base::TaskRunner* task_runner,
uint32_t skip) {
if (skip != 0) {
MojoResult result = ReadDataRaw(
response_->body.get(), nullptr, &skip,
MOJO_READ_DATA_FLAG_ALL_OR_NONE | MOJO_READ_DATA_FLAG_DISCARD);
DCHECK_EQ(result, MOJO_RESULT_OK);
}
return response_.Pass();
}
void NetworkFetcher::RecordCacheToURLMapping(const base::FilePath& path,
const GURL& url) {
// This is used to extract symbols on android.
// TODO(eseidel): All users of this log should move to using the map file.
LOG(INFO) << "Caching mojo app " << url << " at " << path.value();
base::FilePath temp_dir;
base::GetTempDir(&temp_dir);
base::ProcessId pid = base::Process::Current().Pid();
std::string map_name = base::StringPrintf("mojo_shell.%d.maps", pid);
base::FilePath map_path = temp_dir.Append(map_name);
// TODO(eseidel): Paths or URLs with spaces will need quoting.
std::string map_entry =
base::StringPrintf("%s %s\n", path.value().c_str(), url.spec().c_str());
// TODO(eseidel): AppendToFile is missing O_CREAT, crbug.com/450696
if (!PathExists(map_path))
base::WriteFile(map_path, map_entry.data(), map_entry.length());
else
base::AppendToFile(map_path, map_entry.data(), map_entry.length());
}
// AppIds should be be both predictable and unique, but any hash would work.
// Currently we use sha256 from crypto/secure_hash.h
bool NetworkFetcher::ComputeAppId(const base::FilePath& path,
std::string* digest_string) {
scoped_ptr<crypto::SecureHash> ctx(
crypto::SecureHash::Create(crypto::SecureHash::SHA256));
base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!file.IsValid()) {
LOG(ERROR) << "Failed to open " << path.value() << " for computing AppId";
return false;
}
char buf[1024];
while (file.IsValid()) {
int bytes_read = file.ReadAtCurrentPos(buf, sizeof(buf));
if (bytes_read == 0)
break;
ctx->Update(buf, bytes_read);
}
if (!file.IsValid()) {
LOG(ERROR) << "Error reading " << path.value();
return false;
}
// The output is really a vector of unit8, we're cheating by using a string.
std::string output(crypto::kSHA256Length, 0);
ctx->Finish(string_as_array(&output), output.size());
output = base::HexEncode(output.c_str(), output.size());
// Using lowercase for compatiblity with sha256sum output.
*digest_string = base::StringToLowerASCII(output);
return true;
}
bool NetworkFetcher::RenameToAppId(const base::FilePath& old_path,
base::FilePath* new_path) {
std::string app_id;
if (!ComputeAppId(old_path, &app_id))
return false;
base::FilePath temp_dir;
base::GetTempDir(&temp_dir);
std::string unique_name = base::StringPrintf("%s.mojo", app_id.c_str());
*new_path = temp_dir.Append(unique_name);
return base::Move(old_path, *new_path);
}
void NetworkFetcher::CopyCompleted(
base::Callback<void(const base::FilePath&, bool)> callback,
bool success) {
// The copy completed, now move to $TMP/$APP_ID.mojo before the dlopen.
if (success) {
success = false;
base::FilePath new_path;
if (RenameToAppId(path_, &new_path)) {
if (base::PathExists(new_path)) {
path_ = new_path;
success = true;
RecordCacheToURLMapping(path_, url_);
}
}
}
base::MessageLoop::current()->PostTask(FROM_HERE,
base::Bind(callback, path_, success));
}
void NetworkFetcher::AsPath(
base::TaskRunner* task_runner,
base::Callback<void(const base::FilePath&, bool)> callback) {
if (!path_.empty() || !response_) {
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(callback, path_, base::PathExists(path_)));
return;
}
base::CreateTemporaryFile(&path_);
common::CopyToFile(response_->body.Pass(), path_, task_runner,
base::Bind(&NetworkFetcher::CopyCompleted,
weak_ptr_factory_.GetWeakPtr(), callback));
}
std::string NetworkFetcher::MimeType() {
return response_->mime_type;
}
bool NetworkFetcher::HasMojoMagic() {
std::string magic;
return BlockingPeekNBytes(response_->body.get(), &magic, strlen(kMojoMagic),
kPeekTimeout) &&
magic == kMojoMagic;
}
bool NetworkFetcher::PeekFirstLine(std::string* line) {
return BlockingPeekLine(response_->body.get(), line, kMaxShebangLength,
kPeekTimeout);
}
void NetworkFetcher::StartNetworkRequest(const GURL& url,
NetworkService* network_service) {
URLRequestPtr request(URLRequest::New());
request->url = String::From(url);
request->auto_follow_redirects = false;
request->bypass_cache = disable_cache_;
network_service->CreateURLLoader(GetProxy(&url_loader_));
url_loader_->Start(request.Pass(),
base::Bind(&NetworkFetcher::OnLoadComplete,
weak_ptr_factory_.GetWeakPtr()));
}
void NetworkFetcher::OnLoadComplete(URLResponsePtr response) {
if (response->error) {
LOG(ERROR) << "Error (" << response->error->code << ": "
<< response->error->description << ") while fetching "
<< response->url;
loader_callback_.Run(make_scoped_ptr<Fetcher>(NULL));
return;
}
if (response->status_code >= 400 && response->status_code < 600) {
LOG(ERROR) << "Error (" << response->status_code << ": "
<< response->status_line << "): "
<< "while fetching " << response->url;
loader_callback_.Run(make_scoped_ptr<Fetcher>(NULL));
return;
}
response_ = response.Pass();
loader_callback_.Run(make_scoped_ptr(this));
}
} // namespace shell
} // namespace mojo