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> given_resources_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 load_language_tag_lists (given_resources_directory.get_value_or(resources_directory()) / "tags");
202 dcp::base64_decode (string const & in, unsigned char* out, int out_length)
204 auto b64 = BIO_new (BIO_f_base64());
206 /* This means the input should have no newlines */
207 BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
209 /* Copy our input string, removing newlines */
210 char in_buffer[in.size() + 1];
212 for (size_t i = 0; i < in.size(); ++i) {
213 if (in[i] != '\n' && in[i] != '\r') {
218 auto bmem = BIO_new_mem_buf (in_buffer, p - in_buffer);
219 bmem = BIO_push (b64, bmem);
220 int const N = BIO_read (bmem, out, out_length);
228 dcp::fopen_boost (boost::filesystem::path p, string t)
230 #ifdef LIBDCP_WINDOWS
231 wstring w (t.begin(), t.end());
232 /* c_str() here should give a UTF-16 string */
233 return _wfopen (p.c_str(), w.c_str ());
235 return fopen (p.c_str(), t.c_str ());
240 optional<boost::filesystem::path>
241 dcp::relative_to_root (boost::filesystem::path root, boost::filesystem::path file)
243 auto i = root.begin ();
244 auto j = file.begin ();
246 while (i != root.end() && j != file.end() && *i == *j) {
251 if (i != root.end()) {
255 boost::filesystem::path rel;
256 while (j != file.end()) {
265 dcp::ids_equal (string a, string b)
267 transform (a.begin(), a.end(), a.begin(), ::tolower);
268 transform (b.begin(), b.end(), b.begin(), ::tolower);
276 dcp::file_to_string (boost::filesystem::path p, uintmax_t max_length)
278 auto len = boost::filesystem::file_size (p);
279 if (len > max_length) {
280 throw MiscError (String::compose("Unexpectedly long file (%1)", p.string()));
283 auto f = fopen_boost (p, "r");
285 throw FileError ("could not open file", p, errno);
288 char* c = new char[len];
289 /* This may read less than `len' if we are on Windows and we have CRLF in the file */
290 int const N = fread (c, 1, len, f);
301 dcp::private_key_fingerprint (string key)
303 boost::replace_all (key, "-----BEGIN RSA PRIVATE KEY-----\n", "");
304 boost::replace_all (key, "\n-----END RSA PRIVATE KEY-----\n", "");
306 unsigned char buffer[4096];
307 int const N = base64_decode (key, buffer, sizeof (buffer));
311 SHA1_Update (&sha, buffer, N);
313 SHA1_Final (digest, &sha);
315 char digest_base64[64];
316 return Kumu::base64encode (digest, 20, digest_base64, 64);
321 dcp::find_child (xmlpp::Node const * node, string name)
323 auto c = node->get_children ();
325 while (i != c.end() && (*i)->get_name() != name) {
329 DCP_ASSERT (i != c.end ());
335 dcp::remove_urn_uuid (string raw)
337 DCP_ASSERT (raw.substr(0, 9) == "urn:uuid:");
338 return raw.substr (9);
343 dcp::openjpeg_version ()
345 return opj_version ();
353 for (int i = 0; i < n; ++i) {
361 dcp::indent (xmlpp::Element* element, int initial)
363 xmlpp::Node* last = nullptr;
364 for (auto n: element->get_children()) {
365 auto e = dynamic_cast<xmlpp::Element*>(n);
367 element->add_child_text_before (e, "\n" + spaces(initial + 2));
368 indent (e, initial + 2);
373 element->add_child_text (last, "\n" + spaces(initial));
379 dcp::day_less_than_or_equal (LocalTime a, LocalTime b)
381 if (a.year() != b.year()) {
382 return a.year() < b.year();
385 if (a.month() != b.month()) {
386 return a.month() < b.month();
389 return a.day() <= b.day();
394 dcp::day_greater_than_or_equal (LocalTime a, LocalTime b)
396 if (a.year() != b.year()) {
397 return a.year() > b.year();
400 if (a.month() != b.month()) {
401 return a.month() > b.month();
404 return a.day() >= b.day();
409 dcp::unique_string (vector<string> existing, string base)
411 int const max_tries = existing.size() + 1;
412 for (int i = 0; i < max_tries; ++i) {
413 string trial = String::compose("%1%2", base, i);
414 if (find(existing.begin(), existing.end(), trial) == existing.end()) {
423 ASDCPErrorSuspender::ASDCPErrorSuspender ()
424 : _old (Kumu::DefaultLogSink())
426 _sink = new Kumu::EntryListLogSink(_log);
427 Kumu::SetDefaultLogSink (_sink);
431 ASDCPErrorSuspender::~ASDCPErrorSuspender ()
433 Kumu::SetDefaultLogSink (&_old);
438 boost::filesystem::path dcp::directory_containing_executable ()
440 #if BOOST_VERSION >= 106100
441 return boost::filesystem::canonical(boost::dll::program_location().parent_path());
443 char buffer[PATH_MAX];
444 ssize_t N = readlink ("/proc/self/exe", buffer, PATH_MAX);
445 return boost::filesystem::path(string(buffer, N)).parent_path();
450 boost::filesystem::path dcp::resources_directory ()
452 /* We need a way to specify the tags directory for running un-installed binaries */
453 char* prefix = getenv("LIBDCP_RESOURCES");
458 #if defined(LIBDCP_OSX)
459 return directory_containing_executable().parent_path() / "Resources";
460 #elif defined(LIBDCP_WINDOWS)
461 return directory_containing_executable().parent_path();
463 return directory_containing_executable().parent_path() / "share" / "libdcp";