Mercurial > hg > index.fcgi > gift-gnutella > gift-gnutella-0.0.11-1pba
diff 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 |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/gt_http_client.c Sat Feb 20 21:18:28 2010 -0800 1.3 @@ -0,0 +1,909 @@ 1.4 +/* 1.5 + * $Id: gt_http_client.c,v 1.57 2006/02/03 20:11:40 mkern Exp $ 1.6 + * 1.7 + * Copyright (C) 2001-2003 giFT project (gift.sourceforge.net) 1.8 + * 1.9 + * This program is free software; you can redistribute it and/or modify it 1.10 + * under the terms of the GNU General Public License as published by the 1.11 + * Free Software Foundation; either version 2, or (at your option) any 1.12 + * later version. 1.13 + * 1.14 + * This program is distributed in the hope that it will be useful, but 1.15 + * WITHOUT ANY WARRANTY; without even the implied warranty of 1.16 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 1.17 + * General Public License for more details. 1.18 + */ 1.19 + 1.20 +#include "gt_gnutella.h" 1.21 +#include "gt_version.h" 1.22 + 1.23 +#include "gt_xfer_obj.h" 1.24 +#include "gt_xfer.h" 1.25 + 1.26 +#include "gt_http_client.h" 1.27 +#include "gt_http_server.h" 1.28 + 1.29 +#include "gt_accept.h" 1.30 + 1.31 +#include "transfer/source.h" 1.32 + 1.33 +/*****************************************************************************/ 1.34 + 1.35 +/* prototyping this function effectively provides the non-blocking flow of the 1.36 + * program and helps to self-document this file */ 1.37 +static void get_server_reply (int fd, input_id id, GtTransfer *xfer); 1.38 + 1.39 +/*****************************************************************************/ 1.40 +/* CLIENT HELPERS */ 1.41 + 1.42 +static int gt_http_client_send (TCPC *c, char *command, char *request, ...) 1.43 +{ 1.44 + char *key; 1.45 + char *value; 1.46 + String *s; 1.47 + int ret; 1.48 + va_list args; 1.49 + 1.50 + if (!command || !request) 1.51 + return -1; 1.52 + 1.53 + if (!(s = string_new (NULL, 0, 0, TRUE))) 1.54 + return -1; 1.55 + 1.56 + string_appendf (s, "%s %s HTTP/1.1\r\n", command, request); 1.57 + 1.58 + va_start (args, request); 1.59 + 1.60 + for (;;) 1.61 + { 1.62 + /* if we receive the sentinel, bail out */ 1.63 + if (!(key = va_arg (args, char *))) 1.64 + break; 1.65 + 1.66 + if (!(value = va_arg (args, char *))) 1.67 + continue; 1.68 + 1.69 + string_appendf (s, "%s: %s\r\n", key, value); 1.70 + } 1.71 + 1.72 + va_end (args); 1.73 + 1.74 + /* append final message terminator */ 1.75 + string_append (s, "\r\n"); 1.76 + 1.77 + if (HTTP_DEBUG) 1.78 + GT->DBGSOCK (GT, c, "sending client request:\n%s", s->str); 1.79 + 1.80 + ret = tcp_send (c, s->str, s->len); 1.81 + string_free (s); 1.82 + 1.83 + return ret; 1.84 +} 1.85 + 1.86 +/* parse an HTTP server reply */ 1.87 +static BOOL parse_server_reply (GtTransfer *xfer, TCPC *c, char *reply) 1.88 +{ 1.89 + char *response; /* HTTP/1.1 200 OK */ 1.90 + char *version; 1.91 + int code; /* 200, 404, ... */ 1.92 + 1.93 + if (!xfer || !reply) 1.94 + return FALSE; 1.95 + 1.96 + if (HTTP_DEBUG) 1.97 + GT->DBGSOCK (GT, c, "reply:\n%s", reply); 1.98 + 1.99 + response = string_sep_set (&reply, "\r\n"); 1.100 + 1.101 + if (!response) 1.102 + return FALSE; 1.103 + 1.104 + version = string_sep (&response, " "); /* shift past HTTP/1.1 */ 1.105 + code = ATOI (string_sep (&response, " ")); /* shift past 200 */ 1.106 + 1.107 + /* parse the rest of the key/value fields */ 1.108 + gt_http_header_parse (reply, &xfer->header); 1.109 + 1.110 + xfer->code = code; 1.111 + xfer->version = STRDUP (version); 1.112 + 1.113 + return TRUE; 1.114 +} 1.115 + 1.116 +/*****************************************************************************/ 1.117 + 1.118 +/* 1.119 + * Complete interface to the standard HTTP GET / with a server. These routines 1.120 + * require non of OpenFT's extensions and communicate perfectly valid 1.121 + * HTTP/1.1 (to my knowledge). You could use this to transfer non-OpenFT, 1.122 + * hopefully. ;) 1.123 + * 1.124 + * NOTE: 1.125 + * I need to add more text here so this stands out better as one of the two 1.126 + * core subsystems within this file. So here it is. :P 1.127 + */ 1.128 +void gt_http_client_get (Chunk *chunk, GtTransfer *xfer) 1.129 +{ 1.130 + TCPC *c; 1.131 + 1.132 + if (!chunk || !xfer) 1.133 + { 1.134 + GT->DBGFN (GT, "uhm."); 1.135 + return; 1.136 + } 1.137 + 1.138 + xfer->command = STRDUP ("GET"); 1.139 + 1.140 + if (!(c = gt_http_connection_open (GT_TRANSFER_DOWNLOAD, xfer->ip, 1.141 + xfer->port))) 1.142 + { 1.143 + gt_transfer_close (xfer, TRUE); 1.144 + return; 1.145 + } 1.146 + 1.147 + /* pass along the connection with the xfer */ 1.148 + gt_transfer_set_tcpc (xfer, c); 1.149 + assert (xfer->chunk == chunk); 1.150 + assert (chunk->udata == xfer); 1.151 + 1.152 + gt_transfer_status (xfer, SOURCE_WAITING, "Connecting"); 1.153 + 1.154 + /* be a little more aggressive timing out HTTP connections (TIMEOUT_DEF / 1.155 + * 2 + 5), so that useless sources don't occupy Chunks so often */ 1.156 + input_add (c->fd, xfer, INPUT_WRITE, 1.157 + (InputCallback)gt_http_client_start, TIMEOUT_DEF / 2 + 5); 1.158 +} 1.159 + 1.160 +static int client_get_request (GtTransfer *xfer) 1.161 +{ 1.162 + TCPC *c; 1.163 + char host[128]; 1.164 + char range_hdr[64]; 1.165 + int ret; 1.166 + 1.167 + if (!xfer) 1.168 + return FALSE; 1.169 + 1.170 + c = gt_transfer_get_tcpc (xfer); 1.171 + 1.172 + snprintf (range_hdr, sizeof (range_hdr) - 1, "bytes=%i-%i", 1.173 + (int) xfer->start, (int) xfer->stop - 1); 1.174 + 1.175 + snprintf (host, sizeof (host) - 1, "%s:%hu", net_ip_str (xfer->ip), 1.176 + xfer->port); 1.177 + 1.178 + /* always send the Range request just because we always know the full size 1.179 + * we want */ 1.180 + ret = gt_http_client_send (c, "GET", xfer->request, 1.181 + "Range", range_hdr, 1.182 + "Host", host, 1.183 + "User-Agent", gt_version(), 1.184 + "X-Queue", "0.1", 1.185 + NULL); 1.186 + 1.187 + return ret; 1.188 +} 1.189 + 1.190 +/* 1.191 + * Verify connection status and Send the GET request to the server. 1.192 + */ 1.193 +void gt_http_client_start (int fd, input_id id, GtTransfer *xfer) 1.194 +{ 1.195 + Chunk *chunk; 1.196 + TCPC *c; 1.197 + 1.198 + c = gt_transfer_get_tcpc (xfer); 1.199 + chunk = gt_transfer_get_chunk (xfer); 1.200 + 1.201 + if (net_sock_error (c->fd)) 1.202 + { 1.203 + GtSource *gt; 1.204 + 1.205 + gt = gt_transfer_get_source (xfer); 1.206 + 1.207 + /* set the connection as having failed, and retry w/ a push */ 1.208 + gt->connect_failed = TRUE; 1.209 + 1.210 + gt_transfer_status (xfer, SOURCE_CANCELLED, (fd == -1 ? 1.211 + "Connect timeout" : 1.212 + "Connect failed")); 1.213 + gt_transfer_close (xfer, TRUE); 1.214 + return; 1.215 + } 1.216 + 1.217 + /* 1.218 + * Update the length of the chunk in the GtTransfer. We do this 1.219 + * because giftd may change the range of the chunk while we are 1.220 + * playing with it, and this is the last point where we can update 1.221 + * the range. 1.222 + * 1.223 + * If giftd changes the range after this point, we'll be forced to break 1.224 + * this connection :( 1.225 + */ 1.226 + gt_transfer_set_length (xfer, chunk); 1.227 + 1.228 + /* send the GET / request to the server */ 1.229 + if (client_get_request (xfer) <= 0) 1.230 + { 1.231 + gt_transfer_status (xfer, SOURCE_CANCELLED, "GET send failed"); 1.232 + gt_transfer_close (xfer, TRUE); 1.233 + return; 1.234 + } 1.235 + 1.236 + gt_transfer_status (xfer, SOURCE_WAITING, "Sent HTTP request"); 1.237 + 1.238 + /* do not remove all fds associated with this socket until we destroy it */ 1.239 + input_remove (id); 1.240 + 1.241 + /* wait for the server response */ 1.242 + input_add (fd, xfer, INPUT_READ, 1.243 + (InputCallback)get_server_reply, TIMEOUT_DEF); 1.244 +} 1.245 + 1.246 +/* 1.247 + * Read the response body, if any, so persistent HTTP will work. 1.248 + * Note that the GtTransfer can still timeout, in which case 1.249 + * the connection will get closed and so will the xfer. 1.250 + */ 1.251 +static void read_response_body (int fd, input_id id, GtTransfer *xfer) 1.252 +{ 1.253 + Chunk *chunk; 1.254 + TCPC *c; 1.255 + FDBuf *buf; 1.256 + char *response; 1.257 + int n; 1.258 + int len; 1.259 + 1.260 + c = gt_transfer_get_tcpc (xfer); 1.261 + chunk = gt_transfer_get_chunk (xfer); 1.262 + 1.263 + len = xfer->stop - xfer->start; 1.264 + 1.265 + /* since the body isnt important, close if its too large */ 1.266 + if (len >= 16384) 1.267 + { 1.268 + GT->DBGFN (GT, "[%s:%hu] response body too large (%d)", 1.269 + net_ip_str (xfer->ip), xfer->port); 1.270 + gt_transfer_close (xfer, TRUE); 1.271 + return; 1.272 + } 1.273 + 1.274 + buf = tcp_readbuf (c); 1.275 + 1.276 + if ((n = fdbuf_fill (buf, len)) < 0) 1.277 + { 1.278 + GT->DBGFN (GT, "error [%s:%hu]: %s", 1.279 + net_ip_str (xfer->ip), xfer->port, GIFT_NETERROR ()); 1.280 + gt_transfer_close (xfer, TRUE); 1.281 + return; 1.282 + } 1.283 + 1.284 + if (n > 0) 1.285 + return; 1.286 + 1.287 + /* 1.288 + * Set the body as having been completely read. 1.289 + * This allows the connection to be cached. 1.290 + */ 1.291 + xfer->remaining_len -= len; 1.292 + assert (xfer->remaining_len == 0); 1.293 + 1.294 + response = fdbuf_data (buf, NULL); 1.295 + fdbuf_release (buf); 1.296 + 1.297 + if (HTTP_DEBUG) 1.298 + GT->DBGSOCK (GT, c, "body:\n%s", response); 1.299 + 1.300 + input_remove (id); 1.301 + 1.302 + /* perform an orderly close */ 1.303 + gt_transfer_close (xfer, FALSE); 1.304 +} 1.305 + 1.306 +static time_t queue_interval (time_t interval) 1.307 +{ 1.308 + /* 1.309 + * HACK: giftd will retry the queued download every 49 seconds, 1.310 + * so round the next retry time to coincide with that interval. 1.311 + */ 1.312 + if (interval > 49) 1.313 + interval = (interval / 49 + 1) * 49; 1.314 + 1.315 + return interval; 1.316 +} 1.317 + 1.318 +/*****************************************************************************/ 1.319 + 1.320 +/* set the next time to retry from the Retry-After: header */ 1.321 +static void set_retry_after (GtTransfer *xfer) 1.322 +{ 1.323 + int seconds; 1.324 + char *retry_after; 1.325 + GtSource *gt; 1.326 +#if 0 1.327 + char *msg; 1.328 + struct tm *tm; 1.329 +#endif 1.330 + 1.331 + if (!(retry_after = dataset_lookupstr (xfer->header, "retry-after"))) 1.332 + return; 1.333 + 1.334 + /* 1.335 + * This can be either a HTTP date or a number of seconds. We only 1.336 + * support the number of seconds right now. 1.337 + */ 1.338 + seconds = ATOI (retry_after); 1.339 + 1.340 + if (seconds <= 0) 1.341 + return; 1.342 + 1.343 + if (!(gt = gt_transfer_get_source (xfer))) 1.344 + return; 1.345 + 1.346 + /* set the retry time for the next download */ 1.347 + gt->retry_time = time (NULL) + queue_interval (seconds); 1.348 + 1.349 +#if 0 1.350 + /* get the absolute time */ 1.351 + tm = localtime (>->retry_time); 1.352 + 1.353 + /* let the user know when we are going to retry */ 1.354 + msg = stringf_dup ("Queued (retry at %d:%02d:%02d)", tm->tm_hour, 1.355 + tm->tm_min, tm->tm_sec); 1.356 + 1.357 + gt_transfer_status (xfer, SOURCE_QUEUED_REMOTE, msg); 1.358 + free (msg); 1.359 +#endif 1.360 +} 1.361 + 1.362 +/* 1.363 + * Check for both the active-queueing style "X-Queue:" and PARQ-style 1.364 + * "X-Queued:". 1.365 + * 1.366 + * We avoid having to parse the X-Queue: line in the PARQ case (which would 1.367 + * be "1.0") by allowing X-Queued to override X-Queue. 1.368 + */ 1.369 +static size_t find_queue_key (Dataset *header, const char *key) 1.370 +{ 1.371 + size_t pos = 0; 1.372 + char *val; 1.373 + char *line0, *line; 1.374 + char *active_queue_line; 1.375 + char *parq_queue_line; 1.376 + char *sep; 1.377 + 1.378 + active_queue_line = dataset_lookupstr (header, "x-queue"); 1.379 + parq_queue_line = dataset_lookupstr (header, "x-queued"); 1.380 + 1.381 + if (!active_queue_line && !parq_queue_line) 1.382 + return 0; 1.383 + 1.384 + line = active_queue_line; 1.385 + sep = ", "; 1.386 + 1.387 + if (parq_queue_line) 1.388 + { 1.389 + line = parq_queue_line; 1.390 + sep = "; "; 1.391 + } 1.392 + 1.393 + line0 = line = STRDUP (line); 1.394 + 1.395 + while ((val = string_sep_set (&line, sep))) 1.396 + { 1.397 + char *str; 1.398 + 1.399 + str = string_sep_set (&val, "= "); 1.400 + 1.401 + if (!str || !val) 1.402 + continue; 1.403 + 1.404 + if (!strcasecmp (str, key)) 1.405 + pos = ATOI (val); 1.406 + } 1.407 + 1.408 + free (line0); 1.409 + return pos; 1.410 +} 1.411 + 1.412 +/* Create a message describing our position in the remote node's 1.413 + * upload queue. */ 1.414 +static char *get_queue_status (GtTransfer *xfer, char *msg) 1.415 +{ 1.416 + size_t len = 0; 1.417 + size_t pos = 0; 1.418 + 1.419 + pos = find_queue_key (xfer->header, "position"); 1.420 + len = find_queue_key (xfer->header, "length"); 1.421 + 1.422 + msg = STRDUP (msg); 1.423 + 1.424 + if (pos != 0) 1.425 + { 1.426 + free (msg); 1.427 + 1.428 + if (len != 0) 1.429 + msg = stringf_dup ("Queued (%u/%u)", pos, len); 1.430 + else 1.431 + msg = stringf_dup ("Queued (position %u)", pos); 1.432 + } 1.433 + 1.434 + return msg; 1.435 +} 1.436 + 1.437 +/* set the next time to retry the download based on the X-Queue: field */ 1.438 +static void set_queue_status (GtTransfer *xfer) 1.439 +{ 1.440 + GtSource *gt; 1.441 + char *queue_line; 1.442 + int poll_min; 1.443 + 1.444 + if (!(gt = gt_transfer_get_source (xfer))) 1.445 + return; 1.446 + 1.447 + if (!(queue_line = dataset_lookupstr (xfer->header, "x-queue"))) 1.448 + return; 1.449 + 1.450 + if ((poll_min = find_queue_key (xfer->header, "pollmin")) <= 0) 1.451 + return; 1.452 + 1.453 + gt->retry_time = time (NULL) + queue_interval (poll_min); 1.454 +} 1.455 + 1.456 +/* 1.457 + * Try to read any content-body in the response, so that persistent 1.458 + * HTTP can work correctly, which is really important for push downloads. 1.459 + */ 1.460 +static void handle_http_error (GtTransfer *xfer, SourceStatus status, 1.461 + char *status_txt) 1.462 +{ 1.463 + TCPC *c; 1.464 + Chunk *chunk; 1.465 + int len = 0; 1.466 + char *content_len; 1.467 + char *conn_hdr; 1.468 + 1.469 + /* update the interface protocol with the error status of this xfer */ 1.470 + status_txt = get_queue_status (xfer, status_txt); 1.471 + gt_transfer_status (xfer, status, status_txt); 1.472 + 1.473 + free (status_txt); 1.474 + 1.475 + c = gt_transfer_get_tcpc (xfer); 1.476 + chunk = gt_transfer_get_chunk (xfer); 1.477 + 1.478 + /* 1.479 + * Check for a Content-Length: field, and use that for the 1.480 + * length of the response body, if any. 1.481 + */ 1.482 + content_len = dataset_lookupstr (xfer->header, "content-length"); 1.483 + conn_hdr = dataset_lookupstr (xfer->header, "connection"); 1.484 + 1.485 + /* look at the Retry-After: header and set download retry time */ 1.486 + set_retry_after (xfer); 1.487 + 1.488 + /* parse the X-Queue: and X-Queued: headers for the queue position/ 1.489 + * retry time */ 1.490 + set_queue_status (xfer); 1.491 + 1.492 + /* 1.493 + * Don't read the body if they supplied "Connection: Close". 1.494 + * Close if the HTTP version is 1.0 (TODO: this should 1.495 + * not close 1.0 connections if they supplied Connection: 1.496 + * Keep-Alive, I guess) 1.497 + */ 1.498 + if (!STRCASECMP (xfer->version, "HTTP/1.0") || 1.499 + !STRCASECMP (xfer->version, "HTTP") || 1.500 + !STRCASECMP (conn_hdr, "close")) 1.501 + { 1.502 + gt_transfer_close (xfer, TRUE); 1.503 + return; 1.504 + } 1.505 + 1.506 + if (content_len) 1.507 + len = ATOUL (content_len); 1.508 + 1.509 + /* abuse xfer->{start,stop} fields for the length of the response body */ 1.510 + xfer->start = 0; 1.511 + xfer->stop = len; 1.512 + 1.513 + /* set this flag to let gt_transfer_close() know the headers 1.514 + * have been parsed */ 1.515 + xfer->transmitted_hdrs = TRUE; 1.516 + 1.517 + /* set the length so that if the connection times out, it will be 1.518 + * force closed when we havent read the entire body */ 1.519 + xfer->remaining_len = len; 1.520 + 1.521 + /* if there is no length to read, we are done */ 1.522 + if (len == 0) 1.523 + { 1.524 + gt_transfer_close (xfer, FALSE); 1.525 + return; 1.526 + } 1.527 + 1.528 + input_remove_all (c->fd); 1.529 + input_add (c->fd, xfer, INPUT_READ, 1.530 + (InputCallback)read_response_body, TIMEOUT_DEF); 1.531 +} 1.532 + 1.533 +/*****************************************************************************/ 1.534 + 1.535 +/* 1.536 + * Process an HTTP return code (either client or server [push]) and attempt 1.537 + * to appropriately handle/expunge the transfer structure accordingly. The 1.538 + * return value indicates whether or not we may continue after the code 1.539 + * has been processed. Some error codes (404) are considered fatal here 1.540 + * and you should abort after this call. 1.541 + * 1.542 + * NOTE: 1.543 + * If this function returns FALSE, the calling argument is free'd and you 1.544 + * should not access it further. 1.545 + */ 1.546 +int gt_http_handle_code (GtTransfer *xfer, int code) 1.547 +{ 1.548 + TCPC *c; 1.549 + Chunk *chunk; 1.550 + GtSource *gt; 1.551 + 1.552 + /* successful code, do nothing */ 1.553 + if (code >= 200 && code <= 299) 1.554 + return TRUE; 1.555 + 1.556 + c = gt_transfer_get_tcpc (xfer); 1.557 + chunk = gt_transfer_get_chunk (xfer); 1.558 + 1.559 + gt = gt_transfer_get_source (xfer); 1.560 + assert (gt != NULL); 1.561 + 1.562 + /* 1.563 + * We have to be careful not to access the transfer or any 1.564 + * data related to it after calling p->source_abort, which 1.565 + * will cancel the transfer. 1.566 + */ 1.567 + switch (code) 1.568 + { 1.569 + default: 1.570 + case 404: /* not found */ 1.571 + { 1.572 + /* 1.573 + * If this was a uri-res request, retry with an index request. 1.574 + * Otherwise, remove the source. 1.575 + * 1.576 + * TODO: perhaps this should retry with url-encoded index 1.577 + * request, before removing? 1.578 + */ 1.579 + if (gt->uri_res_failed) 1.580 + { 1.581 + GT->source_abort (GT, chunk->transfer, xfer->source); 1.582 + } 1.583 + else 1.584 + { 1.585 + handle_http_error (xfer, SOURCE_QUEUED_REMOTE, 1.586 + "File not found"); 1.587 + gt->uri_res_failed = TRUE; 1.588 + } 1.589 + } 1.590 + break; 1.591 + case 401: /* unauthorized */ 1.592 + handle_http_error (xfer, SOURCE_CANCELLED, "Access denied"); 1.593 + break; 1.594 + case 503: /* remotely queued */ 1.595 + handle_http_error (xfer, SOURCE_QUEUED_REMOTE, "Queued (Remotely)"); 1.596 + break; 1.597 + case 500: /* source may not match, check later */ 1.598 + /* 1.599 + * The remote node has reported that this file has changed 1.600 + * since the last time we received results for it. This more 1.601 + * than likely indicates a hash change, in which case we should 1.602 + * not keep this associated with this transfer. Unfortunately, 1.603 + * giFT fails to provide any sort of abstraction for us 1.604 + * to remove sources ourselves, so we need this hopefully 1.605 + * temporary hack to remove the source. 1.606 + */ 1.607 + GT->source_abort (GT, chunk->transfer, xfer->source); 1.608 + break; 1.609 + } 1.610 + 1.611 + return FALSE; 1.612 +} 1.613 + 1.614 +static int parse_content_range (char *range, off_t *r_start, off_t *r_end, 1.615 + off_t *r_size) 1.616 +{ 1.617 + char *start, *end, *size; 1.618 + 1.619 + string_sep (&range, "bytes"); 1.620 + string_sep_set (&range, " ="); /* accept "bytes=" and "bytes " */ 1.621 + 1.622 + if (r_end) 1.623 + *r_end = -1; 1.624 + if (r_start) 1.625 + *r_start = -1; 1.626 + if (r_size) 1.627 + *r_size = -1; 1.628 + 1.629 + if (!range) 1.630 + return FALSE; 1.631 + 1.632 + start = string_sep (&range, "-"); 1.633 + end = string_sep (&range, "/"); 1.634 + size = range; 1.635 + 1.636 + if (r_start && start) 1.637 + *r_start = ATOUL (start); 1.638 + if (r_end && end) 1.639 + *r_end = ATOUL (end); 1.640 + if (r_size && size) 1.641 + *r_size = ATOUL (size); 1.642 + 1.643 + if (start && end && size) 1.644 + return TRUE; 1.645 + 1.646 + return FALSE; 1.647 +} 1.648 + 1.649 +static int verify_range_response (GtTransfer *xfer, Chunk *chunk) 1.650 +{ 1.651 + char *user_agent; 1.652 + char *content_range; 1.653 + char *content_len; 1.654 + off_t start, stop, size; 1.655 +#if 0 1.656 + off_t file_size; 1.657 +#endif 1.658 + off_t xfer_size; 1.659 + int error = FALSE; 1.660 + 1.661 +#if 0 1.662 + file_size = chunk->transfer->total; 1.663 +#endif 1.664 + xfer_size = xfer->stop - xfer->start; 1.665 + 1.666 + if ((content_len = dataset_lookupstr (xfer->header, "content-length"))) 1.667 + { 1.668 + size = ATOUL (content_len); 1.669 + 1.670 + if (size != xfer_size) 1.671 + { 1.672 + GIFT_ERROR (("bad content len=%lu, expected %lu", size, xfer_size)); 1.673 + error = TRUE; 1.674 + gt_transfer_status (xfer, SOURCE_CANCELLED, "Bad Content-Length"); 1.675 + } 1.676 + } 1.677 + 1.678 + if ((content_range = dataset_lookupstr (xfer->header, "content-range"))) 1.679 + { 1.680 + if (HTTP_DEBUG) 1.681 + { 1.682 + GT->dbg (GT, "Content-Range: %s, start=%lu, stop=%lu", 1.683 + content_range, chunk->start, chunk->stop); 1.684 + } 1.685 + 1.686 + if (parse_content_range (content_range, &start, &stop, &size)) 1.687 + { 1.688 + if (start != xfer->start) 1.689 + { 1.690 + GIFT_ERROR (("bad xfer start: %lu %lu", xfer->start, start)); 1.691 + error = TRUE; 1.692 + } 1.693 + if (stop != xfer->stop - 1) 1.694 + { 1.695 + GIFT_ERROR (("bad xfer end: %lu %lu", xfer->stop, stop)); 1.696 + error = TRUE; 1.697 + } 1.698 +#if 0 1.699 + if (size != file_size) 1.700 + { 1.701 + GIFT_ERROR (("bad xfer size: %lu, expected %lu", file_size, 1.702 + size)); 1.703 + error = TRUE; 1.704 + } 1.705 +#endif 1.706 + } 1.707 + else 1.708 + { 1.709 + GIFT_ERROR (("error parsing content-range hdr")); 1.710 + error = TRUE; 1.711 + } 1.712 + } 1.713 + 1.714 + if (!content_len && !content_range) 1.715 + { 1.716 + if (!(user_agent = dataset_lookupstr (xfer->header, "Server"))) 1.717 + user_agent = dataset_lookupstr (xfer->header, "User-Agent"); 1.718 + 1.719 + GIFT_ERROR (("missing Content-Range/Length, start=%lu, stop=%lu, " 1.720 + "culprit=%s", xfer->start, xfer->stop, user_agent)); 1.721 + 1.722 + error = TRUE; 1.723 + } 1.724 + 1.725 + if (error) 1.726 + { 1.727 + GT->DBGFN (GT, "removing source %s", chunk->source->url); 1.728 + GT->source_abort (GT, chunk->transfer, chunk->source); 1.729 + return FALSE; 1.730 + } 1.731 + 1.732 + return TRUE; 1.733 +} 1.734 + 1.735 +/* 1.736 + * Receive and process the HTTP response 1.737 + */ 1.738 +static void get_server_reply (int fd, input_id id, GtTransfer *xfer) 1.739 +{ 1.740 + Chunk *chunk; 1.741 + TCPC *c; 1.742 + GtSource *gt; 1.743 + FDBuf *buf; 1.744 + unsigned char *data; 1.745 + char *msg; 1.746 + size_t data_len = 0; 1.747 + int n; 1.748 + 1.749 + chunk = gt_transfer_get_chunk (xfer); 1.750 + c = gt_transfer_get_tcpc (xfer); 1.751 + 1.752 + gt = gt_transfer_get_source (xfer); 1.753 + 1.754 + buf = tcp_readbuf (c); 1.755 + 1.756 + /* attempt to read the complete server response */ 1.757 + if ((n = fdbuf_delim (buf, "\n")) < 0) 1.758 + { 1.759 + msg = "Timed out"; 1.760 + 1.761 + /* 1.762 + * If the peer abruptly closed the connection, its possible it 1.763 + * didn't understand our request if it was a uri-res request. 1.764 + * So, disable uri-res in that case. 1.765 + */ 1.766 + if (fd != -1) 1.767 + { 1.768 + gt->uri_res_failed = TRUE; 1.769 + msg = "Connection closed"; 1.770 + } 1.771 + 1.772 + gt_transfer_status (xfer, SOURCE_CANCELLED, msg); 1.773 + gt_transfer_close (xfer, TRUE); 1.774 + return; 1.775 + } 1.776 + 1.777 + if (gt_fdbuf_full (buf)) 1.778 + { 1.779 + gt_transfer_close (xfer, TRUE); 1.780 + return; 1.781 + } 1.782 + 1.783 + if (n > 0) 1.784 + return; 1.785 + 1.786 + data = fdbuf_data (buf, &data_len); 1.787 + 1.788 + if (!gt_http_header_terminated (data, data_len)) 1.789 + return; 1.790 + 1.791 + fdbuf_release (buf); 1.792 + 1.793 + /* parse the server response */ 1.794 + if (!parse_server_reply (xfer, c, data)) 1.795 + { 1.796 + gt_transfer_status (xfer, SOURCE_CANCELLED, "Malformed HTTP header"); 1.797 + gt_transfer_close (xfer, TRUE); 1.798 + return; 1.799 + } 1.800 + 1.801 + /* 1.802 + * NOTE: if we wanted to do any further processing of the server reply 1.803 + * after GET /, this is where it would be 1.804 + */ 1.805 + 1.806 + /* determine what to do with the HTTP code reply */ 1.807 + if (!gt_http_handle_code (xfer, xfer->code)) 1.808 + return; 1.809 + 1.810 + /* make sure the server understood our "Range:" request */ 1.811 + if (!verify_range_response (xfer, chunk)) 1.812 + return; 1.813 + 1.814 + /* 1.815 + * Received HTTP headers, ...and now we are waiting for the file. This 1.816 + * should be a very short wait :) 1.817 + */ 1.818 + gt_transfer_status (xfer, SOURCE_WAITING, "Received HTTP headers"); 1.819 + xfer->transmitted_hdrs = TRUE; 1.820 + 1.821 + /* special case if the request size is 0 */ 1.822 + if (xfer->remaining_len == 0) 1.823 + { 1.824 + gt_transfer_close (xfer, FALSE); 1.825 + return; 1.826 + } 1.827 + 1.828 + /* disable the header read timeout */ 1.829 + timer_remove_zero (&xfer->header_timer); 1.830 + 1.831 + /* wait for the file to be sent by the server */ 1.832 + input_remove (id); 1.833 + input_add (fd, xfer, INPUT_READ, 1.834 + (InputCallback)gt_get_read_file, 0); 1.835 +} 1.836 + 1.837 +/* 1.838 + * Receive the requested file from the server. 1.839 + */ 1.840 +void gt_get_read_file (int fd, input_id id, GtTransfer *xfer) 1.841 +{ 1.842 + TCPC *c; 1.843 + Chunk *chunk; 1.844 + char buf[RW_BUFFER]; 1.845 + size_t size; 1.846 + int recv_len; 1.847 + 1.848 + c = gt_transfer_get_tcpc (xfer); 1.849 + chunk = gt_transfer_get_chunk (xfer); 1.850 + 1.851 + /* set the max initial size of the request */ 1.852 + size = sizeof (buf); 1.853 + 1.854 + /* 1.855 + * Cap the size of the received length at the length 1.856 + * of the request. 1.857 + * 1.858 + * This way we can't receive part of the next request 1.859 + * header with persistent HTTP. 1.860 + */ 1.861 + if (size > xfer->remaining_len) 1.862 + size = xfer->remaining_len; 1.863 + 1.864 + /* 1.865 + * Ask giFT for the max size we should read. If this returns 0, the 1.866 + * download was suspended. 1.867 + */ 1.868 + if ((size = download_throttle (chunk, size)) == 0) 1.869 + return; 1.870 + 1.871 + if ((recv_len = tcp_recv (c, buf, size)) <= 0) 1.872 + { 1.873 + GT->DBGFN (GT, "tcp_recv error (%d) from %s:%hu: %s", recv_len, 1.874 + net_ip_str (c->host), c->port, GIFT_NETERROR()); 1.875 + 1.876 + gt_transfer_status (xfer, SOURCE_CANCELLED, "Cancelled remotely"); 1.877 + gt_transfer_close (xfer, TRUE); 1.878 + return; 1.879 + } 1.880 + 1.881 + /* 1.882 + * We are receiving a file here, so this will always be calling 1.883 + * gt_download. 1.884 + */ 1.885 + gt_transfer_write (xfer, chunk, buf, (size_t)recv_len); 1.886 +} 1.887 + 1.888 +/*****************************************************************************/ 1.889 + 1.890 +static void client_reset_timeout (int fd, input_id id, TCPC *c) 1.891 +{ 1.892 + /* normally we would call recv () here but there's no reason why the server 1.893 + * should be sending stuff down to use...so, disconnect */ 1.894 + if (HTTP_DEBUG) 1.895 + GT->DBGSOCK (GT, c, "closing client HTTP connection"); 1.896 + 1.897 + gt_http_connection_close (GT_TRANSFER_DOWNLOAD, c, TRUE); 1.898 +} 1.899 + 1.900 +void gt_http_client_reset (TCPC *c) 1.901 +{ 1.902 + /* This can happen when the GtTransfer and TCPC have been decoupled, 1.903 + * such as when we are waiting for a GIV response from a node. */ 1.904 + if (!c) 1.905 + return; 1.906 + 1.907 + tcp_flush (c, TRUE); 1.908 + 1.909 + input_remove_all (c->fd); 1.910 + input_add (c->fd, c, INPUT_READ, 1.911 + (InputCallback)client_reset_timeout, 2 * MINUTES); 1.912 +}