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