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