10ffe13504d3560ca941a4485cf886acecec5864
[oweals/gnunet.git] / src / fs / fs_uri.c
1 /*
2      This file is part of GNUnet.
3      (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 2, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file fs/fs_uri.c
23  * @brief Parses and produces uri strings.
24  * @author Igor Wronsky, Christian Grothoff
25  *
26  * GNUnet URIs are of the general form "gnunet://MODULE/IDENTIFIER".
27  * The specific structure of "IDENTIFIER" depends on the module and
28  * maybe differenciated into additional subcategories if applicable.
29  * This module only deals with ecrs identifiers (MODULE = "ecrs").
30  * <p>
31  *
32  * This module only parses URIs for the AFS module.  The ECRS URIs fall
33  * into four categories, "chk", "sks", "ksk" and "loc".  The first three
34  * categories were named in analogy (!) to Freenet, but they do NOT
35  * work in exactly the same way.  They are very similar from the user's
36  * point of view (unique file identifier, subspace, keyword), but the
37  * implementation is rather different in pretty much every detail.
38  * The concrete URI formats are:
39  *
40  * <ul><li>
41  *
42  * First, there are URIs that identify a file.  They have the format
43  * "gnunet://ecrs/chk/HEX1.HEX2.SIZE".  These URIs can be used to
44  * download the file.  The description, filename, mime-type and other
45  * meta-data is NOT part of the file-URI since a URI uniquely
46  * identifies a resource (and the contents of the file would be the
47  * same even if it had a different description).
48  *
49  * </li><li>
50  *
51  * The second category identifies entries in a namespace.  The format
52  * is "gnunet://ecrs/sks/NAMESPACE/IDENTIFIER" where the namespace
53  * should be given in HEX.  Applications may allow using a nickname
54  * for the namespace if the nickname is not ambiguous.  The identifier
55  * can be either an ASCII sequence or a HEX-encoding.  If the
56  * identifier is in ASCII but the format is ambiguous and could denote
57  * a HEX-string a "/" is appended to indicate ASCII encoding.
58  *
59  * </li> <li>
60  *
61  * The third category identifies ordinary searches.  The format is
62  * "gnunet://ecrs/ksk/KEYWORD[+KEYWORD]*".  Using the "+" syntax
63  * it is possible to encode searches with the boolean "AND" operator.
64  * "+" is used since it indicates a commutative 'and' operation and
65  * is unlikely to be used in a keyword by itself.
66  *
67  * </li><li>
68  *
69  * The last category identifies a datum on a specific machine.  The
70  * format is "gnunet://ecrs/loc/HEX1.HEX2.SIZE.PEER.SIG.EXPTIME".  PEER is
71  * the BinName of the public key of the peer storing the datum.  The
72  * signature (SIG) certifies that this peer has this content.
73  * HEX1, HEX2 and SIZE correspond to a 'chk' URI.
74  *
75  * </li></ul>
76  *
77  * The encoding for hexadecimal values is defined in the hashing.c
78  * module in the gnunetutil library and discussed there.
79  * <p>
80  */
81 #include "platform.h"
82 #include "gnunet_fs_service.h"
83 #include "fs.h"
84
85
86 /**
87  * Get a unique key from a URI.  This is for putting URIs
88  * into HashMaps.  The key may change between FS implementations.
89  *
90  * @param uri uri to convert to a unique key
91  * @param key wherer to store the unique key
92  */
93 void 
94 GNUNET_FS_uri_to_key (const struct GNUNET_FS_Uri *uri,
95                       GNUNET_HashCode * key)
96 {
97   switch (uri->type)
98     {
99     case chk:
100       *key = uri->data.chk.chk.query;
101       return;
102     case sks:
103       GNUNET_CRYPTO_hash (uri->data.sks.identifier,
104                           strlen (uri->data.sks.identifier), key);
105       break;
106     case ksk:
107       if (uri->data.ksk.keywordCount > 0)
108         GNUNET_CRYPTO_hash (uri->data.ksk.keywords[0],
109                             strlen (uri->data.ksk.keywords[0]), key);
110       break;
111     case loc:
112       GNUNET_CRYPTO_hash (&uri->data.loc.fi,
113                           sizeof (struct FileIdentifier) +
114                           sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), key);
115       break;
116     default:
117       memset (key, 0, sizeof (GNUNET_HashCode));
118       break;
119     }
120 }
121
122
123 /**
124  * Convert keyword URI to a human readable format
125  * (i.e. the search query that was used in the first place)
126  *
127  * @param uri ksk uri to convert to a string 
128  * @return string with the keywords
129  */
130 char *
131 GNUNET_FS_uri_ksk_to_string_fancy (const struct GNUNET_FS_Uri *uri)
132 {
133   size_t n;
134   char *ret;
135   unsigned int i;
136   const char *keyword;
137   char **keywords;
138   unsigned int keywordCount;
139
140   if ((uri == NULL) || (uri->type != ksk))
141     {
142       GNUNET_break (0);
143       return NULL;
144     }
145   keywords = uri->data.ksk.keywords;
146   keywordCount = uri->data.ksk.keywordCount;
147   n = keywordCount + 1;
148   for (i = 0; i < keywordCount; i++)
149     {
150       keyword = keywords[i];
151       n += strlen (keyword) - 1;
152       if (NULL != strstr (&keyword[1], " "))
153         n += 2;
154       if (keyword[0] == '+')
155         n++;
156     }
157   ret = GNUNET_malloc (n);
158   strcpy (ret, "");
159   for (i = 0; i < keywordCount; i++)
160     {
161       keyword = keywords[i];
162       if (NULL != strstr (&keyword[1], " "))
163         {
164           strcat (ret, "\"");
165           if (keyword[0] == '+')
166             strcat (ret, keyword);
167           else
168             strcat (ret, &keyword[1]);
169           strcat (ret, "\"");
170         }
171       else
172         {
173           if (keyword[0] == '+')
174             strcat (ret, keyword);
175           else
176             strcat (ret, &keyword[1]);
177         }
178       strcat (ret, " ");
179     }
180   return ret;
181 }
182
183
184
185
186 /**
187  * Given a keyword with %-encoding (and possibly quotes to protect
188  * spaces), return a copy of the keyword without %-encoding and
189  * without double-quotes (%22).  Also, add a space at the beginning
190  * if there is not a '+'.
191  * 
192  * @param in string with %-encoding
193  * @param emsg where to store the parser error message (if any)
194  * @return decodded string with leading space (or preserved plus)
195  */
196 static char *
197 percent_decode_keyword (const char *in, char **emsg)
198 {
199   char *out;
200   char *ret;
201   unsigned int rpos;
202   unsigned int wpos;
203   unsigned int hx;
204
205   out = GNUNET_strdup (in);
206   rpos = 0;
207   wpos = 0;
208   while (out[rpos] != '\0')
209     {
210       if (out[rpos] == '%')
211         {
212           if (1 != sscanf (&out[rpos + 1], "%2X", &hx))
213             {
214               GNUNET_free (out);
215               return NULL;
216             }
217           rpos += 3;
218           if (hx == '"')
219             continue;           /* skip double quote */
220           out[wpos++] = (char) hx;
221         }
222       else
223         {
224           out[wpos++] = out[rpos++];
225         }
226     }
227   out[wpos] = '\0';
228   if (out[0] == '+')
229     {
230       ret = GNUNET_strdup (out);
231     }
232   else
233     {
234       /* need to prefix with space */
235       ret = GNUNET_malloc (strlen (out) + 2);
236       strcpy (ret, " ");
237       strcat (ret, out);
238     }
239   GNUNET_free (out);
240   return ret;
241 }
242
243
244 /**
245  * Parse a KSK URI.
246  *
247  * @param s an uri string
248  * @param emsg where to store the parser error message (if any)
249  * @return NULL on error, otherwise the KSK URI
250  */
251 static struct GNUNET_FS_Uri *
252 uri_ksk_parse (const char *s, char **emsg)
253 {
254   struct GNUNET_FS_Uri *ret;
255   char **keywords;
256   unsigned int pos;
257   int max;
258   int iret;
259   int i;
260   size_t slen;
261   char *dup;
262   int saw_quote;
263
264   GNUNET_assert (s != NULL);
265   slen = strlen (s);
266   pos = strlen (GNUNET_FS_URI_PREFIX GNUNET_FS_URI_KSK_INFIX);
267   if ( (slen <= pos) ||
268        (0 != strncmp (s, GNUNET_FS_URI_PREFIX GNUNET_FS_URI_KSK_INFIX, 
269                       pos) ) ||
270        (s[slen - 1] == '+') ||
271        (s[pos] == '+') )
272     return NULL;       /* no keywords / malformed */
273   
274   max = 1;
275   saw_quote = 0;
276   for (i = pos; i < slen; i++)
277     {
278       if ((s[i] == '%') && (&s[i] == strstr (&s[i], "%22")))
279         {
280           saw_quote = (saw_quote + 1) % 2;
281           i += 3;
282           continue;
283         }
284       if ((s[i] == '+') && (saw_quote == 0))
285         {
286           max++;
287           if (s[i - 1] == '+')
288             return NULL;       /* "++" not allowed */
289         }
290     }
291   if (saw_quote == 1)
292     return NULL;       /* quotes not balanced */
293   iret = max;
294   dup = GNUNET_strdup (s);
295   keywords = GNUNET_malloc (max * sizeof (char *));
296   for (i = slen - 1; i >= pos; i--)
297     {
298       if ((s[i] == '%') && (&s[i] == strstr (&s[i], "%22")))
299         {
300           saw_quote = (saw_quote + 1) % 2;
301           i += 3;
302           continue;
303         }
304       if ((dup[i] == '+') && (saw_quote == 0))
305         {
306           keywords[--max] = percent_decode_keyword (&dup[i + 1], emsg);
307           if (NULL == keywords[max])
308             goto CLEANUP;
309           dup[i] = '\0';
310         }
311     }
312   keywords[--max] = percent_decode_keyword (&dup[pos], emsg);
313   if (NULL == keywords[max])
314     goto CLEANUP;
315   GNUNET_assert (max == 0);
316   GNUNET_free (dup);
317   ret = GNUNET_malloc (sizeof(struct GNUNET_FS_Uri));
318   ret->type = ksk;
319   ret->data.ksk.keywordCount = iret;
320   ret->data.ksk.keywords = keywords;
321   return ret;
322 CLEANUP:
323   for (i = 0; i < max; i++)
324     GNUNET_free_non_null (keywords[i]);
325   GNUNET_free (keywords);
326   GNUNET_free (dup);
327   return NULL;
328 }
329
330
331 /**
332  * Parse an SKS URI.
333  *
334  * @param s an uri string
335  * @param emsg where to store the parser error message (if any)
336  * @return NULL on error, SKS URI otherwise
337  */
338 static struct GNUNET_FS_Uri *
339 uri_sks_parse (const char *s, char **emsg)
340 {
341   struct GNUNET_FS_Uri *ret;
342   GNUNET_HashCode namespace;
343   char *identifier;
344   unsigned int pos;
345   size_t slen;
346   char enc[sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded)];
347
348   GNUNET_assert (s != NULL);
349   slen = strlen (s);
350   pos = strlen (GNUNET_FS_URI_PREFIX GNUNET_FS_URI_SKS_INFIX);
351   if ( (slen <= pos) ||
352        (0 != strncmp (s, GNUNET_FS_URI_PREFIX GNUNET_FS_URI_SKS_INFIX, 
353                       pos) ) ||
354        (slen < pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) + 1) ||
355        (s[pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) - 1] != '/') )
356     return NULL;
357   memcpy (enc, &s[pos], sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded));
358   enc[sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded)-1] = '\0';
359   if (GNUNET_OK != GNUNET_CRYPTO_hash_from_string (enc, &namespace))
360     return NULL;
361   identifier = GNUNET_strdup (&s[pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded)]);
362   ret = GNUNET_malloc (sizeof(struct GNUNET_FS_Uri));
363   ret->type = sks;
364   ret->data.sks.namespace = namespace;
365   ret->data.sks.identifier = identifier;
366   return ret;
367 }
368
369
370 /**
371  * Parse a CHK URI.
372  *
373  * @param s an uri string
374  * @param emsg where to store the parser error message (if any)
375  * @return NULL on error, CHK URI otherwise
376  */
377 static struct GNUNET_FS_Uri *
378 uri_chk_parse (const char *s, char **emsg)
379 {
380   struct GNUNET_FS_Uri *ret;
381   struct FileIdentifier fi;
382   unsigned int pos;
383   size_t slen;
384   char h1[sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded)];
385   char h2[sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded)];
386
387   GNUNET_assert (s != NULL);
388
389   slen = strlen (s);
390   pos = strlen (GNUNET_FS_URI_PREFIX GNUNET_FS_URI_CHK_INFIX);
391   if ( (slen < pos + 2 * sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) + 1) ||
392        (0 != strncmp (s, GNUNET_FS_URI_PREFIX GNUNET_FS_URI_CHK_INFIX, 
393                       pos) ) ||
394        (s[pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) - 1] != '.') ||
395        (s[pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) * 2 - 1] != '.') )
396     return NULL;
397
398   memcpy (h1,
399           &s[pos], 
400           sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded));
401   h1[sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded)-1] = '\0';
402   memcpy (h2,
403           &s[pos + sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded)],
404           sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded));
405   h2[sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded)-1] = '\0';
406   
407   if ((GNUNET_OK != GNUNET_CRYPTO_hash_from_string (h1,
408                                                &fi.chk.key)) ||
409       (GNUNET_OK != GNUNET_CRYPTO_hash_from_string (h2,
410                                                &fi.chk.query)) ||
411       (1 != SSCANF (&s[pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) * 2],
412                     "%llu", 
413                     &fi.file_length)))
414     return NULL;
415   fi.file_length = GNUNET_htonll (fi.file_length);
416
417   ret = GNUNET_malloc (sizeof(struct GNUNET_FS_Uri));
418   ret->type = chk;
419   ret->data.chk = fi;
420   return ret;
421 }
422
423
424 /**
425  * Parse a LOC URI.
426  * Also verifies validity of the location URI.
427  *
428  * @param s an uri string
429  * @param emsg where to store the parser error message (if any)
430  * @return NULL on error, valid LOC URI otherwise
431  */
432 static struct GNUNET_FS_Uri *
433 uri_loc_parse (const char *s, char **emsg)
434 {
435   struct GNUNET_FS_Uri *ret;
436   char h1[sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded)];
437   char h2[sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded)];
438   unsigned int pos;
439   unsigned int npos;
440   unsigned long long exptime;
441   int ret;
442   size_t slen;
443   char *addr;
444
445   GNUNET_assert (s != NULL);
446   slen = strlen (s);
447   pos = strlen (GNUNET_FS_URI_PREFIX GNUNET_FS_URI_LOC_INFIX);
448   if ( (slen < pos + 2 * sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) + 1) ||
449        (0 != strncmp (s, GNUNET_FS_URI_PREFIX GNUNET_FS_URI_LOC_INFIX, 
450                       pos) ) ||
451        (s[pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) - 1] != '.') ||
452        (s[pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) * 2 - 1] != '.') )
453     return NULL;
454
455   memcpy (h1,
456           &s[pos], 
457           sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded));
458   h1[sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded)-1] = '\0';
459   memcpy (h2,
460           &s[pos + sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded)],
461           sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded));
462   h2[sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded)-1] = '\0';
463   
464   if ((GNUNET_OK != GNUNET_CRYPTO_hash_from_string (h1,
465                                                     &fi.chk.key)) ||
466       (GNUNET_OK != GNUNET_CRYPTO_hash_from_string (h2,
467                                                     &fi.chk.query)) 
468       (1 != SSCANF (&s[pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) * 2],
469                     "%llu", 
470                     &fi.file_length)) )
471     return NULL;
472   fi.file_length = GNUNET_htonll (fi.file_length);
473
474   npos = pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) * 2;
475   while ((s[npos] != '\0') && (s[npos] != '.'))
476     npos++;
477   if (s[npos] == '\0')
478     goto ERR;
479   ret = enc2bin (&s[npos], 
480                  &loc->peer,
481                  sizeof (GNUNET_RSA_PublicKey));
482   if (ret == -1)
483     goto ERR;
484   npos += ret;
485   if (dup[npos++] != '.')
486     goto ERR;
487   ret = enc2bin (&s[npos],
488                  &loc->contentSignature,
489                  sizeof (struct GNUNET_CRYPTO_RsaSignature));
490   if (ret == -1)
491     goto ERR;
492   npos += ret;
493   if (dup[npos++] != '.')
494     goto ERR;
495   if (1 != SSCANF (&dup[npos], "%llu", &exptime))
496     goto ERR;
497   // FIXME: do something to exptime...
498   /* Finally: verify sigs! */
499   if (GNUNET_OK != GNUNET_RSA_verify (&loc->fi,
500                                       sizeof (struct FileIdentifier) +
501                                       sizeof (GNUNET_PeerIdentity) +
502                                       sizeof (GNUNET_Int32Time),
503                                       &loc->contentSignature, 
504                                       &loc->peer))
505     goto ERR;
506
507   ret = GNUNET_malloc (sizeof(struct GNUNET_FS_Uri));
508   ret->type = loc;
509   ret->data.loc.chk = fi;
510   ret->data.loc.xx = yy;
511
512   return ret;
513 ERR:
514   GNUNET_free_non_null (addr);
515   return NULL;
516 }
517
518
519 /**
520  * Convert a UTF-8 String to a URI.
521  *
522  * @param uri string to parse
523  * @param emsg where to store the parser error message (if any)
524  * @return NULL on error
525  */
526 struct GNUNET_FS_Uri *
527 GNUNET_FS_uri_parse (const char *uri,
528                      char **emsg)
529 {
530   struct GNUNET_FS_Uri *ret;
531
532   if ( (NULL != (ret = uri_chk_parse (uri, emsg))) ||
533        (NULL != (ret = uri_ksk_parse (uri, emsg))) ||
534        (NULL != (ret = uri_sks_parse (uri, emsg))) ||
535        (NULL != (ret = uri_loc_parse (uri, emsg))) )
536     return ret;
537   return NULL;
538 }
539
540
541 /**
542  * Free URI.
543  *
544  * @param uri uri to free
545  */
546 void 
547 GNUNET_FS_uri_destroy (struct GNUNET_FS_Uri *uri)
548 {
549   unsigned int i;
550
551   GNUNET_assert (uri != NULL);
552   switch (uri->type)
553     {
554     case ksk:
555       for (i = 0; i < uri->data.ksk.keywordCount; i++)
556         GNUNET_free (uri->data.ksk.keywords[i]);
557       GNUNET_array_grow (uri->data.ksk.keywords, uri->data.ksk.keywordCount,
558                          0);
559       break;
560     case sks:
561       GNUNET_free (uri->data.sks.identifier);
562       break;
563     case loc:
564       break;
565     default:
566       /* do nothing */
567       break;
568     }
569   GNUNET_free (uri);
570 }
571
572 /**
573  * How many keywords are ANDed in this keyword URI?
574  *
575  * @param uri ksk uri to get the number of keywords from
576  * @return 0 if this is not a keyword URI
577  */
578 unsigned int 
579 GNUNET_FS_uri_ksk_get_keyword_count (const struct GNUNET_FS_Uri *uri)
580 {
581   if (uri->type != ksk)
582     return 0;
583   return uri->data.ksk.keywordCount;
584 }
585
586
587 /**
588  * Iterate over all keywords in this keyword URI.
589  *
590  * @param uri ksk uri to get the keywords from
591  * @param iterator function to call on each keyword
592  * @param iterator_cls closure for iterator
593  * @return -1 if this is not a keyword URI, otherwise number of
594  *   keywords iterated over until iterator aborted
595  */
596 int 
597 GNUNET_FS_uri_ksk_get_keywords (const struct GNUNET_FS_Uri *uri,
598                                 GNUNET_FS_KeywordIterator iterator, 
599                                 void *iterator_cls)
600 {
601   unsigned int i;
602   char *keyword;
603
604   if (uri->type != ksk)
605     return -1;
606   if (iterator == NULL)
607     return uri->data.ksk.keywordCount;
608   for (i = 0; i < uri->data.ksk.keywordCount; i++)
609     {
610       keyword = uri->data.ksk.keywords[i];
611       /* first character of keyword indicates
612          if it is mandatory or not */
613       if (GNUNET_OK != iterator (iterator_cls,
614                                  &keyword[1],
615                                  keyword[0] == '+'))
616         return i;
617     }
618   return i;
619 }
620
621
622 /**
623  * Obtain the identity of the peer offering the data
624  *
625  * @param uri the location URI to inspect
626  * @param peer where to store the identify of the peer (presumably) offering the content
627  * @return GNUNET_SYSERR if this is not a location URI, otherwise GNUNET_OK
628  */
629 int
630 GNUNET_FS_uri_loc_get_peer_identity (const struct GNUNET_FS_Uri *uri,
631                                      struct GNUNET_PeerIdentity * peer)
632 {
633   if (uri->type != loc)
634     return GNUNET_SYSERR;
635   GNUNET_CRYPTO_hash (&uri->data.loc.peer,
636                       sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded),
637                       &peer->hashPubKey);
638   return GNUNET_OK;
639 }
640
641
642 /**
643  * Obtain the URI of the content itself.
644  *
645  * @param uri location URI to get the content URI from
646  * @return NULL if argument is not a location URI
647  */
648 struct GNUNET_FS_Uri *
649 GNUNET_FS_uri_loc_get_uri (const struct GNUNET_FS_Uri *uri)
650 {
651   struct GNUNET_FS_Uri *ret;
652
653   if (uri->type != loc)
654     return NULL;
655   ret = GNUNET_malloc (sizeof (struct GNUNET_FS_Uri));
656   ret->type = chk;
657   ret->data.chk = uri->data.loc.fi;
658   return ret;
659 }
660
661
662 /**
663  * Construct a location URI (this peer will be used for the location).
664  *
665  * @param baseURI content offered by the sender
666  * @param cfg configuration information (used to find our hostkey)
667  * @param expiration_time how long will the content be offered?
668  * @return the location URI, NULL on error
669  */
670 struct GNUNET_FS_Uri *
671 GNUNET_FS_uri_loc_create (const struct GNUNET_FS_Uri *baseUri,
672                           struct GNUNET_CONFIGURATION_Handle *cfg,
673                           struct GNUNET_TIME_Absolute expiration_time);
674
675
676 /**
677  * Canonicalize keyword URI.  Performs operations such
678  * as decapitalization and removal of certain characters.
679  * (useful for search).
680  *
681  * @param uri the URI to canonicalize 
682  * @return canonicalized version of the URI, NULL on error
683  */
684 struct GNUNET_FS_Uri *
685 GNUNET_FS_uri_ksk_canonicalize (const struct GNUNET_FS_Uri *uri)
686 {
687   /* FIXME: not implemented */
688   return NULL;
689 }
690
691
692 /**
693  * Merge the sets of keywords from two KSK URIs.
694  * (useful for merging the canonicalized keywords with
695  * the original keywords for sharing).
696  *
697  * @param u1 first uri
698  * @param u2 second uri
699  * @return merged URI, NULL on error
700  */
701 struct GNUNET_FS_Uri *
702 GNUNET_FS_uri_ksk_merge (const struct GNUNET_FS_Uri *u1,
703                          const struct GNUNET_FS_Uri *u2)
704 {
705   /* FIXME */
706   return NULL;
707 }
708
709
710 /**
711  * Duplicate URI.
712  *
713  * @param uri the URI to duplicate
714  * @return copy of the URI
715  */
716 struct GNUNET_FS_Uri *
717 GNUNET_FS_uri_dup (const struct GNUNET_FS_Uri *uri)
718 {
719   struct GNUNET_FS_Uri *ret;
720   unsigned int i;
721
722   ret = GNUNET_malloc (sizeof (struct GNUNET_FS_Uri));
723   memcpy (ret, uri, sizeof (struct GNUNET_FS_Uri));
724   switch (ret->type)
725     {
726     case ksk:
727       if (ret->data.ksk.keywordCount > 0)
728         {
729           ret->data.ksk.keywords
730             = GNUNET_malloc (ret->data.ksk.keywordCount * sizeof (char *));
731           for (i = 0; i < ret->data.ksk.keywordCount; i++)
732             ret->data.ksk.keywords[i] =
733               GNUNET_strdup (uri->data.ksk.keywords[i]);
734         }
735       else
736         ret->data.ksk.keywords = NULL;  /* just to be sure */
737       break;
738     case sks:
739       ret->data.sks.identifier = GNUNET_strdup (uri->data.sks.identifier);
740       break;
741     case loc:
742       break;
743     default:
744       break;
745     }
746   return ret;
747 }
748
749
750 /**
751  * Create an FS URI from a single user-supplied string of keywords.
752  * The string is broken up at spaces into individual keywords.
753  * Keywords that start with "+" are mandatory.  Double-quotes can
754  * be used to prevent breaking up strings at spaces (and also
755  * to specify non-mandatory keywords starting with "+").
756  *
757  * Keywords must contain a balanced number of double quotes and
758  * double quotes can not be used in the actual keywords (for
759  * example, the string '""foo bar""' will be turned into two
760  * "OR"ed keywords 'foo' and 'bar', not into '"foo bar"'.
761  *
762  * @param keywords the keyword string
763  * @return an FS URI for the given keywords, NULL
764  *  if keywords is not legal (i.e. empty).
765  */
766 struct GNUNET_FS_Uri *
767 GNUNET_FS_uri_ksk_create (const char *keywords)
768 {
769   /* FIXME */
770   return NULL;
771 }
772
773
774 /**
775  * Create an FS URI from a user-supplied command line of keywords.
776  * Arguments should start with "+" to indicate mandatory
777  * keywords.
778  *
779  * @param argc number of keywords
780  * @param argv keywords (double quotes are not required for
781  *             keywords containing spaces; however, double
782  *             quotes are required for keywords starting with
783  *             "+"); there is no mechanism for having double
784  *             quotes in the actual keywords (if the user
785  *             did specifically specify double quotes, the
786  *             caller should convert each double quote
787  *             into two single quotes).
788  * @return an FS URI for the given keywords, NULL
789  *  if keywords is not legal (i.e. empty).
790  */
791 struct GNUNET_FS_Uri *
792 GNUNET_FS_uri_ksk_create_from_args (unsigned int argc,
793                                     const char **argv)
794 {
795   /* FIXME */
796   return NULL;
797 }
798
799
800 /**
801  * Test if two URIs are equal.
802  *
803  * @param u1 one of the URIs
804  * @param u2 the other URI
805  * @return GNUNET_YES if the URIs are equal
806  */
807 int 
808 GNUNET_FS_uri_test_equal (const struct GNUNET_FS_Uri *u1,
809                           const struct GNUNET_FS_Uri *u2)
810 {
811   int ret;
812   unsigned int i;
813   unsigned int j;
814
815   GNUNET_assert (u1 != NULL);
816   GNUNET_assert (u2 != NULL);
817   if (u1->type != u2->type)
818     return GNUNET_NO;
819   switch (u1->type)
820     {
821     case chk:
822       if (0 == memcmp (&u1->data.chk,
823                        &u2->data.chk,
824                        sizeof (struct FileIdentifier)))
825         return GNUNET_YES;
826       return GNUNET_NO;
827     case sks:
828       if ((0 == memcmp (&u1->data.sks.namespace,
829                         &u2->data.sks.namespace,
830                         sizeof (GNUNET_HashCode))) &&
831           (0 == strcmp (u1->data.sks.identifier,
832                         u2->data.sks.identifier)))
833
834         return GNUNET_YES;
835       return GNUNET_NO;
836     case ksk:
837       if (u1->data.ksk.keywordCount != u2->data.ksk.keywordCount)
838         return GNUNET_NO;
839       for (i = 0; i < u1->data.ksk.keywordCount; i++)
840         {
841           ret = GNUNET_NO;
842           for (j = 0; j < u2->data.ksk.keywordCount; j++)
843             {
844               if (0 == strcmp (u1->data.ksk.keywords[i],
845                                u2->data.ksk.keywords[j]))
846                 {
847                   ret = GNUNET_YES;
848                   break;
849                 }
850             }
851           if (ret == GNUNET_NO)
852             return GNUNET_NO;
853         }
854       return GNUNET_YES;
855     case loc:
856       if (memcmp (&u1->data.loc,
857                   &u2->data.loc,
858                   sizeof (struct FileIdentifier) +
859                   sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded) +
860                   sizeof (struct GNUNET_TIME_Absolute) +
861                   sizeof (unsigned short) + sizeof (unsigned short)) != 0)
862         return GNUNET_NO;
863       return GNUNET_YES;
864     default:
865       return GNUNET_NO;
866     }
867 }
868
869
870 /**
871  * Is this a namespace URI?
872  *
873  * @param uri the uri to check
874  * @return GNUNET_YES if this is an SKS uri
875  */
876 int
877 GNUNET_FS_uri_test_sks (const struct GNUNET_FS_Uri *uri)
878 {
879   return uri->type == sks;
880 }
881
882
883 /**
884  * Get the ID of a namespace from the given
885  * namespace URI.
886  *
887  * @param uri the uri to get the namespace ID from
888  * @param nsid where to store the ID of the namespace
889  * @return GNUNET_OK on success
890  */
891 int 
892 GNUNET_FS_uri_sks_get_namespace (const struct GNUNET_FS_Uri *uri,
893                                  GNUNET_HashCode * nsid)
894 {
895   if (! GNUNET_FS_uri_test_sks (uri))
896     {
897       GNUNET_break (0);
898       return GNUNET_SYSERR;
899     }
900   *nsid = uri->data.sks.namespace;
901   return GNUNET_OK;
902 }
903
904
905 /**
906  * Get the content identifier of an SKS URI.
907  *
908  * @param uri the sks uri
909  * @return NULL on error (not a valid SKS URI)
910  */
911 char *
912 GNUNET_FS_uri_sks_get_content_id (const struct GNUNET_FS_Uri *uri)
913 {
914   if (!GNUNET_FS_uri_test_sks (uri))
915     {
916       GNUNET_break (0);
917       return NULL;
918     }
919   return GNUNET_strdup (uri->data.sks.identifier);
920 }
921
922
923 /**
924  * Convert namespace URI to a human readable format
925  * (using the namespace description, if available).
926  *
927  * @param cfg configuration to use
928  * @param uri SKS uri to convert
929  * @return NULL on error (not an SKS URI)
930  */
931 char *
932 GNUNET_FS_uri_sks_to_string_fancy (struct GNUNET_CONFIGURATION_Handle *cfg,
933                                    const struct GNUNET_FS_Uri *uri)
934 {
935   /* FIXME */
936   return NULL;
937 }
938
939
940 /**
941  * Is this a keyword URI?
942  *
943  * @param uri the uri
944  * @return GNUNET_YES if this is a KSK uri
945  */
946 int 
947 GNUNET_FS_uri_test_ksk (const struct GNUNET_FS_Uri *uri)
948 {
949 #if EXTRA_CHECKS
950   unsigned int i;
951
952   if (uri->type == ksk)
953     {
954       for (i = uri->data.ksk.keywordCount - 1; i >= 0; i--)
955         GNUNET_assert (uri->data.ksk.keywords[i] != NULL);
956     }
957 #endif
958   return uri->type == ksk;
959 }
960
961
962 /**
963  * Is this a file (or directory) URI?
964  *
965  * @param uri the uri to check
966  * @return GNUNET_YES if this is a CHK uri
967  */
968 int 
969 GNUNET_FS_uri_test_chk (const struct GNUNET_FS_Uri *uri)
970 {
971   return uri->type == chk;
972 }
973
974
975 /**
976  * What is the size of the file that this URI
977  * refers to?
978  *
979  * @param uri the CHK URI to inspect
980  * @return size of the file as specified in the CHK URI
981  */
982 uint64_t 
983 GNUNET_FS_uri_chk_get_file_size (const struct GNUNET_FS_Uri *uri)
984 {
985   switch (uri->type)
986     {
987     case chk:
988       return GNUNET_ntohll (uri->data.chk.file_length);
989     case loc:
990       return GNUNET_ntohll (uri->data.loc.fi.file_length);
991     default:
992       GNUNET_assert (0);
993     }
994   return 0;                     /* unreachable */
995 }
996
997
998 /**
999  * Is this a location URI?
1000  *
1001  * @param uri the uri to check
1002  * @return GNUNET_YES if this is a LOC uri
1003  */
1004 int 
1005 GNUNET_FS_uri_test_loc (const struct GNUNET_FS_Uri *uri)
1006 {
1007   return uri->type == loc;
1008 }
1009
1010
1011 /**
1012  * Function called on each value in the meta data.
1013  * Adds it to the URI.
1014  *
1015  * @param cls URI to update
1016  * @param type type of the meta data
1017  * @param data value of the meta data
1018  * @return GNUNET_OK (always)
1019  */
1020 static int
1021 gather_uri_data (void *cls,
1022                  EXTRACTOR_KeywordType type, 
1023                  const char *data)
1024 {
1025   struct GNUNET_FS_Uri *uri = cls;
1026   char *nkword;
1027   int j;
1028   
1029   for (j = uri->data.ksk.keywordCount - 1; j >= 0; j--)
1030     if (0 == strcmp (&uri->data.ksk.keywords[j][1], data))
1031       return GNUNET_OK;
1032   nkword = GNUNET_malloc (strlen (data) + 2);
1033   strcpy (nkword, " ");         /* not mandatory */
1034   strcat (nkword, data);
1035   uri->data.ksk.keywords[uri->data.ksk.keywordCount++] = nkword;
1036   return GNUNET_OK;
1037 }
1038
1039
1040 /**
1041  * Construct a keyword-URI from meta-data (take all entries
1042  * in the meta-data and construct one large keyword URI
1043  * that lists all keywords that can be found in the meta-data).
1044  * @deprecated
1045  */
1046 struct GNUNET_FS_Uri *
1047 GNUNET_FS_uri_ksk_create_from_meta_data (const struct GNUNET_CONTAINER_MetaData *md)
1048 {
1049   struct GNUNET_FS_Uri *ret;
1050
1051   if (md == NULL)
1052     return NULL;
1053   ret = GNUNET_malloc (sizeof (struct GNUNET_FS_Uri));
1054   ret->type = ksk;
1055   ret->data.ksk.keywordCount = 0;
1056   ret->data.ksk.keywords = NULL;
1057   ret->data.ksk.keywords
1058     = GNUNET_malloc (sizeof (char *) *
1059                      GNUNET_CONTAINER_meta_data_get_contents (md, NULL, NULL));
1060   GNUNET_CONTAINER_meta_data_get_contents (md, &gather_uri_data, ret);
1061   return ret;
1062
1063 }
1064
1065
1066 /**
1067  * In URI-encoding, does the given character
1068  * need to be encoded using %-encoding?
1069  */
1070 static int
1071 needs_percent (char c)
1072 {
1073   return (!((isalnum (c)) ||
1074             (c == '-') || (c == '_') || (c == '.') || (c == '~')));
1075 }
1076
1077
1078 /**
1079  * Convert a KSK URI to a string.
1080  *
1081  * @param uri the URI to convert
1082  * @return NULL on error (i.e. keywordCount == 0)
1083  */
1084 static char *
1085 uri_ksk_to_string (const struct GNUNET_FS_Uri *uri)
1086 {
1087   char ** keywords; 
1088   unsigned int keywordCount;
1089   size_t n;
1090   char *ret;
1091   unsigned int i;
1092   unsigned int j;
1093   unsigned int wpos;
1094   size_t slen;
1095   const char *keyword;
1096
1097   if (uri->type != ksk)
1098     return NULL;
1099   keywords = uri->data.ksk.keywords;
1100   keywordCount = uri->data.ksk.keywordCount;
1101   n =
1102     keywordCount + strlen (GNUNET_FS_URI_PREFIX) +
1103     strlen (GNUNET_FS_URI_KSK_INFIX) + 1;
1104   for (i = 0; i < keywordCount; i++)
1105     {
1106       keyword = keywords[i];
1107       slen = strlen (keyword);
1108       n += slen;
1109       for (j = 0; j < slen; j++)
1110         {
1111           if ((j == 0) && (keyword[j] == ' '))
1112             {
1113               n--;
1114               continue;         /* skip leading space */
1115             }
1116           if (needs_percent (keyword[j]))
1117             n += 2;             /* will use %-encoding */
1118         }
1119     }
1120   ret = GNUNET_malloc (n);
1121   strcpy (ret, GNUNET_FS_URI_PREFIX);
1122   strcat (ret, GNUNET_FS_URI_KSK_INFIX);
1123   wpos = strlen (ret);
1124   for (i = 0; i < keywordCount; i++)
1125     {
1126       keyword = keywords[i];
1127       slen = strlen (keyword);
1128       for (j = 0; j < slen; j++)
1129         {
1130           if ((j == 0) && (keyword[j] == ' '))
1131             continue;           /* skip leading space */
1132           if (needs_percent (keyword[j]))
1133             {
1134               sprintf (&ret[wpos], "%%%02X", keyword[j]);
1135               wpos += 3;
1136             }
1137           else
1138             {
1139               ret[wpos++] = keyword[j];
1140             }
1141         }
1142       if (i != keywordCount - 1)
1143         ret[wpos++] = '+';
1144     }
1145   return ret;
1146 }
1147
1148
1149 /**
1150  * Convert SKS URI to a string.
1151  *
1152  * @param uri sks uri to convert
1153  * @return NULL on error
1154  */
1155 static char *
1156 uri_sks_to_string (const struct GNUNET_FS_Uri *uri)
1157 {
1158   const GNUNET_HashCode * namespace;
1159   const char *identifier;
1160   char *ret;
1161   struct GNUNET_CRYPTO_HashAsciiEncoded ns;
1162   
1163   if (uri->type != sks)
1164     return NULL;
1165   namespace = &uri->data.sks.namespace;
1166   identifier = uri->data.sks.identifier;
1167   GNUNET_CRYPTO_hash_to_enc (namespace, &ns);
1168   GNUNET_asprintf (&ret,
1169                    "%s%s%s/%s",
1170                    GNUNET_FS_URI_PREFIX, 
1171                    GNUNET_FS_URI_SKS_INFIX,
1172                    (const char *) &ns, identifier);
1173   return ret;
1174 }
1175
1176
1177 /**
1178  * Convert a CHK URI to a string.
1179  *
1180  * @param uri chk uri to convert
1181  * @return NULL on error
1182  */
1183 static char *
1184 uri_chk_to_string (const struct GNUNET_FS_Uri *uri)
1185 {
1186   const struct FileIdentifier * fi;
1187   char *ret;
1188   struct GNUNET_CRYPTO_HashAsciiEncoded keyhash;
1189   struct GNUNET_CRYPTO_HashAsciiEncoded queryhash;
1190
1191   if (uri->type != chk)
1192     return NULL;
1193   fi = &uri->data.chk;
1194   GNUNET_CRYPTO_hash_to_enc (&fi->chk.key, &keyhash);
1195   GNUNET_CRYPTO_hash_to_enc (&fi->chk.query, &queryhash);
1196
1197   GNUNET_asprintf (&ret,
1198                    "%s%s%s.%s.%llu",
1199                    GNUNET_FS_URI_PREFIX,
1200                    GNUNET_FS_URI_CHK_INFIX,
1201                    (const char *) &keyhash, 
1202                    (const char *) &queryhash,
1203                    GNUNET_ntohll (fi->file_length));
1204   return ret;
1205 }
1206
1207 /**
1208  * Convert binary data to a string.
1209  *
1210  * @return converted data
1211  */
1212 static char *
1213 bin2enc (const void *data, size_t size)
1214 {
1215   /**
1216    * 64 characters for encoding, 6 bits per character
1217    */
1218   static char *tbl =
1219     "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_=";
1220   
1221   size_t len;
1222   size_t pos;
1223   unsigned int bits;
1224   unsigned int hbits;
1225   char *ret;
1226
1227   GNUNET_assert (strlen (tbl) == 64);
1228   len = size * 8 / 6;
1229   if (((size * 8) % 6) != 0)
1230     len++;
1231   ret = GNUNET_malloc (len + 1);
1232   ret[len] = '\0';
1233   len = 0;
1234   bits = 0;
1235   hbits = 0;
1236   for (pos = 0; pos < size; pos++)
1237     {
1238       bits |= ((((const unsigned char *) data)[pos]) << hbits);
1239       hbits += 8;
1240       while (hbits >= 6)
1241         {
1242           ret[len++] = tbl[bits & 63];
1243           bits >>= 6;
1244           hbits -= 6;
1245         }
1246     }
1247   if (hbits > 0)
1248     ret[len++] = tbl[bits & 63];
1249   return ret;
1250 }
1251
1252
1253 /**
1254  * Convert a character back to the binary value
1255  * that it represents (given base64-encoding).
1256  *
1257  * @param a character to convert
1258  * @return offset in the "tbl" array
1259  */
1260 static unsigned int
1261 c2v (unsigned char a)
1262 {
1263   if ((a >= '0') && (a <= '9'))
1264     return a - '0';
1265   if ((a >= 'A') && (a <= 'Z'))
1266     return (a - 'A' + 10);
1267   if ((a >= 'a') && (a <= 'z'))
1268     return (a - 'a' + 36);
1269   if (a == '_')
1270     return 62;
1271   if (a == '=')
1272     return 63;
1273   return -1;
1274 }
1275
1276
1277 /**
1278  * Convert string back to binary data.
1279  *
1280  * @param input '\0'-terminated string
1281  * @param data where to write binary data
1282  * @param size how much data should be converted
1283  * @return number of characters processed from input,
1284  *        -1 on error
1285  */
1286 static int
1287 enc2bin (const char *input, void *data, size_t size)
1288 {
1289   size_t len;
1290   size_t pos;
1291   unsigned int bits;
1292   unsigned int hbits;
1293
1294   len = size * 8 / 6;
1295   if (((size * 8) % 6) != 0)
1296     len++;
1297   if (strlen (input) < len)
1298     return -1;                  /* error! */
1299   bits = 0;
1300   hbits = 0;
1301   len = 0;
1302   pos = 0;
1303   for (pos = 0; pos < size; pos++)
1304     {
1305       while (hbits < 8)
1306         {
1307           bits |= (c2v (input[len++]) << hbits);
1308           hbits += 6;
1309         }
1310       (((unsigned char *) data)[pos]) = (unsigned char) bits;
1311       bits >>= 8;
1312       hbits -= 8;
1313     }
1314   return len;
1315 }
1316
1317
1318 /**
1319  * Convert a LOC URI to a string.
1320  *
1321  * @param uri loc uri to convert
1322  * @return NULL on error
1323  */
1324 static char *
1325 uri_loc_to_string (const struct GNUNET_FS_Uri *uri)
1326 {
1327   char *ret;
1328   struct GNUNET_CRYPTO_HashAsciiEncoded keyhash;
1329   struct GNUNET_CRYPTO_HashAsciiEncoded queryhash;
1330   char *peerId;
1331   char *peerSig;
1332
1333   GNUNET_CRYPTO_hash_to_enc (&uri->data.loc.fi.chk.key, &keyhash);
1334   GNUNET_CRYPTO_hash_to_enc (&uri->data.loc.fi.chk.query, &queryhash);
1335   peerId = bin2enc (&uri->data.loc.peer,
1336                     sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded));
1337   peerSig = bin2enc (&uri->data.loc.contentSignature, 
1338                      sizeof (struct GNUNET_CRYPTO_RsaSignature));
1339   GNUNET_asprintf (&ret,
1340                    "%s%s%s.%s.%llu.%s.%s.%u", // FIXME: expirationTime 64-bit???
1341                    GNUNET_FS_URI_PREFIX,
1342                    GNUNET_FS_URI_LOC_INFIX,
1343                    (const char *) &keyhash,
1344                    (const char *) &queryhash,
1345                    GNUNET_ntohll (uri->data.loc.fi.file_length),
1346                    peerId,
1347                    peerSig,
1348                    uri->data.loc.expirationTime);
1349   GNUNET_free (peerSig);
1350   GNUNET_free (peerId);
1351   return ret;
1352 }
1353
1354
1355 /**
1356  * Convert a URI to a UTF-8 String.
1357  *
1358  * @param uri uri to convert to a string
1359  * @return the UTF-8 string
1360  */
1361 char *
1362 GNUNET_FS_uri_to_string (const struct GNUNET_FS_Uri *uri)
1363 {
1364   if (uri == NULL)
1365     {
1366       GNUNET_break (0);
1367       return NULL;
1368     }
1369   switch (uri->type)
1370     {
1371     case ksk:
1372       return uri_ksk_to_string (uri);
1373     case sks:
1374       return uri_sks_to_string (uri);
1375     case chk:
1376       return uri_chk_to_string (uri);
1377     case loc:
1378       return uri_loc_to_string (uri);
1379     default:
1380       GNUNET_break (0);
1381       return NULL;
1382     }
1383 }
1384
1385
1386 #if 0
1387
1388 /**
1389  * Construct a location URI.
1390  *
1391  * @param baseURI content offered by the sender
1392  * @param sender identity of the peer with the content
1393  * @param expiration_time how long will the content be offered?
1394  * @param proto transport protocol to reach the peer
1395  * @param sas sender address size (for HELLO)
1396  * @param address sas bytes of address information
1397  * @param signer function to call for obtaining
1398  *        RSA signatures for "sender".
1399  * @return the location URI
1400  */
1401 struct GNUNET_ECRS_URI *
1402 GNUNET_ECRS_location_to_uri (const struct GNUNET_ECRS_URI *baseUri,
1403                              const GNUNET_RSA_PublicKey * sender,
1404                              GNUNET_Int32Time expirationTime,
1405                              GNUNET_ECRS_SignFunction signer,
1406                              void *signer_cls)
1407 {
1408   struct GNUNET_ECRS_URI *uri;
1409
1410   if (baseUri->type != chk)
1411     return NULL;
1412
1413   uri = GNUNET_malloc (sizeof (struct GNUNET_ECRS_URI));
1414   uri->type = loc;
1415   uri->data.loc.fi = baseUri->data.fi;
1416   uri->data.loc.peer = *sender;
1417   uri->data.loc.expirationTime = expirationTime;
1418   signer (signer_cls,
1419           sizeof (GNUNET_EC_FileIdentifier) +
1420           sizeof (GNUNET_PeerIdentity) +
1421           sizeof (GNUNET_Int32Time),
1422           &uri->data.loc.fi, &uri->data.loc.contentSignature);
1423   return uri;
1424 }
1425
1426 #endif
1427
1428 /* end of uri.c */