Beispiel #1
0
static GVfsFtpDirCacheEntry *
g_vfs_ftp_dir_cache_lookup_entry (GVfsFtpDirCache *  cache,
                                  GVfsFtpTask *      task,
                                  const GVfsFtpFile *dir,
                                  guint              stamp)
{
  GVfsFtpDirCacheEntry *entry;

  g_mutex_lock (cache->lock);
  entry = g_hash_table_lookup (cache->directories, dir);
  if (entry)
    g_vfs_ftp_dir_cache_entry_ref (entry);
  g_mutex_unlock (cache->lock);
  if (entry && entry->stamp < stamp)
    g_vfs_ftp_dir_cache_entry_unref (entry);
  else if (entry)
    return entry;

  if (g_vfs_ftp_task_send (task,
        	           G_VFS_FTP_PASS_550,
        		   "CWD %s", g_vfs_ftp_file_get_ftp_path (dir)) == 550)
    {
      g_set_error_literal (&task->error,
        	           G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY,
        		   _("The file is not a directory"));
    }
  g_vfs_ftp_task_setup_data_connection (task);
  g_vfs_ftp_task_send (task,
        	       G_VFS_FTP_PASS_100 | G_VFS_FTP_FAIL_200,
                       "%s", cache->funcs->command);
  g_vfs_ftp_task_open_data_connection (task);
  if (g_vfs_ftp_task_is_in_error (task))
    return NULL;

  entry = g_vfs_ftp_dir_cache_entry_new (stamp);
  cache->funcs->process (g_io_stream_get_input_stream (g_vfs_ftp_connection_get_data_stream (task->conn)),
                         g_vfs_ftp_connection_get_debug_id (task->conn),
                         dir,
                         entry,
                         task->cancellable,
                         &task->error);
  g_vfs_ftp_task_close_data_connection (task);
  g_vfs_ftp_task_receive (task, 0, NULL);
  if (g_vfs_ftp_task_is_in_error (task))
    {
      g_vfs_ftp_dir_cache_entry_unref (entry);
      return NULL;
    }
  g_mutex_lock (cache->lock);
  g_hash_table_insert (cache->directories,
                       g_vfs_ftp_file_copy (dir),
                       g_vfs_ftp_dir_cache_entry_ref (entry));
  g_mutex_unlock (cache->lock);
  return entry;
}
Beispiel #2
0
/**
 * g_vfs_ftp_task_sendv:
 * @task: the sending task
 * @flags: response flags to use when receiving the reply
 * @reply: %NULL or pointer to char array that takes the full reply from the
 *         server
 * @format: format string to construct command from
 *          (without trailing \r\n)
 * @varargs: arguments to format string
 *
 * This is the varargs version of g_vfs_ftp_task_send(). See that function
 * for details.
 *
 * Returns: the received FTP code or 0 on error.
 **/
guint
g_vfs_ftp_task_sendv (GVfsFtpTask *          task,
                      GVfsFtpResponseFlags   flags,
                      char ***               reply,
                      const char *           format,
                      va_list                varargs)
{
  GString *command;
  gboolean retry_on_timeout = FALSE;
  guint response;

  if (g_vfs_ftp_task_is_in_error (task))
    return 0;

  command = g_string_new ("");
  g_string_append_vprintf (command, format, varargs);
  g_string_append (command, "\r\n");

retry:
  if (task->conn == NULL)
    {
      if (!g_vfs_ftp_task_acquire_connection (task))
        {
          g_string_free (command, TRUE);
          return 0;
        }
      retry_on_timeout = TRUE;
    }

  g_vfs_ftp_connection_send (task->conn,
                             command->str,
                             command->len,
                             task->cancellable,
                             &task->error);

  response = g_vfs_ftp_task_receive (task, flags, reply);
 
  /* NB: requires adaption if we allow passing 4xx responses */
  if (retry_on_timeout &&
      g_vfs_ftp_task_is_in_error (task) &&
      !g_vfs_ftp_connection_is_usable (task->conn))
    {
      g_vfs_ftp_task_clear_error (task);
      g_vfs_ftp_task_release_connection (task);
      goto retry;
    }

  g_string_free (command, TRUE);
  return response;
}
Beispiel #3
0
/**
 * g_vfs_ftp_task_send_and_check:
 * @task: the sending task
 * @flags: response flags to use when sending
 * @funcs: %NULL or %NULL-terminated array of functions used to determine the
 *         exact failure case upon a "550 Operation Failed" reply. This is
 *         often necessary
 * @data: data to pass to @funcs.
 * @reply: %NULL or pointer to take a char array containing the full reply of
 *         the ftp server upon successful reply. Use g_strfreev() to free
 *         after use.
 * @format: format string to construct command from
 *          (without trailing \r\n)
 * @...: arguments to format string
 *
 * Takes an ftp command in printf-style @format, potentially acquires a
 * connection automatically, sends the command and waits for an answer from
 * the ftp server. Without any @flags, FTP response codes other than 2xx cause
 * an error. If @reply is not %NULL, the full reply will be put into a
 * %NULL-terminated string array that must be freed with g_strfreev() after
 * use.
 * If @funcs is set, the 550 response code will cause all of these functions to
 * be called in order passing them the @task and @data arguments given to this
 * function until one of them sets an error on @task. This error will then be
 * returned from this function. If none of those functions sets an error, the
 * generic error for the 550 response will be used.
 * If an error has been set on @task previously, this function will do nothing.
 *
 * Returns: 0 on error or the received FTP code otherwise.
 **/
