Add a nice note for general MXF errors.
[libdcp.git] / src / util.cc
1 /*
2     Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
6     libdcp 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     libdcp 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 libdcp.  If not, see <http://www.gnu.org/licenses/>.
18
19     In addition, as a special exception, the copyright holders give
20     permission to link the code of portions of this program with the
21     OpenSSL library under certain conditions as described in each
22     individual source file, and distribute linked combinations
23     including the two.
24
25     You must obey the GNU General Public License in all respects
26     for all of the code used other than OpenSSL.  If you modify
27     file(s) with this exception, you may extend this exception to your
28     version of the file(s), but you are not obligated to do so.  If you
29     do not wish to do so, delete this exception statement from your
30     version.  If you delete this exception statement from all source
31     files in the program, then also delete it here.
32 */
33
34
35 /** @file  src/util.cc
36  *  @brief Utility methods and classes
37  */
38
39
40 #include "certificate.h"
41 #include "compose.hpp"
42 #include "dcp_assert.h"
43 #include "exceptions.h"
44 #include "file.h"
45 #include "language_tag.h"
46 #include "openjpeg_image.h"
47 #include "rating.h"
48 #include "types.h"
49 #include "util.h"
50 #include <openjpeg.h>
51 #include <asdcp/KM_util.h>
52 #include <asdcp/KM_fileio.h>
53 #include <asdcp/AS_DCP.h>
54 #include <xmlsec/xmldsig.h>
55 #include <xmlsec/dl.h>
56 #include <xmlsec/app.h>
57 #include <xmlsec/crypto.h>
58 #include <libxml++/nodes/element.h>
59 #include <libxml++/document.h>
60 #include <openssl/sha.h>
61 #include <boost/algorithm/string.hpp>
62 #if BOOST_VERSION >= 106100
63 #include <boost/dll/runtime_symbol_info.hpp>
64 #endif
65 #include <boost/filesystem.hpp>
66 #include <stdexcept>
67 #include <iostream>
68 #include <iomanip>
69
70
71 using std::string;
72 using std::wstring;
73 using std::cout;
74 using std::min;
75 using std::max;
76 using std::setw;
77 using std::setfill;
78 using std::ostream;
79 using std::shared_ptr;
80 using std::vector;
81 using boost::shared_array;
82 using boost::optional;
83 using boost::function;
84 using boost::algorithm::trim;
85 using namespace dcp;
86
87
88 /* Some ASDCP objects store this as a *&, for reasons which are not
89  * at all clear, so we have to keep this around forever.
90  */
91 ASDCP::Dictionary const* dcp::asdcp_smpte_dict = nullptr;
92
93
94 string
95 dcp::make_uuid ()
96 {
97         char buffer[64];
98         Kumu::UUID id;
99         Kumu::GenRandomValue (id);
100         id.EncodeHex (buffer, 64);
101         return string (buffer);
102 }
103
104
105 string
106 dcp::make_digest (ArrayData data)
107 {
108         SHA_CTX sha;
109         SHA1_Init (&sha);
110         SHA1_Update (&sha, data.data(), data.size());
111         byte_t byte_buffer[SHA_DIGEST_LENGTH];
112         SHA1_Final (byte_buffer, &sha);
113         char digest[64];
114         return Kumu::base64encode (byte_buffer, SHA_DIGEST_LENGTH, digest, 64);
115 }
116
117
118 string
119 dcp::make_digest (boost::filesystem::path filename, function<void (float)> progress)
120 {
121         Kumu::FileReader reader;
122         auto r = reader.OpenRead (filename.string().c_str ());
123         if (ASDCP_FAILURE(r)) {
124                 boost::throw_exception (FileError("could not open file to compute digest", filename, r));
125         }
126
127         SHA_CTX sha;
128         SHA1_Init (&sha);
129
130         int const buffer_size = 65536;
131         Kumu::ByteString read_buffer (buffer_size);
132
133         Kumu::fsize_t done = 0;
134         Kumu::fsize_t const size = reader.Size ();
135         while (true) {
136                 ui32_t read = 0;
137                 auto r = reader.Read (read_buffer.Data(), read_buffer.Capacity(), &read);
138
139                 if (r == Kumu::RESULT_ENDOFFILE) {
140                         break;
141                 } else if (ASDCP_FAILURE (r)) {
142                         boost::throw_exception (FileError("could not read file to compute digest", filename, r));
143                 }
144
145                 SHA1_Update (&sha, read_buffer.Data(), read);
146
147                 if (progress) {
148                         progress (float (done) / size);
149                         done += read;
150                 }
151         }
152
153         byte_t byte_buffer[SHA_DIGEST_LENGTH];
154         SHA1_Final (byte_buffer, &sha);
155
156         char digest[64];
157         return Kumu::base64encode (byte_buffer, SHA_DIGEST_LENGTH, digest, 64);
158 }
159
160
161 bool
162 dcp::empty_or_white_space (string s)
163 {
164         for (size_t i = 0; i < s.length(); ++i) {
165                 if (s[i] != ' ' && s[i] != '\n' && s[i] != '\t') {
166                         return false;
167                 }
168         }
169
170         return true;
171 }
172
173
174 void
175 dcp::init (optional<boost::filesystem::path> given_resources_directory)
176 {
177         if (xmlSecInit() < 0) {
178                 throw MiscError ("could not initialise xmlsec");
179         }
180
181 #ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
182         if (xmlSecCryptoDLLoadLibrary(BAD_CAST "openssl") < 0) {
183                 throw MiscError ("unable to load openssl xmlsec-crypto library");
184         }
185 #endif
186
187         if (xmlSecCryptoAppInit(0) < 0) {
188                 throw MiscError ("could not initialise crypto");
189         }
190
191         if (xmlSecCryptoInit() < 0) {
192                 throw MiscError ("could not initialise xmlsec-crypto");
193         }
194
195         OpenSSL_add_all_algorithms();
196
197         asdcp_smpte_dict = &ASDCP::DefaultSMPTEDict();
198
199         auto res = given_resources_directory.get_value_or(resources_directory());
200
201         load_language_tag_lists (res / "tags");
202         load_rating_list (res / "ratings");
203 }
204
205
206 int
207 dcp::base64_decode (string const & in, unsigned char* out, int out_length)
208 {
209         auto b64 = BIO_new (BIO_f_base64());
210
211         /* This means the input should have no newlines */
212         BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
213
214         /* Copy our input string, removing newlines */
215         char in_buffer[in.size() + 1];
216         char* p = in_buffer;
217         for (size_t i = 0; i < in.size(); ++i) {
218                 if (in[i] != '\n' && in[i] != '\r') {
219                         *p++ = in[i];
220                 }
221         }
222
223         auto bmem = BIO_new_mem_buf (in_buffer, p - in_buffer);
224         bmem = BIO_push (b64, bmem);
225         int const N = BIO_read (bmem, out, out_length);
226         BIO_free_all (bmem);
227
228         return N;
229 }
230
231
232 optional<boost::filesystem::path>
233 dcp::relative_to_root (boost::filesystem::path root, boost::filesystem::path file)
234 {
235         auto i = root.begin ();
236         auto j = file.begin ();
237
238         while (i != root.end() && j != file.end() && *i == *j) {
239                 ++i;
240                 ++j;
241         }
242
243         if (i != root.end()) {
244                 return {};
245         }
246
247         boost::filesystem::path rel;
248         while (j != file.end()) {
249                 rel /= *j++;
250         }
251
252         return rel;
253 }
254
255
256 bool
257 dcp::ids_equal (string a, string b)
258 {
259         transform (a.begin(), a.end(), a.begin(), ::tolower);
260         transform (b.begin(), b.end(), b.begin(), ::tolower);
261         trim (a);
262         trim (b);
263         return a == b;
264 }
265
266
267 string
268 dcp::file_to_string (boost::filesystem::path p, uintmax_t max_length)
269 {
270         auto len = boost::filesystem::file_size (p);
271         if (len > max_length) {
272                 throw MiscError (String::compose("Unexpectedly long file (%1)", p.string()));
273         }
274
275         File f(p, "r");
276         if (!f) {
277                 throw FileError ("could not open file", p, errno);
278         }
279
280         char* c = new char[len];
281         /* This may read less than `len' if we are on Windows and we have CRLF in the file */
282         int const N = f.read(c, 1, len);
283
284         string s (c, N);
285         delete[] c;
286
287         return s;
288 }
289
290
291 string
292 dcp::private_key_fingerprint (string key)
293 {
294         boost::replace_all (key, "-----BEGIN RSA PRIVATE KEY-----\n", "");
295         boost::replace_all (key, "\n-----END RSA PRIVATE KEY-----\n", "");
296         boost::replace_all (key, "-----BEGIN PRIVATE KEY-----\n", "");
297         boost::replace_all (key, "\n-----END PRIVATE KEY-----\n", "");
298
299         unsigned char buffer[4096];
300         int const N = base64_decode (key, buffer, sizeof (buffer));
301
302         SHA_CTX sha;
303         SHA1_Init (&sha);
304         SHA1_Update (&sha, buffer, N);
305         uint8_t digest[20];
306         SHA1_Final (digest, &sha);
307
308         char digest_base64[64];
309         return Kumu::base64encode (digest, 20, digest_base64, 64);
310 }
311
312
313 xmlpp::Node *
314 dcp::find_child (xmlpp::Node const * node, string name)
315 {
316         auto c = node->get_children ();
317         auto i = c.begin();
318         while (i != c.end() && (*i)->get_name() != name) {
319                 ++i;
320         }
321
322         DCP_ASSERT (i != c.end ());
323         return *i;
324 }
325
326
327 string
328 dcp::remove_urn_uuid (string raw)
329 {
330         DCP_ASSERT (raw.substr(0, 9) == "urn:uuid:");
331         return raw.substr (9);
332 }
333
334
335 string
336 dcp::openjpeg_version ()
337 {
338         return opj_version ();
339 }
340
341
342 string
343 dcp::spaces (int n)
344 {
345         string s = "";
346         for (int i = 0; i < n; ++i) {
347                 s += " ";
348         }
349         return s;
350 }
351
352
353 void
354 dcp::indent (xmlpp::Element* element, int initial)
355 {
356         xmlpp::Node* last = nullptr;
357         for (auto n: element->get_children()) {
358                 auto e = dynamic_cast<xmlpp::Element*>(n);
359                 if (e) {
360                         element->add_child_text_before (e, "\n" + spaces(initial + 2));
361                         indent (e, initial + 2);
362                         last = n;
363                 }
364         }
365         if (last) {
366                 element->add_child_text (last, "\n" + spaces(initial));
367         }
368 }
369
370
371 bool
372 dcp::day_less_than_or_equal (LocalTime a, LocalTime b)
373 {
374         if (a.year() != b.year()) {
375                 return a.year() < b.year();
376         }
377
378         if (a.month() != b.month()) {
379                 return a.month() < b.month();
380         }
381
382         return a.day() <= b.day();
383 }
384
385
386 bool
387 dcp::day_greater_than_or_equal (LocalTime a, LocalTime b)
388 {
389         if (a.year() != b.year()) {
390                 return a.year() > b.year();
391         }
392
393         if (a.month() != b.month()) {
394                 return a.month() > b.month();
395         }
396
397         return a.day() >= b.day();
398 }
399
400
401 string
402 dcp::unique_string (vector<string> existing, string base)
403 {
404         int const max_tries = existing.size() + 1;
405         for (int i = 0; i < max_tries; ++i) {
406                 string trial = String::compose("%1%2", base, i);
407                 if (find(existing.begin(), existing.end(), trial) == existing.end()) {
408                         return trial;
409                 }
410         }
411
412         DCP_ASSERT (false);
413 }
414
415
416 ASDCPErrorSuspender::ASDCPErrorSuspender ()
417         : _old (Kumu::DefaultLogSink())
418 {
419         _sink = new Kumu::EntryListLogSink(_log);
420         Kumu::SetDefaultLogSink (_sink);
421 }
422
423
424 ASDCPErrorSuspender::~ASDCPErrorSuspender ()
425 {
426         Kumu::SetDefaultLogSink (&_old);
427         delete _sink;
428 }
429
430
431 boost::filesystem::path dcp::directory_containing_executable ()
432 {
433 #if BOOST_VERSION >= 106100
434         return boost::filesystem::canonical(boost::dll::program_location().parent_path());
435 #else
436         char buffer[PATH_MAX];
437         ssize_t N = readlink ("/proc/self/exe", buffer, PATH_MAX);
438         return boost::filesystem::path(string(buffer, N)).parent_path();
439 #endif
440 }
441
442
443 boost::filesystem::path dcp::resources_directory ()
444 {
445         /* We need a way to specify the tags directory for running un-installed binaries */
446         char* prefix = getenv("LIBDCP_RESOURCES");
447         if (prefix) {
448                 return prefix;
449         }
450
451 #if defined(LIBDCP_OSX)
452         return directory_containing_executable().parent_path() / "Resources";
453 #elif defined(LIBDCP_WINDOWS)
454         return directory_containing_executable().parent_path();
455 #else
456         return directory_containing_executable().parent_path() / "share" / "libdcp";
457 #endif
458 }
459
460