Mercurial > hg > index.fcgi > gift-gnutella > gift-gnutella-0.0.11-1pba
comparison 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 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:f02ef53fb05d |
---|---|
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 */ | |
16 | |
17 #include "gt_gnutella.h" | |
18 | |
19 #include "gt_xfer_obj.h" | |
20 #include "gt_xfer.h" | |
21 | |
22 #include "gt_http_server.h" | |
23 #include "gt_http_client.h" | |
24 | |
25 #include "gt_accept.h" | |
26 #include "gt_ban.h" | |
27 #include "gt_version.h" | |
28 | |
29 /*****************************************************************************/ | |
30 | |
31 /* convenient shorthand */ | |
32 #define CONTENT_URN_FIELD "X-Gnutella-Content-URN" | |
33 | |
34 #define INCOMING_TIMEOUT (1 * MINUTES) | |
35 | |
36 /*****************************************************************************/ | |
37 | |
38 struct http_incoming | |
39 { | |
40 TCPC *c; | |
41 timer_id timer; | |
42 }; | |
43 | |
44 /*****************************************************************************/ | |
45 | |
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); | |
50 | |
51 /*****************************************************************************/ | |
52 /* SERVER HELPERS */ | |
53 | |
54 static char *lookup_http_code (int code, char **desc) | |
55 { | |
56 char *err; | |
57 char *txt; | |
58 | |
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 } | |
86 | |
87 if (desc) | |
88 *desc = txt; | |
89 | |
90 return err; | |
91 } | |
92 | |
93 static String *alloc_header (int code) | |
94 { | |
95 char *code_text; | |
96 String *s; | |
97 | |
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; | |
102 | |
103 if (!(s = string_new (NULL, 0, 0, TRUE))) | |
104 return FALSE; | |
105 | |
106 string_appendf (s, "HTTP/1.1 %i %s\r\n", code, code_text); | |
107 | |
108 return s; | |
109 } | |
110 | |
111 static void construct_header_va (String *s, int code, va_list args) | |
112 { | |
113 char *key; | |
114 char *value; | |
115 | |
116 /* Add "Server: " header */ | |
117 string_appendf (s, "Server: %s\r\n", gt_version ()); | |
118 | |
119 for (;;) | |
120 { | |
121 if (!(key = va_arg (args, char *))) | |
122 break; | |
123 | |
124 if (!(value = va_arg (args, char *))) | |
125 continue; | |
126 | |
127 string_appendf (s, "%s: %s\r\n", key, value); | |
128 } | |
129 | |
130 /* append final message terminator */ | |
131 string_append (s, "\r\n"); | |
132 } | |
133 | |
134 static String *construct_header (int code, ...) | |
135 { | |
136 String *s; | |
137 va_list args; | |
138 | |
139 if (!(s = alloc_header (code))) | |
140 return NULL; | |
141 | |
142 va_start (args, code); | |
143 construct_header_va (s, code, args); | |
144 va_end (args); | |
145 | |
146 return s; | |
147 } | |
148 | |
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; | |
158 | |
159 if (!(s = alloc_header (code))) | |
160 return FALSE; | |
161 | |
162 va_start (args, code); | |
163 | |
164 construct_header_va (s, code, args); | |
165 | |
166 va_end (args); | |
167 | |
168 if (HTTP_DEBUG) | |
169 GT->DBGSOCK (GT, c, "sending reply to client =\n%s", s->str); | |
170 | |
171 len = s->len; | |
172 ret = tcp_send (c, s->str, s->len); | |
173 | |
174 string_free (s); | |
175 | |
176 return (ret == len); | |
177 } | |
178 | |
179 static char *get_error_page (GtTransfer *xfer, int code) | |
180 { | |
181 char *page; | |
182 char *err; | |
183 char *errtxt = NULL; | |
184 | |
185 if (!(err = lookup_http_code (code, &errtxt))) | |
186 return 0; | |
187 | |
188 page = stringf ("<h1>%i %s</h1><br>%s.", code, err, errtxt); | |
189 | |
190 return page; | |
191 } | |
192 | |
193 static BOOL supports_queue (GtTransfer *xfer) | |
194 { | |
195 char *features; | |
196 | |
197 if (dataset_lookupstr (xfer->header, "x-queue")) | |
198 return TRUE; | |
199 | |
200 if ((features = dataset_lookupstr (xfer->header, "x-features"))) | |
201 { | |
202 /* XXX: case-sensitive */ | |
203 if (strstr (features, "queue")) | |
204 return TRUE; | |
205 } | |
206 | |
207 return FALSE; | |
208 } | |
209 | |
210 static char *get_queue_line (GtTransfer *xfer) | |
211 { | |
212 String *s; | |
213 | |
214 /* do nothing if not queued */ | |
215 if (xfer->queue_pos == 0) | |
216 return NULL; | |
217 | |
218 if (!(s = string_new (NULL, 0, 0, TRUE))) | |
219 return NULL; | |
220 | |
221 string_appendf (s, "position=%d,length=%d,pollMin=%d,pollMax=%d", | |
222 xfer->queue_pos, xfer->queue_ttl, 45, 120); | |
223 | |
224 return string_free_keep (s); | |
225 } | |
226 | |
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; | |
235 | |
236 len = strlen (error_page); | |
237 snprintf (content_len, sizeof (content_len), "%u", len); | |
238 | |
239 if (code == 503 && supports_queue (xfer)) | |
240 queue_line = get_queue_line (xfer); | |
241 | |
242 /* don't send a content-type header if there is no entity body */ | |
243 if (len == 0) | |
244 content_type = NULL; | |
245 | |
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); | |
252 | |
253 free (queue_line); | |
254 return s; | |
255 } | |
256 | |
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; | |
263 | |
264 c = gt_transfer_get_tcpc (xfer); | |
265 | |
266 if (!(error_page = get_error_page (xfer, xfer->code))) | |
267 { | |
268 gt_transfer_close (xfer, TRUE); | |
269 return; | |
270 } | |
271 | |
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 } | |
283 | |
284 if (!(s = get_error_header (xfer, xfer->code, error_page))) | |
285 { | |
286 gt_transfer_close (xfer, TRUE); | |
287 return; | |
288 } | |
289 | |
290 string_append (s, error_page); | |
291 | |
292 if (HTTP_DEBUG) | |
293 GT->DBGSOCK (GT, c, "sending reply to client =\n%s", s->str); | |
294 | |
295 /* send the whole page at once */ | |
296 ret = tcp_send (c, s->str, s->len); | |
297 | |
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 } | |
304 | |
305 string_free (s); | |
306 | |
307 gt_transfer_close (xfer, FALSE); | |
308 } | |
309 | |
310 static void gt_http_server_send_error_and_close (GtTransfer *xfer, int code) | |
311 { | |
312 TCPC *c; | |
313 | |
314 c = gt_transfer_get_tcpc (xfer); | |
315 | |
316 xfer->code = code; | |
317 | |
318 input_remove_all (c->fd); | |
319 input_add (c->fd, xfer, INPUT_WRITE, | |
320 (InputCallback)send_error_reply, TIMEOUT_DEF); | |
321 } | |
322 | |
323 /*****************************************************************************/ | |
324 | |
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; | |
332 | |
333 if (!r_start && !r_stop) | |
334 return; | |
335 | |
336 if (r_start) | |
337 *r_start = 0; | |
338 if (r_stop) | |
339 *r_stop = 0; | |
340 | |
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; | |
345 | |
346 /* WARNING: this butchers the data in the dataset! */ | |
347 string_sep (&range, "bytes"); | |
348 string_sep_set (&range, " ="); | |
349 | |
350 if (!range) | |
351 { | |
352 if (HTTP_DEBUG) | |
353 GT->DBGFN (GT, "error parsing Range: header"); | |
354 | |
355 return; | |
356 } | |
357 | |
358 start = (off_t) ATOI (string_sep (&range, "-")); | |
359 stop = (off_t) ATOI (string_sep (&range, " ")); | |
360 | |
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; | |
369 | |
370 if (r_start) | |
371 *r_start = start; | |
372 if (r_stop) | |
373 *r_stop = stop; | |
374 } | |
375 | |
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; | |
392 | |
393 *request = NULL; | |
394 *version = NULL; | |
395 | |
396 /* trim whitespace inbetween command and request */ | |
397 string_trim (data); | |
398 | |
399 if (!(dup = STRDUP (data))) | |
400 return; | |
401 | |
402 string_upper (dup); | |
403 | |
404 next = dup; | |
405 | |
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 } | |
412 | |
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; | |
418 | |
419 *request = data; | |
420 *version = data + len; | |
421 } | |
422 | |
423 free (dup); | |
424 } | |
425 | |
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; | |
438 | |
439 if (!hdr) | |
440 return FALSE; | |
441 | |
442 /* | |
443 * Get the first line of the request | |
444 */ | |
445 req_line = string_sep_set (&hdr, "\r\n"); | |
446 | |
447 /* get the command (GET, HEAD, etc.) */ | |
448 command = string_sep (&req_line, " "); | |
449 | |
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); | |
455 | |
456 if (HTTP_DEBUG) | |
457 { | |
458 GT->DBGFN (GT, "command=%s version=%s request=%s", | |
459 command, version, request); | |
460 } | |
461 | |
462 if (!request || string_isempty (request)) | |
463 return FALSE; | |
464 | |
465 if (r_command) | |
466 *r_command = command; | |
467 if (r_request) | |
468 *r_request = request; | |
469 if (r_version) | |
470 *r_version = version; | |
471 | |
472 gt_http_header_parse (hdr, &dataset); | |
473 | |
474 if (r_dataset) | |
475 *r_dataset = dataset; | |
476 | |
477 /* handle Range: header */ | |
478 parse_client_request_range (dataset, r_start, r_stop); | |
479 | |
480 if (r_start && r_stop) | |
481 { | |
482 if (HTTP_DEBUG) | |
483 GT->dbg (GT, "range: [%i, %i)", *r_start, *r_stop); | |
484 } | |
485 | |
486 return TRUE; | |
487 } | |
488 | |
489 /*****************************************************************************/ | |
490 | |
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; | |
505 | |
506 if (!xfer) | |
507 return; | |
508 | |
509 c = gt_transfer_get_tcpc (xfer); | |
510 chunk = gt_transfer_get_chunk (xfer); | |
511 | |
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; | |
525 | |
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); | |
530 | |
531 snprintf (length, sizeof (length) - 1, "%i", | |
532 (int) (xfer->stop - xfer->start)); | |
533 | |
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); | |
540 | |
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 } | |
546 | |
547 /*****************************************************************************/ | |
548 | |
549 static void http_incoming_free (struct http_incoming *incoming) | |
550 { | |
551 timer_remove (incoming->timer); | |
552 free (incoming); | |
553 } | |
554 | |
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 } | |
560 | |
561 static BOOL http_incoming_timeout (struct http_incoming *incoming) | |
562 { | |
563 http_incoming_close (incoming); | |
564 return FALSE; | |
565 } | |
566 | |
567 static struct http_incoming *http_incoming_alloc (TCPC *c) | |
568 { | |
569 struct http_incoming *incoming; | |
570 | |
571 incoming = malloc (sizeof (struct http_incoming)); | |
572 if (!incoming) | |
573 return NULL; | |
574 | |
575 incoming->c = c; | |
576 incoming->timer = timer_add (INCOMING_TIMEOUT, | |
577 (TimerCallback)http_incoming_timeout, | |
578 incoming); | |
579 | |
580 return incoming; | |
581 } | |
582 | |
583 void gt_http_server_dispatch (int fd, input_id id, TCPC *c) | |
584 { | |
585 struct http_incoming *incoming; | |
586 | |
587 if (net_sock_error (c->fd)) | |
588 { | |
589 gt_http_connection_close (GT_TRANSFER_UPLOAD, c, TRUE); | |
590 return; | |
591 } | |
592 | |
593 if (!(incoming = http_incoming_alloc (c))) | |
594 { | |
595 gt_http_connection_close (GT_TRANSFER_UPLOAD, c, TRUE); | |
596 return; | |
597 } | |
598 | |
599 /* keep track of this incoming connection */ | |
600 /* gt_http_connection_insert (GT_TRANSFER_UPLOAD, c); */ | |
601 | |
602 input_remove (id); | |
603 input_add (c->fd, incoming, INPUT_READ, | |
604 (InputCallback)get_client_request, 0); | |
605 } | |
606 | |
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; | |
624 | |
625 c = http->c; | |
626 buf = tcp_readbuf (c); | |
627 | |
628 if ((n = fdbuf_delim (buf, "\n")) < 0) | |
629 { | |
630 http_incoming_close (http); | |
631 return; | |
632 } | |
633 | |
634 if (gt_fdbuf_full (buf)) | |
635 { | |
636 http_incoming_close (http); | |
637 return; | |
638 } | |
639 | |
640 if (n > 0) | |
641 return; | |
642 | |
643 data = fdbuf_data (buf, &data_len); | |
644 | |
645 if (!gt_http_header_terminated (data, data_len)) | |
646 return; | |
647 | |
648 fdbuf_release (buf); | |
649 | |
650 if (HTTP_DEBUG) | |
651 GT->DBGSOCK (GT, c, "client request:\n%s", data); | |
652 | |
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 } | |
661 | |
662 /* discard incoming connection timeout maintainance structure */ | |
663 http_incoming_free (http); | |
664 | |
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); | |
675 | |
676 /* connect the connection and the xfer in unholy matrimony */ | |
677 gt_transfer_set_tcpc (xfer, c); | |
678 | |
679 /* assign all our own memory */ | |
680 xfer->command = STRDUP (command); | |
681 xfer->header = dataset; | |
682 xfer->version = STRDUP (version); | |
683 | |
684 if (!gt_transfer_set_request (xfer, request)) | |
685 { | |
686 if (HTTP_DEBUG) | |
687 GT->DBGSOCK (GT, c, "invalid request \"s\"", request); | |
688 | |
689 gt_transfer_close (xfer, TRUE); | |
690 return; | |
691 } | |
692 | |
693 /* no need for this function again */ | |
694 input_remove (id); | |
695 | |
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 } | |
703 | |
704 gt_http_server_send_error_and_close (xfer, 501); | |
705 } | |
706 | |
707 /*****************************************************************************/ | |
708 | |
709 static Transfer *start_upload (GtTransfer *xfer, Chunk **chunk) | |
710 { | |
711 Transfer *transfer; | |
712 char *user; | |
713 | |
714 user = net_ip_str (xfer->ip); | |
715 | |
716 transfer = GT->upload_start (GT, chunk, user, xfer->share_authd, | |
717 xfer->start, xfer->stop); | |
718 | |
719 assert (transfer != NULL); | |
720 | |
721 return transfer; | |
722 } | |
723 | |
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; | |
731 | |
732 if (!xfer) | |
733 return FALSE; | |
734 | |
735 c = gt_transfer_get_tcpc (xfer); | |
736 assert (xfer->chunk == NULL); | |
737 | |
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 } | |
749 | |
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; | |
754 | |
755 /* assign stop a value before we proceed */ | |
756 if (xfer->stop == 0) | |
757 { | |
758 struct stat st; | |
759 | |
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 } | |
767 | |
768 xfer->stop = st.st_size; | |
769 xfer->remaining_len = xfer->stop - xfer->start; | |
770 } | |
771 | |
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 } | |
779 | |
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; | |
783 | |
784 /* assign the circular references for passing the chunk along */ | |
785 gt_transfer_set_chunk (xfer, chunk); | |
786 | |
787 /* finally, seek the file descriptor where it needs to be */ | |
788 fseek (xfer->f, xfer->start, SEEK_SET); | |
789 | |
790 return TRUE; | |
791 } | |
792 | |
793 static void server_handle_get (GtTransfer *xfer) | |
794 { | |
795 TCPC *c; | |
796 | |
797 c = gt_transfer_get_tcpc (xfer); | |
798 assert (xfer->chunk == NULL); | |
799 | |
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; | |
805 | |
806 gt_http_server_send_error_and_close (xfer, xfer->code); | |
807 return; | |
808 } | |
809 | |
810 input_add (c->fd, xfer, INPUT_WRITE, | |
811 (InputCallback)send_http_response, TIMEOUT_DEF); | |
812 } | |
813 | |
814 static void send_http_response (int fd, input_id id, GtTransfer *xfer) | |
815 { | |
816 TCPC *c; | |
817 | |
818 c = gt_transfer_get_tcpc (xfer); | |
819 | |
820 if (net_sock_error (c->fd)) | |
821 { | |
822 gt_transfer_close (xfer, TRUE); | |
823 return; | |
824 } | |
825 | |
826 /* ok, send client the header */ | |
827 reply_to_client_request (xfer); | |
828 | |
829 if (!strcasecmp (xfer->command, "HEAD")) | |
830 { | |
831 gt_transfer_close (xfer, TRUE); | |
832 return; | |
833 } | |
834 | |
835 /* disable header read timer */ | |
836 timer_remove_zero (&xfer->header_timer); | |
837 | |
838 input_remove (id); | |
839 input_add (c->fd, xfer, INPUT_WRITE, | |
840 (InputCallback)gt_server_upload_file, 0); | |
841 } | |
842 | |
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; | |
855 | |
856 c = gt_transfer_get_tcpc (xfer); | |
857 chunk = gt_transfer_get_chunk (xfer); | |
858 | |
859 assert (xfer->f != NULL); | |
860 | |
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 } | |
870 | |
871 size = sizeof (buf); | |
872 | |
873 if (size > remainder) | |
874 size = remainder; | |
875 | |
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; | |
882 | |
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 } | |
892 | |
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 } | |
900 | |
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 } | |
909 | |
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 } | |
920 | |
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 } | |
927 | |
928 /*****************************************************************************/ | |
929 | |
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; | |
938 | |
939 /* finish all queued writes before we reset */ | |
940 tcp_flush (c, TRUE); | |
941 | |
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 } |