diff src/DLD-FUNCTIONS/urlwrite.cc @ 9880:7f77e5081e83

Add ftp objects
author David Bateman <dbateman@free.fr>
date Sat, 28 Nov 2009 11:44:57 +0100
parents 7c02ec148a3c
children dd3fc8ba4796
line wrap: on
line diff
--- a/src/DLD-FUNCTIONS/urlwrite.cc	Fri Nov 27 14:42:07 2009 +0100
+++ b/src/DLD-FUNCTIONS/urlwrite.cc	Sat Nov 28 11:44:57 2009 +0100
@@ -2,6 +2,7 @@
 /*
 
 Copyright (C) 2006, 2007, 2008 Alexander Barth
+Copyright (C) 2009 David Bateman
 
 This file is part of Octave.
 
@@ -31,16 +32,20 @@
 #include <string>
 #include <fstream>
 #include <iomanip>
+#include <iostream>
 
+#include "dir-ops.h"
 #include "file-ops.h"
 #include "file-stat.h"
 #include "oct-env.h"
+#include "glob-match.h"
 
 #include "defun-dld.h"
 #include "error.h"
 #include "oct-obj.h"
 #include "ov-cell.h"
 #include "pager.h"
+#include "oct-map.h"
 #include "unwind-prot.h"
 
 #if defined (HAVE_CURL)
@@ -49,163 +54,636 @@
 #include <curl/types.h>
 #include <curl/easy.h>
 
-// Write callback function for curl.
-
-static int
+static int 
 write_data (void *buffer, size_t size, size_t nmemb, void *streamp)
 {
-  // *stream is actually an ostream object.
   std::ostream& stream = *(static_cast<std::ostream*> (streamp));
   stream.write (static_cast<const char*> (buffer), size*nmemb);
   return (stream.fail () ? 0 : size * nmemb);
 }
 
-// Form the query string based on param.
-
-static std::string
-form_query_string (CURL *curl, const Cell& param)
+static int
+read_data (void *buffer, size_t size, size_t nmemb, void *streamp)
 {
-  std::ostringstream query;
-
-  for (int i = 0; i < param.numel (); i += 2)
-    {
-      std::string name = param(i).string_value ();
-      std::string text = param(i+1).string_value ();
-
-      // Encode strings.
-      char *enc_name = curl_easy_escape (curl, name.c_str (), name.length ());
-      char *enc_text = curl_easy_escape (curl, text.c_str (), text.length ());
-
-      query << enc_name << "=" << enc_text;
-
-      curl_free (enc_name);
-      curl_free (enc_text);
-
-      if (i < param.numel()-1)
-	query << "&";
-    }
-
-  query.flush ();
-
-  return query.str ();
+  std::istream& stream = *(static_cast<std::istream*> (streamp));
+  stream.read (static_cast<char*> (buffer), size*nmemb);
+  if (stream.eof ())
+    return stream.gcount ();
+  else
+    return (stream.fail () ? 0 : size * nmemb);
 }
 
-// curl front-end
-
-static void
-urlget_cleanup (CURL *curl)
+static size_t 
+throw_away (void *, size_t size, size_t nmemb, void *)
 {
-  curl_easy_cleanup (curl);
-  curl_global_cleanup ();
+  return static_cast<size_t>(size * nmemb);
 }
 
-static CURLcode
-urlget (const std::string& url, const std::string& method,
-	const Cell& param, std::ostream& stream)
+class
+curl_handle
 {
-  CURL *curl;
+private:
+  class
+  curl_handle_rep
+  {
+  public:
+    curl_handle_rep (void) : count (1), valid (true), ascii (false)
+      {
+        curl = curl_easy_init ();
+	if (!curl)
+	  error ("can not create curl handle");
+      }
+
+    ~curl_handle_rep (void)
+      {
+	if (curl)
+	  curl_easy_cleanup (curl);
+      }
+
+    bool is_valid (void) const
+      {
+	return valid;
+      }
+
+    bool perform (bool curlerror) const
+      {
+	bool retval = false;
+	if (!error_state)
+	  {
+	    BEGIN_INTERRUPT_IMMEDIATELY_IN_FOREIGN_CODE;
 
-  curl_global_init(CURL_GLOBAL_DEFAULT);
+	    CURLcode res = curl_easy_perform (curl);
+	    if (res != CURLE_OK)
+	      {
+		if (curlerror)
+		  error ("%s", curl_easy_strerror (res));
+	      }
+	    else
+	      retval = true;
+
+	    END_INTERRUPT_IMMEDIATELY_IN_FOREIGN_CODE;
+	  }
+	return retval;
+      }
 
-  curl = curl_easy_init();
+    CURL* handle (void) const
+      {
+	return curl;
+      }
+
+    bool is_ascii (void) const
+      {
+	return ascii;
+      }
+
+    bool is_binary (void) const
+      {
+	return !ascii;
+      }
+
+    size_t count;
+    std::string host;
+    bool valid;
+    bool ascii;
+
+  private:
+    CURL *curl;
 
-  if (! curl)
-    return CURLE_FAILED_INIT;
+    // No copying!
+
+    curl_handle_rep (const curl_handle_rep& ov);
+    
+    curl_handle_rep& operator = (const curl_handle_rep&);
+  };
+
+public:
 
-  // handle paramters of GET or POST request
+// I'd love to rewrite this as a private method of the curl_handle
+// class, but you can't pass the va_list from the wrapper setopt to
+// the curl_easy_setopt function.
+#define setopt(option, parameter) \
+  { \
+    CURLcode res = curl_easy_setopt (rep->handle (), option, parameter); \
+    if (res != CURLE_OK) \
+      error ("%s", curl_easy_strerror (res)); \
+  }
+
+  curl_handle (void) : rep (new curl_handle_rep ()) 
+    { 
+      rep->valid = false;
+    }
+
+  curl_handle (const std::string& _host, const std::string& user, 
+	       const std::string& passwd) :
+    rep (new curl_handle_rep ())
+    {
+      rep->host = _host;
+      init (user, passwd, std::cin, octave_stdout);
+
+      std::string url = "ftp://" + _host;
+      setopt (CURLOPT_URL, url.c_str());
 
-  std::string query_string = form_query_string (curl,param);
-  //octave_stdout << "query_string " << query_string << std::endl;
+      // Setup the link, with no transfer
+      if (!error_state)
+	perform ();
+    }
+
+  curl_handle (const std::string& url, const std::string& method, 
+	       const Cell& param, std::ostream& os, bool& retval) :
+    rep (new curl_handle_rep ())
+    {
+      retval = false;
+
+      init ("", "", std::cin, os);
+
+      setopt (CURLOPT_NOBODY, 0);
+
+      // Don't need to store the parameters here as we can't change
+      // the URL after the handle is created 
+      std::string query_string = form_query_string (param);
 
-  if (method == "get")
+      if (method == "get")
+	{
+	  query_string = url + "?" + query_string;
+	  setopt (CURLOPT_URL, query_string.c_str ());
+	}
+      else if (method == "post")
+	{
+	  setopt (CURLOPT_URL, url.c_str ());
+	  setopt (CURLOPT_POSTFIELDS, query_string.c_str ());
+	}
+      else
+	setopt (CURLOPT_URL, url.c_str());
+
+      if (!error_state)
+	retval = perform (false);
+    }
+
+  curl_handle (const curl_handle& h) : rep (h.rep)
     {
-      query_string = url + "?" + query_string;
-      curl_easy_setopt (curl, CURLOPT_URL, query_string.c_str ());
+      rep->count++;
+    }
+
+  ~curl_handle (void)
+    {
+      if (--rep->count == 0)
+	delete rep;
+    }
+
+  curl_handle& operator = (const curl_handle& h)
+    {
+      if (this != &h)
+	{
+	  if (--rep->count == 0)
+	    delete rep;
+
+	  rep = h.rep;
+	  rep->count++;
+	}
+      return *this;
+    }
+
+  bool is_valid (void) const
+    {
+      return rep->is_valid ();
     }
-  else if (method == "post")
+
+  std::string lasterror (void) const
+    {
+      CURLcode errno;
+
+      curl_easy_getinfo (rep->handle(), CURLINFO_OS_ERRNO, &errno);
+      
+      return std::string (curl_easy_strerror (errno));
+    }
+
+  void set_ostream (std::ostream& os) const
+    {
+      setopt (CURLOPT_WRITEDATA, static_cast<void*> (&os));
+    }
+
+  void set_istream (std::istream& is) const
     {
-      curl_easy_setopt (curl, CURLOPT_URL, url.c_str ());
-      curl_easy_setopt (curl, CURLOPT_POSTFIELDS, query_string.c_str ());
+      setopt (CURLOPT_READDATA, static_cast<void*> (&is));
+    }
+
+  void ascii (void) const
+    {
+      setopt (CURLOPT_TRANSFERTEXT, 1);
+      rep->ascii = true;
+    } 
+
+  void binary (void) const
+    {
+      setopt (CURLOPT_TRANSFERTEXT, 0);
+      rep->ascii = false;
+    } 
+
+  bool is_ascii (void) const
+    {
+      return rep->is_ascii ();
+    }
+
+  bool is_binary (void) const
+    {
+      return rep->is_binary ();
     }
-  else
-    curl_easy_setopt (curl, CURLOPT_URL, url.c_str());
+
+  void cwd (const std::string& path) const
+    {
+      struct curl_slist *slist = 0;
+      std::string cmd = "cwd " + path;
+      slist = curl_slist_append (slist, cmd.c_str());
+      setopt (CURLOPT_POSTQUOTE, slist);
+      if (! error_state)
+	perform ();
+      setopt (CURLOPT_POSTQUOTE, 0);
+      curl_slist_free_all (slist);
+    }
 
-  // Define our callback to get called when there's data to be written.
-  curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, write_data);
+  void del (const std::string& file) const
+    {
+      struct curl_slist *slist = 0;
+      std::string cmd = "dele " + file;
+      slist = curl_slist_append (slist, cmd.c_str());
+      setopt (CURLOPT_POSTQUOTE, slist);
+      if (! error_state)
+	perform ();
+      setopt (CURLOPT_POSTQUOTE, 0);
+      curl_slist_free_all (slist);
+    }
+
+  void rmdir (const std::string& path) const
+    {
+      struct curl_slist *slist = 0;
+      std::string cmd = "rmd " + path;
+      slist = curl_slist_append (slist, cmd.c_str());
+      setopt (CURLOPT_POSTQUOTE, slist);
+      if (! error_state)
+	perform ();
+      setopt (CURLOPT_POSTQUOTE, 0);
+      curl_slist_free_all (slist);
+    }
 
-  // Set a pointer to our struct to pass to the callback.
-  curl_easy_setopt (curl, CURLOPT_WRITEDATA, static_cast<void*> (&stream));
+  bool mkdir (const std::string& path, bool curlerror = true) const
+    {
+      bool retval = false;
+      struct curl_slist *slist = 0;
+      std::string cmd = "mkd " + path;
+      slist = curl_slist_append (slist, cmd.c_str());
+      setopt (CURLOPT_POSTQUOTE, slist);
+      if (! error_state)
+	retval = perform (curlerror);
+      setopt (CURLOPT_POSTQUOTE, 0);
+      curl_slist_free_all (slist);
+      return retval;
+    }
 
-  // Follow redirects.
-  curl_easy_setopt (curl, CURLOPT_FOLLOWLOCATION, true);
+  void rename (const std::string& oldname, const std::string& newname) const
+    {
+      struct curl_slist *slist = 0;
+      std::string cmd = "rnfr " + oldname;
+      slist = curl_slist_append (slist, cmd.c_str());
+      cmd = "rnto " + newname;
+      slist = curl_slist_append (slist, cmd.c_str());
+      setopt (CURLOPT_POSTQUOTE, slist);
+      if (! error_state)
+	perform ();
+      setopt (CURLOPT_POSTQUOTE, 0);
+      curl_slist_free_all (slist);
+    }
+
+  void put (const std::string& file, std::istream& is) const
+    {
+      std::string url = "ftp://" + rep->host + "/" + file;
+      setopt (CURLOPT_URL, url.c_str());
+      setopt (CURLOPT_UPLOAD, 1);
+      setopt (CURLOPT_NOBODY, 0);
+      set_istream (is);
+      if (! error_state)
+	perform ();
+      set_istream (std::cin);
+      setopt (CURLOPT_NOBODY, 1);
+      setopt (CURLOPT_UPLOAD, 0);
+      url = "ftp://" + rep->host;
+      setopt (CURLOPT_URL, url.c_str());
+    }
 
-  // Don't use EPSV since connecting to sites that don't support it
-  // will hang for some time (3 minutes?) before moving on to try PASV
-  // instead.
-  curl_easy_setopt (curl, CURLOPT_FTP_USE_EPSV, false);
+  void get (const std::string& file, std::ostream& os) const
+    {
+      std::string url = "ftp://" + rep->host + "/" + file;
+      setopt (CURLOPT_URL, url.c_str());
+      setopt (CURLOPT_NOBODY, 0);
+      set_ostream (os);
+      if (! error_state)
+	perform ();
+      set_ostream (octave_stdout);
+      setopt (CURLOPT_NOBODY, 1);
+      url = "ftp://" + rep->host;
+      setopt (CURLOPT_URL, url.c_str());
+    }
+
+  void dir (void) const
+    {
+      std::string url = "ftp://" + rep->host + "/";
+      setopt (CURLOPT_URL, url.c_str());
+      setopt (CURLOPT_NOBODY, 0);
+      if (! error_state)
+	perform ();
+      setopt (CURLOPT_NOBODY, 1);
+      url = "ftp://" + rep->host;
+      setopt (CURLOPT_URL, url.c_str());
+    }
 
-  curl_easy_setopt (curl, CURLOPT_NOPROGRESS, true);
-  curl_easy_setopt (curl, CURLOPT_PROGRESSDATA, url.c_str ());
-  curl_easy_setopt (curl, CURLOPT_FAILONERROR, true);
+  string_vector list (void) const
+    {
+      std::ostringstream buf;
+      std::string url = "ftp://" + rep->host + "/";
+      setopt (CURLOPT_WRITEDATA, static_cast<void*> (&buf));
+      setopt (CURLOPT_URL, url.c_str());
+      setopt (CURLOPT_DIRLISTONLY, 1);
+      setopt (CURLOPT_NOBODY, 0);
+      if (! error_state)
+	perform ();
+      setopt (CURLOPT_NOBODY, 1);
+      url = "ftp://" + rep->host;
+      setopt (CURLOPT_WRITEDATA, static_cast<void*> (&octave_stdout));
+      setopt (CURLOPT_DIRLISTONLY, 0);
+      setopt (CURLOPT_URL, url.c_str());
+
+      // Count number of directory entries
+      std::string str = buf.str ();
+      octave_idx_type n = 0;
+      size_t pos = 0;
+      while (true)
+	{
+	  pos = str.find_first_of('\n', pos);
+	  if (pos == std::string::npos)
+	    break;
+	  pos++;
+	  n++;
+	}
+      string_vector retval (n);
+      pos = 0;
+      for (octave_idx_type i = 0; i < n; i++)
+	{
+	  size_t newpos = str.find_first_of('\n', pos);
+	  if (newpos == std::string::npos)
+	    break;
+
+	  retval(i) = str.substr(pos, newpos - pos);
+	  pos = newpos + 1;
+	}
+      return retval;
+    }
+
+  void get_fileinfo (const std::string& filename, double& filesize, 
+		     time_t& filetime, bool& fileisdir) const
+    {
+      std::string path = pwd();
 
-  // Switch on full protocol/debug output.
-  // curl_easy_setopt (curl, CURLOPT_VERBOSE, true);
+      std::string url = "ftp://" + rep->host + "/" + path + "/" + filename;
+      setopt (CURLOPT_URL, url.c_str());
+      setopt (CURLOPT_FILETIME, 1); 
+      setopt (CURLOPT_HEADERFUNCTION, throw_away);
+      setopt (CURLOPT_WRITEFUNCTION, throw_away);
 
-  CURLcode res = CURLE_OK;
+      // FIXME
+      // The MDTM command fails for a directory on the servers I tested
+      // so this is a means of testing for directories. It also means
+      // I can't get the date of directories!
+      if (! error_state)
+	{
+	  if (! perform (false))
+	    {
+	      fileisdir = true;
+	      filetime = -1;
+	      filesize = 0;
+	    }
+	  else
+	    {
+	      fileisdir = false;
+	      time_t ft;
+	      curl_easy_getinfo(rep->handle (), CURLINFO_FILETIME, &ft);
+	      filetime = ft;
+	      double fs;
+	      curl_easy_getinfo(rep->handle (), 
+				CURLINFO_CONTENT_LENGTH_DOWNLOAD, &fs);
+	      filesize = fs;
+	    }
+	}
 
-  // To understand the following, see the definitions of these macros
-  // in libcruft/misc/quit.h.  The idea is that we call sigsetjmp here
-  // then the signal handler calls siglongjmp to get back here
-  // immediately.  Then we perform some cleanup and throw an interrupt
-  // exception which will get us back to the top level, cleaning up
-  // any local C++ objects on the stack as we go.
+      setopt (CURLOPT_WRITEFUNCTION, write_data);
+      setopt (CURLOPT_HEADERFUNCTION, 0);
+      setopt (CURLOPT_FILETIME, 0); 
+      url = "ftp://" + rep->host;
+      setopt (CURLOPT_URL, url.c_str());
+
+      // The MDTM command seems to reset the path to the root with the
+      // servers I tested with, so cd again into the correct path. Make
+      // the path absolute so that this will work even with servers that
+      // don't end up in the root after an MDTM command.
+      cwd ("/" + path);
+    }
+
+  std::string pwd (void) const
+    {
+      struct curl_slist *slist = 0;
+      std::string retval;
+      std::ostringstream buf;
 
-  BEGIN_INTERRUPT_IMMEDIATELY_IN_FOREIGN_CODE_1;
+      slist = curl_slist_append (slist, "pwd");
+      setopt (CURLOPT_POSTQUOTE, slist);
+      setopt (CURLOPT_HEADERFUNCTION, write_data);
+      setopt (CURLOPT_WRITEHEADER, static_cast<void *>(&buf));
+
+      if (! error_state)
+	{
+	  perform ();
+	  retval = buf.str();
+
+	  // Can I assume that the path is alway in "" on the last line
+	  size_t pos2 = retval.rfind ('"');
+	  size_t pos1 = retval.rfind ('"', pos2 - 1);
+	  retval = retval.substr(pos1 + 1, pos2 - pos1 - 1);
+	}
+      setopt (CURLOPT_HEADERFUNCTION, 0);
+      setopt (CURLOPT_WRITEHEADER, 0);
+      setopt (CURLOPT_POSTQUOTE, 0);
+      curl_slist_free_all (slist);
+
+      return retval;
+    }
 
-  // We were interrupted (this code is inside a block that is only
-  // called when siglongjmp is called from a signal handler).
+  bool perform (bool curlerror = true) const
+    {
+      return rep->perform (curlerror);
+    }
+
+private:
+  curl_handle_rep *rep;
+
+  std::string form_query_string (const Cell& param)
+    {
+      std::ostringstream query;
+
+      for (int i = 0; i < param.numel (); i += 2)
+	{
+	  std::string name = param(i).string_value ();
+	  std::string text = param(i+1).string_value ();
+
+	  // Encode strings.
+	  char *enc_name = curl_easy_escape (rep->handle(), name.c_str (), 
+					     name.length ());
+	  char *enc_text = curl_easy_escape (rep->handle(), text.c_str (), 
+					     text.length ());
+
+	  query << enc_name << "=" << enc_text;
+
+	  curl_free (enc_name);
+	  curl_free (enc_text);
+
+	  if (i < param.numel()-1)
+	    query << "&";
+	}
+
+      query.flush ();
 
-  // Is there a better error code to use?  Maybe it doesn't matter
-  // because we are about to throw an execption.
+      return query.str ();
+    }
+
+  void init (const std::string& user, const std::string& passwd, 
+	     std::istream& is, std::ostream& os) 
+    {
+      // No data transfer by default
+      setopt (CURLOPT_NOBODY, 1);
+
+      // Set the username and password
+      if (user.length () != 0)
+	setopt (CURLOPT_USERNAME, user.c_str());
+      if (passwd.length () != 0)
+	setopt (CURLOPT_PASSWORD, passwd.c_str());
+
+      // Define our callback to get called when there's data to be written.
+      setopt (CURLOPT_WRITEFUNCTION, write_data);
 
-  res = CURLE_ABORTED_BY_CALLBACK;
-  urlget_cleanup (curl);
-  octave_rethrow_exception ();
+      // Set a pointer to our struct to pass to the callback.
+      setopt (CURLOPT_WRITEDATA, static_cast<void*> (&os));
+
+      // Define our callback to get called when there's data to be read
+      setopt (CURLOPT_READFUNCTION, read_data);
+
+      // Set a pointer to our struct to pass to the callback.
+      setopt (CURLOPT_READDATA, static_cast<void*> (&is));
+
+      // Follow redirects.
+      setopt (CURLOPT_FOLLOWLOCATION, true);
+
+      // Don't use EPSV since connecting to sites that don't support it
+      // will hang for some time (3 minutes?) before moving on to try PASV
+      // instead.
+      setopt (CURLOPT_FTP_USE_EPSV, false);
+
+      setopt (CURLOPT_NOPROGRESS, true);
+      setopt (CURLOPT_FAILONERROR, true);
 
-  BEGIN_INTERRUPT_IMMEDIATELY_IN_FOREIGN_CODE_2;
+      setopt (CURLOPT_POSTQUOTE, 0);
+      setopt (CURLOPT_QUOTE, 0);
+    }
+
+#undef setopt
+};
+
+class
+curl_handles
+{
+public:
+
+  typedef std::map<std::string, curl_handle>::iterator iterator;
+  typedef std::map<std::string, curl_handle>::const_iterator const_iterator;
 
-  res = curl_easy_perform (curl);
+  curl_handles (void) : map ()
+   {
+     curl_global_init(CURL_GLOBAL_DEFAULT);
+   }
+  
+  ~curl_handles (void) 
+    {
+      // Remove the elements of the map explicitly as they should
+      // be deleted before the call to curl_global_cleanup
+      for (iterator pa = begin (); pa != end (); pa++)
+	map.erase (pa);
 
-  END_INTERRUPT_IMMEDIATELY_IN_FOREIGN_CODE;
+      curl_global_cleanup ();
+    }
+
+  iterator begin (void) { return iterator (map.begin ()); }
+  const_iterator begin (void) const { return const_iterator (map.begin ()); }
+
+  iterator end (void) { return iterator (map.end ()); }
+  const_iterator end (void) const { return const_iterator (map.end ()); }
 
-  // If we are not interuppted, we will end up here, so we still need
-  // to clean up.
+  iterator seek (const std::string& k) { return map.find (k); }
+  const_iterator seek (const std::string& k) const { return map.find (k); }
+
+  std::string key (const_iterator p) const { return p->first; }
+
+  curl_handle& contents (const std::string& k) 
+    {
+      return map[k];
+    }
+
+  curl_handle contents (const std::string& k) const
+    {
+      const_iterator p = seek (k);
+      return p != end () ? p->second : curl_handle ();
+    }
+
+  curl_handle& contents (iterator p)
+    { return p->second; }
 
-  urlget_cleanup (curl);
+  curl_handle contents (const_iterator p) const
+    { return p->second; }
+
+  void del (const std::string& k)
+    {
+      iterator p = map.find (k);
+
+      if (p != map.end ())
+	map.erase (p);
+    }
 
-  return res;
+private:
+  std::map<std::string, curl_handle> map;
+};
+
+static curl_handles handles;
+
+static void 
+cleanup_urlwrite (std::string filename)
+{
+  file_ops::unlink (filename);
 }
 
-#endif
-
-static bool urlwrite_delete_file;
-
-static std::string urlwrite_filename;
+static void 
+reset_path (const curl_handle curl)
+{
+  curl.cwd ("..");
+}
 
-static void
-urlwrite_cleanup_file (void *)
+void delete_file (std::string file)
 {
-  if (urlwrite_delete_file)
-    file_ops::unlink (urlwrite_filename);
+  file_ops::unlink (file);
 }
+#endif
 
 DEFUN_DLD (urlwrite, args, nargout,
   "-*- texinfo -*-\n\
-@deftypefn {Loadable Function} {} urlwrite (@var{URL}, @var{localfile})\n\
+@deftypefn {Loadable Function} {} urlwrite (@var{url}, @var{localfile})\n\
 @deftypefnx {Loadable Function} {@var{f} =} urlwrite (@var{url}, @var{localfile})\n\
 @deftypefnx {Loadable Function} {[@var{f}, @var{success}] =} urlwrite (@var{url}, @var{localfile})\n\
 @deftypefnx {Loadable Function} {[@var{f}, @var{success}, @var{message}] =} urlwrite (@var{url}, @var{localfile})\n\
-Download a remote file specified by its @var{URL} and save it as\n\
+Download a remote file specified by its @var{url} and save it as\n\
 @var{localfile}.  For example,\n\
 \n\
 @example\n\
@@ -270,8 +748,6 @@
   // name to store the file if download is succesful
   std::string filename = args(1).string_value();
 
-  urlwrite_filename = filename;
-
   if (error_state)
     {
       error ("urlwrite: localfile must be a character string");
@@ -319,8 +795,6 @@
 
   file_stat fs (filename);
 
-  urlwrite_delete_file = ! fs.exists ();
-
   std::ofstream ofile (filename.c_str(), std::ios::out | std::ios::binary);
 
   if (! ofile.is_open ())
@@ -329,19 +803,23 @@
       return retval;
     }
 
-  unwind_protect::add (urlwrite_cleanup_file);
+  unwind_protect::frame_id_t uwp_frame = unwind_protect::begin_frame ();
 
-  CURLcode res = urlget (url, method, param, ofile);
+  unwind_protect::add_fcn (cleanup_urlwrite, filename);
+
+  bool res;
+  curl_handle curl = curl_handle (url, method, param, ofile, res);
 
   ofile.close ();
 
-  urlwrite_delete_file = (res != CURLE_OK);
-
-  unwind_protect::run ();
+  if (!error_state)
+    unwind_protect::discard_frame (uwp_frame);
+  else
+    unwind_protect::run_frame (uwp_frame);
 
   if (nargout > 0)
     {
-      if (res == CURLE_OK)
+      if (res)
 	{
 	  retval(2) = std::string ();
 	  retval(1) = true;
@@ -349,14 +827,14 @@
 	}
       else
 	{
-	  retval(2) = std::string (curl_easy_strerror (res));
+	  retval(2) = curl.lasterror ();
 	  retval(1) = false;
 	  retval(0) = std::string ();
 	}
     }
 
-  if (nargout < 2 && res != CURLE_OK)
-    error ("urlwrite: curl: %s", curl_easy_strerror (res));
+  if (nargout < 2 && res)
+    error ("urlwrite: curl: %s", curl.lasterror ().c_str ());
 
 #else
   error ("urlwrite: not available in this version of Octave");
@@ -371,7 +849,7 @@
 @deftypefnx {Loadable Function} {[@var{s}, @var{success}] =} urlread (@var{url})\n\
 @deftypefnx {Loadable Function} {[@var{s}, @var{success}, @var{message}] =} urlread (@var{url})\n\
 @deftypefnx {Loadable Function} {[@dots{}] =} urlread (@var{url}, @var{method}, @var{param})\n\
-Download a remote file specified by its @var{URL} and return its content\n\
+Download a remote file specified by its @var{url} and return its content\n\
 in string @var{s}.  For example,\n\
 \n\
 @example\n\
@@ -463,18 +941,19 @@
 
   std::ostringstream buf;
 
-  CURLcode res = urlget (url, method, param, buf);
+  bool res;
+  curl_handle curl = curl_handle (url, method, param, buf, res);
 
   if (nargout > 0)
     {
       retval(0) = buf.str ();
-      retval(1) = res == CURLE_OK;
+      retval(1) = res;
       // Return empty string if no error occured.
-      retval(2) = std::string (res == CURLE_OK ? "" : curl_easy_strerror (res));
+      retval(2) = res ? "" : curl.lasterror ();
     }
 
-  if (nargout < 2 && res != CURLE_OK)
-    error ("urlread: curl: %s", curl_easy_strerror (res));
+  if (nargout < 2 && !res)
+    error ("urlread: curl: %s", curl.lasterror().c_str());
 
 #else
   error ("urlread: not available in this version of Octave");
@@ -482,3 +961,782 @@
 
   return retval;
 }
+
+DEFUN_DLD (__ftp__, args, ,
+  "-*- texinfo -*-\n\
+@deftypefn {Loadable Function} {} __ftp__ (@var{handle}, @var{host})\n\
+@deftypefnx {Loadable Function} {} __ftp__ (@var{handle}, @var{host}, @var{username}, @var{password})\n\
+Undocumented internal function\n\
+@end deftypefn")
+{
+#ifdef HAVE_CURL
+  int nargin = args.length ();
+  std::string handle;
+  std::string host;
+  std::string user = "anonymous";
+  std::string passwd = "";
+
+  if (nargin < 2 || nargin > 4)
+    error ("incorrect number of arguments");
+  else
+    {
+      handle = args(0).string_value ();
+      host = args(1).string_value ();
+  
+      if (nargin > 1)
+	user = args(2).string_value ();
+
+      if (nargin > 2)
+	passwd = args(3).string_value ();
+  
+      if (!error_state)
+	{
+	  handles.contents (handle) = curl_handle (host, user, passwd);
+	  
+	  if (error_state)
+	    handles.del (handle);
+	}
+    }
+#else
+  error ("__ftp__: not available in this version of Octave");
+#endif
+
+  return octave_value ();
+}
+
+DEFUN_DLD (__ftp_pwd__, args, ,
+  "-*- texinfo -*-\n\
+@deftypefn {Loadable Function} {} __ftp_pwd__ (@var{handle})\n\
+Undocumented internal function\n\
+@end deftypefn")
+{
+  octave_value retval;
+#ifdef HAVE_CURL
+  int nargin = args.length ();
+
+  if (nargin != 1)
+    error ("incorrect number of arguments");
+  else
+    {
+      std::string handle = args(0).string_value ();
+
+      if (!error_state)
+	{
+	  const curl_handle curl = handles.contents (handle);
+
+	  if (curl.is_valid ())
+	    retval = curl.pwd ();
+	  else
+	    error ("__ftp_pwd__: invalid ftp handle");
+	}
+    }
+#else
+  error ("__ftp_pwd__: not available in this version of Octave");
+#endif
+
+  return retval;
+}
+
+DEFUN_DLD (__ftp_cwd__, args, ,
+  "-*- texinfo -*-\n\
+@deftypefn {Loadable Function} {} __ftp_cwd__ (@var{handle}, @var{path})\n\
+Undocumented internal function\n\
+@end deftypefn")
+{
+#ifdef HAVE_CURL
+  int nargin = args.length ();
+
+  if (nargin != 1 && nargin != 2)
+    error ("incorrect number of arguments");
+  else
+    {
+      std::string handle = args(0).string_value ();
+      std::string path = "";
+
+      if (nargin > 1)
+	path  = args(1).string_value ();
+
+      if (!error_state)
+	{
+	  const curl_handle curl = handles.contents (handle);
+
+	  if (curl.is_valid ())
+	    curl.cwd (path);
+	  else
+	    error ("__ftp_cwd__: invalid ftp handle");
+	}
+    }
+#else
+  error ("__ftp_cwd__: not available in this version of Octave");
+#endif
+
+  return octave_value ();
+}
+
+DEFUN_DLD (__ftp_dir__, args, nargout,
+  "-*- texinfo -*-\n\
+@deftypefn {Loadable Function} {} __ftp_dir__ (@var{handle})\n\
+Undocumented internal function\n\
+@end deftypefn")
+{
+  octave_value retval;
+#ifdef HAVE_CURL
+  int nargin = args.length ();
+
+  if (nargin != 1)
+    error ("incorrect number of arguments");
+  else
+    {
+      std::string handle = args(0).string_value ();
+
+      if (!error_state)
+	{
+	  const curl_handle curl = handles.contents (handle);
+
+	  if (curl.is_valid ())
+	    {
+	      if (nargout == 0)
+		curl.dir ();
+	      else
+		{
+		  string_vector sv = curl.list ();
+		  octave_idx_type n = sv.length ();
+		  if (n == 0)
+		    {
+		      string_vector flds (5);
+		      flds(0) = "name";
+		      flds(1) = "date";
+		      flds(2) = "bytes";
+		      flds(3) = "isdir";
+		      flds(4) = "datenum";
+		      retval = Octave_map (flds); 
+		    }
+		  else
+		    {
+		      Octave_map st;
+		      Cell filectime (dim_vector (n, 1));
+		      Cell filesize (dim_vector (n, 1));
+		      Cell fileisdir (dim_vector (n, 1));
+		      Cell filedatenum (dim_vector (n, 1));
+		    
+		      st.assign ("name", Cell (sv));
+
+		      for (octave_idx_type i = 0; i < n; i++)
+			{
+			  time_t ftime;
+			  bool fisdir;
+			  double fsize;
+		      
+			  curl.get_fileinfo (sv(i), fsize, ftime, fisdir);
+
+			  fileisdir (i) = fisdir;
+			  filectime (i) = ctime (&ftime);
+			  filesize (i) = fsize;
+			  filedatenum (i) = double (ftime);
+			}
+		      st.assign ("date", filectime);
+		      st.assign ("bytes", filesize);
+		      st.assign ("isdir", fileisdir);
+		      st.assign ("datenum", filedatenum);
+		      retval = st;
+		    }
+		}
+	    }
+	  else
+	    error ("__ftp_dir__: invalid ftp handle");
+	}
+    }
+#else
+  error ("__ftp_dir__: not available in this version of Octave");
+#endif
+
+  return retval;
+}
+
+DEFUN_DLD (__ftp_ascii__, args, ,
+  "-*- texinfo -*-\n\
+@deftypefn {Loadable Function} {} __ftp_ascii__ (@var{handle})\n\
+Undocumented internal function\n\
+@end deftypefn")
+{
+#ifdef HAVE_CURL
+  int nargin = args.length ();
+
+  if (nargin != 1)
+    error ("incorrect number of arguments");
+  else
+    {
+      std::string handle = args(0).string_value ();
+
+      if (!error_state)
+	{
+	  const curl_handle curl = handles.contents (handle);
+
+	  if (curl.is_valid ())
+	    curl.ascii ();
+	  else
+	    error ("__ftp_ascii__: invalid ftp handle");
+	}
+    }
+#else
+  error ("__ftp_ascii__: not available in this version of Octave");
+#endif
+
+  return octave_value ();
+}
+
+DEFUN_DLD (__ftp_binary__, args, ,
+  "-*- texinfo -*-\n\
+@deftypefn {Loadable Function} {} __ftp_binary__ (@var{handle})\n\
+Undocumented internal function\n\
+@end deftypefn")
+{
+#ifdef HAVE_CURL
+  int nargin = args.length ();
+
+  if (nargin != 1)
+    error ("incorrect number of arguments");
+  else
+    {
+      std::string handle = args(0).string_value ();
+
+      if (!error_state)
+	{
+	  const curl_handle curl = handles.contents (handle);
+
+	  if (curl.is_valid ())
+	    curl.binary ();
+	  else
+	    error ("__ftp_binary__: invalid ftp handle");
+	}
+    }
+#else
+  error ("__ftp_binary__: not available in this version of Octave");
+#endif
+
+  return octave_value ();
+}
+
+DEFUN_DLD (__ftp_close__, args, ,
+  "-*- texinfo -*-\n\
+@deftypefn {Loadable Function} {} __ftp_close__ (@var{handle})\n\
+ Undocumented internal function\n\
+ @end deftypefn")
+ {
+ #ifdef HAVE_CURL
+   int nargin = args.length ();
+
+   if (nargin != 1)
+     error ("incorrect number of arguments");
+   else
+     {
+       std::string handle = args(0).string_value ();
+
+       if (!error_state)
+	 handles.del (handle);
+     }
+ #else
+   error ("__ftp_close__: not available in this version of Octave");
+ #endif
+
+   return octave_value ();
+ }
+
+DEFUN_DLD (__ftp_mode__, args, ,
+  "-*- texinfo -*-\n\
+@deftypefn {Loadable Function} {} __ftp_mode__ (@var{handle})\n\
+ Undocumented internal function\n\
+ @end deftypefn")
+ {
+   octave_value retval;
+ #ifdef HAVE_CURL
+   int nargin = args.length ();
+
+   if (nargin != 1)
+     error ("incorrect number of arguments");
+   else
+     {
+       std::string handle = args(0).string_value ();
+
+
+      if (!error_state)
+	{
+	  const curl_handle curl = handles.contents (handle);
+
+	  if (curl.is_valid ())
+	    retval = (curl.is_ascii() ? "ascii" : "binary");
+	  else
+	    error ("__ftp_binary__: invalid ftp handle");
+	}
+     }
+ #else
+   error ("__ftp_mode__: not available in this version of Octave");
+ #endif
+
+   return retval;
+ }
+
+DEFUN_DLD (__ftp_delete__, args, ,
+  "-*- texinfo -*-\n\
+@deftypefn {Loadable Function} {} __ftp_delete__ (@var{handle}, @var{path})\n\
+Undocumented internal function\n\
+@end deftypefn")
+{
+#ifdef HAVE_CURL
+  int nargin = args.length ();
+
+  if (nargin != 2)
+    error ("incorrect number of arguments");
+  else
+    {
+      std::string handle = args(0).string_value ();
+      std::string file = args(1).string_value ();
+
+      if (!error_state)
+	{
+	  const curl_handle curl = handles.contents (handle);
+
+	  if (curl.is_valid ())
+	    curl.del (file);
+	  else
+	    error ("__ftp_delete__: invalid ftp handle");
+	}
+    }
+#else
+  error ("__ftp_delete__: not available in this version of Octave");
+#endif
+
+  return octave_value ();
+}
+
+DEFUN_DLD (__ftp_rmdir__, args, ,
+  "-*- texinfo -*-\n\
+@deftypefn {Loadable Function} {} __ftp_rmdir__ (@var{handle}, @var{path})\n\
+Undocumented internal function\n\
+@end deftypefn")
+{
+#ifdef HAVE_CURL
+  int nargin = args.length ();
+
+  if (nargin != 2)
+    error ("incorrect number of arguments");
+  else
+    {
+      std::string handle = args(0).string_value ();
+      std::string dir = args(1).string_value ();
+
+      if (!error_state)
+	{
+	  const curl_handle curl = handles.contents (handle);
+
+	  if (curl.is_valid ())
+	    curl.rmdir (dir);
+	  else
+	    error ("__ftp_rmdir__: invalid ftp handle");
+	}
+    }
+#else
+  error ("__ftp_rmdir__: not available in this version of Octave");
+#endif
+
+  return octave_value ();
+}
+
+DEFUN_DLD (__ftp_mkdir__, args, ,
+  "-*- texinfo -*-\n\
+@deftypefn {Loadable Function} {} __ftp_mkdir__ (@var{handle}, @var{path})\n\
+Undocumented internal function\n\
+@end deftypefn")
+{
+#ifdef HAVE_CURL
+  int nargin = args.length ();
+
+  if (nargin != 2)
+    error ("incorrect number of arguments");
+  else
+    {
+      std::string handle = args(0).string_value ();
+      std::string dir = args(1).string_value ();
+
+      if (!error_state)
+	{
+	  const curl_handle curl = handles.contents (handle);
+
+	  if (curl.is_valid ())
+	    curl.mkdir (dir);
+	  else
+	    error ("__ftp_mkdir__: invalid ftp handle");
+	}
+    }
+#else
+  error ("__ftp_mkdir__: not available in this version of Octave");
+#endif
+
+  return octave_value ();
+}
+
+DEFUN_DLD (__ftp_rename__, args, ,
+  "-*- texinfo -*-\n\
+@deftypefn {Loadable Function} {} __ftp_rename__ (@var{handle}, @var{path})\n\
+Undocumented internal function\n\
+@end deftypefn")
+{
+#ifdef HAVE_CURL
+  int nargin = args.length ();
+
+  if (nargin != 3)
+    error ("incorrect number of arguments");
+  else
+    {
+      std::string handle = args(0).string_value ();
+      std::string oldname = args(1).string_value ();
+      std::string newname = args(2).string_value ();
+
+      if (!error_state)
+	{
+	  const curl_handle curl = handles.contents (handle);
+
+	  if (curl.is_valid ())
+	    curl.rename (oldname, newname);
+	  else
+	    error ("__ftp_rename__: invalid ftp handle");
+	}
+    }
+#else
+  error ("__ftp_rename__: not available in this version of Octave");
+#endif
+
+  return octave_value ();
+}
+
+static string_vector
+mput_directory (const curl_handle& curl, const std::string& base,
+		const std::string& dir)
+{
+  string_vector retval;
+
+  if (! curl.mkdir (dir, false))
+    warning ("__ftp_mput__: can not create the remote directory ""%s""",
+	     (base.length() == 0 ? dir : base + 
+	      file_ops::dir_sep_str () + dir).c_str ());
+
+  curl.cwd (dir);
+
+  if (! error_state)
+    {
+      unwind_protect::frame_id_t uwp_frame = 
+	unwind_protect::begin_frame ();
+
+      unwind_protect::add_fcn (reset_path, curl);
+
+      std::string realdir = base.length() == 0 ? dir : base + 
+			 file_ops::dir_sep_str () + dir;
+
+      dir_entry dirlist (realdir);
+
+      if (dirlist)
+	{
+	  string_vector files = dirlist.read ();
+
+	  for (octave_idx_type i = 0; i < files.length (); i++)
+	    {
+	      std::string file = files (i);
+
+	      if (file == "." || file == "..")
+		continue;
+
+	      std::string realfile = realdir + file_ops::dir_sep_str () + file;
+	      file_stat fs (realfile);
+
+	      if (! fs.exists ())
+		{
+		  error ("__ftp__mput: file ""%s"" does not exist", 
+			 realfile.c_str ());
+		  break;
+		}
+
+	      if (fs.is_dir ())
+		{
+		  retval.append (mput_directory (curl, realdir, file));
+
+		  if (error_state)
+		    break;
+		}
+	      else
+		{
+		  // FIXME Does ascii mode need to be flagged here?
+		  std::ifstream ifile (realfile.c_str(), std::ios::in | 
+				       std::ios::binary);
+
+		  if (! ifile.is_open ())
+		    {
+		      error ("__ftp_mput__: unable to open file ""%s""", 
+			     realfile.c_str ());
+		      break;
+		    }
+
+		  curl.put (file, ifile);
+
+		  ifile.close ();
+
+		  if (error_state)
+		    break;
+
+		  retval.append (realfile);
+		}
+	    }
+	}
+      else
+	error ("__ftp_mput__: can not read the directory ""%s""", 
+	       realdir.c_str());
+
+      unwind_protect::run_frame (uwp_frame);
+    }
+
+  return retval;
+}
+
+DEFUN_DLD (__ftp_mput__, args, nargout,
+  "-*- texinfo -*-\n\
+@deftypefn {Loadable Function} {} __ftp_mput__ (@var{handle}, @var{files})\n\
+Undocumented internal function\n\
+@end deftypefn")
+{
+  string_vector retval;
+
+#ifdef HAVE_CURL
+  int nargin = args.length ();
+
+  if (nargin != 2)
+    error ("incorrect number of arguments");
+  else
+    {
+      std::string handle = args(0).string_value ();
+      std::string pat = args(1).string_value ();
+
+      if (!error_state)
+	{
+	  const curl_handle curl = handles.contents (handle);
+
+	  if (curl.is_valid ())
+	    {
+	      glob_match pattern (file_ops::tilde_expand (pat));
+	      string_vector files = pattern.glob ();
+
+	      for (octave_idx_type i = 0; i < files.length (); i++)
+		{
+		  std::string file = files (i);
+
+		  file_stat fs (file);
+
+		  if (! fs.exists ())
+		    {
+		      error ("__ftp__mput: file does not exist");
+		      break;
+		    }
+
+		  if (fs.is_dir ())
+		    {
+		      retval.append (mput_directory (curl, "", file));
+		      if (error_state)
+			break;
+		    }
+		  else
+		    {
+		      // FIXME Does ascii mode need to be flagged here?
+		      std::ifstream ifile (file.c_str(), std::ios::in | 
+					   std::ios::binary);
+
+		      if (! ifile.is_open ())
+			{
+			  error ("__ftp_mput__: unable to open file");
+			  break;
+			}
+
+		      curl.put (file, ifile);
+
+		      ifile.close ();
+
+		      if (error_state)
+			break;
+
+		      retval.append (file);
+		    }
+		}
+	    }
+	  else
+	    error ("__ftp_mput__: invalid ftp handle");
+	}
+    }
+#else
+  error ("__ftp_mput__: not available in this version of Octave");
+#endif
+
+  return (nargout > 0 ? octave_value (retval) : octave_value ());
+}
+
+#ifdef HAVE_CURL
+static void
+getallfiles (const curl_handle& curl, const std::string& dir, 
+	     const std::string& target)
+{
+  std::string sep = file_ops::dir_sep_str ();
+  file_stat fs (dir);
+
+  if (!fs || !fs.is_dir ())
+    { 
+      std::string msg;
+      int status = file_ops::mkdir (dir, 0777, msg);
+
+      if (status < 0)
+	error ("__ftp_mget__: can't create directory %s%s%s. %s", 
+	       target.c_str(), sep.c_str(), dir.c_str(), msg.c_str());
+    }
+
+  if (! error_state)
+    {
+      curl.cwd (dir);
+
+      if (! error_state)
+	{
+	  unwind_protect::frame_id_t uwp_frame = 
+	    unwind_protect::begin_frame ();
+
+	  unwind_protect::add_fcn (reset_path, curl);
+
+	    string_vector sv = curl.list ();
+
+	  for (octave_idx_type i = 0; i < sv.length (); i++)
+	    {
+	      time_t ftime;
+	      bool fisdir;
+	      double fsize;
+		      
+	      curl.get_fileinfo (sv(i), fsize, ftime, fisdir);
+
+	      if (fisdir)
+		getallfiles (curl, sv(i), target + dir + sep);
+	      else
+		{
+		  std::string realfile = target + dir + sep + sv(i);
+		  std::ofstream ofile (realfile.c_str(), 
+				       std::ios::out | 
+				       std::ios::binary);
+
+		  if (! ofile.is_open ())
+		    {
+		      error ("__ftp_mget__: unable to open file");
+		      break;
+		    }
+
+		  unwind_protect::frame_id_t uwp_frame2 = 
+		    unwind_protect::begin_frame ();
+
+		  unwind_protect::add_fcn (delete_file, realfile);
+
+		  curl.get (sv(i), ofile);
+
+		  ofile.close ();
+
+		  if (!error_state)
+		    unwind_protect::discard_frame (uwp_frame2);
+		  else
+		    unwind_protect::run_frame (uwp_frame2);
+		}
+
+	      if (error_state)
+		break;
+	    }
+
+	  unwind_protect::run_frame (uwp_frame);
+	}
+    }
+}
+#endif
+
+DEFUN_DLD (__ftp_mget__, args, ,
+  "-*- texinfo -*-\n\
+@deftypefn {Loadable Function} {} __ftp_mget__ (@var{handle}, @var{files})\n\
+Undocumented internal function\n\
+@end deftypefn")
+{
+#ifdef HAVE_CURL
+  int nargin = args.length ();
+
+  if (nargin != 2 && nargin != 3)
+    error ("incorrect number of arguments");
+  else
+    {
+      std::string handle = args(0).string_value ();
+      std::string file = args(1).string_value ();
+      std::string target;
+
+      if (nargin == 3)
+	target = args(2).string_value () + file_ops::dir_sep_str ();
+
+      if (! error_state)
+	{
+	  const curl_handle curl = handles.contents (handle);
+
+	  if (curl.is_valid ())
+	    {
+	      string_vector sv = curl.list ();
+	      octave_idx_type n = 0;
+	      glob_match pattern (file);
+
+	      for (octave_idx_type i = 0; i < sv.length (); i++)
+		{
+		  if (pattern.match (sv(i)))
+		    {
+		      n++;
+
+		      time_t ftime;
+		      bool fisdir;
+		      double fsize;
+		      
+		      curl.get_fileinfo (sv(i), fsize, ftime, fisdir);
+
+		      if (fisdir)
+			getallfiles (curl, sv(i), target);
+		      else
+			{
+			  std::ofstream ofile ((target + sv(i)).c_str(),
+					       std::ios::out | 
+					       std::ios::binary);
+
+			  if (! ofile.is_open ())
+			    {
+			      error ("__ftp_mget__: unable to open file");
+			      break;
+			    }
+
+			  unwind_protect::frame_id_t uwp_frame = 
+			    unwind_protect::begin_frame ();
+
+			  unwind_protect::add_fcn (delete_file, target + sv(i));
+
+			  curl.get (sv(i), ofile);
+
+			  ofile.close ();
+
+			  if (!error_state)
+			    unwind_protect::discard_frame (uwp_frame);
+			  else
+			    unwind_protect::run_frame (uwp_frame);
+			}
+
+		      if (error_state)
+			break;
+		    }
+		}
+	      if (n == 0)
+		error ("__ftp_mget__: file not found");
+	    }
+	}
+    }
+#else
+  error ("__ftp_mget__: not available in this version of Octave");
+#endif
+
+  return octave_value ();
+}