Home | History | Annotate | Download | only in prototype
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "cloud_print/gcp20/prototype/privet_http_server.h"
      6 
      7 #include "base/command_line.h"
      8 #include "base/json/json_writer.h"
      9 #include "net/base/ip_endpoint.h"
     10 #include "net/base/net_errors.h"
     11 #include "net/base/url_util.h"
     12 #include "net/socket/tcp_listen_socket.h"
     13 #include "url/gurl.h"
     14 
     15 namespace {
     16 
     17 const int kDeviceBusyTimeout = 30;  // in seconds
     18 const int kPendingUserActionTimeout = 5;  // in seconds
     19 
     20 // {"error":|error_type|}
     21 scoped_ptr<base::DictionaryValue> CreateError(const std::string& error_type) {
     22   scoped_ptr<base::DictionaryValue> error(new base::DictionaryValue);
     23   error->SetString("error", error_type);
     24 
     25   return error.Pass();
     26 }
     27 
     28 // {"error":|error_type|, "description":|description|}
     29 scoped_ptr<base::DictionaryValue> CreateErrorWithDescription(
     30     const std::string& error_type,
     31     const std::string& description) {
     32   scoped_ptr<base::DictionaryValue> error(CreateError(error_type));
     33   error->SetString("description", description);
     34   return error.Pass();
     35 }
     36 
     37 // {"error":|error_type|, "timeout":|timout|}
     38 scoped_ptr<base::DictionaryValue> CreateErrorWithTimeout(
     39     const std::string& error_type,
     40     int timeout) {
     41   scoped_ptr<base::DictionaryValue> error(CreateError(error_type));
     42   error->SetInteger("timeout", timeout);
     43   return error.Pass();
     44 }
     45 
     46 // Returns |true| if |request| should be GET method.
     47 bool IsGetMethod(const std::string& request) {
     48   return request == "/privet/info"/* ||
     49          request == "/privet/accesstoken" ||
     50          request == "/privet/capabilities" ||
     51          request == "/privet/printer/jobstate"*/;
     52 }
     53 
     54 // Returns |true| if |request| should be POST method.
     55 bool IsPostMethod(const std::string& request) {
     56   return request == "/privet/register"/* ||
     57          request == "/privet/printer/createjob" ||
     58          request == "/privet/printer/submitdoc"*/;
     59 }
     60 
     61 }  // namespace
     62 
     63 PrivetHttpServer::DeviceInfo::DeviceInfo() : uptime(0) {
     64 }
     65 
     66 PrivetHttpServer::DeviceInfo::~DeviceInfo() {
     67 }
     68 
     69 PrivetHttpServer::PrivetHttpServer(Delegate* delegate)
     70     : port_(0),
     71       delegate_(delegate) {
     72 }
     73 
     74 PrivetHttpServer::~PrivetHttpServer() {
     75   Shutdown();
     76 }
     77 
     78 bool PrivetHttpServer::Start(uint16 port) {
     79   if (server_)
     80     return true;
     81 
     82   net::TCPListenSocketFactory factory("0.0.0.0", port);
     83   server_ = new net::HttpServer(factory, this);
     84   net::IPEndPoint address;
     85 
     86   if (server_->GetLocalAddress(&address) != net::OK) {
     87     NOTREACHED() << "Cannot start HTTP server";
     88     return false;
     89   }
     90 
     91   VLOG(1) << "Address of HTTP server: " << address.ToString();
     92   return true;
     93 }
     94 
     95 void PrivetHttpServer::Shutdown() {
     96   if (!server_)
     97     return;
     98 
     99   server_ = NULL;
    100 }
    101 
    102 void PrivetHttpServer::OnHttpRequest(int connection_id,
    103                                      const net::HttpServerRequestInfo& info) {
    104   VLOG(1) << "Processing HTTP request: " << info.path;
    105   GURL url("http://host" + info.path);
    106 
    107   if (!ValidateRequestMethod(connection_id, url.path(), info.method))
    108     return;
    109 
    110   if (!CommandLine::ForCurrentProcess()->HasSwitch("disable-x-token")) {
    111     net::HttpServerRequestInfo::HeadersMap::const_iterator iter =
    112         info.headers.find("x-privet-token");
    113     if (iter == info.headers.end()) {
    114       server_->Send(connection_id, net::HTTP_BAD_REQUEST,
    115                     "Missing X-Privet-Token header.\n"
    116                     "TODO: Message should be in header, not in the body!",
    117                     "text/plain");
    118       return;
    119     }
    120 
    121     if (url.path() != "/privet/info" &&
    122         !delegate_->CheckXPrivetTokenHeader(iter->second)) {
    123       server_->Send(connection_id, net::HTTP_OK,
    124                     "{\"error\":\"invalid_x_privet_token\"}",
    125                     "application/json");
    126       return;
    127     }
    128   }
    129 
    130   std::string response;
    131   net::HttpStatusCode status_code =
    132       ProcessHttpRequest(url, info.data, &response);
    133 
    134   server_->Send(connection_id, status_code, response, "application/json");
    135 }
    136 
    137 void PrivetHttpServer::OnWebSocketRequest(
    138     int connection_id,
    139     const net::HttpServerRequestInfo& info) {
    140 }
    141 
    142 void PrivetHttpServer::OnWebSocketMessage(int connection_id,
    143                                           const std::string& data) {
    144 }
    145 
    146 void PrivetHttpServer::OnClose(int connection_id) {
    147 }
    148 
    149 void PrivetHttpServer::ReportInvalidMethod(int connection_id) {
    150   server_->Send(connection_id, net::HTTP_BAD_REQUEST, "Invalid method",
    151                 "text/plain");
    152 }
    153 
    154 bool PrivetHttpServer::ValidateRequestMethod(int connection_id,
    155                                              const std::string& request,
    156                                              const std::string& method) {
    157   DCHECK(!IsGetMethod(request) || !IsPostMethod(request));
    158 
    159   if (!IsGetMethod(request) && !IsPostMethod(request)) {
    160     server_->Send404(connection_id);
    161     return false;
    162   }
    163 
    164   if (CommandLine::ForCurrentProcess()->HasSwitch("disable-method-check"))
    165     return true;
    166 
    167   if ((IsGetMethod(request) && method != "GET") ||
    168       (IsPostMethod(request) && method != "POST")) {
    169     ReportInvalidMethod(connection_id);
    170     return false;
    171   }
    172 
    173   return true;
    174 }
    175 
    176 net::HttpStatusCode PrivetHttpServer::ProcessHttpRequest(
    177     const GURL& url,
    178     const std::string& data,
    179     std::string* response) {
    180   net::HttpStatusCode status_code = net::HTTP_OK;
    181   scoped_ptr<base::DictionaryValue> json_response;
    182 
    183   if (url.path() == "/privet/info") {
    184     json_response = ProcessInfo(&status_code);
    185   } else if (url.path() == "/privet/register") {
    186     json_response = ProcessRegister(url, &status_code);
    187   } else {
    188     NOTREACHED();
    189   }
    190 
    191   if (!json_response) {
    192     response->clear();
    193     return status_code;
    194   }
    195 
    196   base::JSONWriter::WriteWithOptions(json_response.get(),
    197                                      base::JSONWriter::OPTIONS_PRETTY_PRINT,
    198                                      response);
    199   return status_code;
    200 }
    201 
    202 // Privet API methods:
    203 
    204 scoped_ptr<base::DictionaryValue> PrivetHttpServer::ProcessInfo(
    205     net::HttpStatusCode* status_code) const {
    206 
    207   DeviceInfo info;
    208   delegate_->CreateInfo(&info);
    209 
    210   scoped_ptr<base::DictionaryValue> response(new base::DictionaryValue);
    211   response->SetString("version", info.version);
    212   response->SetString("name", info.name);
    213   response->SetString("description", info.description);
    214   response->SetString("url", info.url);
    215   response->SetString("id", info.id);
    216   response->SetString("device_state", info.device_state);
    217   response->SetString("connection_state", info.connection_state);
    218   response->SetString("manufacturer", info.manufacturer);
    219   response->SetString("model", info.model);
    220   response->SetString("serial_number", info.serial_number);
    221   response->SetString("firmware", info.firmware);
    222   response->SetInteger("uptime", info.uptime);
    223   response->SetString("x-privet-token", info.x_privet_token);
    224 
    225   base::ListValue api;
    226   for (size_t i = 0; i < info.api.size(); ++i)
    227     api.AppendString(info.api[i]);
    228   response->Set("api", api.DeepCopy());
    229 
    230   base::ListValue type;
    231   for (size_t i = 0; i < info.type.size(); ++i)
    232     type.AppendString(info.type[i]);
    233   response->Set("type", type.DeepCopy());
    234 
    235   *status_code = net::HTTP_OK;
    236   return response.Pass();
    237 }
    238 
    239 scoped_ptr<base::DictionaryValue> PrivetHttpServer::ProcessRegister(
    240     const GURL& url,
    241     net::HttpStatusCode* status_code) {
    242   if (delegate_->IsRegistered()) {
    243     *status_code = net::HTTP_NOT_FOUND;
    244     return scoped_ptr<base::DictionaryValue>();
    245   }
    246 
    247   std::string action;
    248   std::string user;
    249   bool params_present =
    250       net::GetValueForKeyInQuery(url, "action", &action) &&
    251       net::GetValueForKeyInQuery(url, "user", &user) &&
    252       !user.empty();
    253 
    254   RegistrationErrorStatus status = REG_ERROR_INVALID_PARAMS;
    255   scoped_ptr<base::DictionaryValue> response(new DictionaryValue);
    256 
    257   if (params_present) {
    258     response->SetString("action", action);
    259     response->SetString("user", user);
    260 
    261     if (action == "start")
    262       status = delegate_->RegistrationStart(user);
    263 
    264     if (action == "getClaimToken") {
    265       std::string token;
    266       std::string claim_url;
    267       status = delegate_->RegistrationGetClaimToken(user, &token, &claim_url);
    268       response->SetString("token", token);
    269       response->SetString("claim_url", claim_url);
    270     }
    271 
    272     if (action == "complete") {
    273       std::string device_id;
    274       status = delegate_->RegistrationComplete(user, &device_id);
    275       response->SetString("device_id", device_id);
    276     }
    277 
    278     if (action == "cancel")
    279       status = delegate_->RegistrationCancel(user);
    280   }
    281 
    282   if (status != REG_ERROR_OK)
    283     response.reset();
    284 
    285   ProcessRegistrationStatus(status, &response);
    286   *status_code = net::HTTP_OK;
    287   return response.Pass();
    288 }
    289 
    290 void PrivetHttpServer::ProcessRegistrationStatus(
    291     RegistrationErrorStatus status,
    292     scoped_ptr<base::DictionaryValue>* current_response) const {
    293   switch (status) {
    294     case REG_ERROR_OK:
    295       DCHECK(*current_response) << "Response shouldn't be empty.";
    296       break;
    297 
    298     case REG_ERROR_INVALID_PARAMS:
    299       *current_response = CreateError("invalid_params");
    300       break;
    301     case REG_ERROR_DEVICE_BUSY:
    302       *current_response = CreateErrorWithTimeout("device_busy",
    303                                                  kDeviceBusyTimeout);
    304       break;
    305     case REG_ERROR_PENDING_USER_ACTION:
    306       *current_response = CreateErrorWithTimeout("pending_user_action",
    307                                                  kPendingUserActionTimeout);
    308       break;
    309     case REG_ERROR_USER_CANCEL:
    310       *current_response = CreateError("user_cancel");
    311       break;
    312     case REG_ERROR_CONFIRMATION_TIMEOUT:
    313       *current_response = CreateError("confirmation_timeout");
    314       break;
    315     case REG_ERROR_INVALID_ACTION:
    316       *current_response = CreateError("invalid_action");
    317       break;
    318     case REG_ERROR_SERVER_ERROR: {
    319       std::string description;
    320       delegate_->GetRegistrationServerError(&description);
    321       *current_response = CreateErrorWithDescription("server_error",
    322                                                      description);
    323       break;
    324     }
    325 
    326     default:
    327       NOTREACHED();
    328   };
    329 }
    330 
    331 
    332