view src/transfer/source.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: source.c,v 1.12 2005/01/04 14:31:44 mkern Exp $
3 *
4 * Copyright (C) 2002-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"
18 #include "gt_share_file.h"
20 #include "encoding/url.h"
22 #include "transfer/source.h"
23 #include "transfer/download.h"
25 /*****************************************************************************/
27 /*
28 * Most of the goop in this file is for specifying each parameter for the giFT
29 * source URL. The source URL is supposed to encode all the information
30 * necessary for contacting a source.
31 */
33 /*****************************************************************************/
35 typedef BOOL (*UnserializeFunc) (GtSource *gt, const char *key,
36 const char *value);
37 typedef BOOL (*SerializeFunc) (GtSource *gt, String *s);
39 #define URL_OPT_SERIALIZE(name) \
40 BOOL gt_src_spew_##name (GtSource *gt, String *s)
42 #define URL_OPT_UNSERIALIZE(name) \
43 BOOL gt_src_parse_##name (GtSource *gt, const char *key, const char *value)
45 #define DECLARE_URL_OPT(name) \
46 static URL_OPT_SERIALIZE(name); \
47 static URL_OPT_UNSERIALIZE(name)
49 /*****************************************************************************/
51 DECLARE_URL_OPT(ip);
52 DECLARE_URL_OPT(port);
53 DECLARE_URL_OPT(server_ip);
54 DECLARE_URL_OPT(server_port);
55 DECLARE_URL_OPT(guid);
56 DECLARE_URL_OPT(fw);
57 DECLARE_URL_OPT(index);
58 DECLARE_URL_OPT(name);
60 /*
61 * Options that can go in our source URL format.
62 */
63 static struct url_option
64 {
65 const char *key; /* key in url (i.e. "port" in "port=6346") */
66 SerializeFunc serialize;
67 UnserializeFunc unserialize;
68 } gt_source_url_options[] =
69 {
70 { "ip", gt_src_spew_ip, gt_src_parse_ip },
71 { "port", gt_src_spew_port, gt_src_parse_port },
72 { "sip", gt_src_spew_server_ip, gt_src_parse_server_ip },
73 { "sport", gt_src_spew_server_port, gt_src_parse_server_port },
74 { "fw", gt_src_spew_fw, gt_src_parse_fw },
75 { "guid", gt_src_spew_guid, gt_src_parse_guid },
76 { "index", gt_src_spew_index, gt_src_parse_index },
77 { "name", gt_src_spew_name, gt_src_parse_name },
78 { NULL, NULL, NULL }
79 };
81 /*****************************************************************************/
83 /*
84 * These functions return TRUE if they produced some output.
85 */
87 static URL_OPT_SERIALIZE(ip)
88 {
89 if (!gt->user_ip)
90 return FALSE;
92 string_appendf (s, "ip=%s", net_ip_str (gt->user_ip));
93 return TRUE;
94 }
96 static URL_OPT_SERIALIZE(port)
97 {
98 if (gt->user_port == 6346)
99 return FALSE;
101 string_appendf (s, "port=%hu", gt->user_port);
102 return TRUE;
103 }
105 static URL_OPT_SERIALIZE(name)
106 {
107 if (!gt->filename)
108 return FALSE;
110 string_appendf (s, "name=%s", gt->filename);
111 return TRUE;
112 }
114 static URL_OPT_SERIALIZE(guid)
115 {
116 if (gt_guid_is_empty (gt->guid))
117 return FALSE;
119 string_appendf (s, "guid=%s", gt_guid_str (gt->guid));
120 return TRUE;
121 }
123 static URL_OPT_SERIALIZE(server_ip)
124 {
125 if (!gt->server_ip)
126 return FALSE;
128 string_appendf (s, "sip=%s", net_ip_str (gt->server_ip));
129 return TRUE;
130 }
132 static URL_OPT_SERIALIZE(server_port)
133 {
134 if (gt->server_port == 6346)
135 return FALSE;
137 string_appendf (s, "sport=%hu", gt->server_port);
138 return TRUE;
139 }
141 static URL_OPT_SERIALIZE(fw)
142 {
143 if (!gt->firewalled)
144 return FALSE;
146 string_append (s, "fw=1");
147 return TRUE;
148 }
150 static URL_OPT_SERIALIZE(index)
151 {
152 if (!gt->index)
153 return FALSE;
155 string_appendf (s, "index=%u", gt->index);
156 return TRUE;
157 }
159 /*****************************************************************************/
161 /*
162 * These functions return TRUE if they were successful.
163 */
165 static URL_OPT_UNSERIALIZE(ip)
166 {
167 in_addr_t ip;
169 ip = net_ip (value);
171 if (ip == 0 || ip == INADDR_NONE)
172 return FALSE;
174 gt->user_ip = ip;
175 return TRUE;
176 }
178 static URL_OPT_UNSERIALIZE(port)
179 {
180 unsigned long port;
182 port = gift_strtoul (value);
184 if (port == ULONG_MAX || port >= 65536)
185 return FALSE;
187 gt->user_port = port;
188 return TRUE;
189 }
191 static URL_OPT_UNSERIALIZE(name)
192 {
193 char *name;
195 if (!(name = STRDUP (value)))
196 return FALSE;
198 gt->filename = name;
199 return TRUE;
200 }
202 static URL_OPT_UNSERIALIZE(guid)
203 {
204 gt_guid_t *guid;
206 if (!(guid = gt_guid_bin (value)))
207 return FALSE;
209 free (gt->guid);
210 gt->guid = guid;
212 return TRUE;
213 }
215 static URL_OPT_UNSERIALIZE(server_ip)
216 {
217 in_addr_t ip;
219 ip = net_ip (value);
221 if (ip == 0 || ip == INADDR_NONE)
222 return FALSE;
224 gt->server_ip = ip;
225 return TRUE;
226 }
228 static URL_OPT_UNSERIALIZE(server_port)
229 {
230 unsigned long port;
232 port = gift_strtoul (value);
234 if (port == ULONG_MAX || port >= 65536)
235 return FALSE;
237 gt->server_port = port;
238 return TRUE;
239 }
241 static URL_OPT_UNSERIALIZE(fw)
242 {
243 unsigned long fw;
245 fw = gift_strtoul (value);
247 if (fw != 0 && fw != 1)
248 return FALSE;
250 if (fw)
251 gt->firewalled = TRUE;
252 else
253 gt->firewalled = FALSE;
255 return TRUE;
256 }
258 static URL_OPT_UNSERIALIZE(index)
259 {
260 unsigned long index;
262 index = gift_strtoul (value);
264 if (index == ULONG_MAX)
265 return FALSE;
267 gt->index = (uint32_t)index;
268 return TRUE;
269 }
271 /*****************************************************************************/
273 /*
274 * Old Gnutella URL format:
275 *
276 * Gnutella://<u-ip>:<u-port>@<s-ip>:<s-port>[[FW]]:<client-guid>/<index>/<name>
277 *
278 * server_port is the server's gnutella port. This should probably pass
279 * back both the gnutella port instead and the peer's connecting port, to
280 * help in disambiguating different users behind the same firewall.
281 */
282 static BOOL parse_old_url (char *url,
283 uint32_t *r_user_ip, uint16_t *r_user_port,
284 uint32_t *r_server_ip, uint16_t *r_server_port,
285 BOOL *firewalled, char **r_pushid,
286 uint32_t *r_index, char **r_fname)
287 {
288 char *port_and_flags;
289 char *flag;
291 string_sep (&url, "://");
293 /* TODO: check for more errors */
295 *r_user_ip = net_ip (string_sep (&url, ":"));
296 *r_user_port = gift_strtoul (string_sep (&url, "@"));
297 *r_server_ip = net_ip (string_sep (&url, ":"));
299 /* handle bracketed flags after port. ugh, this is so ugly */
300 port_and_flags = string_sep (&url, ":");
301 *r_server_port = gift_strtoul (string_sep (&port_and_flags, "["));
303 if (!string_isempty (port_and_flags))
304 {
305 /* grab any flags inside the brackets */
306 while ((flag = string_sep_set (&port_and_flags, ",]")))
307 {
308 if (!STRCMP (flag, "FW"))
309 *firewalled = TRUE;
310 }
311 }
313 *r_pushid = string_sep (&url, "/");
314 *r_index = gift_strtoul (string_sep (&url, "/"));
315 *r_fname = url;
317 return TRUE;
318 }
320 static struct url_option *lookup_url_option (const char *key)
321 {
322 struct url_option *url_opt;
324 url_opt = &gt_source_url_options[0];
326 while (url_opt->key != NULL)
327 {
328 if (strcmp (url_opt->key, key) == 0)
329 return url_opt;
331 url_opt++;
332 }
334 return NULL;
335 }
337 /*
338 * New parameter-based URL format:
339 *
340 * Gnutella:?ip=<u-ip>&port=<u-port>&sip=<s-ip>&sport=<s-port>[&fw=<FW>]...
341 *
342 * Parameters we don't understand are placed in gt_src->extra Dataset, so we
343 * should be forwards and backwards compatible when adding new parameters.
344 */
345 static BOOL parse_new_url (char *url, GtSource *gt)
346 {
347 char *option;
348 char *key;
349 char *value;
351 /* skip prefix */
352 string_sep (&url, ":?");
354 while ((option = string_sep (&url, "&")))
355 {
356 struct url_option *url_opt;
358 value = option;
359 key = string_sep (&value, "=");
361 if (string_isempty (key) || string_isempty (value))
362 continue;
364 /* look up the key in our list of possible options */
365 if ((url_opt = lookup_url_option (key)))
366 {
367 /* unserialize the specified key */
368 if (url_opt->unserialize (gt, key, value))
369 continue;
371 /* fail through on failure to store failed keys */
372 }
374 /* store the unfound keys in the extra parameter dataset */
375 dataset_insertstr (&gt->extra, key, value);
376 }
378 return TRUE;
379 }
381 /*****************************************************************************/
383 static GtSource *handle_old_url (char *url)
384 {
385 GtSource *gt;
386 char *fname = NULL;
387 char *guid_ascii = NULL;
389 if (!(gt = gt_source_new ()))
390 return NULL;
392 if (!parse_old_url (url, &gt->user_ip, &gt->user_port,
393 &gt->server_ip, &gt->server_port,
394 &gt->firewalled, &guid_ascii, &gt->index, &fname))
395 {
396 gt_source_free (gt);
397 return NULL;
398 }
400 gt->filename = NULL;
401 if (!string_isempty (fname))
402 gt->filename = STRDUP (fname);
404 gt->guid = NULL;
405 if (!string_isempty (guid_ascii))
406 gt->guid = gt_guid_bin (guid_ascii);
408 return gt;
409 }
411 static GtSource *handle_new_url (char *url)
412 {
413 GtSource *gt;
415 if (!(gt = gt_source_new ()))
416 return NULL;
418 if (!parse_new_url (url, gt))
419 {
420 gt_source_free (gt);
421 return NULL;
422 }
424 return gt;
425 }
427 GtSource *gt_source_unserialize (const char *url)
428 {
429 char *t_url;
430 GtSource *src = NULL;
432 if (!url)
433 return NULL;
435 if (!(t_url = STRDUP (url)))
436 return NULL;
438 /*
439 * Determine whether this is the new format URL (beginning with
440 * "Gnutella:?") or the old-style (starts with "Gnutella://")
441 */
442 if (strncmp (t_url, "Gnutella://", sizeof ("Gnutella://") - 1) == 0)
443 {
444 src = handle_old_url (t_url);
445 }
446 else if (strncmp (t_url, "Gnutella:?", sizeof ("Gnutella:?") - 1) == 0)
447 {
448 src = handle_new_url (t_url);
449 }
450 else
451 {
452 /* do nothing */
453 }
455 FREE (t_url);
457 return src;
458 }
460 /* use the old format serialization for now */
461 #if 0
462 static void unknown_opt (ds_data_t *key, ds_data_t *value, void *udata)
463 {
464 String *str = udata;
465 string_appendf (str, "%s=%s&", key->data, value->data);
466 }
468 char *gt_source_serialize (GtSource *gt)
469 {
470 struct url_option *opt;
471 char *url;
472 size_t len;
473 String str;
475 string_init (&str);
476 string_appendf (&str, "%s:?", GT->name);
478 for (opt = gt_source_url_options; opt->key != NULL; opt++)
479 {
480 if (opt->serialize (gt, &str))
481 {
482 /* append separator for next argument */
483 string_appendc (&str, '&');
484 }
485 }
487 /* copy unknown options to the URL */
488 dataset_foreach (gt->extra, unknown_opt, &str);
490 len = str.len;
491 assert (len > 0);
493 url = string_finish_keep (&str);
495 /* remove trailing separator (may not be there if source is empty) */
496 if (url[len - 1] == '&')
497 url[len - 1] = 0;
499 return url;
500 }
501 #endif
503 /* serialize to the old format for now */
504 char *gt_source_serialize (GtSource *gt)
505 {
506 String *str;
508 if (!(str = string_new (NULL, 0, 0, TRUE)))
509 return FALSE;
511 string_appendf (str, "Gnutella://%s:%hu", net_ip_str (gt->user_ip),
512 gt->user_port);
514 string_appendf (str, "@%s:%hu", net_ip_str (gt->server_ip),
515 gt->server_port);
517 string_appendc (str, '[');
519 if (gt->firewalled)
520 string_append (str, "FW");
522 string_appendc (str, ']');
524 string_appendf (str, ":%s/%lu",
525 STRING_NOTNULL (gt_guid_str (gt->guid)), (long)gt->index);
526 string_appendf (str, "/%s",
527 STRING_NOTNULL (gt->filename)); /* already encoded */
529 return string_free_keep (str);
530 }
532 /*****************************************************************************/
534 /*
535 * This is called by the search result code in order to produce
536 * source URLs.
537 *
538 * TODO: This is just wrong -- interface is not very extensible. The search
539 * result code should probably use GtSource and call gt_source_serialize().
540 */
541 char *gt_source_url_new (const char *filename, uint32_t index,
542 in_addr_t user_ip, uint16_t user_port,
543 in_addr_t server_ip, uint16_t server_port,
544 BOOL firewalled, const gt_guid_t *client_id)
545 {
546 GtSource *src;
547 char *url;
549 if (!(src = gt_source_new ()))
550 return NULL;
552 gt_source_set_ip (src, user_ip);
553 gt_source_set_port (src, user_port);
554 gt_source_set_index (src, index);
555 gt_source_set_server_ip (src, server_ip);
556 gt_source_set_server_port (src, server_port);
557 gt_source_set_firewalled (src, firewalled);
559 if (!gt_source_set_guid (src, client_id) ||
560 !gt_source_set_filename (src, filename))
561 {
562 gt_source_free (src);
563 return NULL;
564 }
566 url = gt_source_serialize (src);
567 gt_source_free (src);
569 return url;
570 }
572 /*****************************************************************************/
574 GtSource *gt_source_new (void)
575 {
576 GtSource *src;
578 if (!(src = NEW (GtSource)))
579 return NULL;
581 /* special case: port is 6346 if not specified */
582 src->user_port = 6346;
583 src->server_port = 6346;
585 return src;
586 }
588 void gt_source_free (GtSource *gt)
589 {
590 if (!gt)
591 return;
593 free (gt->guid);
594 free (gt->filename);
595 free (gt->status_txt);
597 FREE (gt);
598 }
600 /*****************************************************************************/
602 void gt_source_set_ip (GtSource *src, in_addr_t ip)
603 {
604 src->user_ip = ip;
605 }
607 void gt_source_set_port (GtSource *src, in_port_t port)
608 {
609 src->user_port = port;
610 }
612 void gt_source_set_index (GtSource *src, uint32_t index)
613 {
614 src->index = index;
615 }
617 void gt_source_set_server_ip (GtSource *src, in_addr_t server_ip)
618 {
619 src->server_ip = server_ip;
620 }
622 void gt_source_set_server_port (GtSource *src, in_port_t server_port)
623 {
624 src->server_port = server_port;
625 }
627 void gt_source_set_firewalled (GtSource *src, BOOL fw)
628 {
629 src->firewalled = fw;
630 }
632 BOOL gt_source_set_filename (GtSource *src, const char *filename)
633 {
634 char *encoded;
636 /* special case for no filename */
637 if (!filename)
638 {
639 free (src->filename);
640 src->filename = NULL;
641 return TRUE;
642 }
644 if (!(encoded = gt_url_encode (filename)))
645 return FALSE;
647 src->filename = encoded;
648 return TRUE;
649 }
651 BOOL gt_source_set_guid (GtSource *src, const gt_guid_t *guid)
652 {
653 gt_guid_t *dup;
655 if (!(dup = gt_guid_dup (guid)))
656 return FALSE;
658 src->guid = dup;
659 return TRUE;
660 }
662 /*****************************************************************************/
664 int gnutella_source_cmp (Protocol *p, Source *a, Source *b)
665 {
666 GtSource *gt_a = NULL;
667 GtSource *gt_b = NULL;
668 int ret = 0;
670 if (!(gt_a = gt_source_unserialize (a->url)) ||
671 !(gt_b = gt_source_unserialize (b->url)))
672 {
673 gt_source_free (gt_a);
674 gt_source_free (gt_b);
675 return -1;
676 }
678 if (gt_a->user_ip > gt_b->user_ip)
679 ret = 1;
680 else if (gt_a->user_ip < gt_b->user_ip)
681 ret = -1;
683 /*
684 * Having two sources with the same IP on the same transfer can trigger a
685 * bug in most versions of giftd. At least as of giftd < 0.11.9, this
686 * causes sources to be reactivated after download completion due to a bug
687 * in handle_next_queued called from download_complete. To avoid that, we
688 * pretend that multiple sources with the same hash from the same user_ip
689 * are the same here, by ignoring the port. If the sources compare
690 * equally, then one will replace the other when added, and there won't be
691 * any dead sources with the same IP available to reactivate when the
692 * download completes.
693 *
694 * It's ok for the client guid to be different, even if the IPs are the
695 * same, since in that case the guid gets reflected in the user string, so
696 * the bug in handle_next_queue() won't trigger.
697 *
698 * Also, Transfers that don't have hashes are ok since they can only ever
699 * have one user. So, if either source doesn't have a hash the bug won't
700 * trigger.
701 */
702 #if 0
703 if (gt_a->user_port > gt_b->user_port)
704 ret = 1;
705 else if (gt_a->user_port < gt_b->user_port)
706 ret = -1;
707 #endif
709 /* if both IPs are private match by the guid */
710 if (gt_is_local_ip (gt_a->user_ip, gt_a->server_ip) &&
711 gt_is_local_ip (gt_b->user_ip, gt_b->server_ip))
712 {
713 ret = gt_guid_cmp (gt_a->guid, gt_b->guid);
714 }
716 if (ret == 0)
717 {
718 /* if the hashes match consider them equal */
719 if (a->hash || b->hash)
720 ret = gift_strcmp (a->hash, b->hash);
721 else
722 ret = gift_strcmp (gt_a->filename, gt_b->filename);
723 }
725 gt_source_free (gt_a);
726 gt_source_free (gt_b);
728 return ret;
729 }
731 int gnutella_source_add (Protocol *p, Transfer *transfer, Source *source)
732 {
733 GtSource *src;
735 assert (source->udata == NULL);
737 if (!(src = gt_source_unserialize (source->url)))
738 return FALSE;
740 source->udata = src;
742 /* track this download */
743 gt_download_add (transfer, source);
745 return TRUE;
746 }
748 void gnutella_source_remove (Protocol *p, Transfer *transfer, Source *source)
749 {
750 gt_download_remove (transfer, source);
752 assert (source->udata != NULL);
753 gt_source_free (source->udata);
754 }