Merge branch 'master' of ssh://main.carlh.net/home/carl/git/libdcp
[libdcp.git] / src / util.cc
1 /*
2     Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
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.
10
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.
15
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/>.
18
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
23     including the two.
24
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.
32 */
33
34 /** @file  src/util.cc
35  *  @brief Utility methods.
36  */
37
38 #include "util.h"
39 #include "exceptions.h"
40 #include "types.h"
41 #include "certificate.h"
42 #include "openjpeg_image.h"
43 #include "dcp_assert.h"
44 #include "compose.hpp"
45 #include <openjpeg.h>
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 <stdexcept>
59 #include <iostream>
60 #include <iomanip>
61
62 using std::string;
63 using std::wstring;
64 using std::cout;
65 using std::min;
66 using std::max;
67 using std::list;
68 using std::setw;
69 using std::setfill;
70 using std::ostream;
71 using boost::shared_ptr;
72 using boost::shared_array;
73 using boost::optional;
74 using boost::function;
75 using boost::algorithm::trim;
76 using namespace dcp;
77
78 /** Create a UUID.
79  *  @return UUID.
80  */
81 string
82 dcp::make_uuid ()
83 {
84         char buffer[64];
85         Kumu::UUID id;
86         Kumu::GenRandomValue (id);
87         id.EncodeHex (buffer, 64);
88         return string (buffer);
89 }
90
91 string
92 dcp::make_digest (Data data)
93 {
94         SHA_CTX sha;
95         SHA1_Init (&sha);
96         SHA1_Update (&sha, data.data().get(), data.size());
97         byte_t byte_buffer[SHA_DIGEST_LENGTH];
98         SHA1_Final (byte_buffer, &sha);
99         char digest[64];
100         return Kumu::base64encode (byte_buffer, SHA_DIGEST_LENGTH, digest, 64);
101 }
102
103 /** Create a digest for a file.
104  *  @param filename File name.
105  *  @param progress Optional progress reporting function.  The function will be called
106  *  with a progress value between 0 and 1.
107  *  @return Digest.
108  */
109 string
110 dcp::make_digest (boost::filesystem::path filename, function<void (float)> progress)
111 {
112         Kumu::FileReader reader;
113         Kumu::Result_t r = reader.OpenRead (filename.string().c_str ());
114         if (ASDCP_FAILURE (r)) {
115                 boost::throw_exception (FileError ("could not open file to compute digest", filename, r));
116         }
117
118         SHA_CTX sha;
119         SHA1_Init (&sha);
120
121         int const buffer_size = 65536;
122         Kumu::ByteString read_buffer (buffer_size);
123
124         Kumu::fsize_t done = 0;
125         Kumu::fsize_t const size = reader.Size ();
126         while (1) {
127                 ui32_t read = 0;
128                 Kumu::Result_t r = reader.Read (read_buffer.Data(), read_buffer.Capacity(), &read);
129
130                 if (r == Kumu::RESULT_ENDOFFILE) {
131                         break;
132                 } else if (ASDCP_FAILURE (r)) {
133                         boost::throw_exception (FileError ("could not read file to compute digest", filename, r));
134                 }
135
136                 SHA1_Update (&sha, read_buffer.Data(), read);
137
138                 if (progress) {
139                         progress (float (done) / size);
140                         done += read;
141                 }
142         }
143
144         byte_t byte_buffer[SHA_DIGEST_LENGTH];
145         SHA1_Final (byte_buffer, &sha);
146
147         char digest[64];
148         return Kumu::base64encode (byte_buffer, SHA_DIGEST_LENGTH, digest, 64);
149 }
150
151 /** @param s A string.
152  *  @return true if the string contains only space, newline or tab characters, or is empty.
153  */
154 bool
155 dcp::empty_or_white_space (string s)
156 {
157         for (size_t i = 0; i < s.length(); ++i) {
158                 if (s[i] != ' ' && s[i] != '\n' && s[i] != '\t') {
159                         return false;
160                 }
161         }
162
163         return true;
164 }
165
166 /** Set up various bits that the library needs.  Should be called one
167  *  by client applications.
168  */
169 void
170 dcp::init ()
171 {
172         if (xmlSecInit() < 0) {
173                 throw MiscError ("could not initialise xmlsec");
174         }
175
176 #ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
177         if (xmlSecCryptoDLLoadLibrary(BAD_CAST XMLSEC_CRYPTO) < 0) {
178                 throw MiscError ("unable to load default xmlsec-crypto library");
179         }
180 #endif
181
182         if (xmlSecCryptoAppInit(0) < 0) {
183                 throw MiscError ("could not initialise crypto");
184         }
185
186         if (xmlSecCryptoInit() < 0) {
187                 throw MiscError ("could not initialise xmlsec-crypto");
188         }
189
190         OpenSSL_add_all_algorithms();
191 }
192
193 /** Decode a base64 string.  The base64 decode routine in KM_util.cpp
194  *  gives different values to both this and the command-line base64
195  *  for some inputs.  Not sure why.
196  *
197  *  @param in base64-encoded string.
198  *  @param out Output buffer.
199  *  @param out_length Length of output buffer.
200  *  @return Number of characters written to the output buffer.
201  */
202 int
203 dcp::base64_decode (string const & in, unsigned char* out, int out_length)
204 {
205         BIO* b64 = BIO_new (BIO_f_base64 ());
206
207         /* This means the input should have no newlines */
208         BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
209
210         /* Copy our input string, removing newlines */
211         char in_buffer[in.size() + 1];
212         char* p = in_buffer;
213         for (size_t i = 0; i < in.size(); ++i) {
214                 if (in[i] != '\n' && in[i] != '\r') {
215                         *p++ = in[i];
216                 }
217         }
218
219         BIO* bmem = BIO_new_mem_buf (in_buffer, p - in_buffer);
220         bmem = BIO_push (b64, bmem);
221         int const N = BIO_read (bmem, out, out_length);
222         BIO_free_all (bmem);
223
224         return N;
225 }
226
227 /** @param p Path to open.
228  *  @param t mode flags, as for fopen(3).
229  *  @return FILE pointer or 0 on error.
230  *
231  *  Apparently there is no way to create an ofstream using a UTF-8
232  *  filename under Windows.  We are hence reduced to using fopen
233  *  with this wrapper.
234  */
235 FILE *
236 dcp::fopen_boost (boost::filesystem::path p, string t)
237 {
238 #ifdef LIBDCP_WINDOWS
239         wstring w (t.begin(), t.end());
240         /* c_str() here should give a UTF-16 string */
241         return _wfopen (p.c_str(), w.c_str ());
242 #else
243         return fopen (p.c_str(), t.c_str ());
244 #endif
245 }
246
247 optional<boost::filesystem::path>
248 dcp::relative_to_root (boost::filesystem::path root, boost::filesystem::path file)
249 {
250         boost::filesystem::path::const_iterator i = root.begin ();
251         boost::filesystem::path::const_iterator j = file.begin ();
252
253         while (i != root.end() && j != file.end() && *i == *j) {
254                 ++i;
255                 ++j;
256         }
257
258         if (i != root.end ()) {
259                 return optional<boost::filesystem::path> ();
260         }
261
262         boost::filesystem::path rel;
263         while (j != file.end ()) {
264                 rel /= *j++;
265         }
266
267         return rel;
268 }
269
270 bool
271 dcp::ids_equal (string a, string b)
272 {
273         transform (a.begin(), a.end(), a.begin(), ::tolower);
274         transform (b.begin(), b.end(), b.begin(), ::tolower);
275         trim (a);
276         trim (b);
277         return a == b;
278 }
279
280 string
281 dcp::file_to_string (boost::filesystem::path p, uintmax_t max_length)
282 {
283         uintmax_t len = boost::filesystem::file_size (p);
284         if (len > max_length) {
285                 throw MiscError (String::compose ("Unexpectedly long file (%1)", p.string()));
286         }
287
288         FILE* f = fopen_boost (p, "r");
289         if (!f) {
290                 throw FileError ("could not open file", p, errno);
291         }
292
293         char* c = new char[len];
294         /* This may read less than `len' if we are on Windows and we have CRLF in the file */
295         int const N = fread (c, 1, len, f);
296         fclose (f);
297
298         string s (c, N);
299         delete[] c;
300
301         return s;
302 }
303
304 /** @param key RSA private key in PEM format (optionally with -----BEGIN... / -----END...)
305  *  @return SHA1 fingerprint of key
306  */
307 string
308 dcp::private_key_fingerprint (string key)
309 {
310         boost::replace_all (key, "-----BEGIN RSA PRIVATE KEY-----\n", "");
311         boost::replace_all (key, "\n-----END RSA PRIVATE KEY-----\n", "");
312
313         unsigned char buffer[4096];
314         int const N = base64_decode (key, buffer, sizeof (buffer));
315
316         SHA_CTX sha;
317         SHA1_Init (&sha);
318         SHA1_Update (&sha, buffer, N);
319         uint8_t digest[20];
320         SHA1_Final (digest, &sha);
321
322         char digest_base64[64];
323         return Kumu::base64encode (digest, 20, digest_base64, 64);
324 }
325
326 xmlpp::Node *
327 dcp::find_child (xmlpp::Node const * node, string name)
328 {
329         xmlpp::Node::NodeList c = node->get_children ();
330         xmlpp::Node::NodeList::iterator i = c.begin();
331         while (i != c.end() && (*i)->get_name() != name) {
332                 ++i;
333         }
334
335         DCP_ASSERT (i != c.end ());
336         return *i;
337 }
338
339 string
340 dcp::remove_urn_uuid (string raw)
341 {
342         DCP_ASSERT (raw.substr(0, 9) == "urn:uuid:");
343         return raw.substr (9);
344 }
345
346 string
347 dcp::openjpeg_version ()
348 {
349         return opj_version ();
350 }