(no commit message)
[oweals/gnunet.git] / src / fs / fs_download.c
index 92c4b831d5b06b58990fa9d59a167645cacf91f9..ebe9b5cac59797c8d9724072e6d7bfd5c15a0193 100644 (file)
@@ -4,7 +4,7 @@
 
      GNUnet is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published
-     by the Free Software Foundation; either version 2, or (at your
+     by the Free Software Foundation; either version 3, or (at your
      option) any later version.
 
      GNUnet is distributed in the hope that it will be useful, but
@@ -23,7 +23,6 @@
  * @author Christian Grothoff
  *
  * TODO:
- * - location URI suppport (can wait, easy)
  * - different priority for scheduling probe downloads?
  * - check if iblocks can be computed from existing blocks (can wait, hard)
  */
@@ -33,7 +32,7 @@
 #include "fs.h"
 #include "fs_tree.h"
 
-#define DEBUG_DOWNLOAD GNUNET_NO
+#define DEBUG_DOWNLOAD GNUNET_YES
 
 /**
  * Determine if the given download (options and meta data) should cause
@@ -250,6 +249,171 @@ process_result_with_request (void *cls,
                             void *value);
 
 
+/**
+ * We've found a matching block without downloading it.
+ * Encrypt it and pass it to our "receive" function as
+ * if we had received it from the network.
+ * 
+ * @param dc download in question
+ * @param chk request this relates to
+ * @param sm request details
+ * @param block plaintext data matching request
+ * @param len number of bytes in block
+ * @param depth depth of the block
+ * @param do_store should we still store the block on disk?
+ * @return GNUNET_OK on success
+ */
+static int
+encrypt_existing_match (struct GNUNET_FS_DownloadContext *dc,
+                       const struct ContentHashKey *chk,
+                       struct DownloadRequest *sm,
+                       const char * block,                    
+                       size_t len,
+                       int depth,
+                       int do_store)
+{
+  struct ProcessResultClosure prc;
+  char enc[len];
+  struct GNUNET_CRYPTO_AesSessionKey sk;
+  struct GNUNET_CRYPTO_AesInitializationVector iv;
+  GNUNET_HashCode query;
+  
+  GNUNET_CRYPTO_hash_to_aes_key (&chk->key, &sk, &iv);
+  if (-1 == GNUNET_CRYPTO_aes_encrypt (block, len,
+                                      &sk,
+                                      &iv,
+                                      enc))
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+  GNUNET_CRYPTO_hash (enc, len, &query);
+  if (0 != memcmp (&query,
+                  &chk->query,
+                  sizeof (GNUNET_HashCode)))
+    {
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Matching block already present, no need for download!\n");
+#endif
+  /* already got it! */
+  prc.dc = dc;
+  prc.data = enc;
+  prc.size = len;
+  prc.type = (dc->treedepth == depth) 
+    ? GNUNET_BLOCK_TYPE_FS_DBLOCK 
+    : GNUNET_BLOCK_TYPE_FS_IBLOCK;
+  prc.query = chk->query;
+  prc.do_store = do_store;
+  process_result_with_request (&prc,
+                              &chk->key,
+                              sm);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Closure for match_full_data.
+ */
+struct MatchDataContext 
+{
+  /**
+   * CHK we are looking for.
+   */
+  const struct ContentHashKey *chk;
+
+  /**
+   * Download we're processing.
+   */
+  struct GNUNET_FS_DownloadContext *dc;
+
+  /**
+   * Request details.
+   */
+  struct DownloadRequest *sm;
+
+  /**
+   * Overall offset in the file.
+   */
+  uint64_t offset;
+
+  /**
+   * Desired length of the block.
+   */
+  size_t len;
+
+  /**
+   * Flag set to GNUNET_YES on success.
+   */
+  int done;
+};
+
+/**
+ * Type of a function that libextractor calls for each
+ * meta data item found.
+ *
+ * @param cls closure (user-defined)
+ * @param plugin_name name of the plugin that produced this value;
+ *        special values can be used (i.e. '<zlib>' for zlib being
+ *        used in the main libextractor library and yielding
+ *        meta data).
+ * @param type libextractor-type describing the meta data
+ * @param format basic format information about data 
+ * @param data_mime_type mime-type of data (not of the original file);
+ *        can be NULL (if mime-type is not known)
+ * @param data actual meta-data found
+ * @param data_len number of bytes in data
+ * @return 0 to continue extracting, 1 to abort
+ */ 
+static int
+match_full_data (void *cls,
+                const char *plugin_name,
+                enum EXTRACTOR_MetaType type,
+                enum EXTRACTOR_MetaFormat format,
+                const char *data_mime_type,
+                const char *data,
+                size_t data_len)
+{
+  struct MatchDataContext *mdc = cls;
+  GNUNET_HashCode key;
+
+  if (type == EXTRACTOR_METATYPE_GNUNET_FULL_DATA) 
+    {
+      if ( (mdc->offset > data_len) ||
+          (mdc->offset + mdc->len > data_len) )
+       return 1;
+      GNUNET_CRYPTO_hash (&data[mdc->offset],
+                         mdc->len,
+                         &key);
+      if (0 != memcmp (&key,
+                      &mdc->chk->key,
+                      sizeof (GNUNET_HashCode)))
+       {
+         GNUNET_break_op (0);
+         return 1;
+       }
+      /* match found! */
+      if (GNUNET_OK !=
+         encrypt_existing_match (mdc->dc,
+                                 mdc->chk,
+                                 mdc->sm,
+                                 &data[mdc->offset],
+                                 mdc->len,
+                                 0,
+                                 GNUNET_YES))
+       {
+         GNUNET_break_op (0);
+         return 1;
+       }
+      mdc->done = GNUNET_YES;
+      return 1;
+    }
+  return 0;
+}
+
 
 /**
  * Schedule the download of the specified block in the tree.
@@ -274,25 +438,18 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
   size_t len;
   char block[DBLOCK_SIZE];
   GNUNET_HashCode key;
-  struct ProcessResultClosure prc;
+  struct MatchDataContext mdc;
   struct GNUNET_DISK_FileHandle *fh;
 
-#if DEBUG_DOWNLOAD
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-             "Scheduling download at offset %llu and depth %u for `%s'\n",
-             (unsigned long long) offset,
-             depth,
-             GNUNET_h2s (&chk->query));
-#endif
   total = GNUNET_ntohll (dc->uri->data.chk.file_length);
-  off = compute_disk_offset (total,
-                            offset,
-                            depth,
-                            dc->treedepth);
   len = GNUNET_FS_tree_calculate_block_size (total,
                                             dc->treedepth,
                                             offset,
                                             depth);
+  off = compute_disk_offset (total,
+                            offset,
+                            depth,
+                            dc->treedepth);
   sm = GNUNET_malloc (sizeof (struct DownloadRequest));
   sm->chk = *chk;
   sm->offset = offset;
@@ -304,6 +461,29 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
                                     &chk->query,
                                     sm,
                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
+  if ( (dc->tried_full_data == GNUNET_NO) &&
+       (depth == 0) )
+    {      
+      mdc.dc = dc;
+      mdc.sm = sm;
+      mdc.chk = chk;
+      mdc.offset = offset;
+      mdc.len = len;
+      mdc.done = GNUNET_NO;
+      GNUNET_CONTAINER_meta_data_iterate (dc->meta,
+                                         &match_full_data,
+                                         &mdc);
+      if (mdc.done == GNUNET_YES)
+       return;
+      dc->tried_full_data = GNUNET_YES; 
+    }
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Scheduling download at offset %llu and depth %u for `%s'\n",
+             (unsigned long long) offset,
+             depth,
+             GNUNET_h2s (&chk->query));
+#endif
   fh = NULL;
   if ( (dc->old_file_size > off) &&
        (dc->filename != NULL) )    
@@ -321,55 +501,22 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
                               len)) )
     {
       GNUNET_CRYPTO_hash (block, len, &key);
-      if (0 == memcmp (&key,
-                      &chk->key,
-                      sizeof (GNUNET_HashCode)))
+      if ( (0 == memcmp (&key,
+                        &chk->key,
+                        sizeof (GNUNET_HashCode))) &&
+          (GNUNET_OK ==
+           encrypt_existing_match (dc,
+                                   chk,
+                                   sm,
+                                   block,
+                                   len,
+                                   depth,
+                                   GNUNET_NO)) )
        {
-         char enc[len];
-         struct GNUNET_CRYPTO_AesSessionKey sk;
-         struct GNUNET_CRYPTO_AesInitializationVector iv;
-         GNUNET_HashCode query;
-
-         GNUNET_CRYPTO_hash_to_aes_key (&key, &sk, &iv);
-         if (-1 == GNUNET_CRYPTO_aes_encrypt (block, len,
-                                              &sk,
-                                              &iv,
-                                              enc))
-           {
-             GNUNET_break (0);
-             goto do_download;
-           }
-         GNUNET_CRYPTO_hash (enc, len, &query);
-         if (0 == memcmp (&query,
-                          &chk->query,
-                          sizeof (GNUNET_HashCode)))
-           {
-#if DEBUG_DOWNLOAD
-             GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                         "Matching block already present, no need for download!\n");
-#endif
-             /* already got it! */
-             prc.dc = dc;
-             prc.data = enc;
-             prc.size = len;
-             prc.type = (dc->treedepth == depth) 
-               ? GNUNET_BLOCK_TYPE_DBLOCK 
-               : GNUNET_BLOCK_TYPE_IBLOCK;
-             prc.query = chk->query;
-             prc.do_store = GNUNET_NO; /* useless */
-             process_result_with_request (&prc,
-                                          &key,
-                                          sm);
-           }
-         else
-           {
-             GNUNET_break_op (0);
-           }
          GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
          return;
        }
     }
