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