| // 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 |