- do_download:
   if (fh != NULL)
     GNUNET_break (GNUNET_OK == GNUNET_DISK_file_close (fh));
   if (depth < dc->treedepth)
@@ -395,6 +542,17 @@ schedule_block_download (struct GNUNET_FS_DownloadContext *dc,
                                                    &transmit_download_request,
                                                    dc);
     }
+  else
+    {
+#if DEBUG_DOWNLOAD
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                 "Transmission request not issued (%p %p)\n",
+                 dc->th, 
+                 dc->client);
+#endif
+
+    }
+
 }
 
 
@@ -663,6 +821,8 @@ check_completed (struct GNUNET_FS_DownloadContext *dc)
 }
 
 
+#define GNUNET_FS_URI_CHK_PREFIX GNUNET_FS_URI_PREFIX GNUNET_FS_URI_CHK_INFIX
+
 /**
  * We found an entry in a directory.  Check if the respective child
  * already exists and if not create the respective child download.
@@ -691,6 +851,7 @@ trigger_recursive_download (void *cls,
   char *us;
   char *ext;
   char *dn;
+  char *pos;
   char *full_name;
 
   if (NULL == uri)
@@ -711,12 +872,11 @@ trigger_recursive_download (void *cls,
   fn = NULL;
   if (NULL == filename)
     {
-      fn = GNUNET_FS_meta_data_suggest_filename (meta);      
+      fn = GNUNET_FS_meta_data_suggest_filename (meta);
       if (fn == NULL)
        {
          us = GNUNET_FS_uri_to_string (uri);
-         fn = GNUNET_strdup (&us [strlen (GNUNET_FS_URI_PREFIX 
-                                          GNUNET_FS_URI_CHK_INFIX)]);
+         fn = GNUNET_strdup (&us [strlen (GNUNET_FS_URI_CHK_PREFIX)]);
          GNUNET_free (us);
        }
       else if (fn[0] == '.')
@@ -725,11 +885,24 @@ trigger_recursive_download (void *cls,
          us = GNUNET_FS_uri_to_string (uri);
          GNUNET_asprintf (&fn,
                           "%s%s",
-                          &us[strlen (GNUNET_FS_URI_PREFIX 
-                                      GNUNET_FS_URI_CHK_INFIX)], ext);
+                          &us[strlen (GNUNET_FS_URI_CHK_PREFIX)], ext);
          GNUNET_free (ext);
          GNUNET_free (us);
        }
+      /* change '\' to '/' (this should have happened
+       during insertion, but malicious peers may
+       not have done this) */
+      while (NULL != (pos = strstr (fn, "\\")))
+       *pos = '/';
+      /* remove '../' everywhere (again, well-behaved
+        peers don't do this, but don't trust that
+        we did not get something nasty) */
+      while (NULL != (pos = strstr (fn, "../")))
+       {
+         pos[0] = '_';
+         pos[1] = '_';
+         pos[2] = '_';
+       }
       filename = fn;
     }
   if (dc->filename == NULL)
