Make FileError say what path the problem was with.
[dcpomatic.git] / src / lib / internet.cc
index aafdf3a839a342b99944c2c20bdd14a9c9c56b82..e0af49b6698e64c75148ec5ecfddee14e1c75969 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2014-2015 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2014-2019 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
@@ -21,6 +21,8 @@
 #include "scoped_temporary.h"
 #include "compose.hpp"
 #include "exceptions.h"
+#include "cross.h"
+#include "util.h"
 #include <curl/curl.h>
 #include <zip.h>
 #include <boost/function.hpp>
@@ -37,30 +39,28 @@ using boost::optional;
 using boost::function;
 using boost::algorithm::trim;
 
+
 static size_t
-get_from_zip_url_data (void* buffer, size_t size, size_t nmemb, void* stream)
+get_from_url_data (void* buffer, size_t size, size_t nmemb, void* stream)
 {
        FILE* f = reinterpret_cast<FILE*> (stream);
        return fwrite (buffer, size, nmemb, f);
 }
 
-/** @param url URL of ZIP file.
- *  @param file Filename within ZIP file.
- *  @param load Function passed a (temporary) filesystem path of the unpacked file.
- */
 optional<string>
-get_from_zip_url (string url, string file, bool pasv, function<void (boost::filesystem::path)> load)
+get_from_url (string url, bool pasv, bool skip_pasv_ip, ScopedTemporary& temp)
 {
-       /* Download the ZIP file to temp_zip */
        CURL* curl = curl_easy_init ();
-       curl_easy_setopt (curl, CURLOPT_URL, url.c_str ());
+       curl_easy_setopt (curl, CURLOPT_URL, url.c_str());
 
-       ScopedTemporary temp_zip;
-       FILE* f = temp_zip.open ("wb");
-       curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, get_from_zip_url_data);
+       FILE* f = temp.open ("wb");
+       curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, get_from_url_data);
        curl_easy_setopt (curl, CURLOPT_WRITEDATA, f);
        curl_easy_setopt (curl, CURLOPT_FTP_USE_EPSV, 0);
        curl_easy_setopt (curl, CURLOPT_FTP_USE_EPRT, 0);
+       if (skip_pasv_ip) {
+               curl_easy_setopt (curl, CURLOPT_FTP_SKIP_PASV_IP, 1);
+       }
        if (!pasv) {
                curl_easy_setopt (curl, CURLOPT_FTPPORT, "-");
        }
