comparison src/gt_xfer_obj.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:d1ff8b32cdb4
1 /*
2 * $Id: gt_xfer_obj.c,v 1.48 2004/12/19 12:38:42 mkern Exp $
3 *
4 * acts as a gateway between giFT and the HTTP implementation
5 *
6 * Copyright (C) 2001-2003 giFT project (gift.sourceforge.net)
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2, or (at your option) any
11 * later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 */
18
19 #include "gt_gnutella.h"
20
21 #include "gt_xfer_obj.h"
22 #include "gt_xfer.h"
23 #include "gt_http_server.h"
24 #include "gt_http_client.h"
25 #include "gt_share.h"
26
27 #include "encoding/url.h"
28
29 #include "transfer/source.h"
30
31 /*****************************************************************************/
32
33 static List *upload_connections = NULL;
34 static List *download_connections = NULL;
35
36 /*****************************************************************************/
37 /* HTTP COMMUNICATION */
38
39 static BOOL header_read_timeout (GtTransfer *xfer)
40 {
41 gt_transfer_status (xfer, SOURCE_TIMEOUT, "Timed out");
42 gt_transfer_close (xfer, TRUE);
43 return FALSE;
44 }
45
46 GtTransfer *gt_transfer_new (GtTransferType type, Source *source,
47 in_addr_t ip, in_port_t port,
48 off_t start, off_t stop)
49 {
50 GtTransfer *xfer;
51 GtTransferCB cb;
52
53 if (!(xfer = malloc (sizeof (GtTransfer))))
54 return NULL;
55
56 memset (xfer, 0, sizeof (GtTransfer));
57
58 if (type == GT_TRANSFER_UPLOAD)
59 cb = gt_upload;
60 else if (type == GT_TRANSFER_DOWNLOAD)
61 cb = gt_download;
62 else
63 abort ();
64
65 xfer->type = type;
66 xfer->callback = cb;
67 xfer->source = source;
68
69 /* parsed information about the source */
70 xfer->ip = ip;
71 xfer->port = port;
72
73 xfer->start = start;
74 xfer->stop = stop;
75
76 xfer->shared = TRUE;
77
78 xfer->detach_timer = TIMER_NONE;
79 xfer->detach_msgtxt = NULL;
80
81 /* set the size of this http request */
82 xfer->remaining_len = xfer->stop - xfer->start;
83
84 /* give this GtTransfer a maximum amount of time before cancelling */
85 xfer->header_timer = timer_add (1 * MINUTES,
86 (TimerCallback)header_read_timeout,
87 xfer);
88
89 return xfer;
90 }
91
92 static void gt_transfer_free (GtTransfer *xfer)
93 {
94 if (!xfer)
95 return;
96
97 free (xfer->command);
98 free (xfer->request);
99 free (xfer->request_path);
100 free (xfer->content_urns);
101 free (xfer->detach_msgtxt);
102
103 if (xfer->header)
104 dataset_clear (xfer->header);
105
106 #if 0
107 if (xfer->share_authd && xfer->shared_authd_free)
108 share_free (xfer->share_authd);
109 #endif
110
111 timer_remove (xfer->detach_timer);
112 timer_remove (xfer->header_timer);
113
114 /* uploads use these */
115 free (xfer->open_path);
116 free (xfer->hash);
117 free (xfer->version);
118
119 if (xfer->f)
120 fclose (xfer->f);
121
122 /* whee */
123 free (xfer);
124 }
125
126 /*****************************************************************************/
127
128 void gt_transfer_set_tcpc (GtTransfer *xfer, TCPC *c)
129 {
130 assert (c->udata == NULL);
131 assert (xfer->c == NULL);
132
133 c->udata = xfer;
134 xfer->c = c;
135 }
136
137 void gt_transfer_set_chunk (GtTransfer *xfer, Chunk *chunk)
138 {
139 assert (chunk->udata == NULL);
140 assert (xfer->chunk == NULL);
141
142 xfer->chunk = chunk;
143 chunk->udata = xfer;
144 }
145
146 Chunk *gt_transfer_get_chunk (GtTransfer *xfer)
147 {
148 assert (xfer->chunk != NULL);
149 assert (xfer->chunk->udata == xfer);
150
151 return xfer->chunk;
152 }
153
154 TCPC *gt_transfer_get_tcpc (GtTransfer *xfer)
155 {
156 assert (xfer->c != NULL);
157 assert (xfer->c->udata == xfer);
158
159 return xfer->c;
160 }
161
162 /*****************************************************************************/
163
164 GtSource *gt_transfer_get_source (GtTransfer *xfer)
165 {
166 Source *source = xfer->source;
167
168 /* could be null for uploads */
169 if (!source)
170 return NULL;
171
172 assert (source->udata != NULL);
173 return source->udata;
174 }
175
176 /*****************************************************************************/
177
178 /*
179 * giftd may change the Chunk size. This routine will reset the range of the
180 * transfer we'll ask the remote end for. It should be called before we've
181 * transmitted the HTTP headers that include the Range: request.
182 */
183 void gt_transfer_set_length (GtTransfer *xfer, Chunk *chunk)
184 {
185 TCPC *c;
186 off_t old_start;
187 off_t old_stop;
188 off_t old_len;
189
190 c = gt_transfer_get_tcpc (xfer);
191
192 /* don't call me if you've already transmitted headers */
193 assert (!xfer->transmitted_hdrs);
194
195 old_start = xfer->start;
196 old_stop = xfer->stop;
197 old_len = xfer->remaining_len;
198
199 xfer->start = chunk->start + chunk->transmit;
200 xfer->stop = chunk->stop;
201
202 xfer->remaining_len = xfer->stop - xfer->start;
203
204 /* i believe this is true even for push downloads... */
205 assert (xfer->start == old_start);
206
207 if (xfer->stop != old_stop)
208 {
209 assert (xfer->remaining_len != old_len);
210
211 GT->DBGSOCK (GT, c, "(%s) old chunk range: [%lu,%lu) "
212 "new range: [%lu,%lu) old len: %lu new len: %lu",
213 xfer->request, (long)old_start,(long)old_stop,
214 (long)xfer->start, (long)xfer->stop,
215 (long)old_len, (long)xfer->remaining_len);
216 }
217 }
218
219 /*****************************************************************************/
220
221 /*
222 * Cancels a possibly active HTTP transfer
223 *
224 * NOTE:
225 * This function may be called recursively if you don't do what giFT expects
226 * of you. This is a feature, not a bug, trust me.
227 */
228 static void gt_transfer_cancel (Chunk *chunk, TransferType type)
229 {
230 GtTransfer *xfer;
231 BOOL force_close = FALSE;
232
233 if (!chunk)
234 return;
235
236 xfer = chunk->udata;
237
238 /* each time this function is called we _MUST_ uncouple the transfer
239 * and the chunk or the code to follow will eventually call this func
240 * again!! */
241 if (!xfer)
242 return;
243
244 /* do not emit a callback signal */
245 xfer->callback = NULL;
246 gt_transfer_close (xfer, force_close);
247 }
248
249 /*****************************************************************************/
250
251 static void close_http_connection (TCPC *c, BOOL force_close,
252 GtTransferType type, GtSource *gt_src)
253 {
254 if (!c)
255 return;
256
257 /*
258 * If this is an incoming indirect download that we sent a push out
259 * for, then don't store the connection in the HTTP connection cache,
260 * store it in the separate push connection cache that uses the client
261 * id as well as the IP address to identify the node.
262 */
263 if (!force_close && type == GT_TRANSFER_DOWNLOAD && !c->outgoing)
264 {
265 if (gt_src != NULL)
266 {
267 if (HTTP_DEBUG)
268 GT->DBGSOCK (GT, c, "Keeping push connection");
269
270 /* nullify the previous data on this connection */
271 c->udata = NULL;
272
273 gt_push_source_add_conn (gt_src->guid, gt_src->user_ip, c);
274 return;
275 }
276
277 /*
278 * This could happen if the chunk has no source url to parse
279 * at the moment. Argh, GtTransfer should always have a GtSource
280 * if xfer->type == GT_TRANSFER_DOWNLOAD.
281 */
282 if (HTTP_DEBUG)
283 GT->DBGSOCK (GT, c, "Closing pushed connection! ARGH!");
284
285 force_close = TRUE;
286 }
287
288 gt_http_connection_close (type, c, force_close);
289 }
290
291 /*
292 * This function is very critical to OpenFT's transfer system. It is called
293 * anytime either the client or server HTTP implementations need to "cleanup"
294 * all data associated. This includes disconnecting the socket, unlinking
295 * itself from the chunk system and registering this status with giFT, just
296 * in case this is premature. If anything is leaking or fucking up, blame
297 * this :)
298 */
299 void gt_transfer_close (GtTransfer *xfer, BOOL force_close)
300 {
301 TCPC *c;
302 Chunk *chunk;
303 GtSource *gt_src = NULL;
304 char *conn_hdr;
305
306 if (!xfer)
307 return;
308
309 c = xfer->c;
310 chunk = xfer->chunk;
311
312 assert (xfer != NULL);
313
314 /* remove the xfer from the indirect src list */
315 gt_push_source_remove_xfer (xfer);
316
317 /* get this source if this was a download */
318 if (xfer->type == GT_TRANSFER_DOWNLOAD && chunk && chunk->source)
319 gt_src = gt_source_unserialize (chunk->source->url);
320
321 /* if we have associated a chunk with this transfer we need to make sure
322 * we remove cleanly detach */
323 if (chunk)
324 {
325 chunk->udata = NULL;
326
327 /*
328 * notify the transfer callback that we have terminated this
329 * connection. let giFT handle the rest
330 *
331 * NOTE:
332 * see gt_transfer_cancel for some warnings about this code
333 */
334 if (xfer->callback)
335 (*xfer->callback) (chunk, NULL, 0);
336
337 /* WARNING: chunk is free'd in the depths of xfer->callback! */
338 }
339
340 /* HTTP/1.0 does not support persist connections or something...i dunno */
341 if (!STRCASECMP (xfer->version, "HTTP/1.0"))
342 force_close = TRUE;
343
344 /* older gnutella clients send a plain "HTTP" version, that is
345 * not persistent */
346 if (!STRCASECMP (xfer->version, "HTTP"))
347 force_close = TRUE;
348
349 /*
350 * We must force a socket close if there is still data waiting to
351 * be read on this transfer.
352 */
353 if (!xfer->transmitted_hdrs || xfer->remaining_len != 0)
354 force_close = TRUE;
355
356 /* close the connection if "Connection: close" was supplied */
357 conn_hdr = dataset_lookupstr (xfer->header, "connection");
358 if (!STRCASECMP (conn_hdr, "close"))
359 force_close = TRUE;
360
361 close_http_connection (c, force_close, xfer->type, gt_src);
362
363 gt_source_free (gt_src);
364
365 gt_transfer_free (xfer);
366 }
367
368 void gt_transfer_status (GtTransfer *xfer, SourceStatus status, char *text)
369 {
370 Chunk *chunk;
371 GtSource *gt_src;
372 char *next_status;
373
374 if (!xfer || !text)
375 return;
376
377 /* Added this on 2004-12-19 to track observed assertion failure in
378 * gt_transfer_get_chunk. -- mkern
379 */
380 if (!xfer->chunk || xfer->chunk->udata != xfer)
381 {
382 GT->DBGFN (GT, "This is where we say good bye. status = %d, "
383 "text = %s, xfer->request_path = %s, xfer->ip = %s",
384 status, text, xfer->request_path, net_ip_str (xfer->ip));
385 }
386
387 chunk = gt_transfer_get_chunk (xfer);
388
389 GT->source_status (GT, chunk->source, status, text);
390
391 /*
392 * HACK: Store the status message on the GtSource,
393 * so we can reuse it sometimes.
394 */
395 if (!xfer->source || !(gt_src = xfer->source->udata))
396 return;
397
398 /* allocate first so it's ok to call this function with an old value of
399 * gt_src->status_txt */
400 next_status = STRDUP (text);
401 free (gt_src->status_txt);
402 gt_src->status_txt = next_status;
403 }
404
405 /*****************************************************************************/
406 /* PERSISTENT HTTP HANDLING */
407
408 struct conn_info
409 {
410 in_addr_t ip;
411 in_port_t port;
412 size_t count;
413 };
414
415 static int conn_cmp (TCPC *c, struct conn_info *info)
416 {
417 if (info->port != c->port)
418 return -1;
419
420 if (net_peer (c->fd) != info->ip)
421 return 1;
422
423 return 0;
424 }
425
426 TCPC *gt_http_connection_lookup (GtTransferType type, in_addr_t ip,
427 in_port_t port)
428 {
429 List *link;
430 List **connlist_ptr;
431 TCPC *c = NULL;
432 struct conn_info info;
433
434 info.ip = ip;
435 info.port = port;
436
437 if (type == GT_TRANSFER_DOWNLOAD)
438 connlist_ptr = &download_connections;
439 else
440 connlist_ptr = &upload_connections;
441
442 link = list_find_custom (*connlist_ptr, &info, (CompareFunc)conn_cmp);
443
444 if (link)
445 {
446 c = link->data;
447
448 GT->DBGFN (GT, "using previous connection to %s:%hu",
449 net_ip_str (ip), port);
450
451 /* remove from the open list */
452 *connlist_ptr = list_remove_link (*connlist_ptr, link);
453 input_remove_all (c->fd);
454 }
455
456 return c;
457 }
458
459 /*
460 * Handles outgoing HTTP connections. This function is capable of
461 * retrieving an already connected socket that was left over from a previous
462 * transfer.
463 */
464 TCPC *gt_http_connection_open (GtTransferType type, in_addr_t ip,
465 in_port_t port)
466 {
467 TCPC *c;
468
469 if (!(c = gt_http_connection_lookup (type, ip, port)))
470 c = tcp_open (ip, port, FALSE);
471
472 return c;
473 }
474
475 static BOOL count_open (TCPC *c, struct conn_info *info)
476 {
477 if (info->ip == net_peer (c->fd))
478 info->count++;
479
480 return FALSE;
481 }
482
483 size_t gt_http_connection_length (GtTransferType type, in_addr_t ip)
484 {
485 struct conn_info info;
486 List *list;
487
488 info.ip = ip;
489 info.port = 0;
490 info.count = 0;
491
492 assert (type == GT_TRANSFER_DOWNLOAD || type == GT_TRANSFER_UPLOAD);
493 list = (type == GT_TRANSFER_DOWNLOAD ? download_connections :
494 upload_connections);
495
496 list_foreach (list, (ListForeachFunc)count_open, &info);
497
498 return info.count;
499 }
500
501 void gt_http_connection_close (GtTransferType type, TCPC *c, BOOL force_close)
502 {
503 List **connlist_ptr;
504
505 if (!c)
506 return;
507
508 switch (type)
509 {
510 case GT_TRANSFER_DOWNLOAD:
511 {
512 gt_http_client_reset (c);
513 connlist_ptr = &download_connections;
514 }
515 break;
516
517 case GT_TRANSFER_UPLOAD:
518 {
519 gt_http_server_reset (c);
520 connlist_ptr = &upload_connections;
521 }
522 break;
523
524 default:
525 abort ();
526 }
527
528 if (force_close)
529 {
530 *connlist_ptr = list_remove (*connlist_ptr, c);
531
532 if (HTTP_DEBUG)
533 GT->DBGSOCK (GT, c, "force closing");
534
535 tcp_close (c);
536 return;
537 }
538
539 /* remove the data associated with this connection */
540 c->udata = NULL;
541
542 /*
543 * This condition will happen because the server doesn't remove the
544 * connection from the persistent list until the connection fails.
545 */
546 if (list_find (*connlist_ptr, c))
547 {
548 assert (type == GT_TRANSFER_UPLOAD);
549 return;
550 }
551
552 /* track it */
553 *connlist_ptr = list_prepend (*connlist_ptr, c);
554 }
555
556 /*****************************************************************************/
557
558 static char *localize_request (GtTransfer *xfer, BOOL *authorized)
559 {
560 char *open_path;
561 char *s_path;
562 int auth = FALSE;
563 int need_free = FALSE;
564
565 if (!xfer || !xfer->request)
566 return NULL;
567
568 /* dont call secure_path if they dont even care if it's a secure
569 * lookup */
570 s_path =
571 (authorized ? file_secure_path (xfer->request) : xfer->request);
572
573 if (authorized)
574 need_free = TRUE;
575
576 open_path = gt_localize_request (xfer, s_path, &auth);
577
578 if (need_free || !open_path)
579 free (s_path);
580
581 if (authorized)
582 *authorized = auth;
583
584 /* we need a unix style path for authorization */
585 return open_path;
586 }
587
588 /*
589 * request is expected in the form:
590 * /shared/Fuck%20Me%20Hard.mpg
591 */
592 BOOL gt_transfer_set_request (GtTransfer *xfer, char *request)
593 {
594 #if 0
595 FileShare *file;
596 char *shared_path;
597 #endif
598
599 free (xfer->request);
600 xfer->request = NULL;
601
602 /* lets keep this sane shall we */
603 if (!request || *request != '/')
604 return FALSE;
605
606 xfer->request = STRDUP (request);
607 xfer->request_path = gt_url_decode (request); /* storing here for opt */
608
609 return TRUE;
610 }
611
612 /* attempts to open the requested file locally.
613 * NOTE: this handles verification */
614 FILE *gt_transfer_open_request (GtTransfer *xfer, int *code)
615 {
616 FILE *f;
617 char *shared_path;
618 char *full_path = NULL;
619 char *host_path;
620 int auth = FALSE;
621 int code_l = 200;
622
623 if (code)
624 *code = 404; /* Not Found */
625
626 if (!xfer || !xfer->request)
627 return NULL;
628
629 if (!(shared_path = localize_request (xfer, &auth)))
630 {
631 /*
632 * If we havent finished syncing shares, that may be why the
633 * request failed. If so, return 503 here.
634 */
635 if (!gt_share_local_sync_is_done () && code != NULL)
636 *code = 503;
637
638 return NULL;
639 }
640
641 /* needs more work for virtual requests */
642 #if 0
643 /* check to see if we matched a special OpenFT condition. If we did, the
644 * localized path is in the wrong order, so convert it (just to be
645 * converted back again...it's easier to maintain this way :) */
646 if (auth)
647 {
648 xfer->shared = FALSE;
649 full_path = file_unix_path (shared_path);
650 }
651 else
652 #endif
653 {
654 Share *share;
655 int reason = UPLOAD_AUTH_NOTSHARED;
656 upload_auth_t cond;
657
658 /*
659 * NOTE: the user string is not consistent with the one
660 * echoed through search results, which prefixes the IP
661 * with the GUID, if this node is firewalled.
662 */
663 if ((share = GT->share_lookup (GT, SHARE_LOOKUP_HPATH, shared_path)))
664 reason = GT->upload_auth (GT, net_ip_str (xfer->ip), share, &cond);
665
666 xfer->share_authd = share;
667
668 switch (reason)
669 {
670 case UPLOAD_AUTH_STALE:
671 code_l = 500; /* Internal Server Error */
672 break;
673 case UPLOAD_AUTH_NOTSHARED:
674 code_l = 404; /* Not Found */
675 break;
676 case UPLOAD_AUTH_ALLOW:
677 code_l = 200; /* OK */
678 xfer->open_path_size = share->size;
679 xfer->content_type = share->mime;
680 full_path = STRDUP (share->path);
681 break;
682 case UPLOAD_AUTH_MAX:
683 case UPLOAD_AUTH_MAX_PERUSER:
684 case UPLOAD_AUTH_HIDDEN:
685 default:
686 code_l = 503; /* Service Unavailable */
687 xfer->queue_pos = cond.queue_pos;
688 xfer->queue_ttl = cond.queue_ttl;
689 break;
690 }
691 }
692
693 if (code)
694 *code = code_l;
695
696 /* error of some kind, get out of here */
697 if (code_l != 200)
698 return NULL;
699
700 /* figure out the actual filename that we should be opening */
701 host_path = file_host_path (full_path);
702 free (full_path);
703
704 /* needs more work for virtual requests */
705 #if 0
706 /* complete the rest of the data required */
707 if (auth)
708 {
709 struct stat st;
710
711 if (!file_stat (host_path, &st))
712 {
713 free (host_path);
714 return NULL;
715 }
716
717 xfer->open_path_size = st.st_size;
718 xfer->content_type = mime_type (host_path);
719 }
720 #endif
721
722 if (!(f = fopen (host_path, "rb")))
723 {
724 *code = 500;
725 return NULL;
726 }
727
728 /* NOTE: gt_transfer_close will be responsible for freeing this now */
729 xfer->open_path = host_path;
730
731 if (code)
732 *code = 200; /* OK */
733
734 return f;
735 }
736
737 /*****************************************************************************/
738
739 /*
740 * The callbacks here are from within the HTTP system, centralized for
741 * maintainability.
742 */
743
744 /* report back the progress of this download chunk */
745 void gt_download (Chunk *chunk, unsigned char *segment, size_t len)
746 {
747 GT->chunk_write (GT, chunk->transfer, chunk, chunk->source,
748 segment, len);
749 }
750
751 /* report back the progress of this upload chunk */
752 void gt_upload (Chunk *chunk, unsigned char *segment, size_t len)
753 {
754 GT->chunk_write (GT, chunk->transfer, chunk, chunk->source,
755 segment, len);
756 }
757
758 void gt_transfer_write (GtTransfer *xfer, Chunk *chunk,
759 unsigned char *segment, size_t len)
760 {
761 /*
762 * Cap the data at the remaining size of the xfer. Note that this is
763 * the size of the HTTP request issued, _NOT_ the chunk size, which may
764 * have been altered by giFT in splitting up chunks. I think giFT
765 * handles that case properly, but we also have to guard against
766 * remaining_len becoming less than 0. Note that p->chunk_write
767 * will cancel the transfer if remaining_len goes to 0.
768 *
769 * TODO: remaining_len is off_t, make sure this is handled right wrt
770 * negative file sizes (do big files become negative sizes?)
771 */
772 if (len > xfer->remaining_len)
773 len = xfer->remaining_len;
774
775 xfer->remaining_len -= len;
776 (*xfer->callback) (chunk, segment, len);
777 }
778
779 /*****************************************************************************/
780
781 static BOOL throttle_suspend (Chunk *chunk, int s_opt, float factor)
782 {
783 GtTransfer *xfer;
784
785 if (!chunk)
786 return FALSE;
787
788 xfer = chunk->udata;
789
790 if (!xfer || !xfer->c)
791 {
792 GT->DBGFN (GT, "no connection found to suspend");
793 return FALSE;
794 }
795
796 input_suspend_all (xfer->c->fd);
797
798 if (factor)
799 net_sock_adj_buf (xfer->c->fd, s_opt, factor);
800
801 return TRUE;
802 }
803
804 static BOOL throttle_resume (Chunk *chunk, int s_opt, float factor)
805 {
806 GtTransfer *xfer = NULL;
807
808 if (!chunk)
809 return FALSE;
810
811 xfer = chunk->udata;
812
813 if (!xfer || !xfer->c)
814 {
815 GT->DBGFN (GT, "no connection found to resume");
816 return FALSE;
817 }
818
819 input_resume_all (xfer->c->fd);
820
821 #if 0
822 if (factor)
823 net_sock_adj_buf (xfer->c->fd, s_opt, factor);
824 #endif
825
826 return TRUE;
827 }
828
829 /*****************************************************************************/
830
831 void gt_download_cancel (Chunk *chunk, void *data)
832 {
833 gt_transfer_cancel (chunk, TRANSFER_DOWNLOAD);
834 }
835
836 /* cancel the transfer associate with the chunk giFT gave us */
837 void gt_upload_cancel (Chunk *chunk, void *data)
838 {
839 gt_transfer_cancel (chunk, TRANSFER_UPLOAD);
840 }
841
842 static int throttle_sopt (Transfer *transfer)
843 {
844 int sopt = 0;
845
846 switch (transfer_direction (transfer))
847 {
848 case TRANSFER_DOWNLOAD:
849 sopt = SO_RCVBUF;
850 break;
851 case TRANSFER_UPLOAD:
852 sopt = SO_SNDBUF;
853 break;
854 }
855
856 return sopt;
857 }
858
859 BOOL gt_chunk_suspend (Chunk *chunk, Transfer *transfer, void *data)
860 {
861 return throttle_suspend (chunk, throttle_sopt (transfer), 0.0);
862 }
863
864 BOOL gt_chunk_resume (Chunk *chunk, Transfer *transfer, void *data)
865 {
866 return throttle_resume (chunk, throttle_sopt (transfer), 0.0);
867 }