guint
g_vfs_ftp_task_send_and_check (GVfsFtpTask *           task,
                               GVfsFtpResponseFlags    flags,
                               const GVfsFtpErrorFunc *funcs,
                               gpointer                data,
                               char ***                reply,
                               const char *            format,
                               ...)
{
  va_list varargs;
  guint response;

  g_return_val_if_fail (task != NULL, 0);
  g_return_val_if_fail (format != NULL, 0);
  g_return_val_if_fail (funcs == NULL || funcs[0] != NULL, 0);

  if (funcs)
    {
      g_return_val_if_fail ((flags & G_VFS_FTP_PASS_550) == 0, 0);
      flags |= G_VFS_FTP_PASS_550;
    }

  va_start (varargs, format);
  response = g_vfs_ftp_task_sendv (task,
        			   flags,
                                   reply,
        			   format,
        			   varargs);
  va_end (varargs);

  if (response == 550 && funcs)
    {
      /* close a potentially open data connection, the error handlers
       * might try to open new ones and that would cause assertions */
      g_vfs_ftp_task_close_data_connection (task);

      while (*funcs && !g_vfs_ftp_task_is_in_error (task))
        {
          (*funcs) (task, data);
          funcs++;
        }
      if (!g_vfs_ftp_task_is_in_error (task))
          g_vfs_ftp_task_set_error_from_response (task, response);
      response = 0;
    }

  return response;
}
Beispiel #4
0
gboolean
g_vfs_ftp_task_login (GVfsFtpTask *task,
                      const char * username,
                      const char * password)
{
  guint status;

  g_return_val_if_fail (task != NULL, FALSE);
  g_return_val_if_fail (username != NULL, FALSE);
  g_return_val_if_fail (password != NULL, FALSE);

  if (g_vfs_ftp_task_is_in_error (task))
    return FALSE;

  status = g_vfs_ftp_task_send (task, G_VFS_FTP_PASS_300,
                                "USER %s", username);
 
  if (G_VFS_FTP_RESPONSE_GROUP (status) == 3)
    {
      /* rationale for choosing the default password:
       * - some ftp servers expect something that looks like an email address
       * - we don't want to send the user's name or address, as that would be
       *   a privacy problem
       * - we want to give ftp server administrators a chance to notify us of
       *   problems with our client.
       * - we don't want to drown in spam.
       */
      if (password == NULL || password[0] == 0)
        password = "******" VERSION "@example.com";
      status = g_vfs_ftp_task_send (task, 0,
        			    "PASS %s", password);
    }

  return status;
}
Beispiel #5
0
/**
 * g_vfs_ftp_task_setup_connection:
 * @task: the task
 *
 * Sends all commands necessary to put the connection into a usable state,
 * like setting the transfer mode to binary. Note that passive mode will
 * will be set on a case-by-case basis when opening a data connection.
 **/
