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