blob: 6a0dc72ef1e6bdb93efa1882658e90586abf2eb5 [file] [log] [blame]
// Copyright (c) 2012 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 "net/dial/dial_http_server.h"
#include <memory>
#include <vector>
#include "base/bind.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
#include "net/dial/dial_service.h"
#include "net/dial/dial_service_handler.h"
#include "net/dial/dial_system_config.h"
#include "net/server/http_connection.h"
#include "net/server/http_server_request_info.h"
#include "net/socket/stream_socket.h"
#include "net/socket/tcp_server_socket.h"
namespace net {
namespace {
const char* kXmlMimeType = "text/xml; charset=\"utf-8\"";
const char* kDdXmlFormat =
"<?xml version=\"1.0\"?>"
"<root"
" xmlns=\"urn:schemas-upnp-org:device-1-0\""
" xmlns:r=\"urn:restful-tv-org:schemas:upnp-dd\">"
"<specVersion>"
"<major>1</major>"
"<minor>0</minor>"
"</specVersion>"
"<device>"
"<deviceType>urn:schemas-upnp-org:device:tvdevice:1</deviceType>"
"<friendlyName>%s</friendlyName>"
"<manufacturer>%s</manufacturer>"
"<modelName>%s</modelName>"
"<UDN>uuid:%s</UDN>"
"</device>"
"</root>";
const char* kAppsPrefix = "/apps/";
constexpr net::NetworkTrafficAnnotationTag kNetworkTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("dial_http_server", "dial_http_server");
base::Optional<net::IPEndPoint> GetLocalIpAddress() {
net::IPEndPoint ip_addr;
SbSocketAddress local_ip;
memset(&local_ip, 0, sizeof(local_ip));
bool result = false;
// DIAL Server only supports Ipv4 now.
SbSocketAddressType address_types = {kSbSocketAddressTypeIpv4};
SbSocketAddress destination;
memset(&(destination.address), 0, sizeof(destination.address));
destination.type = address_types;
if (!SbSocketGetInterfaceAddress(&destination, &local_ip, NULL) ||
!ip_addr.FromSbSocketAddress(&local_ip)) {
DLOG(WARNING) << "Unable to get a local interface address.";
return base::nullopt;
}
return ip_addr;
}
} // namespace
const int kDialHttpServerPort = 0; // Random Port.
DialHttpServer::DialHttpServer(DialService* dial_service)
: dial_service_(dial_service),
task_runner_(base::MessageLoop::current()->task_runner()) {
DCHECK(dial_service);
DCHECK(task_runner_);
auto* server_socket =
new net::TCPServerSocket(NULL /*net_log*/, net::NetLogSource());
base::Optional<net::IPEndPoint> ip_addr = GetLocalIpAddress();
if (!ip_addr) {
LOG(ERROR) << "Can not get a local address for DIAL HTTP Server";
} else {
server_socket->ListenWithAddressAndPort(
ip_addr.value().address().ToString(), ip_addr.value().port(),
1 /*backlog*/);
}
http_server_.reset(
new HttpServer(std::unique_ptr<ServerSocket>(server_socket), this));
ConfigureApplicationUrl();
}
DialHttpServer::~DialHttpServer() {
// Stop() must have been called prior to destruction, to ensure the
// server was destroyed on the right thread.
DCHECK(!http_server_);
}
void DialHttpServer::Stop() {
DCHECK_EQ(task_runner_, base::MessageLoop::current()->task_runner());
http_server_.reset();
}
int DialHttpServer::GetLocalAddress(IPEndPoint* addr) {
DCHECK_EQ(task_runner_, base::MessageLoop::current()->task_runner());
// We want to give second screen the IPv4 address, but we still need to
// get http_server_'s address for its port number.
int ret = http_server_->GetLocalAddress(addr);
if (ret != 0) {
return ERR_FAILED;
}
SbSocketAddress local_ip = {0};
// Now get the IPAddress of the network card.
SbSocketAddress destination = {0};
SbSocketAddress netmask = {0};
// DIAL only works with IPv4.
destination.type = kSbSocketAddressTypeIpv4;
if (!SbSocketGetInterfaceAddress(&destination, &local_ip, NULL)) {
return ERR_FAILED;
}
local_ip.port = addr->port();
LOG_ONCE(INFO) << "In-App DIAL Address http://" << addr->address().ToString()
<< ":" << addr->port();
if (addr->FromSbSocketAddress(&local_ip)) {
return OK;
}
return ERR_FAILED;
}
void DialHttpServer::OnHttpRequest(int conn_id,
const HttpServerRequestInfo& info) {
TRACE_EVENT0("net::dial", "DialHttpServer::OnHttpRequest");
DCHECK_EQ(task_runner_, base::MessageLoop::current()->task_runner());
if (info.method == "GET" &&
base::LowerCaseEqualsASCII(info.path, "/dd.xml")) {
// If dd.xml request
SendDeviceDescriptionManifest(conn_id);
} else if (strstr(info.path.c_str(), kAppsPrefix)) {
if (info.method == "GET" && info.path.length() == strlen(kAppsPrefix)) {
// If /apps/ request, send 302 to current application.
http_server_->SendRaw(
conn_id,
base::StringPrintf("HTTP/1.1 %d %s\r\n"
"Location: %s\r\n"
"Content-Length: 0\r\n"
"\r\n",
HTTP_FOUND, "Found",
(application_url() + "YouTube").c_str()),
kNetworkTrafficAnnotation);
} else if (!DispatchToHandler(conn_id, info)) {
// If handled, let it pass. Otherwise, send 404.
http_server_->Send404(conn_id, kNetworkTrafficAnnotation);
}
} else {
// For all other cases, send 404.
http_server_->Send404(conn_id, kNetworkTrafficAnnotation);
}
}
void DialHttpServer::OnClose(int conn_id) {}
void DialHttpServer::ConfigureApplicationUrl() {
IPEndPoint end_point;
if (OK != GetLocalAddress(&end_point)) {
LOG(ERROR) << "Could not get the local URL!";
return;
}
std::string addr = end_point.ToString();
DCHECK(!addr.empty());
server_url_ = base::StringPrintf("http://%s/", addr.c_str());
}
void DialHttpServer::SendDeviceDescriptionManifest(int conn_id) {
DCHECK_EQ(task_runner_, base::MessageLoop::current()->task_runner());
DialSystemConfig* system_config = DialSystemConfig::GetInstance();
#if defined(COBALT_BUILD_TYPE_GOLD)
const char* friendly_name = system_config->friendly_name();
#else
// For non-Gold builds, append the IP to the friendly name
// to help differentiate the devices.
std::string friendly_name_str = system_config->friendly_name();
IPEndPoint end_point;
if (OK == GetLocalAddress(&end_point)) {
friendly_name_str += " ";
friendly_name_str += end_point.ToStringWithoutPort();
}
const char* friendly_name = friendly_name_str.c_str();
#endif
std::string response_body = base::StringPrintf(
kDdXmlFormat, friendly_name, system_config->manufacturer_name(),
system_config->model_name(), system_config->model_uuid());
HttpServerResponseInfo response_info(HTTP_OK);
response_info.SetBody(response_body, kXmlMimeType);
response_info.AddHeader("Application-URL", application_url().c_str());
http_server_->SendResponse(conn_id, response_info, kNetworkTrafficAnnotation);
}
bool DialHttpServer::DispatchToHandler(int conn_id,
const HttpServerRequestInfo& info) {
DCHECK_EQ(task_runner_, base::MessageLoop::current()->task_runner());
// See if DialService has a handler for this request.
TRACE_EVENT0("net::dial", __FUNCTION__);
std::string handler_path;
scoped_refptr<DialServiceHandler> handler =
dial_service_->GetHandler(info.path, &handler_path);
if (handler.get() == NULL) {
return false;
}
handler->HandleRequest(
handler_path, info,
base::Bind(&DialHttpServer::OnReceivedResponse, this, conn_id));
return true;
}
void DialHttpServer::OnReceivedResponse(
int conn_id,
std::unique_ptr<HttpServerResponseInfo> response) {
if (task_runner_ != base::MessageLoop::current()->task_runner()) {
task_runner_->PostTask(FROM_HERE,
base::Bind(&DialHttpServer::OnReceivedResponse, this,
conn_id, base::Passed(&response)));
return;
}
DCHECK_EQ(task_runner_, base::MessageLoop::current()->task_runner());
TRACE_EVENT0("net::dial", __FUNCTION__);
if (response) {
http_server_->SendResponse(conn_id, *(response.get()),
kNetworkTrafficAnnotation);
} else {
http_server_->Send404(conn_id, kNetworkTrafficAnnotation);
}
}
} // namespace net