view liboctave/util/url-transfer.cc @ 31605:e88a07dec498 stable

maint: Use macros to begin/end C++ namespaces. * oct-conf-post-public.in.h: Define two macros (OCTAVE_BEGIN_NAMESPACE, OCTAVE_END_NAMESPACE) that can be used to start/end a namespace. * mk-opts.pl, build-env.h, build-env.in.cc, __betainc__.cc, __contourc__.cc, __dsearchn__.cc, __eigs__.cc, __expint__.cc, __ftp__.cc, __gammainc__.cc, __ichol__.cc, __ilu__.cc, __isprimelarge__.cc, __lin_interpn__.cc, __magick_read__.cc, __pchip_deriv__.cc, __qp__.cc, amd.cc, auto-shlib.cc, auto-shlib.h, balance.cc, base-text-renderer.cc, base-text-renderer.h, besselj.cc, bitfcns.cc, bsxfun.cc, c-file-ptr-stream.cc, c-file-ptr-stream.h, call-stack.cc, call-stack.h, ccolamd.cc, cellfun.cc, chol.cc, colamd.cc, colloc.cc, conv2.cc, daspk.cc, dasrt.cc, dassl.cc, data.cc, data.h, debug.cc, defaults.cc, defaults.h, defun-int.h, defun.cc, det.cc, dirfns.cc, display.cc, display.h, dlmread.cc, dmperm.cc, dot.cc, dynamic-ld.cc, dynamic-ld.h, eig.cc, ellipj.cc, environment.cc, environment.h, error.cc, error.h, errwarn.h, event-manager.cc, event-manager.h, event-queue.cc, event-queue.h, fcn-info.cc, fcn-info.h, fft.cc, fft2.cc, fftn.cc, file-io.cc, filter.cc, find.cc, ft-text-renderer.cc, ft-text-renderer.h, gcd.cc, getgrent.cc, getpwent.cc, getrusage.cc, givens.cc, gl-render.cc, gl-render.h, gl2ps-print.cc, gl2ps-print.h, graphics-toolkit.cc, graphics-toolkit.h, graphics.cc, graphics.in.h, gsvd.cc, gtk-manager.cc, gtk-manager.h, hash.cc, help.cc, help.h, hess.cc, hex2num.cc, hook-fcn.cc, hook-fcn.h, input.cc, input.h, interpreter-private.cc, interpreter-private.h, interpreter.cc, interpreter.h, inv.cc, jsondecode.cc, jsonencode.cc, kron.cc, latex-text-renderer.cc, latex-text-renderer.h, load-path.cc, load-path.h, load-save.cc, load-save.h, lookup.cc, ls-ascii-helper.cc, ls-ascii-helper.h, ls-oct-text.cc, ls-utils.cc, ls-utils.h, lsode.cc, lu.cc, mappers.cc, matrix_type.cc, max.cc, mex-private.h, mex.cc, mgorth.cc, nproc.cc, oct-fstrm.cc, oct-fstrm.h, oct-hdf5-types.cc, oct-hdf5-types.h, oct-hist.cc, oct-hist.h, oct-iostrm.cc, oct-iostrm.h, oct-opengl.h, oct-prcstrm.cc, oct-prcstrm.h, oct-procbuf.cc, oct-procbuf.h, oct-process.cc, oct-process.h, oct-stdstrm.h, oct-stream.cc, oct-stream.h, oct-strstrm.cc, oct-strstrm.h, oct-tex-lexer.in.ll, oct-tex-parser.yy, ordqz.cc, ordschur.cc, pager.cc, pager.h, pinv.cc, pow2.cc, pr-flt-fmt.cc, pr-output.cc, procstream.cc, procstream.h, psi.cc, qr.cc, quad.cc, quadcc.cc, qz.cc, rand.cc, rcond.cc, regexp.cc, schur.cc, settings.cc, settings.h, sighandlers.cc, sighandlers.h, sparse-xdiv.cc, sparse-xdiv.h, sparse-xpow.cc, sparse-xpow.h, sparse.cc, spparms.cc, sqrtm.cc, stack-frame.cc, stack-frame.h, stream-euler.cc, strfind.cc, strfns.cc, sub2ind.cc, svd.cc, sylvester.cc, symbfact.cc, syminfo.cc, syminfo.h, symrcm.cc, symrec.cc, symrec.h, symscope.cc, symscope.h, symtab.cc, symtab.h, syscalls.cc, sysdep.cc, sysdep.h, text-engine.cc, text-engine.h, text-renderer.cc, text-renderer.h, time.cc, toplev.cc, tril.cc, tsearch.cc, typecast.cc, url-handle-manager.cc, url-handle-manager.h, urlwrite.cc, utils.cc, utils.h, variables.cc, variables.h, xdiv.cc, xdiv.h, xnorm.cc, xnorm.h, xpow.cc, xpow.h, __delaunayn__.cc, __fltk_uigetfile__.cc, __glpk__.cc, __init_fltk__.cc, __init_gnuplot__.cc, __ode15__.cc, __voronoi__.cc, audiodevinfo.cc, audioread.cc, convhulln.cc, fftw.cc, gzip.cc, mk-build-env-features.sh, mk-builtins.pl, cdef-class.cc, cdef-class.h, cdef-fwd.h, cdef-manager.cc, cdef-manager.h, cdef-method.cc, cdef-method.h, cdef-object.cc, cdef-object.h, cdef-package.cc, cdef-package.h, cdef-property.cc, cdef-property.h, cdef-utils.cc, cdef-utils.h, ov-base.cc, ov-base.h, ov-bool-mat.cc, ov-builtin.h, ov-cell.cc, ov-class.cc, ov-class.h, ov-classdef.cc, ov-classdef.h, ov-complex.cc, ov-fcn-handle.cc, ov-fcn-handle.h, ov-fcn.h, ov-java.cc, ov-java.h, ov-mex-fcn.h, ov-null-mat.cc, ov-oncleanup.cc, ov-struct.cc, ov-typeinfo.cc, ov-typeinfo.h, ov-usr-fcn.cc, ov-usr-fcn.h, ov.cc, ov.h, octave.cc, octave.h, mk-ops.sh, op-b-b.cc, op-b-bm.cc, op-b-sbm.cc, op-bm-b.cc, op-bm-bm.cc, op-bm-sbm.cc, op-cdm-cdm.cc, op-cell.cc, op-chm.cc, op-class.cc, op-cm-cm.cc, op-cm-cs.cc, op-cm-m.cc, op-cm-s.cc, op-cm-scm.cc, op-cm-sm.cc, op-cs-cm.cc, op-cs-cs.cc, op-cs-m.cc, op-cs-s.cc, op-cs-scm.cc, op-cs-sm.cc, op-dm-dm.cc, op-dm-scm.cc, op-dm-sm.cc, op-dm-template.cc, op-dms-template.cc, op-fcdm-fcdm.cc, op-fcm-fcm.cc, op-fcm-fcs.cc, op-fcm-fm.cc, op-fcm-fs.cc, op-fcn.cc, op-fcs-fcm.cc, op-fcs-fcs.cc, op-fcs-fm.cc, op-fcs-fs.cc, op-fdm-fdm.cc, op-fm-fcm.cc, op-fm-fcs.cc, op-fm-fm.cc, op-fm-fs.cc, op-fs-fcm.cc, op-fs-fcs.cc, op-fs-fm.cc, op-fs-fs.cc, op-i16-i16.cc, op-i32-i32.cc, op-i64-i64.cc, op-i8-i8.cc, op-int-concat.cc, op-m-cm.cc, op-m-cs.cc, op-m-m.cc, op-m-s.cc, op-m-scm.cc, op-m-sm.cc, op-mi.cc, op-pm-pm.cc, op-pm-scm.cc, op-pm-sm.cc, op-pm-template.cc, op-range.cc, op-s-cm.cc, op-s-cs.cc, op-s-m.cc, op-s-s.cc, op-s-scm.cc, op-s-sm.cc, op-sbm-b.cc, op-sbm-bm.cc, op-sbm-sbm.cc, op-scm-cm.cc, op-scm-cs.cc, op-scm-m.cc, op-scm-s.cc, op-scm-scm.cc, op-scm-sm.cc, op-sm-cm.cc, op-sm-cs.cc, op-sm-m.cc, op-sm-s.cc, op-sm-scm.cc, op-sm-sm.cc, op-str-m.cc, op-str-s.cc, op-str-str.cc, op-struct.cc, op-ui16-ui16.cc, op-ui32-ui32.cc, op-ui64-ui64.cc, op-ui8-ui8.cc, ops.h, anon-fcn-validator.cc, anon-fcn-validator.h, bp-table.cc, bp-table.h, comment-list.cc, comment-list.h, filepos.h, lex.h, lex.ll, oct-lvalue.cc, oct-lvalue.h, oct-parse.yy, parse.h, profiler.cc, profiler.h, pt-anon-scopes.cc, pt-anon-scopes.h, pt-arg-list.cc, pt-arg-list.h, pt-args-block.cc, pt-args-block.h, pt-array-list.cc, pt-array-list.h, pt-assign.cc, pt-assign.h, pt-binop.cc, pt-binop.h, pt-bp.cc, pt-bp.h, pt-cbinop.cc, pt-cbinop.h, pt-cell.cc, pt-cell.h, pt-check.cc, pt-check.h, pt-classdef.cc, pt-classdef.h, pt-cmd.h, pt-colon.cc, pt-colon.h, pt-const.cc, pt-const.h, pt-decl.cc, pt-decl.h, pt-eval.cc, pt-eval.h, pt-except.cc, pt-except.h, pt-exp.cc, pt-exp.h, pt-fcn-handle.cc, pt-fcn-handle.h, pt-id.cc, pt-id.h, pt-idx.cc, pt-idx.h, pt-jump.h, pt-loop.cc, pt-loop.h, pt-mat.cc, pt-mat.h, pt-misc.cc, pt-misc.h, pt-pr-code.cc, pt-pr-code.h, pt-select.cc, pt-select.h, pt-spmd.cc, pt-spmd.h, pt-stmt.cc, pt-stmt.h, pt-tm-const.cc, pt-tm-const.h, pt-unop.cc, pt-unop.h, pt-vm-eval.cc, pt-walk.cc, pt-walk.h, pt.cc, pt.h, token.cc, token.h, Range.cc, Range.h, idx-vector.cc, idx-vector.h, range-fwd.h, CollocWt.cc, CollocWt.h, aepbalance.cc, aepbalance.h, chol.cc, chol.h, gepbalance.cc, gepbalance.h, gsvd.cc, gsvd.h, hess.cc, hess.h, lo-mappers.cc, lo-mappers.h, lo-specfun.cc, lo-specfun.h, lu.cc, lu.h, oct-convn.cc, oct-convn.h, oct-fftw.cc, oct-fftw.h, oct-norm.cc, oct-norm.h, oct-rand.cc, oct-rand.h, oct-spparms.cc, oct-spparms.h, qr.cc, qr.h, qrp.cc, qrp.h, randgamma.cc, randgamma.h, randmtzig.cc, randmtzig.h, randpoisson.cc, randpoisson.h, schur.cc, schur.h, sparse-chol.cc, sparse-chol.h, sparse-lu.cc, sparse-lu.h, sparse-qr.cc, sparse-qr.h, svd.cc, svd.h, child-list.cc, child-list.h, dir-ops.cc, dir-ops.h, file-ops.cc, file-ops.h, file-stat.cc, file-stat.h, lo-sysdep.cc, lo-sysdep.h, lo-sysinfo.cc, lo-sysinfo.h, mach-info.cc, mach-info.h, oct-env.cc, oct-env.h, oct-group.cc, oct-group.h, oct-password.cc, oct-password.h, oct-syscalls.cc, oct-syscalls.h, oct-time.cc, oct-time.h, oct-uname.cc, oct-uname.h, action-container.cc, action-container.h, base-list.h, cmd-edit.cc, cmd-edit.h, cmd-hist.cc, cmd-hist.h, f77-fcn.h, file-info.cc, file-info.h, lo-array-errwarn.cc, lo-array-errwarn.h, lo-hash.cc, lo-hash.h, lo-ieee.h, lo-regexp.cc, lo-regexp.h, lo-utils.cc, lo-utils.h, oct-base64.cc, oct-base64.h, oct-glob.cc, oct-glob.h, oct-inttypes.h, oct-mutex.cc, oct-mutex.h, oct-refcount.h, oct-shlib.cc, oct-shlib.h, oct-sparse.cc, oct-sparse.h, oct-string.h, octave-preserve-stream-state.h, pathsearch.cc, pathsearch.h, quit.cc, quit.h, unwind-prot.cc, unwind-prot.h, url-transfer.cc, url-transfer.h : Use new macros to begin/end C++ namespaces.
author Rik <rik@octave.org>
date Thu, 01 Dec 2022 14:23:45 -0800
parents 243b51ec9ff0
children aac27ad79be6
line wrap: on
line source

