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>
59 #include <boost/foreach.hpp>
72 using std::shared_ptr;
74 using boost::shared_array;
75 using boost::optional;
76 using boost::function;
77 using boost::algorithm::trim;
81 /* Some ASDCP objects store this as a *&, for reasons which are not
82 * at all clear, so we have to keep this around forever.
84 ASDCP::Dictionary const* dcp::asdcp_smpte_dict = 0;
95 Kumu::GenRandomValue (id);
96 id.EncodeHex (buffer, 64);
97 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);
112 /** Create a digest for a file.
113 * @param filename File name.
114 * @param progress Optional progress reporting function. The function will be called
115 * with a progress value between 0 and 1.
119 dcp::make_digest (boost::filesystem::path filename, function<void (float)> progress)
121 Kumu::FileReader reader;
122 Kumu::Result_t 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 Kumu::Result_t 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);
160 /** @param s A string.
161 * @return true if the string contains only space, newline or tab characters, or is empty.
164 dcp::empty_or_white_space (string s)
166 for (size_t i = 0; i < s.length(); ++i) {
167 if (s[i] != ' ' && s[i] != '\n' && s[i] != '\t') {
175 /** Set up various bits that the library needs. Should be called once
176 * by client applications.
178 * @param tags_directory Path to a copy of the tags directory from the source code;
179 * if none is specified libdcp will look for a tags directory inside the environment
180 * variable LIBDCP_SHARE_PREFIX or the LIBDCP_SHARE_PREFIX #defined during the build.
183 dcp::init (optional<boost::filesystem::path> tags_directory)
185 if (xmlSecInit() < 0) {
186 throw MiscError ("could not initialise xmlsec");
189 #ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
190 if (xmlSecCryptoDLLoadLibrary(BAD_CAST "openssl") < 0) {
191 throw MiscError ("unable to load openssl xmlsec-crypto library");
195 if (xmlSecCryptoAppInit(0) < 0) {
196 throw MiscError ("could not initialise crypto");
199 if (xmlSecCryptoInit() < 0) {
200 throw MiscError ("could not initialise xmlsec-crypto");
203 OpenSSL_add_all_algorithms();
205 asdcp_smpte_dict = &ASDCP::DefaultSMPTEDict();
207 if (!tags_directory) {
208 char* prefix = getenv("LIBDCP_SHARE_PREFIX");
210 tags_directory = boost::filesystem::path(prefix) / "tags";
212 tags_directory = LIBDCP_SHARE_PREFIX "/tags";
216 load_language_tag_lists (*tags_directory);
219 /** Decode a base64 string. The base64 decode routine in KM_util.cpp
220 * gives different values to both this and the command-line base64
221 * for some inputs. Not sure why.
223 * @param in base64-encoded string.
224 * @param out Output buffer.
225 * @param out_length Length of output buffer.
226 * @return Number of characters written to the output buffer.
229 dcp::base64_decode (string const & in, unsigned char* out, int out_length)
231 BIO* b64 = BIO_new (BIO_f_base64 ());
233 /* This means the input should have no newlines */
234 BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
236 /* Copy our input string, removing newlines */
237 char in_buffer[in.size() + 1];
239 for (size_t i = 0; i < in.size(); ++i) {
240 if (in[i] != '\n' && in[i] != '\r') {
245 BIO* bmem = BIO_new_mem_buf (in_buffer, p - in_buffer);
246 bmem = BIO_push (b64, bmem);
247 int const N = BIO_read (bmem, out, out_length);
253 /** @param p Path to open.
254 * @param t mode flags, as for fopen(3).
255 * @return FILE pointer or 0 on error.
257 * Apparently there is no way to create an ofstream using a UTF-8
258 * filename under Windows. We are hence reduced to using fopen
262 dcp::fopen_boost (boost::filesystem::path p, string t)
264 #ifdef LIBDCP_WINDOWS
265 wstring w (t.begin(), t.end());
266 /* c_str() here should give a UTF-16 string */
267 return _wfopen (p.c_str(), w.c_str ());
269 return fopen (p.c_str(), t.c_str ());
273 optional<boost::filesystem::path>
274 dcp::relative_to_root (boost::filesystem::path root, boost::filesystem::path file)
276 boost::filesystem::path::const_iterator i = root.begin ();
277 boost::filesystem::path::const_iterator j = file.begin ();
279 while (i != root.end() && j != file.end() && *i == *j) {
284 if (i != root.end ()) {
285 return optional<boost::filesystem::path> ();
288 boost::filesystem::path rel;
289 while (j != file.end ()) {
297 dcp::ids_equal (string a, string b)
299 transform (a.begin(), a.end(), a.begin(), ::tolower);
300 transform (b.begin(), b.end(), b.begin(), ::tolower);
307 dcp::file_to_string (boost::filesystem::path p, uintmax_t max_length)
309 uintmax_t len = boost::filesystem::file_size (p);
310 if (len > max_length) {
311 throw MiscError (String::compose ("Unexpectedly long file (%1)", p.string()));
314 FILE* f = fopen_boost (p, "r");
316 throw FileError ("could not open file", p, errno);
319 char* c = new char[len];
320 /* This may read less than `len' if we are on Windows and we have CRLF in the file */
321 int const N = fread (c, 1, len, f);
330 /** @param key RSA private key in PEM format (optionally with -----BEGIN... / -----END...)
331 * @return SHA1 fingerprint of key
334 dcp::private_key_fingerprint (string key)
336 boost::replace_all (key, "-----BEGIN RSA PRIVATE KEY-----\n", "");
337 boost::replace_all (key, "\n-----END RSA PRIVATE KEY-----\n", "");
339 unsigned char buffer[4096];
340 int const N = base64_decode (key, buffer, sizeof (buffer));
344 SHA1_Update (&sha, buffer, N);
346 SHA1_Final (digest, &sha);
348 char digest_base64[64];
349 return Kumu::base64encode (digest, 20, digest_base64, 64);
353 dcp::find_child (xmlpp::Node const * node, string name)
355 xmlpp::Node::NodeList c = node->get_children ();
356 xmlpp::Node::NodeList::iterator i = c.begin();
357 while (i != c.end() && (*i)->get_name() != name) {
361 DCP_ASSERT (i != c.end ());
366 dcp::remove_urn_uuid (string raw)
368 DCP_ASSERT (raw.substr(0, 9) == "urn:uuid:");
369 return raw.substr (9);
373 dcp::openjpeg_version ()
375 return opj_version ();
382 for (int i = 0; i < n; ++i) {
389 dcp::indent (xmlpp::Element* element, int initial)
391 xmlpp::Node* last = 0;
392 BOOST_FOREACH (xmlpp::Node * n, element->get_children()) {
393 xmlpp::Element* e = dynamic_cast<xmlpp::Element*>(n);
395 element->add_child_text_before (e, "\n" + spaces(initial + 2));
396 indent (e, initial + 2);
401 element->add_child_text (last, "\n" + spaces(initial));
405 /** @return true if the day represented by \ref a is less than or
406 * equal to the one represented by \ref b, ignoring the time parts.
409 dcp::day_less_than_or_equal (LocalTime a, LocalTime b)
411 if (a.year() != b.year()) {
412 return a.year() < b.year();
415 if (a.month() != b.month()) {
416 return a.month() < b.month();
419 return a.day() <= b.day();
422 /** @return true if the day represented by \ref a is greater than or
423 * equal to the one represented by \ref b, ignoring the time parts.
426 dcp::day_greater_than_or_equal (LocalTime a, LocalTime b)
428 if (a.year() != b.year()) {
429 return a.year() > b.year();
432 if (a.month() != b.month()) {
433 return a.month() > b.month();
436 return a.day() >= b.day();
439 /** Try quite hard to find a string which starts with \ref base and is
440 * not in \ref existing.
443 dcp::unique_string (vector<string> existing, string base)
445 int const max_tries = existing.size() + 1;
446 for (int i = 0; i < max_tries; ++i) {
447 string trial = String::compose("%1%2", base, i);
448 if (find(existing.begin(), existing.end(), trial) == existing.end()) {
457 ASDCPErrorSuspender::ASDCPErrorSuspender ()
458 : _old (Kumu::DefaultLogSink())
460 _sink = new Kumu::EntryListLogSink(_log);
461 Kumu::SetDefaultLogSink (_sink);
465 ASDCPErrorSuspender::~ASDCPErrorSuspender ()
467 Kumu::SetDefaultLogSink (&_old);