Mercurial > hg > index.fcgi > gift-gnutella > gift-gnutella-0.0.11-1pba
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 (>->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 } |