annotate src/gt_http_client.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_client.c,v 1.57 2006/02/03 20:11: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 #include "gt_version.h"
paulo@0 19
paulo@0 20 #include "gt_xfer_obj.h"
paulo@0 21 #include "gt_xfer.h"
paulo@0 22
paulo@0 23 #include "gt_http_client.h"
paulo@0 24 #include "gt_http_server.h"
paulo@0 25
paulo@0 26 #include "gt_accept.h"
paulo@0 27
paulo@0 28 #include "transfer/source.h"
paulo@0 29
paulo@0 30 /*****************************************************************************/
paulo@0 31
paulo@0 32 /* prototyping this function effectively provides the non-blocking flow of the
paulo@0 33 * program and helps to self-document this file */
paulo@0 34 static void get_server_reply (int fd, input_id id, GtTransfer *xfer);
paulo@0 35
paulo@0 36 /*****************************************************************************/
paulo@0 37 /* CLIENT HELPERS */
paulo@0 38
paulo@0 39 static int gt_http_client_send (TCPC *c, char *command, char *request, ...)
paulo@0 40 {
paulo@0 41 char *key;
paulo@0 42 char *value;
paulo@0 43 String *s;
paulo@0 44 int ret;
paulo@0 45 va_list args;
paulo@0 46
paulo@0 47 if (!command || !request)
paulo@0 48 return -1;
paulo@0 49
paulo@0 50 if (!(s = string_new (NULL, 0, 0, TRUE)))
paulo@0 51 return -1;
paulo@0 52
paulo@0 53 string_appendf (s, "%s %s HTTP/1.1\r\n", command, request);
paulo@0 54
paulo@0 55 va_start (args, request);
paulo@0 56
paulo@0 57 for (;;)
paulo@0 58 {
paulo@0 59 /* if we receive the sentinel, bail out */
paulo@0 60 if (!(key = va_arg (args, char *)))
paulo@0 61 break;
paulo@0 62
paulo@0 63 if (!(value = va_arg (args, char *)))
paulo@0 64 continue;
paulo@0 65
paulo@0 66 string_appendf (s, "%s: %s\r\n", key, value);
paulo@0 67 }
paulo@0 68
paulo@0 69 va_end (args);
paulo@0 70
paulo@0 71 /* append final message terminator */
paulo@0 72 string_append (s, "\r\n");
paulo@0 73
paulo@0 74 if (HTTP_DEBUG)
paulo@0 75 GT->DBGSOCK (GT, c, "sending client request:\n%s", s->str);
paulo@0 76
paulo@0 77 ret = tcp_send (c, s->str, s->len);
paulo@0 78 string_free (s);
paulo@0 79
paulo@0 80 return ret;
paulo@0 81 }
paulo@0 82
paulo@0 83 /* parse an HTTP server reply */
paulo@0 84 static BOOL parse_server_reply (GtTransfer *xfer, TCPC *c, char *reply)
paulo@0 85 {
paulo@0 86 char *response; /* HTTP/1.1 200 OK */
paulo@0 87 char *version;
paulo@0 88 int code; /* 200, 404, ... */
paulo@0 89
paulo@0 90 if (!xfer || !reply)
paulo@0 91 return FALSE;
paulo@0 92
paulo@0 93 if (HTTP_DEBUG)
paulo@0 94 GT->DBGSOCK (GT, c, "reply:\n%s", reply);
paulo@0 95
paulo@0 96 response = string_sep_set (&reply, "\r\n");
paulo@0 97
paulo@0 98 if (!response)
paulo@0 99 return FALSE;
paulo@0 100
paulo@0 101 version = string_sep (&response, " "); /* shift past HTTP/1.1 */
paulo@0 102 code = ATOI (string_sep (&response, " ")); /* shift past 200 */
paulo@0 103
paulo@0 104 /* parse the rest of the key/value fields */
paulo@0 105 gt_http_header_parse (reply, &xfer->header);
paulo@0 106
paulo@0 107 xfer->code = code;
paulo@0 108 xfer->version = STRDUP (version);
paulo@0 109
paulo@0 110 return TRUE;
paulo@0 111 }
paulo@0 112
paulo@0 113 /*****************************************************************************/
paulo@0 114
paulo@0 115 /*
paulo@0 116 * Complete interface to the standard HTTP GET / with a server. These routines
paulo@0 117 * require non of OpenFT's extensions and communicate perfectly valid
paulo@0 118 * HTTP/1.1 (to my knowledge). You could use this to transfer non-OpenFT,
paulo@0 119 * hopefully. ;)
paulo@0 120 *
paulo@0 121 * NOTE:
paulo@0 122 * I need to add more text here so this stands out better as one of the two
paulo@0 123 * core subsystems within this file. So here it is. :P
paulo@0 124 */
paulo@0 125 void gt_http_client_get (Chunk *chunk, GtTransfer *xfer)
paulo@0 126 {
paulo@0 127 TCPC *c;
paulo@0 128
paulo@0 129 if (!chunk || !xfer)
paulo@0 130 {
paulo@0 131 GT->DBGFN (GT, "uhm.");
paulo@0 132 return;
paulo@0 133 }
paulo@0 134
paulo@0 135 xfer->command = STRDUP ("GET");
paulo@0 136
paulo@0 137 if (!(c = gt_http_connection_open (GT_TRANSFER_DOWNLOAD, xfer->ip,
paulo@0 138 xfer->port)))
paulo@0 139 {
paulo@0 140 gt_transfer_close (xfer, TRUE);
paulo@0 141 return;
paulo@0 142 }
paulo@0 143
paulo@0 144 /* pass along the connection with the xfer */
paulo@0 145 gt_transfer_set_tcpc (xfer, c);
paulo@0 146 assert (xfer->chunk == chunk);
paulo@0 147 assert (chunk->udata == xfer);
paulo@0 148
paulo@0 149 gt_transfer_status (xfer, SOURCE_WAITING, "Connecting");
paulo@0 150
paulo@0 151 /* be a little more aggressive timing out HTTP connections (TIMEOUT_DEF /
paulo@0 152 * 2 + 5), so that useless sources don't occupy Chunks so often */
paulo@0 153 input_add (c->fd, xfer, INPUT_WRITE,
paulo@0 154 (InputCallback)gt_http_client_start, TIMEOUT_DEF / 2 + 5);
paulo@0 155 }
paulo@0 156
paulo@0 157 static int client_get_request (GtTransfer *xfer)
paulo@0 158 {
paulo@0 159 TCPC *c;
paulo@0 160 char host[128];
paulo@0 161 char range_hdr[64];
paulo@0 162 int ret;
paulo@0 163
paulo@0 164 if (!xfer)
paulo@0 165 return FALSE;
paulo@0 166
paulo@0 167 c = gt_transfer_get_tcpc (xfer);
paulo@0 168
paulo@0 169 snprintf (range_hdr, sizeof (range_hdr) - 1, "bytes=%i-%i",
paulo@0 170 (int) xfer->start, (int) xfer->stop - 1);
paulo@0 171
paulo@0 172 snprintf (host, sizeof (host) - 1, "%s:%hu", net_ip_str (xfer->ip),
paulo@0 173 xfer->port);
paulo@0 174
paulo@0 175 /* always send the Range request just because we always know the full size
paulo@0 176 * we want */
paulo@0 177 ret = gt_http_client_send (c, "GET", xfer->request,
paulo@0 178 "Range", range_hdr,
paulo@0 179 "Host", host,
paulo@0 180 "User-Agent", gt_version(),
paulo@0 181 "X-Queue", "0.1",
paulo@0 182 NULL);
paulo@0 183
paulo@0 184 return ret;
paulo@0 185 }
paulo@0 186
paulo@0 187 /*
paulo@0 188 * Verify connection status and Send the GET request to the server.
paulo@0 189 */
paulo@0 190 void gt_http_client_start (int fd, input_id id, GtTransfer *xfer)
paulo@0 191 {
paulo@0 192 Chunk *chunk;
paulo@0 193 TCPC *c;
paulo@0 194
paulo@0 195 c = gt_transfer_get_tcpc (xfer);
paulo@0 196 chunk = gt_transfer_get_chunk (xfer);
paulo@0 197
paulo@0 198 if (net_sock_error (c->fd))
paulo@0 199 {
paulo@0 200 GtSource *gt;
paulo@0 201
paulo@0 202 gt = gt_transfer_get_source (xfer);
paulo@0 203
paulo@0 204 /* set the connection as having failed, and retry w/ a push */
paulo@0 205 gt->connect_failed = TRUE;
paulo@0 206
paulo@0 207 gt_transfer_status (xfer, SOURCE_CANCELLED, (fd == -1 ?
paulo@0 208 "Connect timeout" :
paulo@0 209 "Connect failed"));
paulo@0 210 gt_transfer_close (xfer, TRUE);
paulo@0 211 return;
paulo@0 212 }
paulo@0 213
paulo@0 214 /*
paulo@0 215 * Update the length of the chunk in the GtTransfer. We do this
paulo@0 216 * because giftd may change the range of the chunk while we are
paulo@0 217 * playing with it, and this is the last point where we can update
paulo@0 218 * the range.
paulo@0 219 *
paulo@0 220 * If giftd changes the range after this point, we'll be forced to break
paulo@0 221 * this connection :(
paulo@0 222 */
paulo@0 223 gt_transfer_set_length (xfer, chunk);
paulo@0 224
paulo@0 225 /* send the GET / request to the server */
paulo@0 226 if (client_get_request (xfer) <= 0)
paulo@0 227 {
paulo@0 228 gt_transfer_status (xfer, SOURCE_CANCELLED, "GET send failed");
paulo@0 229 gt_transfer_close (xfer, TRUE);
paulo@0 230 return;
paulo@0 231 }
paulo@0 232
paulo@0 233 gt_transfer_status (xfer, SOURCE_WAITING, "Sent HTTP request");
paulo@0 234
paulo@0 235 /* do not remove all fds associated with this socket until we destroy it */
paulo@0 236 input_remove (id);
paulo@0 237
paulo@0 238 /* wait for the server response */
paulo@0 239 input_add (fd, xfer, INPUT_READ,
paulo@0 240 (InputCallback)get_server_reply, TIMEOUT_DEF);
paulo@0 241 }
paulo@0 242
paulo@0 243 /*
paulo@0 244 * Read the response body, if any, so persistent HTTP will work.
paulo@0 245 * Note that the GtTransfer can still timeout, in which case
paulo@0 246 * the connection will get closed and so will the xfer.
paulo@0 247 */
paulo@0 248 static void read_response_body (int fd, input_id id, GtTransfer *xfer)
paulo@0 249 {
paulo@0 250 Chunk *chunk;
paulo@0 251 TCPC *c;
paulo@0 252 FDBuf *buf;
paulo@0 253 char *response;
paulo@0 254 int n;
paulo@0 255 int len;
paulo@0 256
paulo@0 257 c = gt_transfer_get_tcpc (xfer);
paulo@0 258 chunk = gt_transfer_get_chunk (xfer);
paulo@0 259
paulo@0 260 len = xfer->stop - xfer->start;
paulo@0 261
paulo@0 262 /* since the body isnt important, close if its too large */
paulo@0 263 if (len >= 16384)
paulo@0 264 {
paulo@0 265 GT->DBGFN (GT, "[%s:%hu] response body too large (%d)",
paulo@0 266 net_ip_str (xfer->ip), xfer->port);
paulo@0 267 gt_transfer_close (xfer, TRUE);
paulo@0 268 return;
paulo@0 269 }
paulo@0 270
paulo@0 271 buf = tcp_readbuf (c);
paulo@0 272
paulo@0 273 if ((n = fdbuf_fill (buf, len)) < 0)
paulo@0 274 {
paulo@0 275 GT->DBGFN (GT, "error [%s:%hu]: %s",
paulo@0 276 net_ip_str (xfer->ip), xfer->port, GIFT_NETERROR ());
paulo@0 277 gt_transfer_close (xfer, TRUE);
paulo@0 278 return;
paulo@0 279 }
paulo@0 280
paulo@0 281 if (n > 0)
paulo@0 282 return;
paulo@0 283
paulo@0 284 /*
paulo@0 285 * Set the body as having been completely read.
paulo@0 286 * This allows the connection to be cached.
paulo@0 287 */
paulo@0 288 xfer->remaining_len -= len;
paulo@0 289 assert (xfer->remaining_len == 0);
paulo@0 290
paulo@0 291 response = fdbuf_data (buf, NULL);
paulo@0 292 fdbuf_release (buf);
paulo@0 293
paulo@0 294 if (HTTP_DEBUG)
paulo@0 295 GT->DBGSOCK (GT, c, "body:\n%s", response);
paulo@0 296
paulo@0 297 input_remove (id);
paulo@0 298
paulo@0 299 /* perform an orderly close */
paulo@0 300 gt_transfer_close (xfer, FALSE);
paulo@0 301 }
paulo@0 302
paulo@0 303 static time_t queue_interval (time_t interval)
paulo@0 304 {
paulo@0 305 /*
paulo@0 306 * HACK: giftd will retry the queued download every 49 seconds,
paulo@0 307 * so round the next retry time to coincide with that interval.
paulo@0 308 */
paulo@0 309 if (interval > 49)
paulo@0 310 interval = (interval / 49 + 1) * 49;
paulo@0 311
paulo@0 312 return interval;
paulo@0 313 }
paulo@0 314
paulo@0 315 /*****************************************************************************/
paulo@0 316
paulo@0 317 /* set the next time to retry from the Retry-After: header */
paulo@0 318 static void set_retry_after (GtTransfer *xfer)
paulo@0 319 {
paulo@0 320 int seconds;
paulo@0 321 char *retry_after;
paulo@0 322 GtSource *gt;
paulo@0 323 #if 0
paulo@0 324 char *msg;
paulo@0 325 struct tm *tm;
paulo@0 326 #endif
paulo@0 327
paulo@0 328 if (!(retry_after = dataset_lookupstr (xfer->header, "retry-after")))
paulo@0 329 return;
paulo@0 330
paulo@0 331 /*
paulo@0 332 * This can be either a HTTP date or a number of seconds. We only
paulo@0 333 * support the number of seconds right now.
paulo@0 334 */
paulo@0 335 seconds = ATOI (retry_after);
paulo@0 336
paulo@0 337 if (seconds <= 0)
paulo@0 338 return;
paulo@0 339
paulo@0 340 if (!(gt = gt_transfer_get_source (xfer)))
paulo@0 341 return;
paulo@0 342
paulo@0 343 /* set the retry time for the next download */
paulo@0 344 gt->retry_time = time (NULL) + queue_interval (seconds);
paulo@0 345
paulo@0 346 #if 0
paulo@0 347 /* get the absolute time */
paulo@0 348 tm = localtime (&gt->retry_time);
paulo@0 349
paulo@0 350 /* let the user know when we are going to retry */
paulo@0 351 msg = stringf_dup ("Queued (retry at %d:%02d:%02d)", tm->tm_hour,
paulo@0 352 tm->tm_min, tm->tm_sec);
paulo@0 353
paulo@0 354 gt_transfer_status (xfer, SOURCE_QUEUED_REMOTE, msg);
paulo@0 355 free (msg);
paulo@0 356 #endif
paulo@0 357 }
paulo@0 358
paulo@0 359 /*
paulo@0 360 * Check for both the active-queueing style "X-Queue:" and PARQ-style
paulo@0 361 * "X-Queued:".
paulo@0 362 *
paulo@0 363 * We avoid having to parse the X-Queue: line in the PARQ case (which would
paulo@0 364 * be "1.0") by allowing X-Queued to override X-Queue.
paulo@0 365 */
paulo@0 366 static size_t find_queue_key (Dataset *header, const char *key)
paulo@0 367 {
paulo@0 368 size_t pos = 0;
paulo@0 369 char *val;
paulo@0 370 char *line0, *line;
paulo@0 371 char *active_queue_line;
paulo@0 372 char *parq_queue_line;
paulo@0 373 char *sep;
paulo@0 374
paulo@0 375 active_queue_line = dataset_lookupstr (header, "x-queue");
paulo@0 376 parq_queue_line = dataset_lookupstr (header, "x-queued");
paulo@0 377
paulo@0 378 if (!active_queue_line && !parq_queue_line)
paulo@0 379 return 0;
paulo@0 380
paulo@0 381 line = active_queue_line;
paulo@0 382 sep = ", ";
paulo@0 383
paulo@0 384 if (parq_queue_line)
paulo@0 385 {
paulo@0 386 line = parq_queue_line;
paulo@0 387 sep = "; ";
paulo@0 388 }
paulo@0 389
paulo@0 390 line0 = line = STRDUP (line);
paulo@0 391
paulo@0 392 while ((val = string_sep_set (&line, sep)))
paulo@0 393 {
paulo@0 394 char *str;
paulo@0 395
paulo@0 396 str = string_sep_set (&val, "= ");
paulo@0 397
paulo@0 398 if (!str || !val)
paulo@0 399 continue;
paulo@0 400
paulo@0 401 if (!strcasecmp (str, key))
paulo@0 402 pos = ATOI (val);
paulo@0 403 }
paulo@0 404
paulo@0 405 free (line0);
paulo@0 406 return pos;
paulo@0 407 }
paulo@0 408
paulo@0 409 /* Create a message describing our position in the remote node's
paulo@0 410 * upload queue. */
paulo@0 411 static char *get_queue_status (GtTransfer *xfer, char *msg)
paulo@0 412 {
paulo@0 413 size_t len = 0;
paulo@0 414 size_t pos = 0;
paulo@0 415
paulo@0 416 pos = find_queue_key (xfer->header, "position");
paulo@0 417 len = find_queue_key (xfer->header, "length");
paulo@0 418
paulo@0 419 msg = STRDUP (msg);
paulo@0 420
paulo@0 421 if (pos != 0)
paulo@0 422 {
paulo@0 423 free (msg);
paulo@0 424
paulo@0 425 if (len != 0)
paulo@0 426 msg = stringf_dup ("Queued (%u/%u)", pos, len);
paulo@0 427 else
paulo@0 428 msg = stringf_dup ("Queued (position %u)", pos);
paulo@0 429 }
paulo@0 430
paulo@0 431 return msg;
paulo@0 432 }
paulo@0 433
paulo@0 434 /* set the next time to retry the download based on the X-Queue: field */
paulo@0 435 static void set_queue_status (GtTransfer *xfer)
paulo@0 436 {
paulo@0 437 GtSource *gt;
paulo@0 438 char *queue_line;
paulo@0 439 int poll_min;
paulo@0 440
paulo@0 441 if (!(gt = gt_transfer_get_source (xfer)))
paulo@0 442 return;
paulo@0 443
paulo@0 444 if (!(queue_line = dataset_lookupstr (xfer->header, "x-queue")))
paulo@0 445 return;
paulo@0 446
paulo@0 447 if ((poll_min = find_queue_key (xfer->header, "pollmin")) <= 0)
paulo@0 448 return;
paulo@0 449
paulo@0 450 gt->retry_time = time (NULL) + queue_interval (poll_min);
paulo@0 451 }
paulo@0 452
paulo@0 453 /*
paulo@0 454 * Try to read any content-body in the response, so that persistent
paulo@0 455 * HTTP can work correctly, which is really important for push downloads.
paulo@0 456 */
paulo@0 457 static void handle_http_error (GtTransfer *xfer, SourceStatus status,
paulo@0 458 char *status_txt)
paulo@0 459 {
paulo@0 460 TCPC *c;
paulo@0 461 Chunk *chunk;
paulo@0 462 int len = 0;
paulo@0 463 char *content_len;
paulo@0 464 char *conn_hdr;
paulo@0 465
paulo@0 466 /* update the interface protocol with the error status of this xfer */
paulo@0 467 status_txt = get_queue_status (xfer, status_txt);
paulo@0 468 gt_transfer_status (xfer, status, status_txt);
paulo@0 469
paulo@0 470 free (status_txt);
paulo@0 471
paulo@0 472 c = gt_transfer_get_tcpc (xfer);
paulo@0 473 chunk = gt_transfer_get_chunk (xfer);
paulo@0 474
paulo@0 475 /*
paulo@0 476 * Check for a Content-Length: field, and use that for the
paulo@0 477 * length of the response body, if any.
paulo@0 478 */
paulo@0 479 content_len = dataset_lookupstr (xfer->header, "content-length");
paulo@0 480 conn_hdr = dataset_lookupstr (xfer->header, "connection");
paulo@0 481
paulo@0 482 /* look at the Retry-After: header and set download retry time */
paulo@0 483 set_retry_after (xfer);
paulo@0 484
paulo@0 485 /* parse the X-Queue: and X-Queued: headers for the queue position/
paulo@0 486 * retry time */
paulo@0 487 set_queue_status (xfer);
paulo@0 488
paulo@0 489 /*
paulo@0 490 * Don't read the body if they supplied "Connection: Close".
paulo@0 491 * Close if the HTTP version is 1.0 (TODO: this should
paulo@0 492 * not close 1.0 connections if they supplied Connection:
paulo@0 493 * Keep-Alive, I guess)
paulo@0 494 */
paulo@0 495 if (!STRCASECMP (xfer->version, "HTTP/1.0") ||
paulo@0 496 !STRCASECMP (xfer->version, "HTTP") ||
paulo@0 497 !STRCASECMP (conn_hdr, "close"))
paulo@0 498 {
paulo@0 499 gt_transfer_close (xfer, TRUE);
paulo@0 500 return;
paulo@0 501 }
paulo@0 502
paulo@0 503 if (content_len)
paulo@0 504 len = ATOUL (content_len);
paulo@0 505
paulo@0 506 /* abuse xfer->{start,stop} fields for the length of the response body */
paulo@0 507 xfer->start = 0;
paulo@0 508 xfer->stop = len;
paulo@0 509
paulo@0 510 /* set this flag to let gt_transfer_close() know the headers
paulo@0 511 * have been parsed */
paulo@0 512 xfer->transmitted_hdrs = TRUE;
paulo@0 513
paulo@0 514 /* set the length so that if the connection times out, it will be
paulo@0 515 * force closed when we havent read the entire body */
paulo@0 516 xfer->remaining_len = len;
paulo@0 517
paulo@0 518 /* if there is no length to read, we are done */
paulo@0 519 if (len == 0)
paulo@0 520 {
paulo@0 521 gt_transfer_close (xfer, FALSE);
paulo@0 522 return;
paulo@0 523 }
paulo@0 524
paulo@0 525 input_remove_all (c->fd);
paulo@0 526 input_add (c->fd, xfer, INPUT_READ,
paulo@0 527 (InputCallback)read_response_body, TIMEOUT_DEF);
paulo@0 528 }
paulo@0 529
paulo@0 530 /*****************************************************************************/
paulo@0 531
paulo@0 532 /*
paulo@0 533 * Process an HTTP return code (either client or server [push]) and attempt
paulo@0 534 * to appropriately handle/expunge the transfer structure accordingly. The
paulo@0 535 * return value indicates whether or not we may continue after the code
paulo@0 536 * has been processed. Some error codes (404) are considered fatal here
paulo@0 537 * and you should abort after this call.
paulo@0 538 *
paulo@0 539 * NOTE:
paulo@0 540 * If this function returns FALSE, the calling argument is free'd and you
paulo@0 541 * should not access it further.
paulo@0 542 */
paulo@0 543 int gt_http_handle_code (GtTransfer *xfer, int code)
paulo@0 544 {
paulo@0 545 TCPC *c;
paulo@0 546 Chunk *chunk;
paulo@0 547 GtSource *gt;
paulo@0 548
paulo@0 549 /* successful code, do nothing */
paulo@0 550 if (code >= 200 && code <= 299)
paulo@0 551 return TRUE;
paulo@0 552
paulo@0 553 c = gt_transfer_get_tcpc (xfer);
paulo@0 554 chunk = gt_transfer_get_chunk (xfer);
paulo@0 555
paulo@0 556 gt = gt_transfer_get_source (xfer);
paulo@0 557 assert (gt != NULL);
paulo@0 558
paulo@0 559 /*
paulo@0 560 * We have to be careful not to access the transfer or any
paulo@0 561 * data related to it after calling p->source_abort, which
paulo@0 562 * will cancel the transfer.
paulo@0 563 */
paulo@0 564 switch (code)
paulo@0 565 {
paulo@0 566 default:
paulo@0 567 case 404: /* not found */
paulo@0 568 {
paulo@0 569 /*
paulo@0 570 * If this was a uri-res request, retry with an index request.
paulo@0 571 * Otherwise, remove the source.
paulo@0 572 *
paulo@0 573 * TODO: perhaps this should retry with url-encoded index
paulo@0 574 * request, before removing?
paulo@0 575 */
paulo@0 576 if (gt->uri_res_failed)
paulo@0 577 {
paulo@0 578 GT->source_abort (GT, chunk->transfer, xfer->source);
paulo@0 579 }
paulo@0 580 else
paulo@0 581 {
paulo@0 582 handle_http_error (xfer, SOURCE_QUEUED_REMOTE,
paulo@0 583 "File not found");
paulo@0 584 gt->uri_res_failed = TRUE;
paulo@0 585 }
paulo@0 586 }
paulo@0 587 break;
paulo@0 588 case 401: /* unauthorized */
paulo@0 589 handle_http_error (xfer, SOURCE_CANCELLED, "Access denied");
paulo@0 590 break;
paulo@0 591 case 503: /* remotely queued */
paulo@0 592 handle_http_error (xfer, SOURCE_QUEUED_REMOTE, "Queued (Remotely)");
paulo@0 593 break;
paulo@0 594 case 500: /* source may not match, check later */
paulo@0 595 /*
paulo@0 596 * The remote node has reported that this file has changed
paulo@0 597 * since the last time we received results for it. This more
paulo@0 598 * than likely indicates a hash change, in which case we should
paulo@0 599 * not keep this associated with this transfer. Unfortunately,
paulo@0 600 * giFT fails to provide any sort of abstraction for us
paulo@0 601 * to remove sources ourselves, so we need this hopefully
paulo@0 602 * temporary hack to remove the source.
paulo@0 603 */
paulo@0 604 GT->source_abort (GT, chunk->transfer, xfer->source);
paulo@0 605 break;
paulo@0 606 }
paulo@0 607
paulo@0 608 return FALSE;
paulo@0 609 }
paulo@0 610
paulo@0 611 static int parse_content_range (char *range, off_t *r_start, off_t *r_end,
paulo@0 612 off_t *r_size)
paulo@0 613 {
paulo@0 614 char *start, *end, *size;
paulo@0 615
paulo@0 616 string_sep (&range, "bytes");
paulo@0 617 string_sep_set (&range, " ="); /* accept "bytes=" and "bytes " */
paulo@0 618
paulo@0 619 if (r_end)
paulo@0 620 *r_end = -1;
paulo@0 621 if (r_start)
paulo@0 622 *r_start = -1;
paulo@0 623 if (r_size)
paulo@0 624 *r_size = -1;
paulo@0 625
paulo@0 626 if (!range)
paulo@0 627 return FALSE;
paulo@0 628
paulo@0 629 start = string_sep (&range, "-");
paulo@0 630 end = string_sep (&range, "/");
paulo@0 631 size = range;
paulo@0 632
paulo@0 633 if (r_start && start)
paulo@0 634 *r_start = ATOUL (start);
paulo@0 635 if (r_end && end)
paulo@0 636 *r_end = ATOUL (end);
paulo@0 637 if (r_size && size)
paulo@0 638 *r_size = ATOUL (size);
paulo@0 639
paulo@0 640 if (start && end && size)
paulo@0 641 return TRUE;
paulo@0 642
paulo@0 643 return FALSE;
paulo@0 644 }
paulo@0 645
paulo@0 646 static int verify_range_response (GtTransfer *xfer, Chunk *chunk)
paulo@0 647 {
paulo@0 648 char *user_agent;
paulo@0 649 char *content_range;
paulo@0 650 char *content_len;
paulo@0 651 off_t start, stop, size;
paulo@0 652 #if 0
paulo@0 653 off_t file_size;
paulo@0 654 #endif
paulo@0 655 off_t xfer_size;
paulo@0 656 int error = FALSE;
paulo@0 657
paulo@0 658 #if 0
paulo@0 659 file_size = chunk->transfer->total;
paulo@0 660 #endif
paulo@0 661 xfer_size = xfer->stop - xfer->start;
paulo@0 662
paulo@0 663 if ((content_len = dataset_lookupstr (xfer->header, "content-length")))
paulo@0 664 {
paulo@0 665 size = ATOUL (content_len);
paulo@0 666
paulo@0 667 if (size != xfer_size)
paulo@0 668 {
paulo@0 669 GIFT_ERROR (("bad content len=%lu, expected %lu", size, xfer_size));
paulo@0 670 error = TRUE;
paulo@0 671 gt_transfer_status (xfer, SOURCE_CANCELLED, "Bad Content-Length");
paulo@0 672 }
paulo@0 673 }
paulo@0 674
paulo@0 675 if ((content_range = dataset_lookupstr (xfer->header, "content-range")))
paulo@0 676 {
paulo@0 677 if (HTTP_DEBUG)
paulo@0 678 {
paulo@0 679 GT->dbg (GT, "Content-Range: %s, start=%lu, stop=%lu",
paulo@0 680 content_range, chunk->start, chunk->stop);
paulo@0 681 }
paulo@0 682
paulo@0 683 if (parse_content_range (content_range, &start, &stop, &size))
paulo@0 684 {
paulo@0 685 if (start != xfer->start)
paulo@0 686 {
paulo@0 687 GIFT_ERROR (("bad xfer start: %lu %lu", xfer->start, start));
paulo@0 688 error = TRUE;
paulo@0 689 }
paulo@0 690 if (stop != xfer->stop - 1)
paulo@0 691 {
paulo@0 692 GIFT_ERROR (("bad xfer end: %lu %lu", xfer->stop, stop));
paulo@0 693 error = TRUE;
paulo@0 694 }
paulo@0 695 #if 0
paulo@0 696 if (size != file_size)
paulo@0 697 {
paulo@0 698 GIFT_ERROR (("bad xfer size: %lu, expected %lu", file_size,
paulo@0 699 size));
paulo@0 700 error = TRUE;
paulo@0 701 }
paulo@0 702 #endif
paulo@0 703 }
paulo@0 704 else
paulo@0 705 {
paulo@0 706 GIFT_ERROR (("error parsing content-range hdr"));
paulo@0 707 error = TRUE;
paulo@0 708 }
paulo@0 709 }
paulo@0 710
paulo@0 711 if (!content_len && !content_range)
paulo@0 712 {
paulo@0 713 if (!(user_agent = dataset_lookupstr (xfer->header, "Server")))
paulo@0 714 user_agent = dataset_lookupstr (xfer->header, "User-Agent");
paulo@0 715
paulo@0 716 GIFT_ERROR (("missing Content-Range/Length, start=%lu, stop=%lu, "
paulo@0 717 "culprit=%s", xfer->start, xfer->stop, user_agent));
paulo@0 718
paulo@0 719 error = TRUE;
paulo@0 720 }
paulo@0 721
paulo@0 722 if (error)
paulo@0 723 {
paulo@0 724 GT->DBGFN (GT, "removing source %s", chunk->source->url);
paulo@0 725 GT->source_abort (GT, chunk->transfer, chunk->source);
paulo@0 726 return FALSE;
paulo@0 727 }
paulo@0 728
paulo@0 729 return TRUE;
paulo@0 730 }
paulo@0 731
paulo@0 732 /*
paulo@0 733 * Receive and process the HTTP response
paulo@0 734 */
paulo@0 735 static void get_server_reply (int fd, input_id id, GtTransfer *xfer)
paulo@0 736 {
paulo@0 737 Chunk *chunk;
paulo@0 738 TCPC *c;
paulo@0 739 GtSource *gt;
paulo@0 740 FDBuf *buf;
paulo@0 741 unsigned char *data;
paulo@0 742 char *msg;
paulo@0 743 size_t data_len = 0;
paulo@0 744 int n;
paulo@0 745
paulo@0 746 chunk = gt_transfer_get_chunk (xfer);
paulo@0 747 c = gt_transfer_get_tcpc (xfer);
paulo@0 748
paulo@0 749 gt = gt_transfer_get_source (xfer);
paulo@0 750
paulo@0 751 buf = tcp_readbuf (c);
paulo@0 752
paulo@0 753 /* attempt to read the complete server response */
paulo@0 754 if ((n = fdbuf_delim (buf, "\n")) < 0)
paulo@0 755 {
paulo@0 756 msg = "Timed out";
paulo@0 757
paulo@0 758 /*
paulo@0 759 * If the peer abruptly closed the connection, its possible it
paulo@0 760 * didn't understand our request if it was a uri-res request.
paulo@0 761 * So, disable uri-res in that case.
paulo@0 762 */
paulo@0 763 if (fd != -1)
paulo@0 764 {
paulo@0 765 gt->uri_res_failed = TRUE;
paulo@0 766 msg = "Connection closed";
paulo@0 767 }
paulo@0 768
paulo@0 769 gt_transfer_status (xfer, SOURCE_CANCELLED, msg);
paulo@0 770 gt_transfer_close (xfer, TRUE);
paulo@0 771 return;
paulo@0 772 }
paulo@0 773
paulo@0 774 if (gt_fdbuf_full (buf))
paulo@0 775 {
paulo@0 776 gt_transfer_close (xfer, TRUE);
paulo@0 777 return;
paulo@0 778 }
paulo@0 779
paulo@0 780 if (n > 0)
paulo@0 781 return;
paulo@0 782
paulo@0 783 data = fdbuf_data (buf, &data_len);
paulo@0 784
paulo@0 785 if (!gt_http_header_terminated (data, data_len))
paulo@0 786 return;
paulo@0 787
paulo@0 788 fdbuf_release (buf);
paulo@0 789
paulo@0 790 /* parse the server response */
paulo@0 791 if (!parse_server_reply (xfer, c, data))
paulo@0 792 {
paulo@0 793 gt_transfer_status (xfer, SOURCE_CANCELLED, "Malformed HTTP header");
paulo@0 794 gt_transfer_close (xfer, TRUE);
paulo@0 795 return;
paulo@0 796 }
paulo@0 797
paulo@0 798 /*
paulo@0 799 * NOTE: if we wanted to do any further processing of the server reply
paulo@0 800 * after GET /, this is where it would be
paulo@0 801 */
paulo@0 802
paulo@0 803 /* determine what to do with the HTTP code reply */
paulo@0 804 if (!gt_http_handle_code (xfer, xfer->code))
paulo@0 805 return;
paulo@0 806
paulo@0 807 /* make sure the server understood our "Range:" request */
paulo@0 808 if (!verify_range_response (xfer, chunk))
paulo@0 809 return;
paulo@0 810
paulo@0 811 /*
paulo@0 812 * Received HTTP headers, ...and now we are waiting for the file. This
paulo@0 813 * should be a very short wait :)
paulo@0 814 */
paulo@0 815 gt_transfer_status (xfer, SOURCE_WAITING, "Received HTTP headers");
paulo@0 816 xfer->transmitted_hdrs = TRUE;
paulo@0 817
paulo@0 818 /* special case if the request size is 0 */
paulo@0 819 if (xfer->remaining_len == 0)
paulo@0 820 {
paulo@0 821 gt_transfer_close (xfer, FALSE);
paulo@0 822 return;
paulo@0 823 }
paulo@0 824
paulo@0 825 /* disable the header read timeout */
paulo@0 826 timer_remove_zero (&xfer->header_timer);
paulo@0 827
paulo@0 828 /* wait for the file to be sent by the server */
paulo@0 829 input_remove (id);
paulo@0 830 input_add (fd, xfer, INPUT_READ,
paulo@0 831 (InputCallback)gt_get_read_file, 0);
paulo@0 832 }
paulo@0 833
paulo@0 834 /*
paulo@0 835 * Receive the requested file from the server.
paulo@0 836 */
paulo@0 837 void gt_get_read_file (int fd, input_id id, GtTransfer *xfer)
paulo@0 838 {
paulo@0 839 TCPC *c;
paulo@0 840 Chunk *chunk;
paulo@0 841 char buf[RW_BUFFER];
paulo@0 842 size_t size;
paulo@0 843 int recv_len;
paulo@0 844
paulo@0 845 c = gt_transfer_get_tcpc (xfer);
paulo@0 846 chunk = gt_transfer_get_chunk (xfer);
paulo@0 847
paulo@0 848 /* set the max initial size of the request */
paulo@0 849 size = sizeof (buf);
paulo@0 850
paulo@0 851 /*
paulo@0 852 * Cap the size of the received length at the length
paulo@0 853 * of the request.
paulo@0 854 *
paulo@0 855 * This way we can't receive part of the next request
paulo@0 856 * header with persistent HTTP.
paulo@0 857 */
paulo@0 858 if (size > xfer->remaining_len)
paulo@0 859 size = xfer->remaining_len;
paulo@0 860
paulo@0 861 /*
paulo@0 862 * Ask giFT for the max size we should read. If this returns 0, the
paulo@0 863 * download was suspended.
paulo@0 864 */
paulo@0 865 if ((size = download_throttle (chunk, size)) == 0)
paulo@0 866 return;
paulo@0 867
paulo@0 868 if ((recv_len = tcp_recv (c, buf, size)) <= 0)
paulo@0 869 {
paulo@0 870 GT->DBGFN (GT, "tcp_recv error (%d) from %s:%hu: %s", recv_len,
paulo@0 871 net_ip_str (c->host), c->port, GIFT_NETERROR());
paulo@0 872
paulo@0 873 gt_transfer_status (xfer, SOURCE_CANCELLED, "Cancelled remotely");
paulo@0 874 gt_transfer_close (xfer, TRUE);
paulo@0 875 return;
paulo@0 876 }
paulo@0 877
paulo@0 878 /*
paulo@0 879 * We are receiving a file here, so this will always be calling
paulo@0 880 * gt_download.
paulo@0 881 */
paulo@0 882 gt_transfer_write (xfer, chunk, buf, (size_t)recv_len);
paulo@0 883 }
paulo@0 884
paulo@0 885 /*****************************************************************************/
paulo@0 886
paulo@0 887 static void client_reset_timeout (int fd, input_id id, TCPC *c)
paulo@0 888 {
paulo@0 889 /* normally we would call recv () here but there's no reason why the server
paulo@0 890 * should be sending stuff down to use...so, disconnect */
paulo@0 891 if (HTTP_DEBUG)
paulo@0 892 GT->DBGSOCK (GT, c, "closing client HTTP connection");
paulo@0 893
paulo@0 894 gt_http_connection_close (GT_TRANSFER_DOWNLOAD, c, TRUE);
paulo@0 895 }
paulo@0 896
paulo@0 897 void gt_http_client_reset (TCPC *c)
paulo@0 898 {
paulo@0 899 /* This can happen when the GtTransfer and TCPC have been decoupled,
paulo@0 900 * such as when we are waiting for a GIV response from a node. */
paulo@0 901 if (!c)
paulo@0 902 return;
paulo@0 903
paulo@0 904 tcp_flush (c, TRUE);
paulo@0 905
paulo@0 906 input_remove_all (c->fd);
paulo@0 907 input_add (c->fd, c, INPUT_READ,
paulo@0 908 (InputCallback)client_reset_timeout, 2 * MINUTES);
paulo@0 909 }