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
40 #include "certificate.h"
41 #include "compose.hpp"
42 #include "dcp_assert.h"
43 #include "exceptions.h"
45 #include "language_tag.h"
46 #include "openjpeg_image.h"
51 #include <asdcp/KM_util.h>
52 #include <asdcp/KM_fileio.h>
53 #include <asdcp/AS_DCP.h>
54 #include <xmlsec/xmldsig.h>
55 #include <xmlsec/dl.h>
56 #include <xmlsec/app.h>
57 #include <xmlsec/crypto.h>
58 #include <libxml++/nodes/element.h>
59 #include <libxml++/document.h>
60 #include <openssl/sha.h>
61 #include <boost/algorithm/string.hpp>
62 #if BOOST_VERSION >= 106100
63 #include <boost/dll/runtime_symbol_info.hpp>
65 #include <boost/filesystem.hpp>
79 using std::shared_ptr;
81 using boost::shared_array;
82 using boost::optional;
83 using boost::function;
84 using boost::algorithm::trim;
88 /* Some ASDCP objects store this as a *&, for reasons which are not
89 * at all clear, so we have to keep this around forever.
91 ASDCP::Dictionary const* dcp::asdcp_smpte_dict = nullptr;
99 Kumu::GenRandomValue (id);
100 id.EncodeHex (buffer, 64);
101 return string (buffer);
106 dcp::make_digest (ArrayData data)
110 SHA1_Update (&sha, data.data(), data.size());
111 byte_t byte_buffer[SHA_DIGEST_LENGTH];
112 SHA1_Final (byte_buffer, &sha);
114 return Kumu::base64encode (byte_buffer, SHA_DIGEST_LENGTH, digest, 64);
119 dcp::make_digest (boost::filesystem::path filename, function<void (float)> progress)
121 Kumu::FileReader reader;
122 auto 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));
130 int const buffer_size = 65536;
131 Kumu::ByteString read_buffer (buffer_size);
133 Kumu::fsize_t done = 0;
134 Kumu::fsize_t const size = reader.Size ();
137 auto r = reader.Read (read_buffer.Data(), read_buffer.Capacity(), &read);
139 if (r == Kumu::RESULT_ENDOFFILE) {
141 } else if (ASDCP_FAILURE (r)) {
142 boost::throw_exception (FileError("could not read file to compute digest", filename, r));
145 SHA1_Update (&sha, read_buffer.Data(), read);
148 progress (float (done) / size);
153 byte_t byte_buffer[SHA_DIGEST_LENGTH];
154 SHA1_Final (byte_buffer, &sha);
157 return Kumu::base64encode (byte_buffer, SHA_DIGEST_LENGTH, digest, 64);
162 dcp::empty_or_white_space (string s)
164 for (size_t i = 0; i < s.length(); ++i) {
165 if (s[i] != ' ' && s[i] != '\n' && s[i] != '\t') {
175 dcp::init (optional<boost::filesystem::path> given_resources_directory)
177 if (xmlSecInit() < 0) {
178 throw MiscError ("could not initialise xmlsec");
181 #ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
182 if (xmlSecCryptoDLLoadLibrary(BAD_CAST "openssl") < 0) {
183 throw MiscError ("unable to load openssl xmlsec-crypto library");
187 if (xmlSecCryptoAppInit(0) < 0) {
188 throw MiscError ("could not initialise crypto");
191 if (xmlSecCryptoInit() < 0) {
192 throw MiscError ("could not initialise xmlsec-crypto");
195 OpenSSL_add_all_algorithms();
197 asdcp_smpte_dict = &ASDCP::DefaultSMPTEDict();
199 auto res = given_resources_directory.get_value_or(resources_directory());
201 load_language_tag_lists (res / "tags");
202 load_rating_list (res / "ratings");
207 dcp::base64_decode (string const & in, unsigned char* out, int out_length)
209 auto b64 = BIO_new (BIO_f_base64());
211 /* This means the input should have no newlines */
212 BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
214 /* Copy our input string, removing newlines */
215 char in_buffer[in.size() + 1];
217 for (size_t i = 0; i < in.size(); ++i) {
218 if (in[i] != '\n' && in[i] != '\r') {
223 auto bmem = BIO_new_mem_buf (in_buffer, p - in_buffer);
224 bmem = BIO_push (b64, bmem);
225 int const N = BIO_read (bmem, out, out_length);
232 optional<boost::filesystem::path>
233 dcp::relative_to_root (boost::filesystem::path root, boost::filesystem::path file)
235 auto i = root.begin ();
236 auto j = file.begin ();
238 while (i != root.end() && j != file.end() && *i == *j) {
243 if (i != root.end()) {
247 boost::filesystem::path rel;
248 while (j != file.end()) {
257 dcp::ids_equal (string a, string b)
259 transform (a.begin(), a.end(), a.begin(), ::tolower);
260 transform (b.begin(), b.end(), b.begin(), ::tolower);
268 dcp::file_to_string (boost::filesystem::path p, uintmax_t max_length)
270 auto len = boost::filesystem::file_size (p);
271 if (len > max_length) {
272 throw MiscError (String::compose("Unexpectedly long file (%1)", p.string()));
277 throw FileError ("could not open file", p, errno);
280 char* c = new char[len];
281 /* This may read less than `len' if we are on Windows and we have CRLF in the file */
282 int const N = f.read(c, 1, len);
292 dcp::private_key_fingerprint (string key)
294 boost::replace_all (key, "-----BEGIN RSA PRIVATE KEY-----\n", "");
295 boost::replace_all (key, "\n-----END RSA PRIVATE KEY-----\n", "");
296 boost::replace_all (key, "-----BEGIN PRIVATE KEY-----\n", "");
297 boost::replace_all (key, "\n-----END PRIVATE KEY-----\n", "");
299 unsigned char buffer[4096];
300 int const N = base64_decode (key, buffer, sizeof (buffer));
304 SHA1_Update (&sha, buffer, N);
306 SHA1_Final (digest, &sha);
308 char digest_base64[64];
309 return Kumu::base64encode (digest, 20, digest_base64, 64);
314 dcp::find_child (xmlpp::Node const * node, string name)
316 auto c = node->get_children ();
318 while (i != c.end() && (*i)->get_name() != name) {
322 DCP_ASSERT (i != c.end ());
328 dcp::remove_urn_uuid (string raw)
330 DCP_ASSERT (raw.substr(0, 9) == "urn:uuid:");
331 return raw.substr (9);
336 dcp::openjpeg_version ()
338 return opj_version ();
346 for (int i = 0; i < n; ++i) {
354 dcp::indent (xmlpp::Element* element, int initial)
356 xmlpp::Node* last = nullptr;
357 for (auto n: element->get_children()) {
358 auto e = dynamic_cast<xmlpp::Element*>(n);
360 element->add_child_text_before (e, "\n" + spaces(initial + 2));
361 indent (e, initial + 2);
366 element->add_child_text (last, "\n" + spaces(initial));
372 dcp::day_less_than_or_equal (LocalTime a, LocalTime b)
374 if (a.year() != b.year()) {
375 return a.year() < b.year();
378 if (a.month() != b.month()) {
379 return a.month() < b.month();
382 return a.day() <= b.day();
387 dcp::day_greater_than_or_equal (LocalTime a, LocalTime b)
389 if (a.year() != b.year()) {
390 return a.year() > b.year();
393 if (a.month() != b.month()) {
394 return a.month() > b.month();
397 return a.day() >= b.day();
402 dcp::unique_string (vector<string> existing, string base)
404 int const max_tries = existing.size() + 1;
405 for (int i = 0; i < max_tries; ++i) {
406 string trial = String::compose("%1%2", base, i);
407 if (find(existing.begin(), existing.end(), trial) == existing.end()) {
416 ASDCPErrorSuspender::ASDCPErrorSuspender ()
417 : _old (Kumu::DefaultLogSink())
419 _sink = new Kumu::EntryListLogSink(_log);
420 Kumu::SetDefaultLogSink (_sink);
424 ASDCPErrorSuspender::~ASDCPErrorSuspender ()
426 Kumu::SetDefaultLogSink (&_old);
431 boost::filesystem::path dcp::directory_containing_executable ()
433 #if BOOST_VERSION >= 106100
434 return boost::filesystem::canonical(boost::dll::program_location().parent_path());
436 char buffer[PATH_MAX];
437 ssize_t N = readlink ("/proc/self/exe", buffer, PATH_MAX);
438 return boost::filesystem::path(string(buffer, N)).parent_path();
443 boost::filesystem::path dcp::resources_directory ()
445 /* We need a way to specify the tags directory for running un-installed binaries */
446 char* prefix = getenv("LIBDCP_RESOURCES");
451 #if defined(LIBDCP_OSX)
452 return directory_containing_executable().parent_path() / "Resources";
453 #elif defined(LIBDCP_WINDOWS)
454 return directory_containing_executable().parent_path();
456 return directory_containing_executable().parent_path() / "share" / "libdcp";