1 // 2 // Copyright (C) 2012 The Android Open Source Project 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 // 16 17 // This file implements a simple HTTP server. It can exhibit odd behavior 18 // that's useful for testing. For example, it's useful to test that 19 // the updater can continue a connection if it's dropped, or that it 20 // handles very slow data transfers. 21 22 // To use this, simply make an HTTP connection to localhost:port and 23 // GET a url. 24 25 #include <err.h> 26 #include <errno.h> 27 #include <fcntl.h> 28 #include <inttypes.h> 29 #include <netinet/in.h> 30 #include <signal.h> 31 #include <stdio.h> 32 #include <stdlib.h> 33 #include <string.h> 34 #include <sys/socket.h> 35 #include <sys/stat.h> 36 #include <sys/types.h> 37 #include <unistd.h> 38 39 #include <algorithm> 40 #include <string> 41 #include <vector> 42 43 #include <base/logging.h> 44 #include <base/posix/eintr_wrapper.h> 45 #include <base/strings/string_split.h> 46 #include <base/strings/string_util.h> 47 #include <base/strings/stringprintf.h> 48 49 #include "update_engine/common/http_common.h" 50 51 52 // HTTP end-of-line delimiter; sorry, this needs to be a macro. 53 #define EOL "\r\n" 54 55 using std::string; 56 using std::vector; 57 58 59 namespace chromeos_update_engine { 60 61 static const char* kListeningMsgPrefix = "listening on port "; 62 63 enum { 64 RC_OK = 0, 65 RC_BAD_ARGS, 66 RC_ERR_READ, 67 RC_ERR_SETSOCKOPT, 68 RC_ERR_BIND, 69 RC_ERR_LISTEN, 70 RC_ERR_GETSOCKNAME, 71 RC_ERR_REPORT, 72 }; 73 74 struct HttpRequest { 75 string raw_headers; 76 string host; 77 string url; 78 off_t start_offset{0}; 79 off_t end_offset{0}; // non-inclusive, zero indicates unspecified. 80 HttpResponseCode return_code{kHttpResponseOk}; 81 }; 82 83 bool ParseRequest(int fd, HttpRequest* request) { 84 string headers; 85 do { 86 char buf[1024]; 87 ssize_t r = read(fd, buf, sizeof(buf)); 88 if (r < 0) { 89 perror("read"); 90 exit(RC_ERR_READ); 91 } 92 headers.append(buf, r); 93 } while (!base::EndsWith(headers, EOL EOL, base::CompareCase::SENSITIVE)); 94 95 LOG(INFO) << "got headers:\n--8<------8<------8<------8<----\n" 96 << headers 97 << "\n--8<------8<------8<------8<----"; 98 request->raw_headers = headers; 99 100 // Break header into lines. 101 vector<string> lines; 102 base::SplitStringUsingSubstr( 103 headers.substr(0, headers.length() - strlen(EOL EOL)), EOL, &lines); 104 105 // Decode URL line. 106 vector<string> terms = base::SplitString(lines[0], base::kWhitespaceASCII, 107 base::KEEP_WHITESPACE, 108 base::SPLIT_WANT_NONEMPTY); 109 CHECK_EQ(terms.size(), static_cast<vector<string>::size_type>(3)); 110 CHECK_EQ(terms[0], "GET"); 111 request->url = terms[1]; 112 LOG(INFO) << "URL: " << request->url; 113 114 // Decode remaining lines. 115 size_t i; 116 for (i = 1; i < lines.size(); i++) { 117 terms = base::SplitString(lines[i], base::kWhitespaceASCII, 118 base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); 119 120 if (terms[0] == "Range:") { 121 CHECK_EQ(terms.size(), static_cast<vector<string>::size_type>(2)); 122 string &range = terms[1]; 123 LOG(INFO) << "range attribute: " << range; 124 CHECK(base::StartsWith(range, "bytes=", base::CompareCase::SENSITIVE) && 125 range.find('-') != string::npos); 126 request->start_offset = atoll(range.c_str() + strlen("bytes=")); 127 // Decode end offset and increment it by one (so it is non-inclusive). 128 if (range.find('-') < range.length() - 1) 129 request->end_offset = atoll(range.c_str() + range.find('-') + 1) + 1; 130 request->return_code = kHttpResponsePartialContent; 131 string tmp_str = base::StringPrintf("decoded range offsets: " 132 "start=%jd end=", 133 (intmax_t)request->start_offset); 134 if (request->end_offset > 0) 135 base::StringAppendF(&tmp_str, "%jd (non-inclusive)", 136 (intmax_t)request->end_offset); 137 else 138 base::StringAppendF(&tmp_str, "unspecified"); 139 LOG(INFO) << tmp_str; 140 } else if (terms[0] == "Host:") { 141 CHECK_EQ(terms.size(), static_cast<vector<string>::size_type>(2)); 142 request->host = terms[1]; 143 LOG(INFO) << "host attribute: " << request->host; 144 } else { 145 LOG(WARNING) << "ignoring HTTP attribute: `" << lines[i] << "'"; 146 } 147 } 148 149 return true; 150 } 151 152 string Itoa(off_t num) { 153 char buf[100] = {0}; 154 snprintf(buf, sizeof(buf), "%" PRIi64, num); 155 return buf; 156 } 157 158 // Writes a string into a file. Returns total number of bytes written or -1 if a 159 // write error occurred. 160 ssize_t WriteString(int fd, const string& str) { 161 const size_t total_size = str.size(); 162 size_t remaining_size = total_size; 163 char const *data = str.data(); 164 165 while (remaining_size) { 166 ssize_t written = write(fd, data, remaining_size); 167 if (written < 0) { 168 perror("write"); 169 LOG(INFO) << "write failed"; 170 return -1; 171 } 172 data += written; 173 remaining_size -= written; 174 } 175 176 return total_size; 177 } 178 179 // Writes the headers of an HTTP response into a file. 180 ssize_t WriteHeaders(int fd, const off_t start_offset, const off_t end_offset, 181 HttpResponseCode return_code) { 182 ssize_t written = 0, ret; 183 184 ret = WriteString(fd, 185 string("HTTP/1.1 ") + Itoa(return_code) + " " + 186 GetHttpResponseDescription(return_code) + 187 EOL 188 "Content-Type: application/octet-stream" EOL); 189 if (ret < 0) 190 return -1; 191 written += ret; 192 193 // Compute content legnth. 194 const off_t content_length = end_offset - start_offset;; 195 196 // A start offset that equals the end offset indicates that the response 197 // should contain the full range of bytes in the requested resource. 198 if (start_offset || start_offset == end_offset) { 199 ret = WriteString(fd, 200 string("Accept-Ranges: bytes" EOL 201 "Content-Range: bytes ") + 202 Itoa(start_offset == end_offset ? 0 : start_offset) + 203 "-" + Itoa(end_offset - 1) + "/" + Itoa(end_offset) + 204 EOL); 205 if (ret < 0) 206 return -1; 207 written += ret; 208 } 209 210 ret = WriteString(fd, string("Content-Length: ") + Itoa(content_length) + 211 EOL EOL); 212 if (ret < 0) 213 return -1; 214 written += ret; 215 216 return written; 217 } 218 219 // Writes a predetermined payload of lines of ascending bytes to a file. The 220 // first byte of output is appropriately offset with respect to the request line 221 // length. Returns the number of successfully written bytes. 222 size_t WritePayload(int fd, const off_t start_offset, const off_t end_offset, 223 const char first_byte, const size_t line_len) { 224 CHECK_LE(start_offset, end_offset); 225 CHECK_GT(line_len, static_cast<size_t>(0)); 226 227 LOG(INFO) << "writing payload: " << line_len << "-byte lines starting with `" 228 << first_byte << "', offset range " << start_offset << " -> " 229 << end_offset; 230 231 // Populate line of ascending characters. 232 string line; 233 line.reserve(line_len); 234 char byte = first_byte; 235 size_t i; 236 for (i = 0; i < line_len; i++) 237 line += byte++; 238 239 const size_t total_len = end_offset - start_offset; 240 size_t remaining_len = total_len; 241 bool success = true; 242 243 // If start offset is not aligned with line boundary, output partial line up 244 // to the first line boundary. 245 size_t start_modulo = start_offset % line_len; 246 if (start_modulo) { 247 string partial = line.substr(start_modulo, remaining_len); 248 ssize_t ret = WriteString(fd, partial); 249 if ((success = (ret >= 0 && (size_t) ret == partial.length()))) 250 remaining_len -= partial.length(); 251 } 252 253 // Output full lines up to the maximal line boundary below the end offset. 254 while (success && remaining_len >= line_len) { 255 ssize_t ret = WriteString(fd, line); 256 if ((success = (ret >= 0 && (size_t) ret == line_len))) 257 remaining_len -= line_len; 258 } 259 260 // Output a partial line up to the end offset. 261 if (success && remaining_len) { 262 string partial = line.substr(0, remaining_len); 263 ssize_t ret = WriteString(fd, partial); 264 if ((success = (ret >= 0 && (size_t) ret == partial.length()))) 265 remaining_len -= partial.length(); 266 } 267 268 return (total_len - remaining_len); 269 } 270 271 // Write default payload lines of the form 'abcdefghij'. 272 inline size_t WritePayload(int fd, const off_t start_offset, 273 const off_t end_offset) { 274 return WritePayload(fd, start_offset, end_offset, 'a', 10); 275 } 276 277 // Send an empty response, then kill the server. 278 void HandleQuit(int fd) { 279 WriteHeaders(fd, 0, 0, kHttpResponseOk); 280 LOG(INFO) << "pid(" << getpid() << "): HTTP server exiting ..."; 281 exit(RC_OK); 282 } 283 284 285 // Generates an HTTP response with payload corresponding to requested offsets 286 // and length. Optionally, truncate the payload at a given length and add a 287 // pause midway through the transfer. Returns the total number of bytes 288 // delivered or -1 for error. 289 ssize_t HandleGet(int fd, const HttpRequest& request, const size_t total_length, 290 const size_t truncate_length, const int sleep_every, 291 const int sleep_secs) { 292 ssize_t ret; 293 size_t written = 0; 294 295 // Obtain start offset, make sure it is within total payload length. 296 const size_t start_offset = request.start_offset; 297 if (start_offset >= total_length) { 298 LOG(WARNING) << "start offset (" << start_offset 299 << ") exceeds total length (" << total_length 300 << "), generating error response (" 301 << kHttpResponseReqRangeNotSat << ")"; 302 return WriteHeaders(fd, total_length, total_length, 303 kHttpResponseReqRangeNotSat); 304 } 305 306 // Obtain end offset, adjust to fit in total payload length and ensure it does 307 // not preceded the start offset. 308 size_t end_offset = (request.end_offset > 0 ? 309 request.end_offset : total_length); 310 if (end_offset < start_offset) { 311 LOG(WARNING) << "end offset (" << end_offset << ") precedes start offset (" 312 << start_offset << "), generating error response"; 313 return WriteHeaders(fd, 0, 0, kHttpResponseBadRequest); 314 } 315 if (end_offset > total_length) { 316 LOG(INFO) << "requested end offset (" << end_offset 317 << ") exceeds total length (" << total_length << "), adjusting"; 318 end_offset = total_length; 319 } 320 321 // Generate headers 322 LOG(INFO) << "generating response header: range=" << start_offset << "-" 323 << (end_offset - 1) << "/" << (end_offset - start_offset) 324 << ", return code=" << request.return_code; 325 if ((ret = WriteHeaders(fd, start_offset, end_offset, 326 request.return_code)) < 0) 327 return -1; 328 LOG(INFO) << ret << " header bytes written"; 329 written += ret; 330 331 // Compute payload length, truncate as necessary. 332 size_t payload_length = end_offset - start_offset; 333 if (truncate_length > 0 && truncate_length < payload_length) { 334 LOG(INFO) << "truncating request payload length (" << payload_length 335 << ") at " << truncate_length; 336 payload_length = truncate_length; 337 end_offset = start_offset + payload_length; 338 } 339 340 LOG(INFO) << "generating response payload: range=" << start_offset << "-" 341 << (end_offset - 1) << "/" << (end_offset - start_offset); 342 343 // Decide about optional midway delay. 344 if (truncate_length > 0 && sleep_every > 0 && sleep_secs >= 0 && 345 start_offset % (truncate_length * sleep_every) == 0) { 346 const off_t midway_offset = start_offset + payload_length / 2; 347 348 if ((ret = WritePayload(fd, start_offset, midway_offset)) < 0) 349 return -1; 350 LOG(INFO) << ret << " payload bytes written (first chunk)"; 351 written += ret; 352 353 LOG(INFO) << "sleeping for " << sleep_secs << " seconds..."; 354 sleep(sleep_secs); 355 356 if ((ret = WritePayload(fd, midway_offset, end_offset)) < 0) 357 return -1; 358 LOG(INFO) << ret << " payload bytes written (second chunk)"; 359 written += ret; 360 } else { 361 if ((ret = WritePayload(fd, start_offset, end_offset)) < 0) 362 return -1; 363 LOG(INFO) << ret << " payload bytes written"; 364 written += ret; 365 } 366 367 LOG(INFO) << "response generation complete, " << written 368 << " total bytes written"; 369 return written; 370 } 371 372 ssize_t HandleGet(int fd, const HttpRequest& request, 373 const size_t total_length) { 374 return HandleGet(fd, request, total_length, 0, 0, 0); 375 } 376 377 // Handles /redirect/<code>/<url> requests by returning the specified 378 // redirect <code> with a location pointing to /<url>. 379 void HandleRedirect(int fd, const HttpRequest& request) { 380 LOG(INFO) << "Redirecting..."; 381 string url = request.url; 382 CHECK_EQ(static_cast<size_t>(0), url.find("/redirect/")); 383 url.erase(0, strlen("/redirect/")); 384 string::size_type url_start = url.find('/'); 385 CHECK_NE(url_start, string::npos); 386 HttpResponseCode code = StringToHttpResponseCode(url.c_str()); 387 url.erase(0, url_start); 388 url = "http://" + request.host + url; 389 const char *status = GetHttpResponseDescription(code); 390 if (!status) 391 CHECK(false) << "Unrecognized redirection code: " << code; 392 LOG(INFO) << "Code: " << code << " " << status; 393 LOG(INFO) << "New URL: " << url; 394 395 ssize_t ret; 396 if ((ret = WriteString(fd, "HTTP/1.1 " + Itoa(code) + " " + 397 status + EOL)) < 0) 398 return; 399 WriteString(fd, "Location: " + url + EOL); 400 } 401 402 // Generate a page not found error response with actual text payload. Return 403 // number of bytes written or -1 for error. 404 ssize_t HandleError(int fd, const HttpRequest& request) { 405 LOG(INFO) << "Generating error HTTP response"; 406 407 ssize_t ret; 408 size_t written = 0; 409 410 const string data("This is an error page."); 411 412 if ((ret = WriteHeaders(fd, 0, data.size(), kHttpResponseNotFound)) < 0) 413 return -1; 414 written += ret; 415 416 if ((ret = WriteString(fd, data)) < 0) 417 return -1; 418 written += ret; 419 420 return written; 421 } 422 423 // Generate an error response if the requested offset is nonzero, up to a given 424 // maximal number of successive failures. The error generated is an "Internal 425 // Server Error" (500). 426 ssize_t HandleErrorIfOffset(int fd, const HttpRequest& request, 427 size_t end_offset, int max_fails) { 428 static int num_fails = 0; 429 430 if (request.start_offset > 0 && num_fails < max_fails) { 431 LOG(INFO) << "Generating error HTTP response"; 432 433 ssize_t ret; 434 size_t written = 0; 435 436 const string data("This is an error page."); 437 438 if ((ret = WriteHeaders(fd, 0, data.size(), 439 kHttpResponseInternalServerError)) < 0) 440 return -1; 441 written += ret; 442 443 if ((ret = WriteString(fd, data)) < 0) 444 return -1; 445 written += ret; 446 447 num_fails++; 448 return written; 449 } else { 450 num_fails = 0; 451 return HandleGet(fd, request, end_offset); 452 } 453 } 454 455 // Returns a valid response echoing in the body of the response all the headers 456 // sent by the client. 457 void HandleEchoHeaders(int fd, const HttpRequest& request) { 458 WriteHeaders(fd, 0, request.raw_headers.size(), kHttpResponseOk); 459 WriteString(fd, request.raw_headers); 460 } 461 462 void HandleHang(int fd) { 463 LOG(INFO) << "Hanging until the other side of the connection is closed."; 464 char c; 465 while (HANDLE_EINTR(read(fd, &c, 1)) > 0) {} 466 } 467 468 void HandleDefault(int fd, const HttpRequest& request) { 469 const off_t start_offset = request.start_offset; 470 const string data("unhandled path"); 471 const size_t size = data.size(); 472 ssize_t ret; 473 474 if ((ret = WriteHeaders(fd, start_offset, size, request.return_code)) < 0) 475 return; 476 WriteString(fd, (start_offset < static_cast<off_t>(size) ? 477 data.substr(start_offset) : "")); 478 } 479 480 481 // Break a URL into terms delimited by slashes. 482 class UrlTerms { 483 public: 484 UrlTerms(const string &url, size_t num_terms) { 485 // URL must be non-empty and start with a slash. 486 CHECK_GT(url.size(), static_cast<size_t>(0)); 487 CHECK_EQ(url[0], '/'); 488 489 // Split it into terms delimited by slashes, omitting the preceding slash. 490 terms = base::SplitString(url.substr(1), "/", base::KEEP_WHITESPACE, 491 base::SPLIT_WANT_ALL); 492 493 // Ensure expected length. 494 CHECK_EQ(terms.size(), num_terms); 495 } 496 497 inline string Get(const off_t index) const { 498 return terms[index]; 499 } 500 inline const char *GetCStr(const off_t index) const { 501 return Get(index).c_str(); 502 } 503 inline int GetInt(const off_t index) const { 504 return atoi(GetCStr(index)); 505 } 506 inline size_t GetSizeT(const off_t index) const { 507 return static_cast<size_t>(atol(GetCStr(index))); 508 } 509 510 private: 511 vector<string> terms; 512 }; 513 514 void HandleConnection(int fd) { 515 HttpRequest request; 516 ParseRequest(fd, &request); 517 518 string &url = request.url; 519 LOG(INFO) << "pid(" << getpid() << "): handling url " << url; 520 if (url == "/quitquitquit") { 521 HandleQuit(fd); 522 } else if (base::StartsWith( 523 url, "/download/", base::CompareCase::SENSITIVE)) { 524 const UrlTerms terms(url, 2); 525 HandleGet(fd, request, terms.GetSizeT(1)); 526 } else if (base::StartsWith(url, "/flaky/", base::CompareCase::SENSITIVE)) { 527 const UrlTerms terms(url, 5); 528 HandleGet(fd, request, terms.GetSizeT(1), terms.GetSizeT(2), 529 terms.GetInt(3), terms.GetInt(4)); 530 } else if (url.find("/redirect/") == 0) { 531 HandleRedirect(fd, request); 532 } else if (url == "/error") { 533 HandleError(fd, request); 534 } else if (base::StartsWith(url, "/error-if-offset/", 535 base::CompareCase::SENSITIVE)) { 536 const UrlTerms terms(url, 3); 537 HandleErrorIfOffset(fd, request, terms.GetSizeT(1), terms.GetInt(2)); 538 } else if (url == "/echo-headers") { 539 HandleEchoHeaders(fd, request); 540 } else if (url == "/hang") { 541 HandleHang(fd); 542 } else { 543 HandleDefault(fd, request); 544 } 545 546 close(fd); 547 } 548 549 } // namespace chromeos_update_engine 550 551 using namespace chromeos_update_engine; // NOLINT(build/namespaces) 552 553 554 void usage(const char *prog_arg) { 555 fprintf( 556 stderr, 557 "Usage: %s [ FILE ]\n" 558 "Once accepting connections, the following is written to FILE (or " 559 "stdout):\n" 560 "\"%sN\" (where N is an integer port number)\n", 561 basename(prog_arg), kListeningMsgPrefix); 562 } 563 564 int main(int argc, char** argv) { 565 // Check invocation. 566 if (argc > 2) 567 errx(RC_BAD_ARGS, "unexpected number of arguments (use -h for usage)"); 568 569 // Parse (optional) argument. 570 int report_fd = STDOUT_FILENO; 571 if (argc == 2) { 572 if (!strcmp(argv[1], "-h")) { 573 usage(argv[0]); 574 exit(RC_OK); 575 } 576 577 report_fd = open(argv[1], O_WRONLY | O_CREAT, 00644); 578 } 579 580 // Ignore SIGPIPE on write() to sockets. 581 signal(SIGPIPE, SIG_IGN); 582 583 int listen_fd = socket(AF_INET, SOCK_STREAM, 0); 584 if (listen_fd < 0) 585 LOG(FATAL) << "socket() failed"; 586 587 struct sockaddr_in server_addr = sockaddr_in(); 588 server_addr.sin_family = AF_INET; 589 server_addr.sin_addr.s_addr = INADDR_ANY; 590 server_addr.sin_port = 0; 591 592 { 593 // Get rid of "Address in use" error 594 int tr = 1; 595 if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &tr, 596 sizeof(int)) == -1) { 597 perror("setsockopt"); 598 exit(RC_ERR_SETSOCKOPT); 599 } 600 } 601 602 // Bind the socket and set for listening. 603 if (bind(listen_fd, reinterpret_cast<struct sockaddr *>(&server_addr), 604 sizeof(server_addr)) < 0) { 605 perror("bind"); 606 exit(RC_ERR_BIND); 607 } 608 if (listen(listen_fd, 5) < 0) { 609 perror("listen"); 610 exit(RC_ERR_LISTEN); 611 } 612 613 // Check the actual port. 614 struct sockaddr_in bound_addr = sockaddr_in(); 615 socklen_t bound_addr_len = sizeof(bound_addr); 616 if (getsockname(listen_fd, reinterpret_cast<struct sockaddr*>(&bound_addr), 617 &bound_addr_len) < 0) { 618 perror("getsockname"); 619 exit(RC_ERR_GETSOCKNAME); 620 } 621 in_port_t port = ntohs(bound_addr.sin_port); 622 623 // Output the listening port, indicating that the server is processing 624 // requests. IMPORTANT! (a) the format of this message is as expected by some 625 // unit tests, avoid unilateral changes; (b) it is necessary to flush/sync the 626 // file to prevent the spawning process from waiting indefinitely for this 627 // message. 628 string listening_msg = base::StringPrintf("%s%hu", kListeningMsgPrefix, port); 629 LOG(INFO) << listening_msg; 630 CHECK_EQ(write(report_fd, listening_msg.c_str(), listening_msg.length()), 631 static_cast<int>(listening_msg.length())); 632 CHECK_EQ(write(report_fd, "\n", 1), 1); 633 if (report_fd == STDOUT_FILENO) 634 fsync(report_fd); 635 else 636 close(report_fd); 637 638 while (1) { 639 LOG(INFO) << "pid(" << getpid() << "): waiting to accept new connection"; 640 int client_fd = accept(listen_fd, nullptr, nullptr); 641 LOG(INFO) << "got past accept"; 642 if (client_fd < 0) 643 LOG(FATAL) << "ERROR on accept"; 644 HandleConnection(client_fd); 645 } 646 return 0; 647 } 648