changeset 9880:7f77e5081e83

Add ftp objects
author David Bateman <dbateman@free.fr>
date Sat, 28 Nov 2009 11:44:57 +0100
parents 034677ab6865
children b3089dba88bf
files ChangeLog NEWS configure.ac doc/ChangeLog doc/interpreter/install.txi doc/interpreter/system.txi scripts/@ftp/.dirstamp scripts/@ftp/PKG_ADD scripts/@ftp/ascii.m scripts/@ftp/binary.m scripts/@ftp/cd.m scripts/@ftp/close.m scripts/@ftp/delete.m scripts/@ftp/dir.m scripts/@ftp/display.m scripts/@ftp/ftp.m scripts/@ftp/loadobj.m scripts/@ftp/mget.m scripts/@ftp/mkdir.m scripts/@ftp/module.mk scripts/@ftp/mput.m scripts/@ftp/rename.m scripts/@ftp/rmdir.m scripts/@ftp/saveobj.m scripts/ChangeLog scripts/Makefile.am src/ChangeLog src/DLD-FUNCTIONS/urlwrite.cc
diffstat 26 files changed, 1864 insertions(+), 131 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Fri Nov 27 14:42:07 2009 +0100
+++ b/ChangeLog	Sat Nov 28 11:44:57 2009 +0100
@@ -1,3 +1,8 @@
+2009-11-27  David Bateman  <dbateman@free.fr>
+
+	* NEWS: Document ftp objects.
+	* configure.ac: Document that curl libraries impact ftp objects.
+
 2009-11-26  Jaroslav Hajek  <highegg@gmail.com>
 
 	* NEWS: Update.
--- a/NEWS	Fri Nov 27 14:42:07 2009 +0100
+++ b/NEWS	Sat Nov 28 11:44:57 2009 +0100
@@ -28,6 +28,15 @@
    
       matrix_sum = plus (matrix_list{:});
 