void
g_vfs_ftp_task_setup_connection (GVfsFtpTask *task)
{
  g_return_if_fail (task != NULL);

  /* only binary transfers please */
  g_vfs_ftp_task_send (task, 0, "TYPE I");
  if (g_vfs_ftp_task_is_in_error (task))
    return;

#if 0
  /* RFC 2428 suggests to send this to make NAT routers happy */
  /* XXX: Disabled for the following reasons:
   * - most ftp clients don't use it
   * - lots of broken ftp servers can't see the difference between
   *   "EPSV" and "EPSV ALL"
   * - impossible to dynamically fall back to regular PASV in case
   *   EPSV doesn't work for some reason.
   * If this makes your ftp connection fail, please file a bug and we will
   * try to invent a way to make this all work. Until then, we'll just
   * ignore the RFC.
   */
  if (g_vfs_backend_ftp_has_feature (task->backend, g_VFS_FTP_FEATURE_EPSV))
    g_vfs_ftp_task_send (task, 0, "EPSV ALL");
  g_vfs_ftp_task_clear_error (task);
#endif

  /* instruct server that we'll give and assume we get utf8 */
  if (g_vfs_backend_ftp_has_feature (task->backend, G_VFS_FTP_FEATURE_UTF8))
    {
      if (!g_vfs_ftp_task_send (task, 0, "OPTS UTF8 ON"))
        g_vfs_ftp_task_clear_error (task);
    }
}
Beispiel #6
0
GList *
g_vfs_ftp_dir_cache_lookup_dir (GVfsFtpDirCache *  cache,
                                GVfsFtpTask *      task,
                                const GVfsFtpFile *dir,
                                gboolean           flush,
                                gboolean           resolve_symlinks)
{
  GVfsFtpDirCacheEntry *entry;
  GHashTableIter iter;
  gpointer file, info;
  guint stamp;
  GList *result = NULL;

  g_return_val_if_fail (cache != NULL, NULL);
  g_return_val_if_fail (task != NULL, NULL);
  g_return_val_if_fail (dir != NULL, NULL);

  if (g_vfs_ftp_task_is_in_error (task))
    return NULL;

  if (flush)
    {
      g_mutex_lock (cache->lock);
      g_assert (cache->stamp != G_MAXUINT);
      stamp = ++cache->stamp;
      g_mutex_unlock (cache->lock);
    }
  else
    stamp = 0;

  entry = g_vfs_ftp_dir_cache_lookup_entry (cache, task, dir, stamp);
  if (entry == NULL)
    return NULL;

  g_hash_table_iter_init (&iter, entry->files);
  while (g_hash_table_iter_next (&iter, &file, &info))
    {
      g_object_ref (info);
      if (resolve_symlinks)
        info = g_vfs_ftp_dir_cache_resolve_symlink (cache, task, file, info, stamp);
      g_assert (!g_vfs_ftp_task_is_in_error (task));
      result = g_list_prepend (result, info);
    }
  g_vfs_ftp_dir_cache_entry_unref (entry);

  return result;
}
Beispiel #7
0
static GFileInfo *
g_vfs_ftp_dir_cache_lookup_file_internal (GVfsFtpDirCache *  cache,
                                          GVfsFtpTask *      task,
                                          const GVfsFtpFile *file,
                                          guint              stamp)
{
  GVfsFtpDirCacheEntry *entry;
  GVfsFtpFile *dir;
  GFileInfo *info;

  if (g_vfs_ftp_task_is_in_error (task))
    return NULL;

  if (!g_vfs_ftp_file_is_root (file))
    {
      dir = g_vfs_ftp_file_new_parent (file);
      entry = g_vfs_ftp_dir_cache_lookup_entry (cache, task, dir, stamp);
      g_vfs_ftp_file_free (dir);
      if (entry == NULL)
        return NULL;

      info = g_hash_table_lookup (entry->files, file);
      if (info != NULL)
        {
          /* NB: the order of ref/unref is important here */
          g_object_ref (info);
          g_vfs_ftp_dir_cache_entry_unref (entry);
          return info;
        }

      g_vfs_ftp_dir_cache_entry_unref (entry);
    }

  if (g_vfs_ftp_task_is_in_error (task))
    return NULL;

  return cache->funcs->lookup_uncached (task, file);
}
Beispiel #8
0
/**
 * g_vfs_ftp_task_done:
 * @task: the task to finalize
 *
 * Finalizes the given task and clears all memory in use. It also marks the
 * associated job as success or failure depending on the error state of the
 * task.
 **/
