Support download of KDMs from a web service in swaroop profile.
[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 ("w");
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_t* zip = zip_open_from_source (zip_source, 0, 0);
123         if (!zip) {
124                 return optional<string> (_("Could not open downloaded ZIP file"));
125         }
126
127 #else
128         struct zip* zip = zip_open (temp_zip.c_str(), 0, 0);
129 #endif
130
131         struct zip_file* file_in_zip = zip_fopen (zip, file.c_str(), 0);
132         if (!file_in_zip) {
133                 return optional<string> (_("Unexpected ZIP file contents"));
134         }
135
136         ScopedTemporary temp_cert;
137         FILE* f = temp_cert.open ("wb");
138         char buffer[4096];
139         while (true) {
140                 int const N = zip_fread (file_in_zip, buffer, sizeof (buffer));
141                 fwrite (buffer, 1, N, f);
142                 if (N < int (sizeof (buffer))) {
143                         break;
144                 }
145         }
146         zip_fclose (file_in_zip);
147         zip_close (zip);
148         temp_cert.close ();
149
150         load (temp_cert.file ());
151         return optional<string> ();
152 }