blob: a6410e47fad20ec6517e1c1812162b9266ab8416 [file] [log] [blame]
adlr@google.com3defe6a2009-12-04 20:57:17 +00001// Copyright (c) 2009 The Chromium OS 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// This file implements a simple HTTP server. It can exhibit odd behavior
6// that's useful for testing. For example, it's useful to test that
7// the updater can continue a connection if it's dropped, or that it
8// handles very slow data transfers.
9
10// To use this, simply make an HTTP connection to localhost:port and
11// GET a url.
12
adlr@google.com3defe6a2009-12-04 20:57:17 +000013#include <errno.h>
Andrew de los Reyes08c4e272010-04-15 14:02:17 -070014#include <inttypes.h>
Andrew de los Reyes3fd5d302010-10-07 20:07:18 -070015#include <netinet/in.h>
16#include <signal.h>
adlr@google.com3defe6a2009-12-04 20:57:17 +000017#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
Andrew de los Reyes3fd5d302010-10-07 20:07:18 -070020#include <sys/socket.h>
21#include <sys/types.h>
adlr@google.com3defe6a2009-12-04 20:57:17 +000022#include <unistd.h>
Andrew de los Reyes3fd5d302010-10-07 20:07:18 -070023
adlr@google.com3defe6a2009-12-04 20:57:17 +000024#include <algorithm>
25#include <string>
26#include <vector>
Andrew de los Reyes3fd5d302010-10-07 20:07:18 -070027
28#include <base/logging.h>
Gilad Arnold9bedeb52011-11-17 16:19:57 -080029#include <base/string_split.h>
30#include <base/string_util.h>
31
32#include "update_engine/http_common.h"
33#include "update_engine/http_fetcher_unittest.h"
34
adlr@google.com3defe6a2009-12-04 20:57:17 +000035
36using std::min;
37using std::string;
38using std::vector;
39
Gilad Arnold9bedeb52011-11-17 16:19:57 -080040
adlr@google.com3defe6a2009-12-04 20:57:17 +000041namespace chromeos_update_engine {
42
Gilad Arnold9bedeb52011-11-17 16:19:57 -080043// HTTP end-of-line delimiter; sorry, this needs to be a macro.
44#define EOL "\r\n"
45
adlr@google.com3defe6a2009-12-04 20:57:17 +000046struct HttpRequest {
Gilad Arnold9bedeb52011-11-17 16:19:57 -080047 HttpRequest() : start_offset(0), return_code(kHttpResponseOk) {}
Darin Petkov41c2fcf2010-08-25 13:14:48 -070048 string host;
adlr@google.com3defe6a2009-12-04 20:57:17 +000049 string url;
Gilad Arnold9bedeb52011-11-17 16:19:57 -080050 off_t start_offset;
51 HttpResponseCode return_code;
adlr@google.com3defe6a2009-12-04 20:57:17 +000052};
53
adlr@google.com3defe6a2009-12-04 20:57:17 +000054bool ParseRequest(int fd, HttpRequest* request) {
55 string headers;
Gilad Arnold9bedeb52011-11-17 16:19:57 -080056 do {
57 char buf[1024];
58 ssize_t r = read(fd, buf, sizeof(buf));
adlr@google.com3defe6a2009-12-04 20:57:17 +000059 if (r < 0) {
60 perror("read");
61 exit(1);
62 }
Gilad Arnold9bedeb52011-11-17 16:19:57 -080063 headers.append(buf, r);
64 } while (!EndsWith(headers, EOL EOL, true));
adlr@google.com3defe6a2009-12-04 20:57:17 +000065
Gilad Arnold9bedeb52011-11-17 16:19:57 -080066 LOG(INFO) << "got headers:\n--8<------8<------8<------8<----\n"
67 << headers
68 << "\n--8<------8<------8<------8<----";
adlr@google.com3defe6a2009-12-04 20:57:17 +000069
Gilad Arnold9bedeb52011-11-17 16:19:57 -080070 // Break header into lines.
71 std::vector<string> lines;
72 base::SplitStringUsingSubstr(
73 headers.substr(0, headers.length() - strlen(EOL EOL)), EOL, &lines);
adlr@google.com3defe6a2009-12-04 20:57:17 +000074
Gilad Arnold9bedeb52011-11-17 16:19:57 -080075 // Decode URL line.
76 std::vector<string> terms;
77 base::SplitStringAlongWhitespace(lines[0], &terms);
78 CHECK_EQ(terms.size(), 3);
79 CHECK_EQ(terms[0], "GET");
80 request->url = terms[1];
81 LOG(INFO) << "URL: " << request->url;
adlr@google.com3defe6a2009-12-04 20:57:17 +000082
Gilad Arnold9bedeb52011-11-17 16:19:57 -080083 // Decode remaining lines.
84 size_t i;
85 for (i = 1; i < lines.size(); i++) {
86 std::vector<string> terms;
87 base::SplitStringAlongWhitespace(lines[i], &terms);
Darin Petkov41c2fcf2010-08-25 13:14:48 -070088
Gilad Arnold9bedeb52011-11-17 16:19:57 -080089 if (terms[0] == "Range:") {
90 CHECK_EQ(terms.size(), 2);
91 string &range = terms[1];
92 LOG(INFO) << "range attribute: " << range;
93 CHECK(StartsWithASCII(range, "bytes=", true) &&
94 EndsWith(range, "-", true));
95 request->start_offset = atoll(range.c_str() + strlen("bytes="));
96 request->return_code = kHttpResponsePartialContent;
97 LOG(INFO) << "decoded start offset: " << request->start_offset;
98 } else if (terms[0] == "Host:") {
99 CHECK_EQ(terms.size(), 2);
100 request->host = terms[1];
101 LOG(INFO) << "host attribute: " << request->host;
102 } else {
103 LOG(WARNING) << "ignoring HTTP attribute: `" << lines[i] << "'";
adlr@google.com3defe6a2009-12-04 20:57:17 +0000104 }
adlr@google.com3defe6a2009-12-04 20:57:17 +0000105 }
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800106
Andrew de los Reyes3fd5d302010-10-07 20:07:18 -0700107 return true;
adlr@google.com3defe6a2009-12-04 20:57:17 +0000108}
109
110string Itoa(off_t num) {
111 char buf[100] = {0};
Andrew de los Reyes08c4e272010-04-15 14:02:17 -0700112 snprintf(buf, sizeof(buf), "%" PRIi64, num);
adlr@google.com3defe6a2009-12-04 20:57:17 +0000113 return buf;
114}
115
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800116// Writes a string into a file. Returns total number of bytes written or -1 if a
117// write error occurred.
118ssize_t WriteString(int fd, const string& str) {
119 const size_t total_size = str.size();
120 size_t remaining_size = total_size;
121 char const *data = str.data();
Gilad Arnold48085ba2011-11-16 09:36:08 -0800122
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800123 while (remaining_size) {
124 ssize_t written = write(fd, data, remaining_size);
125 if (written < 0) {
126 perror("write");
127 LOG(INFO) << "write failed";
128 return -1;
129 }
130 data += written;
131 remaining_size -= written;
adlr@google.com3defe6a2009-12-04 20:57:17 +0000132 }
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800133
134 return total_size;
adlr@google.com3defe6a2009-12-04 20:57:17 +0000135}
136
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800137// Writes the headers of an HTTP response into a file.
138ssize_t WriteHeaders(int fd, const off_t start_offset, const off_t end_offset,
139 HttpResponseCode return_code) {
140 ssize_t written = 0, ret;
141
142 ret = WriteString(fd,
143 string("HTTP/1.1 ") + Itoa(return_code) + " " +
144 GetHttpResponseDescription(return_code) +
145 EOL
146 "Content-Type: application/octet-stream" EOL);
147 if (ret < 0)
148 return -1;
149 written += ret;
150
151 off_t content_length = end_offset;
152 if (start_offset) {
153 ret = WriteString(fd,
154 string("Accept-Ranges: bytes" EOL
155 "Content-Range: bytes ") +
156 Itoa(start_offset) + "-" + Itoa(end_offset - 1) + "/" +
157 Itoa(end_offset) + EOL);
158 if (ret < 0)
159 return -1;
160 written += ret;
161
162 content_length -= start_offset;
163 }
164
165 ret = WriteString(fd, string("Content-Length: ") + Itoa(content_length) +
166 EOL EOL);
167 if (ret < 0)
168 return -1;
169 written += ret;
170
171 return written;
172}
173
174// Writes a predetermined payload of lines of ascending bytes to a file. The
175// first byte of output is appropriately offset with respect to the request line
176// length. Returns the number of successfully written bytes.
177size_t WritePayload(int fd, const off_t start_offset, const off_t end_offset,
178 const char first_byte, const size_t line_len) {
179 CHECK_LE(start_offset, end_offset);
180 CHECK_GT(line_len, 0);
181
182 LOG(INFO) << "writing payload: " << line_len << " byte lines starting with `"
183 << first_byte << "', offset range " << start_offset << " -> "
184 << end_offset;
185
186 // Populate line of ascending characters.
187 string line;
188 line.reserve(line_len);
189 char byte = first_byte;
190 size_t i;
191 for (i = 0; i < line_len; i++)
192 line += byte++;
193
194 const size_t total_len = end_offset - start_offset;
195 size_t remaining_len = total_len;
196 bool success = true;
197
198 // If start offset is not aligned with line boundary, output partial line up
199 // to the first line boundary.
200 size_t start_modulo = start_offset % line_len;
201 if (start_modulo) {
202 string partial = line.substr(start_modulo, remaining_len);
203 ssize_t ret = WriteString(fd, partial);
204 if ((success = (ret >= 0 && (size_t) ret == partial.length())))
205 remaining_len -= partial.length();
206 }
207
208 // Output full lines up to the maximal line boundary below the end offset.
209 while (success && remaining_len >= line_len) {
210 ssize_t ret = WriteString(fd, line);
211 if ((success = (ret >= 0 && (size_t) ret == line_len)))
212 remaining_len -= line_len;
213 }
214
215 // Output a partial line up to the end offset.
216 if (success && remaining_len) {
217 string partial = line.substr(0, remaining_len);
218 ssize_t ret = WriteString(fd, partial);
219 if ((success = (ret >= 0 && (size_t) ret == partial.length())))
220 remaining_len -= partial.length();
221 }
222
223 return (total_len - remaining_len);
224}
225
226// Write default payload lines of the form 'abcdefghij'.
227inline size_t WritePayload(int fd, const off_t start_offset,
228 const off_t end_offset) {
229 return WritePayload(fd, start_offset, end_offset, 'a', 10);
230}
231
232// Send an empty response, then kill the server.
233void HandleQuit(int fd) {
234 WriteHeaders(fd, 0, 0, kHttpResponseOk);
adlr@google.com3defe6a2009-12-04 20:57:17 +0000235 exit(0);
236}
237
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800238
239// Generates an HTTP response with payload corresponding to given offset and
240// length. Returns the total number of bytes delivered or -1 for error.
241ssize_t HandleGet(int fd, const HttpRequest& request, const off_t end_offset) {
242 LOG(INFO) << "starting payload";
243 ssize_t written = 0, ret;
244
245 if ((ret = WriteHeaders(fd, request.start_offset, end_offset,
246 request.return_code)) < 0)
247 return -1;
248 written += ret;
249
250 if ((ret = WritePayload(fd, request.start_offset, end_offset)) < 0)
251 return -1;
252 written += ret;
253
254 LOG(INFO) << "payload writing completed, " << written << " bytes written";
255 return written;
adlr@google.com3defe6a2009-12-04 20:57:17 +0000256}
257
adlr@google.com3defe6a2009-12-04 20:57:17 +0000258
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800259// Generates an HTTP response with payload. Payload is truncated at a given
260// length. Transfer may include an optional delay midway.
261ssize_t HandleFlakyGet(int fd, const HttpRequest& request,
262 const off_t max_end_offset, const off_t truncate_length,
263 const int sleep_every, const int sleep_secs) {
264 CHECK_GT(truncate_length, 0);
265 CHECK_GT(sleep_every, 0);
266 CHECK_GE(sleep_secs, 0);
adlr@google.com3defe6a2009-12-04 20:57:17 +0000267
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800268 ssize_t ret;
269 size_t written = 0;
270 const off_t start_offset = request.start_offset;
adlr@google.com3defe6a2009-12-04 20:57:17 +0000271
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800272 if ((ret = WriteHeaders(fd, start_offset, max_end_offset,
273 request.return_code)) < 0)
274 return -1;
275
276 const size_t content_length =
277 min(truncate_length, max_end_offset - start_offset);
278 const off_t end_offset = start_offset + content_length;
279
280 if (start_offset % (truncate_length * sleep_every) == 0) {
281 const size_t midway_content_length = content_length / 2;
282 const off_t midway_offset = start_offset + midway_content_length;
283
284 LOG(INFO) << "writing small data blob of size " << midway_content_length;
285 if ((ret = WritePayload(fd, start_offset, midway_offset)) < 0)
286 return -1;
287 written += ret;
288
289 sleep(sleep_secs);
290
adlr@google.com3defe6a2009-12-04 20:57:17 +0000291 LOG(INFO) << "writing small data blob of size "
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800292 << (content_length - midway_content_length);
293 if ((ret = WritePayload(fd, midway_offset, end_offset)) < 0)
294 return -1;
295 written += ret;
296 } else {
297 LOG(INFO) << "writing data blob of size " << content_length;
298 if ((ret = WritePayload(fd, start_offset, end_offset)) < 0)
299 return -1;
300 written += ret;
adlr@google.com3defe6a2009-12-04 20:57:17 +0000301 }
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800302
303 return written;
adlr@google.com3defe6a2009-12-04 20:57:17 +0000304}
305
Darin Petkov41c2fcf2010-08-25 13:14:48 -0700306// Handles /redirect/<code>/<url> requests by returning the specified
307// redirect <code> with a location pointing to /<url>.
308void HandleRedirect(int fd, const HttpRequest& request) {
309 LOG(INFO) << "Redirecting...";
310 string url = request.url;
311 CHECK_EQ(0, url.find("/redirect/"));
312 url.erase(0, strlen("/redirect/"));
313 string::size_type url_start = url.find('/');
314 CHECK_NE(url_start, string::npos);
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800315 HttpResponseCode code = StringToHttpResponseCode(url.c_str());
Darin Petkov41c2fcf2010-08-25 13:14:48 -0700316 url.erase(0, url_start);
317 url = "http://" + request.host + url;
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800318 const char *status = GetHttpResponseDescription(code);
319 if (!status)
Darin Petkov41c2fcf2010-08-25 13:14:48 -0700320 CHECK(false) << "Unrecognized redirection code: " << code;
Darin Petkov41c2fcf2010-08-25 13:14:48 -0700321 LOG(INFO) << "Code: " << code << " " << status;
322 LOG(INFO) << "New URL: " << url;
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800323
324 ssize_t ret;
325 if ((ret = WriteString(fd, "HTTP/1.1 " + Itoa(code) + " " +
326 status + EOL)) < 0)
327 return;
328 WriteString(fd, "Location: " + url + EOL);
329}
330
331// Generate a page not found error response with actual text payload. Return
332// number of bytes written or -1 for error.
333ssize_t HandleError(int fd, const HttpRequest& request) {
334 LOG(INFO) << "Generating error HTTP response";
335
336 ssize_t ret;
337 size_t written = 0;
338
339 const string data("This is an error page.");
340
341 if ((ret = WriteHeaders(fd, 0, data.size(), kHttpResponseNotFound)) < 0)
342 return -1;
343 written += ret;
344
345 if ((ret = WriteString(fd, data)) < 0)
346 return -1;
347 written += ret;
348
349 return written;
350}
351
352// Generate an error response if the requested offset is nonzero, up to a given
353// maximal number of successive failures. The error generated is an "Internal
354// Server Error" (500).
355ssize_t HandleErrorIfOffset(int fd, const HttpRequest& request,
356 size_t end_offset, int max_fails) {
357 static int num_fails = 0;
358
359 if (request.start_offset > 0 && num_fails < max_fails) {
360 LOG(INFO) << "Generating error HTTP response";
361
362 ssize_t ret;
363 size_t written = 0;
364
365 const string data("This is an error page.");
366
367 if ((ret = WriteHeaders(fd, 0, data.size(),
368 kHttpResponseInternalServerError)) < 0)
369 return -1;
370 written += ret;
371
372 if ((ret = WriteString(fd, data)) < 0)
373 return -1;
374 written += ret;
375
376 num_fails++;
377 return written;
378 } else {
379 num_fails = 0;
380 return HandleGet(fd, request, end_offset);
381 }
Darin Petkov41c2fcf2010-08-25 13:14:48 -0700382}
383
adlr@google.com3defe6a2009-12-04 20:57:17 +0000384void HandleDefault(int fd, const HttpRequest& request) {
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800385 const off_t start_offset = request.start_offset;
adlr@google.com3defe6a2009-12-04 20:57:17 +0000386 const string data("unhandled path");
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800387 const size_t size = data.size();
388 ssize_t ret;
389
390 if ((ret = WriteHeaders(fd, start_offset, size, request.return_code)) < 0)
391 return;
392 WriteString(fd, (start_offset < static_cast<off_t>(size) ?
393 data.substr(start_offset) : ""));
adlr@google.com3defe6a2009-12-04 20:57:17 +0000394}
395
Gilad Arnold48085ba2011-11-16 09:36:08 -0800396
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800397// Break a URL into terms delimited by slashes.
398class UrlTerms {
399 public:
400 UrlTerms(string &url, size_t num_terms) {
401 // URL must be non-empty and start with a slash.
402 CHECK_GT(url.size(), 0);
403 CHECK_EQ(url[0], '/');
404
405 // Split it into terms delimited by slashes, omitting the preceeding slash.
406 base::SplitStringDontTrim(url.substr(1), '/', &terms);
407
408 // Ensure expected length.
409 CHECK_EQ(terms.size(), num_terms);
410 }
411
412 inline string Get(const off_t index) const {
413 return terms[index];
414 }
415 inline const char *GetCStr(const off_t index) const {
416 return Get(index).c_str();
417 }
418 inline int GetInt(const off_t index) const {
419 return atoi(GetCStr(index));
420 }
421 inline long GetLong(const off_t index) const {
422 return atol(GetCStr(index));
423 }
424
425 private:
426 std::vector<string> terms;
427};
Gilad Arnold48085ba2011-11-16 09:36:08 -0800428
adlr@google.com3defe6a2009-12-04 20:57:17 +0000429void HandleConnection(int fd) {
430 HttpRequest request;
431 ParseRequest(fd, &request);
432
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800433 string &url = request.url;
434 if (url == "/quitquitquit") {
435 HandleQuit(fd);
436 } else if (StartsWithASCII(url, "/download/", true)) {
437 const UrlTerms terms(url, 2);
438 HandleGet(fd, request, terms.GetLong(1));
439 } else if (StartsWithASCII(url, "/flaky/", true)) {
440 const UrlTerms terms(url, 5);
441 HandleFlakyGet(fd, request, terms.GetLong(1), terms.GetLong(2),
442 terms.GetLong(3), terms.GetLong(4));
443 } else if (url.find("/redirect/") == 0) {
Darin Petkov41c2fcf2010-08-25 13:14:48 -0700444 HandleRedirect(fd, request);
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800445 } else if (url == "/error") {
Gilad Arnold48085ba2011-11-16 09:36:08 -0800446 HandleError(fd, request);
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800447 } else if (StartsWithASCII(url, "/error-if-offset/", true)) {
448 const UrlTerms terms(url, 3);
449 HandleErrorIfOffset(fd, request, terms.GetLong(1), terms.GetInt(2));
450 } else {
adlr@google.com3defe6a2009-12-04 20:57:17 +0000451 HandleDefault(fd, request);
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800452 }
adlr@google.com3defe6a2009-12-04 20:57:17 +0000453
454 close(fd);
455}
456
457} // namespace chromeos_update_engine
458
459using namespace chromeos_update_engine;
460
461int main(int argc, char** argv) {
Andrew de los Reyes3fd5d302010-10-07 20:07:18 -0700462 // Ignore SIGPIPE on write() to sockets.
463 signal(SIGPIPE, SIG_IGN);
Darin Petkovf67bb1f2010-11-08 16:10:26 -0800464
adlr@google.com3defe6a2009-12-04 20:57:17 +0000465 socklen_t clilen;
466 struct sockaddr_in server_addr;
467 struct sockaddr_in client_addr;
468 memset(&server_addr, 0, sizeof(server_addr));
469 memset(&client_addr, 0, sizeof(client_addr));
470
471 int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
472 if (listen_fd < 0)
473 LOG(FATAL) << "socket() failed";
474
475 server_addr.sin_family = AF_INET;
476 server_addr.sin_addr.s_addr = INADDR_ANY;
Gilad Arnold9bedeb52011-11-17 16:19:57 -0800477 server_addr.sin_port = htons(kServerPort);
adlr@google.com3defe6a2009-12-04 20:57:17 +0000478
479 {
480 // Get rid of "Address in use" error
481 int tr = 1;
482 if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &tr,
483 sizeof(int)) == -1) {
484 perror("setsockopt");
485 exit(1);
486 }
487 }
488
489 if (bind(listen_fd, reinterpret_cast<struct sockaddr *>(&server_addr),
490 sizeof(server_addr)) < 0) {
491 perror("bind");
492 exit(1);
493 }
494 CHECK_EQ(listen(listen_fd,5), 0);
495 while (1) {
496 clilen = sizeof(client_addr);
497 int client_fd = accept(listen_fd,
498 (struct sockaddr *) &client_addr,
499 &clilen);
500 LOG(INFO) << "got past accept";
501 if (client_fd < 0)
502 LOG(FATAL) << "ERROR on accept";
503 HandleConnection(client_fd);
504 }
505 return 0;
506}