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.
5 #include "cloud_print/gcp20/prototype/privet_http_server.h"
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"
19 const int kDeviceBusyTimeout = 30; // in seconds
20 const int kPendingUserActionTimeout = 5; // in seconds
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";
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);
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);
46 // {"error":|error_type|, "timeout":|timeout|}
47 scoped_ptr<base::DictionaryValue> CreateErrorWithTimeout(
48 const std::string& error_type,
50 scoped_ptr<base::DictionaryValue> error(CreateError(error_type));
51 error->SetInteger("timeout", timeout);
55 // Converts state to string.
56 std::string LocalPrintJobStateToString(LocalPrintJob::State state) {
58 case LocalPrintJob::STATE_DRAFT:
61 case LocalPrintJob::STATE_ABORTED:
64 case LocalPrintJob::STATE_DONE:
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;
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;
90 PrivetHttpServer::DeviceInfo::DeviceInfo() : uptime(0) {
93 PrivetHttpServer::DeviceInfo::~DeviceInfo() {
96 PrivetHttpServer::PrivetHttpServer(Delegate* delegate)
101 PrivetHttpServer::~PrivetHttpServer() {
105 bool PrivetHttpServer::Start(uint16 port) {
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));
117 net::IPEndPoint address;
118 if (server_->GetLocalAddress(&address) != net::OK) {
119 NOTREACHED() << "Cannot start HTTP server";
123 VLOG(1) << "Address of HTTP server: " << address.ToString();
127 void PrivetHttpServer::Shutdown() {
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);
139 if (!ValidateRequestMethod(connection_id, url.path(), info.method))
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!",
153 if (url.path() != kPrivetInfo &&
154 !delegate_->CheckXPrivetTokenHeader(iter->second)) {
155 server_->Send(connection_id, net::HTTP_OK,
156 "{\"error\":\"invalid_x_privet_token\"}",
162 std::string response;
163 net::HttpStatusCode status_code = ProcessHttpRequest(url, info, &response);
165 server_->Send(connection_id, status_code, response, "application/json");
168 void PrivetHttpServer::OnWebSocketRequest(
170 const net::HttpServerRequestInfo& info) {
173 void PrivetHttpServer::OnWebSocketMessage(int connection_id,
174 const std::string& data) {
177 void PrivetHttpServer::OnClose(int connection_id) {
180 void PrivetHttpServer::ReportInvalidMethod(int connection_id) {
181 server_->Send(connection_id, net::HTTP_BAD_REQUEST, "Invalid method",
185 bool PrivetHttpServer::ValidateRequestMethod(int connection_id,
186 const std::string& request,
187 const std::string& method) {
188 DCHECK(!IsGetMethod(request) || !IsPostMethod(request));
190 if (!IsGetMethod(request) && !IsPostMethod(request)) {
191 server_->Send404(connection_id);
195 if (CommandLine::ForCurrentProcess()->HasSwitch("disable-method-check"))
198 if ((IsGetMethod(request) && method != "GET") ||
199 (IsPostMethod(request) && method != "POST")) {
200 ReportInvalidMethod(connection_id);
207 net::HttpStatusCode PrivetHttpServer::ProcessHttpRequest(
209 const net::HttpServerRequestInfo& info,
210 std::string* response) {
211 net::HttpStatusCode status_code = net::HTTP_OK;
212 scoped_ptr<base::DictionaryValue> json_response;
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);
230 if (!json_response) {
235 base::JSONWriter::WriteWithOptions(json_response.get(),
236 base::JSONWriter::OPTIONS_PRETTY_PRINT,
241 // Privet API methods:
243 scoped_ptr<base::DictionaryValue> PrivetHttpServer::ProcessInfo(
244 net::HttpStatusCode* status_code) const {
247 delegate_->CreateInfo(&info);
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);
265 for (size_t i = 0; i < info.api.size(); ++i)
266 api.AppendString(info.api[i]);
267 response->Set("api", api.DeepCopy());
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());
274 *status_code = net::HTTP_OK;
275 return response.Pass();
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>();
284 return make_scoped_ptr(delegate_->GetCapabilities().DeepCopy());
287 scoped_ptr<base::DictionaryValue> PrivetHttpServer::ProcessCreateJob(
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>();
297 // TODO(maksymb): Use base::Time for expiration
299 // TODO(maksymb): Use base::TimeDelta for timeout values
300 int error_timeout = -1;
301 std::string error_description;
303 LocalPrintJob::CreateResult result =
304 delegate_->CreateJob(body, &job_id, &expires_in,
305 &error_timeout, &error_description);
307 scoped_ptr<base::DictionaryValue> response;
308 *status_code = net::HTTP_OK;
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();
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);
323 return scoped_ptr<base::DictionaryValue>();
326 scoped_ptr<base::DictionaryValue> PrivetHttpServer::ProcessSubmitDoc(
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>();
335 using net::GetValueForKeyInQuery;
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;
351 // TODO(maksymb): Use base::Time for expiration
353 std::string error_description;
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);
362 *status_code = net::HTTP_OK;
363 scoped_ptr<base::DictionaryValue> response(new base::DictionaryValue);
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);
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();
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);
390 return scoped_ptr<base::DictionaryValue>();
394 scoped_ptr<base::DictionaryValue> PrivetHttpServer::ProcessJobState(
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>();
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");
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();
415 scoped_ptr<base::DictionaryValue> PrivetHttpServer::ProcessRegister(
417 net::HttpStatusCode* status_code) const {
418 if (delegate_->IsRegistered()) {
419 *status_code = net::HTTP_NOT_FOUND;
420 return scoped_ptr<base::DictionaryValue>();
425 bool params_present =
426 net::GetValueForKeyInQuery(url, "action", &action) &&
427 net::GetValueForKeyInQuery(url, "user", &user) &&
430 RegistrationErrorStatus status = REG_ERROR_INVALID_PARAMS;
431 scoped_ptr<base::DictionaryValue> response(new base::DictionaryValue);
433 if (params_present) {
434 response->SetString("action", action);
435 response->SetString("user", user);
437 if (action == "start")
438 status = delegate_->RegistrationStart(user);
440 if (action == "getClaimToken") {
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);
448 if (action == "complete") {
449 std::string device_id;
450 status = delegate_->RegistrationComplete(user, &device_id);
451 response->SetString("device_id", device_id);
454 if (action == "cancel")
455 status = delegate_->RegistrationCancel(user);
458 if (status != REG_ERROR_OK)
461 ProcessRegistrationStatus(status, &response);
462 *status_code = net::HTTP_OK;
463 return response.Pass();
466 void PrivetHttpServer::ProcessRegistrationStatus(
467 RegistrationErrorStatus status,
468 scoped_ptr<base::DictionaryValue>* current_response) const {
471 DCHECK(*current_response) << "Response shouldn't be empty.";
474 case REG_ERROR_INVALID_PARAMS:
475 *current_response = CreateError("invalid_params");
477 case REG_ERROR_DEVICE_BUSY:
478 *current_response = CreateErrorWithTimeout("device_busy",
481 case REG_ERROR_PENDING_USER_ACTION:
482 *current_response = CreateErrorWithTimeout("pending_user_action",
483 kPendingUserActionTimeout);
485 case REG_ERROR_USER_CANCEL:
486 *current_response = CreateError("user_cancel");
488 case REG_ERROR_CONFIRMATION_TIMEOUT:
489 *current_response = CreateError("confirmation_timeout");
491 case REG_ERROR_INVALID_ACTION:
492 *current_response = CreateError("invalid_action");
494 case REG_ERROR_OFFLINE:
495 *current_response = CreateError("offline");
498 case REG_ERROR_SERVER_ERROR: {
499 std::string description;
500 delegate_->GetRegistrationServerError(&description);
501 *current_response = CreateErrorWithDescription("server_error",