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::init (optional<boost::filesystem::path> given_resources_directory)
164 if (xmlSecInit() < 0) {
165 throw MiscError ("could not initialise xmlsec");
168 #ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
169 if (xmlSecCryptoDLLoadLibrary(BAD_CAST "openssl") < 0) {
170 throw MiscError ("unable to load openssl xmlsec-crypto library");
174 if (xmlSecCryptoAppInit(0) < 0) {
175 throw MiscError ("could not initialise crypto");
178 if (xmlSecCryptoInit() < 0) {
179 throw MiscError ("could not initialise xmlsec-crypto");
182 OpenSSL_add_all_algorithms();
184 asdcp_smpte_dict = &ASDCP::DefaultSMPTEDict();
186 auto res = given_resources_directory.get_value_or(resources_directory());
188 load_language_tag_lists (res / "tags");
189 load_rating_list (res / "ratings");
194 dcp::base64_decode (string const & in, unsigned char* out, int out_length)
196 auto b64 = BIO_new (BIO_f_base64());
198 /* This means the input should have no newlines */
199 BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
201 /* Copy our input string, removing newlines */
202 char in_buffer[in.size() + 1];
204 for (size_t i = 0; i < in.size(); ++i) {
205 if (in[i] != '\n' && in[i] != '\r') {
210 auto bmem = BIO_new_mem_buf (in_buffer, p - in_buffer);
211 bmem = BIO_push (b64, bmem);
212 int const N = BIO_read (bmem, out, out_length);
219 optional<boost::filesystem::path>
220 dcp::relative_to_root (boost::filesystem::path root, boost::filesystem::path file)
222 auto i = root.begin ();
223 auto j = file.begin ();
225 while (i != root.end() && j != file.end() && *i == *j) {
230 if (i != root.end()) {
234 boost::filesystem::path rel;
235 while (j != file.end()) {
244 dcp::ids_equal (string a, string b)
246 transform (a.begin(), a.end(), a.begin(), ::tolower);
247 transform (b.begin(), b.end(), b.begin(), ::tolower);
255 dcp::file_to_string (boost::filesystem::path p, uintmax_t max_length)
257 auto len = boost::filesystem::file_size (p);
258 if (len > max_length) {
259 throw MiscError (String::compose("Unexpectedly long file (%1)", p.string()));
264 throw FileError ("could not open file", p, errno);
267 char* c = new char[len];
268 /* This may read less than `len' if we are on Windows and we have CRLF in the file */
269 int const N = f.read(c, 1, len);
279 dcp::private_key_fingerprint (string key)
281 boost::replace_all (key, "-----BEGIN RSA PRIVATE KEY-----\n", "");
282 boost::replace_all (key, "\n-----END RSA PRIVATE KEY-----\n", "");
283 boost::replace_all (key, "-----BEGIN PRIVATE KEY-----\n", "");
284 boost::replace_all (key, "\n-----END PRIVATE KEY-----\n", "");
286 unsigned char buffer[4096];
287 int const N = base64_decode (key, buffer, sizeof (buffer));
291 SHA1_Update (&sha, buffer, N);
293 SHA1_Final (digest, &sha);
295 char digest_base64[64];
296 return Kumu::base64encode (digest, 20, digest_base64, 64);
301 dcp::find_child (xmlpp::Node const * node, string name)
303 auto c = node->get_children ();
305 while (i != c.end() && (*i)->get_name() != name) {
309 DCP_ASSERT (i != c.end ());
315 dcp::remove_urn_uuid (string raw)
317 if (raw.substr(0, 9) != "urn:uuid:") {
318 throw BadURNUUIDError(raw);
321 return raw.substr (9);
326 dcp::openjpeg_version ()
328 return opj_version ();
336 for (int i = 0; i < n; ++i) {
344 dcp::indent (xmlpp::Element* element, int initial)
346 xmlpp::Node* last = nullptr;
347 for (auto n: element->get_children()) {
348 auto e = dynamic_cast<xmlpp::Element*>(n);
350 element->add_child_text_before (e, "\n" + spaces(initial + 2));
351 indent (e, initial + 2);
356 element->add_child_text (last, "\n" + spaces(initial));
362 dcp::day_less_than_or_equal (LocalTime a, LocalTime b)
364 if (a.year() != b.year()) {
365 return a.year() < b.year();
368 if (a.month() != b.month()) {
369 return a.month() < b.month();
372 return a.day() <= b.day();
377 dcp::day_greater_than_or_equal (LocalTime a, LocalTime b)
379 if (a.year() != b.year()) {
380 return a.year() > b.year();
383 if (a.month() != b.month()) {
384 return a.month() > b.month();
387 return a.day() >= b.day();
392 dcp::unique_string (vector<string> existing, string base)
394 int const max_tries = existing.size() + 1;
395 for (int i = 0; i < max_tries; ++i) {
396 string trial = String::compose("%1%2", base, i);
397 if (find(existing.begin(), existing.end(), trial) == existing.end()) {
406 ASDCPErrorSuspender::ASDCPErrorSuspender ()
407 : _old (Kumu::DefaultLogSink())
409 _sink = new Kumu::EntryListLogSink(_log);
410 Kumu::SetDefaultLogSink (_sink);
414 ASDCPErrorSuspender::~ASDCPErrorSuspender ()
416 Kumu::SetDefaultLogSink (&_old);
421 boost::filesystem::path dcp::directory_containing_executable ()
423 #if BOOST_VERSION >= 106100
424 return boost::filesystem::canonical(boost::dll::program_location().parent_path());
426 char buffer[PATH_MAX];
427 ssize_t N = readlink ("/proc/self/exe", buffer, PATH_MAX);
428 return boost::filesystem::path(string(buffer, N)).parent_path();
433 boost::filesystem::path dcp::resources_directory ()
435 /* We need a way to specify the tags directory for running un-installed binaries */
436 char* prefix = getenv("LIBDCP_RESOURCES");
441 #if defined(LIBDCP_OSX)
442 return directory_containing_executable().parent_path() / "Resources";
443 #elif defined(LIBDCP_WINDOWS)
444 return directory_containing_executable().parent_path();
446 return directory_containing_executable().parent_path() / "share" / "libdcp";