Use an enum class for Marker.
[libdcp.git] / src / util.cc
1 /*
2     Copyright (C) 2012-2020 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 /** @file  src/util.cc
35  *  @brief Utility methods.
36  */
37
38 #include "util.h"
39 #include "language_tag.h"
40 #include "exceptions.h"
41 #include "types.h"
42 #include "certificate.h"
43 #include "openjpeg_image.h"
44 #include "dcp_assert.h"
45 #include "compose.hpp"
46 #include <openjpeg.h>
47 #include <asdcp/KM_util.h>
48 #include <asdcp/KM_fileio.h>
49 #include <asdcp/AS_DCP.h>
50 #include <xmlsec/xmldsig.h>
51 #include <xmlsec/dl.h>
52 #include <xmlsec/app.h>
53 #include <xmlsec/crypto.h>
54 #include <libxml++/nodes/element.h>
55 #include <libxml++/document.h>
56 #include <openssl/sha.h>
57 #include <boost/filesystem.hpp>
58 #include <boost/algorithm/string.hpp>
59 #include <boost/foreach.hpp>
60 #include <stdexcept>
61 #include <iostream>
62 #include <iomanip>
63
64 using std::string;
65 using std::wstring;
66 using std::cout;
67 using std::min;
68 using std::max;
69 using std::setw;
70 using std::setfill;
71 using std::ostream;
72 using std::shared_ptr;
73 using std::vector;
74 using boost::shared_array;
75 using boost::optional;
76 using boost::function;
77 using boost::algorithm::trim;
78 using namespace dcp;
79
80
81 /* Some ASDCP objects store this as a *&, for reasons which are not
82  * at all clear, so we have to keep this around forever.
83  */
84 ASDCP::Dictionary const* dcp::asdcp_smpte_dict = 0;
85
86
87 /** Create a UUID.
88  *  @return UUID.
89  */
90 string
91 dcp::make_uuid ()
92 {
93         char buffer[64];
94         Kumu::UUID id;
95         Kumu::GenRandomValue (id);
96         id.EncodeHex (buffer, 64);
97         return string (buffer);
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 /** Create a digest for a file.
113  *  @param filename File name.
114  *  @param progress Optional progress reporting function.  The function will be called
115  *  with a progress value between 0 and 1.
116  *  @return Digest.
117  */
118 string
119 dcp::make_digest (boost::filesystem::path filename, function<void (float)> progress)
120 {
121         Kumu::FileReader reader;
122         Kumu::Result_t 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 (1) {
136                 ui32_t read = 0;
137                 Kumu::Result_t 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 /** @param s A string.
161  *  @return true if the string contains only space, newline or tab characters, or is empty.
162  */
163 bool
164 dcp::empty_or_white_space (string s)
165 {
166         for (size_t i = 0; i < s.length(); ++i) {
167                 if (s[i] != ' ' && s[i] != '\n' && s[i] != '\t') {
168                         return false;
169                 }
170         }
171
172         return true;
173 }
174
175 /** Set up various bits that the library needs.  Should be called once
176  *  by client applications.
177  *
178  *  @param tags_directory Path to a copy of the tags directory from the source code;
179  *  if none is specified libdcp will look for a tags directory inside the environment
180  *  variable LIBDCP_SHARE_PREFIX or the LIBDCP_SHARE_PREFIX #defined during the build.
181  */
182 void
183 dcp::init (optional<boost::filesystem::path> tags_directory)
184 {
185         if (xmlSecInit() < 0) {
186                 throw MiscError ("could not initialise xmlsec");
187         }
188
189 #ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
190         if (xmlSecCryptoDLLoadLibrary(BAD_CAST "openssl") < 0) {
191                 throw MiscError ("unable to load openssl xmlsec-crypto library");
192         }
193 #endif
194
195         if (xmlSecCryptoAppInit(0) < 0) {
196                 throw MiscError ("could not initialise crypto");
197         }
198
199         if (xmlSecCryptoInit() < 0) {
200                 throw MiscError ("could not initialise xmlsec-crypto");
201         }
202
203         OpenSSL_add_all_algorithms();
204
205         asdcp_smpte_dict = &ASDCP::DefaultSMPTEDict();
206
207         if (!tags_directory) {
208                 char* prefix = getenv("LIBDCP_SHARE_PREFIX");
209                 if (prefix) {
210                         tags_directory = boost::filesystem::path(prefix) / "tags";
211                 } else {
212                         tags_directory = LIBDCP_SHARE_PREFIX "/tags";
213                 }
214         }
215
216         load_language_tag_lists (*tags_directory);
217 }
218
219 /** Decode a base64 string.  The base64 decode routine in KM_util.cpp
220  *  gives different values to both this and the command-line base64
221  *  for some inputs.  Not sure why.
222  *
223  *  @param in base64-encoded string.
224  *  @param out Output buffer.
225  *  @param out_length Length of output buffer.
226  *  @return Number of characters written to the output buffer.
227  */
228 int
229 dcp::base64_decode (string const & in, unsigned char* out, int out_length)
230 {
231         BIO* b64 = BIO_new (BIO_f_base64 ());
232
233         /* This means the input should have no newlines */
234         BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
235
236         /* Copy our input string, removing newlines */
237         char in_buffer[in.size() + 1];
238         char* p = in_buffer;
239         for (size_t i = 0; i < in.size(); ++i) {
240                 if (in[i] != '\n' && in[i] != '\r') {
241                         *p++ = in[i];
242                 }
243         }
244
245         BIO* bmem = BIO_new_mem_buf (in_buffer, p - in_buffer);
246         bmem = BIO_push (b64, bmem);
247         int const N = BIO_read (bmem, out, out_length);
248         BIO_free_all (bmem);
249
250         return N;
251 }
252
253 /** @param p Path to open.
254  *  @param t mode flags, as for fopen(3).
255  *  @return FILE pointer or 0 on error.
256  *
257  *  Apparently there is no way to create an ofstream using a UTF-8
258  *  filename under Windows.  We are hence reduced to using fopen
259  *  with this wrapper.
260  */
261 FILE *
262 dcp::fopen_boost (boost::filesystem::path p, string t)
263 {
264 #ifdef LIBDCP_WINDOWS
265         wstring w (t.begin(), t.end());
266         /* c_str() here should give a UTF-16 string */
267         return _wfopen (p.c_str(), w.c_str ());
268 #else
269         return fopen (p.c_str(), t.c_str ());
270 #endif
271 }
272
273 optional<boost::filesystem::path>
274 dcp::relative_to_root (boost::filesystem::path root, boost::filesystem::path file)
275 {
276         boost::filesystem::path::const_iterator i = root.begin ();
277         boost::filesystem::path::const_iterator j = file.begin ();
278
279         while (i != root.end() && j != file.end() && *i == *j) {
280                 ++i;
281                 ++j;
282         }
283
284         if (i != root.end ()) {
285                 return optional<boost::filesystem::path> ();
286         }
287
288         boost::filesystem::path rel;
289         while (j != file.end ()) {
290                 rel /= *j++;
291         }
292
293         return rel;
294 }
295
296 bool
297 dcp::ids_equal (string a, string b)
298 {
299         transform (a.begin(), a.end(), a.begin(), ::tolower);
300         transform (b.begin(), b.end(), b.begin(), ::tolower);
301         trim (a);
302         trim (b);
303         return a == b;
304 }
305
306 string
307 dcp::file_to_string (boost::filesystem::path p, uintmax_t max_length)
308 {
309         uintmax_t len = boost::filesystem::file_size (p);
310         if (len > max_length) {
311                 throw MiscError (String::compose ("Unexpectedly long file (%1)", p.string()));
312         }
313
314         FILE* f = fopen_boost (p, "r");
315         if (!f) {
316                 throw FileError ("could not open file", p, errno);
317         }
318
319         char* c = new char[len];
320         /* This may read less than `len' if we are on Windows and we have CRLF in the file */
321         int const N = fread (c, 1, len, f);
322         fclose (f);
323
324         string s (c, N);
325         delete[] c;
326
327         return s;
328 }
329
330 /** @param key RSA private key in PEM format (optionally with -----BEGIN... / -----END...)
331  *  @return SHA1 fingerprint of key
332  */
333 string
334 dcp::private_key_fingerprint (string key)
335 {
336         boost::replace_all (key, "-----BEGIN RSA PRIVATE KEY-----\n", "");
337         boost::replace_all (key, "\n-----END RSA PRIVATE KEY-----\n", "");
338
339         unsigned char buffer[4096];
340         int const N = base64_decode (key, buffer, sizeof (buffer));
341
342         SHA_CTX sha;
343         SHA1_Init (&sha);
344         SHA1_Update (&sha, buffer, N);
345         uint8_t digest[20];
346         SHA1_Final (digest, &sha);
347
348         char digest_base64[64];
349         return Kumu::base64encode (digest, 20, digest_base64, 64);
350 }
351
352 xmlpp::Node *
353 dcp::find_child (xmlpp::Node const * node, string name)
354 {
355         xmlpp::Node::NodeList c = node->get_children ();
356         xmlpp::Node::NodeList::iterator i = c.begin();
357         while (i != c.end() && (*i)->get_name() != name) {
358                 ++i;
359         }
360
361         DCP_ASSERT (i != c.end ());
362         return *i;
363 }
364
365 string
366 dcp::remove_urn_uuid (string raw)
367 {
368         DCP_ASSERT (raw.substr(0, 9) == "urn:uuid:");
369         return raw.substr (9);
370 }
371
372 string
373 dcp::openjpeg_version ()
374 {
375         return opj_version ();
376 }
377
378 string
379 dcp::spaces (int n)
380 {
381         string s = "";
382         for (int i = 0; i < n; ++i) {
383                 s += " ";
384         }
385         return s;
386 }
387
388 void
389 dcp::indent (xmlpp::Element* element, int initial)
390 {
391         xmlpp::Node* last = 0;
392         BOOST_FOREACH (xmlpp::Node * n, element->get_children()) {
393                 xmlpp::Element* e = dynamic_cast<xmlpp::Element*>(n);
394                 if (e) {
395                         element->add_child_text_before (e, "\n" + spaces(initial + 2));
396                         indent (e, initial + 2);
397                         last = n;
398                 }
399         }
400         if (last) {
401                 element->add_child_text (last, "\n" + spaces(initial));
402         }
403 }
404
405 /** @return true if the day represented by \ref a is less than or
406  *  equal to the one represented by \ref b, ignoring the time parts.
407  */
408 bool
409 dcp::day_less_than_or_equal (LocalTime a, LocalTime b)
410 {
411         if (a.year() != b.year()) {
412                 return a.year() < b.year();
413         }
414
415         if (a.month() != b.month()) {
416                 return a.month() < b.month();
417         }
418
419         return a.day() <= b.day();
420 }
421
422 /** @return true if the day represented by \ref a is greater than or
423  *  equal to the one represented by \ref b, ignoring the time parts.
424  */
425 bool
426 dcp::day_greater_than_or_equal (LocalTime a, LocalTime b)
427 {
428         if (a.year() != b.year()) {
429                 return a.year() > b.year();
430         }
431
432         if (a.month() != b.month()) {
433                 return a.month() > b.month();
434         }
435
436         return a.day() >= b.day();
437 }
438
439 /** Try quite hard to find a string which starts with \ref base and is
440  *  not in \ref existing.
441  */
442 string
443 dcp::unique_string (vector<string> existing, string base)
444 {
445         int const max_tries = existing.size() + 1;
446         for (int i = 0; i < max_tries; ++i) {
447                 string trial = String::compose("%1%2", base, i);
448                 if (find(existing.begin(), existing.end(), trial) == existing.end()) {
449                         return trial;
450                 }
451         }
452
453         DCP_ASSERT (false);
454 }
455
456
457 ASDCPErrorSuspender::ASDCPErrorSuspender ()
458         : _old (Kumu::DefaultLogSink())
459 {
460         _sink = new Kumu::EntryListLogSink(_log);
461         Kumu::SetDefaultLogSink (_sink);
462 }
463
464
465 ASDCPErrorSuspender::~ASDCPErrorSuspender ()
466 {
467         Kumu::SetDefaultLogSink (&_old);
468         delete _sink;
469 }
470