2 Copyright (C) 2012-2020 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.
35 * @brief Utility methods.
39 #include "language_tag.h"
40 #include "exceptions.h"
42 #include "certificate.h"
43 #include "openjpeg_image.h"
44 #include "dcp_assert.h"
45 #include "compose.hpp"
47 #include <asdcp/KM_util.h>
48 #include <asdcp/KM_fileio.h>
49 #include <asdcp/AS_DCP.h>
50 #include <xmlsec/xmldsig.h>
51 #include <xmlsec/dl.h>
52 #include <xmlsec/app.h>
53 #include <xmlsec/crypto.h>
54 #include <libxml++/nodes/element.h>
55 #include <libxml++/document.h>
56 #include <openssl/sha.h>
57 #include <boost/filesystem.hpp>
58 #include <boost/algorithm/string.hpp>
71 using std::shared_ptr;
73 using boost::shared_array;
74 using boost::optional;
75 using boost::function;
76 using boost::algorithm::trim;
80 /* Some ASDCP objects store this as a *&, for reasons which are not
81 * at all clear, so we have to keep this around forever.
83 ASDCP::Dictionary const* dcp::asdcp_smpte_dict = 0;
94 Kumu::GenRandomValue (id);
95 id.EncodeHex (buffer, 64);
96 return string (buffer);
100 dcp::make_digest (ArrayData data)
104 SHA1_Update (&sha, data.data(), data.size());
105 byte_t byte_buffer[SHA_DIGEST_LENGTH];
106 SHA1_Final (byte_buffer, &sha);
108 return Kumu::base64encode (byte_buffer, SHA_DIGEST_LENGTH, digest, 64);
111 /** Create a digest for a file.
112 * @param filename File name.
113 * @param progress Optional progress reporting function. The function will be called
114 * with a progress value between 0 and 1.
118 dcp::make_digest (boost::filesystem::path filename, function<void (float)> progress)
120 Kumu::FileReader reader;
121 Kumu::Result_t r = reader.OpenRead (filename.string().c_str ());
122 if (ASDCP_FAILURE (r)) {
123 boost::throw_exception (FileError ("could not open file to compute digest", filename, r));
129 int const buffer_size = 65536;
130 Kumu::ByteString read_buffer (buffer_size);
132 Kumu::fsize_t done = 0;
133 Kumu::fsize_t const size = reader.Size ();
136 Kumu::Result_t r = reader.Read (read_buffer.Data(), read_buffer.Capacity(), &read);
138 if (r == Kumu::RESULT_ENDOFFILE) {
140 } else if (ASDCP_FAILURE (r)) {
141 boost::throw_exception (FileError ("could not read file to compute digest", filename, r));
144 SHA1_Update (&sha, read_buffer.Data(), read);
147 progress (float (done) / size);
152 byte_t byte_buffer[SHA_DIGEST_LENGTH];
153 SHA1_Final (byte_buffer, &sha);
156 return Kumu::base64encode (byte_buffer, SHA_DIGEST_LENGTH, digest, 64);
159 /** @param s A string.
160 * @return true if the string contains only space, newline or tab characters, or is empty.
163 dcp::empty_or_white_space (string s)
165 for (size_t i = 0; i < s.length(); ++i) {
166 if (s[i] != ' ' && s[i] != '\n' && s[i] != '\t') {
174 /** Set up various bits that the library needs. Should be called once
175 * by client applications.
177 * @param tags_directory Path to a copy of the tags directory from the source code;
178 * if none is specified libdcp will look for a tags directory inside the environment
179 * variable LIBDCP_SHARE_PREFIX or the LIBDCP_SHARE_PREFIX #defined during the build.
182 dcp::init (optional<boost::filesystem::path> tags_directory)
184 if (xmlSecInit() < 0) {
185 throw MiscError ("could not initialise xmlsec");
188 #ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
189 if (xmlSecCryptoDLLoadLibrary(BAD_CAST "openssl") < 0) {
190 throw MiscError ("unable to load openssl xmlsec-crypto library");
194 if (xmlSecCryptoAppInit(0) < 0) {
195 throw MiscError ("could not initialise crypto");
198 if (xmlSecCryptoInit() < 0) {
199 throw MiscError ("could not initialise xmlsec-crypto");
202 OpenSSL_add_all_algorithms();
204 asdcp_smpte_dict = &ASDCP::DefaultSMPTEDict();
206 if (!tags_directory) {
207 char* prefix = getenv("LIBDCP_SHARE_PREFIX");
209 tags_directory = boost::filesystem::path(prefix) / "tags";
211 tags_directory = LIBDCP_SHARE_PREFIX "/tags";
215 load_language_tag_lists (*tags_directory);
218 /** Decode a base64 string. The base64 decode routine in KM_util.cpp
219 * gives different values to both this and the command-line base64
220 * for some inputs. Not sure why.
222 * @param in base64-encoded string.
223 * @param out Output buffer.
224 * @param out_length Length of output buffer.
225 * @return Number of characters written to the output buffer.
228 dcp::base64_decode (string const & in, unsigned char* out, int out_length)
230 BIO* b64 = BIO_new (BIO_f_base64 ());
232 /* This means the input should have no newlines */
233 BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
235 /* Copy our input string, removing newlines */
236 char in_buffer[in.size() + 1];
238 for (size_t i = 0; i < in.size(); ++i) {
239 if (in[i] != '\n' && in[i] != '\r') {
244 BIO* bmem = BIO_new_mem_buf (in_buffer, p - in_buffer);
245 bmem = BIO_push (b64, bmem);
246 int const N = BIO_read (bmem, out, out_length);
252 /** @param p Path to open.
253 * @param t mode flags, as for fopen(3).
254 * @return FILE pointer or 0 on error.
256 * Apparently there is no way to create an ofstream using a UTF-8
257 * filename under Windows. We are hence reduced to using fopen
261 dcp::fopen_boost (boost::filesystem::path p, string t)
263 #ifdef LIBDCP_WINDOWS
264 wstring w (t.begin(), t.end());
265 /* c_str() here should give a UTF-16 string */
266 return _wfopen (p.c_str(), w.c_str ());
268 return fopen (p.c_str(), t.c_str ());
272 optional<boost::filesystem::path>
273 dcp::relative_to_root (boost::filesystem::path root, boost::filesystem::path file)
275 boost::filesystem::path::const_iterator i = root.begin ();
276 boost::filesystem::path::const_iterator j = file.begin ();
278 while (i != root.end() && j != file.end() && *i == *j) {
283 if (i != root.end ()) {
284 return optional<boost::filesystem::path> ();
287 boost::filesystem::path rel;
288 while (j != file.end ()) {
296 dcp::ids_equal (string a, string b)
298 transform (a.begin(), a.end(), a.begin(), ::tolower);
299 transform (b.begin(), b.end(), b.begin(), ::tolower);
306 dcp::file_to_string (boost::filesystem::path p, uintmax_t max_length)
308 uintmax_t len = boost::filesystem::file_size (p);
309 if (len > max_length) {
310 throw MiscError (String::compose ("Unexpectedly long file (%1)", p.string()));
313 FILE* f = fopen_boost (p, "r");
315 throw FileError ("could not open file", p, errno);
318 char* c = new char[len];
319 /* This may read less than `len' if we are on Windows and we have CRLF in the file */
320 int const N = fread (c, 1, len, f);
329 /** @param key RSA private key in PEM format (optionally with -----BEGIN... / -----END...)
330 * @return SHA1 fingerprint of key
333 dcp::private_key_fingerprint (string key)
335 boost::replace_all (key, "-----BEGIN RSA PRIVATE KEY-----\n", "");
336 boost::replace_all (key, "\n-----END RSA PRIVATE KEY-----\n", "");
338 unsigned char buffer[4096];
339 int const N = base64_decode (key, buffer, sizeof (buffer));
343 SHA1_Update (&sha, buffer, N);
345 SHA1_Final (digest, &sha);
347 char digest_base64[64];
348 return Kumu::base64encode (digest, 20, digest_base64, 64);
352 dcp::find_child (xmlpp::Node const * node, string name)
354 xmlpp::Node::NodeList c = node->get_children ();
355 xmlpp::Node::NodeList::iterator i = c.begin();
356 while (i != c.end() && (*i)->get_name() != name) {
360 DCP_ASSERT (i != c.end ());
365 dcp::remove_urn_uuid (string raw)
367 DCP_ASSERT (raw.substr(0, 9) == "urn:uuid:");
368 return raw.substr (9);
372 dcp::openjpeg_version ()
374 return opj_version ();
381 for (int i = 0; i < n; ++i) {
388 dcp::indent (xmlpp::Element* element, int initial)
390 xmlpp::Node* last = 0;
391 for (auto n: element->get_children()) {
392 auto e = dynamic_cast<xmlpp::Element*>(n);
394 element->add_child_text_before (e, "\n" + spaces(initial + 2));
395 indent (e, initial + 2);
400 element->add_child_text (last, "\n" + spaces(initial));
404 /** @return true if the day represented by \ref a is less than or
405 * equal to the one represented by \ref b, ignoring the time parts.
408 dcp::day_less_than_or_equal (LocalTime a, LocalTime b)
410 if (a.year() != b.year()) {
411 return a.year() < b.year();
414 if (a.month() != b.month()) {
415 return a.month() < b.month();
418 return a.day() <= b.day();
421 /** @return true if the day represented by \ref a is greater than or
422 * equal to the one represented by \ref b, ignoring the time parts.
425 dcp::day_greater_than_or_equal (LocalTime a, LocalTime b)
427 if (a.year() != b.year()) {
428 return a.year() > b.year();
431 if (a.month() != b.month()) {
432 return a.month() > b.month();
435 return a.day() >= b.day();
438 /** Try quite hard to find a string which starts with \ref base and is
439 * not in \ref existing.
442 dcp::unique_string (vector<string> existing, string base)
444 int const max_tries = existing.size() + 1;
445 for (int i = 0; i < max_tries; ++i) {
446 string trial = String::compose("%1%2", base, i);
447 if (find(existing.begin(), existing.end(), trial) == existing.end()) {
456 ASDCPErrorSuspender::ASDCPErrorSuspender ()
457 : _old (Kumu::DefaultLogSink())
459 _sink = new Kumu::EntryListLogSink(_log);
460 Kumu::SetDefaultLogSink (_sink);
464 ASDCPErrorSuspender::~ASDCPErrorSuspender ()
466 Kumu::SetDefaultLogSink (&_old);