blob: c76b106499bdc82d522ab986eff298b29e0f33f1 [file] [log] [blame]
// Copyright 2013 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 "chrome/browser/local_discovery/privet_http_impl.h"
#include "base/bind.h"
#include "base/message_loop/message_loop.h"
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/local_discovery/privet_constants.h"
#include "url/gurl.h"
namespace local_discovery {
namespace {
// First format argument (string) is the host, second format argument (int) is
// the port.
const char kPrivetInfoURLFormat[] = "http://%s:%d/privet/info";
// First format argument (string) is the host, second format argument (int) is
// the port, third argument (string) is the action name, fourth argument
// (string) is the user name.
const char kPrivetRegisterURLFormat[] =
"http://%s:%d/privet/register?action=%s&user=%s";
} // namespace
PrivetInfoOperationImpl::PrivetInfoOperationImpl(
PrivetHTTPClientImpl* privet_client,
PrivetInfoOperation::Delegate* delegate)
: privet_client_(privet_client), delegate_(delegate) {
}
PrivetInfoOperationImpl::~PrivetInfoOperationImpl() {
}
void PrivetInfoOperationImpl::Start() {
std::string url = base::StringPrintf(
kPrivetInfoURLFormat,
privet_client_->host_port().host().c_str(),
privet_client_->host_port().port());
url_fetcher_ = privet_client_->fetcher_factory().CreateURLFetcher(
GURL(url), net::URLFetcher::GET, this);
url_fetcher_->Start();
}
PrivetHTTPClient* PrivetInfoOperationImpl::GetHTTPClient() {
return privet_client_;
}
void PrivetInfoOperationImpl::OnError(PrivetURLFetcher* fetcher,
PrivetURLFetcher::ErrorType error) {
if (error == PrivetURLFetcher::RESPONSE_CODE_ERROR) {
delegate_->OnPrivetInfoDone(this, fetcher->response_code(), NULL);
} else {
delegate_->OnPrivetInfoDone(this, kPrivetHTTPCodeInternalFailure, NULL);
}
}
void PrivetInfoOperationImpl::OnParsedJson(PrivetURLFetcher* fetcher,
const base::DictionaryValue* value,
bool has_error) {
if (!has_error)
privet_client_->CacheInfo(value);
delegate_->OnPrivetInfoDone(this, fetcher->response_code(), value);
}
PrivetRegisterOperationImpl::PrivetRegisterOperationImpl(
PrivetHTTPClientImpl* privet_client,
const std::string& user,
PrivetRegisterOperation::Delegate* delegate)
: user_(user), delegate_(delegate), privet_client_(privet_client),
ongoing_(false) {
}
PrivetRegisterOperationImpl::~PrivetRegisterOperationImpl() {
}
void PrivetRegisterOperationImpl::Start() {
if (privet_client_->fetcher_factory().get_token() == "") {
StartInfoOperation();
return;
}
ongoing_ = true;
next_response_handler_ =
base::Bind(&PrivetRegisterOperationImpl::StartResponse,
base::Unretained(this));
SendRequest(kPrivetActionStart);
}
void PrivetRegisterOperationImpl::Cancel() {
url_fetcher_.reset();
// TODO(noamsml): Proper cancelation.
}
void PrivetRegisterOperationImpl::CompleteRegistration() {
next_response_handler_ =
base::Bind(&PrivetRegisterOperationImpl::CompleteResponse,
base::Unretained(this));
SendRequest(kPrivetActionComplete);
}
PrivetHTTPClient* PrivetRegisterOperationImpl::GetHTTPClient() {
return privet_client_;
}
void PrivetRegisterOperationImpl::OnError(PrivetURLFetcher* fetcher,
PrivetURLFetcher::ErrorType error) {
ongoing_ = false;
int visible_http_code = -1;
FailureReason reason = FAILURE_NETWORK;
if (error == PrivetURLFetcher::RESPONSE_CODE_ERROR) {
visible_http_code = fetcher->response_code();
reason = FAILURE_HTTP_ERROR;
} else if (error == PrivetURLFetcher::JSON_PARSE_ERROR) {
reason = FAILURE_MALFORMED_RESPONSE;
}
delegate_->OnPrivetRegisterError(this,
current_action_,
reason,
visible_http_code,
NULL);
}
void PrivetRegisterOperationImpl::OnParsedJson(
PrivetURLFetcher* fetcher,
const base::DictionaryValue* value,
bool has_error) {
if (has_error) {
std::string error;
value->GetString(kPrivetKeyError, &error);
if (error == kPrivetErrorInvalidXPrivetToken) {
StartInfoOperation();
// Use a list of transient error names, but also detect if a "timeout"
// key is present as a fallback.
} else if (PrivetErrorTransient(error) ||
value->HasKey(kPrivetKeyTimeout)) {
int timeout_seconds;
double random_scaling_factor =
1 + base::RandDouble() * kPrivetMaximumTimeRandomAddition;
if (!value->GetInteger(kPrivetKeyTimeout, &timeout_seconds)) {
timeout_seconds = kPrivetDefaultTimeout;
}
int timeout_seconds_randomized =
static_cast<int>(timeout_seconds * random_scaling_factor);
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&PrivetRegisterOperationImpl::SendRequest,
AsWeakPtr(), current_action_),
base::TimeDelta::FromSeconds(timeout_seconds_randomized));
} else {
ongoing_ = false;
delegate_->OnPrivetRegisterError(this,
current_action_,
FAILURE_JSON_ERROR,
fetcher->response_code(),
value);
}
return;
}
// TODO(noamsml): Match the user&action with the user&action in the object,
// and fail if different.
next_response_handler_.Run(*value);
}
void PrivetRegisterOperationImpl::SendRequest(const std::string& action) {
std::string url = base::StringPrintf(
kPrivetRegisterURLFormat,
privet_client_->host_port().host().c_str(),
privet_client_->host_port().port(),
action.c_str(),
user_.c_str());
current_action_ = action;
url_fetcher_ = privet_client_->fetcher_factory().CreateURLFetcher(
GURL(url), net::URLFetcher::POST, this);
url_fetcher_->Start();
}
void PrivetRegisterOperationImpl::StartResponse(
const base::DictionaryValue& value) {
next_response_handler_ =
base::Bind(&PrivetRegisterOperationImpl::GetClaimTokenResponse,
base::Unretained(this));
SendRequest(kPrivetActionGetClaimToken);
}
void PrivetRegisterOperationImpl::GetClaimTokenResponse(
const base::DictionaryValue& value) {
std::string claimUrl;
std::string claimToken;
bool got_url = value.GetString(kPrivetKeyClaimURL, &claimUrl);
bool got_token = value.GetString(kPrivetKeyClaimToken, &claimToken);
if (got_url || got_token) {
delegate_->OnPrivetRegisterClaimToken(this, claimToken, GURL(claimUrl));
} else {
delegate_->OnPrivetRegisterError(this,
current_action_,
FAILURE_MALFORMED_RESPONSE,
-1,
NULL);
}
}
void PrivetRegisterOperationImpl::CompleteResponse(
const base::DictionaryValue& value) {
std::string id;
value.GetString(kPrivetKeyDeviceID, &id);
ongoing_ = false;
delegate_->OnPrivetRegisterDone(this, id);
}
void PrivetRegisterOperationImpl::OnPrivetInfoDone(
PrivetInfoOperation* operation,
int http_code,
const base::DictionaryValue* value) {
// TODO(noamsml): Distinguish between network errors and unparsable JSON in
// this case.
if (!value) {
delegate_->OnPrivetRegisterError(this,
kPrivetActionNameInfo,
FAILURE_NETWORK,
-1,
NULL);
return;
}
// If there is a key in the info response, the InfoOperation
// has stored it in the client.
if (!value->HasKey(kPrivetInfoKeyToken)) {
if (value->HasKey(kPrivetKeyError)) {
delegate_->OnPrivetRegisterError(this,
kPrivetActionNameInfo,
FAILURE_JSON_ERROR,
http_code,
value);
} else {
delegate_->OnPrivetRegisterError(this,
kPrivetActionNameInfo,
FAILURE_MALFORMED_RESPONSE,
-1,
NULL);
}
return;
}
if (!ongoing_) {
Start();
} else {
SendRequest(current_action_);
}
}
void PrivetRegisterOperationImpl::StartInfoOperation() {
info_operation_ = privet_client_->CreateInfoOperation(this);
info_operation_->Start();
}
bool PrivetRegisterOperationImpl::PrivetErrorTransient(
const std::string& error) {
return (error == kPrivetErrorDeviceBusy) ||
(error == kPrivetErrorPendingUserAction);
}
PrivetHTTPClientImpl::PrivetHTTPClientImpl(
const std::string& name,
const net::HostPortPair& host_port,
net::URLRequestContextGetter* request_context)
: name_(name),
fetcher_factory_(request_context),
host_port_(host_port) {
}
PrivetHTTPClientImpl::~PrivetHTTPClientImpl() {
}
const base::DictionaryValue* PrivetHTTPClientImpl::GetCachedInfo() const {
return cached_info_.get();
}
scoped_ptr<PrivetRegisterOperation>
PrivetHTTPClientImpl::CreateRegisterOperation(
const std::string& user,
PrivetRegisterOperation::Delegate* delegate) {
return scoped_ptr<PrivetRegisterOperation>(
new PrivetRegisterOperationImpl(this, user, delegate));
}
scoped_ptr<PrivetInfoOperation> PrivetHTTPClientImpl::CreateInfoOperation(
PrivetInfoOperation::Delegate* delegate) {
return scoped_ptr<PrivetInfoOperation>(
new PrivetInfoOperationImpl(this, delegate));
}
const std::string& PrivetHTTPClientImpl::GetName() {
return name_;
}
void PrivetHTTPClientImpl::CacheInfo(const base::DictionaryValue* cached_info) {
cached_info_.reset(cached_info->DeepCopy());
std::string token;
if (cached_info_->GetString(kPrivetInfoKeyToken, &token)) {
fetcher_factory_.set_token(token);
}
}
} // namespace local_discovery