Mercurial > hg > index.fcgi > gift-gnutella > gift-gnutella-0.0.11-1pba
view src/gt_http_server.c @ 0:d39e1d0d75b6
initial add
author | paulo@hit-nxdomain.opendns.com |
---|---|
date | Sat, 20 Feb 2010 21:18:28 -0800 |
parents | |
children |
line source
1 /*
2 * $Id: gt_http_server.c,v 1.73 2005/01/04 15:03: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 */
17 #include "gt_gnutella.h"
19 #include "gt_xfer_obj.h"
20 #include "gt_xfer.h"
22 #include "gt_http_server.h"
23 #include "gt_http_client.h"
25 #include "gt_accept.h"
26 #include "gt_ban.h"
27 #include "gt_version.h"
29 /*****************************************************************************/
31 /* convenient shorthand */
32 #define CONTENT_URN_FIELD "X-Gnutella-Content-URN"
34 #define INCOMING_TIMEOUT (1 * MINUTES)
36 /*****************************************************************************/
38 struct http_incoming
39 {
40 TCPC *c;
41 timer_id timer;
42 };
44 /*****************************************************************************/
46 static void server_handle_get (GtTransfer *xfer);
47 static void get_client_request (int fd, input_id id,
48 struct http_incoming *http);
49 static void send_http_response (int fd, input_id id, GtTransfer *xfer);
51 /*****************************************************************************/
52 /* SERVER HELPERS */
54 static char *lookup_http_code (int code, char **desc)
55 {
56 char *err;
57 char *txt;
59 switch (code)
60 {
61 case 200: err = "OK";
62 txt = "Success";
63 break;
64 case 206: err = "Partial Content";
65 txt = "Resume successful";
66 break;
67 case 403: err = "Forbidden";
68 txt = "You do not have access to this file";
69 break;
70 case 404: err = "Not Found";
71 txt = "File is not available";
72 break;
73 case 500: err = "Internal Server Error";
74 txt = "Stale file entry, retry later";
75 break;
76 case 501: err = "Not Implemented";
77 txt = "???";
78 break;
79 case 503: err = "Service Unavailable";
80 txt = "Upload queue is currently full, please try again later";
81 break;
82 default: err = NULL;
83 txt = NULL;
84 break;
85 }
87 if (desc)
88 *desc = txt;
90 return err;
91 }
93 static String *alloc_header (int code)
94 {
95 char *code_text;
96 String *s;
98 /* so that we can communicate both the numerical code and the human
99 * readable string */
100 if (!(code_text = lookup_http_code (code, NULL)))
101 return FALSE;
103 if (!(s = string_new (NULL, 0, 0, TRUE)))
104 return FALSE;
106 string_appendf (s, "HTTP/1.1 %i %s\r\n", code, code_text);
108 return s;
109 }
111 static void construct_header_va (String *s, int code, va_list args)
112 {
113 char *key;
114 char *value;
116 /* Add "Server: " header */
117 string_appendf (s, "Server: %s\r\n", gt_version ());
119 for (;;)
120 {
121 if (!(key = va_arg (args, char *)))
122 break;
124 if (!(value = va_arg (args, char *)))
125 continue;
127 string_appendf (s, "%s: %s\r\n", key, value);
128 }
130 /* append final message terminator */
131 string_append (s, "\r\n");
132 }
134 static String *construct_header (int code, ...)
135 {
136 String *s;
137 va_list args;
139 if (!(s = alloc_header (code)))
140 return NULL;
142 va_start (args, code);
143 construct_header_va (s, code, args);
144 va_end (args);
146 return s;
147 }
149 /*
150 * Construct and send a server reply.
151 */
152 static BOOL gt_http_server_send (TCPC *c, int code, ...)
153 {
154 String *s;
155 int ret;
156 size_t len;
157 va_list args;
159 if (!(s = alloc_header (code)))
160 return FALSE;
162 va_start (args, code);
164 construct_header_va (s, code, args);
166 va_end (args);
168 if (HTTP_DEBUG)
169 GT->DBGSOCK (GT, c, "sending reply to client =\n%s", s->str);
171 len = s->len;
172 ret = tcp_send (c, s->str, s->len);
174 string_free (s);
176 return (ret == len);
177 }
179 static char *get_error_page (GtTransfer *xfer, int code)
180 {
181 char *page;
182 char *err;
183 char *errtxt = NULL;
185 if (!(err = lookup_http_code (code, &errtxt)))
186 return 0;
188 page = stringf ("<h1>%i %s</h1><br>%s.", code, err, errtxt);
190 return page;
191 }
193 static BOOL supports_queue (GtTransfer *xfer)
194 {
195 char *features;
197 if (dataset_lookupstr (xfer->header, "x-queue"))
198 return TRUE;
200 if ((features = dataset_lookupstr (xfer->header, "x-features")))
201 {
202 /* XXX: case-sensitive */
203 if (strstr (features, "queue"))
204 return TRUE;
205 }
207 return FALSE;
208 }
210 static char *get_queue_line (GtTransfer *xfer)
211 {
212 String *s;
214 /* do nothing if not queued */
215 if (xfer->queue_pos == 0)
216 return NULL;
218 if (!(s = string_new (NULL, 0, 0, TRUE)))
219 return NULL;
221 string_appendf (s, "position=%d,length=%d,pollMin=%d,pollMax=%d",
222 xfer->queue_pos, xfer->queue_ttl, 45, 120);
224 return string_free_keep (s);
225 }
227 static String *get_error_header (GtTransfer *xfer, int code,
228 const char *error_page)
229 {
230 size_t len;
231 char content_len[256];
232 char *queue_line = NULL;
233 char *content_type = "text/html";
234 String *s;
236 len = strlen (error_page);
237 snprintf (content_len, sizeof (content_len), "%u", len);
239 if (code == 503 && supports_queue (xfer))
240 queue_line = get_queue_line (xfer);
242 /* don't send a content-type header if there is no entity body */
243 if (len == 0)
244 content_type = NULL;
246 s = construct_header (code,
247 "Content-Type", content_type,
248 "Content-Length", content_len,
249 CONTENT_URN_FIELD, xfer->content_urns,
250 "X-Queue", queue_line,
251 NULL);
253 free (queue_line);
254 return s;
255 }
257 static void send_error_reply (int fd, input_id id, GtTransfer *xfer)
258 {
259 String *s;
260 const char *error_page;
261 int ret;
262 TCPC *c;
264 c = gt_transfer_get_tcpc (xfer);
266 if (!(error_page = get_error_page (xfer, xfer->code)))
267 {
268 gt_transfer_close (xfer, TRUE);
269 return;
270 }
272 /*
273 * If the remote end supports queueing or supplied
274 * "X-Gnutella-Content-URN",, it's a Gnutella client and we don't want to
275 * keep sending the error page, because over many requests from this
276 * client the bandwidth could add up.
277 */
278 if (supports_queue (xfer) ||
279 dataset_lookupstr (xfer->header, "x-gnutella-content-urn"))
280 {
281 error_page = ""; /* empty */
282 }
284 if (!(s = get_error_header (xfer, xfer->code, error_page)))
285 {
286 gt_transfer_close (xfer, TRUE);
287 return;
288 }
290 string_append (s, error_page);
292 if (HTTP_DEBUG)
293 GT->DBGSOCK (GT, c, "sending reply to client =\n%s", s->str);
295 /* send the whole page at once */
296 ret = tcp_send (c, s->str, s->len);
298 /* if the whole thing was sent keep the connection open */
299 if (ret == s->len)
300 {
301 xfer->transmitted_hdrs = TRUE;
302 xfer->remaining_len = 0;
303 }
305 string_free (s);
307 gt_transfer_close (xfer, FALSE);
308 }
310 static void gt_http_server_send_error_and_close (GtTransfer *xfer, int code)
311 {
312 TCPC *c;
314 c = gt_transfer_get_tcpc (xfer);
316 xfer->code = code;
318 input_remove_all (c->fd);
319 input_add (c->fd, xfer, INPUT_WRITE,
320 (InputCallback)send_error_reply, TIMEOUT_DEF);
321 }
323 /*****************************************************************************/
325 /* parse the Range: bytes=0-10000 format */
326 static void parse_client_request_range (Dataset *dataset,
327 off_t *r_start, off_t *r_stop)
328 {
329 char *range;
330 off_t start;
331 off_t stop;
333 if (!r_start && !r_stop)
334 return;
336 if (r_start)
337 *r_start = 0;
338 if (r_stop)
339 *r_stop = 0;
341 /* leave stop as 0 if we can't figure anything out yet. This is expected
342 * to be handled separately by GET and PUSH */
343 if (!(range = dataset_lookupstr (dataset, "range")))
344 return;
346 /* WARNING: this butchers the data in the dataset! */
347 string_sep (&range, "bytes");
348 string_sep_set (&range, " =");
350 if (!range)
351 {
352 if (HTTP_DEBUG)
353 GT->DBGFN (GT, "error parsing Range: header");
355 return;
356 }
358 start = (off_t) ATOI (string_sep (&range, "-"));
359 stop = (off_t) ATOI (string_sep (&range, " "));
361 /*
362 * The end of the range is optional (e.g. "Range: 0-"), and in that case
363 * stop == 0. In the case of a single byte file, stop == 1.
364 *
365 * TODO: this is broken for one-byte requests at the start of the file.
366 */
367 if (stop > 0)
368 stop = stop + 1;
370 if (r_start)
371 *r_start = start;
372 if (r_stop)
373 *r_stop = stop;
374 }
376 /*
377 * Parse requests of the forms:
378 *
379 * /get/47/File maybe with spaces HTTP
380 * /get/47/files_%20_url_encoded HTTP/1.1
381 * /uri-res/N2R?urn:sha1:ABCD HTTP/1.0
382 *
383 * and combinations thereof. The "HTTP" trailer is mandatory.
384 */
385 static void get_request_and_version (char *data, char **request,
386 char **version)
387 {
388 size_t len;
389 char *next;
390 char *dup;
391 char *http_version = NULL;
393 *request = NULL;
394 *version = NULL;
396 /* trim whitespace inbetween command and request */
397 string_trim (data);
399 if (!(dup = STRDUP (data)))
400 return;
402 string_upper (dup);
404 next = dup;
406 /* find the last instance of "HTTP" in the string */
407 while ((next = strstr (next, "HTTP")))
408 {
409 http_version = next;
410 next += sizeof ("HTTP") - 1;
411 }
413 /* the rest of the string must be the request */
414 if (http_version != NULL && http_version != dup)
415 {
416 len = http_version - dup;
417 data[len - 1] = 0;
419 *request = data;
420 *version = data + len;
421 }
423 free (dup);
424 }
426 /*
427 * Break down the clients HTTP request
428 */
429 static int parse_client_request (Dataset **r_dataset, char **r_command,
430 char **r_request, char **r_version,
431 off_t *r_start, off_t *r_stop, char *hdr)
432 {
433 Dataset *dataset = NULL;
434 char *command; /* GET */
435 char *request; /* /file.tar.gz */
436 char *version; /* HTTP/1.1 */
437 char *req_line;
439 if (!hdr)
440 return FALSE;
442 /*
443 * Get the first line of the request
444 */
445 req_line = string_sep_set (&hdr, "\r\n");
447 /* get the command (GET, HEAD, etc.) */
448 command = string_sep (&req_line, " ");
450 /*
451 * Handle non-url-encoded requests as well as encoded
452 * ones and get the request and version from this HTTP line.
453 */
454 get_request_and_version (req_line, &request, &version);
456 if (HTTP_DEBUG)
457 {
458 GT->DBGFN (GT, "command=%s version=%s request=%s",
459 command, version, request);
460 }
462 if (!request || string_isempty (request))
463 return FALSE;
465 if (r_command)
466 *r_command = command;
467 if (r_request)
468 *r_request = request;
469 if (r_version)
470 *r_version = version;
472 gt_http_header_parse (hdr, &dataset);
474 if (r_dataset)
475 *r_dataset = dataset;
477 /* handle Range: header */
478 parse_client_request_range (dataset, r_start, r_stop);
480 if (r_start && r_stop)
481 {
482 if (HTTP_DEBUG)
483 GT->dbg (GT, "range: [%i, %i)", *r_start, *r_stop);
484 }
486 return TRUE;
487 }
489 /*****************************************************************************/
491 /*
492 * Send the request reply back to the client
493 *
494 * NOTE:
495 * This is used by both GET / and PUSH /
496 */
497 static void reply_to_client_request (GtTransfer *xfer)
498 {
499 TCPC *c;
500 Chunk *chunk;
501 off_t entity_size;
502 char range[128];
503 char length[32];
504 BOOL ret;
506 if (!xfer)
507 return;
509 c = gt_transfer_get_tcpc (xfer);
510 chunk = gt_transfer_get_chunk (xfer);
512 /*
513 * Determine the "total" entity body that we have locally, not necessarily
514 * the data that we are uploading. HTTP demands this, but OpenFT really
515 * doesn't give a shit.
516 *
517 * NOTE:
518 * This only works to standard when operating on a GET / request, PUSH's
519 * merely use the range!
520 */
521 if (xfer->open_path_size)
522 entity_size = xfer->open_path_size;
523 else
524 entity_size = xfer->stop - xfer->start;
526 /* NOTE: we are "working" around the fact that HTTP's Content-Range
527 * reply is inclusive for the last byte, whereas giFT's is not. */
528 snprintf (range, sizeof (range) - 1, "bytes %i-%i/%i",
529 (int) xfer->start, (int) (xfer->stop - 1), (int) entity_size);
531 snprintf (length, sizeof (length) - 1, "%i",
532 (int) (xfer->stop - xfer->start));
534 ret = gt_http_server_send (c, xfer->code,
535 "Content-Range", range,
536 "Content-Length", length,
537 "Content-Type", xfer->content_type,
538 CONTENT_URN_FIELD, xfer->content_urns,
539 NULL);
541 /* if we transmitted all headers successfully, set transmitted_hdrs
542 * to keep the connection alive, possibly */
543 if (ret)
544 xfer->transmitted_hdrs = TRUE;
545 }
547 /*****************************************************************************/
549 static void http_incoming_free (struct http_incoming *incoming)
550 {
551 timer_remove (incoming->timer);
552 free (incoming);
553 }
555 static void http_incoming_close (struct http_incoming *incoming)
556 {
557 gt_http_connection_close (GT_TRANSFER_UPLOAD, incoming->c, TRUE);
558 http_incoming_free (incoming);
559 }
561 static BOOL http_incoming_timeout (struct http_incoming *incoming)
562 {
563 http_incoming_close (incoming);
564 return FALSE;
565 }
567 static struct http_incoming *http_incoming_alloc (TCPC *c)
568 {
569 struct http_incoming *incoming;
571 incoming = malloc (sizeof (struct http_incoming));
572 if (!incoming)
573 return NULL;
575 incoming->c = c;
576 incoming->timer = timer_add (INCOMING_TIMEOUT,
577 (TimerCallback)http_incoming_timeout,
578 incoming);
580 return incoming;
581 }
583 void gt_http_server_dispatch (int fd, input_id id, TCPC *c)
584 {
585 struct http_incoming *incoming;
587 if (net_sock_error (c->fd))
588 {
589 gt_http_connection_close (GT_TRANSFER_UPLOAD, c, TRUE);
590 return;
591 }
593 if (!(incoming = http_incoming_alloc (c)))
594 {
595 gt_http_connection_close (GT_TRANSFER_UPLOAD, c, TRUE);
596 return;
597 }
599 /* keep track of this incoming connection */
600 /* gt_http_connection_insert (GT_TRANSFER_UPLOAD, c); */
602 input_remove (id);
603 input_add (c->fd, incoming, INPUT_READ,
604 (InputCallback)get_client_request, 0);
605 }
607 /*
608 * Handle the client's GET commands.
609 */
610 static void get_client_request (int fd, input_id id, struct http_incoming *http)
611 {
612 GtTransfer *xfer;
613 TCPC *c;
614 Dataset *dataset = NULL;
615 char *command = NULL;
616 char *request = NULL;
617 char *version = NULL;
618 off_t start = 0;
619 off_t stop = 0;
620 FDBuf *buf;
621 unsigned char *data;
622 size_t data_len = 0;
623 int n;
625 c = http->c;
626 buf = tcp_readbuf (c);
628 if ((n = fdbuf_delim (buf, "\n")) < 0)
629 {
630 http_incoming_close (http);
631 return;
632 }
634 if (gt_fdbuf_full (buf))
635 {
636 http_incoming_close (http);
637 return;
638 }
640 if (n > 0)
641 return;
643 data = fdbuf_data (buf, &data_len);
645 if (!gt_http_header_terminated (data, data_len))
646 return;
648 fdbuf_release (buf);
650 if (HTTP_DEBUG)
651 GT->DBGSOCK (GT, c, "client request:\n%s", data);
653 /* parse the client's request and determine how we should proceed */
654 if (!parse_client_request (&dataset, &command, &request, &version,
655 &start, &stop, data))
656 {
657 GT->DBGSOCK (GT, c, "invalid http header");
658 http_incoming_close (http);
659 return;
660 }
662 /* discard incoming connection timeout maintainance structure */
663 http_incoming_free (http);
665 /*
666 * We have enough information now to actually allocate the transfer
667 * structure and pass it along to all logic that follows this
668 *
669 * NOTE:
670 * Each individual handler can determine when it wants to let giFT
671 * in on this
672 */
673 xfer = gt_transfer_new (GT_TRANSFER_UPLOAD, NULL,
674 net_peer (c->fd), 0, start, stop);
676 /* connect the connection and the xfer in unholy matrimony */
677 gt_transfer_set_tcpc (xfer, c);
679 /* assign all our own memory */
680 xfer->command = STRDUP (command);
681 xfer->header = dataset;
682 xfer->version = STRDUP (version);
684 if (!gt_transfer_set_request (xfer, request))
685 {
686 if (HTTP_DEBUG)
687 GT->DBGSOCK (GT, c, "invalid request \"s\"", request);
689 gt_transfer_close (xfer, TRUE);
690 return;
691 }
693 /* no need for this function again */
694 input_remove (id);
696 /* figure out how to handle this request */
697 if (!strcasecmp (xfer->command, "GET") ||
698 !strcasecmp (xfer->command, "HEAD"))
699 {
700 server_handle_get (xfer);
701 return;
702 }
704 gt_http_server_send_error_and_close (xfer, 501);
705 }
707 /*****************************************************************************/
709 static Transfer *start_upload (GtTransfer *xfer, Chunk **chunk)
710 {
711 Transfer *transfer;
712 char *user;
714 user = net_ip_str (xfer->ip);
716 transfer = GT->upload_start (GT, chunk, user, xfer->share_authd,
717 xfer->start, xfer->stop);
719 assert (transfer != NULL);
721 return transfer;
722 }
724 /* setup the structure for uploading. this will be called from within
725 * client space for PUSH requests as well */
726 int gt_server_setup_upload (GtTransfer *xfer)
727 {
728 Transfer *transfer; /* giFT structure */
729 Chunk *chunk;
730 TCPC *c;
732 if (!xfer)
733 return FALSE;
735 c = gt_transfer_get_tcpc (xfer);
736 assert (xfer->chunk == NULL);
738 /*
739 * Ban the host if they don't have access -- this gives no information
740 * about whether we have the file or not, and i think this is in violation
741 * of the HTTP spec (supposed to return "404 not found" before 403, but
742 * i'm not sure.
743 */
744 if (gt_ban_ipv4_is_banned (c->host))
745 {
746 xfer->code = 403;
747 return FALSE;
748 }
750 /* open the file that was requested before we go bringing giFT into
751 * this */
752 if (!(xfer->f = gt_transfer_open_request (xfer, &xfer->code)))
753 return FALSE;
755 /* assign stop a value before we proceed */
756 if (xfer->stop == 0)
757 {
758 struct stat st;
760 if (!file_stat (xfer->open_path, &st) || st.st_size == 0)
761 {
762 /* stupid bastards have a 0 length file */
763 GT->DBGSOCK (GT, c, "cannot satisfy %s: invalid share",
764 xfer->open_path);
765 return FALSE;
766 }
768 xfer->stop = st.st_size;
769 xfer->remaining_len = xfer->stop - xfer->start;
770 }
772 /* we can now be certain that we are handling a download request from
773 * the client. allocate the appropriate structures to hook into giFT */
774 if (!(transfer = start_upload (xfer, &chunk)))
775 {
776 GT->DBGFN (GT, "unable to register upload with the daemon");
777 return FALSE;
778 }
780 /* override 200 w/ 206 if the request is not the whole file size */
781 if (xfer->remaining_len != xfer->share_authd->size)
782 xfer->code = 206;
784 /* assign the circular references for passing the chunk along */
785 gt_transfer_set_chunk (xfer, chunk);
787 /* finally, seek the file descriptor where it needs to be */
788 fseek (xfer->f, xfer->start, SEEK_SET);
790 return TRUE;
791 }
793 static void server_handle_get (GtTransfer *xfer)
794 {
795 TCPC *c;
797 c = gt_transfer_get_tcpc (xfer);
798 assert (xfer->chunk == NULL);
800 /* WARNING: this block is duplicated in http_client:client_push_request */
801 if (!gt_server_setup_upload (xfer))
802 {
803 if (xfer->code == 200)
804 xfer->code = 404;
806 gt_http_server_send_error_and_close (xfer, xfer->code);
807 return;
808 }
810 input_add (c->fd, xfer, INPUT_WRITE,
811 (InputCallback)send_http_response, TIMEOUT_DEF);
812 }
814 static void send_http_response (int fd, input_id id, GtTransfer *xfer)
815 {
816 TCPC *c;
818 c = gt_transfer_get_tcpc (xfer);
820 if (net_sock_error (c->fd))
821 {
822 gt_transfer_close (xfer, TRUE);
823 return;
824 }
826 /* ok, send client the header */
827 reply_to_client_request (xfer);
829 if (!strcasecmp (xfer->command, "HEAD"))
830 {
831 gt_transfer_close (xfer, TRUE);
832 return;
833 }
835 /* disable header read timer */
836 timer_remove_zero (&xfer->header_timer);
838 input_remove (id);
839 input_add (c->fd, xfer, INPUT_WRITE,
840 (InputCallback)gt_server_upload_file, 0);
841 }
843 /*
844 * Uploads the file requests
845 */
846 void gt_server_upload_file (int fd, input_id id, GtTransfer *xfer)
847 {
848 TCPC *c;
849 Chunk *chunk;
850 char buf[RW_BUFFER];
851 size_t read_len;
852 size_t size;
853 int sent_len = 0;
854 off_t remainder;
856 c = gt_transfer_get_tcpc (xfer);
857 chunk = gt_transfer_get_chunk (xfer);
859 assert (xfer->f != NULL);
861 /* number of bytes left to be uploaded by this chunk */
862 if ((remainder = xfer->remaining_len) <= 0)
863 {
864 /* for whatever reason this function may have been called when we have
865 * already overrun the transfer...in that case we will simply fall
866 * through to the end-of-transfer condition */
867 gt_transfer_write (xfer, chunk, NULL, 0);
868 return;
869 }
871 size = sizeof (buf);
873 if (size > remainder)
874 size = remainder;
876 /*
877 * Ask giFT for the size we should send. If this returns 0, the upload
878 * was suspended.
879 */
880 if ((size = upload_throttle (chunk, size)) == 0)
881 return;
883 /* read as much as we can from the local file */
884 if (!(read_len = fread (buf, sizeof (char), size, xfer->f)))
885 {
886 GT->DBGFN (GT, "unable to read from %s: %s", xfer->open_path,
887 GIFT_STRERROR ());
888 gt_transfer_status (xfer, SOURCE_CANCELLED, "Local read error");
889 gt_transfer_close (xfer, TRUE);
890 return;
891 }
893 if ((sent_len = tcp_send (c, buf, MIN (read_len, remainder))) <= 0)
894 {
895 gt_transfer_status (xfer, SOURCE_CANCELLED,
896 "Unable to send data block");
897 gt_transfer_close (xfer, TRUE);
898 return;
899 }
901 /* check if the file was too small for the transfer TODO: this isn't
902 * checked earlier, but should be */
903 if (read_len != size)
904 {
905 gt_transfer_status (xfer, SOURCE_CANCELLED, "Unexpected end of file");
906 gt_transfer_close (xfer, TRUE);
907 return;
908 }
910 /*
911 * Check for short send(). This could use fseek(), but I have this
912 * growing feeling that using stdio everywhere is a bad idea.
913 */
914 if (read_len != sent_len)
915 {
916 gt_transfer_status (xfer, SOURCE_CANCELLED, "Short send()");
917 gt_transfer_close (xfer, TRUE);
918 return;
919 }
921 /*
922 * Call gt_upload to report back to giFT. This will also cancel
923 * the transfer if the upload has completed.
924 */
925 gt_transfer_write (xfer, chunk, buf, sent_len);
926 }
928 /*****************************************************************************/
930 void gt_http_server_reset (TCPC *c)
931 {
932 /*
933 * This can happen because the GtTransfer and TCPC can be decoupled, as in
934 * the case of a push request sent.
935 */
936 if (!c)
937 return;
939 /* finish all queued writes before we reset */
940 tcp_flush (c, TRUE);
942 /* reset the input state */
943 input_remove_all (c->fd);
944 input_add (c->fd, c, INPUT_READ,
945 (InputCallback)gt_http_server_dispatch, 2 * MINUTES);
946 }