@@ -70,101 +70,89 @@ get_from_zip_url (string url, string file, bool pasv, function<void (boost::file
 
        CURLcode const cr = curl_easy_perform (curl);
 
-       temp_zip.close ();
+       temp.close ();
        curl_easy_cleanup (curl);
        if (cr != CURLE_OK) {
-               return String::compose (_("Download failed (%1/%2 error %3)"), url, file, (int) cr);
+               return String::compose (_("Download failed (%1 error %2)"), url, (int) cr);
+       }
+
+       return optional<string>();
+}
+
+optional<string>
+get_from_url (string url, bool pasv, bool skip_pasv_ip, function<void (boost::filesystem::path)> load)
+{
+       ScopedTemporary temp;
+       optional<string> e = get_from_url (url, pasv, skip_pasv_ip, temp);
+       if (e) {
+               return e;
+       }
+       load (temp.file());
+       return optional<string>();
+}
+
+/** @param url URL of ZIP file.
+ *  @param file Filename within ZIP file.
+ *  @param load Function passed a (temporary) filesystem path of the unpacked file.
+ */
+optional<string>
+get_from_zip_url (string url, string file, bool pasv, bool skip_pasv_ip, function<void (boost::filesystem::path)> load)
+{
+       /* Download the ZIP file to temp_zip */
+       ScopedTemporary temp_zip;
+       optional<string> e = get_from_url (url, pasv, skip_pasv_ip, temp_zip);
+       if (e) {
+               return e;
        }
 
        /* Open the ZIP file and read `file' out of it */
 
-       struct zip* zip = zip_open (temp_zip.c_str(), 0, 0);
-       if (!zip) {
+#ifdef DCPOMATIC_HAVE_ZIP_SOURCE_T
+       /* This is the way to do it with newer versions of libzip, and is required on Windows.
+          The zip_source_t API is missing in the libzip versions shipped with Ubuntu 14.04,
+          Centos 6, Centos 7, Debian 7 and Debian 8.
+       */
+
+       FILE* zip_file = fopen_boost (temp_zip.file (), "rb");
+       if (!zip_file) {
                return optional<string> (_("Could not open downloaded ZIP file"));
        }
 
-       struct zip_file* zip_file = zip_fopen (zip, file.c_str(), 0);
-       if (!zip_file) {
+       zip_source_t* zip_source = zip_source_filep_create (zip_file, 0, -1, 0);
+       if (!zip_source) {
+               return optional<string> (_("Could not open downloaded ZIP file"));
+       }
+
+       zip_error_t error;
+       zip_error_init (&error);
+       zip_t* zip = zip_open_from_source (zip_source, ZIP_RDONLY, &error);
+       if (!zip) {
+               return String::compose (_("Could not open downloaded ZIP file (%1:%2: %3)"), error.zip_err, error.sys_err, error.str ? error.str : "");
+       }
+
+#else
+       struct zip* zip = zip_open (temp_zip.c_str(), 0, 0);
+#endif
+
+       struct zip_file* file_in_zip = zip_fopen (zip, file.c_str(), 0);
+       if (!file_in_zip) {
                return optional<string> (_("Unexpected ZIP file contents"));
        }
 
        ScopedTemporary temp_cert;
-       f = temp_cert.open ("wb");
+       FILE* f = temp_cert.open ("wb");
        char buffer[4096];
        while (true) {
-               int const N = zip_fread (zip_file, buffer, sizeof (buffer));
-               fwrite (buffer, 1, N, f);
+               int const N = zip_fread (file_in_zip, buffer, sizeof (buffer));
+               checked_fwrite (buffer, N, f, temp_cert.file());
                if (N < int (sizeof (buffer))) {
                        break;
                }
        }
+       zip_fclose (file_in_zip);
+       zip_close (zip);
        temp_cert.close ();
 
        load (temp_cert.file ());
        return optional<string> ();
 }
-
-static size_t
-ftp_ls_data (void* buffer, size_t size, size_t nmemb, void* data)
-{
-       string* s = reinterpret_cast<string *> (data);
-       uint8_t* b = reinterpret_cast<uint8_t *> (buffer);
-       for (size_t i = 0; i < (size * nmemb); ++i) {
-               *s += b[i];
-       }
-       return size * nmemb;
-}
-
-list<string>
-ftp_ls (string url, bool pasv)
-{
-       CURL* curl = curl_easy_init ();
-       if (!curl) {
-               throw NetworkError ("could not set up curl");
-       }
-
-       if (url.substr (url.length() - 1, 1) != "/") {
-               url += "/";
-       }
-       curl_easy_setopt (curl, CURLOPT_URL, url.c_str ());
-       /* 20s timeout */
-       curl_easy_setopt (curl, CURLOPT_TIMEOUT, 20);
-
-       string ls_raw;
-       struct curl_slist* commands = 0;
-       commands = curl_slist_append (commands, "NLST");
-       curl_easy_setopt (curl, CURLOPT_POSTQUOTE, commands);
-       curl_easy_setopt (curl, CURLOPT_WRITEDATA, &ls_raw);
-       curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, ftp_ls_data);
-       curl_easy_setopt (curl, CURLOPT_FTP_USE_EPSV, 0);
-       curl_easy_setopt (curl, CURLOPT_FTP_USE_EPRT, 0);
-       curl_easy_setopt (curl, CURLOPT_VERBOSE, 1);
-       if (!pasv) {
-               curl_easy_setopt (curl, CURLOPT_FTPPORT, "-");
-       }
-       CURLcode const r = curl_easy_perform (curl);
-       if (r != CURLE_OK) {
-               curl_easy_cleanup (curl);
-               throw NetworkError (curl_easy_strerror (r));
-       }
-
-       list<string> ls;
-       string line;
-       for (size_t i = 0; i < ls_raw.length(); ++i) {
-               line += ls_raw[i];
-               if (ls_raw[i] == '\n') {
-                       trim (line);
-                       if (line.length() > 55) {
-                               string const file = line.substr (55);
-                               if (file != "." && file != "..") {
-                                       ls.push_back (file);
-                               }
-                       }
-                       line = "";
-               }
-       }
-
-       curl_easy_cleanup (curl);
-
-       return ls;
-}