+ ** An FTP object type based on libcurl has been implemented. These objects
+    allow ftp connections, downloads and uploads to be managed. An example of
+    its use might be
+
+    fp = ftp ("ftp.octave.org);
+    cd (fp, "gnu/octave");
+    mget (fp, "octave-3.2.3.tar.bz2");
+    close (fp);
+
  ** The default behavior of assert (observed, expected) has been relaxed
     to employ less strict checking that does not require the internals
     of the values to match. This avoids previously valid tests from
--- a/configure.ac	Fri Nov 27 14:42:07 2009 +0100
+++ b/configure.ac	Sat Nov 28 11:44:57 2009 +0100
@@ -683,7 +683,7 @@
 save_LIBS="$LIBS"
 LIBS="$Z_LDFLAGS $Z_LIBS $LIBS"
 OCTAVE_CHECK_LIBRARY(curl, cURL,
-  [cURL library not found.  The urlread and urlwrite functions will be disabled.],
+  [cURL library not found.  The ftp objects, urlread and urlwrite functions will be disabled.],
   [curl/curl.h], [curl_easy_escape])
 LIBS="$save_LIBS"
 CPPFLAGS="$save_CPPFLAGS"
--- a/doc/ChangeLog	Fri Nov 27 14:42:07 2009 +0100
+++ b/doc/ChangeLog	Sat Nov 28 11:44:57 2009 +0100
@@ -1,17 +1,23 @@
+2009-11-27  David Bateman  <dbateman@free.fr>
+
+	* interpreter/install.txi: Document that curl libraries impact ftp
+	objects.
+	* interpreter/system.txi: Document the ftp objects
+
 2009-11-25  Rik <octave@nomad.inbox5.com>
 
-	* interpreter/Makefile.am: 
+	* interpreter/Makefile.am:
 	Simplify doc-cache handling in Makefile.am to use only install-data-local
 	target
 
 2009-11-24  Rik <octave@nomad.inbox5.com>
 
-	* interpreter/Makefile.am: 
+	* interpreter/Makefile.am:
 	Correct Makefile so that it halts on error when unable to build doc-cache
 
 2009-11-23  Rik <octave@nomad.inbox5.com>
 
-	* faq/Makefile.am, interpreter/Makefile.am, liboctave/Makefile.am: 
+	* faq/Makefile.am, interpreter/Makefile.am, liboctave/Makefile.am:
 	Stop distribution of conf.texi by autotools.  conf.texi must be re-made
 	from conf.texi.in during configure step
 
--- a/doc/interpreter/install.txi	Fri Nov 27 14:42:07 2009 +0100
+++ b/doc/interpreter/install.txi	Sat Nov 28 11:44:57 2009 +0100
@@ -117,7 +117,7 @@
 Don't use COLAMD, disable some sparse matrix functionality.
 
 @item --without-curl
-Don't use the cURL, disable the @code{urlread} and @code{urlwrite}
+Don't use the cURL, disable the ftp objects, @code{urlread} and @code{urlwrite}
 functions.
 
 @item --without-cxsparse
--- a/doc/interpreter/system.txi	Fri Nov 27 14:42:07 2009 +0100
+++ b/doc/interpreter/system.txi	Sat Nov 28 11:44:57 2009 +0100
@@ -244,6 +244,27 @@
 @node Networking Utilities
 @section Networking Utilities
 
+@menu
+* FTP objects::
+* URL manipulation::
+@end menu
+
+@node FTP objects
+@subsection FTP objects
+
+@DOCSTRING(ftp)
+
+@DOCSTRING(mget)
+
+@DOCSTRING(mput)
+
+@DOCSTRING(ascii)
+
+@DOCSTRING(binary)
+
+@node URL manipulation
+@subsection URL manipulation
+
 @DOCSTRING(urlread)
 
 @DOCSTRING(urlwrite)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/@ftp/ascii.m	Sat Nov 28 11:44:57 2009 +0100
@@ -0,0 +1,24 @@
+## Copyright (C) 2009 David Bateman
+##
+## This program 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 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {} ascii (@var{f})
+## Put the FTP connection @var{f} into ascii mode.
+## @var{f} is an FTP object returned by the ftp function.
+## @end deftypefn
+
+function ascii (obj)
+  __ftp_ascii__ (obj.curlhandle);
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/@ftp/binary.m	Sat Nov 28 11:44:57 2009 +0100
@@ -0,0 +1,24 @@
+## Copyright (C) 2009 David Bateman
+##
+## This program 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 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {} binary (@var{f})
+## Put the FTP connection @var{f} into binary mode.
+## @var{f} is an FTP object returned by the ftp function.
+## @end deftypefn
+
+function binary (obj)
+  __ftp_binary__ (obj.curlhandle);
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/@ftp/cd.m	Sat Nov 28 11:44:57 2009 +0100
@@ -0,0 +1,24 @@
+## Copyright (C) 2009 David Bateman
+##
+## This program 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 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {} cd (@var{f}, @var{path})
+## Set the remote directory to @var{path} on the FTP connection @var{f}.
+## @var{f} is an FTP object returned by the ftp function.
+## @end deftypefn
+
+function cd (obj, path)
+  __ftp_cwd__ (obj.curlhandle, path);
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/@ftp/close.m	Sat Nov 28 11:44:57 2009 +0100
@@ -0,0 +1,24 @@
+## Copyright (C) 2009 David Bateman
+##
+## This program 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 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {} close (@var{f})
+## Close the FTP connection represented by given FTP object @var{f}.
+## @var{f} is an FTP object returned by the ftp function.
+## @end deftypefn
+
+function dir (obj)
+  __ftp_close__ (obj.curlhandle);
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/@ftp/delete.m	Sat Nov 28 11:44:57 2009 +0100
@@ -0,0 +1,24 @@
+## Copyright (C) 2009 David Bateman
+##
+## This program 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 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {} delete (@var{f}, @var{file})
+## Delete the remote file @var{file}, over the FTP connection @var{f}.
+## @var{f} is an FTP object returned by the ftp function.
+## @end deftypefn
+
+function delete (obj, file)
+  __ftp_delete__ (obj.curlhandle, file);
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/@ftp/dir.m	Sat Nov 28 11:44:57 2009 +0100
@@ -0,0 +1,28 @@
+## Copyright (C) 2009 David Bateman
+##
+## This program 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 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{lst} =} dir (@var{path})
+## List the current directory in verbose form for the FTP connection 
+## @var{f}. @var{f} is an FTP object returned by the ftp function.
+## @end deftypefn
+
+function lst = dir (obj)
+  if (nargout == 0)
+    __ftp_dir__ (obj.curlhandle);
+  else
+    lst = __ftp_dir__ (obj.curlhandle);
+  endif
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/@ftp/display.m	Sat Nov 28 11:44:57 2009 +0100
@@ -0,0 +1,22 @@
+## Copyright (C) 2009 David Bateman
+##
+## This program 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 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; If not, see <http://www.gnu.org/licenses/>.
+
+function display (obj)
+  printf ("FTP Object\n");
+  printf (" host: %s\n", obj.host);
+  printf (" user: %s\n", obj.user);
+  printf ("  dir: %s\n", __ftp_pwd__ (obj.curlhandle));
+  printf (" mode: %s\n", __ftp_mode__ (obj.curlhandle));
+endfunction
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/@ftp/ftp.m	Sat Nov 28 11:44:57 2009 +0100
@@ -0,0 +1,32 @@
+## Copyright (C) 2009 David Bateman
+##
+## This program 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 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {@var{f}} = ftp (@var{host})
+## @deftypefnx {Function File} {@var{f}} = ftp (@var{host}, @var{username}, ## @var{password})
+## Connect to the FTP server @var{host} with @var{username} and @var{password}.
+## If @var{username} and @var{password} are not specified, user "anonymous"
+## with no password is used. The returned FTP object @var{f} represents the
+## established FTP connection.
+## @end deftypefn
+
+function obj = ftp (host, user = "anonymous", pass = "")
+  p.host = host;
+  p.user = user;
+  p.pass = pass;
+  p.curlhandle = tmpnam ("ftp-");
+  __ftp__ (p.curlhandle, host, user, pass);
+  obj = class (p, "ftp");
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/@ftp/loadobj.m	Sat Nov 28 11:44:57 2009 +0100
@@ -0,0 +1,24 @@
+## Copyright (C) 2009 David Bateman
+##
+## This program 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 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; If not, see <http://www.gnu.org/licenses/>.
+
+function b = loadobj (a)
+  b = a;
+  b.curlhandle = tmpnam ("ftp-");
+  __ftp__ (b.curlhandle, b.host, b.user, b.pass);
+  if (! isempty (b.dir))
+    __ftp_cwd__ (b.curlhandle, b.dir);
+  endif
+  b = rmfield (b, "dir")
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/@ftp/mget.m	Sat Nov 28 11:44:57 2009 +0100
@@ -0,0 +1,34 @@
+## Copyright (C) 2009 David Bateman
+##
+## This program 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 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {} mget (@var{f}, @var{file})
+## @deftypefnx {Function File} {} mget (@var{f}, @var{dir})
+## @deftypefnx {Function File} {} mget (@dots{}, @var{target})
+## Downloads a remote file @var{file} or directory @var{dir} to the local
+## directory on the FTP connection @var{f}. @var{f} is an FTP object
+## returned by the ftp function. 
+##
+## The arguments @var{file} and @var{dir} can include wildcards and any
+## files or directories on the remote server that match will be downloaded.
+##
+## If a third argument @var{target} is given, then a single file or
+## directory will be downloaded with the name @var{target} to the local
+## directory.
+## @end deftypefn
+
+function mget (obj, file)
+  __ftp_mget__ (obj.curlhandle, file);
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/@ftp/mkdir.m	Sat Nov 28 11:44:57 2009 +0100
@@ -0,0 +1,24 @@
+## Copyright (C) 2009 David Bateman
+##
+## This program 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 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {} mkdir (@var{f}, @var{path})
+## Create the remote directory @var{path}, over the FTP connection @var{f}.
+## @var{f} is an FTP object returned by the ftp function.
+## @end deftypefn
+
+function mkdir (obj, path)
+  __ftp_mkdir__ (obj.curlhandle, path);
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/@ftp/module.mk	Sat Nov 28 11:44:57 2009 +0100
@@ -0,0 +1,22 @@
+FCN_FILE_DIRS += @ftp
+
+@ftp_FCN_FILES = \
+  @ftp/ascii.m \
+  @ftp/binary.m  \
+  @ftp/cd.m  \
+  @ftp/close.m  \
+  @ftp/delete.m  \
+  @ftp/dir.m  \
+  @ftp/display.m  \
+  @ftp/ftp.m  \
+  @ftp/loadobj.m  \
+  @ftp/mget.m  \
+  @ftp/mkdir.m  \
+  @ftp/mput.m  \
+  @ftp/rename.m  \
+  @ftp/rmdir.m  \
+  @ftp/saveobj.m
+
+FCN_FILES += $(@ftp_FCN_FILES)
+
+PKG_ADD_FILES += @ftp/PKG_ADD
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/@ftp/mput.m	Sat Nov 28 11:44:57 2009 +0100
@@ -0,0 +1,32 @@
+## Copyright (C) 2009 David Bateman
+##
+## This program 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 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {} mput (@var{f}, @var{file})
+## Upload the local file @var{file} into the current remote directory on
+## the FTP connection @var{f}. @var{f} is an FTP object returned by the
+## ftp function. 
+##
+## The argument @var{file} is passed by the @dfn{glob} function and any
+## files that match the wildcards in @var{file} will be uploaded.
+## @end deftypefn
+
+function retval = mput (obj, file)
+  if (nargout == 0)
+    __ftp_mput__ (obj.curlhandle, file);
+  else
+    retval = __ftp_mput__ (obj.curlhandle, file);
+  endif
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/@ftp/rename.m	Sat Nov 28 11:44:57 2009 +0100
@@ -0,0 +1,25 @@
+## Copyright (C) 2009 David Bateman
+##
+## This program 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 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {} rename (@var{f}, @var{oldname}, @var{newname})
+## Rename or move the remote file or directory @var{oldname} to @var{newname},
+##  over the FTP connection @var{f}. @var{f} is an FTP object returned by the
+## ftp function.
+## @end deftypefn
+
+function rename (obj, oldname, newname)
+  __ftp_rename__ (obj.curlhandle, oldname, newname);
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/@ftp/rmdir.m	Sat Nov 28 11:44:57 2009 +0100
@@ -0,0 +1,24 @@
+## Copyright (C) 2009 David Bateman
+##
+## This program 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 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {} rmdir (@var{f}, @var{path})
+## Remove the remote directory @var{path}, over the FTP connection @var{f}.
+## @var{f} is an FTP object returned by the ftp function.
+## @end deftypefn
+
+function rmdir (obj, path)
+  __ftp_rmdir__ (obj.curlhandle, path);
+endfunction
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/@ftp/saveobj.m	Sat Nov 28 11:44:57 2009 +0100
@@ -0,0 +1,20 @@
+## Copyright (C) 2009 David Bateman
+##
+## This program 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 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; If not, see <http://www.gnu.org/licenses/>.
+
+function b = saveobj (a)
+  b = a;
+  b = rmfield (b, "curlhandle")
+  b.dir = __ftp_pwd (a.curlhandle);
+endfunction
--- a/scripts/ChangeLog	Fri Nov 27 14:42:07 2009 +0100
+++ b/scripts/ChangeLog	Sat Nov 28 11:44:57 2009 +0100
@@ -1,7 +1,15 @@
+2009-11-27  David Bateman  <dbateman@free.fr>
+
+	* @ftp/ascii.m, @ftp/binary.m, @ftp/cd.m, @ftp/close.m,
+	@ftp/delete.m, @ftp/dir.m, @ftp/display.m, @ftp/ftp.m,
+	@ftp/loadobj.m, @ftp/mget.m, @ftp/mkdir.m, @ftp/module.mk,
+	@ftp/mput.m, @ftp/rename.m, @ftp/rmdir.m, @ftp/saveobj.m: New files
+	* Makefile.am: Add the @ftp to the build.
+
 2009-11-26  Jaroslav Hajek  <highegg@gmail.com>
 
 	* linear-algebra/cross.m: Avoid doing indexing twice.
-		
+
 2009-11-26  Jaroslav Hajek  <highegg@gmail.com>
 
 	* linear-algebra/normest.m: Randomize initial vector.
--- a/scripts/Makefile.am	Fri Nov 27 14:42:07 2009 +0100
+++ b/scripts/Makefile.am	Sat Nov 28 11:44:57 2009 +0100
@@ -40,6 +40,7 @@
 
 IMAGES =
 
+include @ftp/module.mk
 include audio/module.mk
 include deprecated/module.mk
 include elfun/module.mk
@@ -73,6 +74,11 @@
 
 octave_dirstamp = $(am__leading_dot)dirstamp
 
+@ftp/PKG_ADD: $(@ftp_FCN_FILES) $(@ftp_GEN_FCN_FILES) @ftp/$(octave_dirstamp) mk-pkg-add
+	@echo "generating $@"
+	@$(srcdir)/mk-pkg-add $(srcdir) $(@ftp_FCN_FILES) -- $(@ftp_GEN_FCN_FILES) > $@-t
+	@mv $@-t $@
+
 audio/PKG_ADD: $(audio_FCN_FILES) $(audio_GEN_FCN_FILES) audio/$(octave_dirstamp) mk-pkg-add
 	@echo "generating $@"
 	@$(srcdir)/mk-pkg-add $(srcdir) $(audio_FCN_FILES) -- $(audio_GEN_FCN_FILES) > $@-t
@@ -213,6 +219,7 @@
 	@$(srcdir)/mk-pkg-add $(srcdir) $(time_FCN_FILES) -- $(time_GEN_FCN_FILES) > $@-t
 	@mv $@-t $@
 
+$(@ftp_GEN_FCN_FILES): @ftp/$(octave_dirstamp)
 $(audio_GEN_FCN_FILES): audio/$(octave_dirstamp)
 $(deprecated_GEN_FCN_FILES): deprecated/$(octave_dirstamp)
 $(elfun_GEN_FCN_FILES): elfun/$(octave_dirstamp)
@@ -242,6 +249,9 @@
 $(testfun_GEN_FCN_FILES): testfun/$(octave_dirstamp)
 $(time_GEN_FCN_FILES): time/$(octave_dirstamp)
 
+@ftp/$(octave_dirstamp):
+	@$(MKDIR_P) @ftp
+	@: > @ftp/$(octave_dirstamp)
 audio/$(octave_dirstamp):
 	@$(MKDIR_P) audio
 	@: > audio/$(octave_dirstamp)
--- a/src/ChangeLog	Fri Nov 27 14:42:07 2009 +0100
+++ b/src/ChangeLog	Sat Nov 28 11:44:57 2009 +0100
@@ -1,5 +1,14 @@
+2009-11-27  David Bateman  <dbateman@free.fr>
+
+	* DLD-FUNCTIONS/urlwrite.cc (curl_handle, curl_handles): Add classes
+	to manage the open curl connections.
+	(F__ftp__, F__ftp_pwd__, F__ftp_cwd__, F__ftp_dir__, F__ftp_ascii__,
+	F__ftp_binary__, F__ftp_close__, F__ftp_mode__, F__ftp_delete__,
+	F__ftp_rmdir__, F__ftp_mkdir__, F__ftp_rename__, F__ftp_mput__,
+	F__ftp_mget__): New functions.
+
 2009-11-27  Jaroslav Hajek  <highegg@gmail.com>
-
+ 
 	* ov.cc (octave_value::octave_value (const index_vector&)): Take a
 	copy if idx to allow mutation.
 
@@ -1025,7 +1034,7 @@
 	* ov-perm.cc (octave_perm_matrix::save_binary,
 	octave_perm_matrix::load_binary): Avoid shadow warning from gcc.
 
-2008-09-01  David Bateman  <dbateman@free.fr>
+2009-09-01  David Bateman  <dbateman@free.fr>
 
 	* DLD-FUNCTIONS/eig.cc (Feigs): Correct nesting error in option
 	parsing that prevented the use of a function for generalized
--- 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 ();
+}