ad313bd3f71fb7623dcef484bc8f6d3c70b3c64f
[dcpomatic.git] / src / lib / internet.cc
1 /*
2     Copyright (C) 2014-2015 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 #include "scoped_temporary.h"
22 #include "compose.hpp"
23 #include "exceptions.h"
24 #include "cross.h"
25 #include <curl/curl.h>
26 #include <zip.h>
27 #include <boost/function.hpp>
28 #include <boost/optional.hpp>
29 #include <boost/filesystem.hpp>
30 #include <boost/algorithm/string.hpp>
31 #include <string>
32
33 #include "i18n.h"
34
35 using std::string;
36 using std::list;
37 using boost::optional;
38 using boost::function;
39 using boost::algorithm::trim;
40
41
42 static size_t
43 get_from_url_data (void* buffer, size_t size, size_t nmemb, void* stream)
44 {
45         FILE* f = reinterpret_cast<FILE*> (stream);
46         return fwrite (buffer, size, nmemb, f);
47 }
48
49 optional<string>
50 get_from_url (string url, bool pasv, ScopedTemporary& temp)
51 {
52         CURL* curl = curl_easy_init ();
53         curl_easy_setopt (curl, CURLOPT_URL, url.c_str());
54
55         FILE* f = temp.open ("wb");
56         curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, get_from_url_data);
57         curl_easy_setopt (curl, CURLOPT_WRITEDATA, f);
58         curl_easy_setopt (curl, CURLOPT_FTP_USE_EPSV, 0);
59         curl_easy_setopt (curl, CURLOPT_FTP_USE_EPRT, 0);
60         if (!pasv) {
61                 curl_easy_setopt (curl, CURLOPT_FTPPORT, "-");
62         }
63
64         /* Maximum time is 20s */
65         curl_easy_setopt (curl, CURLOPT_TIMEOUT, 20);
66
67         CURLcode const cr = curl_easy_perform (curl);
68
69         temp.close ();
70         curl_easy_cleanup (curl);
71         if (cr != CURLE_OK) {
72                 return String::compose (_("Download failed (%1 error %2)"), url, (int) cr);
73         }
74
75         return optional<string>();
76 }
77
78 optional<string>
79 get_from_url (string url, bool pasv, function<void (boost::filesystem::path)> load)
80 {
81         ScopedTemporary temp;
82         optional<string> e = get_from_url (url, pasv, temp);
83         if (e) {
84                 return e;
85         }
86         load (temp.file());
87         return optional<string>();
88 }
89
90 /** @param url URL of ZIP file.
91  *  @param file Filename within ZIP file.
92  *  @param load Function passed a (temporary) filesystem path of the unpacked file.
93  */
94 optional<string>
95 get_from_zip_url (string url, string file, bool pasv, function<void (boost::filesystem::path)> load)
96 {
97         /* Download the ZIP file to temp_zip */
98         ScopedTemporary temp_zip;
99         optional<string> e = get_from_url (url, pasv, temp_zip);
100         if (e) {
101                 return e;
102         }
103
104         /* Open the ZIP file and read `file' out of it */
105
106 #ifdef DCPOMATIC_HAVE_ZIP_SOURCE_T
107         /* This is the way to do it with newer versions of libzip, and is required on Windows.
108            The zip_source_t API is missing in the libzip versions shipped with Ubuntu 14.04,
109            Centos 6, Centos 7, Debian 7 and Debian 8.
110         */
111
112         FILE* zip_file = fopen_boost (temp_zip.file (), "rb");
113         if (!zip_file) {
114                 return optional<string> (_("Could not open downloaded ZIP file"));
115         }
116
117         zip_source_t* zip_source = zip_source_filep_create (zip_file, 0, -1, 0);
118         if (!zip_source) {
119                 return optional<string> (_("Could not open downloaded ZIP file"));
120         }
121
122         zip_error_t error;
123         zip_error_init (&error);
124         zip_t* zip = zip_open_from_source (zip_source, ZIP_RDONLY, &error);
125         if (!zip) {
126                 return String::compose (_("Could not open downloaded ZIP file (%1:%2: %3)"), error.zip_err, error.sys_err, error.str ? error.str : "");
127         }
128
129 #else
130         struct zip* zip = zip_open (temp_zip.c_str(), 0, 0);
131 #endif
132
133         struct zip_file* file_in_zip = zip_fopen (zip, file.c_str(), 0);
134         if (!file_in_zip) {
135                 return optional<string> (_("Unexpected ZIP file contents"));
136         }
137
138         ScopedTemporary temp_cert;
139         FILE* f = temp_cert.open ("wb");
140         char buffer[4096];
141         while (true) {
142                 int const N = zip_fread (file_in_zip, buffer, sizeof (buffer));
143                 fwrite (buffer, 1, N, f);
144                 if (N < int (sizeof (buffer))) {
145                         break;
146                 }
147         }
148         zip_fclose (file_in_zip);
149         zip_close (zip);
150         temp_cert.close ();
151
152         load (temp_cert.file ());
153         return optional<string> ();
154 }