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 "KM_fileio.h"
48 #include <xmlsec/xmldsig.h>
49 #include <xmlsec/dl.h>
50 #include <xmlsec/app.h>
51 #include <xmlsec/crypto.h>
52 #include <libxml++/nodes/element.h>
53 #include <libxml++/document.h>
54 #include <openssl/sha.h>
55 #include <boost/filesystem.hpp>
56 #include <boost/algorithm/string.hpp>
65 using std::stringstream;
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 /** Create a digest for a file.
94 * @param filename File name.
95 * @param progress Optional progress reporting function. The function will be called
96 * with a progress value between 0 and 1.
100 dcp::make_digest (boost::filesystem::path filename, function<void (float)> progress)
102 Kumu::FileReader reader;
103 Kumu::Result_t r = reader.OpenRead (filename.string().c_str ());
104 if (ASDCP_FAILURE (r)) {
105 boost::throw_exception (FileError ("could not open file to compute digest", filename, r));
111 int const buffer_size = 65536;
112 Kumu::ByteString read_buffer (buffer_size);
114 Kumu::fsize_t done = 0;
115 Kumu::fsize_t const size = reader.Size ();
118 Kumu::Result_t r = reader.Read (read_buffer.Data(), read_buffer.Capacity(), &read);
120 if (r == Kumu::RESULT_ENDOFFILE) {
122 } else if (ASDCP_FAILURE (r)) {
123 boost::throw_exception (FileError ("could not read file to compute digest", filename, r));
126 SHA1_Update (&sha, read_buffer.Data(), read);
129 progress (float (done) / size);
134 byte_t byte_buffer[SHA_DIGEST_LENGTH];
135 SHA1_Final (byte_buffer, &sha);
138 return Kumu::base64encode (byte_buffer, SHA_DIGEST_LENGTH, digest, 64);
141 /** Convert a content kind to a string which can be used in a
142 * <ContentKind> node.
143 * @param kind ContentKind.
147 dcp::content_kind_to_string (ContentKind kind)
159 return "transitional";
166 case PUBLIC_SERVICE_ANNOUNCEMENT:
169 return "advertisement";
175 /** Convert a string from a <ContentKind> node to a libdcp ContentKind.
176 * Reasonably tolerant about varying case.
177 * @param kind Content kind string.
178 * @return libdcp ContentKind.
181 dcp::content_kind_from_string (string kind)
183 transform (kind.begin(), kind.end(), kind.begin(), ::tolower);
185 if (kind == "feature") {
187 } else if (kind == "short") {
189 } else if (kind == "trailer") {
191 } else if (kind == "test") {
193 } else if (kind == "transitional") {
195 } else if (kind == "rating") {
197 } else if (kind == "teaser") {
199 } else if (kind == "policy") {
201 } else if (kind == "psa") {
202 return PUBLIC_SERVICE_ANNOUNCEMENT;
203 } else if (kind == "advertisement") {
204 return ADVERTISEMENT;
210 /** @param s A string.
211 * @return true if the string contains only space, newline or tab characters, or is empty.
214 dcp::empty_or_white_space (string s)
216 for (size_t i = 0; i < s.length(); ++i) {
217 if (s[i] != ' ' && s[i] != '\n' && s[i] != '\t') {
225 /** Set up various bits that the library needs. Should be called one
226 * by client applications.
231 if (xmlSecInit() < 0) {
232 throw MiscError ("could not initialise xmlsec");
235 #ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
236 if (xmlSecCryptoDLLoadLibrary(BAD_CAST XMLSEC_CRYPTO) < 0) {
237 throw MiscError ("unable to load default xmlsec-crypto library");
241 if (xmlSecCryptoAppInit(0) < 0) {
242 throw MiscError ("could not initialise crypto");
245 if (xmlSecCryptoInit() < 0) {
246 throw MiscError ("could not initialise xmlsec-crypto");
250 bool dcp::operator== (dcp::Size const & a, dcp::Size const & b)
252 return (a.width == b.width && a.height == b.height);
255 bool dcp::operator!= (dcp::Size const & a, dcp::Size const & b)
260 ostream& dcp::operator<< (ostream& s, dcp::Size const & a)
262 s << a.width << "x" << a.height;
266 /** Decode a base64 string. The base64 decode routine in KM_util.cpp
267 * gives different values to both this and the command-line base64
268 * for some inputs. Not sure why.
270 * @param in base64-encoded string.
271 * @param out Output buffer.
272 * @param out_length Length of output buffer.
273 * @return Number of characters written to the output buffer.
276 dcp::base64_decode (string const & in, unsigned char* out, int out_length)
278 BIO* b64 = BIO_new (BIO_f_base64 ());
280 /* This means the input should have no newlines */
281 BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
283 /* Copy our input string, removing newlines */
284 char in_buffer[in.size() + 1];
286 for (size_t i = 0; i < in.size(); ++i) {
287 if (in[i] != '\n' && in[i] != '\r') {
292 BIO* bmem = BIO_new_mem_buf (in_buffer, p - in_buffer);
293 bmem = BIO_push (b64, bmem);
294 int const N = BIO_read (bmem, out, out_length);
300 /** @param p Path to open.
301 * @param t mode flags, as for fopen(3).
302 * @return FILE pointer or 0 on error.
304 * Apparently there is no way to create an ofstream using a UTF-8
305 * filename under Windows. We are hence reduced to using fopen
309 dcp::fopen_boost (boost::filesystem::path p, string t)
311 #ifdef LIBDCP_WINDOWS
312 wstring w (t.begin(), t.end());
313 /* c_str() here should give a UTF-16 string */
314 return _wfopen (p.c_str(), w.c_str ());
316 return fopen (p.c_str(), t.c_str ());
320 optional<boost::filesystem::path>
321 dcp::relative_to_root (boost::filesystem::path root, boost::filesystem::path file)
323 boost::filesystem::path::const_iterator i = root.begin ();
324 boost::filesystem::path::const_iterator j = file.begin ();
326 while (i != root.end() && j != file.end() && *i == *j) {
331 if (i != root.end ()) {
332 return optional<boost::filesystem::path> ();
335 boost::filesystem::path rel;
336 while (j != file.end ()) {
344 dcp::ids_equal (string a, string b)
346 transform (a.begin(), a.end(), a.begin(), ::tolower);
347 transform (b.begin(), b.end(), b.begin(), ::tolower);
354 dcp::file_to_string (boost::filesystem::path p, uintmax_t max_length)
356 uintmax_t len = boost::filesystem::file_size (p);
357 if (len > max_length) {
358 throw MiscError ("Unexpectedly long file");
361 FILE* f = fopen_boost (p, "r");
363 throw FileError ("could not open file", p, errno);
366 char* c = new char[len];
367 /* This may read less than `len' if we are on Windows and we have CRLF in the file */
368 int const N = fread (c, 1, len, f);
377 /** @param key RSA private key in PEM format (optionally with -----BEGIN... / -----END...)
378 * @return SHA1 fingerprint of key
381 dcp::private_key_fingerprint (string key)
383 boost::replace_all (key, "-----BEGIN RSA PRIVATE KEY-----\n", "");
384 boost::replace_all (key, "\n-----END RSA PRIVATE KEY-----\n", "");
386 unsigned char buffer[4096];
387 int const N = base64_decode (key, buffer, sizeof (buffer));
391 SHA1_Update (&sha, buffer, N);
393 SHA1_Final (digest, &sha);
395 char digest_base64[64];
396 return Kumu::base64encode (digest, 20, digest_base64, 64);
400 dcp::find_child (xmlpp::Node const * node, string name)
402 xmlpp::Node::NodeList c = node->get_children ();
403 xmlpp::Node::NodeList::iterator i = c.begin();
404 while (i != c.end() && (*i)->get_name() != name) {
408 DCP_ASSERT (i != c.end ());
413 dcp::remove_urn_uuid (string raw)
415 DCP_ASSERT (raw.substr(0, 9) == "urn:uuid:");
416 return raw.substr (9);