Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / cloud_print / gcp20 / prototype / privet_http_server.cc
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 }