////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2006-2022 The Octave Project Developers
//
// See the file COPYRIGHT.md in the top-level directory of this
// distribution or <https://octave.org/copyright/>.
//
// This file is part of Octave.
//
// Octave 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 3 of the License, or
// (at your option) any later version.
//
// Octave 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 Octave; see the file COPYING.  If not, see
// <https://www.gnu.org/licenses/>.
//
////////////////////////////////////////////////////////////////////////

#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

#include <fstream>
#include <iomanip>
#include <iostream>

#include "dir-ops.h"
#include "file-ops.h"
#include "file-stat.h"
#include "lo-sysdep.h"
#include "oct-env.h"
#include "unwind-prot.h"
#include "url-transfer.h"
#include "version.h"

#if defined (HAVE_CURL)
#  include <curl/curl.h>
#  include <curl/curlver.h>
#  include <curl/easy.h>
#endif

OCTAVE_BEGIN_NAMESPACE(octave)

  base_url_transfer::base_url_transfer (void)
    : m_host_or_url (), m_valid (false), m_ftp (false),
      m_ascii_mode (false), m_ok (true), m_errmsg (),
      m_curr_istream (&std::cin), m_curr_ostream (&std::cout)
  { }

  base_url_transfer::base_url_transfer (const std::string& host,
                                        const std::string& /* user_arg */,
                                        const std::string& /* passwd */,
                                        std::ostream& os)
    : m_host_or_url (host), m_valid (false), m_ftp (true),
      m_ascii_mode (false), m_ok (true), m_errmsg (),
      m_curr_istream (&std::cin), m_curr_ostream (&os)
  { }

  base_url_transfer::base_url_transfer (const std::string& url,
                                        std::ostream& os)
    : m_host_or_url (url), m_valid (false), m_ftp (false),
      m_ascii_mode (false), m_ok (true), m_errmsg (),
      m_curr_istream (&std::cin), m_curr_ostream (&os)
  { }

  void
  base_url_transfer::mget_directory (const std::string& directory,
                                     const std::string& target)
  {
    std::string sep = sys::file_ops::dir_sep_str ();
    sys::file_stat fs (directory);

    if (! fs || ! fs.is_dir ())
      {
        std::string msg;
        int status = sys::mkdir (directory, 0777, msg);

        if (status < 0)
          {
            m_ok = false;
            m_errmsg = "__ftp_mget__: can not create directory '"
                       + target + sep + directory + "': " + msg;
            return;
          }
      }

    cwd (directory);

    if (good ())
      {
        unwind_action_safe reset_path (&base_url_transfer::cwd, this, "..");

        string_vector sv = list ();

        for (octave_idx_type i = 0; i < sv.numel (); i++)
          {
            OCTAVE_TIME_T ftime;
            bool fisdir;
            double fsize;

            get_fileinfo (sv(i), fsize, ftime, fisdir);

            if (fisdir)
              mget_directory (sv(i), target + directory + sep);
            else
              {
                std::string realfile = target + directory + sep + sv(i);

                std::ofstream ofile =
                  sys::ofstream (realfile.c_str (),
                                 std::ios::out | std::ios::binary);

                if (! ofile.is_open ())
                  {
                    m_ok = false;
                    m_errmsg = "__ftp_mget__: unable to open file";
                    break;
                  }
                int(*unlink_fptr)(const std::string&) = sys::unlink;
                unwind_action_safe delete_file (unlink_fptr, realfile);

                get (sv(i), ofile);

                ofile.close ();

                if (good ())
                  delete_file.discard ();
              }

            if (! good ())
              break;
          }
      }
  }

  string_vector
  base_url_transfer::mput_directory (const std::string& base,
                                     const std::string& directory)
  {
    string_vector file_list;

    std::string realdir
      = (base.empty ()
         ? directory : base + sys::file_ops::dir_sep_str () + directory);

    mkdir (directory);

    if (! good ())
      return file_list;

    cwd (directory);

    if (good ())
      {
        unwind_action_safe reset_path (&base_url_transfer::cwd, this, "..");

        string_vector files;
        std::string msg;

        if (sys::get_dirlist (realdir, files, msg))
          for (octave_idx_type i = 0; i < files.numel (); i++)
            {
              std::string file = files (i);

              if (file == "." || file == "..")
                continue;

              std::string realfile
                = realdir + sys::file_ops::dir_sep_str () + file;

              sys::file_stat fs (realfile);

              if (! fs.exists ())
                {
                  m_ok = false;
                  m_errmsg = "__ftp__mput: file '" + realfile
                             + "' does not exist";
                  break;
                }

              if (fs.is_dir ())
                {
                  file_list.append (mput_directory (realdir, file));

                  if (! good ())
                    break;
                }
              else
                {
                  // FIXME: Does ascii mode need to be flagged here?
                  std::ifstream ifile =
                    sys::ifstream (realfile.c_str (),
                                   std::ios::in | std::ios::binary);

                  if (! ifile.is_open ())
                    {
                      m_ok = false;
                      m_errmsg = "__ftp_mput__: unable to open file '"
                                 + realfile + "'";
                      break;
                    }

                  put (file, ifile);

                  ifile.close ();

                  if (! good ())
                    break;

                  file_list.append (realfile);
                }
            }
        else
          {
            m_ok = false;
            m_errmsg = "__ftp_mput__: can not read the directory '"
                       + realdir + "'";
          }
      }

    return file_list;
  }