void
g_vfs_ftp_task_done (GVfsFtpTask *task)
{
  g_return_if_fail (task != NULL);

  g_vfs_ftp_task_release_connection (task);

  if (task->job)
    {
      if (g_vfs_ftp_task_is_in_error (task))
        g_vfs_job_failed_from_error (task->job, task->error);
      else
        g_vfs_job_succeeded (task->job);
    }

  g_vfs_ftp_task_clear_error (task);
}
Beispiel #9
0
/**
 * g_vfs_ftp_task_receive:
 * @task: the receiving task
 * @flags: response flags to use
 * @reply: %NULL or pointer to char array that takes the full reply from the
 *         server
 *
 * Unless @task is in an error state, this function receives a reply from
 * the @task's connection. The @task must have a connection set, which will
 * happen when either g_vfs_ftp_task_send() or
 * g_vfs_ftp_task_give_connection() have been called on the @task before.
 * Unless @flags are given, all reply codes not in the 200s cause an error.
 * If @task is in an error state when calling this function, nothing will
 * happen and the function will just return.
 *
 * Returns: the received FTP code or 0 on error.
 **/
guint
g_vfs_ftp_task_receive (GVfsFtpTask *        task,
                        GVfsFtpResponseFlags flags,
                        char ***             reply)
{
  guint response;

  g_return_val_if_fail (task != NULL, 0);
  if (g_vfs_ftp_task_is_in_error (task))
    return 0;
  g_return_val_if_fail (task->conn != NULL, 0);

  response = g_vfs_ftp_connection_receive (task->conn,
                                           reply,
                                           task->cancellable,
                                           &task->error);

  switch (G_VFS_FTP_RESPONSE_GROUP (response))
    {
      case 0:
        return 0;
      case 1:
        if (flags & G_VFS_FTP_PASS_100)
          break;
        g_vfs_ftp_task_set_error_from_response (task, response);
        break;
      case 2:
        if (flags & G_VFS_FTP_FAIL_200)
          g_vfs_ftp_task_set_error_from_response (task, response);
        break;
      case 3:
        if (flags & G_VFS_FTP_PASS_300)
          break;
        g_vfs_ftp_task_set_error_from_response (task, response);
        break;
      case 4:
        g_vfs_ftp_task_set_error_from_response (task, response);
        break;
      case 5:
        if ((flags & G_VFS_FTP_PASS_500) ||
            (response == 550 && (flags & G_VFS_FTP_PASS_550)))
          break;
        g_vfs_ftp_task_set_error_from_response (task, response);
        break;
      default:
        g_assert_not_reached ();
        break;
    }

  if (g_vfs_ftp_task_is_in_error (task))
    {
      if (response != 0 && reply)
        {
          g_strfreev (*reply);
          *reply = NULL;
        }
      response = 0;
    }

  return response;
}
Beispiel #10
0
/**
 * g_vfs_ftp_task_acquire_connection:
 * @task: a task without an associated connection
 *
 * Acquires a new connection for use by this @task. This uses the connection
 * pool of @task's backend, so it reuses previously opened connections and
 * does not reopen new connections unnecessarily. If all connections are busy,
 * it waits %G_VFS_FTP_TIMEOUT_IN_SECONDS seconds for a new connection to
 * become available. Keep in mind that a newly acquired connection might have
 * timed out and therefore closed by the FTP server. You must account for
 * this when sending the first command to the server.
 *
 * Returns: %TRUE if a connection could be acquired, %FALSE if an error
 *          occured
 **/
