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