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