@@ -1234,7 +1407,7 @@ transmit_download_request (void *cls,
   msize = 0;
   sm = buf;
   while ( (dc->pending != NULL) &&
-         (size > msize + sizeof (struct SearchMessage)) )
+         (size >= msize + sizeof (struct SearchMessage)) )
     {
 #if DEBUG_DOWNLOAD
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -1250,9 +1423,9 @@ transmit_download_request (void *cls,
       else
        sm->options = htonl (0);      
       if (dc->pending->depth == dc->treedepth)
-       sm->type = htonl (GNUNET_BLOCK_TYPE_DBLOCK);
+       sm->type = htonl (GNUNET_BLOCK_TYPE_FS_DBLOCK);
       else
-       sm->type = htonl (GNUNET_BLOCK_TYPE_IBLOCK);
+       sm->type = htonl (GNUNET_BLOCK_TYPE_FS_IBLOCK);
       sm->anonymity_level = htonl (dc->anonymity);
       sm->target = dc->target.hashPubKey;
       sm->query = dc->pending->chk.query;
@@ -1286,8 +1459,7 @@ do_reconnect (void *cls,
   struct GNUNET_CLIENT_Connection *client;
   
   dc->task = GNUNET_SCHEDULER_NO_TASK;
-  client = GNUNET_CLIENT_connect (dc->h->sched,
-                                 "fs",
+  client = GNUNET_CLIENT_connect ("fs",
                                  dc->h->cfg);
   if (NULL == client)
     {
@@ -1370,8 +1542,7 @@ try_reconnect (struct GNUNET_FS_DownloadContext *dc)
              "Will try to reconnect in 1s\n");
 #endif
   dc->task
-    = GNUNET_SCHEDULER_add_delayed (dc->h->sched,
-                                   GNUNET_TIME_UNIT_SECONDS,
+    = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS,
                                    &do_reconnect,
                                    dc);
 }
@@ -1391,6 +1562,10 @@ activate_fs_download (void *cls,
   struct GNUNET_FS_DownloadContext *dc = cls;
   struct GNUNET_FS_ProgressInfo pi;
 
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Download activated\n");
+#endif
   GNUNET_assert (NULL != client);
   GNUNET_assert (dc->client == NULL);
   GNUNET_assert (dc->th == NULL);
@@ -1404,12 +1579,17 @@ activate_fs_download (void *cls,
   GNUNET_CONTAINER_multihashmap_iterate (dc->active,
                                         &retry_entry,
                                         dc);
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Asking for transmission to FS service\n");
+#endif
   dc->th = GNUNET_CLIENT_notify_transmit_ready (dc->client,
                                                sizeof (struct SearchMessage),
                                                GNUNET_CONSTANTS_SERVICE_TIMEOUT,
                                                GNUNET_NO,
                                                &transmit_download_request,
                                                dc);    
+  GNUNET_assert (dc->th != NULL);
 }
 
 
@@ -1423,7 +1603,11 @@ deactivate_fs_download (void *cls)
 {
   struct GNUNET_FS_DownloadContext *dc = cls;
   struct GNUNET_FS_ProgressInfo pi;
-  
+
+#if DEBUG_DOWNLOAD
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Download deactivated\n");
+#endif  
   if (NULL != dc->th)
     {
       GNUNET_CLIENT_notify_transmit_ready_cancel (dc->th);
@@ -1472,8 +1656,7 @@ GNUNET_FS_download_signal_suspend_ (void *cls)
   pi.status = GNUNET_FS_STATUS_DOWNLOAD_SUSPEND;
   GNUNET_FS_download_make_status_ (&pi, dc);
   if (GNUNET_SCHEDULER_NO_TASK != dc->task)
-    GNUNET_SCHEDULER_cancel (dc->h->sched,
-                            dc->task);
+    GNUNET_SCHEDULER_cancel (dc->task);
   GNUNET_CONTAINER_multihashmap_iterate (dc->active,
                                         &free_entry,
                                         NULL);
@@ -1533,9 +1716,11 @@ GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
   struct GNUNET_FS_ProgressInfo pi;
   struct GNUNET_FS_DownloadContext *dc;
 
-  GNUNET_assert (GNUNET_FS_uri_test_chk (uri));
+  GNUNET_assert (GNUNET_FS_uri_test_chk (uri) ||
+                GNUNET_FS_uri_test_loc (uri) );
+                
   if ( (offset + length < offset) ||
-       (offset + length > uri->data.chk.file_length) )
+       (offset + length > GNUNET_FS_uri_chk_get_file_size (uri)) )
     {      
       GNUNET_break (0);
       return NULL;
@@ -1576,7 +1761,7 @@ GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
   dc->anonymity = anonymity;
   dc->options = options;
   dc->active = GNUNET_CONTAINER_multihashmap_create (1 + 2 * (length / DBLOCK_SIZE));
-  dc->treedepth = GNUNET_FS_compute_depth (GNUNET_ntohll(dc->uri->data.chk.file_length));
+  dc->treedepth = GNUNET_FS_compute_depth (GNUNET_FS_uri_chk_get_file_size(dc->uri));
   if ( (filename == NULL) &&
        (is_recursive_download (dc) ) )
     {
@@ -1601,7 +1786,9 @@ GNUNET_FS_download_start (struct GNUNET_FS_Handle *h,
   pi.value.download.specifics.start.meta = meta;
   GNUNET_FS_download_make_status_ (&pi, dc);
   schedule_block_download (dc, 
-                          &dc->uri->data.chk.chk,
+                          (dc->uri->type == chk) 
+                          ? &dc->uri->data.chk.chk
+                          : &dc->uri->data.loc.fi.chk,
                           0, 
                           1 /* 0 == CHK, 1 == top */); 
   GNUNET_FS_download_sync_ (dc);
@@ -1664,7 +1851,8 @@ GNUNET_FS_download_start_from_search (struct GNUNET_FS_Handle *h,
       GNUNET_break (0);
       return NULL;
     }
-  GNUNET_assert (GNUNET_FS_uri_test_chk (sr->uri));
+  GNUNET_assert (GNUNET_FS_uri_test_chk (sr->uri) ||
+                GNUNET_FS_uri_test_loc (sr->uri) );             
   if ( (offset + length < offset) ||
        (offset + length > sr->uri->data.chk.file_length) )
     {      
@@ -1801,8 +1989,7 @@ GNUNET_FS_download_stop (struct GNUNET_FS_DownloadContext *dc,
   pi.status = GNUNET_FS_STATUS_DOWNLOAD_STOPPED;
   GNUNET_FS_download_make_status_ (&pi, dc);
   if (GNUNET_SCHEDULER_NO_TASK != dc->task)
-    GNUNET_SCHEDULER_cancel (dc->h->sched,
-                            dc->task);
+    GNUNET_SCHEDULER_cancel (dc->task);
   GNUNET_CONTAINER_multihashmap_iterate (dc->active,
                                         &free_entry,
                                         NULL);