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 "filesystem.h"
46 #include "language_tag.h"
47 #include "openjpeg_image.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>
66 #include <boost/filesystem.hpp>
80 using std::shared_ptr;
82 using boost::shared_array;
83 using boost::optional;
84 using boost::function;
85 using boost::algorithm::trim;
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.
92 ASDCP::Dictionary const* dcp::asdcp_smpte_dict = nullptr;
100 Kumu::GenRandomValue (id);
101 id.EncodeHex (buffer, 64);
102 return string (buffer);
107 dcp::make_digest (ArrayData data)
111 SHA1_Update (&sha, data.data(), data.size());
112 byte_t byte_buffer[SHA_DIGEST_LENGTH];
113 SHA1_Final (byte_buffer, &sha);
115 return Kumu::base64encode (byte_buffer, SHA_DIGEST_LENGTH, digest, 64);
120 dcp::make_digest(boost::filesystem::path filename, function<void (int64_t, int64_t)> progress)
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));
131 int const buffer_size = 65536;
132 Kumu::ByteString read_buffer (buffer_size);
134 Kumu::fsize_t done = 0;
135 Kumu::fsize_t const size = reader.Size ();
138 auto r = reader.Read (read_buffer.Data(), read_buffer.Capacity(), &read);
140 if (r == Kumu::RESULT_ENDOFFILE) {
142 } else if (ASDCP_FAILURE (r)) {
143 boost::throw_exception (FileError("could not read file to compute digest", filename, r));
146 SHA1_Update (&sha, read_buffer.Data(), read);
149 progress(done, size);
154 byte_t byte_buffer[SHA_DIGEST_LENGTH];
155 SHA1_Final (byte_buffer, &sha);
158 return Kumu::base64encode (byte_buffer, SHA_DIGEST_LENGTH, digest, 64);
163 dcp::init (optional<boost::filesystem::path> given_resources_directory)
165 if (xmlSecInit() < 0) {
166 throw MiscError ("could not initialise xmlsec");
169 #ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
170 if (xmlSecCryptoDLLoadLibrary(BAD_CAST "openssl") < 0) {
171 throw MiscError ("unable to load openssl xmlsec-crypto library");
175 if (xmlSecCryptoAppInit(0) < 0) {
176 throw MiscError ("could not initialise crypto");
179 if (xmlSecCryptoInit() < 0) {
180 throw MiscError ("could not initialise xmlsec-crypto");
183 OpenSSL_add_all_algorithms();
185 asdcp_smpte_dict = &ASDCP::DefaultSMPTEDict();
187 auto res = given_resources_directory.get_value_or(resources_directory());
189 load_language_tag_lists (res / "tags");
190 load_rating_list (res / "ratings");
195 dcp::base64_decode (string const & in, unsigned char* out, int out_length)
197 auto b64 = BIO_new (BIO_f_base64());
199 /* This means the input should have no newlines */
200 BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
202 /* Copy our input string, removing newlines */
203 char in_buffer[in.size() + 1];
205 for (size_t i = 0; i < in.size(); ++i) {
206 if (in[i] != '\n' && in[i] != '\r') {
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);
220 optional<boost::filesystem::path>
221 dcp::relative_to_root (boost::filesystem::path root, boost::filesystem::path file)
223 auto i = root.begin ();
224 auto j = file.begin ();
226 while (i != root.end() && j != file.end() && *i == *j) {
231 if (i != root.end()) {
235 boost::filesystem::path rel;
236 while (j != file.end()) {
245 dcp::ids_equal (string a, string b)
247 transform (a.begin(), a.end(), a.begin(), ::tolower);
248 transform (b.begin(), b.end(), b.begin(), ::tolower);
256 dcp::file_to_string (boost::filesystem::path p, uintmax_t max_length)
258 auto len = filesystem::file_size(p);
259 if (len > max_length) {
260 throw MiscError (String::compose("Unexpectedly long file (%1)", p.string()));
265 throw FileError ("could not open file", p, errno);
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);
276 dcp::write_string_to_file(string const& string, boost::filesystem::path const& path)
278 File file(path, "w");
280 throw FileError("could not open file", path, errno);
283 file.write(string.c_str(), string.length(), 1);
288 dcp::private_key_fingerprint (string key)
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", "");
295 unsigned char buffer[4096];
296 int const N = base64_decode (key, buffer, sizeof (buffer));
300 SHA1_Update (&sha, buffer, N);
302 SHA1_Final (digest, &sha);
304 char digest_base64[64];
305 return Kumu::base64encode (digest, 20, digest_base64, 64);
310 dcp::find_child (xmlpp::Node const * node, string name)
312 auto c = node->get_children ();
314 while (i != c.end() && (*i)->get_name() != name) {
318 DCP_ASSERT (i != c.end ());
324 dcp::remove_urn_uuid (string raw)
326 if (raw.substr(0, 9) != "urn:uuid:") {
327 throw BadURNUUIDError(raw);
330 return raw.substr (9);
335 dcp::openjpeg_version ()
337 return opj_version ();
345 for (int i = 0; i < n; ++i) {
353 dcp::indent (xmlpp::Element* element, int initial)
355 xmlpp::Node* last = nullptr;
356 for (auto n: element->get_children()) {
357 auto e = dynamic_cast<xmlpp::Element*>(n);
359 element->add_child_text_before (e, "\n" + spaces(initial + 2));
360 indent (e, initial + 2);
365 element->add_child_text (last, "\n" + spaces(initial));
371 dcp::day_less_than_or_equal (LocalTime a, LocalTime b)
373 if (a.year() != b.year()) {
374 return a.year() < b.year();
377 if (a.month() != b.month()) {
378 return a.month() < b.month();
381 return a.day() <= b.day();
386 dcp::day_greater_than_or_equal (LocalTime a, LocalTime b)
388 if (a.year() != b.year()) {
389 return a.year() > b.year();
392 if (a.month() != b.month()) {
393 return a.month() > b.month();
396 return a.day() >= b.day();
401 dcp::unique_string (vector<string> existing, string base)
403 int const max_tries = existing.size() + 1;
404 for (int i = 0; i < max_tries; ++i) {
405 string trial = String::compose("%1%2", base, i);
406 if (find(existing.begin(), existing.end(), trial) == existing.end()) {
415 ASDCPErrorSuspender::ASDCPErrorSuspender ()
416 : _old (Kumu::DefaultLogSink())
418 _sink = new Kumu::EntryListLogSink(_log);
419 Kumu::SetDefaultLogSink (_sink);
423 ASDCPErrorSuspender::~ASDCPErrorSuspender ()
425 Kumu::SetDefaultLogSink (&_old);
430 boost::filesystem::path dcp::directory_containing_executable ()
432 #if BOOST_VERSION >= 106100
433 return filesystem::canonical(boost::dll::program_location().parent_path());
435 char buffer[PATH_MAX];
436 ssize_t N = readlink ("/proc/self/exe", buffer, PATH_MAX);
437 return boost::filesystem::path(string(buffer, N)).parent_path();
442 boost::filesystem::path dcp::resources_directory ()
444 /* We need a way to specify the tags directory for running un-installed binaries */
445 char* prefix = getenv("LIBDCP_RESOURCES");
450 #if defined(LIBDCP_OSX)
451 return directory_containing_executable().parent_path() / "Resources";
452 #elif defined(LIBDCP_WINDOWS)
453 return directory_containing_executable().parent_path();
455 return directory_containing_executable().parent_path() / "share" / "libdcp";