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