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/filesystem.hpp>
60 #include <boost/algorithm/string.hpp>
74 using std::shared_ptr;
76 using boost::shared_array;
77 using boost::optional;
78 using boost::function;
79 using boost::algorithm::trim;
83 /* Some ASDCP objects store this as a *&, for reasons which are not
84 * at all clear, so we have to keep this around forever.
86 ASDCP::Dictionary const* dcp::asdcp_smpte_dict = nullptr;
94 Kumu::GenRandomValue (id);
95 id.EncodeHex (buffer, 64);
96 return string (buffer);
101 dcp::make_digest (ArrayData data)
105 SHA1_Update (&sha, data.data(), data.size());
106 byte_t byte_buffer[SHA_DIGEST_LENGTH];
107 SHA1_Final (byte_buffer, &sha);
109 return Kumu::base64encode (byte_buffer, SHA_DIGEST_LENGTH, digest, 64);
114 dcp::make_digest (boost::filesystem::path filename, function<void (float)> progress)
116 Kumu::FileReader reader;
117 auto r = reader.OpenRead (filename.string().c_str ());
118 if (ASDCP_FAILURE(r)) {
119 boost::throw_exception (FileError("could not open file to compute digest", filename, r));
125 int const buffer_size = 65536;
126 Kumu::ByteString read_buffer (buffer_size);
128 Kumu::fsize_t done = 0;
129 Kumu::fsize_t const size = reader.Size ();
132 auto r = reader.Read (read_buffer.Data(), read_buffer.Capacity(), &read);
134 if (r == Kumu::RESULT_ENDOFFILE) {
136 } else if (ASDCP_FAILURE (r)) {
137 boost::throw_exception (FileError("could not read file to compute digest", filename, r));
140 SHA1_Update (&sha, read_buffer.Data(), read);
143 progress (float (done) / size);
148 byte_t byte_buffer[SHA_DIGEST_LENGTH];
149 SHA1_Final (byte_buffer, &sha);
152 return Kumu::base64encode (byte_buffer, SHA_DIGEST_LENGTH, digest, 64);
157 dcp::empty_or_white_space (string s)
159 for (size_t i = 0; i < s.length(); ++i) {
160 if (s[i] != ' ' && s[i] != '\n' && s[i] != '\t') {
170 dcp::init (optional<boost::filesystem::path> tags_directory)
172 if (xmlSecInit() < 0) {
173 throw MiscError ("could not initialise xmlsec");
176 #ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
177 if (xmlSecCryptoDLLoadLibrary(BAD_CAST "openssl") < 0) {
178 throw MiscError ("unable to load openssl xmlsec-crypto library");
182 if (xmlSecCryptoAppInit(0) < 0) {
183 throw MiscError ("could not initialise crypto");
186 if (xmlSecCryptoInit() < 0) {
187 throw MiscError ("could not initialise xmlsec-crypto");
190 OpenSSL_add_all_algorithms();
192 asdcp_smpte_dict = &ASDCP::DefaultSMPTEDict();
194 if (!tags_directory) {
195 char* prefix = getenv("LIBDCP_SHARE_PREFIX");
197 tags_directory = boost::filesystem::path(prefix) / "tags";
199 tags_directory = LIBDCP_SHARE_PREFIX "/tags";
203 load_language_tag_lists (*tags_directory);
208 dcp::base64_decode (string const & in, unsigned char* out, int out_length)
210 auto b64 = BIO_new (BIO_f_base64());
212 /* This means the input should have no newlines */
213 BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
215 /* Copy our input string, removing newlines */
216 char in_buffer[in.size() + 1];
218 for (size_t i = 0; i < in.size(); ++i) {
219 if (in[i] != '\n' && in[i] != '\r') {
224 auto bmem = BIO_new_mem_buf (in_buffer, p - in_buffer);
225 bmem = BIO_push (b64, bmem);
226 int const N = BIO_read (bmem, out, out_length);
234 dcp::fopen_boost (boost::filesystem::path p, string t)
236 #ifdef LIBDCP_WINDOWS
237 wstring w (t.begin(), t.end());
238 /* c_str() here should give a UTF-16 string */
239 return _wfopen (p.c_str(), w.c_str ());
241 return fopen (p.c_str(), t.c_str ());
246 optional<boost::filesystem::path>
247 dcp::relative_to_root (boost::filesystem::path root, boost::filesystem::path file)
249 auto i = root.begin ();
250 auto j = file.begin ();
252 while (i != root.end() && j != file.end() && *i == *j) {
257 if (i != root.end()) {
261 boost::filesystem::path rel;
262 while (j != file.end()) {
271 dcp::ids_equal (string a, string b)
273 transform (a.begin(), a.end(), a.begin(), ::tolower);
274 transform (b.begin(), b.end(), b.begin(), ::tolower);
282 dcp::file_to_string (boost::filesystem::path p, uintmax_t max_length)
284 auto len = boost::filesystem::file_size (p);
285 if (len > max_length) {
286 throw MiscError (String::compose("Unexpectedly long file (%1)", p.string()));
289 auto f = fopen_boost (p, "r");
291 throw FileError ("could not open file", p, errno);
294 char* c = new char[len];
295 /* This may read less than `len' if we are on Windows and we have CRLF in the file */
296 int const N = fread (c, 1, len, f);
307 dcp::private_key_fingerprint (string key)
309 boost::replace_all (key, "-----BEGIN RSA PRIVATE KEY-----\n", "");
310 boost::replace_all (key, "\n-----END RSA PRIVATE KEY-----\n", "");
312 unsigned char buffer[4096];
313 int const N = base64_decode (key, buffer, sizeof (buffer));
317 SHA1_Update (&sha, buffer, N);
319 SHA1_Final (digest, &sha);
321 char digest_base64[64];
322 return Kumu::base64encode (digest, 20, digest_base64, 64);
327 dcp::find_child (xmlpp::Node const * node, string name)
329 auto c = node->get_children ();
331 while (i != c.end() && (*i)->get_name() != name) {
335 DCP_ASSERT (i != c.end ());
341 dcp::remove_urn_uuid (string raw)
343 DCP_ASSERT (raw.substr(0, 9) == "urn:uuid:");
344 return raw.substr (9);
349 dcp::openjpeg_version ()
351 return opj_version ();
359 for (int i = 0; i < n; ++i) {
367 dcp::indent (xmlpp::Element* element, int initial)
369 xmlpp::Node* last = nullptr;
370 for (auto n: element->get_children()) {
371 auto e = dynamic_cast<xmlpp::Element*>(n);
373 element->add_child_text_before (e, "\n" + spaces(initial + 2));
374 indent (e, initial + 2);
379 element->add_child_text (last, "\n" + spaces(initial));
385 dcp::day_less_than_or_equal (LocalTime a, LocalTime b)
387 if (a.year() != b.year()) {
388 return a.year() < b.year();
391 if (a.month() != b.month()) {
392 return a.month() < b.month();
395 return a.day() <= b.day();
400 dcp::day_greater_than_or_equal (LocalTime a, LocalTime b)
402 if (a.year() != b.year()) {
403 return a.year() > b.year();
406 if (a.month() != b.month()) {
407 return a.month() > b.month();
410 return a.day() >= b.day();
415 dcp::unique_string (vector<string> existing, string base)
417 int const max_tries = existing.size() + 1;
418 for (int i = 0; i < max_tries; ++i) {
419 string trial = String::compose("%1%2", base, i);
420 if (find(existing.begin(), existing.end(), trial) == existing.end()) {
429 ASDCPErrorSuspender::ASDCPErrorSuspender ()
430 : _old (Kumu::DefaultLogSink())
432 _sink = new Kumu::EntryListLogSink(_log);
433 Kumu::SetDefaultLogSink (_sink);
437 ASDCPErrorSuspender::~ASDCPErrorSuspender ()
439 Kumu::SetDefaultLogSink (&_old);