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