static gboolean
g_vfs_ftp_task_acquire_connection (GVfsFtpTask *task)
{
  GVfsBackendFtp *ftp;
  gint64 end_time;
  gulong id;

  g_return_val_if_fail (task != NULL, FALSE);
  g_return_val_if_fail (task->conn == NULL, FALSE);

  if (g_vfs_ftp_task_is_in_error (task))
    return FALSE;

  ftp = task->backend;
  g_mutex_lock (&ftp->mutex);
  id = g_cancellable_connect (task->cancellable,
        		      G_CALLBACK (do_broadcast),
        		      &ftp->cond, NULL);
  while (task->conn == NULL && ftp->queue != NULL)
    {
      if (g_cancellable_is_cancelled (task->cancellable))
        {
          task->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED,
        		                     _("Operation was cancelled"));
          break;
        }

      task->conn = g_queue_pop_head (ftp->queue);
      if (task->conn != NULL)
        break;

      if (ftp->connections < ftp->max_connections)
        {
          static GThread *last_thread = NULL;
          /* Save current number of connections here, so we can limit maximum
           * connections later.
           * This is necessary for threading reasons (connections can be
           * opened or closed while we are still in the opening process. */
          guint maybe_max_connections = ftp->connections;

          ftp->connections++;
          last_thread = g_thread_self ();
          g_mutex_unlock (&ftp->mutex);
          task->conn = g_vfs_ftp_connection_new (ftp->addr, task->cancellable, &task->error);
          if (G_LIKELY (task->conn != NULL))
            {
              g_vfs_ftp_task_receive (task, 0, NULL);
              g_vfs_ftp_task_login (task, ftp->user, ftp->password);
              g_vfs_ftp_task_setup_connection (task);
              if (G_LIKELY (!g_vfs_ftp_task_is_in_error (task)))
                break;
            }

          g_vfs_ftp_connection_free (task->conn);
          task->conn = NULL;
          g_mutex_lock (&ftp->mutex);
          ftp->connections--;
          /* If this value is still equal to our thread it means there were no races 
           * trying to open connections and the maybe_max_connections value is 
           * reliable. */
          if (last_thread == g_thread_self () && 
              !g_vfs_ftp_task_error_matches (task, G_IO_ERROR, G_IO_ERROR_CANCELLED))
            {
              g_print ("maybe: %u, max %u (due to %s)\n", maybe_max_connections, ftp->max_connections, task->error->message);
              ftp->max_connections = MIN (ftp->max_connections, maybe_max_connections);
              if (ftp->max_connections == 0)
                {
                  g_debug ("no more connections left, exiting...\n");
                  /* FIXME: shut down properly */
                  exit (0);
                }
            }

          g_vfs_ftp_task_clear_error (task);
          continue;
        }

      end_time = g_get_monotonic_time () + G_VFS_FTP_TIMEOUT_IN_SECONDS * G_TIME_SPAN_SECOND;
      if (ftp->busy_connections >= ftp->connections ||
          !g_cond_wait_until (&ftp->cond, &ftp->mutex, end_time))
        {
          task->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_BUSY,
        		                     _("The FTP server is busy. Try again later"));
          break;
        }
    }
  g_cancellable_disconnect (task->cancellable, id);
  g_mutex_unlock (&ftp->mutex);

  return task->conn != NULL;
}
Beispiel #11
0
static GFileInfo *
g_vfs_ftp_dir_cache_resolve_symlink (GVfsFtpDirCache *  cache,
                                     GVfsFtpTask *      task,
                                     const GVfsFtpFile *file,
                                     GFileInfo *        original,
                                     guint              stamp)
{
  static const char *copy_attributes[] = {
    G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK,
    G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
    G_FILE_ATTRIBUTE_STANDARD_NAME,
    G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
    G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME,
    G_FILE_ATTRIBUTE_STANDARD_COPY_NAME,
    G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET
  };
  GFileInfo *info, *result;
  GVfsFtpFile *tmp, *link;
  guint i, lookups = 0;
  const char *target;

  if (!g_file_info_get_is_symlink (original) ||
      g_vfs_ftp_task_is_in_error (task))
    return original;

  info = g_object_ref (original);
  link = g_vfs_ftp_file_copy (file);
  do {
      target = g_file_info_get_symlink_target (info);
      if (target == NULL)
        {
          /* This happens when bad servers don't report a symlink target.
           * We now want to figure out if this is a directory or regular file,
           * so we can at least report something useful.
           */
          g_object_unref (info);
          info = cache->funcs->lookup_uncached (task, file);
          break;
        }
      tmp = link;
      link = cache->funcs->resolve_symlink (task, tmp, g_file_info_get_symlink_target (info));
      g_vfs_ftp_file_free (tmp);
      g_object_unref (info);
      if (link == NULL)
        {
          g_vfs_ftp_task_clear_error (task);
          return original;
        }
      info = g_vfs_ftp_dir_cache_lookup_file_internal (cache, task, link, stamp);
      if (info == NULL)
        {
          g_vfs_ftp_file_free (link);
          g_vfs_ftp_task_clear_error (task);
          return original;
        }
    }
  while (g_file_info_get_is_symlink (info) && lookups++ < 8);

  g_vfs_ftp_file_free (link);
  if (g_file_info_get_is_symlink (info))
    {
      /* too many recursions */
      g_object_unref (info);
      return original;
    }

  result = g_file_info_dup (info);
  g_object_unref (info);
  for (i = 0; i < G_N_ELEMENTS (copy_attributes); i++)
    {
      GFileAttributeType type;
      gpointer value;

      if (!g_file_info_get_attribute_data (original,
        				   copy_attributes[i],
        				   &type,
        				   &value,
        				   NULL))
        continue;
     
      g_file_info_set_attribute (result,
                                 copy_attributes[i],
        			 type,
        			 value);
    }
  g_object_unref (original);

  return result;
}