#if defined (HAVE_CURL)

  static int
  write_data (void *buffer, std::size_t size, std::size_t nmemb, void *streamp)
  {
    std::ostream& stream = *(static_cast<std::ostream *> (streamp));
    stream.write (static_cast<const char *> (buffer), size*nmemb);
    return (stream.fail () ? 0 : size * nmemb);
  }

  static int
  read_data (void *buffer, std::size_t size, std::size_t nmemb, void *streamp)
  {
    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);
  }

  static std::size_t
  throw_away (void *, std::size_t size, std::size_t nmemb, void *)
  {
    return static_cast<std::size_t> (size * nmemb);
  }

  // I'd love to rewrite this as a private method of the url_transfer
  // class, but you can't pass the va_list from the wrapper SETOPT to
  // the curl_easy_setopt function.
#define SETOPT(option, parameter)                                       \
  do                                                                    \
    {                                                                   \
      CURLcode res = curl_easy_setopt (m_curl, option, parameter);      \
      if (res != CURLE_OK)                                              \
        {                                                               \
          m_ok = false;                                                 \
          m_errmsg = curl_easy_strerror (res);                          \
          return;                                                       \
        }                                                               \
    }                                                                   \
  while (0)

  // Same as above but with a return value.
#define SETOPTR(option, parameter)                                      \
  do                                                                    \
    {                                                                   \
      CURLcode res = curl_easy_setopt (m_curl, option, parameter);      \
      if (res != CURLE_OK)                                              \
        {                                                               \
          m_ok = false;                                                 \
          m_errmsg = curl_easy_strerror (res);                          \
          return retval;                                                \
        }                                                               \
    }                                                                   \
  while (0)

  class curl_transfer : public base_url_transfer
  {
  public:

    curl_transfer (void)
      : base_url_transfer (), m_curl (curl_easy_init ()), m_errnum (), m_url (),
        m_userpwd ()
    {
      if (m_curl)
        m_valid = true;
      else
        m_errmsg = "can not create curl object";
    }

    curl_transfer (const std::string& host, const std::string& user_arg,
                   const std::string& passwd, std::ostream& os)
      : base_url_transfer (host, user_arg, passwd, os),
        m_curl (curl_easy_init ()), m_errnum (), m_url (), m_userpwd ()
    {
      if (m_curl)
        m_valid = true;
      else
        {
          m_errmsg = "can not create curl object";
          return;
        }

      init (user_arg, passwd, std::cin, os);

      m_url = "ftp://" + host;
      SETOPT (CURLOPT_URL, m_url.c_str ());

      // Set up the link, with no transfer.
      perform ();
    }

    curl_transfer (const std::string& url_str, std::ostream& os)
      : base_url_transfer (url_str, os), m_curl (curl_easy_init ()),
        m_errnum (), m_url (), m_userpwd ()
    {
      if (m_curl)
        m_valid = true;
      else
        {
          m_errmsg = "can not create curl object";
          return;
        }

      init ("", "", std::cin, os);

      std::string cainfo = sys::env::getenv ("CURLOPT_CAINFO");
      if (! cainfo.empty ())
        SETOPT (CURLOPT_CAINFO, cainfo.c_str ());

      std::string capath = sys::env::getenv ("CURLOPT_CAPATH");
      if (! capath.empty ())
        SETOPT (CURLOPT_CAPATH, capath.c_str ());

      SETOPT (CURLOPT_NOBODY, 0);

      // Restore the default HTTP request method to GET after setting
      // NOBODY to true (in the init method) and back to false (above).
      // This is needed for backward compatibility with versions of
      // libcurl < 7.18.2.
      SETOPT (CURLOPT_HTTPGET, 1);
    }

    // No copying!

    curl_transfer (const curl_transfer&) = delete;

    curl_transfer& operator = (const curl_transfer&) = delete;

    ~curl_transfer (void)
    {
      if (m_curl)
        curl_easy_cleanup (m_curl);
    }

    void perform (void)
    {
      m_errnum = curl_easy_perform (m_curl);

      if (m_errnum != CURLE_OK)
        {
          m_ok = false;
          m_errmsg = curl_easy_strerror (m_errnum);
        }
    }

    std::string lasterror (void) const
    {
      return std::string (curl_easy_strerror (m_errnum));
    }

    std::ostream& set_ostream (std::ostream& os)
    {
      std::ostream& retval = *m_curr_ostream;
      m_curr_ostream = &os;
      SETOPTR (CURLOPT_WRITEDATA, static_cast<void *> (m_curr_ostream));
      return retval;
    }

    std::istream& set_istream (std::istream& is)
    {
      std::istream& retval = *m_curr_istream;
      m_curr_istream = &is;
      SETOPTR (CURLOPT_READDATA, static_cast<void *> (m_curr_istream));
      return retval;
    }

    void ascii (void)
    {
      m_ascii_mode = true;
      SETOPT (CURLOPT_TRANSFERTEXT, 1);
    }

    void binary (void)
    {
      m_ascii_mode = false;
      SETOPT (CURLOPT_TRANSFERTEXT, 0);
    }

    void cwd (const std::string& path)
    {
      ftp_file_or_dir_action (path, "cwd");
    }

    void del (const std::string& file)
    {
      ftp_file_or_dir_action (file, "dele");
    }

    void rmdir (const std::string& path)
    {
      ftp_file_or_dir_action (path, "rmd");
    }

    void mkdir (const std::string& path)
    {
      ftp_file_or_dir_action (path, "mkd");
    }

    void rename (const std::string& oldname, const std::string& newname)
    {
      struct curl_slist *slist = nullptr;

      unwind_action cleanup_slist ([=] () { curl_slist_free_all (slist); });

      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);

      perform ();
      if (! good ())
        return;

      SETOPT (CURLOPT_POSTQUOTE, 0);
    }

    void put (const std::string& file, std::istream& is)
    {
      m_url = "ftp://" + m_host_or_url + '/' + file;
      SETOPT (CURLOPT_URL, m_url.c_str ());
      SETOPT (CURLOPT_UPLOAD, 1);
      SETOPT (CURLOPT_NOBODY, 0);
      std::istream& old_is = set_istream (is);

      perform ();
      if (! good ())
        return;

      set_istream (old_is);
      SETOPT (CURLOPT_NOBODY, 1);
      SETOPT (CURLOPT_UPLOAD, 0);
      m_url = "ftp://" + m_host_or_url;
      SETOPT (CURLOPT_URL, m_url.c_str ());
    }

    void get (const std::string& file, std::ostream& os)
    {
      m_url = "ftp://" + m_host_or_url + '/' + file;
      SETOPT (CURLOPT_URL, m_url.c_str ());
      SETOPT (CURLOPT_NOBODY, 0);
      std::ostream& old_os = set_ostream (os);

      perform ();
      if (! good ())
        return;

      set_ostream (old_os);
      SETOPT (CURLOPT_NOBODY, 1);
      m_url = "ftp://" + m_host_or_url;
      SETOPT (CURLOPT_URL, m_url.c_str ());
    }

    void dir (void)
    {
      m_url = "ftp://" + m_host_or_url + '/';
      SETOPT (CURLOPT_URL, m_url.c_str ());
      SETOPT (CURLOPT_NOBODY, 0);

      perform ();
      if (! good ())
        return;

      SETOPT (CURLOPT_NOBODY, 1);
      m_url = "ftp://" + m_host_or_url;
      SETOPT (CURLOPT_URL, m_url.c_str ());
    }

    string_vector list (void)
    {
      string_vector retval;

      std::ostringstream buf;
      m_url = "ftp://" + m_host_or_url + '/';
      SETOPTR (CURLOPT_WRITEDATA, static_cast<void *> (&buf));
      SETOPTR (CURLOPT_URL, m_url.c_str ());
      SETOPTR (CURLOPT_DIRLISTONLY, 1);
      SETOPTR (CURLOPT_NOBODY, 0);

      perform ();
      if (! good ())
        return retval;

      SETOPTR (CURLOPT_NOBODY, 1);
      m_url = "ftp://" + m_host_or_url;
      SETOPTR (CURLOPT_WRITEDATA, static_cast<void *> (m_curr_ostream));
      SETOPTR (CURLOPT_DIRLISTONLY, 0);
      SETOPTR (CURLOPT_URL, m_url.c_str ());

      // Count number of directory entries
      std::string str = buf.str ();
      octave_idx_type n = 0;
      std::size_t pos = 0;
      while (true)
        {
          pos = str.find_first_of ('\n', pos);
          if (pos == std::string::npos)
            break;
          pos++;
          n++;
        }
      retval.resize (n);
      pos = 0;
      for (octave_idx_type i = 0; i < n; i++)
        {
          std::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,
                       OCTAVE_TIME_T& filetime, bool& fileisdir)
    {
      std::string path = pwd ();

      m_url = "ftp://" + m_host_or_url + '/' + path + '/' + filename;
      SETOPT (CURLOPT_URL, m_url.c_str ());
      SETOPT (CURLOPT_FILETIME, 1);
      SETOPT (CURLOPT_HEADERFUNCTION, throw_away);
      SETOPT (CURLOPT_WRITEFUNCTION, throw_away);

      // 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!

      perform ();
      if (! good ())
        {
          fileisdir = true;
          filetime = -1;
          filesize = 0;

          return;
        }

      fileisdir = false;
      OCTAVE_TIME_T ft;
      curl_easy_getinfo (m_curl, CURLINFO_FILETIME, &ft);
      filetime = ft;
      double fs;
      curl_easy_getinfo (m_curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &fs);
      filesize = fs;

      SETOPT (CURLOPT_WRITEFUNCTION, write_data);
      SETOPT (CURLOPT_HEADERFUNCTION, 0);
      SETOPT (CURLOPT_FILETIME, 0);
      m_url = "ftp://" + m_host_or_url;
      SETOPT (CURLOPT_URL, m_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)
    {
      std::string retval;

      struct curl_slist *slist = nullptr;

      unwind_action cleanup_slist ([=] () { curl_slist_free_all (slist); });

      slist = curl_slist_append (slist, "pwd");
      SETOPTR (CURLOPT_POSTQUOTE, slist);
      SETOPTR (CURLOPT_HEADERFUNCTION, write_data);

      std::ostringstream buf;
      SETOPTR (CURLOPT_WRITEHEADER, static_cast<void *>(&buf));

      perform ();
      if (! good ())
        return retval;

      retval = buf.str ();

      // Can I assume that the path is always in "" on the last line
      std::size_t pos2 = retval.rfind ('"');
      std::size_t pos1 = retval.rfind ('"', pos2 - 1);
      retval = retval.substr (pos1 + 1, pos2 - pos1 - 1);

      SETOPTR (CURLOPT_HEADERFUNCTION, 0);
      SETOPTR (CURLOPT_WRITEHEADER, 0);
      SETOPTR (CURLOPT_POSTQUOTE, 0);

      return retval;
    }

    void http_get (const Array<std::string>& param)
    {
      http_action (param, "get");
    }

    void http_post (const Array<std::string>& param)
    {
      http_action (param, "post");
    }

    void http_action (const Array<std::string>& param, const std::string& action)
    {
      m_url = m_host_or_url;

      std::string query_string;

      query_string = form_query_string (param);

      if (action.empty () || action == "get")
        {
          if (! query_string.empty ())
            m_url += '?' + query_string;

          SETOPT (CURLOPT_URL, m_url.c_str ());
        }
      else if (action == "post" || action == "put" || action == "delete")
        {
          SETOPT (CURLOPT_POSTFIELDS, query_string.c_str ());

          if (action == "put")
            {
              SETOPT (CURLOPT_CUSTOMREQUEST, "PUT");
            }

          if (action == "delete")
            {
              SETOPT (CURLOPT_CUSTOMREQUEST, "DELETE");
            }

          SETOPT (CURLOPT_URL, m_url.c_str ());
        }
      else
        {
          m_ok = false;
          m_errmsg = "curl_transfer: unknown http action";
        }

      if (m_ok)
        perform ();
    }

    void cookie_jar (const std::string& filename)
    {
      SETOPT (CURLOPT_COOKIEJAR, filename.c_str ());

      SETOPT (CURLOPT_COOKIEFILE, filename.c_str ());
    }

    // Sets the header fields in a transfer.  Input should be in the form
    // of an array of strings with pairs of keys and values together
    void set_header_fields (const Array<std::string>& param)
    {
      struct curl_slist *slist = nullptr;

      unwind_action cleanup_slist ([=] () { curl_slist_free_all (slist); });

      if (param.numel () >= 2)
        {
          for (int i = 0; i < param.numel (); i += 2)
            {
              std::string header = param(i) + ": " + param(i+1);

              slist = curl_slist_append (slist, header.c_str ());
            }

          SETOPT (CURLOPT_HTTPHEADER, slist);
        }
    }

    // Sets and sends the form data associated with a transfer.
    // Input should be an array of strings with each pair of strings
    // corresponding to the fieldname and it's value.
    // To attach a file, you should use 'file' as the fieldname with the
    // path of the file as its value.
    void form_data_post (const Array<std::string>& param)
    {
      struct curl_httppost *post = nullptr;
      struct curl_httppost *last = nullptr;

      SETOPT (CURLOPT_URL, m_host_or_url.c_str ());

      unwind_action cleanup_httppost ([=] () { curl_formfree (post); });

      if (param.numel () >= 2)
        {
          for (int i = 0; i < param.numel (); i += 2)
            {
              std::string name = param(i);
              std::string data = param(i+1);

              if (name == "file")
                curl_formadd (&post, &last, CURLFORM_COPYNAME, name.c_str (),
                              CURLFORM_FILE, data.c_str (), CURLFORM_END);
              else
                curl_formadd(&post, &last, CURLFORM_COPYNAME, name.c_str (),
                             CURLFORM_COPYCONTENTS, data.c_str (), CURLFORM_END);
            }

          SETOPT (CURLOPT_HTTPPOST, post);
        }

      perform ();
    }

    // Sets the various options specified by weboptions object.
    void set_weboptions (const struct weboptions& options)
    {
      // Remove this after completing fixmes.
      std::string temp = "";

      set_header_fields (options.HeaderFields);

      SETOPT (CURLOPT_TIMEOUT, options.Timeout);

      if (! options.UserAgent.empty ())
        SETOPT (CURLOPT_USERAGENT, options.UserAgent.c_str ());

      if (! options.Username.empty ())
        {
          if (! options.Password.empty ())
            {
              std::string tmp = options.Username + ":" + options.Password;
              SETOPT (CURLOPT_USERPWD, tmp.c_str ());
            }
          else
            {
              std::string tmp = options.Username + ":";
              SETOPT (CURLOPT_USERPWD, tmp.c_str ());
            }
        }

      // Unimplemented.  Only for MATLAB compatibility.
      if (! options.ContentReader.empty ())
        temp = options.ContentReader;

      // Unimplemented.  Only for MATLAB compatibility.
      if (! options.ArrayFormat.empty ())
        temp = options.ArrayFormat;

      // Unimplemented.  Only for MATLAB compatibility.
      if (! options.CertificateFilename.empty ())
        temp = options.CertificateFilename;
    }

  private:

    // Pointer to cURL object.
    CURL *m_curl;

    // cURL error code.
    CURLcode m_errnum;

    // The cURL library changed the curl_easy_setopt call to make an
    // internal copy of string parameters in version 7.17.0.  Prior
    // versions only held a pointer to a string provided by the caller
    // that must persist for the lifetime of the CURL handle.
    //
    // The associated API did not change, only the behavior of the library
    // implementing the function call.
    //
    // To be compatible with any version of cURL, the caller must keep a
    // copy of all string parameters associated with a CURL handle until
    // the handle is released.  The curl_handle::curl_handle_rep class
    // contains the pointer to the CURL handle and so is the best
    // candidate for storing the strings as well. (bug #36717)
    std::string m_url;
    std::string m_userpwd;

    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
      m_userpwd = user;
      if (! passwd.empty ())
        m_userpwd += ':' + passwd;
      if (! m_userpwd.empty ())
        SETOPT (CURLOPT_USERPWD, m_userpwd.c_str ());

      // Define our callback to get called when there's data to be written.
      SETOPT (CURLOPT_WRITEFUNCTION, write_data);

      // 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);

      // Set the user agent for the curl request
      // Needed by mediaWiki API.
      curl_version_info_data *data = curl_version_info(CURLVERSION_NOW);
      const char *lib_ver = data->version;
      std::string user_agent
        ("GNU Octave/"
         + std::string (OCTAVE_VERSION)
         + " (https://www.gnu.org/software/octave/ ; help@octave.org) libcurl/"
         + std::string (lib_ver));

      SETOPT (CURLOPT_USERAGENT, user_agent.c_str ());

      SETOPT (CURLOPT_NOPROGRESS, true);
      SETOPT (CURLOPT_FAILONERROR, true);

      SETOPT (CURLOPT_POSTQUOTE, 0);
      SETOPT (CURLOPT_QUOTE, 0);
    }

    std::string form_query_string (const Array<std::string>& param)
    {
      std::ostringstream query;

      if (param.numel () >= 2)
        for (int i = 0; i < param.numel (); i += 2)
          {
            std::string name = param(i);
            std::string text = param(i+1);

            // Encode strings.
            char *enc_name = curl_easy_escape (m_curl, name.c_str (),
                                               name.length ());
            char *enc_text = curl_easy_escape (m_curl, text.c_str (),
                                               text.length ());

            query << enc_name << '=' << enc_text;

            curl_free (enc_name);
            curl_free (enc_text);

            if (i < param.numel ()-2)
              query << '&';
          }

      query.flush ();

      return query.str ();
    }

    void ftp_file_or_dir_action (const std::string& file_or_dir,
                                 const std::string& action)
    {
      struct curl_slist *slist = nullptr;

      unwind_action cleanup_slist ([=] () { curl_slist_free_all (slist); });

      std::string cmd = action + ' ' + file_or_dir;

      slist = curl_slist_append (slist, cmd.c_str ());

      SETOPT (CURLOPT_POSTQUOTE, slist);

      perform ();

      if (! good ())
        return;

      SETOPT (CURLOPT_POSTQUOTE, 0);
    }
  };

#undef SETOPT

#endif

#if defined (HAVE_CURL)
#  define REP_CLASS curl_transfer
#else
#  define REP_CLASS base_url_transfer
#endif

  url_transfer::url_transfer (void) : m_rep (new REP_CLASS ())
  { }

  url_transfer::url_transfer (const std::string& host, const std::string& user,
                              const std::string& passwd, std::ostream& os)
    : m_rep (new REP_CLASS (host, user, passwd, os))
  { }

  url_transfer::url_transfer (const std::string& url, std::ostream& os)
    : m_rep (new REP_CLASS (url, os))
  { }

#undef REP_CLASS

OCTAVE_END_NAMESPACE(octave)