annotate src/gt_http_server.c @ 0:d39e1d0d75b6

initial add
author paulo@hit-nxdomain.opendns.com
date Sat, 20 Feb 2010 21:18:28 -0800
parents
children
rev   line source
paulo@0 1 /*
paulo@0 2 * $Id: gt_http_server.c,v 1.73 2005/01/04 15:03:40 mkern Exp $
paulo@0 3 *
paulo@0 4 * Copyright (C) 2001-2003 giFT project (gift.sourceforge.net)
paulo@0 5 *
paulo@0 6 * This program is free software; you can redistribute it and/or modify it
paulo@0 7 * under the terms of the GNU General Public License as published by the
paulo@0 8 * Free Software Foundation; either version 2, or (at your option) any
paulo@0 9 * later version.
paulo@0 10 *
paulo@0 11 * This program is distributed in the hope that it will be useful, but
paulo@0 12 * WITHOUT ANY WARRANTY; without even the implied warranty of
paulo@0 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
paulo@0 14 * General Public License for more details.
paulo@0 15 */
paulo@0 16
paulo@0 17 #include "gt_gnutella.h"
paulo@0 18
paulo@0 19 #include "gt_xfer_obj.h"
paulo@0 20 #include "gt_xfer.h"
paulo@0 21
paulo@0 22 #include "gt_http_server.h"
paulo@0 23 #include "gt_http_client.h"
paulo@0 24
paulo@0 25 #include "gt_accept.h"
paulo@0 26 #include "gt_ban.h"
paulo@0 27 #include "gt_version.h"
paulo@0 28
paulo@0 29 /*****************************************************************************/
paulo@0 30
paulo@0 31 /* convenient shorthand */
paulo@0 32 #define CONTENT_URN_FIELD "X-Gnutella-Content-URN"
paulo@0 33
paulo@0 34 #define INCOMING_TIMEOUT (1 * MINUTES)
paulo@0 35
paulo@0 36 /*****************************************************************************/
paulo@0 37
paulo@0 38 struct http_incoming
paulo@0 39 {
paulo@0 40 TCPC *c;
paulo@0 41 timer_id timer;
paulo@0 42 };
paulo@0 43
paulo@0 44 /*****************************************************************************/
paulo@0 45
paulo@0 46 static void server_handle_get (GtTransfer *xfer);
paulo@0 47 static void get_client_request (int fd, input_id id,
paulo@0 48 struct http_incoming *http);
paulo@0 49 static void send_http_response (int fd, input_id id, GtTransfer *xfer);
paulo@0 50
paulo@0 51 /*****************************************************************************/
paulo@0 52 /* SERVER HELPERS */
paulo@0 53
paulo@0 54 static char *lookup_http_code (int code, char **desc)
paulo@0 55 {
paulo@0 56 char *err;
paulo@0 57 char *txt;
paulo@0 58
paulo@0 59 switch (code)
paulo@0 60 {
paulo@0 61 case 200: err = "OK";
paulo@0 62 txt = "Success";
paulo@0 63 break;
paulo@0 64 case 206: err = "Partial Content";
paulo@0 65 txt = "Resume successful";
paulo@0 66 break;
paulo@0 67 case 403: err = "Forbidden";
paulo@0 68 txt = "You do not have access to this file";
paulo@0 69 break;
paulo@0 70 case 404: err = "Not Found";
paulo@0 71 txt = "File is not available";
paulo@0 72 break;
paulo@0 73 case 500: err = "Internal Server Error";
paulo@0 74 txt = "Stale file entry, retry later";
paulo@0 75 break;
paulo@0 76 case 501: err = "Not Implemented";
paulo@0 77 txt = "???";
paulo@0 78 break;
paulo@0 79 case 503: err = "Service Unavailable";
paulo@0 80 txt = "Upload queue is currently full, please try again later";
paulo@0 81 break;
paulo@0 82 default: err = NULL;
paulo@0 83 txt = NULL;
paulo@0 84 break;
paulo@0 85 }
paulo@0 86
paulo@0 87 if (desc)
paulo@0 88 *desc = txt;
paulo@0 89
paulo@0 90 return err;
paulo@0 91 }
paulo@0 92
paulo@0 93 static String *alloc_header (int code)
paulo@0 94 {
paulo@0 95 char *code_text;
paulo@0 96 String *s;
paulo@0 97
paulo@0 98 /* so that we can communicate both the numerical code and the human
paulo@0 99 * readable string */
paulo@0 100 if (!(code_text = lookup_http_code (code, NULL)))
paulo@0 101 return FALSE;
paulo@0 102
paulo@0 103 if (!(s = string_new (NULL, 0, 0, TRUE)))
paulo@0 104 return FALSE;
paulo@0 105
paulo@0 106 string_appendf (s, "HTTP/1.1 %i %s\r\n", code, code_text);
paulo@0 107
paulo@0 108 return s;
paulo@0 109 }
paulo@0 110
paulo@0 111 static void construct_header_va (String *s, int code, va_list args)
paulo@0 112 {
paulo@0 113 char *key;
paulo@0 114 char *value;
paulo@0 115
paulo@0 116 /* Add "Server: " header */
paulo@0 117 string_appendf (s, "Server: %s\r\n", gt_version ());
paulo@0 118
paulo@0 119 for (;;)
paulo@0 120 {
paulo@0 121 if (!(key = va_arg (args, char *)))
paulo@0 122 break;
paulo@0 123
paulo@0 124 if (!(value = va_arg (args, char *)))
paulo@0 125 continue;
paulo@0 126
paulo@0 127 string_appendf (s, "%s: %s\r\n", key, value);
paulo@0 128 }
paulo@0 129
paulo@0 130 /* append final message terminator */
paulo@0 131 string_append (s, "\r\n");
paulo@0 132 }
paulo@0 133
paulo@0 134 static String *construct_header (int code, ...)
paulo@0 135 {
paulo@0 136 String *s;
paulo@0 137 va_list args;
paulo@0 138
paulo@0 139 if (!(s = alloc_header (code)))
paulo@0 140 return NULL;
paulo@0 141
paulo@0 142 va_start (args, code);
paulo@0 143 construct_header_va (s, code, args);
paulo@0 144 va_end (args);
paulo@0 145
paulo@0 146 return s;
paulo@0 147 }
paulo@0 148
paulo@0 149 /*
paulo@0 150 * Construct and send a server reply.
paulo@0 151 */
paulo@0 152 static BOOL gt_http_server_send (TCPC *c, int code, ...)
paulo@0 153 {
paulo@0 154 String *s;
paulo@0 155 int ret;
paulo@0 156 size_t len;
paulo@0 157 va_list args;
paulo@0 158
paulo@0 159 if (!(s = alloc_header (code)))
paulo@0 160 return FALSE;
paulo@0 161
paulo@0 162 va_start (args, code);
paulo@0 163
paulo@0 164 construct_header_va (s, code, args);
paulo@0 165
paulo@0 166 va_end (args);
paulo@0 167
paulo@0 168 if (HTTP_DEBUG)
paulo@0 169 GT->DBGSOCK (GT, c, "sending reply to client =\n%s", s->str);
paulo@0 170
paulo@0 171 len = s->len;
paulo@0 172 ret = tcp_send (c, s->str, s->len);
paulo@0 173
paulo@0 174 string_free (s);
paulo@0 175
paulo@0 176 return (ret == len);
paulo@0 177 }
paulo@0 178
paulo@0 179 static char *get_error_page (GtTransfer *xfer, int code)
paulo@0 180 {
paulo@0 181 char *page;
paulo@0 182 char *err;
paulo@0 183 char *errtxt = NULL;
paulo@0 184
paulo@0 185 if (!(err = lookup_http_code (code, &errtxt)))
paulo@0 186 return 0;
paulo@0 187
paulo@0 188 page = stringf ("<h1>%i %s</h1><br>%s.", code, err, errtxt);
paulo@0 189
paulo@0 190 return page;
paulo@0 191 }
paulo@0 192
paulo@0 193 static BOOL supports_queue (GtTransfer *xfer)
paulo@0 194 {
paulo@0 195 char *features;
paulo@0 196
paulo@0 197 if (dataset_lookupstr (xfer->header, "x-queue"))
paulo@0 198 return TRUE;
paulo@0 199
paulo@0 200 if ((features = dataset_lookupstr (xfer->header, "x-features")))
paulo@0 201 {
paulo@0 202 /* XXX: case-sensitive */
paulo@0 203 if (strstr (features, "queue"))
paulo@0 204 return TRUE;
paulo@0 205 }
paulo@0 206
paulo@0 207 return FALSE;
paulo@0 208 }
paulo@0 209
paulo@0 210 static char *get_queue_line (GtTransfer *xfer)
paulo@0 211 {
paulo@0 212 String *s;
paulo@0 213
paulo@0 214 /* do nothing if not queued */
paulo@0 215 if (xfer->queue_pos == 0)
paulo@0 216 return NULL;
paulo@0 217
paulo@0 218 if (!(s = string_new (NULL, 0, 0, TRUE)))
paulo@0 219 return NULL;
paulo@0 220
paulo@0 221 string_appendf (s, "position=%d,length=%d,pollMin=%d,pollMax=%d",
paulo@0 222 xfer->queue_pos, xfer->queue_ttl, 45, 120);
paulo@0 223
paulo@0 224 return string_free_keep (s);
paulo@0 225 }
paulo@0 226
paulo@0 227 static String *get_error_header (GtTransfer *xfer, int code,
paulo@0 228 const char *error_page)
paulo@0 229 {
paulo@0 230 size_t len;
paulo@0 231 char content_len[256];
paulo@0 232 char *queue_line = NULL;
paulo@0 233 char *content_type = "text/html";
paulo@0 234 String *s;
paulo@0 235
paulo@0 236 len = strlen (error_page);
paulo@0 237 snprintf (content_len, sizeof (content_len), "%u", len);
paulo@0 238
paulo@0 239 if (code == 503 && supports_queue (xfer))
paulo@0 240 queue_line = get_queue_line (xfer);
paulo@0 241
paulo@0 242 /* don't send a content-type header if there is no entity body */
paulo@0 243 if (len == 0)
paulo@0 244 content_type = NULL;
paulo@0 245
paulo@0 246 s = construct_header (code,
paulo@0 247 "Content-Type", content_type,
paulo@0 248 "Content-Length", content_len,
paulo@0 249 CONTENT_URN_FIELD, xfer->content_urns,
paulo@0 250 "X-Queue", queue_line,
paulo@0 251 NULL);
paulo@0 252
paulo@0 253 free (queue_line);
paulo@0 254 return s;
paulo@0 255 }
paulo@0 256
paulo@0 257 static void send_error_reply (int fd, input_id id, GtTransfer *xfer)
paulo@0 258 {
paulo@0 259 String *s;
paulo@0 260 const char *error_page;
paulo@0 261 int ret;
paulo@0 262 TCPC *c;
paulo@0 263
paulo@0 264 c = gt_transfer_get_tcpc (xfer);
paulo@0 265
paulo@0 266 if (!(error_page = get_error_page (xfer, xfer->code)))
paulo@0 267 {
paulo@0 268 gt_transfer_close (xfer, TRUE);
paulo@0 269 return;
paulo@0 270 }
paulo@0 271
paulo@0 272 /*
paulo@0 273 * If the remote end supports queueing or supplied
paulo@0 274 * "X-Gnutella-Content-URN",, it's a Gnutella client and we don't want to
paulo@0 275 * keep sending the error page, because over many requests from this
paulo@0 276 * client the bandwidth could add up.
paulo@0 277 */
paulo@0 278 if (supports_queue (xfer) ||
paulo@0 279 dataset_lookupstr (xfer->header, "x-gnutella-content-urn"))
paulo@0 280 {
paulo@0 281 error_page = ""; /* empty */
paulo@0 282 }
paulo@0 283
paulo@0 284 if (!(s = get_error_header (xfer, xfer->code, error_page)))
paulo@0 285 {
paulo@0 286 gt_transfer_close (xfer, TRUE);
paulo@0 287 return;
paulo@0 288 }
paulo@0 289
paulo@0 290 string_append (s, error_page);
paulo@0 291
paulo@0 292 if (HTTP_DEBUG)
paulo@0 293 GT->DBGSOCK (GT, c, "sending reply to client =\n%s", s->str);
paulo@0 294
paulo@0 295 /* send the whole page at once */
paulo@0 296 ret = tcp_send (c, s->str, s->len);
paulo@0 297
paulo@0 298 /* if the whole thing was sent keep the connection open */
paulo@0 299 if (ret == s->len)
paulo@0 300 {
paulo@0 301 xfer->transmitted_hdrs = TRUE;
paulo@0 302 xfer->remaining_len = 0;
paulo@0 303 }
paulo@0 304
paulo@0 305 string_free (s);
paulo@0 306
paulo@0 307 gt_transfer_close (xfer, FALSE);
paulo@0 308 }
paulo@0 309
paulo@0 310 static void gt_http_server_send_error_and_close (GtTransfer *xfer, int code)
paulo@0 311 {
paulo@0 312 TCPC *c;
paulo@0 313
paulo@0 314 c = gt_transfer_get_tcpc (xfer);
paulo@0 315
paulo@0 316 xfer->code = code;
paulo@0 317
paulo@0 318 input_remove_all (c->fd);
paulo@0 319 input_add (c->fd, xfer, INPUT_WRITE,
paulo@0 320 (InputCallback)send_error_reply, TIMEOUT_DEF);
paulo@0 321 }
paulo@0 322
paulo@0 323 /*****************************************************************************/
paulo@0 324
paulo@0 325 /* parse the Range: bytes=0-10000 format */
paulo@0 326 static void parse_client_request_range (Dataset *dataset,
paulo@0 327 off_t *r_start, off_t *r_stop)
paulo@0 328 {
paulo@0 329 char *range;
paulo@0 330 off_t start;
paulo@0 331 off_t stop;
paulo@0 332
paulo@0 333 if (!r_start && !r_stop)
paulo@0 334 return;
paulo@0 335
paulo@0 336 if (r_start)
paulo@0 337 *r_start = 0;
paulo@0 338 if (r_stop)
paulo@0 339 *r_stop = 0;
paulo@0 340
paulo@0 341 /* leave stop as 0 if we can't figure anything out yet. This is expected
paulo@0 342 * to be handled separately by GET and PUSH */
paulo@0 343 if (!(range = dataset_lookupstr (dataset, "range")))
paulo@0 344 return;
paulo@0 345
paulo@0 346 /* WARNING: this butchers the data in the dataset! */
paulo@0 347 string_sep (&range, "bytes");
paulo@0 348 string_sep_set (&range, " =");
paulo@0 349
paulo@0 350 if (!range)
paulo@0 351 {
paulo@0 352 if (HTTP_DEBUG)
paulo@0 353 GT->DBGFN (GT, "error parsing Range: header");
paulo@0 354
paulo@0 355 return;
paulo@0 356 }
paulo@0 357
paulo@0 358 start = (off_t) ATOI (string_sep (&range, "-"));
paulo@0 359 stop = (off_t) ATOI (string_sep (&range, " "));
paulo@0 360
paulo@0 361 /*
paulo@0 362 * The end of the range is optional (e.g. "Range: 0-"), and in that case
paulo@0 363 * stop == 0. In the case of a single byte file, stop == 1.
paulo@0 364 *
paulo@0 365 * TODO: this is broken for one-byte requests at the start of the file.
paulo@0 366 */
paulo@0 367 if (stop > 0)
paulo@0 368 stop = stop + 1;
paulo@0 369
paulo@0 370 if (r_start)
paulo@0 371 *r_start = start;
paulo@0 372 if (r_stop)
paulo@0 373 *r_stop = stop;
paulo@0 374 }
paulo@0 375
paulo@0 376 /*
paulo@0 377 * Parse requests of the forms:
paulo@0 378 *
paulo@0 379 * /get/47/File maybe with spaces HTTP
paulo@0 380 * /get/47/files_%20_url_encoded HTTP/1.1
paulo@0 381 * /uri-res/N2R?urn:sha1:ABCD HTTP/1.0
paulo@0 382 *
paulo@0 383 * and combinations thereof. The "HTTP" trailer is mandatory.
paulo@0 384 */
paulo@0 385 static void get_request_and_version (char *data, char **request,
paulo@0 386 char **version)
paulo@0 387 {
paulo@0 388 size_t len;
paulo@0 389 char *next;
paulo@0 390 char *dup;
paulo@0 391 char *http_version = NULL;
paulo@0 392
paulo@0 393 *request = NULL;
paulo@0 394 *version = NULL;
paulo@0 395
paulo@0 396 /* trim whitespace inbetween command and request */
paulo@0 397 string_trim (data);
paulo@0 398
paulo@0 399 if (!(dup = STRDUP (data)))
paulo@0 400 return;
paulo@0 401
paulo@0 402 string_upper (dup);
paulo@0 403
paulo@0 404 next = dup;
paulo@0 405
paulo@0 406 /* find the last instance of "HTTP" in the string */
paulo@0 407 while ((next = strstr (next, "HTTP")))
paulo@0 408 {
paulo@0 409 http_version = next;
paulo@0 410 next += sizeof ("HTTP") - 1;
paulo@0 411 }
paulo@0 412
paulo@0 413 /* the rest of the string must be the request */
paulo@0 414 if (http_version != NULL && http_version != dup)
paulo@0 415 {
paulo@0 416 len = http_version - dup;
paulo@0 417 data[len - 1] = 0;
paulo@0 418
paulo@0 419 *request = data;
paulo@0 420 *version = data + len;
paulo@0 421 }
paulo@0 422
paulo@0 423 free (dup);
paulo@0 424 }
paulo@0 425
paulo@0 426 /*
paulo@0 427 * Break down the clients HTTP request
paulo@0 428 */
paulo@0 429 static int parse_client_request (Dataset **r_dataset, char **r_command,
paulo@0 430 char **r_request, char **r_version,
paulo@0 431 off_t *r_start, off_t *r_stop, char *hdr)
paulo@0 432 {
paulo@0 433 Dataset *dataset = NULL;
paulo@0 434 char *command; /* GET */
paulo@0 435 char *request; /* /file.tar.gz */
paulo@0 436 char *version; /* HTTP/1.1 */
paulo@0 437 char *req_line;
paulo@0 438
paulo@0 439 if (!hdr)
paulo@0 440 return FALSE;
paulo@0 441
paulo@0 442 /*
paulo@0 443 * Get the first line of the request
paulo@0 444 */
paulo@0 445 req_line = string_sep_set (&hdr, "\r\n");
paulo@0 446
paulo@0 447 /* get the command (GET, HEAD, etc.) */
paulo@0 448 command = string_sep (&req_line, " ");
paulo@0 449
paulo@0 450 /*
paulo@0 451 * Handle non-url-encoded requests as well as encoded
paulo@0 452 * ones and get the request and version from this HTTP line.
paulo@0 453 */
paulo@0 454 get_request_and_version (req_line, &request, &version);
paulo@0 455
paulo@0 456 if (HTTP_DEBUG)
paulo@0 457 {
paulo@0 458 GT->DBGFN (GT, "command=%s version=%s request=%s",
paulo@0 459 command, version, request);
paulo@0 460 }
paulo@0 461
paulo@0 462 if (!request || string_isempty (request))
paulo@0 463 return FALSE;
paulo@0 464
paulo@0 465 if (r_command)
paulo@0 466 *r_command = command;
paulo@0 467 if (r_request)
paulo@0 468 *r_request = request;
paulo@0 469 if (r_version)
paulo@0 470 *r_version = version;
paulo@0 471
paulo@0 472 gt_http_header_parse (hdr, &dataset);
paulo@0 473
paulo@0 474 if (r_dataset)
paulo@0 475 *r_dataset = dataset;
paulo@0 476
paulo@0 477 /* handle Range: header */
paulo@0 478 parse_client_request_range (dataset, r_start, r_stop);
paulo@0 479
paulo@0 480 if (r_start && r_stop)
paulo@0 481 {
paulo@0 482 if (HTTP_DEBUG)
paulo@0 483 GT->dbg (GT, "range: [%i, %i)", *r_start, *r_stop);
paulo@0 484 }
paulo@0 485
paulo@0 486 return TRUE;
paulo@0 487 }
paulo@0 488
paulo@0 489 /*****************************************************************************/
paulo@0 490
paulo@0 491 /*
paulo@0 492 * Send the request reply back to the client
paulo@0 493 *
paulo@0 494 * NOTE:
paulo@0 495 * This is used by both GET / and PUSH /
paulo@0 496 */
paulo@0 497 static void reply_to_client_request (GtTransfer *xfer)
paulo@0 498 {
paulo@0 499 TCPC *c;
paulo@0 500 Chunk *chunk;
paulo@0 501 off_t entity_size;
paulo@0 502 char range[128];
paulo@0 503 char length[32];
paulo@0 504 BOOL ret;
paulo@0 505
paulo@0 506 if (!xfer)
paulo@0 507 return;
paulo@0 508
paulo@0 509 c = gt_transfer_get_tcpc (xfer);
paulo@0 510 chunk = gt_transfer_get_chunk (xfer);
paulo@0 511
paulo@0 512 /*
paulo@0 513 * Determine the "total" entity body that we have locally, not necessarily
paulo@0 514 * the data that we are uploading. HTTP demands this, but OpenFT really
paulo@0 515 * doesn't give a shit.
paulo@0 516 *
paulo@0 517 * NOTE:
paulo@0 518 * This only works to standard when operating on a GET / request, PUSH's
paulo@0 519 * merely use the range!
paulo@0 520 */
paulo@0 521 if (xfer->open_path_size)
paulo@0 522 entity_size = xfer->open_path_size;
paulo@0 523 else
paulo@0 524 entity_size = xfer->stop - xfer->start;
paulo@0 525
paulo@0 526 /* NOTE: we are "working" around the fact that HTTP's Content-Range
paulo@0 527 * reply is inclusive for the last byte, whereas giFT's is not. */
paulo@0 528 snprintf (range, sizeof (range) - 1, "bytes %i-%i/%i",
paulo@0 529 (int) xfer->start, (int) (xfer->stop - 1), (int) entity_size);
paulo@0 530
paulo@0 531 snprintf (length, sizeof (length) - 1, "%i",
paulo@0 532 (int) (xfer->stop - xfer->start));
paulo@0 533
paulo@0 534 ret = gt_http_server_send (c, xfer->code,
paulo@0 535 "Content-Range", range,
paulo@0 536 "Content-Length", length,
paulo@0 537 "Content-Type", xfer->content_type,
paulo@0 538 CONTENT_URN_FIELD, xfer->content_urns,
paulo@0 539 NULL);
paulo@0 540
paulo@0 541 /* if we transmitted all headers successfully, set transmitted_hdrs
paulo@0 542 * to keep the connection alive, possibly */
paulo@0 543 if (ret)
paulo@0 544 xfer->transmitted_hdrs = TRUE;
paulo@0 545 }
paulo@0 546
paulo@0 547 /*****************************************************************************/
paulo@0 548
paulo@0 549 static void http_incoming_free (struct http_incoming *incoming)
paulo@0 550 {
paulo@0 551 timer_remove (incoming->timer);
paulo@0 552 free (incoming);
paulo@0 553 }
paulo@0 554
paulo@0 555 static void http_incoming_close (struct http_incoming *incoming)
paulo@0 556 {
paulo@0 557 gt_http_connection_close (GT_TRANSFER_UPLOAD, incoming->c, TRUE);
paulo@0 558 http_incoming_free (incoming);
paulo@0 559 }
paulo@0 560
paulo@0 561 static BOOL http_incoming_timeout (struct http_incoming *incoming)
paulo@0 562 {
paulo@0 563 http_incoming_close (incoming);
paulo@0 564 return FALSE;
paulo@0 565 }
paulo@0 566
paulo@0 567 static struct http_incoming *http_incoming_alloc (TCPC *c)
paulo@0 568 {
paulo@0 569 struct http_incoming *incoming;
paulo@0 570
paulo@0 571 incoming = malloc (sizeof (struct http_incoming));
paulo@0 572 if (!incoming)
paulo@0 573 return NULL;
paulo@0 574
paulo@0 575 incoming->c = c;
paulo@0 576 incoming->timer = timer_add (INCOMING_TIMEOUT,
paulo@0 577 (TimerCallback)http_incoming_timeout,
paulo@0 578 incoming);
paulo@0 579
paulo@0 580 return incoming;
paulo@0 581 }
paulo@0 582
paulo@0 583 void gt_http_server_dispatch (int fd, input_id id, TCPC *c)
paulo@0 584 {
paulo@0 585 struct http_incoming *incoming;
paulo@0 586
paulo@0 587 if (net_sock_error (c->fd))
paulo@0 588 {
paulo@0 589 gt_http_connection_close (GT_TRANSFER_UPLOAD, c, TRUE);
paulo@0 590 return;
paulo@0 591 }
paulo@0 592
paulo@0 593 if (!(incoming = http_incoming_alloc (c)))
paulo@0 594 {
paulo@0 595 gt_http_connection_close (GT_TRANSFER_UPLOAD, c, TRUE);
paulo@0 596 return;
paulo@0 597 }
paulo@0 598
paulo@0 599 /* keep track of this incoming connection */
paulo@0 600 /* gt_http_connection_insert (GT_TRANSFER_UPLOAD, c); */
paulo@0 601
paulo@0 602 input_remove (id);
paulo@0 603 input_add (c->fd, incoming, INPUT_READ,
paulo@0 604 (InputCallback)get_client_request, 0);
paulo@0 605 }
paulo@0 606
paulo@0 607 /*
paulo@0 608 * Handle the client's GET commands.
paulo@0 609 */
paulo@0 610 static void get_client_request (int fd, input_id id, struct http_incoming *http)
paulo@0 611 {
paulo@0 612 GtTransfer *xfer;
paulo@0 613 TCPC *c;
paulo@0 614 Dataset *dataset = NULL;
paulo@0 615 char *command = NULL;
paulo@0 616 char *request = NULL;
paulo@0 617 char *version = NULL;
paulo@0 618 off_t start = 0;
paulo@0 619 off_t stop = 0;
paulo@0 620 FDBuf *buf;
paulo@0 621 unsigned char *data;
paulo@0 622 size_t data_len = 0;
paulo@0 623 int n;
paulo@0 624
paulo@0 625 c = http->c;
paulo@0 626 buf = tcp_readbuf (c);
paulo@0 627
paulo@0 628 if ((n = fdbuf_delim (buf, "\n")) < 0)
paulo@0 629 {
paulo@0 630 http_incoming_close (http);
paulo@0 631 return;
paulo@0 632 }
paulo@0 633
paulo@0 634 if (gt_fdbuf_full (buf))
paulo@0 635 {
paulo@0 636 http_incoming_close (http);
paulo@0 637 return;
paulo@0 638 }
paulo@0 639
paulo@0 640 if (n > 0)
paulo@0 641 return;
paulo@0 642
paulo@0 643 data = fdbuf_data (buf, &data_len);
paulo@0 644
paulo@0 645 if (!gt_http_header_terminated (data, data_len))
paulo@0 646 return;
paulo@0 647
paulo@0 648 fdbuf_release (buf);
paulo@0 649
paulo@0 650 if (HTTP_DEBUG)
paulo@0 651 GT->DBGSOCK (GT, c, "client request:\n%s", data);
paulo@0 652
paulo@0 653 /* parse the client's request and determine how we should proceed */
paulo@0 654 if (!parse_client_request (&dataset, &command, &request, &version,
paulo@0 655 &start, &stop, data))
paulo@0 656 {
paulo@0 657 GT->DBGSOCK (GT, c, "invalid http header");
paulo@0 658 http_incoming_close (http);
paulo@0 659 return;
paulo@0 660 }
paulo@0 661
paulo@0 662 /* discard incoming connection timeout maintainance structure */
paulo@0 663 http_incoming_free (http);
paulo@0 664
paulo@0 665 /*
paulo@0 666 * We have enough information now to actually allocate the transfer
paulo@0 667 * structure and pass it along to all logic that follows this
paulo@0 668 *
paulo@0 669 * NOTE:
paulo@0 670 * Each individual handler can determine when it wants to let giFT
paulo@0 671 * in on this
paulo@0 672 */
paulo@0 673 xfer = gt_transfer_new (GT_TRANSFER_UPLOAD, NULL,
paulo@0 674 net_peer (c->fd), 0, start, stop);
paulo@0 675
paulo@0 676 /* connect the connection and the xfer in unholy matrimony */
paulo@0 677 gt_transfer_set_tcpc (xfer, c);
paulo@0 678
paulo@0 679 /* assign all our own memory */
paulo@0 680 xfer->command = STRDUP (command);
paulo@0 681 xfer->header = dataset;
paulo@0 682 xfer->version = STRDUP (version);
paulo@0 683
paulo@0 684 if (!gt_transfer_set_request (xfer, request))
paulo@0 685 {
paulo@0 686 if (HTTP_DEBUG)
paulo@0 687 GT->DBGSOCK (GT, c, "invalid request \"s\"", request);
paulo@0 688
paulo@0 689 gt_transfer_close (xfer, TRUE);
paulo@0 690 return;
paulo@0 691 }
paulo@0 692
paulo@0 693 /* no need for this function again */
paulo@0 694 input_remove (id);
paulo@0 695
paulo@0 696 /* figure out how to handle this request */
paulo@0 697 if (!strcasecmp (xfer->command, "GET") ||
paulo@0 698 !strcasecmp (xfer->command, "HEAD"))
paulo@0 699 {
paulo@0 700 server_handle_get (xfer);
paulo@0 701 return;
paulo@0 702 }
paulo@0 703
paulo@0 704 gt_http_server_send_error_and_close (xfer, 501);
paulo@0 705 }
paulo@0 706
paulo@0 707 /*****************************************************************************/
paulo@0 708
paulo@0 709 static Transfer *start_upload (GtTransfer *xfer, Chunk **chunk)
paulo@0 710 {
paulo@0 711 Transfer *transfer;
paulo@0 712 char *user;
paulo@0 713
paulo@0 714 user = net_ip_str (xfer->ip);
paulo@0 715
paulo@0 716 transfer = GT->upload_start (GT, chunk, user, xfer->share_authd,
paulo@0 717 xfer->start, xfer->stop);
paulo@0 718
paulo@0 719 assert (transfer != NULL);
paulo@0 720
paulo@0 721 return transfer;
paulo@0 722 }
paulo@0 723
paulo@0 724 /* setup the structure for uploading. this will be called from within
paulo@0 725 * client space for PUSH requests as well */
paulo@0 726 int gt_server_setup_upload (GtTransfer *xfer)
paulo@0 727 {
paulo@0 728 Transfer *transfer; /* giFT structure */
paulo@0 729 Chunk *chunk;
paulo@0 730 TCPC *c;
paulo@0 731
paulo@0 732 if (!xfer)
paulo@0 733 return FALSE;
paulo@0 734
paulo@0 735 c = gt_transfer_get_tcpc (xfer);
paulo@0 736 assert (xfer->chunk == NULL);
paulo@0 737
paulo@0 738 /*
paulo@0 739 * Ban the host if they don't have access -- this gives no information
paulo@0 740 * about whether we have the file or not, and i think this is in violation
paulo@0 741 * of the HTTP spec (supposed to return "404 not found" before 403, but
paulo@0 742 * i'm not sure.
paulo@0 743 */
paulo@0 744 if (gt_ban_ipv4_is_banned (c->host))
paulo@0 745 {
paulo@0 746 xfer->code = 403;
paulo@0 747 return FALSE;
paulo@0 748 }
paulo@0 749
paulo@0 750 /* open the file that was requested before we go bringing giFT into
paulo@0 751 * this */
paulo@0 752 if (!(xfer->f = gt_transfer_open_request (xfer, &xfer->code)))
paulo@0 753 return FALSE;
paulo@0 754
paulo@0 755 /* assign stop a value before we proceed */
paulo@0 756 if (xfer->stop == 0)
paulo@0 757 {
paulo@0 758 struct stat st;
paulo@0 759
paulo@0 760 if (!file_stat (xfer->open_path, &st) || st.st_size == 0)
paulo@0 761 {
paulo@0 762 /* stupid bastards have a 0 length file */
paulo@0 763 GT->DBGSOCK (GT, c, "cannot satisfy %s: invalid share",
paulo@0 764 xfer->open_path);
paulo@0 765 return FALSE;
paulo@0 766 }
paulo@0 767
paulo@0 768 xfer->stop = st.st_size;
paulo@0 769 xfer->remaining_len = xfer->stop - xfer->start;
paulo@0 770 }
paulo@0 771
paulo@0 772 /* we can now be certain that we are handling a download request from
paulo@0 773 * the client. allocate the appropriate structures to hook into giFT */
paulo@0 774 if (!(transfer = start_upload (xfer, &chunk)))
paulo@0 775 {
paulo@0 776 GT->DBGFN (GT, "unable to register upload with the daemon");
paulo@0 777 return FALSE;
paulo@0 778 }
paulo@0 779
paulo@0 780 /* override 200 w/ 206 if the request is not the whole file size */
paulo@0 781 if (xfer->remaining_len != xfer->share_authd->size)
paulo@0 782 xfer->code = 206;
paulo@0 783
paulo@0 784 /* assign the circular references for passing the chunk along */
paulo@0 785 gt_transfer_set_chunk (xfer, chunk);
paulo@0 786
paulo@0 787 /* finally, seek the file descriptor where it needs to be */
paulo@0 788 fseek (xfer->f, xfer->start, SEEK_SET);
paulo@0 789
paulo@0 790 return TRUE;
paulo@0 791 }
paulo@0 792
paulo@0 793 static void server_handle_get (GtTransfer *xfer)
paulo@0 794 {
paulo@0 795 TCPC *c;
paulo@0 796
paulo@0 797 c = gt_transfer_get_tcpc (xfer);
paulo@0 798 assert (xfer->chunk == NULL);
paulo@0 799
paulo@0 800 /* WARNING: this block is duplicated in http_client:client_push_request */
paulo@0 801 if (!gt_server_setup_upload (xfer))
paulo@0 802 {
paulo@0 803 if (xfer->code == 200)
paulo@0 804 xfer->code = 404;
paulo@0 805
paulo@0 806 gt_http_server_send_error_and_close (xfer, xfer->code);
paulo@0 807 return;
paulo@0 808 }
paulo@0 809
paulo@0 810 input_add (c->fd, xfer, INPUT_WRITE,
paulo@0 811 (InputCallback)send_http_response, TIMEOUT_DEF);
paulo@0 812 }
paulo@0 813
paulo@0 814 static void send_http_response (int fd, input_id id, GtTransfer *xfer)
paulo@0 815 {
paulo@0 816 TCPC *c;
paulo@0 817
paulo@0 818 c = gt_transfer_get_tcpc (xfer);
paulo@0 819
paulo@0 820 if (net_sock_error (c->fd))
paulo@0 821 {
paulo@0 822 gt_transfer_close (xfer, TRUE);
paulo@0 823 return;
paulo@0 824 }
paulo@0 825
paulo@0 826 /* ok, send client the header */
paulo@0 827 reply_to_client_request (xfer);
paulo@0 828
paulo@0 829 if (!strcasecmp (xfer->command, "HEAD"))
paulo@0 830 {
paulo@0 831 gt_transfer_close (xfer, TRUE);
paulo@0 832 return;
paulo@0 833 }
paulo@0 834
paulo@0 835 /* disable header read timer */
paulo@0 836 timer_remove_zero (&xfer->header_timer);
paulo@0 837
paulo@0 838 input_remove (id);
paulo@0 839 input_add (c->fd, xfer, INPUT_WRITE,
paulo@0 840 (InputCallback)gt_server_upload_file, 0);
paulo@0 841 }
paulo@0 842
paulo@0 843 /*
paulo@0 844 * Uploads the file requests
paulo@0 845 */
paulo@0 846 void gt_server_upload_file (int fd, input_id id, GtTransfer *xfer)
paulo@0 847 {
paulo@0 848 TCPC *c;
paulo@0 849 Chunk *chunk;
paulo@0 850 char buf[RW_BUFFER];
paulo@0 851 size_t read_len;
paulo@0 852 size_t size;
paulo@0 853 int sent_len = 0;
paulo@0 854 off_t remainder;
paulo@0 855
paulo@0 856 c = gt_transfer_get_tcpc (xfer);
paulo@0 857 chunk = gt_transfer_get_chunk (xfer);
paulo@0 858
paulo@0 859 assert (xfer->f != NULL);
paulo@0 860
paulo@0 861 /* number of bytes left to be uploaded by this chunk */
paulo@0 862 if ((remainder = xfer->remaining_len) <= 0)
paulo@0 863 {
paulo@0 864 /* for whatever reason this function may have been called when we have
paulo@0 865 * already overrun the transfer...in that case we will simply fall
paulo@0 866 * through to the end-of-transfer condition */
paulo@0 867 gt_transfer_write (xfer, chunk, NULL, 0);
paulo@0 868 return;
paulo@0 869 }
paulo@0 870
paulo@0 871 size = sizeof (buf);
paulo@0 872
paulo@0 873 if (size > remainder)
paulo@0 874 size = remainder;
paulo@0 875
paulo@0 876 /*
paulo@0 877 * Ask giFT for the size we should send. If this returns 0, the upload
paulo@0 878 * was suspended.
paulo@0 879 */
paulo@0 880 if ((size = upload_throttle (chunk, size)) == 0)
paulo@0 881 return;
paulo@0 882
paulo@0 883 /* read as much as we can from the local file */
paulo@0 884 if (!(read_len = fread (buf, sizeof (char), size, xfer->f)))
paulo@0 885 {
paulo@0 886 GT->DBGFN (GT, "unable to read from %s: %s", xfer->open_path,
paulo@0 887 GIFT_STRERROR ());
paulo@0 888 gt_transfer_status (xfer, SOURCE_CANCELLED, "Local read error");
paulo@0 889 gt_transfer_close (xfer, TRUE);
paulo@0 890 return;
paulo@0 891 }
paulo@0 892
paulo@0 893 if ((sent_len = tcp_send (c, buf, MIN (read_len, remainder))) <= 0)
paulo@0 894 {
paulo@0 895 gt_transfer_status (xfer, SOURCE_CANCELLED,
paulo@0 896 "Unable to send data block");
paulo@0 897 gt_transfer_close (xfer, TRUE);
paulo@0 898 return;
paulo@0 899 }
paulo@0 900
paulo@0 901 /* check if the file was too small for the transfer TODO: this isn't
paulo@0 902 * checked earlier, but should be */
paulo@0 903 if (read_len != size)
paulo@0 904 {
paulo@0 905 gt_transfer_status (xfer, SOURCE_CANCELLED, "Unexpected end of file");
paulo@0 906 gt_transfer_close (xfer, TRUE);
paulo@0 907 return;
paulo@0 908 }
paulo@0 909
paulo@0 910 /*
paulo@0 911 * Check for short send(). This could use fseek(), but I have this
paulo@0 912 * growing feeling that using stdio everywhere is a bad idea.
paulo@0 913 */
paulo@0 914 if (read_len != sent_len)
paulo@0 915 {
paulo@0 916 gt_transfer_status (xfer, SOURCE_CANCELLED, "Short send()");
paulo@0 917 gt_transfer_close (xfer, TRUE);
paulo@0 918 return;
paulo@0 919 }
paulo@0 920
paulo@0 921 /*
paulo@0 922 * Call gt_upload to report back to giFT. This will also cancel
paulo@0 923 * the transfer if the upload has completed.
paulo@0 924 */
paulo@0 925 gt_transfer_write (xfer, chunk, buf, sent_len);
paulo@0 926 }
paulo@0 927
paulo@0 928 /*****************************************************************************/
paulo@0 929
paulo@0 930 void gt_http_server_reset (TCPC *c)
paulo@0 931 {
paulo@0 932 /*
paulo@0 933 * This can happen because the GtTransfer and TCPC can be decoupled, as in
paulo@0 934 * the case of a push request sent.
paulo@0 935 */
paulo@0 936 if (!c)
paulo@0 937 return;
paulo@0 938
paulo@0 939 /* finish all queued writes before we reset */
paulo@0 940 tcp_flush (c, TRUE);
paulo@0 941
paulo@0 942 /* reset the input state */
paulo@0 943 input_remove_all (c->fd);
paulo@0 944 input_add (c->fd, c, INPUT_READ,
paulo@0 945 (InputCallback)gt_http_server_dispatch, 2 * MINUTES);
paulo@0 946 }