9097d9884b566533fa545da69579f7cfa4e5b837
[oweals/gnunet.git] / src / fs / fs_uri.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2003--2014 GNUnet e.V.
4
5      GNUnet is free software: you can redistribute it and/or modify it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your 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      Affero General Public License for more details.
14
15      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
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 fs identifiers (MODULE = "fs").
30  * <p>
31  *
32  * This module only parses URIs for the AFS module.  The FS 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  * </p>
41  *
42  * <ul><li>
43  *
44  * First, there are URIs that identify a file.  They have the format
45  * "gnunet://fs/chk/HEX1.HEX2.SIZE".  These URIs can be used to
46  * download the file.  The description, filename, mime-type and other
47  * meta-data is NOT part of the file-URI since a URI uniquely
48  * identifies a resource (and the contents of the file would be the
49  * same even if it had a different description).
50  *
51  * </li><li>
52  *
53  * The second category identifies entries in a namespace.  The format
54  * is "gnunet://fs/sks/NAMESPACE/IDENTIFIER" where the namespace
55  * should be given in HEX.  Applications may allow using a nickname
56  * for the namespace if the nickname is not ambiguous.  The identifier
57  * can be either an ASCII sequence or a HEX-encoding.  If the
58  * identifier is in ASCII but the format is ambiguous and could denote
59  * a HEX-string a "/" is appended to indicate ASCII encoding.
60  *
61  * </li> <li>
62  *
63  * The third category identifies ordinary searches.  The format is
64  * "gnunet://fs/ksk/KEYWORD[+KEYWORD]*".  Using the "+" syntax
65  * it is possible to encode searches with the boolean "AND" operator.
66  * "+" is used since it indicates a commutative 'and' operation and
67  * is unlikely to be used in a keyword by itself.
68  *
69  * </li><li>
70  *
71  * The last category identifies a datum on a specific machine.  The
72  * format is "gnunet://fs/loc/HEX1.HEX2.SIZE.PEER.SIG.EXPTIME".  PEER is
73  * the BinName of the public key of the peer storing the datum.  The
74  * signature (SIG) certifies that this peer has this content.
75  * HEX1, HEX2 and SIZE correspond to a 'chk' URI.
76  *
77  * </li></ul>
78  *
79  * The encoding for hexadecimal values is defined in the hashing.c
80  * module in the gnunetutil library and discussed there.
81  *
82  */
83 #include "platform.h"
84 #include "gnunet_fs_service.h"
85 #include "gnunet_signatures.h"
86 #include "fs_api.h"
87 #include <unitypes.h>
88 #include <unicase.h>
89 #include <uniconv.h>
90 #include <unistr.h>
91 #include <unistdio.h>
92
93
94
95 /**
96  * Get a unique key from a URI.  This is for putting URIs
97  * into HashMaps.  The key may change between FS implementations.
98  *
99  * @param uri uri to convert to a unique key
100  * @param key where to store the unique key
101  * @return #GNUNET_OK on success
102  */
103 int
104 GNUNET_FS_uri_to_key (const struct GNUNET_FS_Uri *uri,
105                       struct GNUNET_HashCode *key)
106 {
107   switch (uri->type)
108   {
109   case GNUNET_FS_URI_CHK:
110     *key = uri->data.chk.chk.query;
111     return GNUNET_OK;
112   case GNUNET_FS_URI_SKS:
113     GNUNET_CRYPTO_hash (uri->data.sks.identifier,
114                         strlen (uri->data.sks.identifier),
115                         key);
116     return GNUNET_OK;
117   case GNUNET_FS_URI_KSK:
118     if (uri->data.ksk.keywordCount > 0)
119     {
120       GNUNET_CRYPTO_hash (uri->data.ksk.keywords[0],
121                           strlen (uri->data.ksk.keywords[0]),
122                           key);
123       return GNUNET_OK;
124     }
125     else
126     {
127       memset (key, 0, sizeof (struct GNUNET_HashCode));
128       return GNUNET_SYSERR;
129     }
130     break;
131   case GNUNET_FS_URI_LOC:
132     GNUNET_CRYPTO_hash (&uri->data.loc.fi,
133                         sizeof (struct FileIdentifier) +
134                         sizeof (struct GNUNET_PeerIdentity),
135                         key);
136     return GNUNET_OK;
137   default:
138     memset (key, 0, sizeof (struct GNUNET_HashCode));
139     return GNUNET_SYSERR;
140   }
141 }
142
143
144 /**
145  * Convert keyword URI to a human readable format
146  * (i.e. the search query that was used in the first place)
147  *
148  * @param uri ksk uri to convert to a string
149  * @return string with the keywords
150  */
151 char *
152 GNUNET_FS_uri_ksk_to_string_fancy (const struct GNUNET_FS_Uri *uri)
153 {
154   size_t n;
155   char *ret;
156   unsigned int i;
157   const char *keyword;
158   char **keywords;
159   unsigned int keywordCount;
160
161   if ((NULL == uri) || (GNUNET_FS_URI_KSK != uri->type))
162   {
163     GNUNET_break (0);
164     return NULL;
165   }
166   keywords = uri->data.ksk.keywords;
167   keywordCount = uri->data.ksk.keywordCount;
168   n = keywordCount + 1;
169   for (i = 0; i < keywordCount; i++)
170   {
171     keyword = keywords[i];
172     n += strlen (keyword) - 1;
173     if (NULL != strstr (&keyword[1], " "))
174       n += 2;
175     if (keyword[0] == '+')
176       n++;
177   }
178   ret = GNUNET_malloc (n);
179   strcpy (ret, "");
180   for (i = 0; i < keywordCount; i++)
181   {
182     keyword = keywords[i];
183     if (NULL != strstr (&keyword[1], " "))
184     {
185       strcat (ret, "\"");
186       if (keyword[0] == '+')
187         strcat (ret, keyword);
188       else
189         strcat (ret, &keyword[1]);
190       strcat (ret, "\"");
191     }
192     else
193     {
194       if (keyword[0] == '+')
195         strcat (ret, keyword);
196       else
197         strcat (ret, &keyword[1]);
198     }
199     strcat (ret, " ");
200   }
201   return ret;
202 }
203
204
205 /**
206  * Given a keyword with %-encoding (and possibly quotes to protect
207  * spaces), return a copy of the keyword without %-encoding and
208  * without double-quotes (%22).  Also, add a space at the beginning
209  * if there is not a '+'.
210  *
211  * @param in string with %-encoding
212  * @param emsg where to store the parser error message (if any)
213  * @return decodded string with leading space (or preserved plus)
214  */
215 static char *
216 percent_decode_keyword (const char *in,
217                         char **emsg)
218 {
219   char *out;
220   char *ret;
221   unsigned int rpos;
222   unsigned int wpos;
223   unsigned int hx;
224
225   out = GNUNET_strdup (in);
226   rpos = 0;
227   wpos = 0;
228   while (out[rpos] != '\0')
229   {
230     if (out[rpos] == '%')
231     {
232       if (1 != SSCANF (&out[rpos + 1], "%2X", &hx))
233       {
234         GNUNET_free (out);
235         *emsg = GNUNET_strdup (_(/* xgettext:no-c-format */
236                                  "Malformed KSK URI (`%' must be followed by HEX number)"));
237         return NULL;
238       }
239       rpos += 3;
240       if (hx == '"')
241         continue;               /* skip double quote */
242       out[wpos++] = (char) hx;
243     }
244     else
245     {
246       out[wpos++] = out[rpos++];
247     }
248   }
249   out[wpos] = '\0';
250   if (out[0] == '+')
251   {
252     ret = GNUNET_strdup (out);
253   }
254   else
255   {
256     /* need to prefix with space */
257     ret = GNUNET_malloc (strlen (out) + 2);
258     strcpy (ret, " ");
259     strcat (ret, out);
260   }
261   GNUNET_free (out);
262   return ret;
263 }
264
265 #define GNUNET_FS_URI_KSK_PREFIX GNUNET_FS_URI_PREFIX GNUNET_FS_URI_KSK_INFIX
266
267 /**
268  * Parse a KSK URI.
269  *
270  * @param s an uri string
271  * @param emsg where to store the parser error message (if any)
272  * @return NULL on error, otherwise the KSK URI
273  */
274 static struct GNUNET_FS_Uri *
275 uri_ksk_parse (const char *s,
276                char **emsg)
277 {
278   struct GNUNET_FS_Uri *ret;
279   char **keywords;
280   unsigned int pos;
281   int max;
282   int iret;
283   int i;
284   size_t slen;
285   char *dup;
286   int saw_quote;
287
288   slen = strlen (s);
289   pos = strlen (GNUNET_FS_URI_KSK_PREFIX);
290   if ((slen <= pos) || (0 != strncmp (s, GNUNET_FS_URI_KSK_PREFIX, pos)))
291     return NULL;                /* not KSK URI */
292   if ((s[slen - 1] == '+') || (s[pos] == '+'))
293   {
294     *emsg =
295         GNUNET_strdup (_("Malformed KSK URI (must not begin or end with `+')"));
296     return NULL;
297   }
298   max = 1;
299   saw_quote = 0;
300   for (i = pos; i < slen; i++)
301   {
302     if ((s[i] == '%') && (&s[i] == strstr (&s[i], "%22")))
303     {
304       saw_quote = (saw_quote + 1) % 2;
305       i += 3;
306       continue;
307     }
308     if ((s[i] == '+') && (saw_quote == 0))
309     {
310       max++;
311       if (s[i - 1] == '+')
312       {
313         *emsg = GNUNET_strdup (_("Malformed KSK URI (`++' not allowed)"));
314         return NULL;
315       }
316     }
317   }
318   if (saw_quote == 1)
319   {
320     *emsg = GNUNET_strdup (_("Malformed KSK URI (quotes not balanced)"));
321     return NULL;
322   }
323   iret = max;
324   dup = GNUNET_strdup (s);
325   keywords = GNUNET_new_array (max,
326                                char *);
327   for (i = slen - 1; i >= (int) pos; i--)
328   {
329     if ((s[i] == '%') && (&s[i] == strstr (&s[i], "%22")))
330     {
331       saw_quote = (saw_quote + 1) % 2;
332       continue;
333     }
334     if ((dup[i] == '+') && (saw_quote == 0))
335     {
336       keywords[--max] = percent_decode_keyword (&dup[i + 1], emsg);
337       if (NULL == keywords[max])
338         goto CLEANUP;
339       dup[i] = '\0';
340     }
341   }
342   keywords[--max] = percent_decode_keyword (&dup[pos], emsg);
343   if (NULL == keywords[max])
344     goto CLEANUP;
345   GNUNET_assert (0 == max);
346   GNUNET_free (dup);
347   ret = GNUNET_new (struct GNUNET_FS_Uri);
348   ret->type = GNUNET_FS_URI_KSK;
349   ret->data.ksk.keywordCount = iret;
350   ret->data.ksk.keywords = keywords;
351   return ret;
352 CLEANUP:
353   for (i = 0; i < max; i++)
354     GNUNET_free_non_null (keywords[i]);
355   GNUNET_free (keywords);
356   GNUNET_free (dup);
357   return NULL;
358 }
359
360
361 #define GNUNET_FS_URI_SKS_PREFIX GNUNET_FS_URI_PREFIX GNUNET_FS_URI_SKS_INFIX
362
363 /**
364  * Parse an SKS URI.
365  *
366  * @param s an uri string
367  * @param emsg where to store the parser error message (if any)
368  * @return NULL on error, SKS URI otherwise
369  */
370 static struct GNUNET_FS_Uri *
371 uri_sks_parse (const char *s,
372                char **emsg)
373 {
374   struct GNUNET_FS_Uri *ret;
375   struct GNUNET_CRYPTO_EcdsaPublicKey ns;
376   size_t pos;
377   char *end;
378
379   pos = strlen (GNUNET_FS_URI_SKS_PREFIX);
380   if ((strlen (s) <= pos) || (0 != strncmp (s, GNUNET_FS_URI_SKS_PREFIX, pos)))
381     return NULL;                /* not an SKS URI */
382   end = strchr (&s[pos], '/');
383   if ( (NULL == end) ||
384        (GNUNET_OK !=
385         GNUNET_STRINGS_string_to_data (&s[pos],
386                                        end - &s[pos],
387                                        &ns,
388                                        sizeof (ns))) )
389   {
390     *emsg = GNUNET_strdup (_("Malformed SKS URI (wrong syntax)"));
391     return NULL; /* malformed */
392   }
393   end++; /* skip over '/' */
394   ret = GNUNET_new (struct GNUNET_FS_Uri);
395   ret->type = GNUNET_FS_URI_SKS;
396   ret->data.sks.ns = ns;
397   ret->data.sks.identifier = GNUNET_strdup (end);
398   return ret;
399 }
400
401 #define GNUNET_FS_URI_CHK_PREFIX GNUNET_FS_URI_PREFIX GNUNET_FS_URI_CHK_INFIX
402
403
404 /**
405  * Parse a CHK URI.
406  *
407  * @param s an uri string
408  * @param emsg where to store the parser error message (if any)
409  * @return NULL on error, CHK URI otherwise
410  */
411 static struct GNUNET_FS_Uri *
412 uri_chk_parse (const char *s,
413                char **emsg)
414 {
415   struct GNUNET_FS_Uri *ret;
416   struct FileIdentifier fi;
417   unsigned int pos;
418   unsigned long long flen;
419   size_t slen;
420   char h1[sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded)];
421   char h2[sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded)];
422
423   slen = strlen (s);
424   pos = strlen (GNUNET_FS_URI_CHK_PREFIX);
425   if ((slen < pos + 2 * sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) + 1) ||
426       (0 != strncmp (s, GNUNET_FS_URI_CHK_PREFIX, pos)))
427     return NULL;                /* not a CHK URI */
428   if ((s[pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) - 1] != '.') ||
429       (s[pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) * 2 - 1] != '.'))
430   {
431     *emsg = GNUNET_strdup (_("Malformed CHK URI (wrong syntax)"));
432     return NULL;
433   }
434   GNUNET_memcpy (h1, &s[pos], sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded));
435   h1[sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) - 1] = '\0';
436   GNUNET_memcpy (h2, &s[pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded)],
437           sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded));
438   h2[sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) - 1] = '\0';
439
440   if ((GNUNET_OK != GNUNET_CRYPTO_hash_from_string (h1, &fi.chk.key)) ||
441       (GNUNET_OK != GNUNET_CRYPTO_hash_from_string (h2, &fi.chk.query)) ||
442       (1 !=
443        SSCANF (&s[pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) * 2],
444                "%llu", &flen)))
445   {
446     *emsg = GNUNET_strdup (_("Malformed CHK URI (failed to decode CHK)"));
447     return NULL;
448   }
449   fi.file_length = GNUNET_htonll (flen);
450   ret = GNUNET_new (struct GNUNET_FS_Uri);
451   ret->type = GNUNET_FS_URI_CHK;
452   ret->data.chk = fi;
453   return ret;
454 }
455
456
457 GNUNET_NETWORK_STRUCT_BEGIN
458 /**
459  * Structure that defines how the contents of a location URI must be
460  * assembled in memory to create or verify the signature of a location
461  * URI.
462  */
463 struct LocUriAssembly
464 {
465   /**
466    * What is being signed (rest of this struct).
467    */
468   struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
469
470   /**
471    * Expiration time of the offer.
472    */
473   struct GNUNET_TIME_AbsoluteNBO exptime;
474
475   /**
476    * File being offered.
477    */
478   struct FileIdentifier fi;
479
480   /**
481    * Peer offering the file.
482    */
483   struct GNUNET_PeerIdentity peer;
484
485 };
486 GNUNET_NETWORK_STRUCT_END
487
488
489 #define GNUNET_FS_URI_LOC_PREFIX GNUNET_FS_URI_PREFIX GNUNET_FS_URI_LOC_INFIX
490
491 #define SIGNATURE_ASCII_LENGTH 103
492
493 /**
494  * Parse a LOC URI.
495  * Also verifies validity of the location URI.
496  *
497  * @param s an uri string
498  * @param emsg where to store the parser error message (if any)
499  * @return NULL on error, valid LOC URI otherwise
500  */
501 static struct GNUNET_FS_Uri *
502 uri_loc_parse (const char *s,
503                char **emsg)
504 {
505   struct GNUNET_FS_Uri *uri;
506   char h1[sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded)];
507   char h2[sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded)];
508   unsigned int pos;
509   unsigned int npos;
510   unsigned long long exptime;
511   unsigned long long flen;
512   struct GNUNET_TIME_Absolute et;
513   struct GNUNET_CRYPTO_EddsaSignature sig;
514   struct LocUriAssembly ass;
515   size_t slen;
516
517   slen = strlen (s);
518   pos = strlen (GNUNET_FS_URI_LOC_PREFIX);
519   if ((slen < pos + 2 * sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) + 1) ||
520       (0 != strncmp (s, GNUNET_FS_URI_LOC_PREFIX, pos)))
521     return NULL;                /* not a LOC URI */
522   if ((s[pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) - 1] != '.') ||
523       (s[pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) * 2 - 1] != '.'))
524   {
525     *emsg = GNUNET_strdup (_("LOC URI malformed (wrong syntax)"));
526     return NULL;
527   }
528   GNUNET_memcpy (h1, &s[pos], sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded));
529   h1[sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) - 1] = '\0';
530   GNUNET_memcpy (h2, &s[pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded)],
531           sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded));
532   h2[sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) - 1] = '\0';
533
534   if ((GNUNET_OK != GNUNET_CRYPTO_hash_from_string (h1, &ass.fi.chk.key)) ||
535       (GNUNET_OK != GNUNET_CRYPTO_hash_from_string (h2, &ass.fi.chk.query)) ||
536       (1 !=
537        SSCANF (&s[pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) * 2],
538                "%llu", &flen)))
539   {
540     *emsg = GNUNET_strdup (_("LOC URI malformed (no CHK)"));
541     return NULL;
542   }
543   ass.fi.file_length = GNUNET_htonll (flen);
544
545   npos = pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) * 2;
546   while ((s[npos] != '\0') && (s[npos] != '.'))
547     npos++;
548   if (s[npos] == '\0')
549   {
550     *emsg = GNUNET_strdup (_("LOC URI malformed (missing LOC)"));
551     goto ERR;
552   }
553   npos++;
554   if ( (strlen (&s[npos]) <= GNUNET_CRYPTO_PKEY_ASCII_LENGTH + 1) ||
555        ('.' != s[npos+GNUNET_CRYPTO_PKEY_ASCII_LENGTH]) )
556   {
557     *emsg =
558       GNUNET_strdup (_("LOC URI malformed (wrong syntax for public key)"));
559   }
560   if (GNUNET_OK !=
561       GNUNET_CRYPTO_eddsa_public_key_from_string (&s[npos],
562                                                   GNUNET_CRYPTO_PKEY_ASCII_LENGTH,
563                                                   &ass.peer.public_key))
564   {
565     *emsg =
566         GNUNET_strdup (_("LOC URI malformed (could not decode public key)"));
567     goto ERR;
568   }
569   npos += GNUNET_CRYPTO_PKEY_ASCII_LENGTH;
570   if (s[npos++] != '.')
571   {
572     *emsg = GNUNET_strdup (_("LOC URI malformed (could not find signature)"));
573     goto ERR;
574   }
575   if ( (strlen (&s[npos]) <= SIGNATURE_ASCII_LENGTH + 1) ||
576        ('.' != s[npos + SIGNATURE_ASCII_LENGTH]) )
577   {
578     *emsg = GNUNET_strdup (_("LOC URI malformed (wrong syntax for signature)"));
579     goto ERR;
580   }
581   if (GNUNET_OK !=
582       GNUNET_STRINGS_string_to_data (&s[npos],
583                                      SIGNATURE_ASCII_LENGTH,
584                                      &sig,
585                                      sizeof (struct GNUNET_CRYPTO_EddsaSignature)))
586   {
587     *emsg = GNUNET_strdup (_("LOC URI malformed (could not decode signature)"));
588     goto ERR;
589   }
590   npos += SIGNATURE_ASCII_LENGTH;
591   if (s[npos++] != '.')
592   {
593     *emsg = GNUNET_strdup (_("LOC URI malformed (wrong syntax for expiration time)"));
594     goto ERR;
595   }
596   if (1 != SSCANF (&s[npos], "%llu", &exptime))
597   {
598     *emsg =
599         GNUNET_strdup (_("LOC URI malformed (could not parse expiration time)"));
600     goto ERR;
601   }
602   ass.purpose.size = htonl (sizeof (struct LocUriAssembly));
603   ass.purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_PEER_PLACEMENT);
604   et.abs_value_us = exptime * 1000LL * 1000LL;
605   ass.exptime = GNUNET_TIME_absolute_hton (et);
606   if (GNUNET_OK !=
607       GNUNET_CRYPTO_eddsa_verify (GNUNET_SIGNATURE_PURPOSE_PEER_PLACEMENT,
608                                   &ass.purpose, &sig, &ass.peer.public_key))
609   {
610     *emsg =
611         GNUNET_strdup (_("LOC URI malformed (signature failed validation)"));
612     goto ERR;
613   }
614   uri = GNUNET_new (struct GNUNET_FS_Uri);
615   uri->type = GNUNET_FS_URI_LOC;
616   uri->data.loc.fi = ass.fi;
617   uri->data.loc.peer = ass.peer;
618   uri->data.loc.expirationTime = et;
619   uri->data.loc.contentSignature = sig;
620
621   return uri;
622 ERR:
623   return NULL;
624 }
625
626
627 /**
628  * Convert a UTF-8 String to a URI.
629  *
630  * @param uri string to parse
631  * @param emsg where to store the parser error message (if any)
632  * @return NULL on error
633  */
634 struct GNUNET_FS_Uri *
635 GNUNET_FS_uri_parse (const char *uri,
636                      char **emsg)
637 {
638   struct GNUNET_FS_Uri *ret;
639   char *msg;
640
641   if (NULL == uri)
642   {
643     GNUNET_break (0);
644     if (NULL != emsg)
645       *emsg = GNUNET_strdup (_("invalid argument"));
646     return NULL;
647   }
648   if (NULL == emsg)
649     emsg = &msg;
650   *emsg = NULL;
651   if ((NULL != (ret = uri_chk_parse (uri, emsg))) ||
652       (NULL != (ret = uri_ksk_parse (uri, emsg))) ||
653       (NULL != (ret = uri_sks_parse (uri, emsg))) ||
654       (NULL != (ret = uri_loc_parse (uri, emsg))))
655     return ret;
656   if (NULL == *emsg)
657     *emsg = GNUNET_strdup (_("Unrecognized URI type"));
658   if (emsg == &msg)
659     GNUNET_free (msg);
660   return NULL;
661 }
662
663
664 /**
665  * Free URI.
666  *
667  * @param uri uri to free
668  */
669 void
670 GNUNET_FS_uri_destroy (struct GNUNET_FS_Uri *uri)
671 {
672   unsigned int i;
673
674   switch (uri->type)
675   {
676   case GNUNET_FS_URI_KSK:
677     for (i = 0; i < uri->data.ksk.keywordCount; i++)
678       GNUNET_free (uri->data.ksk.keywords[i]);
679     GNUNET_array_grow (uri->data.ksk.keywords, uri->data.ksk.keywordCount, 0);
680     break;
681   case GNUNET_FS_URI_SKS:
682     GNUNET_free (uri->data.sks.identifier);
683     break;
684   case GNUNET_FS_URI_LOC:
685     break;
686   default:
687     /* do nothing */
688     break;
689   }
690   GNUNET_free (uri);
691 }
692
693
694 /**
695  * How many keywords are ANDed in this keyword URI?
696  *
697  * @param uri ksk uri to get the number of keywords from
698  * @return 0 if this is not a keyword URI
699  */
700 unsigned int
701 GNUNET_FS_uri_ksk_get_keyword_count (const struct GNUNET_FS_Uri *uri)
702 {
703   if (uri->type != GNUNET_FS_URI_KSK)
704     return 0;
705   return uri->data.ksk.keywordCount;
706 }
707
708
709 /**
710  * Iterate over all keywords in this keyword URI.
711  *
712  * @param uri ksk uri to get the keywords from
713  * @param iterator function to call on each keyword
714  * @param iterator_cls closure for iterator
715  * @return -1 if this is not a keyword URI, otherwise number of
716  *   keywords iterated over until iterator aborted
717  */
718 int
719 GNUNET_FS_uri_ksk_get_keywords (const struct GNUNET_FS_Uri *uri,
720                                 GNUNET_FS_KeywordIterator iterator,
721                                 void *iterator_cls)
722 {
723   unsigned int i;
724   char *keyword;
725
726   if (uri->type != GNUNET_FS_URI_KSK)
727     return -1;
728   if (NULL == iterator)
729     return uri->data.ksk.keywordCount;
730   for (i = 0; i < uri->data.ksk.keywordCount; i++)
731   {
732     keyword = uri->data.ksk.keywords[i];
733     /* first character of keyword indicates
734      * if it is mandatory or not */
735     if (GNUNET_OK != iterator (iterator_cls, &keyword[1], keyword[0] == '+'))
736       return i;
737   }
738   return i;
739 }
740
741
742 /**
743  * Add the given keyword to the set of keywords represented by the URI.
744  * Does nothing if the keyword is already present.
745  *
746  * @param uri ksk uri to modify
747  * @param keyword keyword to add
748  * @param is_mandatory is this keyword mandatory?
749  */
750 void
751 GNUNET_FS_uri_ksk_add_keyword (struct GNUNET_FS_Uri *uri,
752                                const char *keyword,
753                                int is_mandatory)
754 {
755   unsigned int i;
756   const char *old;
757   char *n;
758
759   GNUNET_assert (uri->type == GNUNET_FS_URI_KSK);
760   for (i = 0; i < uri->data.ksk.keywordCount; i++)
761   {
762     old = uri->data.ksk.keywords[i];
763     if (0 == strcmp (&old[1], keyword))
764       return;
765   }
766   GNUNET_asprintf (&n, is_mandatory ? "+%s" : " %s", keyword);
767   GNUNET_array_append (uri->data.ksk.keywords, uri->data.ksk.keywordCount, n);
768 }
769
770
771 /**
772  * Remove the given keyword from the set of keywords represented by the URI.
773  * Does nothing if the keyword is not present.
774  *
775  * @param uri ksk uri to modify
776  * @param keyword keyword to add
777  */
778 void
779 GNUNET_FS_uri_ksk_remove_keyword (struct GNUNET_FS_Uri *uri,
780                                   const char *keyword)
781 {
782   unsigned int i;
783   char *old;
784
785   GNUNET_assert (uri->type == GNUNET_FS_URI_KSK);
786   for (i = 0; i < uri->data.ksk.keywordCount; i++)
787   {
788     old = uri->data.ksk.keywords[i];
789     if (0 == strcmp (&old[1], keyword))
790     {
791       uri->data.ksk.keywords[i] =
792           uri->data.ksk.keywords[uri->data.ksk.keywordCount - 1];
793       GNUNET_array_grow (uri->data.ksk.keywords, uri->data.ksk.keywordCount,
794                          uri->data.ksk.keywordCount - 1);
795       GNUNET_free (old);
796       return;
797     }
798   }
799 }
800
801
802 /**
803  * Obtain the identity of the peer offering the data
804  *
805  * @param uri the location URI to inspect
806  * @param peer where to store the identify of the peer (presumably) offering the content
807  * @return #GNUNET_SYSERR if this is not a location URI, otherwise #GNUNET_OK
808  */
809 int
810 GNUNET_FS_uri_loc_get_peer_identity (const struct GNUNET_FS_Uri *uri,
811                                      struct GNUNET_PeerIdentity *peer)
812 {
813   if (uri->type != GNUNET_FS_URI_LOC)
814     return GNUNET_SYSERR;
815   *peer = uri->data.loc.peer;
816   return GNUNET_OK;
817 }
818
819
820 /**
821  * Obtain the expiration of the LOC URI.
822  *
823  * @param uri location URI to get the expiration from
824  * @return expiration time of the URI
825  */
826 struct GNUNET_TIME_Absolute
827 GNUNET_FS_uri_loc_get_expiration (const struct GNUNET_FS_Uri *uri)
828 {
829   GNUNET_assert (uri->type == GNUNET_FS_URI_LOC);
830   return uri->data.loc.expirationTime;
831 }
832
833
834 /**
835  * Obtain the URI of the content itself.
836  *
837  * @param uri location URI to get the content URI from
838  * @return NULL if argument is not a location URI
839  */
840 struct GNUNET_FS_Uri *
841 GNUNET_FS_uri_loc_get_uri (const struct GNUNET_FS_Uri *uri)
842 {
843   struct GNUNET_FS_Uri *ret;
844
845   if (uri->type != GNUNET_FS_URI_LOC)
846     return NULL;
847   ret = GNUNET_new (struct GNUNET_FS_Uri);
848   ret->type = GNUNET_FS_URI_CHK;
849   ret->data.chk = uri->data.loc.fi;
850   return ret;
851 }
852
853
854 /**
855  * Construct a location URI (this peer will be used for the location).
856  * This function should only be called from within gnunet-service-fs,
857  * as it requires the peer's private key which is generally unavailable
858  * to processes directly under the user's control.  However, for
859  * testing and as it logically fits under URIs, it is in this API.
860  *
861  * @param base_uri content offered by the sender
862  * @param sign_key private key of the peer
863  * @param expiration_time how long will the content be offered?
864  * @return the location URI, NULL on error
865  */
866 struct GNUNET_FS_Uri *
867 GNUNET_FS_uri_loc_create (const struct GNUNET_FS_Uri *base_uri,
868                           const struct GNUNET_CRYPTO_EddsaPrivateKey *sign_key,
869                           struct GNUNET_TIME_Absolute expiration_time)
870 {
871   struct GNUNET_FS_Uri *uri;
872   struct GNUNET_CRYPTO_EddsaPublicKey my_public_key;
873   struct LocUriAssembly ass;
874   struct GNUNET_TIME_Absolute et;
875
876   if (GNUNET_FS_URI_CHK != base_uri->type)
877     return NULL;
878   /* we round expiration time to full seconds for SKS URIs */
879   et.abs_value_us = (expiration_time.abs_value_us / 1000000LL) * 1000000LL;
880   GNUNET_CRYPTO_eddsa_key_get_public (sign_key,
881                                       &my_public_key);
882   ass.purpose.size = htonl (sizeof (struct LocUriAssembly));
883   ass.purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_PEER_PLACEMENT);
884   ass.exptime = GNUNET_TIME_absolute_hton (et);
885   ass.fi = base_uri->data.chk;
886   ass.peer.public_key = my_public_key;
887   uri = GNUNET_new (struct GNUNET_FS_Uri);
888   uri->type = GNUNET_FS_URI_LOC;
889   uri->data.loc.fi = base_uri->data.chk;
890   uri->data.loc.expirationTime = et;
891   uri->data.loc.peer.public_key = my_public_key;
892   GNUNET_assert (GNUNET_OK ==
893                  GNUNET_CRYPTO_eddsa_sign (sign_key,
894                                            &ass.purpose,
895                                            &uri->data.loc.contentSignature));
896   return uri;
897 }
898
899
900 /**
901  * Create an SKS URI from a namespace ID and an identifier.
902  *
903  * @param ns namespace ID
904  * @param id identifier
905  * @return an FS URI for the given namespace and identifier
906  */
907 struct GNUNET_FS_Uri *
908 GNUNET_FS_uri_sks_create (const struct GNUNET_CRYPTO_EcdsaPublicKey *ns,
909                           const char *id)
910 {
911   struct GNUNET_FS_Uri *ns_uri;
912
913   ns_uri = GNUNET_new (struct GNUNET_FS_Uri);
914   ns_uri->type = GNUNET_FS_URI_SKS;
915   ns_uri->data.sks.ns = *ns;
916   ns_uri->data.sks.identifier = GNUNET_strdup (id);
917   return ns_uri;
918 }
919
920
921 /**
922  * Merge the sets of keywords from two KSK URIs.
923  * (useful for merging the canonicalized keywords with
924  * the original keywords for sharing).
925  *
926  * @param u1 first uri
927  * @param u2 second uri
928  * @return merged URI, NULL on error
929  */
930 struct GNUNET_FS_Uri *
931 GNUNET_FS_uri_ksk_merge (const struct GNUNET_FS_Uri *u1,
932                          const struct GNUNET_FS_Uri *u2)
933 {
934   struct GNUNET_FS_Uri *ret;
935   unsigned int kc;
936   unsigned int i;
937   unsigned int j;
938   int found;
939   const char *kp;
940   char **kl;
941
942   if ((u1 == NULL) && (u2 == NULL))
943     return NULL;
944   if (u1 == NULL)
945     return GNUNET_FS_uri_dup (u2);
946   if (u2 == NULL)
947     return GNUNET_FS_uri_dup (u1);
948   if ((u1->type != GNUNET_FS_URI_KSK) || (u2->type != GNUNET_FS_URI_KSK))
949   {
950     GNUNET_break (0);
951     return NULL;
952   }
953   kc = u1->data.ksk.keywordCount;
954   kl = GNUNET_new_array (kc + u2->data.ksk.keywordCount,
955                          char *);
956   for (i = 0; i < u1->data.ksk.keywordCount; i++)
957     kl[i] = GNUNET_strdup (u1->data.ksk.keywords[i]);
958   for (i = 0; i < u2->data.ksk.keywordCount; i++)
959   {
960     kp = u2->data.ksk.keywords[i];
961     found = 0;
962     for (j = 0; j < u1->data.ksk.keywordCount; j++)
963       if (0 == strcmp (kp + 1, kl[j] + 1))
964       {
965         found = 1;
966         if (kp[0] == '+')
967           kl[j][0] = '+';
968         break;
969       }
970     if (0 == found)
971       kl[kc++] = GNUNET_strdup (kp);
972   }
973   ret = GNUNET_new (struct GNUNET_FS_Uri);
974   ret->type = GNUNET_FS_URI_KSK;
975   ret->data.ksk.keywordCount = kc;
976   ret->data.ksk.keywords = kl;
977   return ret;
978 }
979
980
981 /**
982  * Duplicate URI.
983  *
984  * @param uri the URI to duplicate
985  * @return copy of the URI
986  */
987 struct GNUNET_FS_Uri *
988 GNUNET_FS_uri_dup (const struct GNUNET_FS_Uri *uri)
989 {
990   struct GNUNET_FS_Uri *ret;
991   unsigned int i;
992
993   if (uri == NULL)
994     return NULL;
995   ret = GNUNET_new (struct GNUNET_FS_Uri);
996   GNUNET_memcpy (ret, uri, sizeof (struct GNUNET_FS_Uri));
997   switch (ret->type)
998   {
999   case GNUNET_FS_URI_KSK:
1000     if (ret->data.ksk.keywordCount >=
1001         GNUNET_MAX_MALLOC_CHECKED / sizeof (char *))
1002     {
1003       GNUNET_break (0);
1004       GNUNET_free (ret);
1005       return NULL;
1006     }
1007     if (ret->data.ksk.keywordCount > 0)
1008     {
1009       ret->data.ksk.keywords
1010         = GNUNET_new_array (ret->data.ksk.keywordCount,
1011                             char *);
1012       for (i = 0; i < ret->data.ksk.keywordCount; i++)
1013         ret->data.ksk.keywords[i] = GNUNET_strdup (uri->data.ksk.keywords[i]);
1014     }
1015     else
1016       ret->data.ksk.keywords = NULL;    /* just to be sure */
1017     break;
1018   case GNUNET_FS_URI_SKS:
1019     ret->data.sks.identifier = GNUNET_strdup (uri->data.sks.identifier);
1020     break;
1021   case GNUNET_FS_URI_LOC:
1022     break;
1023   default:
1024     break;
1025   }
1026   return ret;
1027 }
1028
1029
1030 /**
1031  * Create an FS URI from a single user-supplied string of keywords.
1032  * The string is broken up at spaces into individual keywords.
1033  * Keywords that start with "+" are mandatory.  Double-quotes can
1034  * be used to prevent breaking up strings at spaces (and also
1035  * to specify non-mandatory keywords starting with "+").
1036  *
1037  * Keywords must contain a balanced number of double quotes and
1038  * double quotes can not be used in the actual keywords (for
1039  * example, the string '""foo bar""' will be turned into two
1040  * "OR"ed keywords 'foo' and 'bar', not into '"foo bar"'.
1041  *
1042  * @param keywords the keyword string
1043  * @param emsg where to store an error message
1044  * @return an FS URI for the given keywords, NULL
1045  *  if keywords is not legal (i.e. empty).
1046  */
1047 struct GNUNET_FS_Uri *
1048 GNUNET_FS_uri_ksk_create (const char *keywords,
1049                           char **emsg)
1050 {
1051   char **keywordarr;
1052   unsigned int num_Words;
1053   int inWord;
1054   char *pos;
1055   struct GNUNET_FS_Uri *uri;
1056   char *searchString;
1057   int saw_quote;
1058
1059   if (keywords == NULL)
1060   {
1061     *emsg = GNUNET_strdup (_("No keywords specified!\n"));
1062     GNUNET_break (0);
1063     return NULL;
1064   }
1065   searchString = GNUNET_strdup (keywords);
1066   num_Words = 0;
1067   inWord = 0;
1068   saw_quote = 0;
1069   pos = searchString;
1070   while ('\0' != *pos)
1071   {
1072     if ((saw_quote == 0) && (isspace ((unsigned char) *pos)))
1073     {
1074       inWord = 0;
1075     }
1076     else if (0 == inWord)
1077     {
1078       inWord = 1;
1079       ++num_Words;
1080     }
1081     if ('"' == *pos)
1082       saw_quote = (saw_quote + 1) % 2;
1083     pos++;
1084   }
1085   if (num_Words == 0)
1086   {
1087     GNUNET_free (searchString);
1088     *emsg = GNUNET_strdup (_("No keywords specified!\n"));
1089     return NULL;
1090   }
1091   if (saw_quote != 0)
1092   {
1093     GNUNET_free (searchString);
1094     *emsg = GNUNET_strdup (_("Number of double-quotes not balanced!\n"));
1095     return NULL;
1096   }
1097   keywordarr = GNUNET_new_array (num_Words,
1098                                  char *);
1099   num_Words = 0;
1100   inWord = 0;
1101   pos = searchString;
1102   while ('\0' != *pos)
1103   {
1104     if ((saw_quote == 0) && (isspace ((unsigned char) *pos)))
1105     {
1106       inWord = 0;
1107       *pos = '\0';
1108     }
1109     else if (0 == inWord)
1110     {
1111       keywordarr[num_Words] = pos;
1112       inWord = 1;
1113       ++num_Words;
1114     }
1115     if ('"' == *pos)
1116       saw_quote = (saw_quote + 1) % 2;
1117     pos++;
1118   }
1119   uri =
1120       GNUNET_FS_uri_ksk_create_from_args (num_Words,
1121                                           (const char **) keywordarr);
1122   GNUNET_free (keywordarr);
1123   GNUNET_free (searchString);
1124   return uri;
1125 }
1126
1127
1128 /**
1129  * Create an FS URI from a user-supplied command line of keywords.
1130  * Arguments should start with "+" to indicate mandatory
1131  * keywords.
1132  *
1133  * @param argc number of keywords
1134  * @param argv keywords (double quotes are not required for
1135  *             keywords containing spaces; however, double
1136  *             quotes are required for keywords starting with
1137  *             "+"); there is no mechanism for having double
1138  *             quotes in the actual keywords (if the user
1139  *             did specifically specify double quotes, the
1140  *             caller should convert each double quote
1141  *             into two single quotes).
1142  * @return an FS URI for the given keywords, NULL
1143  *  if keywords is not legal (i.e. empty).
1144  */
1145 struct GNUNET_FS_Uri *
1146 GNUNET_FS_uri_ksk_create_from_args (unsigned int argc,
1147                                     const char **argv)
1148 {
1149   unsigned int i;
1150   struct GNUNET_FS_Uri *uri;
1151   const char *keyword;
1152   char *val;
1153   const char *r;
1154   char *w;
1155   char *emsg;
1156
1157   if (argc == 0)
1158     return NULL;
1159   /* allow URI to be given as one and only keyword and
1160    * handle accordingly */
1161   emsg = NULL;
1162   if ((argc == 1) && (strlen (argv[0]) > strlen (GNUNET_FS_URI_PREFIX)) &&
1163       (0 ==
1164        strncmp (argv[0], GNUNET_FS_URI_PREFIX, strlen (GNUNET_FS_URI_PREFIX)))
1165       && (NULL != (uri = GNUNET_FS_uri_parse (argv[0], &emsg))))
1166     return uri;
1167   GNUNET_free_non_null (emsg);
1168   uri = GNUNET_new (struct GNUNET_FS_Uri);
1169   uri->type = GNUNET_FS_URI_KSK;
1170   uri->data.ksk.keywordCount = argc;
1171   uri->data.ksk.keywords = GNUNET_new_array (argc,
1172                                              char *);
1173   for (i = 0; i < argc; i++)
1174   {
1175     keyword = argv[i];
1176     if (keyword[0] == '+')
1177       val = GNUNET_strdup (keyword);
1178     else
1179       GNUNET_asprintf (&val, " %s", keyword);
1180     r = val;
1181     w = val;
1182     while ('\0' != *r)
1183     {
1184       if ('"' == *r)
1185         r++;
1186       else
1187         *(w++) = *(r++);
1188     }
1189     *w = '\0';
1190     uri->data.ksk.keywords[i] = val;
1191   }
1192   return uri;
1193 }
1194
1195
1196 /**
1197  * Test if two URIs are equal.
1198  *
1199  * @param u1 one of the URIs
1200  * @param u2 the other URI
1201  * @return #GNUNET_YES if the URIs are equal
1202  */
1203 int
1204 GNUNET_FS_uri_test_equal (const struct GNUNET_FS_Uri *u1,
1205                           const struct GNUNET_FS_Uri *u2)
1206 {
1207   int ret;
1208   unsigned int i;
1209   unsigned int j;
1210
1211   GNUNET_assert (u1 != NULL);
1212   GNUNET_assert (u2 != NULL);
1213   if (u1->type != u2->type)
1214     return GNUNET_NO;
1215   switch (u1->type)
1216   {
1217   case GNUNET_FS_URI_CHK:
1218     if (0 ==
1219         memcmp (&u1->data.chk, &u2->data.chk, sizeof (struct FileIdentifier)))
1220       return GNUNET_YES;
1221     return GNUNET_NO;
1222   case GNUNET_FS_URI_SKS:
1223     if ((0 ==
1224          memcmp (&u1->data.sks.ns, &u2->data.sks.ns,
1225                  sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey))) &&
1226         (0 == strcmp (u1->data.sks.identifier, u2->data.sks.identifier)))
1227
1228       return GNUNET_YES;
1229     return GNUNET_NO;
1230   case GNUNET_FS_URI_KSK:
1231     if (u1->data.ksk.keywordCount != u2->data.ksk.keywordCount)
1232       return GNUNET_NO;
1233     for (i = 0; i < u1->data.ksk.keywordCount; i++)
1234     {
1235       ret = GNUNET_NO;
1236       for (j = 0; j < u2->data.ksk.keywordCount; j++)
1237       {
1238         if (0 == strcmp (u1->data.ksk.keywords[i], u2->data.ksk.keywords[j]))
1239         {
1240           ret = GNUNET_YES;
1241           break;
1242         }
1243       }
1244       if (ret == GNUNET_NO)
1245         return GNUNET_NO;
1246     }
1247     return GNUNET_YES;
1248   case GNUNET_FS_URI_LOC:
1249     if (memcmp
1250         (&u1->data.loc, &u2->data.loc,
1251          sizeof (struct FileIdentifier) +
1252          sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) +
1253          sizeof (struct GNUNET_TIME_Absolute) + sizeof (unsigned short) +
1254          sizeof (unsigned short)) != 0)
1255       return GNUNET_NO;
1256     return GNUNET_YES;
1257   default:
1258     return GNUNET_NO;
1259   }
1260 }
1261
1262
1263 /**
1264  * Is this a namespace URI?
1265  *
1266  * @param uri the uri to check
1267  * @return #GNUNET_YES if this is an SKS uri
1268  */
1269 int
1270 GNUNET_FS_uri_test_sks (const struct GNUNET_FS_Uri *uri)
1271 {
1272   return uri->type == GNUNET_FS_URI_SKS;
1273 }
1274
1275
1276 /**
1277  * Get the ID of a namespace from the given
1278  * namespace URI.
1279  *
1280  * @param uri the uri to get the namespace ID from
1281  * @param pseudonym where to store the ID of the namespace
1282  * @return #GNUNET_OK on success
1283  */
1284 int
1285 GNUNET_FS_uri_sks_get_namespace (const struct GNUNET_FS_Uri *uri,
1286                                  struct GNUNET_CRYPTO_EcdsaPublicKey *pseudonym)
1287 {
1288   if (!GNUNET_FS_uri_test_sks (uri))
1289   {
1290     GNUNET_break (0);
1291     return GNUNET_SYSERR;
1292   }
1293   *pseudonym = uri->data.sks.ns;
1294   return GNUNET_OK;
1295 }
1296
1297
1298 /**
1299  * Get the content identifier of an SKS URI.
1300  *
1301  * @param uri the sks uri
1302  * @return NULL on error (not a valid SKS URI)
1303  */
1304 char *
1305 GNUNET_FS_uri_sks_get_content_id (const struct GNUNET_FS_Uri *uri)
1306 {
1307   if (!GNUNET_FS_uri_test_sks (uri))
1308   {
1309     GNUNET_break (0);
1310     return NULL;
1311   }
1312   return GNUNET_strdup (uri->data.sks.identifier);
1313 }
1314
1315
1316 /**
1317  * Is this a keyword URI?
1318  *
1319  * @param uri the uri
1320  * @return #GNUNET_YES if this is a KSK uri
1321  */
1322 int
1323 GNUNET_FS_uri_test_ksk (const struct GNUNET_FS_Uri *uri)
1324 {
1325 #if EXTRA_CHECKS
1326   unsigned int i;
1327
1328   if (uri->type == GNUNET_FS_URI_KSK)
1329   {
1330     for (i=0;i < uri->data.ksk.keywordCount; i++)
1331       GNUNET_assert (uri->data.ksk.keywords[i] != NULL);
1332   }
1333 #endif
1334   return uri->type == GNUNET_FS_URI_KSK;
1335 }
1336
1337
1338 /**
1339  * Is this a file (or directory) URI?
1340  *
1341  * @param uri the uri to check
1342  * @return #GNUNET_YES if this is a CHK uri
1343  */
1344 int
1345 GNUNET_FS_uri_test_chk (const struct GNUNET_FS_Uri *uri)
1346 {
1347   return uri->type == GNUNET_FS_URI_CHK;
1348 }
1349
1350
1351 /**
1352  * What is the size of the file that this URI
1353  * refers to?
1354  *
1355  * @param uri the CHK URI to inspect
1356  * @return size of the file as specified in the CHK URI
1357  */
1358 uint64_t
1359 GNUNET_FS_uri_chk_get_file_size (const struct GNUNET_FS_Uri * uri)
1360 {
1361   switch (uri->type)
1362   {
1363   case GNUNET_FS_URI_CHK:
1364     return GNUNET_ntohll (uri->data.chk.file_length);
1365   case GNUNET_FS_URI_LOC:
1366     return GNUNET_ntohll (uri->data.loc.fi.file_length);
1367   default:
1368     GNUNET_assert (0);
1369   }
1370   return 0;                     /* unreachable */
1371 }
1372
1373
1374 /**
1375  * Is this a location URI?
1376  *
1377  * @param uri the uri to check
1378  * @return #GNUNET_YES if this is a LOC uri
1379  */
1380 int
1381 GNUNET_FS_uri_test_loc (const struct GNUNET_FS_Uri *uri)
1382 {
1383   return uri->type == GNUNET_FS_URI_LOC;
1384 }
1385
1386
1387 /**
1388  * Add a keyword as non-mandatory (with ' '-prefix) to the
1389  * given keyword list at offset 'index'.  The array is
1390  * guaranteed to be long enough.
1391  *
1392  * @param s keyword to add
1393  * @param array array to add the keyword to
1394  * @param index offset where to add the keyword
1395  */
1396 static void
1397 insert_non_mandatory_keyword (const char *s,
1398                               char **array,
1399                               int index)
1400 {
1401   char *nkword;
1402
1403   GNUNET_asprintf (&nkword,
1404                    " %s", /* space to mark as 'non mandatory' */
1405                    s);
1406   array[index] = nkword;
1407 }
1408
1409
1410 /**
1411  * Test if the given keyword @a s is already present in the
1412  * given array, ignoring the '+'-mandatory prefix in the array.
1413  *
1414  * @param s keyword to test
1415  * @param array keywords to test against, with ' ' or '+' prefix to ignore
1416  * @param array_length length of the @a array
1417  * @return #GNUNET_YES if the keyword exists, #GNUNET_NO if not
1418  */
1419 static int
1420 find_duplicate (const char *s,
1421                 const char **array,
1422                 int array_length)
1423 {
1424   int j;
1425
1426   for (j = array_length - 1; j >= 0; j--)
1427     if (0 == strcmp (&array[j][1], s))
1428       return GNUNET_YES;
1429   return GNUNET_NO;
1430 }
1431
1432
1433 /**
1434  * FIXME: comment
1435  */
1436 static char *
1437 normalize_metadata (enum EXTRACTOR_MetaFormat format,
1438                     const char *data,
1439                     size_t data_len)
1440 {
1441   uint8_t *free_str = NULL;
1442   uint8_t *str_to_normalize = (uint8_t *) data;
1443   uint8_t *normalized;
1444   size_t r_len;
1445   if (str_to_normalize == NULL)
1446     return NULL;
1447   /* Don't trust libextractor */
1448   if (format == EXTRACTOR_METAFORMAT_UTF8)
1449   {
1450     free_str = (uint8_t *) u8_check ((const uint8_t *) data, data_len);
1451     if (free_str == NULL)
1452       free_str = NULL;
1453     else
1454       format = EXTRACTOR_METAFORMAT_C_STRING;
1455   }
1456   if (format == EXTRACTOR_METAFORMAT_C_STRING)
1457   {
1458     free_str = u8_strconv_from_encoding (data, locale_charset (), iconveh_escape_sequence);
1459     if (free_str == NULL)
1460       return NULL;
1461   }
1462
1463   normalized = u8_tolower (str_to_normalize, strlen ((char *) str_to_normalize), NULL, UNINORM_NFD, NULL, &r_len);
1464   /* free_str is allocated by libunistring internally, use free() */
1465   if (free_str != NULL)
1466     free (free_str);
1467   if (normalized != NULL)
1468   {
1469     /* u8_tolower allocates a non-NULL-terminated string! */
1470     free_str = GNUNET_malloc (r_len + 1);
1471     GNUNET_memcpy (free_str, normalized, r_len);
1472     free_str[r_len] = '\0';
1473     free (normalized);
1474     normalized = free_str;
1475   }
1476   return (char *) normalized;
1477 }
1478
1479
1480 /**
1481  * Counts the number of UTF-8 characters (not bytes) in the string,
1482  * returns that count.
1483  */
1484 static size_t
1485 u8_strcount (const uint8_t *s)
1486 {
1487   size_t count;
1488   ucs4_t c;
1489   GNUNET_assert (s != NULL);
1490   if (s[0] == 0)
1491     return 0;
1492   for (count = 0; s != NULL; count++)
1493     s = u8_next (&c, s);
1494   return count - 1;
1495 }
1496
1497
1498 /**
1499  * Break the filename up by matching [], () and {} pairs to make
1500  * keywords. In case of nesting parentheses only the inner pair counts.
1501  * You can't escape parentheses to scan something like "[blah\{foo]" to
1502  * make a "blah{foo" keyword, this function is only a heuristic!
1503  *
1504  * @param s string to break down.
1505  * @param array array to fill with enclosed tokens. If NULL, then tokens
1506  *        are only counted.
1507  * @param index index at which to start filling the array (entries prior
1508  *        to it are used to check for duplicates). ignored if @a array == NULL.
1509  * @return number of tokens counted (including duplicates), or number of
1510  *         tokens extracted (excluding duplicates). 0 if there are no
1511  *         matching parens in the string (when counting), or when all tokens
1512  *         were duplicates (when extracting).
1513  */
1514 static int
1515 get_keywords_from_parens (const char *s,
1516                           char **array,
1517                           int index)
1518 {
1519   int count = 0;
1520   char *open_paren;
1521   char *close_paren;
1522   char *ss;
1523   char tmp;
1524
1525   if (NULL == s)
1526     return 0;
1527   ss = GNUNET_strdup (s);
1528   open_paren = ss - 1;
1529   while (NULL != (open_paren = strpbrk (open_paren + 1, "[{(")))
1530   {
1531     int match = 0;
1532
1533     close_paren = strpbrk (open_paren + 1, "]})");
1534     if (NULL == close_paren)
1535       continue;
1536     switch (open_paren[0])
1537     {
1538     case '[':
1539       if (']' == close_paren[0])
1540         match = 1;
1541       break;
1542     case '{':
1543       if ('}' == close_paren[0])
1544         match = 1;
1545       break;
1546     case '(':
1547       if (')' == close_paren[0])
1548         match = 1;
1549       break;
1550     default:
1551       break;
1552     }
1553     if (match && (close_paren - open_paren > 1))
1554     {
1555       tmp = close_paren[0];
1556       close_paren[0] = '\0';
1557       /* Keywords must be at least 3 characters long */
1558       if (u8_strcount ((const uint8_t *) &open_paren[1]) <= 2)
1559       {
1560         close_paren[0] = tmp;
1561         continue;
1562       }
1563       if (NULL != array)
1564       {
1565         char *normalized;
1566         if (GNUNET_NO == find_duplicate ((const char *) &open_paren[1],
1567             (const char **) array, index + count))
1568         {
1569           insert_non_mandatory_keyword ((const char *) &open_paren[1], array,
1570                                         index + count);
1571           count++;
1572         }
1573         normalized = normalize_metadata (EXTRACTOR_METAFORMAT_UTF8,
1574             &open_paren[1], close_paren - &open_paren[1]);
1575         if (normalized != NULL)
1576         {
1577           if (GNUNET_NO == find_duplicate ((const char *) normalized,
1578               (const char **) array, index + count))
1579           {
1580             insert_non_mandatory_keyword ((const char *) normalized, array,
1581                                           index + count);
1582             count++;
1583           }
1584           GNUNET_free (normalized);
1585         }
1586       }
1587       else
1588         count++;
1589       close_paren[0] = tmp;
1590     }
1591   }
1592   GNUNET_free (ss);
1593   return count;
1594 }
1595
1596
1597 /**
1598  * Where to break up keywords
1599  */
1600 #define TOKENS "_. /-!?#&+@\"\'\\;:,()[]{}$<>|"
1601
1602 /**
1603  * Break the filename up by TOKENS to make
1604  * keywords.
1605  *
1606  * @param s string to break down.
1607  * @param array array to fill with tokens. If NULL, then tokens are only
1608  *        counted.
1609  * @param index index at which to start filling the array (entries prior
1610  *        to it are used to check for duplicates). ignored if @a array == NULL.
1611  * @return number of tokens (>1) counted (including duplicates), or number of
1612  *         tokens extracted (excluding duplicates). 0 if there are no
1613  *         separators in the string (when counting), or when all tokens were
1614  *         duplicates (when extracting).
1615  */
1616 static int
1617 get_keywords_from_tokens (const char *s,
1618                           char **array,
1619                           int index)
1620 {
1621   char *p;
1622   char *ss;
1623   int seps = 0;
1624
1625   ss = GNUNET_strdup (s);
1626   for (p = strtok (ss, TOKENS); p != NULL; p = strtok (NULL, TOKENS))
1627   {
1628     /* Keywords must be at least 3 characters long */
1629     if (u8_strcount ((const uint8_t *) p) <= 2)
1630       continue;
1631     if (NULL != array)
1632     {
1633       char *normalized;
1634       if (GNUNET_NO == find_duplicate (p, (const char **) array, index + seps))
1635       {
1636         insert_non_mandatory_keyword (p, array,
1637                                       index + seps);
1638         seps++;
1639       }
1640       normalized = normalize_metadata (EXTRACTOR_METAFORMAT_UTF8,
1641           p, strlen (p));
1642       if (normalized != NULL)
1643       {
1644         if (GNUNET_NO == find_duplicate ((const char *) normalized,
1645             (const char **) array, index + seps))
1646         {
1647           insert_non_mandatory_keyword ((const char *) normalized, array,
1648                                   index + seps);
1649           seps++;
1650         }
1651         GNUNET_free (normalized);
1652       }
1653     }
1654     else
1655       seps++;
1656   }
1657   GNUNET_free (ss);
1658   return seps;
1659 }
1660 #undef TOKENS
1661
1662
1663 /**
1664  * Function called on each value in the meta data.
1665  * Adds it to the URI.
1666  *
1667  * @param cls URI to update
1668  * @param plugin_name name of the plugin that produced this value;
1669  *        special values can be used (i.e. '&lt;zlib&gt;' for zlib being
1670  *        used in the main libextractor library and yielding
1671  *        meta data).
1672  * @param type libextractor-type describing the meta data
1673  * @param format basic format information about data
1674  * @param data_mime_type mime-type of data (not of the original file);
1675  *        can be NULL (if mime-type is not known)
1676  * @param data actual meta-data found
1677  * @param data_len number of bytes in @a data
1678  * @return 0 (always)
1679  */
1680 static int
1681 gather_uri_data (void *cls, const char *plugin_name,
1682                  enum EXTRACTOR_MetaType type,
1683                  enum EXTRACTOR_MetaFormat format,
1684                  const char *data_mime_type,
1685                  const char *data,
1686                  size_t data_len)
1687 {
1688   struct GNUNET_FS_Uri *uri = cls;
1689   char *normalized_data;
1690   const char *sep;
1691
1692   if ((format != EXTRACTOR_METAFORMAT_UTF8) &&
1693       (format != EXTRACTOR_METAFORMAT_C_STRING))
1694     return 0;
1695   /* Keywords must be at least 3 characters long
1696    * If given non-utf8 string it will, most likely, find it to be invalid,
1697    * and will return the length of its valid part, skipping the keyword.
1698    * If it does - fix the extractor, not this check!
1699    */
1700   if (u8_strcount ((const uint8_t *) data) <= 2)
1701     return 0;
1702   if ( (EXTRACTOR_METATYPE_MIMETYPE == type) &&
1703        (NULL != (sep = memchr (data, '/', data_len))) &&
1704        (sep != data) )
1705   {
1706     char *xtra;
1707
1708     GNUNET_asprintf (&xtra,
1709                      "mimetype:%.*s",
1710                      (int) (sep - data),
1711                      data);
1712     if (! find_duplicate (xtra,
1713                           (const char **) uri->data.ksk.keywords,
1714                           uri->data.ksk.keywordCount))
1715     {
1716       insert_non_mandatory_keyword (xtra,
1717                                     uri->data.ksk.keywords,
1718                                     uri->data.ksk.keywordCount);
1719       uri->data.ksk.keywordCount++;
1720     }
1721     GNUNET_free (xtra);
1722   }
1723
1724   normalized_data = normalize_metadata (format, data, data_len);
1725   if (! find_duplicate (data,
1726                         (const char **) uri->data.ksk.keywords,
1727                         uri->data.ksk.keywordCount))
1728   {
1729     insert_non_mandatory_keyword (data,
1730                                   uri->data.ksk.keywords, uri->data.ksk.keywordCount);
1731     uri->data.ksk.keywordCount++;
1732   }
1733   if (NULL != normalized_data)
1734     {
1735     if (! find_duplicate (normalized_data,
1736                           (const char **) uri->data.ksk.keywords,
1737                           uri->data.ksk.keywordCount))
1738     {
1739       insert_non_mandatory_keyword (normalized_data,
1740                                     uri->data.ksk.keywords, uri->data.ksk.keywordCount);
1741       uri->data.ksk.keywordCount++;
1742     }
1743     GNUNET_free (normalized_data);
1744   }
1745   return 0;
1746 }
1747
1748
1749 /**
1750  * Construct a keyword-URI from meta-data (take all entries
1751  * in the meta-data and construct one large keyword URI
1752  * that lists all keywords that can be found in the meta-data).
1753  *
1754  * @param md metadata to use
1755  * @return NULL on error, otherwise a KSK URI
1756  */
1757 struct GNUNET_FS_Uri *
1758 GNUNET_FS_uri_ksk_create_from_meta_data (const struct GNUNET_CONTAINER_MetaData *md)
1759 {
1760   struct GNUNET_FS_Uri *ret;
1761   char *filename;
1762   char *full_name = NULL;
1763   char *ss;
1764   int ent;
1765   int tok_keywords = 0;
1766   int paren_keywords = 0;
1767
1768   if (NULL == md)
1769     return NULL;
1770   ret = GNUNET_new (struct GNUNET_FS_Uri);
1771   ret->type = GNUNET_FS_URI_KSK;
1772   ent = GNUNET_CONTAINER_meta_data_iterate (md, NULL, NULL);
1773   if (ent > 0)
1774   {
1775     full_name = GNUNET_CONTAINER_meta_data_get_first_by_types (md,
1776         EXTRACTOR_METATYPE_GNUNET_ORIGINAL_FILENAME, -1);
1777     if (NULL != full_name)
1778     {
1779       filename = full_name;
1780       while (NULL != (ss = strstr (filename, DIR_SEPARATOR_STR)))
1781         filename = ss + 1;
1782       tok_keywords = get_keywords_from_tokens (filename, NULL, 0);
1783       paren_keywords = get_keywords_from_parens (filename, NULL, 0);
1784     }
1785     /* x3 because there might be a normalized variant of every keyword,
1786        plus theoretically one more for mime... */
1787     ret->data.ksk.keywords
1788       = GNUNET_new_array ((ent + tok_keywords + paren_keywords) * 3,
1789                           char *);
1790     GNUNET_CONTAINER_meta_data_iterate (md, &gather_uri_data, ret);
1791   }
1792   if (tok_keywords > 0)
1793     ret->data.ksk.keywordCount += get_keywords_from_tokens (filename,
1794                                                             ret->data.ksk.keywords,
1795                                                             ret->data.ksk.keywordCount);
1796   if (paren_keywords > 0)
1797     ret->data.ksk.keywordCount += get_keywords_from_parens (filename,
1798                                                             ret->data.ksk.keywords,
1799                                                             ret->data.ksk.keywordCount);
1800   if (ent > 0)
1801     GNUNET_free_non_null (full_name);
1802   return ret;
1803 }
1804
1805
1806 /**
1807  * In URI-encoding, does the given character
1808  * need to be encoded using %-encoding?
1809  */
1810 static int
1811 needs_percent (char c)
1812 {
1813   return (!
1814           ((isalnum ((unsigned char) c)) || (c == '-') || (c == '_') ||
1815            (c == '.') || (c == '~')));
1816 }
1817
1818
1819 /**
1820  * Convert a KSK URI to a string.
1821  *
1822  * @param uri the URI to convert
1823  * @return NULL on error (i.e. keywordCount == 0)
1824  */
1825 static char *
1826 uri_ksk_to_string (const struct GNUNET_FS_Uri *uri)
1827 {
1828   char **keywords;
1829   unsigned int keywordCount;
1830   size_t n;
1831   char *ret;
1832   unsigned int i;
1833   unsigned int j;
1834   unsigned int wpos;
1835   size_t slen;
1836   const char *keyword;
1837
1838   if (uri->type != GNUNET_FS_URI_KSK)
1839     return NULL;
1840   keywords = uri->data.ksk.keywords;
1841   keywordCount = uri->data.ksk.keywordCount;
1842   n = keywordCount + strlen (GNUNET_FS_URI_PREFIX) +
1843       strlen (GNUNET_FS_URI_KSK_INFIX) + 1;
1844   for (i = 0; i < keywordCount; i++)
1845   {
1846     keyword = keywords[i];
1847     slen = strlen (keyword);
1848     n += slen;
1849     for (j = 0; j < slen; j++)
1850     {
1851       if ((j == 0) && (keyword[j] == ' '))
1852       {
1853         n--;
1854         continue;               /* skip leading space */
1855       }
1856       if (needs_percent (keyword[j]))
1857         n += 2;                 /* will use %-encoding */
1858     }
1859   }
1860   ret = GNUNET_malloc (n);
1861   strcpy (ret, GNUNET_FS_URI_PREFIX);
1862   strcat (ret, GNUNET_FS_URI_KSK_INFIX);
1863   wpos = strlen (ret);
1864   for (i = 0; i < keywordCount; i++)
1865   {
1866     keyword = keywords[i];
1867     slen = strlen (keyword);
1868     for (j = 0; j < slen; j++)
1869     {
1870       if ((j == 0) && (keyword[j] == ' '))
1871         continue;               /* skip leading space */
1872       if (needs_percent (keyword[j]))
1873       {
1874         sprintf (&ret[wpos], "%%%02X", (unsigned char) keyword[j]);
1875         wpos += 3;
1876       }
1877       else
1878       {
1879         ret[wpos++] = keyword[j];
1880       }
1881     }
1882     if (i != keywordCount - 1)
1883       ret[wpos++] = '+';
1884   }
1885   return ret;
1886 }
1887
1888
1889 /**
1890  * Convert SKS URI to a string.
1891  *
1892  * @param uri sks uri to convert
1893  * @return NULL on error
1894  */
1895 static char *
1896 uri_sks_to_string (const struct GNUNET_FS_Uri *uri)
1897 {
1898   char *ret;
1899   char buf[1024];
1900
1901   if (GNUNET_FS_URI_SKS != uri->type)
1902     return NULL;
1903   ret = GNUNET_STRINGS_data_to_string (&uri->data.sks.ns,
1904                                        sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
1905                                        buf,
1906                                        sizeof (buf));
1907   GNUNET_assert (NULL != ret);
1908   ret[0] = '\0';
1909   GNUNET_asprintf (&ret, "%s%s%s/%s", GNUNET_FS_URI_PREFIX,
1910                    GNUNET_FS_URI_SKS_INFIX, buf,
1911                    uri->data.sks.identifier);
1912   return ret;
1913 }
1914
1915
1916 /**
1917  * Convert a CHK URI to a string.
1918  *
1919  * @param uri chk uri to convert
1920  * @return NULL on error
1921  */
1922 static char *
1923 uri_chk_to_string (const struct GNUNET_FS_Uri *uri)
1924 {
1925   const struct FileIdentifier *fi;
1926   char *ret;
1927   struct GNUNET_CRYPTO_HashAsciiEncoded keyhash;
1928   struct GNUNET_CRYPTO_HashAsciiEncoded queryhash;
1929
1930   if (uri->type != GNUNET_FS_URI_CHK)
1931     return NULL;
1932   fi = &uri->data.chk;
1933   GNUNET_CRYPTO_hash_to_enc (&fi->chk.key, &keyhash);
1934   GNUNET_CRYPTO_hash_to_enc (&fi->chk.query, &queryhash);
1935
1936   GNUNET_asprintf (&ret, "%s%s%s.%s.%llu", GNUNET_FS_URI_PREFIX,
1937                    GNUNET_FS_URI_CHK_INFIX, (const char *) &keyhash,
1938                    (const char *) &queryhash, GNUNET_ntohll (fi->file_length));
1939   return ret;
1940 }
1941
1942
1943 /**
1944  * Convert a LOC URI to a string.
1945  *
1946  * @param uri loc uri to convert
1947  * @return NULL on error
1948  */
1949 static char *
1950 uri_loc_to_string (const struct GNUNET_FS_Uri *uri)
1951 {
1952   char *ret;
1953   struct GNUNET_CRYPTO_HashAsciiEncoded keyhash;
1954   struct GNUNET_CRYPTO_HashAsciiEncoded queryhash;
1955   char *peer_id;
1956   char peer_sig[SIGNATURE_ASCII_LENGTH + 1];
1957
1958   GNUNET_CRYPTO_hash_to_enc (&uri->data.loc.fi.chk.key, &keyhash);
1959   GNUNET_CRYPTO_hash_to_enc (&uri->data.loc.fi.chk.query, &queryhash);
1960   peer_id =
1961     GNUNET_CRYPTO_eddsa_public_key_to_string (&uri->data.loc.peer.public_key);
1962   GNUNET_assert (NULL !=
1963                  GNUNET_STRINGS_data_to_string (&uri->data.loc.contentSignature,
1964                                                 sizeof (struct GNUNET_CRYPTO_EddsaSignature),
1965                                                 peer_sig,
1966                                                 sizeof (peer_sig)));
1967   GNUNET_asprintf (&ret,
1968                    "%s%s%s.%s.%llu.%s.%s.%llu", GNUNET_FS_URI_PREFIX,
1969                    GNUNET_FS_URI_LOC_INFIX, (const char *) &keyhash,
1970                    (const char *) &queryhash,
1971                    (unsigned long long) GNUNET_ntohll (uri->data.loc.
1972                                                        fi.file_length),
1973                    peer_id,
1974                    peer_sig,
1975                    (unsigned long long) uri->data.loc.expirationTime.abs_value_us / 1000000LL);
1976   GNUNET_free (peer_id);
1977   return ret;
1978 }
1979
1980
1981 /**
1982  * Convert a URI to a UTF-8 String.
1983  *
1984  * @param uri uri to convert to a string
1985  * @return the UTF-8 string
1986  */
1987 char *
1988 GNUNET_FS_uri_to_string (const struct GNUNET_FS_Uri *uri)
1989 {
1990   if (uri == NULL)
1991   {
1992     GNUNET_break (0);
1993     return NULL;
1994   }
1995   switch (uri->type)
1996   {
1997   case GNUNET_FS_URI_KSK:
1998     return uri_ksk_to_string (uri);
1999   case GNUNET_FS_URI_SKS:
2000     return uri_sks_to_string (uri);
2001   case GNUNET_FS_URI_CHK:
2002     return uri_chk_to_string (uri);
2003   case GNUNET_FS_URI_LOC:
2004     return uri_loc_to_string (uri);
2005   default:
2006     GNUNET_break (0);
2007     return NULL;
2008   }
2009 }
2010
2011 /* end of fs_uri.c */