2 Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
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.
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.
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/>.
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
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.
36 * @brief Utility methods and classes
41 #include "language_tag.h"
42 #include "exceptions.h"
44 #include "certificate.h"
45 #include "openjpeg_image.h"
46 #include "dcp_assert.h"
47 #include "compose.hpp"
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/algorithm/string.hpp>
60 #if BOOST_VERSION >= 106100
61 #include <boost/dll/runtime_symbol_info.hpp>
63 #include <boost/filesystem.hpp>
77 using std::shared_ptr;
79 using boost::shared_array;
80 using boost::optional;
81 using boost::function;
82 using boost::algorithm::trim;
86 /* Some ASDCP objects store this as a *&, for reasons which are not
87 * at all clear, so we have to keep this around forever.
89 ASDCP::Dictionary const* dcp::asdcp_smpte_dict = nullptr;
97 Kumu::GenRandomValue (id);
98 id.EncodeHex (buffer, 64);
99 return string (buffer);
104 dcp::make_digest (ArrayData data)
108 SHA1_Update (&sha, data.data(), data.size());
109 byte_t byte_buffer[SHA_DIGEST_LENGTH];
110 SHA1_Final (byte_buffer, &sha);
112 return Kumu::base64encode (byte_buffer, SHA_DIGEST_LENGTH, digest, 64);
117 dcp::make_digest (boost::filesystem::path filename, function<void (float)> progress)
119 Kumu::FileReader reader;
120 auto r = reader.OpenRead (filename.string().c_str ());
121 if (ASDCP_FAILURE(r)) {
122 boost::throw_exception (FileError("could not open file to compute digest", filename, r));
128 int const buffer_size = 65536;
129 Kumu::ByteString read_buffer (buffer_size);
131 Kumu::fsize_t done = 0;
132 Kumu::fsize_t const size = reader.Size ();
135 auto r = reader.Read (read_buffer.Data(), read_buffer.Capacity(), &read);
137 if (r == Kumu::RESULT_ENDOFFILE) {
139 } else if (ASDCP_FAILURE (r)) {
140 boost::throw_exception (FileError("could not read file to compute digest", filename, r));
143 SHA1_Update (&sha, read_buffer.Data(), read);
146 progress (float (done) / size);
151 byte_t byte_buffer[SHA_DIGEST_LENGTH];
152 SHA1_Final (byte_buffer, &sha);
155 return Kumu::base64encode (byte_buffer, SHA_DIGEST_LENGTH, digest, 64);
160 dcp::empty_or_white_space (string s)
162 for (size_t i = 0; i < s.length(); ++i) {
163 if (s[i] != ' ' && s[i] != '\n' && s[i] != '\t') {
173 dcp::init (optional<boost::filesystem::path> tags_directory)
175 if (xmlSecInit() < 0) {
176 throw MiscError ("could not initialise xmlsec");
179 #ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
180 if (xmlSecCryptoDLLoadLibrary(BAD_CAST "openssl") < 0) {
181 throw MiscError ("unable to load openssl xmlsec-crypto library");
185 if (xmlSecCryptoAppInit(0) < 0) {
186 throw MiscError ("could not initialise crypto");
189 if (xmlSecCryptoInit() < 0) {
190 throw MiscError ("could not initialise xmlsec-crypto");
193 OpenSSL_add_all_algorithms();
195 asdcp_smpte_dict = &ASDCP::DefaultSMPTEDict();
197 if (!tags_directory) {
198 tags_directory = resources_directory() / "tags";
201 load_language_tag_lists (*tags_directory);
206 dcp::base64_decode (string const & in, unsigned char* out, int out_length)
208 auto b64 = BIO_new (BIO_f_base64());
210 /* This means the input should have no newlines */
211 BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
213 /* Copy our input string, removing newlines */
214 char in_buffer[in.size() + 1];
216 for (size_t i = 0; i < in.size(); ++i) {
217 if (in[i] != '\n' && in[i] != '\r') {
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);
232 dcp::fopen_boost (boost::filesystem::path p, string t)
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 ());
239 return fopen (p.c_str(), t.c_str ());
244 optional<boost::filesystem::path>
245 dcp::relative_to_root (boost::filesystem::path root, boost::filesystem::path file)
247 auto i = root.begin ();
248 auto j = file.begin ();
250 while (i != root.end() && j != file.end() && *i == *j) {
255 if (i != root.end()) {
259 boost::filesystem::path rel;
260 while (j != file.end()) {
269 dcp::ids_equal (string a, string b)
271 transform (a.begin(), a.end(), a.begin(), ::tolower);
272 transform (b.begin(), b.end(), b.begin(), ::tolower);
280 dcp::file_to_string (boost::filesystem::path p, uintmax_t max_length)
282 auto len = boost::filesystem::file_size (p);
283 if (len > max_length) {
284 throw MiscError (String::compose("Unexpectedly long file (%1)", p.string()));
287 auto f = fopen_boost (p, "r");
289 throw FileError ("could not open file", p, errno);
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);
305 dcp::private_key_fingerprint (string key)
307 boost::replace_all (key, "-----BEGIN RSA PRIVATE KEY-----\n", "");
308 boost::replace_all (key, "\n-----END RSA PRIVATE KEY-----\n", "");
310 unsigned char buffer[4096];
311 int const N = base64_decode (key, buffer, sizeof (buffer));
315 SHA1_Update (&sha, buffer, N);
317 SHA1_Final (digest, &sha);
319 char digest_base64[64];
320 return Kumu::base64encode (digest, 20, digest_base64, 64);
325 dcp::find_child (xmlpp::Node const * node, string name)
327 auto c = node->get_children ();
329 while (i != c.end() && (*i)->get_name() != name) {
333 DCP_ASSERT (i != c.end ());
339 dcp::remove_urn_uuid (string raw)
341 DCP_ASSERT (raw.substr(0, 9) == "urn:uuid:");
342 return raw.substr (9);
347 dcp::openjpeg_version ()
349 return opj_version ();
357 for (int i = 0; i < n; ++i) {
365 dcp::indent (xmlpp::Element* element, int initial)
367 xmlpp::Node* last = nullptr;
368 for (auto n: element->get_children()) {
369 auto e = dynamic_cast<xmlpp::Element*>(n);
371 element->add_child_text_before (e, "\n" + spaces(initial + 2));
372 indent (e, initial + 2);
377 element->add_child_text (last, "\n" + spaces(initial));
383 dcp::day_less_than_or_equal (LocalTime a, LocalTime b)
385 if (a.year() != b.year()) {
386 return a.year() < b.year();
389 if (a.month() != b.month()) {
390 return a.month() < b.month();
393 return a.day() <= b.day();
398 dcp::day_greater_than_or_equal (LocalTime a, LocalTime b)
400 if (a.year() != b.year()) {
401 return a.year() > b.year();
404 if (a.month() != b.month()) {
405 return a.month() > b.month();
408 return a.day() >= b.day();
413 dcp::unique_string (vector<string> existing, string base)
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()) {
427 ASDCPErrorSuspender::ASDCPErrorSuspender ()
428 : _old (Kumu::DefaultLogSink())
430 _sink = new Kumu::EntryListLogSink(_log);
431 Kumu::SetDefaultLogSink (_sink);
435 ASDCPErrorSuspender::~ASDCPErrorSuspender ()
437 Kumu::SetDefaultLogSink (&_old);
442 boost::filesystem::path dcp::directory_containing_executable ()
444 #if BOOST_VERSION >= 106100
445 return boost::filesystem::canonical(boost::dll::program_location().parent_path());
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();
454 boost::filesystem::path dcp::resources_directory ()
456 /* We need a way to specify the tags directory for running un-installed binaries */
457 char* prefix = getenv("LIBDCP_RESOURCES");
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();
467 return directory_containing_executable().parent_path() / "share" / "libdcp";