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 "net/base/ip_endpoint.h" 10 #include "net/base/net_errors.h" 11 #include "net/base/url_util.h" 12 #include "net/socket/tcp_listen_socket.h" 13 #include "url/gurl.h" 14 15 namespace { 16 17 const int kDeviceBusyTimeout = 30; // in seconds 18 const int kPendingUserActionTimeout = 5; // in seconds 19 20 // {"error":|error_type|} 21 scoped_ptr<base::DictionaryValue> CreateError(const std::string& error_type) { 22 scoped_ptr<base::DictionaryValue> error(new base::DictionaryValue); 23 error->SetString("error", error_type); 24 25 return error.Pass(); 26 } 27 28 // {"error":|error_type|, "description":|description|} 29 scoped_ptr<base::DictionaryValue> CreateErrorWithDescription( 30 const std::string& error_type, 31 const std::string& description) { 32 scoped_ptr<base::DictionaryValue> error(CreateError(error_type)); 33 error->SetString("description", description); 34 return error.Pass(); 35 } 36 37 // {"error":|error_type|, "timeout":|timout|} 38 scoped_ptr<base::DictionaryValue> CreateErrorWithTimeout( 39 const std::string& error_type, 40 int timeout) { 41 scoped_ptr<base::DictionaryValue> error(CreateError(error_type)); 42 error->SetInteger("timeout", timeout); 43 return error.Pass(); 44 } 45 46 // Returns |true| if |request| should be GET method. 47 bool IsGetMethod(const std::string& request) { 48 return request == "/privet/info"/* || 49 request == "/privet/accesstoken" || 50 request == "/privet/capabilities" || 51 request == "/privet/printer/jobstate"*/; 52 } 53 54 // Returns |true| if |request| should be POST method. 55 bool IsPostMethod(const std::string& request) { 56 return request == "/privet/register"/* || 57 request == "/privet/printer/createjob" || 58 request == "/privet/printer/submitdoc"*/; 59 } 60 61 } // namespace 62 63 PrivetHttpServer::DeviceInfo::DeviceInfo() : uptime(0) { 64 } 65 66 PrivetHttpServer::DeviceInfo::~DeviceInfo() { 67 } 68 69 PrivetHttpServer::PrivetHttpServer(Delegate* delegate) 70 : port_(0), 71 delegate_(delegate) { 72 } 73 74 PrivetHttpServer::~PrivetHttpServer() { 75 Shutdown(); 76 } 77 78 bool PrivetHttpServer::Start(uint16 port) { 79 if (server_) 80 return true; 81 82 net::TCPListenSocketFactory factory("0.0.0.0", port); 83 server_ = new net::HttpServer(factory, this); 84 net::IPEndPoint address; 85 86 if (server_->GetLocalAddress(&address) != net::OK) { 87 NOTREACHED() << "Cannot start HTTP server"; 88 return false; 89 } 90 91 VLOG(1) << "Address of HTTP server: " << address.ToString(); 92 return true; 93 } 94 95 void PrivetHttpServer::Shutdown() { 96 if (!server_) 97 return; 98 99 server_ = NULL; 100 } 101 102 void PrivetHttpServer::OnHttpRequest(int connection_id, 103 const net::HttpServerRequestInfo& info) { 104 VLOG(1) << "Processing HTTP request: " << info.path; 105 GURL url("http://host" + info.path); 106 107 if (!ValidateRequestMethod(connection_id, url.path(), info.method)) 108 return; 109 110 if (!CommandLine::ForCurrentProcess()->HasSwitch("disable-x-token")) { 111 net::HttpServerRequestInfo::HeadersMap::const_iterator iter = 112 info.headers.find("x-privet-token"); 113 if (iter == info.headers.end()) { 114 server_->Send(connection_id, net::HTTP_BAD_REQUEST, 115 "Missing X-Privet-Token header.\n" 116 "TODO: Message should be in header, not in the body!", 117 "text/plain"); 118 return; 119 } 120 121 if (url.path() != "/privet/info" && 122 !delegate_->CheckXPrivetTokenHeader(iter->second)) { 123 server_->Send(connection_id, net::HTTP_OK, 124 "{\"error\":\"invalid_x_privet_token\"}", 125 "application/json"); 126 return; 127 } 128 } 129 130 std::string response; 131 net::HttpStatusCode status_code = 132 ProcessHttpRequest(url, info.data, &response); 133 134 server_->Send(connection_id, status_code, response, "application/json"); 135 } 136 137 void PrivetHttpServer::OnWebSocketRequest( 138 int connection_id, 139 const net::HttpServerRequestInfo& info) { 140 } 141 142 void PrivetHttpServer::OnWebSocketMessage(int connection_id, 143 const std::string& data) { 144 } 145 146 void PrivetHttpServer::OnClose(int connection_id) { 147 } 148 149 void PrivetHttpServer::ReportInvalidMethod(int connection_id) { 150 server_->Send(connection_id, net::HTTP_BAD_REQUEST, "Invalid method", 151 "text/plain"); 152 } 153 154 bool PrivetHttpServer::ValidateRequestMethod(int connection_id, 155 const std::string& request, 156 const std::string& method) { 157 DCHECK(!IsGetMethod(request) || !IsPostMethod(request)); 158 159 if (!IsGetMethod(request) && !IsPostMethod(request)) { 160 server_->Send404(connection_id); 161 return false; 162 } 163 164 if (CommandLine::ForCurrentProcess()->HasSwitch("disable-method-check")) 165 return true; 166 167 if ((IsGetMethod(request) && method != "GET") || 168 (IsPostMethod(request) && method != "POST")) { 169 ReportInvalidMethod(connection_id); 170 return false; 171 } 172 173 return true; 174 } 175 176 net::HttpStatusCode PrivetHttpServer::ProcessHttpRequest( 177 const GURL& url, 178 const std::string& data, 179 std::string* response) { 180 net::HttpStatusCode status_code = net::HTTP_OK; 181 scoped_ptr<base::DictionaryValue> json_response; 182 183 if (url.path() == "/privet/info") { 184 json_response = ProcessInfo(&status_code); 185 } else if (url.path() == "/privet/register") { 186 json_response = ProcessRegister(url, &status_code); 187 } else { 188 NOTREACHED(); 189 } 190 191 if (!json_response) { 192 response->clear(); 193 return status_code; 194 } 195 196 base::JSONWriter::WriteWithOptions(json_response.get(), 197 base::JSONWriter::OPTIONS_PRETTY_PRINT, 198 response); 199 return status_code; 200 } 201 202 // Privet API methods: 203 204 scoped_ptr<base::DictionaryValue> PrivetHttpServer::ProcessInfo( 205 net::HttpStatusCode* status_code) const { 206 207 DeviceInfo info; 208 delegate_->CreateInfo(&info); 209 210 scoped_ptr<base::DictionaryValue> response(new base::DictionaryValue); 211 response->SetString("version", info.version); 212 response->SetString("name", info.name); 213 response->SetString("description", info.description); 214 response->SetString("url", info.url); 215 response->SetString("id", info.id); 216 response->SetString("device_state", info.device_state); 217 response->SetString("connection_state", info.connection_state); 218 response->SetString("manufacturer", info.manufacturer); 219 response->SetString("model", info.model); 220 response->SetString("serial_number", info.serial_number); 221 response->SetString("firmware", info.firmware); 222 response->SetInteger("uptime", info.uptime); 223 response->SetString("x-privet-token", info.x_privet_token); 224 225 base::ListValue api; 226 for (size_t i = 0; i < info.api.size(); ++i) 227 api.AppendString(info.api[i]); 228 response->Set("api", api.DeepCopy()); 229 230 base::ListValue type; 231 for (size_t i = 0; i < info.type.size(); ++i) 232 type.AppendString(info.type[i]); 233 response->Set("type", type.DeepCopy()); 234 235 *status_code = net::HTTP_OK; 236 return response.Pass(); 237 } 238 239 scoped_ptr<base::DictionaryValue> PrivetHttpServer::ProcessRegister( 240 const GURL& url, 241 net::HttpStatusCode* status_code) { 242 if (delegate_->IsRegistered()) { 243 *status_code = net::HTTP_NOT_FOUND; 244 return scoped_ptr<base::DictionaryValue>(); 245 } 246 247 std::string action; 248 std::string user; 249 bool params_present = 250 net::GetValueForKeyInQuery(url, "action", &action) && 251 net::GetValueForKeyInQuery(url, "user", &user) && 252 !user.empty(); 253 254 RegistrationErrorStatus status = REG_ERROR_INVALID_PARAMS; 255 scoped_ptr<base::DictionaryValue> response(new DictionaryValue); 256 257 if (params_present) { 258 response->SetString("action", action); 259 response->SetString("user", user); 260 261 if (action == "start") 262 status = delegate_->RegistrationStart(user); 263 264 if (action == "getClaimToken") { 265 std::string token; 266 std::string claim_url; 267 status = delegate_->RegistrationGetClaimToken(user, &token, &claim_url); 268 response->SetString("token", token); 269 response->SetString("claim_url", claim_url); 270 } 271 272 if (action == "complete") { 273 std::string device_id; 274 status = delegate_->RegistrationComplete(user, &device_id); 275 response->SetString("device_id", device_id); 276 } 277 278 if (action == "cancel") 279 status = delegate_->RegistrationCancel(user); 280 } 281 282 if (status != REG_ERROR_OK) 283 response.reset(); 284 285 ProcessRegistrationStatus(status, &response); 286 *status_code = net::HTTP_OK; 287 return response.Pass(); 288 } 289 290 void PrivetHttpServer::ProcessRegistrationStatus( 291 RegistrationErrorStatus status, 292 scoped_ptr<base::DictionaryValue>* current_response) const { 293 switch (status) { 294 case REG_ERROR_OK: 295 DCHECK(*current_response) << "Response shouldn't be empty."; 296 break; 297 298 case REG_ERROR_INVALID_PARAMS: 299 *current_response = CreateError("invalid_params"); 300 break; 301 case REG_ERROR_DEVICE_BUSY: 302 *current_response = CreateErrorWithTimeout("device_busy", 303 kDeviceBusyTimeout); 304 break; 305 case REG_ERROR_PENDING_USER_ACTION: 306 *current_response = CreateErrorWithTimeout("pending_user_action", 307 kPendingUserActionTimeout); 308 break; 309 case REG_ERROR_USER_CANCEL: 310 *current_response = CreateError("user_cancel"); 311 break; 312 case REG_ERROR_CONFIRMATION_TIMEOUT: 313 *current_response = CreateError("confirmation_timeout"); 314 break; 315 case REG_ERROR_INVALID_ACTION: 316 *current_response = CreateError("invalid_action"); 317 break; 318 case REG_ERROR_SERVER_ERROR: { 319 std::string description; 320 delegate_->GetRegistrationServerError(&description); 321 *current_response = CreateErrorWithDescription("server_error", 322 description); 323 break; 324 } 325 326 default: 327 NOTREACHED(); 328 }; 329 } 330 331 332