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;
87 Kumu::GenRandomValue (id);
88 id.EncodeHex (buffer, 64);
89 return string (buffer);
93 dcp::make_digest (Data data)
97 SHA1_Update (&sha, data.data().get(), data.size());
98 byte_t byte_buffer[SHA_DIGEST_LENGTH];
99 SHA1_Final (byte_buffer, &sha);
101 return Kumu::base64encode (byte_buffer, SHA_DIGEST_LENGTH, digest, 64);
104 /** Create a digest for a file.
105 * @param filename File name.
106 * @param progress Optional progress reporting function. The function will be called
107 * with a progress value between 0 and 1.
111 dcp::make_digest (boost::filesystem::path filename, function<void (float)> progress)
113 Kumu::FileReader reader;
114 Kumu::Result_t r = reader.OpenRead (filename.string().c_str ());
115 if (ASDCP_FAILURE (r)) {
116 boost::throw_exception (FileError ("could not open file to compute digest", filename, r));
122 int const buffer_size = 65536;
123 Kumu::ByteString read_buffer (buffer_size);
125 Kumu::fsize_t done = 0;
126 Kumu::fsize_t const size = reader.Size ();
129 Kumu::Result_t r = reader.Read (read_buffer.Data(), read_buffer.Capacity(), &read);
131 if (r == Kumu::RESULT_ENDOFFILE) {
133 } else if (ASDCP_FAILURE (r)) {
134 boost::throw_exception (FileError ("could not read file to compute digest", filename, r));
137 SHA1_Update (&sha, read_buffer.Data(), read);
140 progress (float (done) / size);
145 byte_t byte_buffer[SHA_DIGEST_LENGTH];
146 SHA1_Final (byte_buffer, &sha);
149 return Kumu::base64encode (byte_buffer, SHA_DIGEST_LENGTH, digest, 64);
152 /** @param s A string.
153 * @return true if the string contains only space, newline or tab characters, or is empty.
156 dcp::empty_or_white_space (string s)
158 for (size_t i = 0; i < s.length(); ++i) {
159 if (s[i] != ' ' && s[i] != '\n' && s[i] != '\t') {
167 /** Set up various bits that the library needs. Should be called one
168 * by client applications.
173 if (xmlSecInit() < 0) {
174 throw MiscError ("could not initialise xmlsec");
177 #ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
178 if (xmlSecCryptoDLLoadLibrary(BAD_CAST XMLSEC_CRYPTO) < 0) {
179 throw MiscError (String::compose("unable to load default xmlsec-crypto library '%1'", reinterpret_cast<const char*>((XMLSEC_CRYPTO))));
183 if (xmlSecCryptoAppInit(0) < 0) {
184 throw MiscError ("could not initialise crypto");
187 if (xmlSecCryptoInit() < 0) {
188 throw MiscError ("could not initialise xmlsec-crypto");
191 OpenSSL_add_all_algorithms();
194 /** Decode a base64 string. The base64 decode routine in KM_util.cpp
195 * gives different values to both this and the command-line base64
196 * for some inputs. Not sure why.
198 * @param in base64-encoded string.
199 * @param out Output buffer.
200 * @param out_length Length of output buffer.
201 * @return Number of characters written to the output buffer.
204 dcp::base64_decode (string const & in, unsigned char* out, int out_length)
206 BIO* b64 = BIO_new (BIO_f_base64 ());
208 /* This means the input should have no newlines */
209 BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
211 /* Copy our input string, removing newlines */
212 char in_buffer[in.size() + 1];
214 for (size_t i = 0; i < in.size(); ++i) {
215 if (in[i] != '\n' && in[i] != '\r') {
220 BIO* bmem = BIO_new_mem_buf (in_buffer, p - in_buffer);
221 bmem = BIO_push (b64, bmem);
222 int const N = BIO_read (bmem, out, out_length);
228 /** @param p Path to open.
229 * @param t mode flags, as for fopen(3).
230 * @return FILE pointer or 0 on error.
232 * Apparently there is no way to create an ofstream using a UTF-8
233 * filename under Windows. We are hence reduced to using fopen
237 dcp::fopen_boost (boost::filesystem::path p, string t)
239 #ifdef LIBDCP_WINDOWS
240 wstring w (t.begin(), t.end());
241 /* c_str() here should give a UTF-16 string */
242 return _wfopen (p.c_str(), w.c_str ());
244 return fopen (p.c_str(), t.c_str ());
248 optional<boost::filesystem::path>
249 dcp::relative_to_root (boost::filesystem::path root, boost::filesystem::path file)
251 boost::filesystem::path::const_iterator i = root.begin ();
252 boost::filesystem::path::const_iterator j = file.begin ();
254 while (i != root.end() && j != file.end() && *i == *j) {
259 if (i != root.end ()) {
260 return optional<boost::filesystem::path> ();
263 boost::filesystem::path rel;
264 while (j != file.end ()) {
272 dcp::ids_equal (string a, string b)
274 transform (a.begin(), a.end(), a.begin(), ::tolower);
275 transform (b.begin(), b.end(), b.begin(), ::tolower);
282 dcp::file_to_string (boost::filesystem::path p, uintmax_t max_length)
284 uintmax_t len = boost::filesystem::file_size (p);
285 if (len > max_length) {
286 throw MiscError (String::compose ("Unexpectedly long file (%1)", p.string()));
289 FILE* 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);
305 /** @param key RSA private key in PEM format (optionally with -----BEGIN... / -----END...)
306 * @return SHA1 fingerprint of key
309 dcp::private_key_fingerprint (string key)
311 boost::replace_all (key, "-----BEGIN RSA PRIVATE KEY-----\n", "");
312 boost::replace_all (key, "\n-----END RSA PRIVATE KEY-----\n", "");
314 unsigned char buffer[4096];
315 int const N = base64_decode (key, buffer, sizeof (buffer));
319 SHA1_Update (&sha, buffer, N);
321 SHA1_Final (digest, &sha);
323 char digest_base64[64];
324 return Kumu::base64encode (digest, 20, digest_base64, 64);
328 dcp::find_child (xmlpp::Node const * node, string name)
330 xmlpp::Node::NodeList c = node->get_children ();
331 xmlpp::Node::NodeList::iterator i = c.begin();
332 while (i != c.end() && (*i)->get_name() != name) {
336 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);
348 dcp::openjpeg_version ()
350 return opj_version ();
357 for (int i = 0; i < n; ++i) {
364 dcp::indent (xmlpp::Element* element, int initial)
366 xmlpp::Node* last = 0;
367 BOOST_FOREACH (xmlpp::Node * n, element->get_children()) {
368 xmlpp::Element* e = dynamic_cast<xmlpp::Element*>(n);
370 element->add_child_text_before (e, "\n" + spaces(initial + 2));
371 indent (e, initial + 2);
376 element->add_child_text (last, "\n" + spaces(initial));
380 /** @return true if the day represented by \ref a is less than or
381 * equal to the one represented by \ref b, ignoring the time parts.
384 dcp::day_less_than_or_equal (LocalTime a, LocalTime b)
386 if (a.year() != b.year()) {
387 return a.year() < b.year();
390 if (a.month() != b.month()) {
391 return a.month() < b.month();
394 return a.day() <= b.day();
397 /** @return true if the day represented by \ref a is greater than or
398 * equal to the one represented by \ref b, ignoring the time parts.
401 dcp::day_greater_than_or_equal (LocalTime a, LocalTime b)
403 if (a.year() != b.year()) {
404 return a.year() > b.year();
407 if (a.month() != b.month()) {
408 return a.month() > b.month();
411 return a.day() >= b.day();
414 /** Try quite hard to find a string which starts with \ref base and is
415 * not in \ref existing.
418 dcp::unique_string (list<string> existing, string base)
420 int const max_tries = existing.size() + 1;
421 for (int i = 0; i < max_tries; ++i) {
422 string trial = String::compose("%1%2", base, i);
423 if (find(existing.begin(), existing.end(), trial) == existing.end()) {