2 Copyright (C) 2012-2014 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 "exceptions.h"
41 #include "certificate.h"
42 #include "openjpeg_image.h"
43 #include "dcp_assert.h"
44 #include "compose.hpp"
46 #include <asdcp/KM_util.h>
47 #include <asdcp/KM_fileio.h>
48 #include <asdcp/AS_DCP.h>
49 #include <xmlsec/xmldsig.h>
50 #include <xmlsec/dl.h>
51 #include <xmlsec/app.h>
52 #include <xmlsec/crypto.h>
53 #include <libxml++/nodes/element.h>
54 #include <libxml++/document.h>
55 #include <openssl/sha.h>
56 #include <boost/filesystem.hpp>
57 #include <boost/algorithm/string.hpp>
58 #include <boost/foreach.hpp>
72 using boost::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 (Data data)
104 SHA1_Update (&sha, data.data().get(), 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.
180 if (xmlSecInit() < 0) {
181 throw MiscError ("could not initialise xmlsec");
184 #ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
185 if (xmlSecCryptoDLLoadLibrary(BAD_CAST "openssl") < 0) {
186 throw MiscError ("unable to load openssl xmlsec-crypto library");
190 if (xmlSecCryptoAppInit(0) < 0) {
191 throw MiscError ("could not initialise crypto");
194 if (xmlSecCryptoInit() < 0) {
195 throw MiscError ("could not initialise xmlsec-crypto");
198 OpenSSL_add_all_algorithms();
200 asdcp_smpte_dict = &ASDCP::DefaultSMPTEDict();
203 /** Decode a base64 string. The base64 decode routine in KM_util.cpp
204 * gives different values to both this and the command-line base64
205 * for some inputs. Not sure why.
207 * @param in base64-encoded string.
208 * @param out Output buffer.
209 * @param out_length Length of output buffer.
210 * @return Number of characters written to the output buffer.
213 dcp::base64_decode (string const & in, unsigned char* out, int out_length)
215 BIO* b64 = BIO_new (BIO_f_base64 ());
217 /* This means the input should have no newlines */
218 BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
220 /* Copy our input string, removing newlines */
221 char in_buffer[in.size() + 1];
223 for (size_t i = 0; i < in.size(); ++i) {
224 if (in[i] != '\n' && in[i] != '\r') {
229 BIO* bmem = BIO_new_mem_buf (in_buffer, p - in_buffer);
230 bmem = BIO_push (b64, bmem);
231 int const N = BIO_read (bmem, out, out_length);
237 /** @param p Path to open.
238 * @param t mode flags, as for fopen(3).
239 * @return FILE pointer or 0 on error.
241 * Apparently there is no way to create an ofstream using a UTF-8
242 * filename under Windows. We are hence reduced to using fopen
246 dcp::fopen_boost (boost::filesystem::path p, string t)
248 #ifdef LIBDCP_WINDOWS
249 wstring w (t.begin(), t.end());
250 /* c_str() here should give a UTF-16 string */
251 return _wfopen (p.c_str(), w.c_str ());
253 return fopen (p.c_str(), t.c_str ());
257 optional<boost::filesystem::path>
258 dcp::relative_to_root (boost::filesystem::path root, boost::filesystem::path file)
260 boost::filesystem::path::const_iterator i = root.begin ();
261 boost::filesystem::path::const_iterator j = file.begin ();
263 while (i != root.end() && j != file.end() && *i == *j) {
268 if (i != root.end ()) {
269 return optional<boost::filesystem::path> ();
272 boost::filesystem::path rel;
273 while (j != file.end ()) {
281 dcp::ids_equal (string a, string b)
283 transform (a.begin(), a.end(), a.begin(), ::tolower);
284 transform (b.begin(), b.end(), b.begin(), ::tolower);
291 dcp::file_to_string (boost::filesystem::path p, uintmax_t max_length)
293 uintmax_t len = boost::filesystem::file_size (p);
294 if (len > max_length) {
295 throw MiscError (String::compose ("Unexpectedly long file (%1)", p.string()));
298 FILE* f = fopen_boost (p, "r");
300 throw FileError ("could not open file", p, errno);
303 char* c = new char[len];
304 /* This may read less than `len' if we are on Windows and we have CRLF in the file */
305 int const N = fread (c, 1, len, f);
314 /** @param key RSA private key in PEM format (optionally with -----BEGIN... / -----END...)
315 * @return SHA1 fingerprint of key
318 dcp::private_key_fingerprint (string key)
320 boost::replace_all (key, "-----BEGIN RSA PRIVATE KEY-----\n", "");
321 boost::replace_all (key, "\n-----END RSA PRIVATE KEY-----\n", "");
323 unsigned char buffer[4096];
324 int const N = base64_decode (key, buffer, sizeof (buffer));
328 SHA1_Update (&sha, buffer, N);
330 SHA1_Final (digest, &sha);
332 char digest_base64[64];
333 return Kumu::base64encode (digest, 20, digest_base64, 64);
337 dcp::find_child (xmlpp::Node const * node, string name)
339 xmlpp::Node::NodeList c = node->get_children ();
340 xmlpp::Node::NodeList::iterator i = c.begin();
341 while (i != c.end() && (*i)->get_name() != name) {
345 DCP_ASSERT (i != c.end ());
350 dcp::remove_urn_uuid (string raw)
352 DCP_ASSERT (raw.substr(0, 9) == "urn:uuid:");
353 return raw.substr (9);
357 dcp::openjpeg_version ()
359 return opj_version ();
366 for (int i = 0; i < n; ++i) {
373 dcp::indent (xmlpp::Element* element, int initial)
375 xmlpp::Node* last = 0;
376 BOOST_FOREACH (xmlpp::Node * n, element->get_children()) {
377 xmlpp::Element* e = dynamic_cast<xmlpp::Element*>(n);
379 element->add_child_text_before (e, "\n" + spaces(initial + 2));
380 indent (e, initial + 2);
385 element->add_child_text (last, "\n" + spaces(initial));
389 /** @return true if the day represented by \ref a is less than or
390 * equal to the one represented by \ref b, ignoring the time parts.
393 dcp::day_less_than_or_equal (LocalTime a, LocalTime b)
395 if (a.year() != b.year()) {
396 return a.year() < b.year();
399 if (a.month() != b.month()) {
400 return a.month() < b.month();
403 return a.day() <= b.day();
406 /** @return true if the day represented by \ref a is greater than or
407 * equal to the one represented by \ref b, ignoring the time parts.
410 dcp::day_greater_than_or_equal (LocalTime a, LocalTime b)
412 if (a.year() != b.year()) {
413 return a.year() > b.year();
416 if (a.month() != b.month()) {
417 return a.month() > b.month();
420 return a.day() >= b.day();
423 /** Try quite hard to find a string which starts with \ref base and is
424 * not in \ref existing.
427 dcp::unique_string (list<string> existing, string base)
429 int const max_tries = existing.size() + 1;
430 for (int i = 0; i < max_tries; ++i) {
431 string trial = String::compose("%1%2", base, i);
432 if (find(existing.begin(), existing.end(), trial) == existing.end()) {
441 ASDCPErrorSuspender::ASDCPErrorSuspender ()
442 : _old (Kumu::DefaultLogSink())
444 _sink = new Kumu::EntryListLogSink(_log);
445 Kumu::SetDefaultLogSink (_sink);
449 ASDCPErrorSuspender::~ASDCPErrorSuspender ()
451 Kumu::SetDefaultLogSink (&_old);