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 "base/strings/stringprintf.h"
     10 #include "cloud_print/gcp20/prototype/gcp20_switches.h"
     11 #include "net/base/ip_endpoint.h"
     12 #include "net/base/net_errors.h"
     13 #include "net/base/url_util.h"
     14 #include "net/socket/tcp_server_socket.h"
     15 #include "url/gurl.h"
     16 
     17 namespace {
     18 
     19 const int kDeviceBusyTimeout = 30;  // in seconds
     20 const int kPendingUserActionTimeout = 5;  // in seconds
     21 
     22 const char kPrivetInfo[] = "/privet/info";
     23 const char kPrivetRegister[] = "/privet/register";
     24 const char kPrivetCapabilities[] = "/privet/capabilities";
     25 const char kPrivetPrinterCreateJob[] = "/privet/printer/createjob";
     26 const char kPrivetPrinterSubmitDoc[] = "/privet/printer/submitdoc";
     27 const char kPrivetPrinterJobState[] = "/privet/printer/jobstate";
     28 
     29 // {"error":|error_type|}
     30 scoped_ptr<base::DictionaryValue> CreateError(const std::string& error_type) {
     31   scoped_ptr<base::DictionaryValue> error(new base::DictionaryValue);
     32   error->SetString("error", error_type);
     33 
     34   return error.Pass();
     35 }
     36 
     37 // {"error":|error_type|, "description":|description|}
     38 scoped_ptr<base::DictionaryValue> CreateErrorWithDescription(
     39     const std::string& error_type,
     40     const std::string& description) {
     41   scoped_ptr<base::DictionaryValue> error(CreateError(error_type));
     42   error->SetString("description", description);
     43   return error.Pass();
     44 }
     45 
     46 // {"error":|error_type|, "timeout":|timeout|}
     47 scoped_ptr<base::DictionaryValue> CreateErrorWithTimeout(
     48     const std::string& error_type,
     49     int timeout) {
     50   scoped_ptr<base::DictionaryValue> error(CreateError(error_type));
     51   error->SetInteger("timeout", timeout);
     52   return error.Pass();
     53 }
     54 
     55 // Converts state to string.
     56 std::string LocalPrintJobStateToString(LocalPrintJob::State state) {
     57   switch (state) {
     58     case LocalPrintJob::STATE_DRAFT:
     59       return "draft";
     60       break;
     61     case LocalPrintJob::STATE_ABORTED:
     62       return "done";
     63       break;
     64     case LocalPrintJob::STATE_DONE:
     65       return "done";
     66       break;
     67     default:
     68       NOTREACHED();
     69       return std::string();
     70   }
     71 }
     72 
     73 
     74 // Returns |true| if |request| should be GET method.
     75 bool IsGetMethod(const std::string& request) {
     76   return request == kPrivetInfo ||
     77          request == kPrivetCapabilities ||
     78          request == kPrivetPrinterJobState;
     79 }
     80 
     81 // Returns |true| if |request| should be POST method.
     82 bool IsPostMethod(const std::string& request) {
     83   return request == kPrivetRegister ||
     84          request == kPrivetPrinterCreateJob ||
     85          request == kPrivetPrinterSubmitDoc;
     86 }
     87 
     88 }  // namespace
     89 
     90 PrivetHttpServer::DeviceInfo::DeviceInfo() : uptime(0) {
     91 }
     92 
     93 PrivetHttpServer::DeviceInfo::~DeviceInfo() {
     94 }
     95 
     96 PrivetHttpServer::PrivetHttpServer(Delegate* delegate)
     97     : port_(0),
     98       delegate_(delegate) {
     99 }
    100 
    101 PrivetHttpServer::~PrivetHttpServer() {
    102   Shutdown();
    103 }
    104 
    105 bool PrivetHttpServer::Start(uint16 port) {
    106   if (server_)
    107     return true;
    108 
    109   scoped_ptr<net::ServerSocket> server_socket(
    110       new net::TCPServerSocket(NULL, net::NetLog::Source()));
    111   std::string listen_address = "::";
    112   if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableIpv6))
    113     listen_address = "0.0.0.0";
    114   server_socket->ListenWithAddressAndPort(listen_address, port, 1);
    115   server_.reset(new net::HttpServer(server_socket.Pass(), this));
    116 
    117   net::IPEndPoint address;
    118   if (server_->GetLocalAddress(&address) != net::OK) {
    119     NOTREACHED() << "Cannot start HTTP server";
    120     return false;
    121   }
    122 
    123   VLOG(1) << "Address of HTTP server: " << address.ToString();
    124   return true;
    125 }
    126 
    127 void PrivetHttpServer::Shutdown() {
    128   if (!server_)
    129     return;
    130 
    131   server_.reset(NULL);
    132 }
    133 
    134 void PrivetHttpServer::OnHttpRequest(int connection_id,
    135                                      const net::HttpServerRequestInfo& info) {
    136   VLOG(1) << "Processing HTTP request: " << info.path;
    137   GURL url("http://host" + info.path);
    138 
    139   if (!ValidateRequestMethod(connection_id, url.path(), info.method))
    140     return;
    141 
    142   if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableXTocken)) {
    143     net::HttpServerRequestInfo::HeadersMap::const_iterator iter =
    144         info.headers.find("x-privet-token");
    145     if (iter == info.headers.end()) {
    146       server_->Send(connection_id, net::HTTP_BAD_REQUEST,
    147                     "Missing X-Privet-Token header.\n"
    148                     "TODO: Message should be in header, not in the body!",
    149                     "text/plain");
    150       return;
    151     }
    152 
    153     if (url.path() != kPrivetInfo &&
    154         !delegate_->CheckXPrivetTokenHeader(iter->second)) {
    155       server_->Send(connection_id, net::HTTP_OK,
    156                     "{\"error\":\"invalid_x_privet_token\"}",
    157                     "application/json");
    158       return;
    159     }
    160   }
    161 
    162   std::string response;
    163   net::HttpStatusCode status_code = ProcessHttpRequest(url, info, &response);
    164 
    165   server_->Send(connection_id, status_code, response, "application/json");
    166 }
    167 
    168 void PrivetHttpServer::OnWebSocketRequest(
    169     int connection_id,
    170     const net::HttpServerRequestInfo& info) {
    171 }
    172 
    173 void PrivetHttpServer::OnWebSocketMessage(int connection_id,
    174                                           const std::string& data) {
    175 }
    176 
    177 void PrivetHttpServer::OnClose(int connection_id) {
    178 }
    179 
    180 void PrivetHttpServer::ReportInvalidMethod(int connection_id) {
    181   server_->Send(connection_id, net::HTTP_BAD_REQUEST, "Invalid method",
    182                 "text/plain");
    183 }
    184 
    185 bool PrivetHttpServer::ValidateRequestMethod(int connection_id,
    186                                              const std::string& request,
    187                                              const std::string& method) {
    188   DCHECK(!IsGetMethod(request) || !IsPostMethod(request));
    189 
    190   if (!IsGetMethod(request) && !IsPostMethod(request)) {
    191     server_->Send404(connection_id);
    192     return false;
    193   }
    194 
    195   if (CommandLine::ForCurrentProcess()->HasSwitch("disable-method-check"))
    196     return true;
    197 
    198   if ((IsGetMethod(request) && method != "GET") ||
    199       (IsPostMethod(request) && method != "POST")) {
    200     ReportInvalidMethod(connection_id);
    201     return false;
    202   }
    203 
    204   return true;
    205 }
    206 
    207 net::HttpStatusCode PrivetHttpServer::ProcessHttpRequest(
    208     const GURL& url,
    209     const net::HttpServerRequestInfo& info,
    210     std::string* response) {
    211   net::HttpStatusCode status_code = net::HTTP_OK;
    212   scoped_ptr<base::DictionaryValue> json_response;
    213 
    214   if (url.path() == kPrivetInfo) {
    215     json_response = ProcessInfo(&status_code);
    216   } else if (url.path() == kPrivetRegister) {
    217     json_response = ProcessRegister(url, &status_code);
    218   } else if (url.path() == kPrivetCapabilities) {
    219     json_response = ProcessCapabilities(&status_code);
    220   } else if (url.path() == kPrivetPrinterCreateJob) {
    221     json_response = ProcessCreateJob(url, info.data, &status_code);
    222   } else if (url.path() == kPrivetPrinterSubmitDoc) {
    223     json_response = ProcessSubmitDoc(url, info, &status_code);
    224   } else if (url.path() == kPrivetPrinterJobState) {
    225     json_response = ProcessJobState(url, &status_code);
    226   } else {
    227     NOTREACHED();
    228   }
    229 
    230   if (!json_response) {
    231     response->clear();
    232     return status_code;
    233   }
    234 
    235   base::JSONWriter::WriteWithOptions(json_response.get(),
    236                                      base::JSONWriter::OPTIONS_PRETTY_PRINT,
    237                                      response);
    238   return status_code;
    239 }
    240 
    241 // Privet API methods:
    242 
    243 scoped_ptr<base::DictionaryValue> PrivetHttpServer::ProcessInfo(
    244     net::HttpStatusCode* status_code) const {
    245 
    246   DeviceInfo info;
    247   delegate_->CreateInfo(&info);
    248 
    249   scoped_ptr<base::DictionaryValue> response(new base::DictionaryValue);
    250   response->SetString("version", info.version);
    251   response->SetString("name", info.name);
    252   response->SetString("description", info.description);
    253   response->SetString("url", info.url);
    254   response->SetString("id", info.id);
    255   response->SetString("device_state", info.device_state);
    256   response->SetString("connection_state", info.connection_state);
    257   response->SetString("manufacturer", info.manufacturer);
    258   response->SetString("model", info.model);
    259   response->SetString("serial_number", info.serial_number);
    260   response->SetString("firmware", info.firmware);
    261   response->SetInteger("uptime", info.uptime);
    262   response->SetString("x-privet-token", info.x_privet_token);
    263 
    264   base::ListValue api;
    265   for (size_t i = 0; i < info.api.size(); ++i)
    266     api.AppendString(info.api[i]);
    267   response->Set("api", api.DeepCopy());
    268 
    269   base::ListValue type;
    270   for (size_t i = 0; i < info.type.size(); ++i)
    271     type.AppendString(info.type[i]);
    272   response->Set("type", type.DeepCopy());
    273 
    274   *status_code = net::HTTP_OK;
    275   return response.Pass();
    276 }
    277 
    278 scoped_ptr<base::DictionaryValue> PrivetHttpServer::ProcessCapabilities(
    279     net::HttpStatusCode* status_code) const {
    280   if (!delegate_->IsRegistered()) {
    281     *status_code = net::HTTP_NOT_FOUND;
    282     return scoped_ptr<base::DictionaryValue>();
    283   }
    284   return make_scoped_ptr(delegate_->GetCapabilities().DeepCopy());
    285 }
    286 
    287 scoped_ptr<base::DictionaryValue> PrivetHttpServer::ProcessCreateJob(
    288     const GURL& url,
    289     const std::string& body,
    290     net::HttpStatusCode* status_code) const {
    291   if (!delegate_->IsRegistered() || !delegate_->IsLocalPrintingAllowed()) {
    292     *status_code = net::HTTP_NOT_FOUND;
    293     return scoped_ptr<base::DictionaryValue>();
    294   }
    295 
    296   std::string job_id;
    297   // TODO(maksymb): Use base::Time for expiration
    298   int expires_in = 0;
    299   // TODO(maksymb): Use base::TimeDelta for timeout values
    300   int error_timeout = -1;
    301   std::string error_description;
    302 
    303   LocalPrintJob::CreateResult result =
    304       delegate_->CreateJob(body, &job_id, &expires_in,
    305                            &error_timeout, &error_description);
    306 
    307   scoped_ptr<base::DictionaryValue> response;
    308   *status_code = net::HTTP_OK;
    309   switch (result) {
    310     case LocalPrintJob::CREATE_SUCCESS:
    311       response.reset(new base::DictionaryValue);
    312       response->SetString("job_id", job_id);
    313       response->SetInteger("expires_in", expires_in);
    314       return response.Pass();
    315 
    316     case LocalPrintJob::CREATE_INVALID_TICKET:
    317       return CreateError("invalid_ticket");
    318     case LocalPrintJob::CREATE_PRINTER_BUSY:
    319       return CreateErrorWithTimeout("printer_busy", error_timeout);
    320     case LocalPrintJob::CREATE_PRINTER_ERROR:
    321       return CreateErrorWithDescription("printer_error", error_description);
    322   }
    323   return scoped_ptr<base::DictionaryValue>();
    324 }
    325 
    326 scoped_ptr<base::DictionaryValue> PrivetHttpServer::ProcessSubmitDoc(
    327     const GURL& url,
    328     const net::HttpServerRequestInfo& info,
    329     net::HttpStatusCode* status_code) const {
    330   if (!delegate_->IsRegistered() || !delegate_->IsLocalPrintingAllowed()) {
    331     *status_code = net::HTTP_NOT_FOUND;
    332     return scoped_ptr<base::DictionaryValue>();
    333   }
    334 
    335   using net::GetValueForKeyInQuery;
    336 
    337   // Parse query
    338   LocalPrintJob job;
    339   std::string offline;
    340   std::string job_id;
    341   bool job_name_present = GetValueForKeyInQuery(url, "job_name", &job.job_name);
    342   bool job_id_present = GetValueForKeyInQuery(url, "job_id", &job_id);
    343   GetValueForKeyInQuery(url, "client_name", &job.client_name);
    344   GetValueForKeyInQuery(url, "user_name", &job.user_name);
    345   GetValueForKeyInQuery(url, "offline", &offline);
    346   job.offline = (offline == "1");
    347   job.content_type = info.GetHeaderValue("content-type");
    348   job.content = info.data;
    349 
    350   // Call delegate
    351   // TODO(maksymb): Use base::Time for expiration
    352   int expires_in = 0;
    353   std::string error_description;
    354   int timeout;
    355   LocalPrintJob::SaveResult status = job_id_present
    356       ? delegate_->SubmitDocWithId(job, job_id, &expires_in,
    357                                    &error_description, &timeout)
    358       : delegate_->SubmitDoc(job, &job_id, &expires_in,
    359                              &error_description, &timeout);
    360 
    361   // Create response
    362   *status_code = net::HTTP_OK;
    363   scoped_ptr<base::DictionaryValue> response(new base::DictionaryValue);
    364   switch (status) {
    365     case LocalPrintJob::SAVE_SUCCESS:
    366       response->SetString("job_id", job_id);
    367       response->SetInteger("expires_in", expires_in);
    368       response->SetString("job_type", job.content_type);
    369       response->SetString(
    370           "job_size",
    371           base::StringPrintf("%u", static_cast<uint32>(job.content.size())));
    372       if (job_name_present)
    373         response->SetString("job_name", job.job_name);
    374       return response.Pass();
    375 
    376     case LocalPrintJob::SAVE_INVALID_PRINT_JOB:
    377       return CreateErrorWithTimeout("invalid_print_job", timeout);
    378     case LocalPrintJob::SAVE_INVALID_DOCUMENT_TYPE:
    379       return CreateError("invalid_document_type");
    380     case LocalPrintJob::SAVE_INVALID_DOCUMENT:
    381       return CreateError("invalid_document");
    382     case LocalPrintJob::SAVE_DOCUMENT_TOO_LARGE:
    383       return CreateError("document_too_large");
    384     case LocalPrintJob::SAVE_PRINTER_BUSY:
    385       return CreateErrorWithTimeout("printer_busy", -2);
    386     case LocalPrintJob::SAVE_PRINTER_ERROR:
    387       return CreateErrorWithDescription("printer_error", error_description);
    388     default:
    389       NOTREACHED();
    390       return scoped_ptr<base::DictionaryValue>();
    391   }
    392 }
    393 
    394 scoped_ptr<base::DictionaryValue> PrivetHttpServer::ProcessJobState(
    395     const GURL& url,
    396     net::HttpStatusCode* status_code) const {
    397   if (!delegate_->IsRegistered() || !delegate_->IsLocalPrintingAllowed()) {
    398     *status_code = net::HTTP_NOT_FOUND;
    399     return scoped_ptr<base::DictionaryValue>();
    400   }
    401 
    402   std::string job_id;
    403   net::GetValueForKeyInQuery(url, "job_id", &job_id);
    404   LocalPrintJob::Info info;
    405   if (!delegate_->GetJobState(job_id, &info))
    406     return CreateError("invalid_print_job");
    407 
    408   scoped_ptr<base::DictionaryValue> response(new base::DictionaryValue);
    409   response->SetString("job_id", job_id);
    410   response->SetString("state", LocalPrintJobStateToString(info.state));
    411   response->SetInteger("expires_in", info.expires_in);
    412   return response.Pass();
    413 }
    414 
    415 scoped_ptr<base::DictionaryValue> PrivetHttpServer::ProcessRegister(
    416     const GURL& url,
    417     net::HttpStatusCode* status_code) const {
    418   if (delegate_->IsRegistered()) {
    419     *status_code = net::HTTP_NOT_FOUND;
    420     return scoped_ptr<base::DictionaryValue>();
    421   }
    422 
    423   std::string action;
    424   std::string user;
    425   bool params_present =
    426       net::GetValueForKeyInQuery(url, "action", &action) &&
    427       net::GetValueForKeyInQuery(url, "user", &user) &&
    428       !user.empty();
    429 
    430   RegistrationErrorStatus status = REG_ERROR_INVALID_PARAMS;
    431   scoped_ptr<base::DictionaryValue> response(new base::DictionaryValue);
    432 
    433   if (params_present) {
    434     response->SetString("action", action);
    435     response->SetString("user", user);
    436 
    437     if (action == "start")
    438       status = delegate_->RegistrationStart(user);
    439 
    440     if (action == "getClaimToken") {
    441       std::string token;
    442       std::string claim_url;
    443       status = delegate_->RegistrationGetClaimToken(user, &token, &claim_url);
    444       response->SetString("token", token);
    445       response->SetString("claim_url", claim_url);
    446     }
    447 
    448     if (action == "complete") {
    449       std::string device_id;
    450       status = delegate_->RegistrationComplete(user, &device_id);
    451       response->SetString("device_id", device_id);
    452     }
    453 
    454     if (action == "cancel")
    455       status = delegate_->RegistrationCancel(user);
    456   }
    457 
    458   if (status != REG_ERROR_OK)
    459     response.reset();
    460 
    461   ProcessRegistrationStatus(status, &response);
    462   *status_code = net::HTTP_OK;
    463   return response.Pass();
    464 }
    465 
    466 void PrivetHttpServer::ProcessRegistrationStatus(
    467     RegistrationErrorStatus status,
    468     scoped_ptr<base::DictionaryValue>* current_response) const {
    469   switch (status) {
    470     case REG_ERROR_OK:
    471       DCHECK(*current_response) << "Response shouldn't be empty.";
    472       break;
    473 
    474     case REG_ERROR_INVALID_PARAMS:
    475       *current_response = CreateError("invalid_params");
    476       break;
    477     case REG_ERROR_DEVICE_BUSY:
    478       *current_response = CreateErrorWithTimeout("device_busy",
    479                                                  kDeviceBusyTimeout);
    480       break;
    481     case REG_ERROR_PENDING_USER_ACTION:
    482       *current_response = CreateErrorWithTimeout("pending_user_action",
    483                                                  kPendingUserActionTimeout);
    484       break;
    485     case REG_ERROR_USER_CANCEL:
    486       *current_response = CreateError("user_cancel");
    487       break;
    488     case REG_ERROR_CONFIRMATION_TIMEOUT:
    489       *current_response = CreateError("confirmation_timeout");
    490       break;
    491     case REG_ERROR_INVALID_ACTION:
    492       *current_response = CreateError("invalid_action");
    493       break;
    494     case REG_ERROR_OFFLINE:
    495       *current_response = CreateError("offline");
    496       break;
    497 
    498     case REG_ERROR_SERVER_ERROR: {
    499       std::string description;
    500       delegate_->GetRegistrationServerError(&description);
    501       *current_response = CreateErrorWithDescription("server_error",
    502                                                      description);
    503       break;
    504     }
    505 
    506     default:
    507       NOTREACHED();
    508   };
    509 }
    510