Mercurial > octave
diff liboctave/util/url-transfer.cc @ 17555:0946b0e06544
move url_transfer classes to liboctave
* liboctave/util/url-transfer.h, liboctave/util/url-transfer.cc:
New files, extracted from libinterp/dldfcn/urlwrite.cc.
* libinterp/corefcn/urlwrite.cc: Move here from
libinterp/dldfcn/urlwrite.cc.
* libinterp/corefcn/module.mk, libinterp/dldfcn/module-files,
liboctave/link-deps.mk liboctave/util/module.mk: Update for new and
renamed files.
author | John W. Eaton <jwe@octave.org> |
---|---|
date | Thu, 03 Oct 2013 15:52:49 -0400 |
parents | |
children | 58039875767d |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/liboctave/util/url-transfer.cc Thu Oct 03 15:52:49 2013 -0400 @@ -0,0 +1,793 @@ +/* + +Copyright (C) 2013 John W. Eaton +Copyright (C) 2006-2012 Alexander Barth +Copyright (C) 2009 David Bateman + +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 +<http://www.gnu.org/licenses/>. + +*/ + +// Author: Alexander Barth <abarth@marine.usf.edu> +// Author: jwe + +#ifdef 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 "unwind-prot.h" +#include "url-transfer.h" + +#ifdef HAVE_CURL +#include <curl/curl.h> +#include <curl/curlver.h> +#include <curl/easy.h> +#endif + +void +base_url_transfer::mget_directory (const std::string& directory, + const std::string& target) +{ + std::string sep = file_ops::dir_sep_str (); + file_stat fs (directory); + + if (!fs || !fs.is_dir ()) + { + std::string msg; + int status = octave_mkdir (directory, 0777, msg); + + if (status < 0) + { + ok = false; + errmsg = "__ftp_mget__: can not create directory '" + + target + sep + directory + "': " + msg; + return; + } + } + + cwd (directory); + + if (good ()) + { + unwind_protect_safe frame; + + frame.add_fcn (reset_path, this); + + string_vector sv = list (); + + for (octave_idx_type i = 0; i < sv.length (); i++) + { + 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 (realfile.c_str (), + std::ios::out | std::ios::binary); + + if (! ofile.is_open ()) + { + ok = false; + errmsg = "__ftp_mget__: unable to open file"; + break; + } + + unwind_protect_safe frame2; + + frame2.add_fcn (delete_file, realfile); + + get (sv(i), ofile); + + ofile.close (); + + if (good ()) + frame2.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.length () == 0 + ? directory : base + file_ops::dir_sep_str () + directory); + + mkdir (directory); + + if (! good ()) + return file_list; + + cwd (directory); + + if (good ()) + { + unwind_protect_safe frame; + + frame.add_fcn (reset_path, this); + + 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 ()) + { + ok = false; + 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 (realfile.c_str (), std::ios::in | + std::ios::binary); + + if (! ifile.is_open ()) + { + ok = false; + errmsg = "__ftp_mput__: unable to open file '" + + realfile + "'"; + break; + } + + put (file, ifile); + + ifile.close (); + + if (! good ()) + break; + + file_list.append (realfile); + } + } + } + else + { + ok = false; + errmsg = "__ftp_mput__: can not read the directory '" + + realdir + "'"; + } + } +} + +#if defined (HAVE_CURL) + +static int +write_data (void *buffer, size_t size, 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, size_t size, 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 size_t +throw_away (void *, size_t size, size_t nmemb, void *) +{ + return static_cast<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 (curl, option, parameter); \ + if (res != CURLE_OK) \ + { \ + ok = false; \ + 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 (curl, option, parameter); \ + if (res != CURLE_OK) \ + { \ + ok = false; \ + errmsg = curl_easy_strerror (res); \ + return retval; \ + } \ + } \ + while (0) + +class curl_transfer : public base_url_transfer +{ +public: + + curl_transfer (void) + : base_url_transfer (), curl (curl_easy_init ()), errnum () + { + if (curl) + valid = true; + else + errmsg = "can not create curl object"; + } + + curl_transfer (const std::string& host_arg, const std::string& user_arg, + const std::string& passwd, std::ostream& os) + : base_url_transfer (host_arg, user_arg, passwd, os), + curl (curl_easy_init ()), errnum () + { + if (curl) + valid = true; + else + { + errmsg = "can not create curl object"; + return; + } + + init (user_arg, passwd, std::cin, os); + + std::string url ("ftp://" + host_arg); + SETOPT (CURLOPT_URL, url.c_str ()); + + // Setup the link, with no transfer. + perform (); + } + + curl_transfer (const std::string& url, const std::string& method, + const Array<std::string>& param, std::ostream& os) + : base_url_transfer (url, method, param, os), + curl (curl_easy_init ()), errnum () + { + if (curl) + valid = true; + else + { + errmsg = "can not create curl object"; + return; + } + + init ("", "", std::cin, os); + + SETOPT (CURLOPT_NOBODY, 0); + + // Restore the default HTTP request method to GET after setting + // NOBODY to true and back to false. This is needed for backward + // compatibility with versions of libcurl < 7.18.2. + SETOPT (CURLOPT_HTTPGET, 1); + + // Don't need to store the parameters here as we can't change + // the URL after the object is created + std::string query_string = form_query_string (param); + + 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 ()); + + perform (); + } + + ~curl_transfer (void) + { + if (curl) + curl_easy_cleanup (curl); + } + + void perform (void) + { + BEGIN_INTERRUPT_IMMEDIATELY_IN_FOREIGN_CODE; + + errnum = curl_easy_perform (curl); + + if (errnum != CURLE_OK) + { + ok = false; + errmsg = curl_easy_strerror (errnum); + } + + END_INTERRUPT_IMMEDIATELY_IN_FOREIGN_CODE; + } + + std::string lasterror (void) const + { + return std::string (curl_easy_strerror (errnum)); + } + + std::ostream& set_ostream (std::ostream& os) + { + std::ostream& retval = *curr_ostream; + curr_ostream = &os; + SETOPTR (CURLOPT_WRITEDATA, static_cast<void*> (curr_ostream)); + return retval; + } + + std::istream& set_istream (std::istream& is) + { + std::istream& retval = *curr_istream; + curr_istream = &is; + SETOPTR (CURLOPT_READDATA, static_cast<void*> (curr_istream)); + return retval; + } + + void ascii (void) + { + ascii_mode = true; + SETOPT (CURLOPT_TRANSFERTEXT, 1); + } + + void binary (void) + { + ascii_mode = false; + SETOPT (CURLOPT_TRANSFERTEXT, 0); + } + + void cwd (const std::string& path) + { + struct curl_slist *slist = 0; + + unwind_protect frame; + frame.add_fcn (curl_slist_free_all, slist); + + std::string cmd = "cwd " + path; + slist = curl_slist_append (slist, cmd.c_str ()); + SETOPT (CURLOPT_POSTQUOTE, slist); + + perform (); + if (! good ()) + return; + + SETOPT (CURLOPT_POSTQUOTE, 0); + } + + void del (const std::string& file) + { + struct curl_slist *slist = 0; + + unwind_protect frame; + frame.add_fcn (curl_slist_free_all, slist); + + std::string cmd = "dele " + file; + slist = curl_slist_append (slist, cmd.c_str ()); + SETOPT (CURLOPT_POSTQUOTE, slist); + + perform (); + if (! good ()) + return; + + SETOPT (CURLOPT_POSTQUOTE, 0); + } + + void rmdir (const std::string& path) + { + struct curl_slist *slist = 0; + + unwind_protect frame; + frame.add_fcn (curl_slist_free_all, slist); + + std::string cmd = "rmd " + path; + slist = curl_slist_append (slist, cmd.c_str ()); + SETOPT (CURLOPT_POSTQUOTE, slist); + + perform (); + if (! good ()) + return; + + SETOPT (CURLOPT_POSTQUOTE, 0); + } + + void mkdir (const std::string& path) + { + struct curl_slist *slist = 0; + + unwind_protect frame; + frame.add_fcn (curl_slist_free_all, slist); + + std::string cmd = "mkd " + path; + slist = curl_slist_append (slist, cmd.c_str ()); + SETOPT (CURLOPT_POSTQUOTE, slist); + + perform (); + if (! good ()) + return; + + SETOPT (CURLOPT_POSTQUOTE, 0); + } + + void rename (const std::string& oldname, const std::string& newname) + { + struct curl_slist *slist = 0; + + unwind_protect frame; + frame.add_fcn (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) + { + std::string url = "ftp://" + host + "/" + file; + SETOPT (CURLOPT_URL, 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); + url = "ftp://" + host; + SETOPT (CURLOPT_URL, url.c_str ()); + } + + void get (const std::string& file, std::ostream& os) + { + std::string url = "ftp://" + host + "/" + file; + SETOPT (CURLOPT_URL, 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); + url = "ftp://" + host; + SETOPT (CURLOPT_URL, url.c_str ()); + } + + void dir (void) + { + std::string url = "ftp://" + host + "/"; + SETOPT (CURLOPT_URL, url.c_str ()); + SETOPT (CURLOPT_NOBODY, 0); + + perform (); + if (! good ()) + return; + + SETOPT (CURLOPT_NOBODY, 1); + url = "ftp://" + host; + SETOPT (CURLOPT_URL, url.c_str ()); + } + + string_vector list (void) + { + string_vector retval; + + std::ostringstream buf; + std::string url = "ftp://" + host + "/"; + SETOPTR (CURLOPT_WRITEDATA, static_cast<void*> (&buf)); + SETOPTR (CURLOPT_URL, url.c_str ()); + SETOPTR (CURLOPT_DIRLISTONLY, 1); + SETOPTR (CURLOPT_NOBODY, 0); + + perform (); + if (! good ()) + return retval; + + SETOPTR (CURLOPT_NOBODY, 1); + url = "ftp://" + host; + SETOPTR (CURLOPT_WRITEDATA, static_cast<void*> (curr_ostream)); + SETOPTR (CURLOPT_DIRLISTONLY, 0); + SETOPTR (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++; + } + retval.resize (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) + { + std::string path = pwd (); + + std::string url = "ftp://" + host + "/" + path + "/" + filename; + SETOPT (CURLOPT_URL, 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; + time_t ft; + curl_easy_getinfo (curl, CURLINFO_FILETIME, &ft); + filetime = ft; + double fs; + curl_easy_getinfo (curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &fs); + filesize = fs; + + SETOPT (CURLOPT_WRITEFUNCTION, write_data); + SETOPT (CURLOPT_HEADERFUNCTION, 0); + SETOPT (CURLOPT_FILETIME, 0); + url = "ftp://" + 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) + { + std::string retval; + + struct curl_slist *slist = 0; + + unwind_protect frame; + frame.add_fcn (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 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); + + SETOPTR (CURLOPT_HEADERFUNCTION, 0); + SETOPTR (CURLOPT_WRITEHEADER, 0); + SETOPTR (CURLOPT_POSTQUOTE, 0); + + return retval; + } + +private: + + CURL *curl; + CURLcode errnum; + + // No copying! + + curl_transfer (const curl_transfer&); + + curl_transfer& operator = (const curl_transfer&); + + 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 + userpwd = user; + if (! passwd.empty ()) + userpwd += ":" + passwd; + if (! userpwd.empty ()) + SETOPT (CURLOPT_USERPWD, 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); + + 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; + + 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 (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 (); + } +}; + +#undef SETOPT + +#else + +static void +disabled_error (void) +{ + error ("support for url transfers was disabled when Octave was built"); +} + +#endif + +#if defined (HAVE_CURL) +# define REP_CLASS curl_transfer +#else +# define REP_CLASS base_url_transfer +#endif + +url_transfer::url_transfer (void) : rep (new REP_CLASS ()) +{ +#if !defined (HAVE_CURL) + disabled_error (); +#endif +} + +url_transfer::url_transfer (const std::string& host, const std::string& user, + const std::string& passwd, std::ostream& os) + : rep (new REP_CLASS (host, user, passwd, os)) +{ +#if !defined (HAVE_CURL) + disabled_error (); +#endif +} + +url_transfer::url_transfer (const std::string& url, const std::string& method, + const Array<std::string>& param, std::ostream& os) + : rep (new REP_CLASS (url, method, param, os)) +{ +#if !defined (HAVE_CURL) + disabled_error (); +#endif +} + +#undef REP_CLASS