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