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