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