blob: a8bd3f1ca0700be3929c937d9718e99cfc146377 [file] [log] [blame]
// Copyright 2015 The Chromium OS 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 "libwebserv/dbus_protocol_handler.h"
#include <tuple>
#include <utility>
#include <base/bind_helpers.h>
#include <base/logging.h>
#include <brillo/map_utils.h>
#include <brillo/streams/file_stream.h>
#include <brillo/streams/stream_utils.h>
#include "dbus_bindings/org.chromium.WebServer.RequestHandler.h"
#include "libwebserv/dbus_response.h"
#include "libwebserv/dbus_server.h"
#include "libwebserv/protocol_handler.h"
#include "libwebserv/request.h"
#include "libwebserv/request_handler_callback.h"
#include "webservd/dbus-proxies.h"
namespace libwebserv {
namespace {
// Dummy callback for async D-Bus errors.
void IgnoreDBusError(brillo::Error* /* error */) {}
// Copies the data from |src_stream| to the destination stream represented
// by a file descriptor |fd|.
void WriteResponseData(brillo::StreamPtr src_stream, const base::ScopedFD& fd) {
int dupfd = dup(fd.get());
auto dest_stream =
brillo::FileStream::FromFileDescriptor(dupfd, true, nullptr);
CHECK(dest_stream);
// Dummy callbacks for success/error of data-copy operation. We ignore both
// notifications here.
auto on_success = [](brillo::StreamPtr, brillo::StreamPtr, uint64_t) {};
auto on_error = [](brillo::StreamPtr, brillo::StreamPtr,
const brillo::Error*) {};
brillo::stream_utils::CopyData(std::move(src_stream), std::move(dest_stream),
base::Bind(on_success), base::Bind(on_error));
}
} // anonymous namespace
DBusProtocolHandler::DBusProtocolHandler(const std::string& name,
DBusServer* server)
: name_{name}, server_{server} {}
DBusProtocolHandler::~DBusProtocolHandler() {
// Remove any existing handlers, so the web server knows that we don't
// need them anymore.
// We need to get a copy of the map keys since removing the handlers will
// modify the map in the middle of the loop and that's not a good thing.
auto handler_ids = brillo::GetMapKeys(request_handlers_);
for (int handler_id : handler_ids) {
RemoveHandler(handler_id);
}
}
bool DBusProtocolHandler::IsConnected() const {
return !proxies_.empty();
}
std::string DBusProtocolHandler::GetName() const {
return name_;
}
std::set<uint16_t> DBusProtocolHandler::GetPorts() const {
std::set<uint16_t> ports;
for (const auto& pair : proxies_)
ports.insert(pair.second->port());
return ports;
}
std::set<std::string> DBusProtocolHandler::GetProtocols() const {
std::set<std::string> protocols;
for (const auto& pair : proxies_)
protocols.insert(pair.second->protocol());
return protocols;
}
brillo::Blob DBusProtocolHandler::GetCertificateFingerprint() const {
brillo::Blob fingerprint;
for (const auto& pair : proxies_) {
fingerprint = pair.second->certificate_fingerprint();
if (!fingerprint.empty())
break;
}
return fingerprint;
}
int DBusProtocolHandler::AddHandler(
const std::string& url,
const std::string& method,
std::unique_ptr<RequestHandlerInterface> handler) {
request_handlers_.emplace(
++last_handler_id_,
HandlerMapEntry{url, method,
std::map<ProtocolHandlerProxyInterface*, std::string>{},
std::move(handler)});
// For each instance of remote protocol handler object sharing the same name,
// add the request handler.
for (const auto& pair : proxies_) {
pair.second->AddRequestHandlerAsync(
url, method, server_->service_name_,
base::Bind(&DBusProtocolHandler::AddHandlerSuccess,
weak_ptr_factory_.GetWeakPtr(), last_handler_id_,
pair.second),
base::Bind(&DBusProtocolHandler::AddHandlerError,
weak_ptr_factory_.GetWeakPtr(), last_handler_id_));
}
return last_handler_id_;
}
int DBusProtocolHandler::AddHandlerCallback(
const std::string& url,
const std::string& method,
const base::Callback<RequestHandlerInterface::HandlerSignature>&
handler_callback) {
std::unique_ptr<RequestHandlerInterface> handler{
new RequestHandlerCallback{handler_callback}};
return AddHandler(url, method, std::move(handler));
}
bool DBusProtocolHandler::RemoveHandler(int handler_id) {
auto p = request_handlers_.find(handler_id);
if (p == request_handlers_.end())
return false;
for (const auto& pair : p->second.remote_handler_ids) {
pair.first->RemoveRequestHandlerAsync(pair.second, base::DoNothing(),
base::Bind(&IgnoreDBusError));
}
request_handlers_.erase(p);
return true;
}
void DBusProtocolHandler::Connect(ProtocolHandlerProxyInterface* proxy) {
proxies_.emplace(proxy->GetObjectPath(), proxy);
for (const auto& pair : request_handlers_) {
proxy->AddRequestHandlerAsync(
pair.second.url, pair.second.method, server_->service_name_,
base::Bind(&DBusProtocolHandler::AddHandlerSuccess,
weak_ptr_factory_.GetWeakPtr(), pair.first, proxy),
base::Bind(&DBusProtocolHandler::AddHandlerError,
weak_ptr_factory_.GetWeakPtr(), pair.first));
}
}
void DBusProtocolHandler::Disconnect(const dbus::ObjectPath& object_path) {
proxies_.erase(object_path);
if (proxies_.empty()) {
remote_handler_id_map_.clear();
request_id_map_.clear();
}
for (auto& pair : request_handlers_)
pair.second.remote_handler_ids.clear();
}
void DBusProtocolHandler::AddHandlerSuccess(
int handler_id,
ProtocolHandlerProxyInterface* proxy,
const std::string& remote_handler_id) {
auto p = request_handlers_.find(handler_id);
if (p == request_handlers_.end()) {
// The handler was already removed. This can happen if RemoveHandler is
// called between when AddHandler runs and when this callback runs.
return;
}
p->second.remote_handler_ids.emplace(proxy, remote_handler_id);
remote_handler_id_map_.emplace(remote_handler_id, handler_id);
}
void DBusProtocolHandler::AddHandlerError(int /* handler_id */,
brillo::Error* /* error */) {
// Nothing to do at the moment.
}
bool DBusProtocolHandler::ProcessRequest(const std::string& protocol_handler_id,
const std::string& remote_handler_id,
const std::string& request_id,
std::unique_ptr<Request> request,
brillo::ErrorPtr* error) {
auto id_iter = remote_handler_id_map_.find(remote_handler_id);
if (id_iter == remote_handler_id_map_.end()) {
brillo::Error::AddToPrintf(
error, FROM_HERE, brillo::errors::dbus::kDomain, DBUS_ERROR_FAILED,
"Unknown request handler '%s'", remote_handler_id.c_str());
return false;
}
auto handler_iter = request_handlers_.find(id_iter->second);
if (handler_iter == request_handlers_.end()) {
brillo::Error::AddToPrintf(
error, FROM_HERE, brillo::errors::dbus::kDomain, DBUS_ERROR_FAILED,
"Handler # %d is no longer available", id_iter->second);
return false;
}
request_id_map_.emplace(request_id, protocol_handler_id);
handler_iter->second.handler->HandleRequest(
std::move(request),
std::unique_ptr<Response>{new DBusResponse{this, request_id}});
return true;
}
void DBusProtocolHandler::CompleteRequest(
const std::string& request_id,
int status_code,
const std::multimap<std::string, std::string>& headers,
brillo::StreamPtr data_stream) {
// Once request gets a response and the request_id_map should remove
// the request id from the map
auto clear_request_itr = request_id_map_.find(request_id);
ProtocolHandlerProxyInterface* proxy =
GetRequestProtocolHandlerProxy(request_id);
if (!proxy) {
if (clear_request_itr != request_id_map_.end()) {
request_id_map_.erase(clear_request_itr);
}
return;
}
std::vector<std::tuple<std::string, std::string>> header_list;
header_list.reserve(headers.size());
for (const auto& pair : headers)
header_list.emplace_back(pair.first, pair.second);
int64_t data_size = -1;
if (data_stream->CanGetSize())
data_size = data_stream->GetRemainingSize();
proxy->CompleteRequestAsync(
request_id, status_code, header_list, data_size,
base::Bind(&WriteResponseData, base::Passed(&data_stream)),
base::Bind(&IgnoreDBusError));
if (clear_request_itr != request_id_map_.end()) {
request_id_map_.erase(clear_request_itr);
}
}
void DBusProtocolHandler::GetFileData(
const std::string& request_id,
int file_id,
const base::Callback<void(brillo::StreamPtr)>& success_callback,
const base::Callback<void(brillo::Error*)>& error_callback) {
ProtocolHandlerProxyInterface* proxy =
GetRequestProtocolHandlerProxy(request_id);
CHECK(proxy);
// Store the success/error callback in a shared object so it can be referenced
// by the two wrapper callbacks. Since the original callbacks MAY contain
// move-only types, copying the base::Callback object is generally unsafe and
// may destroy the source object of the copy (despite the fact that it is
// constant). So, here we move both callbacks to |Callbacks| structure and
// use a shared pointer to it in both success and error callback wrappers.
struct Callbacks {
base::Callback<void(brillo::StreamPtr)> on_success;
base::Callback<void(brillo::Error*)> on_error;
};
auto callbacks = std::make_shared<Callbacks>();
callbacks->on_success = success_callback;
callbacks->on_error = error_callback;
auto on_success = [](std::shared_ptr<Callbacks> callbacks,
const base::ScopedFD& fd) {
brillo::ErrorPtr error;
// Unfortunately there is no way to take ownership of the file descriptor
// since |fd| is a const reference, so duplicate the descriptor.
int dupfd = dup(fd.get());
auto stream = brillo::FileStream::FromFileDescriptor(dupfd, true, &error);
if (!stream)
return callbacks->on_error.Run(error.get());
callbacks->on_success.Run(std::move(stream));
};
auto on_error = [](std::shared_ptr<Callbacks> callbacks,
brillo::Error* error) { callbacks->on_error.Run(error); };
proxy->GetRequestFileDataAsync(request_id, file_id,
base::Bind(on_success, callbacks),
base::Bind(on_error, callbacks));
}
DBusProtocolHandler::ProtocolHandlerProxyInterface*
DBusProtocolHandler::GetRequestProtocolHandlerProxy(
const std::string& request_id) const {
auto iter = request_id_map_.find(request_id);
if (iter == request_id_map_.end()) {
LOG(ERROR) << "Can't find pending request with ID " << request_id;
return nullptr;
}
std::string handler_id = iter->second;
auto find_proxy_by_id = [handler_id](decltype(*proxies_.begin()) pair) {
return pair.second->id() == handler_id;
};
auto proxy_iter =
std::find_if(proxies_.begin(), proxies_.end(), find_proxy_by_id);
if (proxy_iter == proxies_.end()) {
LOG(WARNING) << "Completing a request after the handler proxy is removed";
return nullptr;
}
return proxy_iter->second;
}
} // namespace libwebserv