blob: 0a210591ddbb5b205a7ddc5e1ffec6ff41c92fca [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 "cloud_print/gcp20/prototype/privet_http_server.h"
#include "base/command_line.h"
#include "base/json/json_writer.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
#include "net/base/url_util.h"
#include "net/socket/tcp_listen_socket.h"
#include "url/gurl.h"
namespace {
const int kDeviceBusyTimeout = 30; // in seconds
const int kPendingUserActionTimeout = 5; // in seconds
// {"error":|error_type|}
scoped_ptr<base::DictionaryValue> CreateError(const std::string& error_type) {
scoped_ptr<base::DictionaryValue> error(new base::DictionaryValue);
error->SetString("error", error_type);
return error.Pass();
}
// {"error":|error_type|, "description":|description|}
scoped_ptr<base::DictionaryValue> CreateErrorWithDescription(
const std::string& error_type,
const std::string& description) {
scoped_ptr<base::DictionaryValue> error(CreateError(error_type));
error->SetString("description", description);
return error.Pass();
}
// {"error":|error_type|, "timeout":|timout|}
scoped_ptr<base::DictionaryValue> CreateErrorWithTimeout(
const std::string& error_type,
int timeout) {
scoped_ptr<base::DictionaryValue> error(CreateError(error_type));
error->SetInteger("timeout", timeout);
return error.Pass();
}
// Returns |true| if |request| should be GET method.
bool IsGetMethod(const std::string& request) {
return request == "/privet/info"/* ||
request == "/privet/accesstoken" ||
request == "/privet/capabilities" ||
request == "/privet/printer/jobstate"*/;
}
// Returns |true| if |request| should be POST method.
bool IsPostMethod(const std::string& request) {
return request == "/privet/register"/* ||
request == "/privet/printer/createjob" ||
request == "/privet/printer/submitdoc"*/;
}
} // namespace
PrivetHttpServer::DeviceInfo::DeviceInfo() : uptime(0) {
}
PrivetHttpServer::DeviceInfo::~DeviceInfo() {
}
PrivetHttpServer::PrivetHttpServer(Delegate* delegate)
: port_(0),
delegate_(delegate) {
}
PrivetHttpServer::~PrivetHttpServer() {
Shutdown();
}
bool PrivetHttpServer::Start(uint16 port) {
if (server_)
return true;
net::TCPListenSocketFactory factory("0.0.0.0", port);
server_ = new net::HttpServer(factory, this);
net::IPEndPoint address;
if (server_->GetLocalAddress(&address) != net::OK) {
NOTREACHED() << "Cannot start HTTP server";
return false;
}
VLOG(1) << "Address of HTTP server: " << address.ToString();
return true;
}
void PrivetHttpServer::Shutdown() {
if (!server_)
return;
server_ = NULL;
}
void PrivetHttpServer::OnHttpRequest(int connection_id,
const net::HttpServerRequestInfo& info) {
VLOG(1) << "Processing HTTP request: " << info.path;
GURL url("http://host" + info.path);
if (!ValidateRequestMethod(connection_id, url.path(), info.method))
return;
if (!CommandLine::ForCurrentProcess()->HasSwitch("disable-x-token")) {
net::HttpServerRequestInfo::HeadersMap::const_iterator iter =
info.headers.find("x-privet-token");
if (iter == info.headers.end()) {
server_->Send(connection_id, net::HTTP_BAD_REQUEST,
"Missing X-Privet-Token header.\n"
"TODO: Message should be in header, not in the body!",
"text/plain");
return;
}
if (url.path() != "/privet/info" &&
!delegate_->CheckXPrivetTokenHeader(iter->second)) {
server_->Send(connection_id, net::HTTP_OK,
"{\"error\":\"invalid_x_privet_token\"}",
"application/json");
return;
}
}
std::string response;
net::HttpStatusCode status_code =
ProcessHttpRequest(url, info.data, &response);
server_->Send(connection_id, status_code, response, "application/json");
}
void PrivetHttpServer::OnWebSocketRequest(
int connection_id,
const net::HttpServerRequestInfo& info) {
}
void PrivetHttpServer::OnWebSocketMessage(int connection_id,
const std::string& data) {
}
void PrivetHttpServer::OnClose(int connection_id) {
}
void PrivetHttpServer::ReportInvalidMethod(int connection_id) {
server_->Send(connection_id, net::HTTP_BAD_REQUEST, "Invalid method",
"text/plain");
}
bool PrivetHttpServer::ValidateRequestMethod(int connection_id,
const std::string& request,
const std::string& method) {
DCHECK(!IsGetMethod(request) || !IsPostMethod(request));
if (!IsGetMethod(request) && !IsPostMethod(request)) {
server_->Send404(connection_id);
return false;
}
if (CommandLine::ForCurrentProcess()->HasSwitch("disable-method-check"))
return true;
if ((IsGetMethod(request) && method != "GET") ||
(IsPostMethod(request) && method != "POST")) {
ReportInvalidMethod(connection_id);
return false;
}
return true;
}
net::HttpStatusCode PrivetHttpServer::ProcessHttpRequest(
const GURL& url,
const std::string& data,
std::string* response) {
net::HttpStatusCode status_code = net::HTTP_OK;
scoped_ptr<base::DictionaryValue> json_response;
if (url.path() == "/privet/info") {
json_response = ProcessInfo(&status_code);
} else if (url.path() == "/privet/register") {
json_response = ProcessRegister(url, &status_code);
} else {
NOTREACHED();
}
if (!json_response) {
response->clear();
return status_code;
}
base::JSONWriter::WriteWithOptions(json_response.get(),
base::JSONWriter::OPTIONS_PRETTY_PRINT,
response);
return status_code;
}
// Privet API methods:
scoped_ptr<base::DictionaryValue> PrivetHttpServer::ProcessInfo(
net::HttpStatusCode* status_code) const {
DeviceInfo info;
delegate_->CreateInfo(&info);
scoped_ptr<base::DictionaryValue> response(new base::DictionaryValue);
response->SetString("version", info.version);
response->SetString("name", info.name);
response->SetString("description", info.description);
response->SetString("url", info.url);
response->SetString("id", info.id);
response->SetString("device_state", info.device_state);
response->SetString("connection_state", info.connection_state);
response->SetString("manufacturer", info.manufacturer);
response->SetString("model", info.model);
response->SetString("serial_number", info.serial_number);
response->SetString("firmware", info.firmware);
response->SetInteger("uptime", info.uptime);
response->SetString("x-privet-token", info.x_privet_token);
base::ListValue api;
for (size_t i = 0; i < info.api.size(); ++i)
api.AppendString(info.api[i]);
response->Set("api", api.DeepCopy());
base::ListValue type;
for (size_t i = 0; i < info.type.size(); ++i)
type.AppendString(info.type[i]);
response->Set("type", type.DeepCopy());
*status_code = net::HTTP_OK;
return response.Pass();
}
scoped_ptr<base::DictionaryValue> PrivetHttpServer::ProcessRegister(
const GURL& url,
net::HttpStatusCode* status_code) {
if (delegate_->IsRegistered()) {
*status_code = net::HTTP_NOT_FOUND;
return scoped_ptr<base::DictionaryValue>();
}
std::string action;
std::string user;
bool params_present =
net::GetValueForKeyInQuery(url, "action", &action) &&
net::GetValueForKeyInQuery(url, "user", &user) &&
!user.empty();
RegistrationErrorStatus status = REG_ERROR_INVALID_PARAMS;
scoped_ptr<base::DictionaryValue> response(new DictionaryValue);
if (params_present) {
response->SetString("action", action);
response->SetString("user", user);
if (action == "start")
status = delegate_->RegistrationStart(user);
if (action == "getClaimToken") {
std::string token;
std::string claim_url;
status = delegate_->RegistrationGetClaimToken(user, &token, &claim_url);
response->SetString("token", token);
response->SetString("claim_url", claim_url);
}
if (action == "complete") {
std::string device_id;
status = delegate_->RegistrationComplete(user, &device_id);
response->SetString("device_id", device_id);
}
if (action == "cancel")
status = delegate_->RegistrationCancel(user);
}
if (status != REG_ERROR_OK)
response.reset();
ProcessRegistrationStatus(status, &response);
*status_code = net::HTTP_OK;
return response.Pass();
}
void PrivetHttpServer::ProcessRegistrationStatus(
RegistrationErrorStatus status,
scoped_ptr<base::DictionaryValue>* current_response) const {
switch (status) {
case REG_ERROR_OK:
DCHECK(*current_response) << "Response shouldn't be empty.";
break;
case REG_ERROR_INVALID_PARAMS:
*current_response = CreateError("invalid_params");
break;
case REG_ERROR_DEVICE_BUSY:
*current_response = CreateErrorWithTimeout("device_busy",
kDeviceBusyTimeout);
break;
case REG_ERROR_PENDING_USER_ACTION:
*current_response = CreateErrorWithTimeout("pending_user_action",
kPendingUserActionTimeout);
break;
case REG_ERROR_USER_CANCEL:
*current_response = CreateError("user_cancel");
break;
case REG_ERROR_CONFIRMATION_TIMEOUT:
*current_response = CreateError("confirmation_timeout");
break;
case REG_ERROR_INVALID_ACTION:
*current_response = CreateError("invalid_action");
break;
case REG_ERROR_SERVER_ERROR: {
std::string description;
delegate_->GetRegistrationServerError(&description);
*current_response = CreateErrorWithDescription("server_error",
description);
break;
}
default:
NOTREACHED();
};
}