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